import { basename } from "node:path";
import { Editor, truncateToWidth, visibleWidth } from "@threadwell/tui";
/**
 * Custom editor that handles app-level keybindings for coding-agent.
 */
export class CustomEditor extends Editor {
    keybindings;
    actionHandlers = new Map();
    // Special handlers that can be dynamically replaced
    onEscape;
    onCtrlD;
    onPasteImage;
    /** Handler for extension-registered shortcuts. Returns true if handled. */
    onExtensionShortcut;
    imageAttachments = new Map();
    submittedImageAttachments = [];
    nextImageAttachmentId = 1;
    constructor(tui, theme, keybindings, options) {
        super(tui, theme, options);
        this.keybindings = keybindings;
    }
    /**
     * Register a handler for an app action.
     */
    onAction(action, handler) {
        this.actionHandlers.set(action, handler);
    }
    insertImageAttachment(path, mimeType) {
        const id = this.nextImageAttachmentId++;
        this.imageAttachments.set(id, { id, path, mimeType });
        this.insertTextAtCursor(`[${id}]`);
    }
    setText(text) {
        super.setText(text);
        if (text.length === 0) {
            this.imageAttachments.clear();
            this.nextImageAttachmentId = 1;
        }
    }
    render(width) {
        const active = this.getActiveImageAttachments();
        const editorLines = super.render(width);
        if (active.length === 0 || editorLines.length < 2) {
            return editorLines;
        }
        const innerWidth = Math.max(1, width - 2);
        const chips = active.map((attachment) => this.renderImageChip(attachment)).join(" ");
        const content = truncateToWidth(chips, innerWidth, "…", true);
        const padding = " ".repeat(Math.max(0, innerWidth - visibleWidth(content)));
        const chipLine = `${this.borderColor("│")}${content}${padding}${this.borderColor("│")}`;
        return [editorLines[0], chipLine, ...editorLines.slice(1)];
    }
    prepareSubmitText(text) {
        const base = super.prepareSubmitText(text);
        const active = this.getActiveImageAttachments(base);
        this.submittedImageAttachments = active;
        if (active.length === 0) {
            return base;
        }
        let result = base;
        for (const attachment of active) {
            const referencePattern = new RegExp(`\\[${attachment.id}\\]`, "g");
            result = result.replace(referencePattern, attachment.path);
        }
        return result.trim();
    }
    afterSubmit() {
        this.imageAttachments.clear();
        this.nextImageAttachmentId = 1;
    }
    prepareExternalSubmitText() {
        return this.prepareSubmitText(this.getText());
    }
    consumeSubmittedImageAttachments() {
        const attachments = this.submittedImageAttachments;
        this.submittedImageAttachments = [];
        return attachments;
    }
    renderImageChip(attachment) {
        const subtype = attachment.mimeType.split("/")[1] ?? "image";
        const name = basename(attachment.path).replace(/\.[^.]*$/u, "");
        const shortName = name.length > 5 ? `…${name.slice(-5)}` : name;
        return this.borderColor(`[image${attachment.id}/${subtype} ${shortName}]`);
    }
    getActiveImageAttachments(text = this.getText()) {
        const refs = new Set();
        for (const match of text.matchAll(/\[(\d+)\]/g)) {
            const id = Number.parseInt(match[1], 10);
            if (Number.isFinite(id))
                refs.add(id);
        }
        return [...this.imageAttachments.values()].filter((attachment) => refs.has(attachment.id));
    }
    handleInput(data) {
        // Check extension-registered shortcuts first
        if (this.onExtensionShortcut?.(data)) {
            return;
        }
        // Check for paste image keybinding
        if (this.keybindings.matches(data, "app.clipboard.pasteImage")) {
            this.onPasteImage?.();
            return;
        }
        // Check app keybindings first
        // Escape/interrupt - only if autocomplete is NOT active
        if (this.keybindings.matches(data, "app.interrupt")) {
            if (!this.isShowingAutocomplete()) {
                // Use dynamic onEscape if set, otherwise registered handler
                const handler = this.onEscape ?? this.actionHandlers.get("app.interrupt");
                if (handler) {
                    handler();
                    return;
                }
            }
            // Let parent handle escape for autocomplete cancellation
            super.handleInput(data);
            return;
        }
        // Exit (Ctrl+D) - only when editor is empty
        if (this.keybindings.matches(data, "app.exit")) {
            if (this.getText().length === 0) {
                const handler = this.onCtrlD ?? this.actionHandlers.get("app.exit");
                if (handler)
                    handler();
                return;
            }
            // Fall through to editor handling for delete-char-forward when not empty
        }
        // Check all other app actions
        for (const [action, handler] of this.actionHandlers) {
            if (action !== "app.interrupt" && action !== "app.exit" && this.keybindings.matches(data, action)) {
                handler();
                return;
            }
        }
        // Pass to parent for editor handling
        super.handleInput(data);
    }
}
//# sourceMappingURL=custom-editor.js.map