import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useEffect, useRef, useState } from "react";
import { MiniMap } from "./MiniMap";
import { useEditContext, useEditContextRef, useFieldsEditContext, useFieldsEditContextRef, } from "../client/editContext";
import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
import { PageEditorChrome } from "../page-editor-chrome/PageEditorChrome";
import { useViewportChangeSignal } from "./pageViewContext";
import morphdom from "morphdom";
import uuid from "react-uuid";
import { cn } from "../../lib/utils";
import { findComponentRect, findFieldElement, findNearestEditableComponentId, findParentWithAttribute, getAbsolutePosition, getFieldDescriptorFromElement, findClosestFieldElement, extractItemIdFromItemUri, } from "../utils";
import { extractDOMSelectionContext } from "../utils/selectionContext";
import { cleanId } from "../utils/id-helper";
import { getAllComponentInstances, getComponentById, } from "../componentTreeHelper";
import { buildComponentContextMenuItems } from "../ContextMenu";
import { loadFieldButtons } from "../services/editService";
import { usePathname } from "../client/navigation";
import { EditorWarnings } from "../EditorWarnings";
import { FieldActionsOverlay, } from "../FieldActionsOverlay";
import { NoLayout } from "../page-editor-chrome/NoLayout";
import { Spinner } from "../ui/Spinner";
import { DeviceToolbar } from "./DeviceToolbar";
import { buildPageModelSkeleton } from "./pageModelSkeletonBuilder";
import { toSitecoreDate } from "../utils/sitecoreDate";
const EDITOR_CSS_STYLE_ID = "parhelia-editor-css";
export function PageViewerFrame({ compareView, pageViewContext, className, slotCloseButton, }) {
    const editContext = useEditContext();
    const fieldsContext = useFieldsEditContext();
    useViewportChangeSignal();
    const pathname = usePathname();
    const pageViewContextRef = useRef(undefined);
    if (!editContext || !pageViewContext)
        return null;
    useEffect(() => {
        pageViewContextRef.current = pageViewContext;
    }, [pageViewContext]);
    const editContextRef = useEditContextRef();
    const fieldsContextRef = useFieldsEditContextRef();
    const iframeRef = useRef(null);
    const currentLoadRef = useRef(null);
    const rebindIframeInteractionsRef = useRef(null);
    const prevModeRef = useRef(editContext.mode);
    const [showSpinner, setShowSpinner] = useState(false);
    const [scroll, setScroll] = useState(0);
    const [showMiniMap, setShowMiniMap] = useState(false);
    const fieldActionsOverlay = useRef(null);
    const [contextMenuFieldButtons, setContextMenuFieldButtons] = useState([]);
    const [contextMenuField, setContextMenuField] = useState();
    const [contextMenuPosition, setContextMenuPosition] = useState();
    const [preSelectedAction, setPreSelectedAction] = useState();
    // Clear preSelectedAction when overlay is closed
    useEffect(() => {
        if (editContext?.currentOverlay !==
            `${contextMenuField?.fieldId || ""}_generators`) {
            setPreSelectedAction(undefined);
        }
    }, [editContext?.currentOverlay, contextMenuField?.fieldId]);
    const zoom = pageViewContext.zoom;
    const blockBlurEventRef = useRef(0);
    const [currentItemDescriptor, setCurrentItemDescriptor] = useState(undefined);
    // Field action handlers for the overlay
    const handleActionClick = async (action, event) => {
        if (contextMenuField) {
            editContext?.triggerFieldAction(contextMenuField, action, undefined, event);
        }
    };
    const handleParameterizedActionExecute = async (action, parameters) => {
        if (contextMenuField) {
            editContext?.triggerFieldAction(contextMenuField, action, parameters);
        }
    };
    // Note: handleParameterizedActionFromContextMenu is now created inline in handleContextMenu
    // to avoid React state timing issues with contextMenuPosition
    const pageItemDescriptor = pageViewContext.pageItemDescriptor;
    // Update the context whenever the iframe ref changes
    useEffect(() => {
        pageViewContext.setEditorIframe(iframeRef.current);
    }, [iframeRef.current, pageViewContext.setEditorIframe]);
    const updateMiniMapVisibility = useDebouncedCallback(() => {
        if (!iframeRef.current)
            return;
        const iframe = iframeRef.current;
        const doc = iframe.contentDocument;
        if (!doc?.documentElement)
            return;
        const scrollContainer = doc.scrollingElement || doc.body;
        if (!scrollContainer)
            return;
        const contentHeight = scrollContainer.scrollHeight;
        const clientHeight = iframe.clientHeight;
        const upperThreshold = clientHeight + 100; // show minimap if content exceeds this height
        const lowerThreshold = clientHeight; // hide minimap if content falls below this
        // Check if minimap is enabled in settings and user controls
        const minimapEnabled = editContext.parheliaSettings?.showMinimap !== false &&
            editContext.showMinimap;
        if (showMiniMap) {
            if (contentHeight <= lowerThreshold || !minimapEnabled) {
                setShowMiniMap(false);
            }
        }
        else {
            if (contentHeight > upperThreshold && minimapEnabled) {
                setShowMiniMap(true);
            }
        }
    }, 100);
    updateMiniMapVisibility();
    const buildPageModelThrottled = useThrottledCallback(buildPageModelSkeleton, 200, {
        leading: true,
        trailing: true,
    });
    const requestPageModelBuild = (doc) => {
        if (!doc)
            return;
        buildPageModelThrottled(doc, editContextRef, pageViewContextRef);
    };
    const [iframeSrc, setIframeSrc] = useState();
    const editorCssGuardRef = useRef({ doc: null, observer: null, editMode: true });
    const ensureEditorCssGuard = (doc, editMode) => {
        if (!doc)
            return;
        // Keep latest mode so the observer can re-apply the right CSS after hydration/head updates.
        editorCssGuardRef.current.editMode = editMode;
        const needsNewObserver = editorCssGuardRef.current.doc !== doc;
        if (needsNewObserver) {
            try {
                editorCssGuardRef.current.observer?.disconnect();
            }
            catch { }
            editorCssGuardRef.current.doc = doc;
            editorCssGuardRef.current.observer = null;
            // Guard against frameworks (Next/Head managers) removing/replacing our injected <style>.
            let isApplying = false;
            const observer = new MutationObserver(() => {
                const currentDoc = editorCssGuardRef.current.doc;
                if (!currentDoc || currentDoc !== doc)
                    return;
                if (isApplying)
                    return;
                const head = currentDoc.head;
                if (!head)
                    return;
                const style = head.querySelector(`#${EDITOR_CSS_STYLE_ID}`);
                if (style)
                    return;
                try {
                    isApplying = true;
                    injectEditorCSS(currentDoc, editorCssGuardRef.current.editMode);
                }
                finally {
                    isApplying = false;
                }
            });
            // Observe <head> for style removals, and <html> for rare head replacements.
            try {
                if (doc.head)
                    observer.observe(doc.head, { childList: true });
                if (doc.documentElement)
                    observer.observe(doc.documentElement, { childList: true });
            }
            catch { }
            editorCssGuardRef.current.observer = observer;
        }
        // Apply now, and again shortly after to catch post-hydration head normalization.
        injectEditorCSS(doc, editMode);
        setTimeout(() => {
            if (editorCssGuardRef.current.doc === doc) {
                injectEditorCSS(doc, editorCssGuardRef.current.editMode);
            }
        }, 0);
    };
    // Disconnect the CSS guard on unmount.
    useEffect(() => {
        return () => {
            try {
                editorCssGuardRef.current.observer?.disconnect();
            }
            catch { }
            editorCssGuardRef.current.observer = null;
            editorCssGuardRef.current.doc = null;
        };
    }, []);
    // If the editor mode flips (edit/preview), re-apply CSS and disable inline editing in the iframe.
    useEffect(() => {
        const doc = iframeRef.current?.contentDocument;
        const isPreview = editContext.mode === "preview";
        if (doc)
            ensureEditorCssGuard(doc, !isPreview);
        if (isPreview) {
            editContextRef.current?.setSelectedRange(undefined);
            fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
        }
    }, [editContext.mode]);
    useEffect(() => {
        if (!pageItemDescriptor ||
            !pageViewContext.editUrl ||
            !pageViewContext.previewUrl)
            return;
        const urlPath = editContext.mode === "preview"
            ? pageViewContext.previewUrl
            : pageViewContext.editUrl;
        const prevMode = prevModeRef.current;
        prevModeRef.current = editContext.mode;
        const modeOnlyChange = prevMode !== editContext.mode &&
            prevMode !== "preview" &&
            editContext.mode !== "preview";
        const savedScrollY = iframeRef.current?.contentWindow?.scrollY ?? 0;
        const renderUrl = new URL(urlPath, window.location.origin);
        const editRev = uuid();
        renderUrl.searchParams.set("edit_rev", editRev);
        if (editContext.mode !== "preview" && editContext.sessionId) {
            renderUrl.searchParams.set("parhelia_session", editContext.sessionId);
        }
        if (editContext.mode === "preview" && editContext.previewDate) {
            renderUrl.searchParams.delete("sc_version");
            renderUrl.searchParams.set("sc_date", toSitecoreDate(editContext.previewDate));
        }
        // Detect if the version in the URL changed - this requires a full reload, not just requestRefresh
        // because Next.js router.replace may not properly refetch server data for version changes.
        const currentIframeUrl = iframeRef.current?.contentWindow?.location?.href;
        const currentVersion = currentIframeUrl
            ? new URL(currentIframeUrl).searchParams.get("sc_version")
            : null;
        const newVersion = renderUrl.searchParams.get("sc_version");
        const versionChanged = (currentVersion !== null || newVersion !== null) &&
            currentVersion !== newVersion;
        const initialLoad = currentItemDescriptor?.id !== pageItemDescriptor.id ||
            currentItemDescriptor?.language !== pageItemDescriptor.language ||
            currentItemDescriptor?.version !== pageItemDescriptor.version;
        const shouldUseIframeSrcReload = initialLoad || pageViewContext.fullscreen;
        const runIntegrationRefresh = (refreshFn, message) => {
            console.log(message);
            refreshFn(renderUrl.toString());
            const doc = iframeRef.current?.contentDocument;
            if (doc) {
                requestPageModelBuild(doc);
            }
            // The integration refresh path may replace/normalize <head> during hydration,
            // which can remove our injected style. Re-ensure a few times post-refresh.
            const ensureLater = (delay) => {
                setTimeout(() => {
                    const doc = iframeRef.current?.contentDocument;
                    if (doc)
                        ensureEditorCssGuard(doc, editContext.mode !== "preview");
                    rebindIframeInteractionsRef.current?.();
                }, delay);
            };
            ensureLater(0);
            ensureLater(100);
            ensureLater(500);
            ensureLater(1200);
            if (modeOnlyChange && savedScrollY > 0) {
                const restoreScroll = (delay) => {
                    setTimeout(() => {
                        const win = iframeRef.current?.contentWindow;
                        if (win && win.scrollY === 0) {
                            win.scrollTo(0, savedScrollY);
                        }
                    }, delay);
                };
                restoreScroll(50);
                restoreScroll(200);
                restoreScroll(500);
                restoreScroll(1000);
                restoreScroll(2000);
            }
        };
        const runFallbackRefresh = () => {
            setShowSpinner(true);
            if (shouldUseIframeSrcReload) {
                // For initial loads and fullscreen mode, prefer a full iframe src reload.
                // The morphdom path can race with framework hydration and leave the frame blank.
                console.log(initialLoad
                    ? "Initial load - setting iframe src"
                    : "Fullscreen load - setting iframe src");
                // Clear any stale load tracking to prevent handleLoad from incorrectly
                // skipping spinner dismissal due to revision mismatch
                currentLoadRef.current = null;
                setIframeSrc(renderUrl.toString());
            }
            else {
                console.log("No integration - reloading frame via fetch");
                // Abort any in-flight load before starting a new one
                try {
                    currentLoadRef.current?.controller.abort();
                }
                catch { }
                const controller = new AbortController();
                const expectedRevision = editContext.revision ?? "";
                currentLoadRef.current = { controller, revision: expectedRevision };
                loadContent(renderUrl.toString(), initialLoad, expectedRevision, controller.signal);
            }
        };
        // Use requestRefresh for normal refreshes, but force full reload for version changes.
        // If requestRefresh is temporarily unavailable, wait briefly before falling back.
        const integrationRefreshFn = iframeRef.current?.contentWindow?.requestRefresh;
        if (!versionChanged && integrationRefreshFn) {
            runIntegrationRefresh(integrationRefreshFn, "Integration - requesting refresh");
        }
        else if (!versionChanged && !shouldUseIframeSrcReload) {
            const retryDelayMs = 150;
            const retryTimer = setTimeout(() => {
                const retryIntegrationRefreshFn = iframeRef.current?.contentWindow?.requestRefresh;
                if (retryIntegrationRefreshFn) {
                    runIntegrationRefresh(retryIntegrationRefreshFn, "Integration became available after brief wait - requesting refresh");
                    return;
                }
                runFallbackRefresh();
            }, retryDelayMs);
            setCurrentItemDescriptor(pageItemDescriptor);
            return () => clearTimeout(retryTimer);
        }
        else {
            runFallbackRefresh();
        }
        setCurrentItemDescriptor(pageItemDescriptor);
    }, [
        pathname,
        editContext.revision,
        pageItemDescriptor,
        pageViewContext.editUrl,
        pageViewContext.previewUrl,
        pageViewContext.fullscreen,
        editContext.mode,
        editContext.previewDate,
        editContext.sessionId,
    ]);
    useEffect(() => {
        if (fieldsContext?.focusedField) {
            if (editContext.selection.length > 0 &&
                fieldsContext.focusedField.item.id !== editContext.selection[0])
                return;
            const fieldElement = findFieldElement(iframeRef.current, fieldsContext.focusedField);
            if (fieldElement) {
                const rect = getAbsolutePosition(fieldElement, iframeRef.current);
                const scrollTop = iframeRef.current?.contentWindow?.scrollY || 0;
                const iframeHeight = iframeRef.current?.getBoundingClientRect().height;
                const isInViewport = rect.y + rect.height > scrollTop &&
                    rect.y < scrollTop + (iframeHeight || 0);
                if (!isInViewport) {
                    iframeRef.current?.contentWindow?.scrollTo({
                        top: rect.y,
                        behavior: "smooth",
                    });
                }
            }
        }
    }, [fieldsContext?.focusedField]);
    useEffect(() => {
        if (!fieldsContext?.focusedField && editContext.selection.length > 0) {
            const lastSelectedComponent = getComponentById(editContext.selection[editContext.selection.length - 1], pageViewContextRef.current.page);
            if (lastSelectedComponent) {
                editContext.setScrollIntoView(lastSelectedComponent.id);
            }
        }
    }, [editContext.selection, fieldsContext?.focusedField]);
    const loadContent = async (href, initialLoad, expectedRevision, signal) => {
        console.log("Loading content:", href);
        const start = performance.now();
        if (!href)
            return;
        try {
            const content = await fetch(href, { signal });
            // Detect redirect to login page (session expired)
            if (content.redirected) {
                const redirectUrl = content.url.toLowerCase();
                if (redirectUrl.includes("/identity/login") ||
                    redirectUrl.includes("/sitecore/login") ||
                    redirectUrl.includes("returnurl=")) {
                    console.warn("Session expired - redirected to login page:", content.url);
                    setShowSpinner(false);
                    // Redirect the main window to the login page
                    window.location.href = content.url;
                    return;
                }
            }
            const text = await content.text();
            console.log("Content loaded in " + (performance.now() - start) + " ms");
            // Skip applying if this response is stale
            if (expectedRevision !== (editContextRef.current?.revision ?? "")) {
                console.log("Stale refresh skipped", {
                    expectedRevision,
                    current: editContextRef.current?.revision,
                });
                return;
            }
            console.log("Content loaded in " + (performance.now() - start) + " ms");
            const doc = iframeRef.current?.contentDocument;
            if (doc) {
                if (initialLoad) {
                    // Guard again just before applying
                    if (expectedRevision !== (editContextRef.current?.revision ?? "")) {
                        console.log("Stale initial load skipped", {
                            expectedRevision,
                            current: editContextRef.current?.revision,
                        });
                        return;
                    }
                    doc.open();
                    doc.write(text);
                    doc.close();
                }
                else {
                    const parser = new DOMParser();
                    const newDoc = parser.parseFromString(text, "text/html");
                    // Guard before morphing DOM to avoid applying stale content
                    if (expectedRevision !== (editContextRef.current?.revision ?? "")) {
                        console.log("Stale morphdom skipped", {
                            expectedRevision,
                            current: editContextRef.current?.revision,
                        });
                        return;
                    }
                    morphdom(doc.documentElement, newDoc.documentElement);
                    rebindIframeInteractionsRef.current?.();
                    if (iframeRef.current.contentWindow.XA) {
                        console.log("init XA");
                        iframeRef.current.contentWindow.XA.init();
                    }
                    setShowSpinner(false);
                }
                ensureEditorCssGuard(doc, editContext.mode !== "preview");
                setTimeout(() => {
                    injectSXAScripts(iframeRef.current);
                }, 1000);
                try {
                    requestPageModelBuild(doc);
                }
                catch (buildErr) { }
            }
        }
        catch (err) {
            if (err?.name === "AbortError") {
                // Swallow aborts – a newer refresh superseded this one
                return;
            }
            throw err;
        }
    };
    useEffect(() => {
        if (!editContext.scrollIntoView ||
            !iframeRef.current?.contentDocument?.documentElement)
            return;
        const component = getComponentById(editContext.scrollIntoView, pageViewContextRef.current.page);
        if (!component)
            return;
        const rect = findComponentRect(iframeRef.current, component, true);
        if (!rect)
            return;
        // Check if element is already in viewport
        const iframeHeight = iframeRef.current.getBoundingClientRect().height;
        const scrollTop = iframeRef.current?.contentWindow?.scrollY || 0;
        const elementTop = rect.rect.y;
        const isInViewport = elementTop >= scrollTop && elementTop <= scrollTop + iframeHeight;
        // If already in viewport, no need to scroll
        if (isInViewport) {
            editContext.setScrollIntoView(undefined);
            return;
        }
        const scrollPosition = rect.rect.y - iframeHeight / 2 + rect.rect.height / 2;
        iframeRef.current?.contentWindow?.scrollTo({
            top: scrollPosition,
            behavior: "smooth",
        });
        editContext.setScrollIntoView(undefined);
    }, [editContext.scrollIntoView, pageViewContext.page]);
    useEffect(() => {
        const handleMessage = (message) => {
            if (message.data.type === "editor-exitFullscreen") {
                pageViewContext.setFullscreen(false);
            }
            if (message.data.type === "editor-timings") {
                editContext.setTimings(message.data.timings);
            }
        };
        window.addEventListener("message", handleMessage);
        return () => {
            window.removeEventListener("message", handleMessage);
        };
    }, []);
    const selecionChangeHandler = useDebouncedCallback(() => {
        const sel = iframeRef.current?.contentDocument?.getSelection();
        const isInlineAiUiFocused = () => {
            const activeEl = document.activeElement;
            return !!(activeEl?.closest(".agent-inline-dialog") ||
                activeEl?.closest(".agent-inline-trigger") ||
                activeEl?.closest('[role="dialog"]'));
        };
        if (!sel || sel.rangeCount === 0) {
            if (!isInlineAiUiFocused()) {
                editContextRef.current?.setSelectedRange(undefined);
            }
            return;
        }
        // Preserve the last non-collapsed selection while the inline AI UI takes focus.
        // This avoids losing context when users click the trigger or dialog.
        if (sel.isCollapsed && isInlineAiUiFocused()) {
            return;
        }
        // Find the field element containing the selection/caret
        const fieldElement = findClosestFieldElement(sel.anchorNode);
        if (!fieldElement) {
            if (!isInlineAiUiFocused()) {
                editContextRef.current?.setSelectedRange(undefined);
            }
            return;
        }
        const fieldId = fieldElement.getAttribute("data-fieldid");
        if (!fieldId)
            return;
        const range = sel.getRangeAt(0);
        // Guard: if layout components are hidden, do not set focus for fields of layout components
        if (editContextRef.current?.showLayoutComponents === false &&
            pageViewContextRef.current?.page) {
            const ownerId = fieldElement.getAttribute("data-itemid") || "";
            const owner = getComponentById(ownerId, pageViewContextRef.current.page);
            if (owner?.layoutId)
                return;
        }
        // Compute the global offsets relative to the field element.
        const globalStartOffset = getGlobalTextOffset(fieldElement, range.startContainer, range.startOffset);
        const globalEndOffset = getGlobalTextOffset(fieldElement, range.endContainer, range.endOffset);
        const selectedText = range.toString();
        // Extract plain text context from the DOM
        const { contextBefore, contextAfter } = extractDOMSelectionContext(fieldElement, globalStartOffset, globalEndOffset);
        // Clone the range for the replaceText callback (ranges can become invalid after DOM changes)
        const rangeClone = range.cloneRange();
        const itemId = fieldElement.getAttribute("data-itemid") || "";
        const language = fieldElement.getAttribute("data-language") || "";
        const versionStr = fieldElement.getAttribute("data-version");
        const version = versionStr ? parseInt(versionStr, 10) : 0;
        editContextRef.current?.setSelectedRange({
            itemId,
            fieldId: fieldId,
            language,
            version,
            startOffset: globalStartOffset,
            endOffset: globalEndOffset,
            text: selectedText,
            contextBefore,
            contextAfter,
            // Create a callback that uses DOM Range API to replace text in contenteditable
            replaceText: (newText) => {
                try {
                    // Delete the selected content (no-op for collapsed ranges)
                    rangeClone.deleteContents();
                    // Insert the new text - use the correct document from the range containers
                    const doc = rangeClone.startContainer.ownerDocument || document;
                    const textNode = doc.createTextNode(newText);
                    rangeClone.insertNode(textNode);
                    // Collapse the range to the end of the inserted text
                    rangeClone.setStartAfter(textNode);
                    rangeClone.collapse(true);
                    // Update the selection
                    const iframeSel = iframeRef.current?.contentDocument?.getSelection();
                    if (iframeSel) {
                        iframeSel.removeAllRanges();
                        iframeSel.addRange(rangeClone);
                    }
                    // Explicitly save the field value since the MutationObserver may not be active
                    // when text is selected without entering inline edit mode
                    const isRichText = fieldElement?.getAttribute("data-is-richtext") === "true";
                    const valueToSave = isRichText
                        ? fieldElement?.innerHTML
                        : fieldElement?.innerText;
                    if (fieldId &&
                        itemId &&
                        language &&
                        version &&
                        editContextRef.current) {
                        editContextRef.current.operations.editField({
                            field: {
                                fieldId,
                                fieldName: fieldElement?.getAttribute("data-fieldname") || undefined,
                                item: { id: itemId, language, version },
                            },
                            refresh: "none",
                            value: valueToSave,
                        });
                    }
                }
                catch (error) {
                    console.error("[PageViewerFrame] Failed to replace text:", error);
                }
            },
        });
    }, 300);
    useEffect(() => {
        const iframe = iframeRef.current;
        if (!iframe)
            return;
        let boundDocument = null;
        let boundDocumentElement = null;
        let boundScrollContainer = null;
        let mutationObserver = null;
        const handleIframeMouseDown = async (event) => {
            const target = event.target;
            const targetElement = target;
            if (editContextRef.current?.isRefreshing && showSpinner)
                return;
            // Activate the editor slot this iframe belongs to
            const slotElement = iframe?.closest("[data-editor-slot]");
            const slotId = slotElement?.getAttribute("data-slotid");
            if (slotId && editContextRef.current?.activeSlotId !== slotId) {
                editContextRef.current?.setActiveSlot(slotId);
            }
            // Skip selection changes on right-click (button 2) - let context menu handler deal with it
            if (event.button === 2)
                return;
            const fieldElement = targetElement
                ? findParentWithAttribute(targetElement, "data-fieldid")
                : null;
            const pageForSelection = pageViewContextRef.current?.page;
            const fieldDescriptor = fieldElement?.hasAttribute("data-itemid")
                ? getFieldDescriptorFromElement(fieldElement)
                : undefined;
            const componentIdFromField = fieldDescriptor && targetElement && pageForSelection
                ? resolveComponentIdForFieldTarget(fieldDescriptor, targetElement, pageForSelection)
                : undefined;
            const rawComponentId = componentIdFromField ??
                (targetElement ? findNearestEditableComponentId(targetElement) : undefined);
            let componentId = rawComponentId;
            if (!componentIdFromField && componentId && pageForSelection) {
                componentId = resolveComponentIdForTarget(componentId, targetElement, pageForSelection);
            }
            // Layout components can still be selected even when showLayoutComponents is false
            // They will be displayed in read-only mode
            const currentOverlayName = editContextRef.current?.currentOverlay;
            const isGeneratorOverlay = !!(currentOverlayName &&
                typeof currentOverlayName === "string" &&
                currentOverlayName.endsWith("_generators"));
            // Don't close context-menu overlay on mousedown - let it handle its own closing
            if (editContextRef.current?.currentOverlay &&
                !isGeneratorOverlay &&
                editContextRef.current.currentOverlay !== "context-menu")
                editContextRef.current?.setCurrentOverlay(undefined);
            if (componentId) {
                const currentSelection = editContextRef.current?.selection || [];
                if (event.shiftKey && currentSelection.length > 0) {
                    const page = pageViewContextRef.current?.page;
                    if (page) {
                        // Build ordered list of visible component ids by DOM order
                        const doc = iframeRef.current?.contentDocument;
                        const orderedIds = [];
                        if (doc) {
                            const all = Array.from(doc.querySelectorAll("[data-component-id]"));
                            for (const el of all) {
                                const id = el.getAttribute("data-component-id");
                                if (!id)
                                    continue;
                                // Layout components are now selectable even when showLayoutComponents is false
                                orderedIds.push(id);
                            }
                        }
                        const anchorId = currentSelection[currentSelection.length - 1];
                        const aIdx = orderedIds.indexOf(anchorId);
                        const bIdx = orderedIds.indexOf(componentId);
                        if (aIdx !== -1 && bIdx !== -1) {
                            const start = Math.min(aIdx, bIdx);
                            const end = Math.max(aIdx, bIdx);
                            const range = orderedIds.slice(start, end + 1);
                            if (event.ctrlKey) {
                                const union = new Set([...currentSelection, ...range]);
                                editContextRef.current?.select(Array.from(union));
                            }
                            else {
                                editContextRef.current?.select(range);
                            }
                        }
                        else {
                            editContextRef.current?.select([componentId]);
                        }
                    }
                    else {
                        editContextRef.current?.select([componentId]);
                    }
                }
                else if (event.ctrlKey) {
                    // Toggle selection: add if not selected, remove if already selected
                    const idx = currentSelection.indexOf(componentId);
                    if (idx === -1) {
                        editContextRef.current?.select([...currentSelection, componentId]);
                    }
                    else {
                        const newSelection = [...currentSelection];
                        newSelection.splice(idx, 1);
                        editContextRef.current?.select(newSelection);
                    }
                }
                else {
                    // Regular click: always select just this component
                    editContextRef.current?.select([componentId]);
                }
                //  if (mode !== "edit") {
                //editContextRef.current?.setScrollIntoView(componentId);
                //}
            }
            else {
                editContextRef.current?.select([]);
            }
            // Skip field focusing when using modifier keys for multi-selection
            if (!event.ctrlKey &&
                !event.shiftKey &&
                ((editContextRef.current?.mode === "edit" &&
                    pageViewContextRef.current?.page?.item.canWriteItem) ||
                    editContextRef.current?.mode === "suggestions")) {
                if (fieldElement?.hasAttribute("data-itemid")) {
                    // Guard: if layout components are hidden, do not allow editing fields of layout components
                    if (editContextRef.current?.showLayoutComponents === false &&
                        pageViewContextRef.current?.page) {
                        const owningItemId = fieldElement.getAttribute("data-itemid") || "";
                        const ownerComponent = getComponentById(owningItemId, pageViewContextRef.current.page);
                        if (ownerComponent?.layoutId) {
                            return;
                        }
                    }
                    blockBlurEventRef.current = Date.now() + 500;
                    const shouldRequestLock = editContextRef.current?.mode !== "suggestions";
                    const hasLock = (await fieldsContextRef.current?.setFocusedField(fieldDescriptor, shouldRequestLock)) ?? false;
                    if (hasLock &&
                        fieldsContextRef.current?.inlineEditingFieldElement !== fieldElement) {
                        fieldsContextRef.current?.setInlineEditingFieldElement(fieldElement);
                        // Don't prevent default - we want the browser's natural cursor positioning
                    }
                }
                else {
                    // Clicked inside iframe but not on a field: clear focused field
                    fieldsContextRef.current?.setFocusedField(undefined, false);
                    fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
                    // Release field locks when unfocusing field
                    if (editContextRef.current?.unlockField) {
                        editContextRef.current.unlockField(undefined);
                    }
                }
            }
            const clickEvent = new MouseEvent("click", {
                view: window,
                bubbles: true,
                cancelable: true,
                clientX: event.clientX,
                clientY: event.clientY,
            });
            if (!isGeneratorOverlay) {
                document.dispatchEvent(clickEvent);
            }
        };
        const handleIframeScroll = () => {
            const scrollTop = boundScrollContainer?.scrollTop || 0;
            scrollHandler(scrollTop);
        };
        const handleIframeWindowScroll = () => {
            const scrollTop = boundScrollContainer?.scrollTop || 0;
            scrollHandler(scrollTop);
        };
        const handleIframeKeyDown = (event) => {
            // Handle Ctrl+. keyboard shortcut to open AI text editor
            // Forward this to the parent window since InlineAiTrigger listens there
            const isCtrlPeriod = (event.ctrlKey || event.metaKey) && event.key === ".";
            if (isCtrlPeriod) {
                // Dispatch the inline-ai-open event on the parent window's document
                // so InlineAiTrigger can catch it
                const parentEvent = new CustomEvent("inline-ai-open", {
                    bubbles: true,
                    cancelable: true,
                });
                window.parent.document.dispatchEvent(parentEvent);
                // Also prevent default to avoid any browser behavior
                event.preventDefault();
                event.stopPropagation();
                return;
            }
            editContextRef.current?.handleKeyDown(event);
        };
        const handleIframeBlur = () => {
            // Block blur event if it was triggered by clicking on a field
            if (blockBlurEventRef.current < Date.now()) {
                // Only clear if the focus didn't move to the parent document (e.g. AI dialog)
                // OR if we still have a selection range.
                // We use a small timeout because document.activeElement might not be updated yet
                setTimeout(() => {
                    const activeEl = document.activeElement;
                    const isDialog = activeEl?.closest(".agent-inline-dialog") ||
                        activeEl?.closest('[role="dialog"]');
                    const isTrigger = activeEl?.closest(".agent-inline-trigger");
                    // If focus moved to dialog/trigger (e.g. inline AI), don't force a blur boundary yet.
                    if (!isDialog && !isTrigger) {
                        // Always mark a field blur operation boundary so consecutive edits do not
                        // collapse into a single undo step when a selection range is still present.
                        editContextRef.current?.operations.onFieldBlur?.();
                        // Leaving the iframe ends inline editing even if we keep the
                        // selection metadata. Suggestions mode relies on this blur
                        // boundary so pending suggestions can be re-applied after focus
                        // moves to the surrounding editor UI.
                        fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
                    }
                }, 100);
            }
            else {
                blockBlurEventRef.current = 0;
            }
        };
        const detachListeners = () => {
            if (!iframe || !boundDocument || !boundDocumentElement)
                return;
            boundDocumentElement.removeEventListener("mousedown", handleIframeMouseDown);
            boundDocumentElement.removeEventListener("click", handleIframeClick);
            iframe.contentWindow?.removeEventListener("contextmenu", handleContextMenu, true);
            boundDocument.removeEventListener("selectionchange", selecionChangeHandler);
            boundDocument.removeEventListener("keydown", handleIframeKeyDown);
            boundDocumentElement.removeEventListener("blur", handleIframeBlur, true);
            boundScrollContainer?.removeEventListener("scroll", handleIframeScroll);
            iframe.contentWindow?.removeEventListener("scroll", handleIframeWindowScroll);
            mutationObserver?.disconnect();
            mutationObserver = null;
            boundScrollContainer = null;
            boundDocumentElement = null;
            boundDocument = null;
        };
        const attachListeners = () => {
            const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document;
            const iframeDocumentElement = iframeDocument?.documentElement;
            if (!iframeDocument || !iframeDocumentElement)
                return;
            // Skip if already bound to current root element.
            if (boundDocument === iframeDocument &&
                boundDocumentElement === iframeDocumentElement) {
                return;
            }
            detachListeners();
            boundDocument = iframeDocument;
            boundDocumentElement = iframeDocumentElement;
            iframeDocumentElement.addEventListener("mousedown", handleIframeMouseDown);
            iframeDocumentElement.addEventListener("click", handleIframeClick);
            iframe.contentWindow?.addEventListener("contextmenu", handleContextMenu, true);
            boundScrollContainer =
                iframe.contentWindow?.document.scrollingElement ||
                    iframe.contentWindow?.document.body ||
                    null;
            boundScrollContainer?.addEventListener("scroll", handleIframeScroll);
            iframe.contentWindow?.addEventListener("scroll", handleIframeWindowScroll);
            iframeDocument.addEventListener("selectionchange", selecionChangeHandler);
            iframeDocument.addEventListener("keydown", handleIframeKeyDown);
            iframeDocumentElement.addEventListener("blur", handleIframeBlur, true);
            mutationObserver = new MutationObserver((records) => {
                // Ignore all text field edits
                if (records.some((x) => !(x &&
                    "getAttribute" in x.target &&
                    x.target.getAttribute("data-fieldid") &&
                    x.target.getAttribute("data-itemid")))) {
                    requestPageModelBuild(iframeDocument);
                }
            });
            mutationObserver.observe(iframeDocument, {
                childList: true, // observe direct children changes
                subtree: true, // observe all descendants changes
                characterData: false, // observe text changes
                //attributes: true, // observe attribute changes (like style or class)
            });
            requestPageModelBuild(iframeDocument);
        };
        const parsePositiveInt = (value) => {
            if (!value)
                return undefined;
            const parsed = parseInt(value, 10);
            return Number.isFinite(parsed) ? parsed : undefined;
        };
        const resolveLinkedItemDescriptor = (anchor, clickedElement) => {
            const href = anchor.getAttribute("href") || anchor.href;
            let hrefUrl;
            try {
                hrefUrl = new URL(anchor.href, iframe.contentWindow?.location?.href || window.location.href);
            }
            catch {
                return undefined;
            }
            const attributeContainer = clickedElement?.closest("[data-itemid],[data-language],[data-version],[sc_item],[sc_itemid],[sc_lang]") || anchor;
            // Parse Sitecore RTE format first: ~/link.aspx?_id=GUID&_z=z (target item, not the containing component)
            let itemIdRaw;
            if (href.includes("~/link.aspx?_id=") ||
                href.includes("link.aspx?_id=")) {
                const idFromUrl = hrefUrl.searchParams.get("_id");
                if (idFromUrl) {
                    // Convert compact GUID (AD973E51E8454BD2B333859375FBBA24) to standard format with dashes
                    itemIdRaw = idFromUrl
                        .replace(/^(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})$/i, "$1-$2-$3-$4-$5")
                        .toLowerCase();
                }
            }
            if (!itemIdRaw) {
                itemIdRaw =
                    hrefUrl.searchParams.get("sc_itemid") ??
                        hrefUrl.searchParams.get("itemid") ??
                        anchor.getAttribute("data-itemid") ??
                        anchor.getAttribute("sc_itemid") ??
                        attributeContainer?.getAttribute("data-itemid") ??
                        attributeContainer?.getAttribute("sc_itemid") ??
                        (anchor.getAttribute("sc_item")
                            ? extractItemIdFromItemUri(anchor.getAttribute("sc_item"))
                            : undefined) ??
                        (attributeContainer?.getAttribute("sc_item")
                            ? extractItemIdFromItemUri(attributeContainer.getAttribute("sc_item"))
                            : undefined);
            }
            const itemId = cleanId(itemIdRaw);
            if (!itemId)
                return undefined;
            const language = hrefUrl.searchParams.get("sc_lang") ??
                hrefUrl.searchParams.get("lang") ??
                hrefUrl.searchParams.get("language") ??
                anchor.getAttribute("data-language") ??
                anchor.getAttribute("sc_lang") ??
                attributeContainer?.getAttribute("data-language") ??
                attributeContainer?.getAttribute("sc_lang") ??
                editContextRef.current?.currentItemDescriptor?.language ??
                editContextRef.current?.item?.language;
            if (!language)
                return undefined;
            const version = parsePositiveInt(hrefUrl.searchParams.get("sc_version")) ??
                parsePositiveInt(hrefUrl.searchParams.get("version")) ??
                parsePositiveInt(anchor.getAttribute("data-version")) ??
                parsePositiveInt(attributeContainer?.getAttribute("data-version")) ??
                editContextRef.current?.currentItemDescriptor?.version ??
                editContextRef.current?.item?.version ??
                0;
            return {
                id: itemId,
                language,
                version,
            };
        };
        const handleIframeClick = async (event) => {
            const target = event.target;
            if (!target)
                return;
            const anchor = target.tagName.toLowerCase() === "a"
                ? target
                : target.closest("a");
            if (!anchor)
                return;
            const href = anchor.getAttribute("href") || anchor.href;
            if (!href || href.startsWith("#") || href.startsWith("javascript:")) {
                return;
            }
            const mode = editContextRef.current?.mode;
            const isPreviewOrSuggestions = mode === "preview" || mode === "suggestions";
            if (isPreviewOrSuggestions) {
                let hrefUrl;
                try {
                    hrefUrl = new URL(anchor.href, iframe.contentWindow?.location?.href || window.location.href);
                }
                catch {
                    return;
                }
                const iframeOrigin = iframe.contentWindow?.location?.origin;
                const isInternalLink = hrefUrl.origin === window.location.origin ||
                    (iframeOrigin ? hrefUrl.origin === iframeOrigin : false);
                if (!isInternalLink) {
                    event.preventDefault();
                    event.stopPropagation();
                    window.open(hrefUrl.toString(), "_blank", "noopener,noreferrer");
                    return;
                }
                if (isInternalLink) {
                    const linkedItem = resolveLinkedItemDescriptor(anchor, target);
                    if (linkedItem) {
                        event.preventDefault();
                        event.stopPropagation();
                        await editContextRef.current?.loadItem(linkedItem, {
                            openInNewSlot: event.altKey,
                        });
                        return;
                    }
                }
                // If this link cannot be resolved to an item, allow browser behavior.
                return;
            }
            // In edit mode, keep navigation inside iframe disabled.
            event.preventDefault();
        };
        const handleContextMenu = async (event) => {
            if (editContextRef.current?.isRefreshing && showSpinner)
                return;
            const target = event.target;
            if (!target)
                return;
            if (event.ctrlKey)
                return;
            event.preventDefault();
            event.stopPropagation();
            let componentId = findNearestEditableComponentId(target);
            const pageForContextMenu = pageViewContextRef.current?.page;
            if (componentId && pageForContextMenu) {
                componentId = resolveComponentIdForTarget(componentId, target, pageForContextMenu);
            }
            // Layout components can now be right-clicked even when showLayoutComponents is false
            // Context menu will show limited/read-only actions
            if (componentId) {
                // Only change selection if right-clicking on a component that's not in the current selection
                if (!editContextRef.current?.selection.includes(componentId)) {
                    // Right-clicking on a component not in the selection - select just that component
                    editContextRef.current.select([componentId]);
                }
                // else: right-clicking on a component already in the selection - keep current selection
            }
            const fieldElement = findParentWithAttribute(target, "data-fieldid");
            const selectedComponents = editContextRef.current?.selection
                .map((id) => getComponentById(id, pageViewContextRef.current.page))
                .filter((x) => x);
            // Context menu will now show for layout components even when showLayoutComponents is false
            // Commands will be appropriately filtered/disabled
            const iframeRect = iframe.getBoundingClientRect();
            const adjustedEvent = new MouseEvent("contextmenu", {
                bubbles: true,
                cancelable: true,
                shiftKey: event.shiftKey,
                altKey: event.altKey,
                ctrlKey: event.ctrlKey,
                view: window,
                clientX: event.clientX + iframeRect.x,
                clientY: event.clientY + iframeRect.y,
            });
            // Store the original context menu position for overlay positioning
            const menuPosition = {
                x: event.clientX + iframeRect.x,
                y: event.clientY + iframeRect.y,
            };
            setContextMenuPosition(menuPosition);
            // Only show a spinner if work takes longer than 100ms
            let loadingShown = false;
            const loadingTimer = setTimeout(() => {
                loadingShown = true;
                editContextRef.current?.showContextMenu(adjustedEvent, [
                    {
                        id: "loading",
                        label: "Loading…",
                        disabled: true,
                        icon: _jsx(Spinner, { size: "sm", className: "mr-2" }),
                    },
                ]);
            }, 100);
            const field = fieldElement
                ? getFieldDescriptorFromElement(fieldElement)
                : undefined;
            const fieldButtons = field ? await loadFieldButtons(field) : [];
            // Create a handler that has access to the current menu position
            const handleParameterizedActionWithPosition = (field, action, allFieldButtons, event) => {
                setContextMenuField(field);
                setContextMenuFieldButtons([action]);
                setPreSelectedAction(action);
                // Create a new MouseEvent with the captured position
                const syntheticEvent = new MouseEvent("click", {
                    bubbles: true,
                    cancelable: true,
                    view: window,
                    clientX: menuPosition.x,
                    clientY: menuPosition.y,
                    screenX: menuPosition.x,
                    screenY: menuPosition.y,
                });
                // Add target property to the event for proper positioning
                Object.defineProperty(syntheticEvent, "target", {
                    value: document.body,
                    enumerable: true,
                });
                console.log("About to show overlay with position:", {
                    menuPosition,
                    syntheticEvent,
                    preSelectedAction: action,
                });
                // Add a small delay to ensure context menu has closed
                setTimeout(() => {
                    console.log("Showing overlay with position:", {
                        menuPosition,
                        syntheticEvent,
                        preSelectedAction: action,
                    });
                    // Create a temporary element at the exact position for better positioning
                    const tempElement = document.createElement("div");
                    tempElement.style.position = "fixed";
                    tempElement.style.left = menuPosition.x + "px";
                    tempElement.style.top = menuPosition.y + "px";
                    tempElement.style.width = "1px";
                    tempElement.style.height = "1px";
                    tempElement.style.visibility = "hidden";
                    tempElement.style.pointerEvents = "none";
                    document.body.appendChild(tempElement);
                    // Create event targeting the positioned element
                    const positionedEvent = new MouseEvent("click", {
                        bubbles: true,
                        cancelable: true,
                        view: window,
                        clientX: menuPosition.x,
                        clientY: menuPosition.y,
                    });
                    Object.defineProperty(positionedEvent, "target", {
                        value: tempElement,
                        enumerable: true,
                    });
                    fieldActionsOverlay.current?.show(positionedEvent, action);
                    // Clean up the temporary element after overlay is shown
                    setTimeout(() => {
                        document.body.removeChild(tempElement);
                    }, 1000);
                }, 100);
            };
            const items = await buildComponentContextMenuItems(selectedComponents, editContextRef.current, field, fieldButtons, handleParameterizedActionWithPosition);
            clearTimeout(loadingTimer);
            if (loadingShown)
                editContextRef.current?.updateContextMenu(items);
            else
                editContextRef.current?.showContextMenu(adjustedEvent, items);
        };
        const handleLoad = () => {
            // Skip handling if this load does not correspond to the latest requested revision
            const expectedRevision = currentLoadRef.current?.revision ?? "";
            const currentRevision = editContextRef.current?.revision ?? "";
            if (expectedRevision && expectedRevision !== currentRevision) {
                console.log("Stale load skipped", {
                    expectedRevision,
                    currentRevision,
                });
                return;
            }
            setShowSpinner(false);
            attachListeners();
        };
        rebindIframeInteractionsRef.current = attachListeners;
        if (iframe) {
            // If the iframe is already loaded, attach the listener immediately
            if (iframe.contentDocument?.readyState === "complete" ||
                iframe.contentDocument?.readyState === "interactive") {
                handleLoad();
            }
            iframe.addEventListener("load", handleLoad);
        }
        // Cleanup function
        return () => {
            rebindIframeInteractionsRef.current = null;
            if (iframe) {
                iframe.removeEventListener("load", handleLoad);
            }
            detachListeners();
        };
    }, [iframeRef.current]);
    useEffect(() => {
        iframeRef.current?.contentWindow?.postMessage({ type: "componentsSelected", componentIds: editContext.selection }, "*");
    }, [editContext.selection]);
    const updateScrollPosition = (e) => {
        setScroll(e);
        if (!compareView)
            pageViewContextRef.current?.setScroll(e);
    };
    useEffect(() => {
        if (!iframeRef.current)
            return;
        const body = iframeRef.current.contentWindow?.document.documentElement;
        if (!body)
            return;
        body.style.transform = `scale(${zoom})`;
        body.style.transformOrigin = "top center";
        body.style.overflow = "auto";
        //body.style.height = `${body.scrollHeight / zoom}px`;
    }, [zoom]);
    const scrollHandler = useThrottledCallback(updateScrollPosition, 100);
    if (pageViewContext.page?.item && !pageViewContext.page?.item.hasLayout) {
        return _jsx(NoLayout, {});
    }
    const deviceHeight = pageViewContext.device === "desktop" || !pageViewContext.deviceHeight
        ? "100%"
        : pageViewContext.deviceHeight || 640;
    return (_jsxs("div", { className: cn("relative flex h-full w-full flex-col items-center select-none", className, editContext.showAgentsPanel && !editContext.currentWizardId && "pr-0"), children: [!pageViewContext.fullscreen && (_jsx(EditorWarnings, { item: pageViewContext.page?.item })), slotCloseButton && (_jsx("div", { className: "absolute top-3 right-3 z-50", children: slotCloseButton })), pageViewContext.device !== "desktop" && (_jsx(DeviceToolbar, { pageViewContext: pageViewContext, configuration: editContext.configuration })), _jsxs("div", { className: "relative flex flex-1 select-none", "data-testid": "page-viewer-viewport", style: {
                    width: pageViewContext.device === "desktop" ||
                        pageViewContext.device === "Responsive"
                        ? "100%"
                        : (pageViewContext.deviceWidth || 640) +
                            editContext.configuration.outline.width +
                            "px",
                }, children: [_jsxs("div", { className: "relative h-full w-full overflow-hidden", children: [_jsx("iframe", { ref: iframeRef, className: "page-iframe h-full w-full bg-white", style: { height: deviceHeight }, src: iframeSrc, "data-testid": "pageEditoriframe", onLoad: () => {
                                    // Handle iframe load when using src attribute (initial load)
                                    const doc = iframeRef.current?.contentDocument;
                                    if (doc && iframeSrc) {
                                        setShowSpinner(false);
                                        ensureEditorCssGuard(doc, editContext.mode !== "preview");
                                        setTimeout(() => {
                                            injectSXAScripts(iframeRef.current);
                                        }, 1000);
                                        requestPageModelBuild(doc);
                                    }
                                } }), iframeRef.current && (_jsx(PageEditorChrome, { iframe: iframeRef.current, compareView: compareView, pageViewContext: pageViewContext })), pageViewContext.deviceHeight && pageViewContext.device && (_jsx("div", { className: "bg-gray-4 relative z-40 h-full w-full" }))] }), !pageViewContext.fullscreen &&
                        showMiniMap &&
                        editContext.showMinimap &&
                        !editContext.isMobile &&
                        editContext.parheliaSettings?.showMinimap !== false && (_jsx(MiniMap, { compareView: compareView, scroll: scroll, mainViewIframeRef: iframeRef, pageViewContext: pageViewContext, deviceHeight: pageViewContext.device === "Desktop"
                            ? undefined
                            : pageViewContext.deviceHeight })), showSpinner && (_jsxs(_Fragment, { children: [_jsx("div", { className: "bg-gray-4/50 absolute top-0 left-0 h-full w-full" }), _jsx("div", { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform", children: _jsx(Spinner, {}) })] }))] }), _jsx(FieldActionsOverlay, { ref: fieldActionsOverlay, generatorButtons: contextMenuFieldButtons, onActionClick: handleActionClick, onParameterizedActionExecute: handleParameterizedActionExecute, currentOverlay: editContext?.currentOverlay, fieldId: contextMenuField?.fieldId || "", setCurrentOverlay: editContext?.setCurrentOverlay || (() => { }), preSelectedAction: preSelectedAction, iframe: iframeRef.current })] }));
}
/**
 * In preview mode, disable editing in the iframe: set contentEditable=false on all
 * editable elements and blur the active element so the caret is removed and typing does nothing.
 */
function disableEditingInIframeDocument(doc) {
    if (!doc || !doc.body)
        return;
    const editable = doc.querySelectorAll("[contenteditable='true'], [contenteditable='']");
    editable.forEach((el) => {
        el.contentEditable = "false";
    });
    const active = doc.activeElement;
    if (active?.isContentEditable) {
        active.blur();
    }
}
function injectEditorCSS(iframeDocument, editMode) {
    if (!iframeDocument)
        return;
    const styleId = EDITOR_CSS_STYLE_ID;
    // Remove existing style element if present to avoid duplicates
    const existingStyle = iframeDocument.getElementById(styleId);
    if (existingStyle) {
        existingStyle.remove();
    }
    const style = iframeDocument.createElement("style");
    style.id = styleId;
    style.textContent = editMode
        ? `[contentEditable]:empty:before {
    content: attr(placeholder);
    opacity: 0.6;
  }

  .scChromeData, code {
    display: none;
  }

  [contenteditable] {
    outline: 0px solid transparent;
  }
      `
        : `
 [contenteditable] {
    outline: 0px solid transparent;
  }
  .scChromeData, code {
    display: none;
  }
`;
    if (iframeDocument && iframeDocument.head) {
        iframeDocument.head.appendChild(style);
    }
    if (!editMode) {
        disableEditingInIframeDocument(iframeDocument);
    }
}
function resolveComponentIdForTarget(rawId, target, page) {
    const direct = getComponentById(rawId, page);
    const candidates = getAllComponentInstances(rawId, page);
    if (candidates.length === 0) {
        return direct?.id || rawId;
    }
    const containing = candidates.filter((component) => isTargetInsideComponent(target, component));
    if (containing.length === 0) {
        return direct?.id || candidates[0].id;
    }
    // Prefer the deepest/most specific matching component for nested SXA markup.
    containing.sort((a, b) => getElementDepth(b.firstDOMElement || null) -
        getElementDepth(a.firstDOMElement || null));
    return containing[0].id;
}
function resolveComponentIdForFieldTarget(field, target, page) {
    const matches = findComponentsRenderingField(field, page);
    const containingMatches = matches.filter((component) => isTargetInsideComponent(target, component));
    const rankedMatches = (containingMatches.length > 0
        ? containingMatches
        : matches).sort((a, b) => getElementDepth(b.firstDOMElement || null) -
        getElementDepth(a.firstDOMElement || null));
    return rankedMatches[0]?.id;
}
function findComponentsRenderingField(field, page) {
    if (!page?.rootComponent)
        return [];
    const matches = [];
    const visit = (component) => {
        if (componentRendersField(component, field)) {
            matches.push(component);
        }
        for (const placeholder of component.placeholders || []) {
            for (const child of placeholder.components || []) {
                visit(child);
            }
        }
    };
    visit(page.rootComponent);
    return matches;
}
function componentRendersField(component, field) {
    const renderedItems = [
        component.datasourceItem,
        ...component.items.filter((item) => !component.datasourceItem ||
            !(item.id === component.datasourceItem.id &&
                item.language === component.datasourceItem.language &&
                item.version === component.datasourceItem.version)),
    ].filter(Boolean);
    return renderedItems.some((item) => item.id === field.item.id &&
        item.language === field.item.language &&
        item.version === field.item.version &&
        item.renderedFieldIds.includes(field.fieldId));
}
function isTargetInsideComponent(target, component) {
    const start = component.firstDOMElement;
    const end = component.lastDOMElement || component.firstDOMElement;
    if (!start || !end)
        return false;
    if (start.contains(target) || end.contains(target))
        return true;
    const startPos = start.compareDocumentPosition(target);
    const endPos = end.compareDocumentPosition(target);
    const isAfterStart = !!(startPos & Node.DOCUMENT_POSITION_FOLLOWING) ||
        !!(startPos & Node.DOCUMENT_POSITION_CONTAINED_BY);
    const isBeforeEnd = !!(endPos & Node.DOCUMENT_POSITION_PRECEDING) ||
        !!(endPos & Node.DOCUMENT_POSITION_CONTAINED_BY);
    return isAfterStart && isBeforeEnd;
}
function getElementDepth(element) {
    let depth = 0;
    let current = element;
    while (current) {
        depth++;
        current = current.parentElement;
    }
    return depth;
}
function injectSXAScripts(iframe) {
    if (!iframe)
        return;
    const iframeDocument = iframe.contentDocument;
    const iframeWindow = iframe.contentWindow;
    if (!iframeDocument || !iframeWindow)
        return;
    iframeWindow.Sitecore = {
        PageModes: {
            HoverFrame: {
                extend: () => {
                    //  console.log("extend hoverframe");
                },
            },
            ChromeManager: {
                chromes: () => {
                    //  console.log("chromes");
                    return [];
                },
                resetChromes: () => {
                    //   console.log("resetChromes");
                    iframeWindow.XA.init();
                },
                chromesReseted: {
                    observe: () => {
                        //  console.log("chromesReseted.observe");
                    },
                },
                // Dummy selectionChanged with an _callbacks array
                selectionChanged: {
                    _callbacks: [],
                    // Optionally, you can add a method to trigger all callbacks if needed.
                    trigger: function (...args) {
                        this._callbacks.forEach((callback) => callback(...args));
                    },
                },
            },
            ChromeControls: function () { },
            ChromeTypes: {
                PlaceholderSorting: function () { },
                Rendering: function () { },
            },
        },
    };
    // Option 1: Use main window's jQuery if available.
    if (iframeWindow.jQuery) {
        // Be cautious: if the iframe's document is different, it's better to load jQuery within the iframe.
        iframeWindow.$sc = iframeWindow.jQuery;
    }
    else {
        // Option 2: Dynamically load jQuery in the iframe.
        const jqueryScript = iframeDocument.createElement("script");
        jqueryScript.type = "text/javascript";
        jqueryScript.src = "https://code.jquery.com/jquery-3.6.0.min.js"; // Use the version you prefer.
        jqueryScript.onload = () => {
            // Once loaded, assign it to $sc using noConflict to avoid global collisions.
            iframeWindow.$sc = iframeWindow.jQuery.noConflict();
            //  console.log("jQuery loaded in iframe and assigned to $sc");
        };
        (iframeDocument.head || iframeDocument.body).appendChild(jqueryScript);
    }
    // Add a dummy implementation for renderExpandCommand on the ChromeControls prototype.
    iframeWindow.Sitecore.PageModes.ChromeControls.prototype.renderExpandCommand =
        function () {
            console.log("renderExpandCommand called");
            // Return a dummy value if needed. For example, you might return a dummy HTML string.
            return "<div>Dummy Expand Command</div>";
        };
    iframeWindow.Sitecore.PageModes.ChromeTypes.PlaceholderSorting.prototype.insertSortingHandle =
        function () {
            console.log("insertSortingHandle called");
        };
}
/**
 * Calculates the global offset of a given target node and its local offset,
 * relative to the container's complete text content.
 *
 * @param container - The container element that holds the text nodes.
 * @param targetNode - The text node where the selection starts or ends.
 * @param localOffset - The offset within the target text node.
 * @returns The computed global offset.
 */
function getGlobalTextOffset(container, targetNode, localOffset) {
    let globalOffset = 0;
    const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);
    while (walker.nextNode()) {
        const currentNode = walker.currentNode;
        // If we've reached the target node, add the local offset and return.
        if (currentNode === targetNode) {
            return globalOffset + localOffset;
        }
        // Otherwise, add the length of this text node.
        globalOffset += (currentNode.textContent || "").length;
    }
    return globalOffset;
}
//# sourceMappingURL=PageViewerFrame.js.map