import { jsx as _jsx } from "react/jsx-runtime";
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useEditContext, useFieldsEditContext, useEditContextRef, } from "../client/editContext";
import { findFieldElement, getFieldDescriptorFromElement, hasFieldLock, getSessionWithFieldLock, } from "../utils";
import { applyPatch, diffWords } from "diff";
import { createPatch } from "diff";
// import { useDebouncedCallback } from "use-debounce";
// import { executePrompt } from "../services/aiService";
import { useInlineAiCompletion } from "./useInlineAICompletion";
import { getComponentById } from "../componentTreeHelper";
import { useSlotContext } from "../views/editorSlotContext";
const INLINE_FORMAT_QUERY_COMMANDS_BY_MARK = {
    bold: "bold",
    italic: "italic",
    underline: "underline",
    strikethrough: "strikeThrough",
    subscript: "subscript",
    superscript: "superscript",
};
function getInlineFormatMarkId(menuItemId) {
    return menuItemId
        .replace(/^slate-format-mark-/, "")
        .replace(/^format-mark-/, "");
}
function getInlineFieldKey(fieldElement) {
    const fieldId = fieldElement.getAttribute("data-fieldid") || "";
    const itemId = fieldElement.getAttribute("data-itemid") || "";
    const language = fieldElement.getAttribute("data-language") || "";
    const version = fieldElement.getAttribute("data-version") || "";
    return `${itemId}:${language}:${version}:${fieldId}`;
}
function getSelectedTextAnchorRect(range, root) {
    const ownerDocument = root.ownerDocument;
    const nodeFilter = ownerDocument.defaultView?.NodeFilter.SHOW_TEXT ?? NodeFilter.SHOW_TEXT;
    const walker = ownerDocument.createTreeWalker(root, nodeFilter);
    let currentNode = walker.nextNode();
    while (currentNode) {
        if (range.intersectsNode(currentNode)) {
            const textNode = currentNode;
            const start = textNode === range.startContainer ? range.startOffset : 0;
            const end = textNode === range.endContainer ? range.endOffset : textNode.length;
            if (end > start) {
                const probeRange = ownerDocument.createRange();
                probeRange.setStart(textNode, start);
                probeRange.setEnd(textNode, end);
                const rect = Array.from(probeRange.getClientRects()).find((candidate) => candidate.width > 0 && candidate.height > 0);
                probeRange.detach();
                if (rect)
                    return rect;
            }
        }
        currentNode = walker.nextNode();
    }
    return null;
}
function getFirstVisibleSelectionLineRect(range, viewportWidth, viewportHeight) {
    const visibleRects = Array.from(range.getClientRects())
        .filter((rect) => rect.width > 0 &&
        rect.height > 0 &&
        rect.bottom >= 0 &&
        rect.top <= viewportHeight &&
        rect.right >= 0 &&
        rect.left <= viewportWidth)
        .sort((a, b) => a.top - b.top || a.left - b.left);
    const firstRect = visibleRects[0];
    if (!firstRect)
        return null;
    const rowTolerance = Math.max(2, firstRect.height / 2);
    const firstLineRects = visibleRects.filter((rect) => Math.abs(rect.top - firstRect.top) <= rowTolerance);
    const left = Math.min(...firstLineRects.map((rect) => rect.left));
    const top = Math.min(...firstLineRects.map((rect) => rect.top));
    const right = Math.max(...firstLineRects.map((rect) => rect.right));
    const bottom = Math.max(...firstLineRects.map((rect) => rect.bottom));
    return DOMRect.fromRect({
        x: left,
        y: top,
        width: right - left,
        height: bottom - top,
    });
}
function hasUsableRect(rect) {
    return (!!rect &&
        Number.isFinite(rect.left) &&
        Number.isFinite(rect.top) &&
        rect.width > 0 &&
        rect.height > 0);
}
function getInlineFormatAnchorRect(range, fieldElement, viewportWidth, viewportHeight) {
    const candidates = [
        getFirstVisibleSelectionLineRect(range, viewportWidth, viewportHeight),
        getSelectedTextAnchorRect(range, fieldElement),
        range.getBoundingClientRect(),
        fieldElement.getBoundingClientRect(),
    ].filter(hasUsableRect);
    return (candidates.find((rect) => rect.bottom >= 0 &&
        rect.top <= viewportHeight &&
        rect.right >= 0 &&
        rect.left <= viewportWidth) ??
        candidates[0] ??
        null);
}
function getTextNodeAtTextOffset(root, offset, affinity = "backward") {
    const ownerDocument = root.ownerDocument || document;
    const nodeFilter = ownerDocument.defaultView?.NodeFilter.SHOW_TEXT ?? NodeFilter.SHOW_TEXT;
    const walker = ownerDocument.createTreeWalker(root, nodeFilter);
    let currentNode;
    let boundaryNode = null;
    let remainingOffset = offset;
    while ((currentNode = walker.nextNode())) {
        const nodeLength = currentNode.textContent?.length || 0;
        if (remainingOffset < nodeLength) {
            return { node: currentNode, offset: remainingOffset };
        }
        if (remainingOffset === nodeLength) {
            if (affinity === "backward") {
                return { node: currentNode, offset: remainingOffset };
            }
            boundaryNode = currentNode;
            remainingOffset = 0;
            continue;
        }
        remainingOffset -= nodeLength;
    }
    return boundaryNode
        ? { node: boundaryNode, offset: boundaryNode.textContent?.length || 0 }
        : null;
}
function createRangeFromTextOffsets(fieldElement, startOffset, endOffset) {
    const startData = getTextNodeAtTextOffset(fieldElement, startOffset, "forward");
    const endData = getTextNodeAtTextOffset(fieldElement, endOffset, "backward");
    if (!startData || !endData)
        return null;
    try {
        const range = fieldElement.ownerDocument.createRange();
        range.setStart(startData.node, startData.offset);
        range.setEnd(endData.node, endData.offset);
        return range;
    }
    catch {
        return null;
    }
}
function getAccessibleIframeDocument(iframe) {
    if (!iframe)
        return null;
    try {
        return iframe.contentDocument || iframe.contentWindow?.document || null;
    }
    catch {
        return null;
    }
}
function getAccessibleIframeSelection(iframe) {
    if (!iframe)
        return null;
    try {
        return iframe.contentWindow?.getSelection() ?? null;
    }
    catch {
        return null;
    }
}
export function InlineEditor(props) {
    const context = useEditContext();
    if (!context)
        return null;
    return _jsx(InlineEditorContent, { ...props, context: context });
}
function InlineEditorContent({ pageViewContext, compareView, context, }) {
    const contextRef = useEditContextRef();
    const modifiedFieldsContext = useFieldsEditContext();
    const slotContext = useSlotContext();
    const [cursorSpanId] = useState(() => `cursor-indicator-${Math.random().toString(36).substring(2, 9)}`);
    const isUpdatingRef = useRef(false);
    const mutationObserverRef = useRef(null);
    const selectionChangeListenerRef = useRef(null);
    const lastQueuedInlineValueRef = useRef(new Map());
    const lastInlineUserEditAtRef = useRef(0);
    const [inlineFormatOverlay, setInlineFormatOverlay] = useState({
        visible: false,
        left: 0,
        top: 0,
        activeCommandIds: [],
    });
    const inlineFormatOverlayRef = useRef(null);
    const inlineFormatSelectionRef = useRef(null);
    const inlineFormatCommandsCacheRef = useRef(new Map());
    const inlineFormatCommandsLoadingRef = useRef(new Set());
    const inlineFormatPointerDownRef = useRef(false);
    const caretUpdateFrameRef = useRef(null);
    const lastSentCaretRef = useRef(null);
    const refreshCompletion = useInlineAiCompletion({
        pageViewContext,
        cursorSpanId,
        isUpdatingRef,
    });
    const isTextFieldType = (fieldType) => {
        const fieldTypeLower = fieldType.toLowerCase();
        return (fieldTypeLower === "single-line text" ||
            fieldTypeLower === "multi-line text" ||
            fieldTypeLower === "rich text");
    };
    const hideInlineFormatOverlay = () => {
        inlineFormatSelectionRef.current = null;
        setInlineFormatOverlay((current) => current.visible
            ? { visible: false, left: 0, top: 0, activeCommandIds: [] }
            : current);
    };
    const getSelectedInlineRichTextField = (range) => {
        const candidate = range.commonAncestorContainer instanceof Element
            ? range.commonAncestorContainer
            : range.commonAncestorContainer.parentElement;
        const fieldElement = candidate?.closest?.('[data-is-richtext="true"][data-fieldid][data-itemid][data-language][data-version]');
        if (!fieldElement ||
            !fieldElement.contains(range.startContainer) ||
            !fieldElement.contains(range.endContainer)) {
            return null;
        }
        return fieldElement;
    };
    const getInlineRichTextFieldForSelectedRange = (iframeDocument, selectedRange) => {
        if (!selectedRange?.fieldId || !selectedRange.itemId)
            return null;
        const fields = Array.from(iframeDocument.querySelectorAll('[data-is-richtext="true"][data-fieldid][data-itemid][data-language][data-version]'));
        return (fields.find((fieldElement) => {
            const version = fieldElement.getAttribute("data-version");
            return (fieldElement.getAttribute("data-fieldid") === selectedRange.fieldId &&
                fieldElement.getAttribute("data-itemid") === selectedRange.itemId &&
                (!selectedRange.language ||
                    fieldElement.getAttribute("data-language") ===
                        selectedRange.language) &&
                (selectedRange.version == null ||
                    version === String(selectedRange.version)));
        }) ?? null);
    };
    const loadInlineFormatCommands = async (fieldElement) => {
        const key = getInlineFieldKey(fieldElement);
        if (inlineFormatCommandsCacheRef.current.has(key) ||
            inlineFormatCommandsLoadingRef.current.has(key)) {
            return;
        }
        inlineFormatCommandsLoadingRef.current.add(key);
        try {
            const currentContext = contextRef.current;
            if (!currentContext)
                return;
            const field = await currentContext.itemsRepository.getField(getFieldDescriptorFromElement(fieldElement));
            const contextMenuFactory = field
                ? currentContext.configuration.fieldTypes[field.type]
                    ?.contextMenuFactory
                : undefined;
            if (!field || !contextMenuFactory) {
                inlineFormatCommandsCacheRef.current.set(key, []);
                return;
            }
            const menuItems = await contextMenuFactory(field, currentContext);
            const formatSubmenu = menuItems.find((item) => item.items?.length &&
                (item.id === "format-submenu" ||
                    item.id === "slate-format-submenu" ||
                    item.label === "Format"));
            const commands = formatSubmenu?.items
                ?.filter((item) => !item.separator && !item.disabled && !!item.command)
                .map((item) => ({
                id: item.id,
                label: item.label ?? "",
                icon: item.icon,
                command: item.command,
                queryCommand: INLINE_FORMAT_QUERY_COMMANDS_BY_MARK[getInlineFormatMarkId(item.id)],
            })) ?? [];
            inlineFormatCommandsCacheRef.current.set(key, commands);
        }
        catch (error) {
            console.error("[InlineEditor] Failed to load format overlay:", error);
            inlineFormatCommandsCacheRef.current.set(key, []);
        }
        finally {
            inlineFormatCommandsLoadingRef.current.delete(key);
            window.requestAnimationFrame(updateInlineFormatOverlay);
        }
    };
    const getActiveInlineFormatCommandIds = (iframeDocument, range, commands) => {
        const selection = iframeDocument.getSelection();
        if (!selection)
            return [];
        const queryActiveCommands = () => commands
            .filter((command) => {
            if (!command.queryCommand)
                return false;
            try {
                return iframeDocument.queryCommandState(command.queryCommand);
            }
            catch {
                return false;
            }
        })
            .map((command) => command.id);
        const currentRange = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
        if (!currentRange ||
            currentRange.startContainer !== range.startContainer ||
            currentRange.startOffset !== range.startOffset ||
            currentRange.endContainer !== range.endContainer ||
            currentRange.endOffset !== range.endOffset) {
            return [];
        }
        return queryActiveCommands();
    };
    const updateInlineFormatOverlay = () => {
        if (inlineFormatPointerDownRef.current) {
            hideInlineFormatOverlay();
            return;
        }
        if (compareView ||
            contextRef.current?.readonly ||
            (contextRef.current?.mode !== "edit" &&
                contextRef.current?.mode !== "suggestions")) {
            hideInlineFormatOverlay();
            return;
        }
        const iframe = pageViewContext.editorIframe;
        const iframeDocument = getAccessibleIframeDocument(iframe);
        if (!iframe || !iframeDocument) {
            hideInlineFormatOverlay();
            return;
        }
        const selection = getAccessibleIframeSelection(iframe);
        const selectedRange = contextRef.current?.selectedRange;
        if (selection?.rangeCount && selection.isCollapsed) {
            hideInlineFormatOverlay();
            return;
        }
        const liveRange = selection && !selection.isCollapsed && selection.rangeCount > 0
            ? selection.getRangeAt(0)
            : null;
        const liveRangeText = liveRange?.toString().trim();
        if (!liveRangeText && !selectedRange?.text?.trim()) {
            hideInlineFormatOverlay();
            return;
        }
        const liveRangeFieldElement = liveRange
            ? getSelectedInlineRichTextField(liveRange)
            : null;
        const fieldElement = liveRangeFieldElement ??
            getInlineRichTextFieldForSelectedRange(iframeDocument, selectedRange);
        if (!fieldElement || !fieldElement.isContentEditable) {
            hideInlineFormatOverlay();
            return;
        }
        const range = (liveRangeFieldElement ? liveRange : null) ??
            createRangeFromTextOffsets(fieldElement, selectedRange?.startOffset ?? 0, selectedRange?.endOffset ?? 0);
        if (!range) {
            hideInlineFormatOverlay();
            return;
        }
        const commandKey = getInlineFieldKey(fieldElement);
        const inlineFormatCommands = inlineFormatCommandsCacheRef.current.get(commandKey);
        if (!inlineFormatCommands) {
            void loadInlineFormatCommands(fieldElement);
            hideInlineFormatOverlay();
            return;
        }
        if (inlineFormatCommands.length === 0) {
            hideInlineFormatOverlay();
            return;
        }
        const iframeViewportWidth = iframe.contentWindow?.innerWidth ?? iframe.clientWidth;
        const iframeViewportHeight = iframe.contentWindow?.innerHeight ?? iframe.clientHeight;
        const anchorRect = getInlineFormatAnchorRect(range, fieldElement, iframeViewportWidth, iframeViewportHeight) ?? fieldElement.getBoundingClientRect();
        const iframeRect = iframe.getBoundingClientRect();
        const overlayWidth = inlineFormatOverlayRef.current?.offsetWidth ?? 180;
        const overlayHeight = inlineFormatOverlayRef.current?.offsetHeight ?? 34;
        const viewportPadding = 8;
        const selectionGap = 8;
        const anchorCenter = iframeRect.left +
            anchorRect.left +
            Math.min(anchorRect.width, overlayWidth) / 2;
        let top = iframeRect.top + anchorRect.top - overlayHeight - selectionGap;
        if (top < viewportPadding) {
            top = iframeRect.top + anchorRect.bottom + selectionGap + 36;
        }
        top = Math.max(viewportPadding, Math.min(top, window.innerHeight - overlayHeight - viewportPadding));
        const left = Math.max(viewportPadding, Math.min(anchorCenter - overlayWidth / 2, window.innerWidth - overlayWidth - viewportPadding));
        inlineFormatSelectionRef.current = {
            range: range.cloneRange(),
            fieldElement,
        };
        setInlineFormatOverlay({
            visible: true,
            left,
            top,
            activeCommandIds: getActiveInlineFormatCommandIds(iframeDocument, range, inlineFormatCommands),
        });
    };
    const applyInlineFormatCommand = async (command, event) => {
        event.preventDefault();
        event.stopPropagation();
        const selectionContext = inlineFormatSelectionRef.current;
        if (!selectionContext?.range || !command.command)
            return;
        const iframeWindow = pageViewContext.editorIframe?.contentWindow;
        const iframeDocument = selectionContext.range.startContainer.ownerDocument;
        if (!iframeDocument)
            return;
        const selection = iframeDocument.getSelection();
        if (!selection)
            return;
        iframeWindow?.focus();
        selectionContext.fieldElement.focus({ preventScroll: true });
        selection.removeAllRanges();
        selection.addRange(selectionContext.range);
        await command.command(event);
        if (selection.rangeCount > 0) {
            inlineFormatSelectionRef.current = {
                range: selection.getRangeAt(0).cloneRange(),
                fieldElement: selectionContext.fieldElement,
            };
        }
        window.requestAnimationFrame(updateInlineFormatOverlay);
    };
    // Stable identity check: compare by field key (not by DOM node reference).
    // This avoids stomping the active field if the iframe DOM is replaced/morphed.
    const getFieldKeyFromElement = (el) => {
        const fieldId = el.getAttribute("data-fieldid") || "";
        const itemId = el.getAttribute("data-itemid") || "";
        const language = el.getAttribute("data-language") || "";
        const versionRaw = el.getAttribute("data-version");
        const version = versionRaw ? parseInt(versionRaw, 10) : NaN;
        return { fieldId, itemId, language, version };
    };
    const isSameFieldKey = (el, key) => {
        if (!key)
            return false;
        const k = getFieldKeyFromElement(el);
        return (k.fieldId === key.fieldId &&
            k.itemId === key.itemId &&
            k.language === key.language &&
            k.version === key.version);
    };
    // Only skip DOM updates for fields being edited inline in the iframe.
    // Do NOT skip updates for sidebar-focused fields - we want those to update the preview.
    const activeFieldKey = modifiedFieldsContext?.inlineEditingFieldElement
        ? getFieldKeyFromElement(modifiedFieldsContext.inlineEditingFieldElement)
        : context.selectedRange
            ? {
                fieldId: context.selectedRange.fieldId,
                itemId: context.selectedRange.itemId,
                language: context.selectedRange.language,
                version: context.selectedRange.version,
            }
            : undefined;
    const isElementActiveInIframe = (doc, element) => {
        if (!doc || !element)
            return false;
        const activeElement = doc.activeElement;
        return (activeElement === element ||
            (activeElement ? element.contains(activeElement) : false));
    };
    const isSelectionInsideElement = (element) => {
        if (!element)
            return false;
        const iframe = pageViewContext.editorIframe;
        const iframeDocument = getAccessibleIframeDocument(iframe);
        const iframeHasFocus = iframe?.ownerDocument.activeElement === iframe ||
            iframeDocument?.hasFocus() === true;
        if (!iframeHasFocus)
            return false;
        const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
        return !!(selection?.anchorNode &&
            selection?.focusNode &&
            element.contains(selection.anchorNode) &&
            element.contains(selection.focusNode));
    };
    const isElementActiveOrSelectedInIframe = (doc, element) => isElementActiveInIframe(doc, element) || isSelectionInsideElement(element);
    const clearRemoteCaretPosition = () => {
        if (caretUpdateFrameRef.current != null) {
            window.cancelAnimationFrame(caretUpdateFrameRef.current);
            caretUpdateFrameRef.current = null;
        }
        if (lastSentCaretRef.current === "clear")
            return;
        context.sendSocketMessage({
            type: "caret-position",
            payload: { offset: null },
        });
        lastSentCaretRef.current = "clear";
    };
    const scheduleCaretPositionUpdate = (element, fieldDescriptor) => {
        if (caretUpdateFrameRef.current != null)
            return;
        caretUpdateFrameRef.current = window.requestAnimationFrame(() => {
            caretUpdateFrameRef.current = null;
            const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
            if (!isElementActiveOrSelectedInIframe(iframeDocument, element)) {
                clearRemoteCaretPosition();
                return;
            }
            const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
            if (!selection || selection.rangeCount === 0 || !selection.isCollapsed) {
                clearRemoteCaretPosition();
                return;
            }
            const caret = saveCaretPosition(element);
            if (!caret || caret.startOffset !== caret.endOffset) {
                clearRemoteCaretPosition();
                return;
            }
            const value = sanitizeEditableValue(element, element.getAttribute("data-is-richtext") === "true");
            const nextKey = `${fieldDescriptor.fieldId}:${fieldDescriptor.item.id}:${fieldDescriptor.item.language}:${fieldDescriptor.item.version}:${caret.startOffset}:${value}`;
            if (lastSentCaretRef.current === nextKey)
                return;
            context.sendSocketMessage({
                type: "caret-position",
                payload: {
                    fieldId: fieldDescriptor.fieldId,
                    item: fieldDescriptor.item,
                    offset: caret.startOffset,
                    value,
                },
            });
            lastSentCaretRef.current = nextKey;
        });
    };
    const isInlineFieldBeingEdited = (doc, element) => {
        if (!doc || !element)
            return false;
        const iframe = pageViewContext.editorIframe;
        const iframeHasFocus = iframe?.ownerDocument.activeElement === iframe || doc.hasFocus() === true;
        return ((iframeHasFocus && isElementActiveInIframe(doc, element)) ||
            isSelectionInsideElement(element));
    };
    const focusInlineElement = (element) => {
        const iframeWindow = pageViewContext.editorIframe?.contentWindow;
        const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
        iframeWindow?.focus();
        element.focus({ preventScroll: true });
        if (!selection || isSelectionInsideElement(element))
            return;
        const range = element.ownerDocument.createRange();
        range.selectNodeContents(element);
        range.collapse(false);
        selection.removeAllRanges();
        selection.addRange(range);
    };
    function hasRemoteCaretForField({ fieldId, itemId, language, version, }) {
        if (!fieldId || !itemId || !language || version == null)
            return false;
        return Object.values(contextRef.current?.remoteCarets ?? context.remoteCarets).some((caret) => caret.offset != null &&
            caret.fieldId === fieldId &&
            caret.item?.id === itemId &&
            caret.item?.language === language &&
            caret.item?.version === version);
    }
    // Helper: set innerHTML when content changes
    function setInnerHTMLWithStackTrace(element, nextHTML, meta) {
        const prevHTML = element.innerHTML ?? "";
        if (prevHTML === nextHTML)
            return false;
        if (meta.source !== "remote-caret:value" &&
            hasRemoteCaretForField({
                fieldId: meta.fieldId ?? element.getAttribute("data-fieldid"),
                itemId: meta.itemId ?? element.getAttribute("data-itemid"),
                language: meta.language ?? element.getAttribute("data-language"),
                version: meta.version ??
                    (element.getAttribute("data-version")
                        ? parseInt(element.getAttribute("data-version"), 10)
                        : null),
            })) {
            return false;
        }
        if (contextRef.current?.mode === "suggestions") {
            const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
            const iframeHasFocus = pageViewContext.editorIframe?.ownerDocument.activeElement ===
                pageViewContext.editorIframe || iframeDocument?.hasFocus() === true;
            const recentlyEdited = Date.now() - lastInlineUserEditAtRef.current < 1500;
            const isFocusedEditable = iframeHasFocus &&
                (isElementActiveInIframe(iframeDocument, element) ||
                    isSelectionInsideElement(element));
            const willSkipRewrite = isFocusedEditable || (element.isContentEditable && recentlyEdited);
            if (willSkipRewrite) {
                return false;
            }
        }
        isUpdatingRef.current = true;
        element.innerHTML = nextHTML;
        setTimeout(() => {
            isUpdatingRef.current = false;
        }, 0);
        // try {
        //   const resolvedFieldId =
        //     meta.fieldId ?? element.getAttribute("data-fieldid");
        //   const resolvedItemId = meta.itemId ?? element.getAttribute("data-itemid");
        //   const resolvedLanguage =
        //     meta.language ?? element.getAttribute("data-language");
        //   const resolvedVersion =
        //     meta.version ??
        //     (element.getAttribute("data-version")
        //       ? parseInt(element.getAttribute("data-version") as string, 10)
        //       : null);
        //   // Log concise info and stack trace
        //   // Limit previews to keep console tidy
        //   const preview = (s: string) =>
        //     s.length > 200 ? s.slice(0, 200) + "…" : s;
        //   console.groupCollapsed(
        //     `[InlineEditor] DOM field update (${meta.source}) ${nextHTML} `,
        //   );
        //   const modifiedFieldValue = modifiedFieldsContext?.modifiedFields.find(
        //     (x) =>
        //       x.fieldId === resolvedFieldId &&
        //       x.item.id === resolvedItemId &&
        //       x.item.language === resolvedLanguage &&
        //       x.item.version === resolvedVersion,
        //   )?.value;
        //   console.log({
        //     fieldId: resolvedFieldId,
        //     itemId: resolvedItemId,
        //     language: resolvedLanguage,
        //     version: resolvedVersion,
        //     prevLength: prevHTML.length,
        //     nextLength: nextHTML.length,
        //     prevPreview: preview(prevHTML),
        //     nextPreview: preview(nextHTML),
        //     modifiedFieldsContext: modifiedFieldsContext?.modifiedFields,
        //     repository: contextRef.current?.itemsRepository,
        //     modifiedFieldValue,
        //   });
        //   console.trace();
        //   console.groupEnd();
        // } catch {
        //   // no-op logging failure
        // }
        return true;
    }
    function setEditableElementValue(element, nextValue, isRichText, meta) {
        if (isRichText) {
            return setInnerHTMLWithStackTrace(element, nextValue, meta);
        }
        if ((element.innerText ?? "") === nextValue)
            return false;
        isUpdatingRef.current = true;
        element.innerText = nextValue;
        setTimeout(() => {
            isUpdatingRef.current = false;
        }, 0);
        return true;
    }
    function isCurrentInlineEditSource(sourceElement, key) {
        if (!sourceElement.isConnected)
            return false;
        const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
        if (iframeDocument && sourceElement.ownerDocument !== iframeDocument) {
            return false;
        }
        const activePage = pageViewContext.pageItemDescriptor;
        if (activePage?.language && activePage.language !== key.language) {
            return false;
        }
        const activeElement = modifiedFieldsContext?.inlineEditingFieldElement;
        if (activeElement && isSameFieldKey(sourceElement, key))
            return true;
        if (key.fromUserInput &&
            contextRef.current?.mode === "suggestions" &&
            sourceElement instanceof HTMLElement &&
            sourceElement.isContentEditable &&
            isSameFieldKey(sourceElement, key)) {
            return true;
        }
        if (contextRef.current?.mode === "suggestions" &&
            sourceElement instanceof HTMLElement &&
            sourceElement.isContentEditable &&
            isSameFieldKey(sourceElement, key) &&
            isInlineFieldBeingEdited(iframeDocument, sourceElement)) {
            return true;
        }
        if (!activeElement || !isSameFieldKey(sourceElement, key))
            return false;
        return true;
    }
    function queueInlineFieldValue({ value, fieldId, fieldName, itemId, language, version, sourceElement, fromUserInput = false, source = "unknown", }) {
        const isCurrentSource = isCurrentInlineEditSource(sourceElement, {
            fieldId,
            itemId,
            language,
            version,
            fromUserInput,
        });
        if (!isCurrentSource) {
            return;
        }
        const fieldValueKey = `${itemId}:${language}:${version}:${fieldId}`;
        if (lastQueuedInlineValueRef.current.get(fieldValueKey) === value)
            return;
        lastQueuedInlineValueRef.current.set(fieldValueKey, value);
        lastInlineUserEditAtRef.current = Date.now();
        contextRef.current?.operations.editField({
            field: {
                fieldId,
                fieldName: fieldName ?? undefined,
                item: {
                    id: itemId,
                    language,
                    version,
                },
            },
            forceMode: contextRef.current?.mode === "suggestions" ||
                context.mode === "suggestions"
                ? "suggestions"
                : undefined,
            originatingSlotId: slotContext?.slotId,
            refresh: "none",
            value,
        });
    }
    const sanitizeEditableValue = (element, isRichText) => {
        let currentValue = isRichText ? element.innerHTML : element.innerText;
        if (isRichText) {
            const clone = element.cloneNode(true);
            const cursorElem = clone.querySelector(`#${cursorSpanId}`);
            if (cursorElem)
                cursorElem.parentNode?.removeChild(cursorElem);
            const ownerDoc = clone.ownerDocument || document;
            const walker = ownerDoc.createTreeWalker(clone, NodeFilter.SHOW_TEXT);
            const toClean = [];
            while (walker.nextNode()) {
                const tn = walker.currentNode;
                if (tn.nodeValue && tn.nodeValue.includes("\u200B")) {
                    toClean.push(tn);
                }
            }
            toClean.forEach((tn) => (tn.nodeValue = tn.nodeValue?.replaceAll("\u200B", "") || ""));
            currentValue = clone.innerHTML;
        }
        else {
            currentValue = currentValue.replaceAll("\u200B", "");
        }
        return currentValue;
    };
    useEffect(() => {
        const editorIframe = pageViewContext.editorIframe;
        if (compareView || !editorIframe)
            return;
        context.activeSessions.forEach((session) => {
            if (session.sessionId === context.sessionId || !session.fieldLock)
                return;
            const remoteCaret = context.remoteCarets[session.sessionId];
            if (remoteCaret?.value == null)
                return;
            const caretMatchesLock = remoteCaret.fieldId === session.fieldLock.fieldId &&
                remoteCaret.item?.id === session.fieldLock.item.id &&
                remoteCaret.item?.language === session.fieldLock.item.language &&
                remoteCaret.item?.version === session.fieldLock.item.version;
            if (!caretMatchesLock)
                return;
            const element = findFieldElement(editorIframe, session.fieldLock);
            if (!element || element === modifiedFieldsContext?.inlineEditingFieldElement)
                return;
            setEditableElementValue(element, remoteCaret.value, element.getAttribute("data-is-richtext") === "true", {
                source: "remote-caret:value",
                fieldId: session.fieldLock.fieldId,
                itemId: session.fieldLock.item.id,
                language: session.fieldLock.item.language,
                version: session.fieldLock.item.version,
            });
        });
    }, [
        compareView,
        context.activeSessions,
        context.remoteCarets,
        context.sessionId,
        modifiedFieldsContext?.inlineEditingFieldElement,
        pageViewContext.editorIframe,
    ]);
    useEffect(() => {
        if (compareView || context.mode === "preview")
            return;
        const queueElementValue = (element, fromUserInput = false) => {
            if (contextRef.current?.mode !== "suggestions" &&
                context.mode !== "suggestions") {
                return;
            }
            if (isUpdatingRef.current)
                return;
            if (!element?.isContentEditable)
                return;
            const fieldId = element.getAttribute("data-fieldid");
            const fieldName = element.getAttribute("data-fieldname");
            const itemId = element.getAttribute("data-itemid");
            const language = element.getAttribute("data-language");
            const versionText = element.getAttribute("data-version");
            const version = versionText ? parseInt(versionText) : undefined;
            if (!fieldId || !itemId || !language || !version)
                return;
            queueInlineFieldValue({
                value: sanitizeEditableValue(element, element.getAttribute("data-is-richtext") === "true"),
                fieldId,
                fieldName,
                itemId,
                language,
                version,
                sourceElement: element,
                fromUserInput,
            });
        };
        const getFieldElement = (target) => {
            const element = target instanceof Element ? target : target?.parentElement;
            return element?.closest?.("[data-fieldid][data-itemid][data-language][data-version]");
        };
        const inputHandler = (event) => {
            queueElementValue(getFieldElement(event.target), true);
        };
        let detachDocumentInput = null;
        const attachToCurrentIframeDocument = () => {
            detachDocumentInput?.();
            const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
            if (!iframeDocument) {
                detachDocumentInput = null;
                return;
            }
            iframeDocument.addEventListener("input", inputHandler, true);
            const observer = new MutationObserver((mutations) => {
                const queuedElements = new Set();
                for (const mutation of mutations) {
                    const fieldElement = getFieldElement(mutation.target);
                    if (fieldElement)
                        queuedElements.add(fieldElement);
                    mutation.addedNodes.forEach((node) => {
                        const addedFieldElement = getFieldElement(node);
                        if (addedFieldElement)
                            queuedElements.add(addedFieldElement);
                    });
                }
                queuedElements.forEach((element) => queueElementValue(element));
            });
            observer.observe(iframeDocument.body || iframeDocument.documentElement, {
                childList: true,
                characterData: true,
                subtree: true,
            });
            const pollFocusedField = () => {
                if (contextRef.current?.mode !== "suggestions" &&
                    context.mode !== "suggestions") {
                    return;
                }
                const activeField = iframeDocument.activeElement?.closest?.("[data-fieldid][data-itemid][data-language][data-version]");
                if (activeField) {
                    queueElementValue(activeField, true);
                    return;
                }
                const selection = iframeDocument.defaultView?.getSelection();
                const selectionElement = selection?.anchorNode instanceof Element
                    ? selection.anchorNode
                    : selection?.anchorNode?.parentElement;
                const selectedField = selectionElement?.closest?.("[data-fieldid][data-itemid][data-language][data-version]");
                if (selectedField) {
                    queueElementValue(selectedField, true);
                    return;
                }
                iframeDocument
                    .querySelectorAll('[contenteditable="true"][data-fieldid][data-itemid][data-language][data-version]')
                    .forEach((fieldElement) => queueElementValue(fieldElement, true));
            };
            const pollIntervalId = iframeDocument.defaultView?.setInterval(pollFocusedField, 250);
            detachDocumentInput = () => {
                iframeDocument.removeEventListener("input", inputHandler, true);
                observer.disconnect();
                if (pollIntervalId !== undefined) {
                    iframeDocument.defaultView?.clearInterval(pollIntervalId);
                }
            };
        };
        attachToCurrentIframeDocument();
        pageViewContext.editorIframe?.addEventListener("load", attachToCurrentIframeDocument);
        return () => {
            pageViewContext.editorIframe?.removeEventListener("load", attachToCurrentIframeDocument);
            detachDocumentInput?.();
        };
    }, [compareView, context.mode, pageViewContext.editorIframe]);
    useEffect(() => {
        if (!context || compareView || context.mode === "preview")
            return;
        const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
        let element = modifiedFieldsContext?.inlineEditingFieldElement;
        if (!element && context.mode === "suggestions" && iframeDocument) {
            const iframeHasFocus = pageViewContext.editorIframe?.ownerDocument.activeElement ===
                pageViewContext.editorIframe || iframeDocument.hasFocus() === true;
            if (iframeHasFocus) {
                const activeFieldElement = iframeDocument.activeElement?.closest?.("[data-fieldid][data-itemid][data-language][data-version]") ?? null;
                const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
                const selectionElement = selection?.anchorNode instanceof Element
                    ? selection.anchorNode
                    : selection?.anchorNode?.parentElement;
                const selectedFieldElement = selectionElement?.closest?.("[data-fieldid][data-itemid][data-language][data-version]") ?? null;
                const recoveredElement = (activeFieldElement ??
                    selectedFieldElement);
                if (recoveredElement?.isConnected) {
                    element = recoveredElement;
                    modifiedFieldsContext?.setInlineEditingFieldElement(recoveredElement);
                }
            }
        }
        const editableElements = iframeDocument?.querySelectorAll('[contenteditable="true"]');
        if (element) {
            editableElements?.forEach((editableElement) => {
                if (editableElement !== element) {
                    editableElement.setAttribute("contenteditable", "false");
                }
            });
        }
        if (element) {
            lastQueuedInlineValueRef.current.clear();
            // Clean up any existing cursor indicators
            const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
            const existingCursors = iframeDocument?.querySelectorAll('[data-cursor-indicator="true"]');
            existingCursors?.forEach((el) => el.parentNode?.removeChild(el));
            const fieldId = element.getAttribute("data-fieldid");
            const fieldName = element.getAttribute("data-fieldname");
            const itemId = element.getAttribute("data-itemid");
            const language = element.getAttribute("data-language");
            const versionText = element.getAttribute("data-version");
            const version = versionText ? parseInt(versionText) : undefined;
            const isRichText = element.getAttribute("data-is-richtext") === "true";
            if (!fieldId || !itemId || !language || !version)
                return;
            // Guard: if layout components are hidden, do not allow editing fields of layout components
            if (contextRef.current?.showLayoutComponents === false &&
                contextRef.current?.page) {
                const owner = getComponentById(itemId, contextRef.current.page);
                if (owner?.layoutId)
                    return;
            }
            const fieldDescriptor = getFieldDescriptorFromElement(element);
            // Check if another session has this field locked
            const fieldLockedBySession = getSessionWithFieldLock(fieldDescriptor, context);
            const isLockedByAnotherUser = fieldLockedBySession &&
                fieldLockedBySession.sessionId !== context.sessionId;
            // If another user has the lock, don't allow editing
            if (isLockedByAnotherUser)
                return;
            const hasFielLock = context.mode === "suggestions" ||
                hasFieldLock(fieldDescriptor, context);
            if (!hasFielLock)
                return;
            element.setAttribute("contenteditable", "true");
            const iframeWindow = pageViewContext.editorIframe?.contentWindow;
            const sanitizeCurrentValue = () => sanitizeEditableValue(element, isRichText);
            const inputHandler = () => {
                const currentValue = sanitizeCurrentValue();
                queueInlineFieldValue({
                    value: currentValue,
                    fieldId,
                    fieldName,
                    itemId,
                    language,
                    version,
                    sourceElement: element,
                    fromUserInput: true,
                    source: "input",
                });
                scheduleCaretPositionUpdate(element, fieldDescriptor);
            };
            element.addEventListener("input", inputHandler);
            // Setup a more robust mutation observer
            mutationObserverRef.current = new MutationObserver((mutations) => {
                // If we're already processing an update, don't trigger another one
                if (isUpdatingRef.current)
                    return;
                // Check if mutations only affect our cursor span
                const hasRealChanges = mutations.some((mutation) => {
                    // Skip our cursor span
                    if (mutation.target.nodeType === Node.ELEMENT_NODE &&
                        mutation.target.id === cursorSpanId) {
                        console.log("skipping cursor span");
                        return false;
                    }
                    // Skip mutations where target is our span
                    if (mutation.target.nodeType === Node.ELEMENT_NODE &&
                        mutation.target.getAttribute &&
                        mutation.target.getAttribute("data-cursor-indicator") === "true") {
                        console.log("skipping cursor span 2");
                        return false;
                    }
                    // Skip if all added/removed nodes are just our cursor span
                    if (mutation.type === "childList") {
                        const nonCursorChanges = Array.from(mutation.addedNodes)
                            .concat(Array.from(mutation.removedNodes))
                            .filter((node) => {
                            return (node.nodeType !== Node.ELEMENT_NODE ||
                                !node.id ||
                                node.id !== cursorSpanId);
                        });
                        return nonCursorChanges.length > 0;
                    }
                    return true;
                });
                if (hasRealChanges) {
                    // Capture and sanitize the value at mutation time
                    const capturedValue = sanitizeCurrentValue();
                    queueInlineFieldValue({
                        value: capturedValue,
                        fieldId,
                        fieldName,
                        itemId,
                        language,
                        version,
                        sourceElement: element,
                        source: "mutation",
                    });
                }
                // Always update cursor position after any mutation,
                // but delay to let the mutation processing complete
                // if (!isUpdatingRef.current) {
                //   setTimeout(updateCursorSpan, 0);
                //   //   updateCompletion();
                // }
            });
            const focusIfNeeded = () => {
                // Only focus if no other important element has focus (like the AI dialog)
                const activeEl = document.activeElement;
                const isInDialog = activeEl?.closest(".agent-inline-dialog") ||
                    activeEl?.closest('[role="dialog"]');
                const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
                const alreadyFocused = isElementActiveInIframe(iframeDocument, element);
                if (!isInDialog && !alreadyFocused) {
                    focusInlineElement(element);
                }
            };
            focusIfNeeded();
            setTimeout(focusIfNeeded, 50);
            scheduleCaretPositionUpdate(element, fieldDescriptor);
            // Initial cursor placement
            // setTimeout(updateCursorSpan, 10);
            // Add listener for selection changes
            if (iframeWindow) {
                const selectionChangeHandler = () => {
                    scheduleCaretPositionUpdate(element, fieldDescriptor);
                };
                const keyOrMouseHandler = () => {
                    scheduleCaretPositionUpdate(element, fieldDescriptor);
                };
                const blurHandler = () => {
                    clearRemoteCaretPosition();
                };
                iframeWindow.document.addEventListener("selectionchange", selectionChangeHandler);
                iframeWindow.document.addEventListener("keyup", keyOrMouseHandler);
                iframeWindow.document.addEventListener("mouseup", keyOrMouseHandler);
                element.addEventListener("blur", blurHandler);
                selectionChangeListenerRef.current = () => {
                    iframeWindow.document.removeEventListener("selectionchange", selectionChangeHandler);
                    iframeWindow.document.removeEventListener("keyup", keyOrMouseHandler);
                    iframeWindow.document.removeEventListener("mouseup", keyOrMouseHandler);
                    element.removeEventListener("blur", blurHandler);
                };
                mutationObserverRef.current.observe(element, {
                    childList: true, // observe direct children changes
                    subtree: true, // observe all descendants changes
                    characterData: true, // observe text changes
                });
                // Cleanup
                return () => {
                    element.removeEventListener("input", inputHandler);
                    if (mutationObserverRef.current) {
                        mutationObserverRef.current.disconnect();
                        mutationObserverRef.current = null;
                    }
                    if (selectionChangeListenerRef.current) {
                        selectionChangeListenerRef.current();
                        selectionChangeListenerRef.current = null;
                    }
                    // iframeWindow.document.removeEventListener("keydown", keyHandler);
                    // Clean up any cursor indicators on unmount
                    const cursorElement = iframeDocument?.getElementById(cursorSpanId);
                    if (cursorElement) {
                        cursorElement.parentNode?.removeChild(cursorElement);
                    }
                    clearRemoteCaretPosition();
                };
            }
        }
        async function updateFocusedFieldContent() {
            const element = modifiedFieldsContext?.inlineEditingFieldElement;
            if (!element)
                return;
            const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
            if (isElementActiveOrSelectedInIframe(iframeDocument, element))
                return;
            if (Date.now() - lastInlineUserEditAtRef.current < 1000)
                return;
            const savedPosition = saveCaretPosition(element);
            let contentWasUpdated = false;
            const fieldId = element.getAttribute("data-fieldid");
            const itemId = element.getAttribute("data-itemid");
            const language = element.getAttribute("data-language");
            const versionStr = element.getAttribute("data-version");
            if (!fieldId || !itemId || !language || !versionStr)
                return;
            const version = parseInt(versionStr, 10);
            const descriptor = { id: itemId, language, version };
            // Retrieve the current field value from the repository.
            const loadedItem = await contextRef.current?.itemsRepository.getItem(descriptor);
            if (!loadedItem)
                return;
            // Get the baseline value from the repository.
            const repositoryField = loadedItem.fields.find((f) => f.id === fieldId);
            let baseValue = repositoryField ? repositoryField.rawValue || "" : "";
            // Override with the modified field value if it exists.
            const modField = modifiedFieldsContext?.modifiedFields.find((mod) => mod.fieldId === fieldId &&
                mod.item.id === itemId &&
                mod.item.language === language &&
                mod.item.version === version);
            if (modField) {
                baseValue = modField.value || "";
            }
            // If suggestions mode is active, merge all suggestions for this field.
            if (contextRef.current?.mode === "suggestions") {
                const pageVer = pageViewContext.pageItemDescriptor?.version;
                const fieldSuggestions = (contextRef.current?.suggestedEdits ?? []).filter((s) => s.fieldId === fieldId &&
                    s.itemId === itemId &&
                    s.mainItemLanguage === language &&
                    (pageVer != null
                        ? s.mainItemVersion === pageVer || s.mainItemVersion === 0
                        : s.mainItemVersion === version));
                // Sort suggestions in chronological order (oldest first).
                fieldSuggestions.sort((a, b) => new Date(a.created).getTime() - new Date(b.created).getTime());
                let mergedValue = baseValue;
                for (const suggestion of fieldSuggestions) {
                    const patch = createPatch("field", suggestion.oldValue, suggestion.newValue);
                    const patchedCandidate = applyPatch(mergedValue, patch);
                    if (patchedCandidate !== false &&
                        typeof patchedCandidate === "string") {
                        mergedValue = patchedCandidate;
                    }
                    else {
                        mergedValue = suggestion.newValue;
                    }
                }
                // Update focused field element with the merged plain text value.
                setInnerHTMLWithStackTrace(element, mergedValue, {
                    source: "updateFocusedFieldContent:mergedValue",
                    fieldId,
                    itemId,
                    language,
                    version,
                });
                contentWasUpdated = true;
            }
            else {
                if (element.innerHTML !== baseValue) {
                    // If suggestions mode is off, update with the base value.
                    setInnerHTMLWithStackTrace(element, baseValue, {
                        source: "updateFocusedFieldContent:baseValue",
                        fieldId,
                        itemId,
                        language,
                        version,
                    });
                    contentWasUpdated = true;
                }
            }
            if (contentWasUpdated) {
                setTimeout(() => {
                    if (savedPosition)
                        restoreCaretPosition(element, savedPosition);
                }, 0);
            }
        }
        // Avoid rewriting the focused editable DOM while the user is typing in normal edit mode.
        // This prevents lost/reordered characters on slow machines.
        if (contextRef.current?.mode === "suggestions" ||
            contextRef.current?.showSuggestedEdits) {
            setTimeout(() => {
                updateFocusedFieldContent();
            }, 50);
        }
        return () => {
            if (mutationObserverRef.current) {
                mutationObserverRef.current.disconnect();
                mutationObserverRef.current = null;
            }
            if (selectionChangeListenerRef.current) {
                selectionChangeListenerRef.current();
                selectionChangeListenerRef.current = null;
            }
            // Clean up any cursor indicators on unmount
            const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
            const cursorElement = iframeDocument?.getElementById(cursorSpanId);
            if (cursorElement) {
                cursorElement.parentNode?.removeChild(cursorElement);
            }
            clearRemoteCaretPosition();
        };
    }, [context.mode, modifiedFieldsContext?.inlineEditingFieldElement]);
    function saveCaretPosition(editableElement) {
        const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
        if (!selection ||
            selection.rangeCount === 0 ||
            !editableElement.contains(selection.anchorNode)) {
            return null;
        }
        // Get the current range
        const range = selection.getRangeAt(0);
        const getGlobalOffset = (node, offset) => {
            let absoluteOffset = 0;
            function walk(current) {
                if (current === node) {
                    absoluteOffset += offset;
                    return true;
                }
                if (current.nodeType === Node.TEXT_NODE) {
                    absoluteOffset += current.length;
                }
                else {
                    for (let i = 0; i < current.childNodes.length; i++) {
                        if (walk(current.childNodes[i]))
                            return true;
                    }
                }
                return false;
            }
            walk(editableElement);
            return absoluteOffset;
        };
        return {
            startOffset: getGlobalOffset(range.startContainer, range.startOffset),
            endOffset: getGlobalOffset(range.endContainer, range.endOffset),
        };
    }
    function restoreCaretPosition(element, position) {
        const offsets = typeof position === "number"
            ? { startOffset: position, endOffset: position }
            : position;
        if (!offsets)
            return;
        const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
        if (!selection)
            return;
        selection.removeAllRanges();
        function findNodeAndOffset(targetPos) {
            let currentPos = 0;
            function walk(node) {
                if (node.nodeType === Node.TEXT_NODE) {
                    const length = node.length;
                    if (currentPos + length >= targetPos) {
                        return { node, offset: targetPos - currentPos };
                    }
                    currentPos += length;
                }
                else {
                    for (let i = 0; i < node.childNodes.length; i++) {
                        const res = walk(node.childNodes[i]);
                        if (res)
                            return res;
                    }
                }
                return null;
            }
            return walk(element);
        }
        const start = findNodeAndOffset(offsets.startOffset);
        const end = findNodeAndOffset(offsets.endOffset);
        if (start && end) {
            const range = new Range();
            range.setStart(start.node, start.offset);
            range.setEnd(end.node, end.offset);
            selection.addRange(range);
        }
        else if (element.firstChild) {
            // Fallback to end of content
            const range = new Range();
            range.selectNodeContents(element);
            range.collapse(false);
            selection.addRange(range);
        }
    }
    useEffect(() => {
        const updateFieldsWithSuggestions = async () => {
            const iframeWindow = pageViewContext.editorIframe?.contentWindow;
            if (!iframeWindow)
                return;
            const doc = iframeWindow.document;
            // First, process modified fields directly (from updateFields functionality)
            // IMPORTANT: Only update fields that belong to this page's language/version context.
            // There can be multiple InlineEditor instances (main + comparison), and each should only
            // update its own iframe's fields to avoid cross-contamination.
            const currentPageLang = pageViewContext.pageItemDescriptor?.language;
            const currentPageVer = pageViewContext.pageItemDescriptor?.version;
            for (const modField of modifiedFieldsContext?.modifiedFields ?? []) {
                // Skip fields that don't belong to this page's context
                if (currentPageLang && modField.item.language !== currentPageLang)
                    continue;
                if (currentPageVer && modField.item.version !== currentPageVer)
                    continue;
                // In SXA markup, the same field can appear multiple times for a component.
                // Update all matching nodes and only skip the exact node currently edited inline.
                const allMatchingElements = doc.querySelectorAll(`[data-fieldid="${modField.fieldId}"][data-itemid="${modField.item.id}"]`);
                const inlineEditingElement = modifiedFieldsContext?.inlineEditingFieldElement ?? null;
                const isInlineEditActive = inlineEditingElement &&
                    isElementActiveOrSelectedInIframe(doc, inlineEditingElement);
                const targetElements = Array.from(allMatchingElements).filter((candidate) => !isInlineEditActive || candidate !== inlineEditingElement);
                if (targetElements.length > 0) {
                    const realField = await context.itemsRepository.getField({
                        fieldId: modField.fieldId,
                        item: modField.item,
                    });
                    const fieldType = realField?.type;
                    if (fieldType && isTextFieldType(fieldType)) {
                        const next = modField.value ? modField.value : "";
                        let wroteAtLeastOne = false;
                        for (const targetElement of targetElements) {
                            const currentHTML = targetElement.innerHTML;
                            if (currentHTML === next)
                                continue;
                            wroteAtLeastOne = true;
                            // Suppress MutationObserver to prevent feedback loop where server-driven
                            // updates get re-sent to the backend as new operations
                            isUpdatingRef.current = true;
                            setInnerHTMLWithStackTrace(targetElement, next, {
                                source: "updateFieldsWithSuggestions:modifiedField",
                                fieldId: modField.fieldId,
                                itemId: modField.item.id,
                                language: modField.item.language,
                                version: modField.item.version,
                            });
                        }
                        if (wroteAtLeastOne) {
                            // Reset after macrotask to ensure MutationObserver callbacks (which are microtasks) are suppressed
                            setTimeout(() => {
                                isUpdatingRef.current = false;
                            }, 0);
                        }
                    }
                }
            }
            if (context.mode !== "suggestions" && !context.showSuggestedEdits) {
                return;
            }
            // Now proceed with the original updateFieldsWithSuggestions logic
            const allFieldElements = doc.querySelectorAll("[data-fieldid][data-itemid][data-language][data-version]");
            const fieldItems = await context.itemsRepository.getItems(Array.from(allFieldElements)
                .map((element) => {
                const fieldId = element.getAttribute("data-fieldid");
                return fieldId
                    ? {
                        id: fieldId,
                        language: "en",
                        version: 0,
                    }
                    : null;
            })
                .filter((item) => item !== null));
            const validFields = fieldItems.filter((item) => {
                const typeField = item.fields.find((f) => f.name === "Type");
                if (!typeField)
                    return false;
                const typeValue = typeField.value;
                return typeValue && isTextFieldType(typeValue);
            });
            const validFieldElements = Array.from(allFieldElements).filter((element) => validFields.some((item) => element.getAttribute("data-fieldid") === item.id));
            validFields.forEach(async (item, index) => {
                const fieldElement = validFieldElements[index];
                if (!fieldElement)
                    return;
                // Do not update if this field is currently being edited inline.
                // In suggestions mode we show suggested text in all fields, but must NOT overwrite
                // the focused field while the user is actively typing - that resets the cursor.
                // Treat stale iframe activeElement state as inactive once focus has left
                // the iframe, otherwise persisted suggestions can overwrite active typing.
                const fieldId = fieldElement.getAttribute("data-fieldid");
                const itemId = fieldElement.getAttribute("data-itemid");
                const language = fieldElement.getAttribute("data-language");
                const versionStr = fieldElement.getAttribute("data-version");
                if (!fieldId || !itemId || !language || !versionStr)
                    return;
                const version = parseInt(versionStr, 10);
                const isModifiedNow = modifiedFieldsContext?.modifiedFields?.some((m) => m.fieldId === fieldId &&
                    m.item.id === itemId &&
                    m.item.language === language &&
                    m.item.version === version);
                const iframeDoc = getAccessibleIframeDocument(pageViewContext.editorIframe);
                const isFieldBeingEdited = isInlineFieldBeingEdited(iframeDoc, fieldElement);
                if (isFieldBeingEdited) {
                    return;
                }
                if (isSameFieldKey(fieldElement, activeFieldKey)) {
                    if (context.mode !== "suggestions") {
                        return;
                    }
                }
                // Skip if this field currently has local modifications to avoid stomping fresh edits.
                // In suggestions mode, don't skip after the edit has been persisted - we need to
                // re-apply the merged suggestion even if the iframe still reports the field as active.
                if (isModifiedNow && context.mode !== "suggestions") {
                    return;
                }
                // Build an item descriptor.
                const descriptor = { id: itemId, language, version };
                // Fetch the current item from the repository.
                const loadedItem = await context.itemsRepository.getItem(descriptor);
                if (!loadedItem)
                    return;
                // Get the baseline from repository.
                const repositoryField = loadedItem.fields.find((f) => f.id === fieldId);
                let originalValue = repositoryField
                    ? repositoryField.rawValue || ""
                    : "";
                // If the field is modified locally, use that value.
                const modField = modifiedFieldsContext?.modifiedFields.find((mod) => mod.fieldId === fieldId &&
                    mod.item.id === itemId &&
                    mod.item.language === language &&
                    mod.item.version === version);
                if (modField) {
                    originalValue = modField.value || "";
                }
                // If showSuggestedEdits is false, update with the base value.
                if (!context.showSuggestedEdits && context.mode !== "suggestions") {
                    if (fieldElement.innerHTML !== originalValue) {
                        setInnerHTMLWithStackTrace(fieldElement, originalValue, {
                            source: "updateFieldsWithSuggestions:baselineOnly",
                            fieldId,
                            itemId,
                            language,
                            version,
                        });
                    }
                    return;
                }
                // Otherwise, gather all suggestions for this field.
                const pageId = pageViewContext.pageItemDescriptor?.id;
                const pageVer = pageViewContext.pageItemDescriptor?.version;
                const fieldSuggestions = context.suggestedEdits.filter((s) => s.status === "pending" &&
                    s.fieldId === fieldId &&
                    s.itemId === itemId &&
                    s.mainItemLanguage === language &&
                    s.mainItemId === pageId &&
                    (pageVer != null
                        ? s.mainItemVersion === pageVer || s.mainItemVersion === 0
                        : s.mainItemVersion === version));
                if (!fieldSuggestions.length)
                    return;
                // Sort suggestions in chronological order (oldest first).
                fieldSuggestions.sort((a, b) => new Date(a.created).getTime() - new Date(b.created).getTime());
                // Apply suggestions sequentially to generate the merged value.
                let mergedValue = originalValue;
                let couldApplyPatch = true;
                for (const suggestion of fieldSuggestions) {
                    // Compute a patch from the suggestion's baseline to its intended new value.
                    const patch = createPatch("field", suggestion.oldValue, suggestion.newValue);
                    const patchedCandidate = applyPatch(mergedValue, patch);
                    if (patchedCandidate !== false &&
                        typeof patchedCandidate === "string") {
                        mergedValue = patchedCandidate;
                    }
                    else {
                        couldApplyPatch = false;
                    }
                }
                if (!couldApplyPatch)
                    mergedValue = fieldSuggestions[0]?.newValue || originalValue;
                // If showSuggestedEditsDiff is false, show only the merged text
                if (!context.showSuggestedEditsDiff) {
                    if (fieldElement.innerHTML !== mergedValue) {
                        setInnerHTMLWithStackTrace(fieldElement, mergedValue, {
                            source: "updateFieldsWithSuggestions:mergedNoDiff",
                            fieldId,
                            itemId,
                            language,
                            version,
                        });
                    }
                    return;
                }
                // Compute a word-based diff between originalValue and mergedValue.
                const diffParts = diffWords(originalValue, mergedValue);
                // Build HTML markup from the diff:
                let diffHTML = "";
                diffParts.forEach((part) => {
                    let style = "";
                    if (part.added) {
                        style = "color: green;";
                    }
                    else if (part.removed) {
                        style = "color: red; text-decoration: line-through;";
                    }
                    else {
                        style = "color: gray;";
                    }
                    // Escape any HTML in part.value if needed.
                    diffHTML += `<span style="${style}">${part.value}</span>`;
                });
                // Update the element's innerHTML with the diff markup.
                if (fieldElement.innerHTML !== diffHTML) {
                    setInnerHTMLWithStackTrace(fieldElement, diffHTML, {
                        source: "updateFieldsWithSuggestions:diffMarkup",
                        fieldId,
                        itemId,
                        language,
                        version,
                    });
                }
            });
        };
        updateFieldsWithSuggestions();
    }, [
        context.mode,
        modifiedFieldsContext?.modifiedFields,
        context?.itemsRepository.revision,
        modifiedFieldsContext?.inlineEditingFieldElement,
        context?.showSuggestedEdits,
        context?.suggestedEdits,
        pageViewContext.pageItemDescriptor,
        pageViewContext.page,
        context.showSuggestedEditsDiff,
        context.showSuggestedEdits,
        context.remoteCarets,
    ]);
    useEffect(() => {
        if (!context || compareView)
            return;
        const iframeDoc = getAccessibleIframeDocument(pageViewContext.editorIframe);
        if (!iframeDoc)
            return;
        // Get current page language and version to filter elements - use currentItemDescriptor as fallback during transitions
        const resetEffectPageLanguage = context.page?.item?.language ?? context.currentItemDescriptor?.language;
        const resetEffectPageVersion = context.page?.item?.version ?? context.currentItemDescriptor?.version;
        // Query all field elements
        const allFieldEls = Array.from(iframeDoc.querySelectorAll("[data-fieldid][data-itemid][data-language][data-version]"));
        allFieldEls.forEach(async (el) => {
            // don't stomp on the one that's live‑editing (only in non-preview modes)
            if (context.mode !== "preview" && isSameFieldKey(el, activeFieldKey))
                return;
            if (context.mode !== "preview" &&
                isElementActiveOrSelectedInIframe(iframeDoc, el)) {
                return;
            }
            const fieldId = el.getAttribute("data-fieldid");
            const itemId = el.getAttribute("data-itemid");
            const language = el.getAttribute("data-language");
            const version = parseInt(el.getAttribute("data-version"), 10);
            // CRITICAL FIX: Skip elements from other languages or versions during transitions
            // to prevent cross-language/version contamination during rapid switching
            if (resetEffectPageLanguage && language !== resetEffectPageLanguage) {
                return;
            }
            if (resetEffectPageVersion && version !== resetEffectPageVersion) {
                return;
            }
            // In suggestions mode (but not preview), only reset fields that don't have any suggested edits
            if (context.mode === "suggestions" ||
                (context.mode !== "preview" && context.showSuggestedEdits)) {
                const hasActiveSuggestions = context.suggestedEdits.some((edit) => edit.status === "pending" &&
                    edit.fieldId === fieldId &&
                    edit.itemId === itemId &&
                    edit.mainItemLanguage === language &&
                    (resetEffectPageVersion != null
                        ? edit.mainItemVersion === resetEffectPageVersion ||
                            edit.mainItemVersion === 0
                        : edit.mainItemVersion === version));
                // If field has active suggestions, don't reset it (let the other hook handle it)
                if (hasActiveSuggestions)
                    return;
            }
            const realField = await context.itemsRepository.getItem({
                id: fieldId,
                language: "en",
                version: 0,
            });
            const fieldType = realField?.fields.find((f) => f.name === "Type")?.value;
            if (!fieldType || !isTextFieldType(fieldType)) {
                return;
            }
            // lookup baseline
            const loaded = await context.itemsRepository.getItem({
                id: itemId,
                language,
                version,
            });
            const repoF = loaded?.fields.find((f) => f.id === fieldId);
            let value = repoF?.rawValue ?? "";
            // override with any local edit (only in non-preview modes)
            // CRITICAL FIX: Only apply modified fields that match the current page language and version
            // to prevent cross-language/version contamination during rapid switching
            if (context.mode !== "preview" &&
                resetEffectPageLanguage &&
                language === resetEffectPageLanguage &&
                resetEffectPageVersion &&
                version === resetEffectPageVersion) {
                const mod = modifiedFieldsContext?.modifiedFields.find((m) => m.fieldId === fieldId &&
                    m.item.id === itemId &&
                    m.item.language === language &&
                    m.item.version === version);
                if (mod)
                    value = mod.value;
            }
            // write it in
            if (el.innerHTML !== value) {
                setInnerHTMLWithStackTrace(el, value, {
                    source: "resetAllFieldsToBaselineOrLocal",
                    fieldId,
                    itemId,
                    language,
                    version,
                });
            }
        });
    }, [
        context.mode,
        context.showSuggestedEdits,
        context.suggestedEdits,
        context.remoteCarets,
    ]);
    useEffect(() => {
        if (compareView || context.mode === "preview") {
            hideInlineFormatOverlay();
            return;
        }
        const iframe = pageViewContext.editorIframe;
        if (!iframe) {
            hideInlineFormatOverlay();
            return;
        }
        let detachDocumentListeners = null;
        const attachToCurrentIframeDocument = () => {
            detachDocumentListeners?.();
            const iframeDocument = getAccessibleIframeDocument(iframe);
            if (!iframeDocument) {
                detachDocumentListeners = null;
                hideInlineFormatOverlay();
                return;
            }
            let animationFrame = 0;
            const scheduleUpdate = () => {
                window.cancelAnimationFrame(animationFrame);
                animationFrame = window.requestAnimationFrame(updateInlineFormatOverlay);
            };
            const handlePointerDown = () => {
                inlineFormatPointerDownRef.current = true;
                hideInlineFormatOverlay();
            };
            const handlePointerUp = () => {
                inlineFormatPointerDownRef.current = false;
                scheduleUpdate();
            };
            iframeDocument.addEventListener("selectionchange", scheduleUpdate);
            iframeDocument.addEventListener("pointerdown", handlePointerDown);
            iframeDocument.addEventListener("pointerup", handlePointerUp);
            iframeDocument.addEventListener("mouseup", scheduleUpdate);
            iframeDocument.addEventListener("keyup", scheduleUpdate);
            iframe.contentWindow?.addEventListener("scroll", scheduleUpdate, true);
            window.addEventListener("scroll", scheduleUpdate, true);
            window.addEventListener("resize", scheduleUpdate);
            scheduleUpdate();
            detachDocumentListeners = () => {
                window.cancelAnimationFrame(animationFrame);
                iframeDocument.removeEventListener("selectionchange", scheduleUpdate);
                iframeDocument.removeEventListener("pointerdown", handlePointerDown);
                iframeDocument.removeEventListener("pointerup", handlePointerUp);
                iframeDocument.removeEventListener("mouseup", scheduleUpdate);
                iframeDocument.removeEventListener("keyup", scheduleUpdate);
                iframe.contentWindow?.removeEventListener("scroll", scheduleUpdate, true);
                window.removeEventListener("scroll", scheduleUpdate, true);
                window.removeEventListener("resize", scheduleUpdate);
            };
        };
        attachToCurrentIframeDocument();
        iframe.addEventListener("load", attachToCurrentIframeDocument);
        return () => {
            iframe.removeEventListener("load", attachToCurrentIframeDocument);
            detachDocumentListeners?.();
        };
    }, [compareView, context.mode, pageViewContext.editorIframe]);
    useEffect(() => {
        const animationFrame = window.requestAnimationFrame(updateInlineFormatOverlay);
        return () => window.cancelAnimationFrame(animationFrame);
    }, [
        context.selectedRange?.fieldId,
        context.selectedRange?.itemId,
        context.selectedRange?.language,
        context.selectedRange?.version,
        context.selectedRange?.startOffset,
        context.selectedRange?.endOffset,
        context.selectedRange?.text,
    ]);
    useEffect(() => {
        if (!inlineFormatOverlay.visible)
            return;
        const handlePointerDown = (event) => {
            const target = event.target;
            if (target &&
                (inlineFormatOverlayRef.current?.contains(target) ||
                    target === pageViewContext.editorIframe)) {
                return;
            }
            hideInlineFormatOverlay();
        };
        window.addEventListener("pointerdown", handlePointerDown, true);
        return () => {
            window.removeEventListener("pointerdown", handlePointerDown, true);
        };
    }, [inlineFormatOverlay.visible, pageViewContext.editorIframe]);
    const visibleInlineFormatCommands = inlineFormatSelectionRef.current
        ? (inlineFormatCommandsCacheRef.current.get(getInlineFieldKey(inlineFormatSelectionRef.current.fieldElement)) ?? [])
        : [];
    if (!inlineFormatOverlay.visible ||
        visibleInlineFormatCommands.length === 0) {
        return null;
    }
    const overlay = (_jsx("div", { ref: inlineFormatOverlayRef, className: "border-border-default bg-neutral-grey-5 fixed z-[1000] flex h-8 items-center gap-0.5 rounded-md border px-0.5 py-px shadow-lg", style: {
            left: inlineFormatOverlay.left,
            top: inlineFormatOverlay.top,
        }, onMouseDown: (event) => {
            event.preventDefault();
            event.stopPropagation();
        }, children: visibleInlineFormatCommands.map((item) => {
            const isActive = inlineFormatOverlay.activeCommandIds.includes(item.id);
            return (_jsx("button", { type: "button", className: `inline-flex h-7 w-7 items-center justify-center rounded-sm p-0 text-[13px] leading-none font-semibold focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 ${isActive
                    ? "text-neutral-grey-100 bg-white"
                    : "bg-neutral-grey-5 text-neutral-grey-100 hover:bg-neutral-grey-10"}`, title: item.label, "aria-label": item.label, "aria-pressed": isActive, onMouseDown: (event) => void applyInlineFormatCommand(item, event), children: _jsx("span", { "aria-hidden": "true", className: "inline-flex translate-y-px items-center justify-center leading-none", children: item.icon ?? item.label }) }, item.id));
        }) }));
    return createPortal(overlay, document.body);
}
//# sourceMappingURL=InlineEditor.js.map