import { spawn } from "child_process";
import { rgPath } from "../utils/ripgrep.js";
import { getDisplayPath } from "../utils/path.js";
import { GREP_TOOL_NAME, BASH_TOOL_NAME, AGENT_TOOL_NAME, } from "../constants/tools.js";
/**
 * Grep tool plugin - powerful search tool based on ripgrep
 */
export const grepTool = {
    name: GREP_TOOL_NAME,
    config: {
        type: "function",
        function: {
            name: GREP_TOOL_NAME,
            description: "A powerful search tool built on ripgrep",
            parameters: {
                type: "object",
                properties: {
                    pattern: {
                        type: "string",
                        description: "The regular expression pattern to search for in file contents",
                    },
                    path: {
                        type: "string",
                        description: "File or directory to search in (rg PATH). Defaults to current working directory.",
                    },
                    glob: {
                        type: "string",
                        description: 'Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}") - maps to rg --glob',
                    },
                    output_mode: {
                        type: "string",
                        enum: ["content", "files_with_matches", "count"],
                        description: 'Output mode: "content" shows matching lines (supports -A/-B/-C context, -n line numbers, head_limit), "files_with_matches" shows file paths (supports head_limit), "count" shows match counts (supports head_limit). Defaults to "files_with_matches".',
                    },
                    "-B": {
                        type: "number",
                        description: 'Number of lines to show before each match (rg -B). Requires output_mode: "content", ignored otherwise.',
                    },
                    "-A": {
                        type: "number",
                        description: 'Number of lines to show after each match (rg -A). Requires output_mode: "content", ignored otherwise.',
                    },
                    "-C": {
                        type: "number",
                        description: 'Number of lines to show before and after each match (rg -C). Requires output_mode: "content", ignored otherwise.',
                    },
                    "-n": {
                        type: "boolean",
                        description: 'Show line numbers in output (rg -n). Requires output_mode: "content", ignored otherwise. Defaults to true.',
                    },
                    "-i": {
                        type: "boolean",
                        description: "Case insensitive search (rg -i)",
                    },
                    type: {
                        type: "string",
                        description: "File type to search (rg --type). Common types: js, py, rust, go, java, etc. More efficient than include for standard file types.",
                    },
                    head_limit: {
                        type: "number",
                        description: 'Limit output to first N lines/entries, equivalent to "| head -N". Works across all output modes: content (limits output lines), files_with_matches (limits file paths), count (limits count entries). Defaults to 0 (unlimited).',
                    },
                    multiline: {
                        type: "boolean",
                        description: "Enable multiline mode where . matches newlines and patterns can span lines (rg -U --multiline-dotall). Default: false.",
                    },
                    offset: {
                        type: "number",
                        description: "The number of matches to skip.",
                    },
                    context: {
                        type: "number",
                        description: 'Alias for -C. Number of lines to show before and after each match. Requires output_mode: "content", ignored otherwise.',
                    },
                },
                required: ["pattern"],
            },
        },
    },
    prompt: () => `A powerful search tool built on ripgrep

  Usage:
  - ALWAYS use ${GREP_TOOL_NAME} for search tasks. NEVER invoke \`grep\` or \`rg\` as a ${BASH_TOOL_NAME} command. The ${GREP_TOOL_NAME} tool has been optimized for correct permissions and access.
  - Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
  - Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
  - Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
  - Use ${AGENT_TOOL_NAME} tool for open-ended searches requiring multiple rounds
  - Pattern syntax: Uses ripgrep (not grep) - literal braces need escaping (use \`interface\\{\\}\` to find \`interface{}\` in Go code)
  - Multiline matching: By default patterns match within single lines only. For cross-line patterns like \`struct \\{[\\s\\S]*?field\`, use \`multiline: true\`
`,
    execute: async (args, context) => {
        const pattern = args.pattern;
        const searchPath = args.path;
        const globPattern = args.glob;
        const outputMode = args.output_mode || "files_with_matches";
        const contextBefore = args["-B"];
        const contextAfter = args["-A"];
        const contextAround = args["-C"];
        const showLineNumbers = args["-n"] !== false;
        const caseInsensitive = args["-i"];
        const fileType = args.type;
        const headLimit = args.head_limit;
        const multiline = args.multiline;
        const offset = args.offset;
        const contextArg = args.context;
        if (!pattern || typeof pattern !== "string") {
            return {
                success: false,
                content: "",
                error: "pattern parameter is required and must be a string",
            };
        }
        if (!rgPath) {
            return {
                success: false,
                content: "",
                error: "ripgrep is not available. Please install @vscode/ripgrep package.",
            };
        }
        try {
            const workdir = context.workdir;
            const rgArgs = ["--color=never", "--max-columns=500"];
            // Set output mode
            if (outputMode === "files_with_matches") {
                rgArgs.push("-l");
            }
            else if (outputMode === "count") {
                rgArgs.push("-c");
            }
            // content mode is default, no special parameters needed
            // Add line numbers (only effective in content mode)
            if (showLineNumbers && outputMode === "content") {
                rgArgs.push("-n");
            }
            // Add file names (in content mode)
            if (outputMode === "content") {
                rgArgs.push("-H");
            }
            // Case insensitive
            if (caseInsensitive) {
                rgArgs.push("-i");
            }
            // Multiline mode
            if (multiline) {
                rgArgs.push("-U", "--multiline-dotall");
            }
            // Context lines (only effective in content mode)
            if (outputMode === "content") {
                const effectiveContext = contextArg ?? contextAround;
                if (effectiveContext) {
                    rgArgs.push("-C", effectiveContext.toString());
                }
                else {
                    if (contextBefore) {
                        rgArgs.push("-B", contextBefore.toString());
                    }
                    if (contextAfter) {
                        rgArgs.push("-A", contextAfter.toString());
                    }
                }
            }
            // File type filtering
            if (fileType) {
                rgArgs.push("--type", fileType);
            }
            // Glob pattern filtering
            if (globPattern) {
                rgArgs.push("--glob", globPattern);
            }
            // Add search pattern - use -e parameter to avoid patterns starting with - being mistaken as command line options
            rgArgs.push("-e", pattern);
            // Add search path
            if (searchPath) {
                rgArgs.push(searchPath);
            }
            else {
                rgArgs.push(".");
            }
            const result = await executeCommand(rgPath, rgArgs, workdir);
            if (result.error && result.exitCode !== 1) {
                // rg returns 1 for no matches, not an error
                return {
                    success: false,
                    content: "",
                    error: `ripgrep failed: ${result.stderr}`,
                };
            }
            const output = result.stdout.trim();
            if (!output) {
                return {
                    success: true,
                    content: "No matches found. Suggestion: specify the 'path' field to search in ignored or other directories (e.g., 'node_modules'), as the default search path is the current working directory and respects .gitignore.",
                    shortResult: "No matches found",
                    metadata: {
                        numMatches: 0,
                    },
                };
            }
            let lines = output.split("\n");
            const totalMatches = lines.length;
            // Apply offset
            const effectiveOffset = offset || 0;
            if (effectiveOffset > 0) {
                lines = lines.slice(effectiveOffset);
            }
            // Apply head_limit
            const effectiveHeadLimit = headLimit || 0;
            let truncated = false;
            if (effectiveHeadLimit > 0 && lines.length > effectiveHeadLimit) {
                lines = lines.slice(0, effectiveHeadLimit);
                truncated = true;
            }
            const finalOutput = lines.join("\n");
            // Generate short result
            let shortResult;
            const numMatches = lines.length;
            if (outputMode === "files_with_matches") {
                shortResult = `Found ${numMatches} file${numMatches === 1 ? "" : "s"}`;
            }
            else if (outputMode === "count") {
                shortResult = `Match counts for ${numMatches} file${numMatches === 1 ? "" : "s"}`;
            }
            else {
                shortResult = `Found ${numMatches} matching line${numMatches === 1 ? "" : "s"}`;
            }
            if (effectiveOffset > 0 || truncated) {
                shortResult += ` (showing ${numMatches} of ${totalMatches})`;
            }
            return {
                success: true,
                content: finalOutput,
                shortResult,
                metadata: {
                    numMatches: totalMatches,
                    truncated,
                    appliedLimit: effectiveHeadLimit,
                    appliedOffset: effectiveOffset,
                },
            };
        }
        catch (error) {
            return {
                success: false,
                content: "",
                error: `Search failed: ${error instanceof Error ? error.message : String(error)}`,
            };
        }
    },
    formatCompactParams: (params, context) => {
        const pattern = params.pattern;
        const outputMode = params.output_mode;
        const fileType = params.type;
        const path = params.path;
        let result = pattern || "";
        if (fileType) {
            result += ` ${fileType}`;
        }
        if (path) {
            const displayPath = getDisplayPath(path, context.workdir);
            result += ` in ${displayPath}`;
        }
        if (outputMode && outputMode !== "files_with_matches") {
            result += ` [${outputMode}]`;
        }
        return result;
    },
};
/**
 * Execute command and return result
 */
function executeCommand(command, args, cwd) {
    return new Promise((resolve) => {
        const child = spawn(command, args, {
            cwd,
            stdio: ["ignore", "pipe", "pipe"],
        });
        let stdout = "";
        let stderr = "";
        child.stdout?.on("data", (data) => {
            stdout += data.toString();
        });
        child.stderr?.on("data", (data) => {
            stderr += data.toString();
        });
        child.on("close", (code) => {
            resolve({
                stdout,
                stderr,
                exitCode: code,
                error: code !== 0 && code !== 1, // rg returns 1 for no matches, not an error
            });
        });
        child.on("error", (err) => {
            resolve({
                stdout,
                stderr: err.message,
                exitCode: null,
                error: true,
            });
        });
    });
}
