/**
 * ============================================================
 *  src/tools/grep.ts —— Grep 工具（基于 ripgrep 的内容搜索）
 * ------------------------------------------------------------
 *  对应 kakadeai 主仓库的 GrepTool。
 *  实现策略：spawn 系统 `rg` 命令。
 *  没装 ripgrep 时给"教学型"错误：
 *    - macOS: brew install ripgrep
 *    - Windows: scoop install ripgrep
 *    - Ubuntu: sudo apt install ripgrep
 *
 *  为何不用纯 Node 实现？
 *    递归遍历 + 正则 + glob 排除 + 字符编码处理 = 几百行；
 *    最小化原则下复用 rg 子进程更划算。
 * ============================================================
 */
import { spawn } from 'node:child_process';
import { resolve } from 'node:path';
import { z } from 'zod';
import { getWorkingDir } from '../../bootstrap/workingDir.js';
import { resolveRgPath } from './rgPath.js';
import { DEFAULT_MAX_RESULT_SIZE_CHARS } from '../types.js';
import { toToolParameters } from '../../utils/zodToJson.js';
// ---------------- 1. Zod 输入 schema ----------------
const inputSchema = z.object({
    pattern: z.string().min(1).describe('正则表达式（ripgrep 兼容语法）'),
    path: z
        .string()
        .optional()
        .describe('搜索的根目录或文件（默认当前工作目录）'),
    glob: z
        .string()
        .optional()
        .describe('文件名 glob 过滤，如 "*.ts"'),
    type: z
        .string()
        .optional()
        .describe('rg 的文件类型快捷名，如 "py"、"rust"、"js"'),
    output_mode: z
        .enum(['content', 'files_with_matches', 'count'])
        .optional()
        .describe('输出模式：content=匹配行；files_with_matches=只列文件；count=每文件计数'),
    '-i': z.boolean().optional().describe('忽略大小写'),
    '-n': z.boolean().optional().describe('显示行号（仅 content 模式）'),
    '-A': z.number().int().min(0).optional().describe('匹配后展示几行上下文'),
    '-B': z.number().int().min(0).optional().describe('匹配前展示几行上下文'),
    '-C': z.number().int().min(0).optional().describe('匹配前后各展示几行（覆盖 -A/-B）'),
    head_limit: z
        .number()
        .int()
        .positive()
        .optional()
        .describe('输出最多保留前 N 行（防止结果过大）'),
});
// ---------------- 2. JSON Schema（由 Zod 自动派生） ----------------
const parameters = toToolParameters(inputSchema);
// ---------------- 3. Description（搬自 kakadeai/tools/GrepTool/prompt.ts） ----------------
const description = `A powerful search tool built on ripgrep.

Usage:
- ALWAYS use Grep for content search tasks. Do NOT invoke \`grep\` or \`rg\` directly via Bash.
- 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
- Pattern syntax: Uses ripgrep (not classic grep)`;
// ---------------- 4. call() ----------------
async function call(input, signal) {
    const args = [];
    // 输出模式（默认 files_with_matches）
    const mode = input.output_mode ?? 'files_with_matches';
    if (mode === 'files_with_matches')
        args.push('-l');
    else if (mode === 'count')
        args.push('--count-matches');
    // mode === 'content' 是 rg 默认行为，不加 flag
    if (input['-i'])
        args.push('-i');
    if (mode === 'content' && input['-n'])
        args.push('-n');
    // 上下文行数：-C 优先，否则各加 -A/-B
    if (typeof input['-C'] === 'number') {
        args.push('-C', String(input['-C']));
    }
    else {
        if (typeof input['-A'] === 'number')
            args.push('-A', String(input['-A']));
        if (typeof input['-B'] === 'number')
            args.push('-B', String(input['-B']));
    }
    if (input.glob)
        args.push('--glob', input.glob);
    if (input.type)
        args.push('--type', input.type);
    // 安全：限制单文件输出大小（防止超大行）
    args.push('--max-columns', '500');
    args.push('--max-columns-preview');
    // 按修改时间降序排列（新文件/新匹配在前，与 Glob 的旧在前形成互补锁定模板）
    args.push('--sort', 'modified');
    // pattern 必须用 -e 显式标记，否则以 - 开头的 pattern 会被当成 flag
    args.push('-e', input.pattern);
    // 搜索路径
    args.push(input.path ? resolve(input.path) : '.');
    // ---- 找 rg ----
    // 优先级：MINIMAL_AGENT_RIPGREP_PATH → 系统 PATH → Claude Code 自带的 rg
    const rgPath = await resolveRgPath();
    if (!rgPath) {
        return {
            ok: false,
            error: '找不到 ripgrep（rg）。可以三选一：\n' +
                '  ① 装 Claude Code（自带 rg）：npm i -g @anthropic-ai/claude-code\n' +
                '  ② 系统装 ripgrep：\n' +
                '     macOS:   brew install ripgrep\n' +
                '     Windows: scoop install ripgrep\n' +
                '     Ubuntu:  sudo apt install ripgrep\n' +
                '  ③ 在 .env 设置 MINIMAL_AGENT_RIPGREP_PATH=/绝对/路径/到/rg',
        };
    }
    // ---- spawn rg ----
    let stdout = '';
    let stderr = '';
    let exitCode = null;
    try {
        await new Promise((resolveP, rejectP) => {
            const child = spawn(rgPath, args, {
                cwd: getWorkingDir(),
                signal,
                windowsHide: true,
            });
            child.stdout.setEncoding('utf8');
            child.stderr.setEncoding('utf8');
            child.stdout.on('data', (chunk) => (stdout += chunk));
            child.stderr.on('data', (chunk) => (stderr += chunk));
            child.on('error', (e) => rejectP(e));
            child.on('close', (code) => {
                exitCode = code;
                resolveP();
            });
        });
    }
    catch (e) {
        const err = e;
        return { ok: false, error: `执行 rg 失败（${rgPath}）：${err.message}` };
    }
    // rg 的退出码语义：
    //   0 = 找到匹配
    //   1 = 没找到匹配（不算错）
    //   2 = 真正的错误
    if (exitCode === 1) {
        return { ok: true, content: `(no matches for pattern: ${input.pattern})` };
    }
    if (exitCode !== 0) {
        return {
            ok: false,
            error: `rg 退出码 ${exitCode}：${stderr.slice(0, 500) || '(无 stderr)'}`,
        };
    }
    // ---- head_limit + 大小兜底 ----
    let content = stdout;
    if (input.head_limit && input.head_limit > 0) {
        const lines = content.split('\n');
        if (lines.length > input.head_limit) {
            content =
                lines.slice(0, input.head_limit).join('\n') +
                    `\n... (已截断到 ${input.head_limit} 行 / 共 ${lines.length} 行，保留最新匹配)`;
        }
    }
    // 超长截断：保留头部（最新结果），丢弃尾部（旧结果）
    if (content.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
        content =
            content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) +
                `\n\n... (输出超过 ${DEFAULT_MAX_RESULT_SIZE_CHARS} 字符，已截断尾部旧结果)`;
    }
    return { ok: true, content };
}
// ---------------- 5. 导出 ----------------
export const grepTool = {
    name: 'Grep',
    description,
    inputSchema,
    parameters,
    isReadOnly: true,
    isConcurrencySafe: true,
    maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
    call,
};
