import { z } from "zod"
import { Tool } from "./tool"
import { App } from "../app/app"
import { Ripgrep } from "../external/ripgrep"

import DESCRIPTION from "./grep.txt"

export const GrepTool = Tool.define({
  id: "grep",
  description: DESCRIPTION,
  parameters: z.object({
    pattern: z
      .string()
      .describe("The regex pattern to search for in file contents"),
    path: z
      .string()
      .optional()
      .describe(
        "The directory to search in. Defaults to the current working directory.",
      ),
    include: z
      .string()
      .optional()
      .describe(
        'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")',
      ),
  }),
  async execute(params) {
    if (!params.pattern) {
      throw new Error("pattern is required")
    }

    const app = App.info()
    const searchPath = params.path || app.path.cwd

    const rgPath = await Ripgrep.filepath()
    const args = ["-n", params.pattern]
    if (params.include) {
      args.push("--glob", params.include)
    }
    args.push(searchPath)

    const proc = Bun.spawn([rgPath, ...args], {
      stdout: "pipe",
      stderr: "pipe",
    })

    const output = await new Response(proc.stdout).text()
    const errorOutput = await new Response(proc.stderr).text()
    const exitCode = await proc.exited

    if (exitCode === 1) {
      return {
        metadata: { matches: 0, truncated: false, title: params.pattern },
        output: "No files found",
      }
    }

    if (exitCode !== 0) {
      throw new Error(`ripgrep failed: ${errorOutput}`)
    }

    const lines = output.trim().split("\n")
    const matches = []

    for (const line of lines) {
      if (!line) continue

      const parts = line.split(":", 3)
      if (parts.length < 3) continue

      const filePath = parts[0]
      const lineNum = parseInt(parts[1], 10)
      const lineText = parts[2]

      const file = Bun.file(filePath)
      const stats = await file.stat().catch(() => null)
      if (!stats) continue

      matches.push({
        path: filePath,
        modTime: stats.mtime.getTime(),
        lineNum,
        lineText,
      })
    }

    matches.sort((a, b) => b.modTime - a.modTime)

    const limit = 100
    const truncated = matches.length > limit
    const finalMatches = truncated ? matches.slice(0, limit) : matches

    if (finalMatches.length === 0) {
      return {
        metadata: { matches: 0, truncated: false, title: params.pattern },
        output: "No files found",
      }
    }

    const outputLines = [`Found ${finalMatches.length} matches`]

    let currentFile = ""
    for (const match of finalMatches) {
      if (currentFile !== match.path) {
        if (currentFile !== "") {
          outputLines.push("")
        }
        currentFile = match.path
        outputLines.push(`${match.path}:`)
      }
      outputLines.push(`  Line ${match.lineNum}: ${match.lineText}`)
    }

    if (truncated) {
      outputLines.push("")
      outputLines.push(
        "(Results are truncated. Consider using a more specific path or pattern.)",
      )
    }

    return {
      metadata: {
        matches: finalMatches.length,
        truncated,
        title: params.pattern,
      },
      output: outputLines.join("\n"),
    }
  },
})
