import { createTwoFilesPatch } from "diff";
import z from "zod";
import { createToolFactory, ToolCategory, ToolExecutionError, } from "../tools2/types.js";
import { markSdkApiAsUntestedIfNeeded } from "./apis/api-testing-state.js";
import { replace } from "./build-edit-file.js";
import { debugCache, createTypeScriptCheckFunction } from "./debug-cache.js";
function trimDiff(diff) {
    const lines = diff.split("\n");
    const contentLines = lines.filter((line) => (line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) &&
        !line.startsWith("---") &&
        !line.startsWith("+++"));
    if (contentLines.length === 0)
        return diff;
    let min = Infinity;
    for (const line of contentLines) {
        const content = line.slice(1);
        if (content.trim().length > 0) {
            const match = content.match(/^(\s*)/);
            if (match?.[1])
                min = Math.min(min, match[1].length);
        }
    }
    if (min === Infinity || min === 0)
        return diff;
    const trimmedLines = lines.map((line) => {
        if ((line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) &&
            !line.startsWith("---") &&
            !line.startsWith("+++")) {
            const prefix = line[0] ?? "";
            const content = line.slice(1);
            return prefix + content.slice(min);
        }
        return line;
    });
    return trimmedLines.join("\n");
}
export const buildMultiEditFileToolFactory = createToolFactory("build_multiEditFile", ({ clark, services, }) => {
    return {
        category: ToolCategory.FILE,
        description: `
      A tool for making multiple edits to a single file in one operation. Built on top of the Edit tool, it allows efficient find-and-replace operations across a file.

      Before using this tool:
      1. Use the Read tool to understand the file's contents and context
      2. Verify the directory path is correct

      To make multiple file edits, provide the following:
      1. filePath: The relative path to the file to modify (must be relative, not absolute)
      2. edits: An array of edit operations to perform, where each edit contains:
         - oldString: The text to replace (must match the file contents exactly, including all whitespace and indentation)
         - newString: The edited text to replace the oldString
         - replaceAll: Replace all occurrences of oldString. This parameter is optional and defaults to false.

      IMPORTANT:
      - All edits are applied in sequence, in the order they are provided
      - Each edit operates on the result of the previous edit
      - All edits must be valid for the operation to succeed - if any edit fails, none will be applied
      - This tool is ideal when you need to make several changes to different parts of the same file

      CRITICAL REQUIREMENTS:
      1. All edits follow the same requirements as the single Edit tool
      2. The edits are atomic - either all succeed or none are applied
      3. Plan your edits carefully to avoid conflicts between sequential operations

      WARNING:
      - The tool will fail if edits.oldString doesn't match the file contents exactly (including whitespace)
      - The tool will fail if edits.oldString and edits.new String are the same
      - Since edits are applied in sequence, ensure that earlier edits don't affect the text that later edits are trying to find

      When making edits:
      - Ensure all edits result in idiomatic, correct code
      - Do not leave the code in a broken state
      - Always use relative file paths
      - Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
      - Use replaceAll for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.

      If you want to create a new file, use:
      - A new file path, including dir name if needed
      - First edit: empty oldString and the new file's contents as newString
      - Subsequent edits: normal edit operations on the created content

      DO NOT USE FOR API SOURCE CODE. Use the appropriate specialized tools instead.
    `,
        inputSchema: z.object({
            filePath: z
                .string()
                .describe("The relative path to the file to modify"),
            edits: z
                .array(z.object({
                oldString: z.string().describe("The text to replace"),
                newString: z.string().describe("The text to replace it with"),
                replaceAll: z
                    .boolean()
                    .optional()
                    .describe("Replace all occurrences of oldString (default false)."),
            }))
                .min(1)
                .describe("Array of edit operations to perform sequentially on the file"),
        }),
        execute: async ({ filePath, edits }) => {
            if (!filePath) {
                throw new Error("filePath is required");
            }
            // Prevent editing API source code - use writeApi + buildApi instead
            // Match apis/*.ts but not apis/*.d.ts (type definitions are allowed)
            if (/^apis\/[^/]+\.ts$/.test(filePath) && !filePath.endsWith(".d.ts")) {
                throw new ToolExecutionError(`Cannot edit API source code directly. Use the writeApi tool to modify "${filePath}", then buildApi to compile and validate it.`, {
                    toolName: "build_multiEditFile",
                    input: {},
                    context: {},
                    severity: "warn",
                });
            }
            if (!edits || edits.length === 0) {
                throw new Error("At least one edit operation is required");
            }
            // Validate all edits first
            for (const [index, edit] of edits.entries()) {
                if (edit.oldString === edit.newString) {
                    throw new ToolExecutionError(`Edit ${index + 1}: oldString and newString must be different`, { toolName: "", input: {}, context: {}, severity: "warn" });
                }
            }
            let contentCurrent = "";
            let contentOriginal = "";
            // Handle file creation if first edit has empty oldString
            const firstEdit = edits[0];
            if (firstEdit.oldString === "") {
                contentCurrent = firstEdit.newString;
                contentOriginal = "";
                // Apply remaining edits to the created content
                for (let i = 1; i < edits.length; i++) {
                    const edit = edits[i];
                    try {
                        contentCurrent = replace(contentCurrent, edit.oldString, edit.newString, edit.replaceAll ?? false);
                    }
                    catch (error) {
                        throw new Error(`Edit ${i + 1} failed: ${error instanceof Error ? error.message : String(error)}`);
                    }
                }
            }
            else {
                try {
                    contentCurrent = await services.appShell.readFile(filePath);
                    contentOriginal = contentCurrent;
                }
                catch {
                    throw new ToolExecutionError(`File ${filePath} not found`, {
                        toolName: "",
                        input: {},
                        context: {},
                        severity: "warn",
                    });
                }
                // Apply all edits sequentially
                for (let i = 0; i < edits.length; i++) {
                    const edit = edits[i];
                    try {
                        contentCurrent = replace(contentCurrent, edit.oldString, edit.newString, edit.replaceAll ?? false);
                    }
                    catch (error) {
                        throw new Error(`Edit ${i + 1} failed: ${error instanceof Error ? error.message : String(error)}`);
                    }
                }
            }
            // Generate diff between original and final content
            const diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOriginal, contentCurrent));
            await services.appShell.writeFile(filePath, contentCurrent);
            // Immediately invalidate TypeScript cache since we just modified files
            debugCache.invalidateTypeScript();
            // Update last modification time
            clark.updateContext({
                lastFileModificationTime: Date.now(),
                runtimeErrors: [],
            });
            debugCache.invalidateContextErrors();
            // When an SDK API file is created/edited, mark it as untested
            try {
                await markSdkApiAsUntestedIfNeeded(filePath, clark, services);
            }
            catch {
                // Best-effort bookkeeping should not fail a successful file write.
            }
            // Trigger eager TypeScript check after multi-edit (debounced to avoid excessive checks)
            const typeCheckFunction = createTypeScriptCheckFunction(services.appShell, clark.context.abortController?.signal);
            debugCache.startEagerTypeScriptCheck(typeCheckFunction);
            const result = {
                filePath,
                diff,
                editsApplied: edits.length,
                originalLength: contentOriginal.length,
                finalLength: contentCurrent.length,
                message: `Successfully applied ${edits.length} edit${edits.length === 1 ? "" : "s"} to ${filePath}`,
            };
            return result;
        },
        getSummary: (_input, output) => {
            const count = output.editsApplied;
            return {
                output: `${count} edit${count === 1 ? "" : "s"} applied`,
            };
        },
    };
});
//# sourceMappingURL=build-multi-edit-file.js.map