import { z } from "zod";
import * as fs from "fs";
import * as path from "path";
import { Tool } from "./tool";
import { LSP } from "../lsp";
import { FileTimes } from "./util/file-times";

const MAX_READ_SIZE = 250 * 1024;
const DEFAULT_READ_LIMIT = 2000;
const MAX_LINE_LENGTH = 2000;

const DESCRIPTION = `File viewing tool that reads and displays the contents of files with line numbers, allowing you to examine code, logs, or text data.

WHEN TO USE THIS TOOL:
- Use when you need to read the contents of a specific file
- Helpful for examining source code, configuration files, or log files
- Perfect for looking at text-based file formats

HOW TO USE:
- Provide the path to the file you want to view
- Optionally specify an offset to start reading from a specific line
- Optionally specify a limit to control how many lines are read

FEATURES:
- Displays file contents with line numbers for easy reference
- Can read from any position in a file using the offset parameter
- Handles large files by limiting the number of lines read
- Automatically truncates very long lines for better display
- Suggests similar file names when the requested file isn't found

LIMITATIONS:
- Maximum file size is 250KB
- Default reading limit is 2000 lines
- Lines longer than 2000 characters are truncated
- Cannot display binary files or images
- Images can be identified but not displayed

TIPS:
- Use with Glob tool to first find files you want to view
- For code exploration, first use Grep to find relevant files, then View to examine them
- When viewing large files, use the offset parameter to read specific sections`;

export const view = Tool.define({
  name: "view",
  description: DESCRIPTION,
  parameters: z.object({
    filePath: z.string().describe("The path to the file to read"),
    offset: z
      .number()
      .describe("The line number to start reading from (0-based)")
      .optional(),
    limit: z
      .number()
      .describe("The number of lines to read (defaults to 2000)")
      .optional(),
  }),
  async execute(params) {
    let filePath = params.filePath;
    if (!path.isAbsolute(filePath)) {
      filePath = path.join(process.cwd(), filePath);
    }

    const file = Bun.file(filePath);
    if (!(await file.exists())) {
      const dir = path.dirname(filePath);
      const base = path.basename(filePath);

      const dirEntries = fs.readdirSync(dir);
      const suggestions = dirEntries
        .filter(
          (entry) =>
            entry.toLowerCase().includes(base.toLowerCase()) ||
            base.toLowerCase().includes(entry.toLowerCase()),
        )
        .map((entry) => path.join(dir, entry))
        .slice(0, 3);

      if (suggestions.length > 0) {
        throw new Error(
          `File not found: ${filePath}\n\nDid you mean one of these?\n${suggestions.join("\n")}`,
        );
      }

      throw new Error(`File not found: ${filePath}`);
    }
    const stats = await file.stat();

    if (stats.size > MAX_READ_SIZE)
      throw new Error(
        `File is too large (${stats.size} bytes). Maximum size is ${MAX_READ_SIZE} bytes`,
      );
    const limit = params.limit ?? DEFAULT_READ_LIMIT;
    const offset = params.offset || 0;
    const isImage = isImageFile(filePath);
    if (isImage)
      throw new Error(
        `This is an image file of type: ${isImage}\nUse a different tool to process images`,
      );
    const lines = await file.text().then((text) => text.split("\n"));
    const content = lines.slice(offset, offset + limit).map((line, index) => {
      line =
        line.length > MAX_LINE_LENGTH
          ? line.substring(0, MAX_LINE_LENGTH) + "..."
          : line;
      return `${(index + offset + 1).toString().padStart(5, "0")}| ${line}`;
    });

    let output = "<file>\n";
    output += content.join("\n");

    if (lines.length > offset + content.length) {
      output += `\n\n(File has more lines. Use 'offset' parameter to read beyond line ${
        offset + content.length
      })`;
    }
    output += "\n</file>";

    // just warms the lsp client
    LSP.file(filePath);
    FileTimes.read(filePath);

    return {
      output,
    };
  },
});

function isImageFile(filePath: string): string | false {
  const ext = path.extname(filePath).toLowerCase();
  switch (ext) {
    case ".jpg":
    case ".jpeg":
      return "JPEG";
    case ".png":
      return "PNG";
    case ".gif":
      return "GIF";
    case ".bmp":
      return "BMP";
    case ".svg":
      return "SVG";
    case ".webp":
      return "WebP";
    default:
      return false;
  }
}
