import { spawn } from 'node:child_process';
import fs from 'node:fs/promises';
import fg from 'fast-glob';
import { z } from 'zod';
import { truncateOutput } from './types.js';
const schema = z.object({
    pattern: z.string().min(1),
    path: z.string().optional(),
    glob: z.string().optional(),
    ignore_case: z.boolean().optional(),
});
function runRipgrep(input, cwd) {
    return new Promise((resolve) => {
        const args = ['--line-number', '--no-heading', '--color', 'never', '--max-count', '500'];
        if (input.ignore_case)
            args.push('-i');
        if (input.glob)
            args.push('--glob', input.glob);
        args.push('-e', input.pattern, input.path ?? '.');
        const child = spawn('rg', args, { cwd });
        let out = '';
        child.stdout.on('data', (d) => (out += d.toString()));
        child.on('error', () => resolve(null)); // rg not installed -> JS fallback
        child.on('close', (code) => {
            if (code === 0)
                resolve(out);
            else if (code === 1)
                resolve('No matches found.');
            else
                resolve(null);
        });
    });
}
async function jsFallback(input, cwd) {
    let re;
    try {
        re = new RegExp(input.pattern, input.ignore_case ? 'i' : '');
    }
    catch (err) {
        return `error: invalid regex: ${err.message}`;
    }
    const files = await fg(input.glob ?? '**/*', {
        cwd: input.path ?? cwd,
        ignore: ['**/node_modules/**', '**/.git/**'],
        onlyFiles: true,
        absolute: true,
        suppressErrors: true,
    });
    const results = [];
    for (const file of files.slice(0, 5000)) {
        if (results.length >= 500)
            break;
        let text;
        try {
            text = await fs.readFile(file, 'utf8');
        }
        catch {
            continue;
        }
        if (text.includes('\u0000'))
            continue; // binary
        const lines = text.split('\n');
        for (let i = 0; i < lines.length && results.length < 500; i++) {
            if (re.test(lines[i]))
                results.push(`${file}:${i + 1}:${lines[i]}`);
        }
    }
    return results.length > 0 ? results.join('\n') : 'No matches found.';
}
export const grepTool = {
    name: 'grep',
    description: 'Search file contents for a regex pattern. Returns "file:line:content" matches (max 500). ' +
        'Optionally scope with path (directory) and glob (e.g. "*.ts").',
    schema,
    jsonSchema: {
        type: 'object',
        properties: {
            pattern: { type: 'string', description: 'Regex pattern to search for' },
            path: { type: 'string', description: 'Directory to search in (default: working directory)' },
            glob: { type: 'string', description: 'Filter files by glob, e.g. "*.tsx"' },
            ignore_case: { type: 'boolean', description: 'Case-insensitive search' },
        },
        required: ['pattern'],
    },
    readOnly: true,
    summarize: (i) => i.pattern,
    run: async (input, ctx) => {
        const viaRg = await runRipgrep(input, ctx.cwd);
        const out = viaRg ?? (await jsFallback(input, ctx.cwd));
        return truncateOutput(out.trimEnd() || 'No matches found.');
    },
};
