/**
 * Ghost text autocomplete — shows AI suggestions as translucent overlay at cursor.
 * Tab accepts, Escape dismisses, any other key dismisses and re-triggers after debounce.
 * Editor-agnostic: works with both Quill and tiptap via MailxEditor interface.
 */
import { autocomplete } from "../lib/api-client.js";
let ghostEl = null;
let currentSuggestion = null;
let debounceTimer = null;
let abortController = null;
let activeEditor = null;
let debounceMs = 600;
function dismiss() {
    currentSuggestion = null;
    if (ghostEl) {
        ghostEl.remove();
        ghostEl = null;
    }
}
function positionGhost(editor, text) {
    dismiss();
    const sel = window.getSelection();
    if (!sel || sel.rangeCount === 0 || !sel.isCollapsed)
        return;
    const range = sel.getRangeAt(0);
    let rect = range.getBoundingClientRect();
    // Collapsed range may return zero-width rect — use a temp span to measure
    if (rect.width === 0 && rect.height === 0) {
        const span = document.createElement("span");
        span.textContent = "\u200B"; // zero-width space
        range.insertNode(span);
        rect = span.getBoundingClientRect();
        span.remove();
        // Restore selection
        sel.removeAllRanges();
        sel.addRange(range);
    }
    const container = editor.getScrollContainer();
    const containerRect = container.getBoundingClientRect();
    ghostEl = document.createElement("span");
    ghostEl.className = "ghost-text";
    ghostEl.textContent = text;
    ghostEl.style.top = `${rect.top - containerRect.top + container.scrollTop}px`;
    ghostEl.style.left = `${rect.left - containerRect.left + container.scrollLeft}px`;
    container.appendChild(ghostEl);
    currentSuggestion = text;
}
function requestSuggestion(editor, context) {
    // Cancel any in-flight request
    if (abortController) {
        abortController.abort();
        abortController = null;
    }
    const bodyText = editor.getText();
    if (!bodyText || bodyText.trim().length < 3)
        return; // need at least a few characters
    abortController = new AbortController();
    const signal = abortController.signal;
    autocomplete({
        subject: context.getSubject(),
        to: context.getTo(),
        bodyText,
        cursorOffset: bodyText.length,
    }, signal).then((result) => {
        if (signal.aborted)
            return;
        if (result.suggestion) {
            positionGhost(editor, result.suggestion);
        }
    }).catch((e) => {
        if (e.name === "AbortError")
            return;
        // Silently ignore autocomplete errors
    });
}
export function initGhostText(editor, context, options) {
    activeEditor = editor;
    if (options?.debounceMs)
        debounceMs = options.debounceMs;
    // Debounced content change → request suggestion
    editor.onContentChange(() => {
        dismiss();
        if (debounceTimer)
            clearTimeout(debounceTimer);
        debounceTimer = setTimeout(() => {
            requestSuggestion(editor, context);
        }, debounceMs);
    });
    // Key handler: Tab accepts, Escape dismisses
    editor.onKeyDown((e) => {
        if (!currentSuggestion)
            return;
        if (e.key === "Tab") {
            e.preventDefault();
            e.stopPropagation();
            const text = currentSuggestion;
            dismiss();
            editor.insertTextAtCursor(text);
            return;
        }
        if (e.key === "Escape") {
            e.preventDefault();
            e.stopPropagation();
            dismiss();
            return;
        }
        // Any other key: dismiss (new suggestion will come after debounce)
        dismiss();
    });
    // Dismiss on blur/scroll
    editor.root.addEventListener("blur", dismiss);
    editor.getScrollContainer().addEventListener("scroll", dismiss);
}
export function destroyGhostText() {
    dismiss();
    if (debounceTimer)
        clearTimeout(debounceTimer);
    if (abortController)
        abortController.abort();
    activeEditor = null;
}
//# sourceMappingURL=ghost-text.js.map