import { mkdir as fsMkdir, writeFile as fsWriteFile } from "fs/promises";
import { dirname } from "path";
import { Type } from "typebox";
import { getLanguageFromPath, highlightCode } from "../theme.js";
import { withFileMutationQueue } from "./file-mutation-queue.js";
import { resolveToCwd } from "./path-utils.js";
import { normalizeDisplayText, replaceTabs, shortenPath, str } from "./render-utils.js";
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
const writeSchema = Type.Object({
    path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
    content: Type.String({ description: "Content to write to the file" }),
});
const defaultWriteOperations = {
    writeFile: (path, content) => fsWriteFile(path, content, "utf-8"),
    mkdir: (dir) => fsMkdir(dir, { recursive: true }).then(() => { }),
};
function trimTrailingEmptyLines(lines) {
    let end = lines.length;
    while (end > 0 && lines[end - 1] === "") {
        end--;
    }
    return lines.slice(0, end);
}
function formatWriteCall(args, options, _theme) {
    const rawPath = str(args?.file_path ?? args?.path);
    const fileContent = str(args?.content);
    const path = rawPath !== null ? shortenPath(rawPath) : null;
    const pathDisplay = path === null ? "[invalid]" : path || "...";
    let text = `write ${pathDisplay}`;
    if (fileContent === null) {
        text += `\n\n[invalid content arg - expected string]`;
    }
    else if (fileContent) {
        const lang = rawPath ? getLanguageFromPath(rawPath) : undefined;
        const renderedLines = lang
            ? highlightCode(replaceTabs(normalizeDisplayText(fileContent)), lang)
            : normalizeDisplayText(fileContent).split("\n");
        const lines = trimTrailingEmptyLines(renderedLines);
        const totalLines = lines.length;
        const maxLines = options.expanded ? lines.length : 10;
        const displayLines = lines.slice(0, maxLines);
        const remaining = lines.length - maxLines;
        text += `\n\n${displayLines.join("\n")}`;
        if (remaining > 0) {
            text += `\n... (${remaining} more lines, ${totalLines} total)`;
        }
    }
    return text;
}
function formatWriteResult(result, _theme) {
    if (!result.isError) {
        return undefined;
    }
    const output = result.content
        .filter((c) => c.type === "text")
        .map((c) => c.text || "")
        .join("\n");
    if (!output) {
        return undefined;
    }
    return `\n${output}`;
}
export function createWriteToolDefinition(cwd, options) {
    const ops = options?.operations ?? defaultWriteOperations;
    return {
        name: "write",
        label: "write",
        description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
        promptSnippet: "Create or overwrite files",
        promptGuidelines: ["Use write only for new files or complete rewrites."],
        parameters: writeSchema,
        async execute(_toolCallId, { path, content }, signal, _onUpdate, _ctx) {
            const absolutePath = resolveToCwd(path, cwd);
            const dir = dirname(absolutePath);
            return withFileMutationQueue(absolutePath, () => new Promise((resolve, reject) => {
                if (signal?.aborted) {
                    reject(new Error("Operation aborted"));
                    return;
                }
                let aborted = false;
                const onAbort = () => {
                    aborted = true;
                    reject(new Error("Operation aborted"));
                };
                signal?.addEventListener("abort", onAbort, { once: true });
                (async () => {
                    try {
                        // Create parent directories if needed.
                        await ops.mkdir(dir);
                        if (aborted)
                            return;
                        // Write the file contents.
                        await ops.writeFile(absolutePath, content);
                        if (aborted)
                            return;
                        signal?.removeEventListener("abort", onAbort);
                        resolve({
                            content: [
                                { type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` },
                            ],
                            details: undefined,
                        });
                    }
                    catch (error) {
                        signal?.removeEventListener("abort", onAbort);
                        if (!aborted)
                            reject(error);
                    }
                })();
            }));
        },
    };
}
export function createWriteTool(cwd, options) {
    return wrapToolDefinition(createWriteToolDefinition(cwd, options));
}
