import fs from 'node:fs/promises';
import { z } from 'zod';
import { resolveInCwd } from './types.js';
const schema = z.object({
    path: z.string().min(1),
    old_string: z.string().min(1),
    new_string: z.string(),
    replace_all: z.boolean().optional(),
});
function countOccurrences(haystack, needle) {
    let count = 0;
    let idx = 0;
    while ((idx = haystack.indexOf(needle, idx)) !== -1) {
        count++;
        idx += needle.length;
    }
    return count;
}
/**
 * Whitespace-tolerant fallback: build a regex from old_string where every run
 * of whitespace matches any run of whitespace. Rescues the common case where
 * the model got indentation slightly wrong. Only applied when it matches
 * exactly once, so it can never make an ambiguous edit.
 */
function fuzzyFind(content, oldString) {
    const escaped = oldString
        .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
        .replace(/\s+/g, '\\s+');
    let re;
    try {
        re = new RegExp(escaped, 'g');
    }
    catch {
        return null;
    }
    const matches = [...content.matchAll(re)];
    if (matches.length !== 1)
        return null;
    const m = matches[0];
    return { start: m.index, end: m.index + m[0].length };
}
export const editTool = {
    name: 'edit_file',
    description: 'Replace an exact string in a file. old_string must match the file contents exactly ' +
        '(including whitespace) and must be unique unless replace_all is true. ' +
        'Include enough surrounding lines to make it unique. Read the file first.',
    schema,
    jsonSchema: {
        type: 'object',
        properties: {
            path: { type: 'string', description: 'File path (absolute or relative to cwd)' },
            old_string: { type: 'string', description: 'Exact text to replace' },
            new_string: { type: 'string', description: 'Replacement text' },
            replace_all: { type: 'boolean', description: 'Replace every occurrence (default false)' },
        },
        required: ['path', 'old_string', 'new_string'],
    },
    readOnly: false,
    summarize: (i) => i.path,
    run: async (input, ctx) => {
        const file = resolveInCwd(ctx.cwd, input.path);
        const content = await fs.readFile(file, 'utf8');
        if (input.old_string === input.new_string) {
            return 'error: old_string and new_string are identical';
        }
        const occurrences = countOccurrences(content, input.old_string);
        if (input.replace_all) {
            if (occurrences === 0)
                return `error: old_string not found in ${input.path}`;
            const updated = content.split(input.old_string).join(input.new_string);
            await fs.writeFile(file, updated, 'utf8');
            return `Replaced ${occurrences} occurrence(s) in ${input.path}`;
        }
        if (occurrences === 1) {
            const updated = content.replace(input.old_string, input.new_string);
            await fs.writeFile(file, updated, 'utf8');
            return `Edited ${input.path}`;
        }
        if (occurrences > 1) {
            return `error: old_string appears ${occurrences} times in ${input.path}; add more context to make it unique or pass replace_all`;
        }
        // Not found exactly; try the whitespace-tolerant fallback.
        const fuzzy = fuzzyFind(content, input.old_string);
        if (fuzzy) {
            const updated = content.slice(0, fuzzy.start) + input.new_string + content.slice(fuzzy.end);
            await fs.writeFile(file, updated, 'utf8');
            return `Edited ${input.path} (whitespace-tolerant match)`;
        }
        return `error: old_string not found in ${input.path}; re-read the file and try again with the exact current text`;
    },
};
