"use client";
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useCallback, useEffect, useRef } from "react";
import { Sparkles } from "lucide-react";
import { toast } from "sonner";
import { cn } from "../../lib/utils";
import { useEditContext } from "../client/editContext";
import { useInlineAiPosition, calculateDialogPosition, getInputSelectionPosition, } from "./useInlineAiPosition";
import { InlineAiDialog } from "./InlineAiDialog";
import { prepareInlineAiTextSelection } from "./prepareInlineAiTextSelection";
import { INLINE_AI_TEXT_EDIT_LABEL } from "./inlineAiTextEditLabels";
import { getInlineAiAnchorFromBridgeToolbar, getVisibleBridgeToolbarRect, } from "../page-editor-chrome/bridgeInlineFormatToolbarLayout";
const INLINE_AI_CLOSE_EVENT = "inline-ai-close";
const INLINE_AI_OVERLAY_ID = "inline-ai";
const INLINE_AI_TRIGGER_BUTTON_WIDTH = 98;
const INLINE_AI_TRIGGER_VIEWPORT_PADDING = 8;
function getCollapsedRangeRect(range) {
    const rect = range.getBoundingClientRect();
    if (rect.width > 0 || rect.height > 0)
        return rect;
    const marker = document.createElement("span");
    marker.style.display = "inline-block";
    marker.style.width = "0";
    marker.style.height = "1em";
    marker.style.overflow = "hidden";
    marker.textContent = "\u200b";
    try {
        range.insertNode(marker);
        const markerRect = marker.getBoundingClientRect();
        return markerRect.width > 0 || markerRect.height > 0 ? markerRect : null;
    }
    catch {
        return null;
    }
    finally {
        marker.parentNode?.removeChild(marker);
    }
}
function getAnchorFromSelectedRange(selectedRange) {
    if (!selectedRange || selectedRange.elementKey) {
        return undefined;
    }
    if (selectedRange.element instanceof HTMLInputElement ||
        selectedRange.element instanceof HTMLTextAreaElement) {
        const start = selectedRange.startOffset ??
            selectedRange.element.selectionStart ??
            selectedRange.element.value.length;
        const end = selectedRange.endOffset ?? selectedRange.element.selectionEnd ?? start;
        const pos = getInputSelectionPosition(selectedRange.element, start, end);
        if (!pos)
            return undefined;
        return {
            x: pos.x,
            y: pos.y,
            origin: "caret",
        };
    }
    if (selectedRange.clientRect) {
        const rect = selectedRange.clientRect;
        return {
            x: rect.left + rect.width / 2,
            y: rect.bottom,
            origin: "caret",
        };
    }
    return undefined;
}
function getCaretAnchorFromActiveElement() {
    const activeEl = document.activeElement;
    if (!activeEl)
        return undefined;
    if (activeEl instanceof HTMLInputElement ||
        activeEl instanceof HTMLTextAreaElement) {
        const offset = activeEl.selectionStart ?? activeEl.value.length;
        const pos = getInputSelectionPosition(activeEl, offset, offset);
        if (!pos)
            return undefined;
        return {
            x: pos.x,
            y: pos.y,
            origin: "caret",
        };
    }
    if (activeEl.getAttribute("contenteditable") === "true" ||
        activeEl.closest('[contenteditable="true"]') ||
        activeEl.closest(".slate-editable")) {
        const selection = window.getSelection();
        if (!selection || selection.rangeCount === 0)
            return undefined;
        const range = selection.getRangeAt(0).cloneRange();
        range.collapse(false);
        const rect = getCollapsedRangeRect(range);
        if (!rect)
            return undefined;
        return {
            x: rect.left,
            y: rect.bottom,
            origin: "caret",
        };
    }
    return undefined;
}
/**
 * Floating trigger button that appears when text is selected.
 * Opens the InlineAiDialog when clicked.
 */
export function InlineAiTrigger({ className }) {
    const editContext = useEditContext();
    const workspaceId = editContext?.workspaceId;
    const selectedRange = editContext?.selectedRange;
    const position = useInlineAiPosition(selectedRange);
    const [dialogOpen, setDialogOpen] = useState(false);
    const [focusAllowsTrigger, setFocusAllowsTrigger] = useState(false);
    const [dialogPosition, setDialogPosition] = useState({ x: 0, y: 0 });
    const [dialogSelectionSnapshot, setDialogSelectionSnapshot] = useState(null);
    const dialogWorkspaceIdRef = useRef(null);
    const setCurrentOverlayRef = useRef(editContext?.setCurrentOverlay);
    setCurrentOverlayRef.current = editContext?.setCurrentOverlay;
    // Grace period after opening: the dialog opens on the context menu's mousedown,
    // and the subsequent click can fall through into the iframe, collapsing the
    // selection before the dialog gains focus. Ignore selection changes during this
    // window so the dialog is not torn down immediately (causing a brief flash).
    const justOpenedRef = useRef(false);
    const justOpenedTimeoutRef = useRef(null);
    useEffect(() => {
        return () => {
            if (justOpenedTimeoutRef.current) {
                clearTimeout(justOpenedTimeoutRef.current);
            }
        };
    }, []);
    const shouldAllowTriggerForCurrentFocus = useCallback(() => {
        if (!selectedRange?.text || dialogOpen)
            return false;
        const activeEl = document.activeElement;
        if (!activeEl)
            return false;
        if (activeEl.closest(".agent-inline-trigger"))
            return true;
        const inAnyDialog = activeEl.closest('[role="dialog"]');
        if (inAnyDialog && !activeEl.closest(".agent-inline-dialog"))
            return false;
        if (selectedRange.element) {
            return (activeEl === selectedRange.element ||
                selectedRange.element.contains(activeEl));
        }
        const selectedFieldEl = activeEl.closest("[data-fieldid][data-itemid]");
        if (selectedFieldEl) {
            return (selectedFieldEl.getAttribute("data-fieldid") ===
                selectedRange.fieldId &&
                selectedFieldEl.getAttribute("data-itemid") === selectedRange.itemId);
        }
        return activeEl.classList.contains("page-iframe");
    }, [
        dialogOpen,
        selectedRange?.element,
        selectedRange?.fieldId,
        selectedRange?.itemId,
        selectedRange?.text,
    ]);
    // Hide trigger when dialog is open, no selection, or focus left the selected field.
    // Bridge/page-host selections carry an `elementKey`; for those the unified inline
    // toolbar (BridgeInlineFormatOverlay) hosts the AI button, so the floating
    // sparkle is suppressed to avoid a duplicate entry point. The standalone form
    // editor (no elementKey) keeps the floating trigger.
    const showTrigger = position.visible &&
        !!selectedRange?.text &&
        !selectedRange?.elementKey &&
        !dialogOpen &&
        focusAllowsTrigger;
    const openDialog = useCallback((anchor, selectionOverride) => {
        const activeSelection = selectionOverride ?? selectedRange;
        const resolvedAnchor = anchor ??
            getInlineAiAnchorFromBridgeToolbar(activeSelection, getVisibleBridgeToolbarRect()) ??
            getAnchorFromSelectedRange(activeSelection) ??
            getCaretAnchorFromActiveElement();
        // Calculate dialog position - use selection position if available, otherwise use a default position
        let pos;
        if (resolvedAnchor) {
            pos = calculateDialogPosition({
                x: resolvedAnchor.x,
                y: resolvedAnchor.y,
                anchorTop: resolvedAnchor.anchorTop,
            }, undefined, undefined, resolvedAnchor.align, resolvedAnchor.origin ?? "caret");
        }
        else if (activeSelection?.text && position.visible) {
            pos = calculateDialogPosition({ x: position.x, y: position.y });
        }
        else {
            // Default position: center of viewport
            pos = {
                x: window.innerWidth / 2 - 95, // Half dialog width (190px / 2)
                y: window.innerHeight / 2 - 200, // Approximate center
            };
        }
        setDialogPosition(pos);
        setDialogSelectionSnapshot(activeSelection ? { ...activeSelection } : null);
        dialogWorkspaceIdRef.current = workspaceId ?? null;
        // Start grace period - must outlast PageViewerFrame's 300ms selectionchange
        // debounce and the 500ms focus-loss auto-close timer below.
        justOpenedRef.current = true;
        if (justOpenedTimeoutRef.current) {
            clearTimeout(justOpenedTimeoutRef.current);
        }
        justOpenedTimeoutRef.current = setTimeout(() => {
            justOpenedRef.current = false;
            justOpenedTimeoutRef.current = null;
        }, 700);
        // Claim the shared overlay slot so other registered overlays (comment
        // popover, context menu, field generators) close themselves. Batched
        // with setDialogOpen so the takeover effect below sees our own claim.
        setCurrentOverlayRef.current?.(INLINE_AI_OVERLAY_ID);
        setDialogOpen(true);
    }, [workspaceId, selectedRange, position.x, position.y, position.visible]);
    const handleClick = useCallback(() => {
        openDialog();
    }, [openDialog]);
    const triggerLeft = typeof window === "undefined"
        ? position.x
        : Math.max(INLINE_AI_TRIGGER_VIEWPORT_PADDING, Math.min(position.x - INLINE_AI_TRIGGER_BUTTON_WIDTH / 2, window.innerWidth -
            INLINE_AI_TRIGGER_BUTTON_WIDTH -
            INLINE_AI_TRIGGER_VIEWPORT_PADDING));
    const handleAccept = useCallback((newText) => {
        if (editContext) {
            const hadSelectionWhenOpened = !!dialogSelectionSnapshot?.text;
            const currentSelection = editContext.selectedRange;
            const hasComparableSnapshotOffsets = dialogSelectionSnapshot?.startOffset != null &&
                dialogSelectionSnapshot?.endOffset != null;
            const hasComparableCurrentOffsets = currentSelection?.startOffset != null &&
                currentSelection?.endOffset != null;
            const selectionStillValid = !!currentSelection &&
                !!dialogSelectionSnapshot?.text &&
                !!currentSelection.text &&
                currentSelection.text === dialogSelectionSnapshot.text &&
                currentSelection.itemId === dialogSelectionSnapshot.itemId &&
                currentSelection.fieldId === dialogSelectionSnapshot.fieldId &&
                hasComparableSnapshotOffsets &&
                hasComparableCurrentOffsets &&
                currentSelection.startOffset ===
                    dialogSelectionSnapshot.startOffset &&
                currentSelection.endOffset === dialogSelectionSnapshot.endOffset;
            if (hadSelectionWhenOpened &&
                currentSelection?.text &&
                !selectionStillValid) {
                toast.warning("Selection was lost while generating", {
                    description: "Result was not inserted to avoid changing an unexpected location. Re-select text and run again.",
                });
                return false;
            }
            if (hadSelectionWhenOpened && editContext.replaceSelectedText) {
                // Replace selected text
                editContext.replaceSelectedText(newText, dialogSelectionSnapshot ?? currentSelection);
            }
            else if (editContext.insertTextAtCursor) {
                // Insert at cursor position when there's no selection
                editContext.insertTextAtCursor(newText);
            }
            else if (dialogSelectionSnapshot && editContext.replaceSelectedText) {
                // Fallback: try replaceSelectedText even without selected text (it should handle insertion)
                editContext.replaceSelectedText(newText);
            }
        }
        setDialogOpen(false);
        setDialogSelectionSnapshot(null);
        dialogWorkspaceIdRef.current = null;
        return true;
    }, [editContext, dialogSelectionSnapshot]);
    const handleClose = useCallback(() => {
        setDialogOpen(false);
        setDialogSelectionSnapshot(null);
        dialogWorkspaceIdRef.current = null;
    }, []);
    useEffect(() => {
        const updateTriggerVisibility = () => {
            setFocusAllowsTrigger(shouldAllowTriggerForCurrentFocus());
        };
        updateTriggerVisibility();
        const scheduleUpdate = () => {
            window.setTimeout(updateTriggerVisibility, 0);
        };
        document.addEventListener("focusin", scheduleUpdate, true);
        document.addEventListener("focusout", scheduleUpdate, true);
        return () => {
            document.removeEventListener("focusin", scheduleUpdate, true);
            document.removeEventListener("focusout", scheduleUpdate, true);
        };
    }, [shouldAllowTriggerForCurrentFocus]);
    useEffect(() => {
        if (!dialogOpen) {
            dialogWorkspaceIdRef.current = null;
            return;
        }
        const currentWorkspaceId = workspaceId;
        if (!currentWorkspaceId)
            return;
        if (dialogWorkspaceIdRef.current === null) {
            dialogWorkspaceIdRef.current = currentWorkspaceId;
            return;
        }
        if (dialogWorkspaceIdRef.current !== currentWorkspaceId) {
            setDialogOpen(false);
            setDialogSelectionSnapshot(null);
            dialogWorkspaceIdRef.current = null;
        }
    }, [dialogOpen, workspaceId]);
    // Close when another named overlay claims the shared currentOverlay slot
    // (e.g. the comment popover). Undefined resets keep the dialog open, same
    // convention as FieldActionsOverlay.
    useEffect(() => {
        if (!dialogOpen)
            return;
        const overlay = editContext?.currentOverlay;
        if (!overlay || overlay === INLINE_AI_OVERLAY_ID)
            return;
        setDialogOpen(false);
        setDialogSelectionSnapshot(null);
        dialogWorkspaceIdRef.current = null;
    }, [dialogOpen, editContext?.currentOverlay]);
    // Release the overlay slot on any close path, unless another overlay has
    // already claimed it.
    useEffect(() => {
        if (!dialogOpen)
            return;
        return () => {
            setCurrentOverlayRef.current?.((overlay) => overlay === INLINE_AI_OVERLAY_ID ? undefined : overlay);
        };
    }, [dialogOpen]);
    // Handle keyboard shortcuts (Ctrl/Cmd + .)
    useEffect(() => {
        const handleKeyDown = (e) => {
            const isCtrlPeriod = (e.ctrlKey || e.metaKey) && e.key === ".";
            // Allow triggering even without selection
            if (isCtrlPeriod && !dialogOpen) {
                // Only trigger if we're in a text field or have a focused field
                const activeEl = document.activeElement;
                const isInTextField = activeEl &&
                    (activeEl.tagName === "INPUT" ||
                        activeEl.tagName === "TEXTAREA" ||
                        activeEl.getAttribute("contenteditable") === "true" ||
                        activeEl.closest('[contenteditable="true"]') ||
                        activeEl.closest(".slate-editable"));
                // Check if focus is in an iframe (which means we're editing content inside the iframe)
                const isInIframe = activeEl && activeEl.tagName === "IFRAME";
                // Allow triggering if:
                // 1. We're in a text field directly, OR
                // 2. We have a focused field in the edit context, OR
                // 3. We're in an iframe AND have a selected range (text selected in iframe field)
                if (isInTextField ||
                    !!editContext?.focusedField ||
                    (isInIframe && !!selectedRange?.text)) {
                    e.preventDefault();
                    e.stopPropagation();
                    const prepared = prepareInlineAiTextSelection(editContext);
                    openDialog(getAnchorFromSelectedRange(prepared.selectionRange) ??
                        getCaretAnchorFromActiveElement(), prepared.selectionRange ?? null);
                }
            }
        };
        document.addEventListener("keydown", handleKeyDown, true);
        return () => {
            document.removeEventListener("keydown", handleKeyDown, true);
        };
    }, [dialogOpen, openDialog, editContext?.focusedField, selectedRange?.text]);
    // Close dialog when the selection changes (user selected different text in the editor)
    useEffect(() => {
        if (!dialogOpen)
            return;
        // Ignore the transient selection change that happens right after opening
        // (e.g. opening from the context menu, where the follow-up click collapses
        // the iframe selection before the dialog gains focus).
        if (justOpenedRef.current)
            return;
        // Only react to selection changes when focus is in the editor - this avoids closing
        // when the selection was cleared due to focus moving to the dialog on open
        const activeEl = document.activeElement;
        if (activeEl?.closest(".agent-inline-dialog") ||
            activeEl?.closest('[role="dialog"]')) {
            return;
        }
        const isInEditor = activeEl &&
            (activeEl.tagName === "INPUT" ||
                activeEl.tagName === "TEXTAREA" ||
                activeEl.getAttribute("contenteditable") === "true" ||
                activeEl.closest('[contenteditable="true"]') ||
                activeEl.closest(".slate-editable") ||
                activeEl.tagName === "IFRAME");
        if (!isInEditor)
            return;
        const snapshotText = dialogSelectionSnapshot?.text ?? "";
        const currentText = selectedRange?.text ?? "";
        // Selection changed if the current selection differs from what we captured when opening
        if (currentText !== snapshotText) {
            setDialogOpen(false);
            setDialogSelectionSnapshot(null);
        }
    }, [
        dialogOpen,
        dialogSelectionSnapshot?.text,
        selectedRange?.text,
        editContext?.focusedField,
    ]);
    // Close dialog when focus is lost (but allow it to stay open even without selection)
    useEffect(() => {
        if (dialogOpen) {
            if (dialogSelectionSnapshot?.text)
                return;
            // Only close if focus moved away from text fields and dialog
            const timeout = setTimeout(() => {
                // Don't tear down a dialog that was just opened (e.g. from the context
                // menu, where the menu click's trailing mouseup refocuses the document
                // body before the dialog can hold focus).
                if (justOpenedRef.current)
                    return;
                const activeEl = document.activeElement;
                const isInDialog = activeEl?.closest(".agent-inline-dialog") ||
                    activeEl?.closest('[role="dialog"]');
                const isInTextField = activeEl &&
                    (activeEl.tagName === "INPUT" ||
                        activeEl.tagName === "TEXTAREA" ||
                        activeEl.getAttribute("contenteditable") === "true" ||
                        activeEl.closest('[contenteditable="true"]') ||
                        activeEl.closest(".slate-editable"));
                // Keep the dialog open while there is still an active text selection to
                // operate on - opening from the Page View context menu leaves focus on
                // the document body, so the focus checks above are not sufficient.
                const hasActiveSelection = !!editContext?.selectedRange?.text;
                // Don't close if focus is in dialog or text field
                if (!isInDialog &&
                    !isInTextField &&
                    !editContext?.focusedField &&
                    !hasActiveSelection) {
                    setDialogOpen(false);
                }
            }, 500);
            return () => clearTimeout(timeout);
        }
    }, [
        dialogOpen,
        dialogSelectionSnapshot?.text,
        editContext?.focusedField,
        editContext?.selectedRange?.text,
    ]);
    // Listen for custom event from context menu or keyboard shortcuts
    useEffect(() => {
        const handleInlineAiOpen = (event) => {
            const detail = event.detail;
            if (!dialogOpen) {
                const prepared = prepareInlineAiTextSelection(editContext);
                openDialog(detail?.anchor, detail?.selectionRange ?? prepared.selectionRange ?? null);
            }
        };
        const handleInlineAiClose = () => {
            if (justOpenedRef.current)
                return;
            setDialogOpen(false);
            setDialogSelectionSnapshot(null);
        };
        document.addEventListener("inline-ai-open", handleInlineAiOpen);
        document.addEventListener(INLINE_AI_CLOSE_EVENT, handleInlineAiClose);
        return () => {
            document.removeEventListener("inline-ai-open", handleInlineAiOpen);
            document.removeEventListener(INLINE_AI_CLOSE_EVENT, handleInlineAiClose);
        };
    }, [dialogOpen, openDialog, editContext]);
    return (_jsxs(_Fragment, { children: [showTrigger && (_jsxs("button", { onClick: handleClick, tabIndex: 0, "aria-label": INLINE_AI_TEXT_EDIT_LABEL, className: cn("agent-inline-trigger fixed z-[40]", "text-neutral-grey-100 hover:bg-neutral-grey-5 inline-flex h-8 items-center gap-1 rounded-[4px] bg-white px-2 text-xs leading-none font-medium", "border-border-default border shadow-lg hover:shadow-xl", "focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600", "transition-all duration-150 ease-out", "animate-in fade-in-0 zoom-in-95", className), style: {
                    left: triggerLeft,
                    top: position.y,
                }, children: [_jsx(Sparkles, { "aria-hidden": "true", className: "h-3 w-3" }), _jsx("span", { children: INLINE_AI_TEXT_EDIT_LABEL })] })), dialogOpen && (_jsx(InlineAiDialog, { selectedText: dialogSelectionSnapshot?.text, contextBefore: dialogSelectionSnapshot?.contextBefore, contextAfter: dialogSelectionSnapshot?.contextAfter, onAccept: handleAccept, onClose: handleClose, position: dialogPosition }))] }));
}
export default InlineAiTrigger;
//# sourceMappingURL=InlineAiTrigger.js.map