// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: GPL-3.0-or-later

import { spawnSync } from "node:child_process";
import { relative } from "node:path";
import { Type } from "@sinclair/typebox";
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { resolveToCwd, ensureWorkspacePath } from "./path-utils.js";
import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "../../util/truncate.js";
import { ToolInputError } from "../../util/errors.js";

const DEFAULT_LIMIT = 1000;

const FindSchema = Type.Object({
  pattern: Type.String({ description: "Glob pattern to match files, e.g. '*.ts', '**/*.json'" }),
  path: Type.Optional(Type.String({ description: "Directory to search in (default: current directory)" })),
  limit: Type.Optional(Type.Number({ description: "Maximum number of results (default: 1000)" })),
});

export const createFindTool = (workspacePath: string): AgentTool => {
  const fdResult = spawnSync("which", ["fd"], { encoding: "utf-8" });
  const fdPath = fdResult.stdout?.trim();
  if (!fdPath) throw new ToolInputError("find requires fd to be installed");

  return {
    name: "find",
    label: "Find Files",
    description: `Search for files matching a glob pattern. Returns up to ${DEFAULT_LIMIT} results and ${DEFAULT_MAX_BYTES / 1024}KB of output. Respects .gitignore.`,
    parameters: FindSchema as any,
    execute: async (_toolCallId: string, params: any) => {
      const searchDir: string = params.path || ".";
      const effectiveLimit = params.limit ?? DEFAULT_LIMIT;
      const searchPath = resolveToCwd(searchDir, workspacePath);
      ensureWorkspacePath(workspacePath, searchPath);

    const args = [
      "--glob",
      "--color=never",
      "--hidden",
      "--max-results",
      String(effectiveLimit),
      params.pattern,
      searchPath,
    ];

    const result = spawnSync(fdPath, args, {
      encoding: "utf-8",
      maxBuffer: 10 * 1024 * 1024,
    });

    const rawOutput = result.stdout?.trim() ?? "";
    if (!rawOutput) {
      return {
        content: [{ type: "text", text: "No files found matching pattern" }],
        details: { pattern: params.pattern, path: searchDir, matches: 0 },
      };
    }

    const lines = rawOutput.split("\n").map((line) => {
      const isDir = line.endsWith("/");
      const rel = relative(searchPath, line);
      return isDir && !rel.endsWith("/") ? `${rel}/` : rel;
    });

    const relativized = lines.join("\n");
    const truncated = truncateHead(relativized, { maxLines: Number.MAX_SAFE_INTEGER });

    const notices: string[] = [];
    if (lines.length >= effectiveLimit) {
      notices.push(`Result limit reached (${effectiveLimit}). Narrow your pattern for complete results.`);
    }
    if (truncated.truncatedBy === "bytes") {
      const droppedBytes = truncated.totalBytes - truncated.outputBytes;
      notices.push(`Output truncated by ${formatSize(droppedBytes)} to fit ${formatSize(DEFAULT_MAX_BYTES)} limit.`);
    }

    const output = notices.length > 0
      ? `${truncated.content}\n\n${notices.join("\n")}`
      : truncated.content;

    return {
      content: [{ type: "text", text: output }],
      details: {
        pattern: params.pattern,
        path: searchDir,
        matches: lines.length,
        ...(truncated.truncated && { truncatedBy: truncated.truncatedBy }),
      },
    };
    },
  };
};
