import { readFile, writeFile } from "fs/promises";
import { logger } from "../utils/globalLogger.js";
import { resolvePath, getDisplayPath } from "../utils/path.js";
import { escapeRegExp, analyzeEditMismatch } from "../utils/editUtils.js";
import { EDIT_TOOL_NAME, READ_TOOL_NAME } from "../constants/tools.js";
/**
 * Format compact parameter display
 */
function formatCompactParams(args, context) {
    const filePath = args.file_path;
    return getDisplayPath(filePath || "", context.workdir);
}
/**
 * Single file edit tool plugin
 */
export const editTool = {
    name: EDIT_TOOL_NAME,
    formatCompactParams,
    prompt: () => `Performs exact string replacements in files.

Usage:
- You must use your \`${READ_TOOL_NAME}\` tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file. 
- When editing text from ${READ_TOOL_NAME} tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + tab. Everything after that tab is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string.
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
- The edit will FAIL if \`old_string\` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use \`replace_all\` to change every instance of \`old_string\`. 
- Use \`replace_all\` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.`,
    config: {
        type: "function",
        function: {
            name: EDIT_TOOL_NAME,
            description: "A tool for editing files",
            parameters: {
                type: "object",
                properties: {
                    file_path: {
                        type: "string",
                        description: "The absolute path to the file to modify",
                    },
                    old_string: {
                        type: "string",
                        description: "The text to replace",
                    },
                    new_string: {
                        type: "string",
                        description: "The text to replace it with (must be different from old_string)",
                    },
                    replace_all: {
                        type: "boolean",
                        default: false,
                        description: "Replace all occurences of old_string (default false)",
                    },
                },
                required: ["file_path", "old_string", "new_string"],
                additionalProperties: false,
            },
        },
    },
    execute: async (args, context) => {
        const filePath = args.file_path;
        const oldString = args.old_string;
        const newString = args.new_string;
        const replaceAll = args.replace_all || false;
        // Validate required parameters
        if (!filePath || typeof filePath !== "string") {
            return {
                success: false,
                content: "",
                error: "file_path parameter is required and must be a string",
            };
        }
        if (typeof oldString !== "string") {
            return {
                success: false,
                content: "",
                error: "old_string parameter is required and must be a string",
            };
        }
        if (typeof newString !== "string") {
            return {
                success: false,
                content: "",
                error: "new_string parameter is required and must be a string",
            };
        }
        if (oldString === newString) {
            return {
                success: false,
                content: "",
                error: "old_string and new_string must be different",
            };
        }
        // Touch file to track it in context
        context.messageManager?.touchFile(filePath);
        try {
            const resolvedPath = resolvePath(filePath, context.workdir);
            // Read file content
            let originalContent;
            try {
                originalContent = await readFile(resolvedPath, "utf-8");
            }
            catch (readError) {
                return {
                    success: false,
                    content: "",
                    error: `Failed to read file: ${readError instanceof Error ? readError.message : String(readError)}`,
                };
            }
            // Check if old_string exists
            const index = originalContent.indexOf(oldString);
            const matchedOldString = index !== -1 ? oldString : null;
            const startLineNumber = index !== -1
                ? originalContent.substring(0, index).split("\n").length
                : undefined;
            if (!matchedOldString) {
                return {
                    success: false,
                    content: "",
                    error: analyzeEditMismatch(originalContent, oldString),
                };
            }
            let newContent;
            let replacementCount;
            if (replaceAll) {
                // Replace all matches
                const regex = new RegExp(escapeRegExp(matchedOldString), "g");
                newContent = originalContent.replace(regex, newString);
                replacementCount = (originalContent.match(regex) || []).length;
            }
            else {
                // Replace only the first match, but first check if it's unique
                const matches = originalContent.split(matchedOldString).length - 1;
                if (matches > 1) {
                    return {
                        success: false,
                        content: "",
                        error: `old_string appears ${matches} times in the file. Either provide a larger string with more surrounding context to make it unique or use replace_all=true to change every instance.`,
                    };
                }
                newContent = originalContent.replace(matchedOldString, newString);
                replacementCount = 1;
            }
            // Permission check after validation but before real operation
            if (context.permissionManager) {
                try {
                    const permissionContext = context.permissionManager.createContext(EDIT_TOOL_NAME, context.permissionMode || "default", context.canUseToolCallback, {
                        file_path: filePath,
                        old_string: oldString,
                        new_string: newString,
                        replace_all: replaceAll,
                        startLineNumber,
                    }, context.toolCallId);
                    const permissionResult = await context.permissionManager.checkPermission(permissionContext);
                    if (permissionResult.behavior === "deny") {
                        return {
                            success: false,
                            content: "",
                            error: `${EDIT_TOOL_NAME} operation denied by user, reason: ${permissionResult.message || "No reason provided"}`,
                        };
                    }
                }
                catch {
                    return {
                        success: false,
                        content: "",
                        error: "Permission check failed",
                    };
                }
            }
            // Record snapshot for reversion
            let snapshotId;
            if (context.reversionManager && context.messageId) {
                snapshotId = await context.reversionManager.recordSnapshot(context.messageId, resolvedPath, "modify");
            }
            // Write file
            try {
                await writeFile(resolvedPath, newContent, "utf-8");
                // Commit snapshot on success
                if (context.reversionManager && snapshotId) {
                    await context.reversionManager.commitSnapshot(snapshotId);
                }
            }
            catch (writeError) {
                return {
                    success: false,
                    content: "",
                    error: `Failed to write file: ${writeError instanceof Error ? writeError.message : String(writeError)}`,
                };
            }
            const shortResult = replaceAll
                ? `Replaced ${replacementCount} instances`
                : "Text replaced successfully";
            logger.debug(`Edit tool: ${shortResult}`);
            return {
                success: true,
                content: shortResult,
                shortResult,
                filePath: resolvedPath,
                startLineNumber,
            };
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            logger.error(`Edit tool error: ${errorMessage}`);
            return {
                success: false,
                content: "",
                error: errorMessage,
            };
        }
    },
};
