"use client";
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useMemo, useRef, useState } from "react";
import { cn } from "../../lib/utils";
import { useEditContext } from "../client/editContext";
import { usePageViewContext } from "../page-viewer/pageViewContext";
import { fieldKey, findComponentRectById, findFieldElements, getTextNodeAtOffset, resolveVersionDiffTargets, } from "../version-diff/versionDiffTargets";
import { versionDiffHighlightClasses } from "../version-diff/versionDiffHighlightStyles";
const SOURCE_WIDTH = 480;
const SOURCE_HEIGHT = 720;
const PREVIEW_SCALE = 0.2;
const PREVIEW_WIDTH = SOURCE_WIDTH * PREVIEW_SCALE;
const PREVIEW_HEIGHT = SOURCE_HEIGHT * PREVIEW_SCALE;
const SCROLL_PADDING_TOP = 16;
export function VersionPreviewCard({ itemDescriptor, version, className, }) {
    const rootRef = useRef(null);
    const [isInViewport, setIsInViewport] = useState(false);
    const [iframe, setIframe] = useState(null);
    useEffect(() => {
        const element = rootRef.current;
        if (!element)
            return;
        if (typeof IntersectionObserver === "undefined") {
            setIsInViewport(true);
            return;
        }
        const observer = new IntersectionObserver(([entry]) => {
            setIsInViewport(!!entry?.isIntersecting);
        }, {
            root: findScrollParent(element),
            threshold: 0.01,
        });
        observer.observe(element);
        return () => observer.disconnect();
    }, []);
    const handleWheel = (event) => {
        const iframeWindow = iframe?.contentWindow;
        const iframeDocument = iframeWindow?.document;
        const scrollElement = iframeDocument?.scrollingElement || iframeDocument?.documentElement;
        if (!iframeWindow || !scrollElement)
            return;
        const canScrollY = scrollElement.scrollHeight > scrollElement.clientHeight &&
            ((event.deltaY < 0 && scrollElement.scrollTop > 0) ||
                (event.deltaY > 0 &&
                    scrollElement.scrollTop + scrollElement.clientHeight <
                        scrollElement.scrollHeight));
        const canScrollX = scrollElement.scrollWidth > scrollElement.clientWidth &&
            ((event.deltaX < 0 && scrollElement.scrollLeft > 0) ||
                (event.deltaX > 0 &&
                    scrollElement.scrollLeft + scrollElement.clientWidth <
                        scrollElement.scrollWidth));
        if (!canScrollY && !canScrollX)
            return;
        event.preventDefault();
        event.stopPropagation();
        iframeWindow.scrollBy({
            top: event.deltaY,
            left: event.deltaX,
            behavior: "instant",
        });
    };
    return (_jsx("div", { ref: rootRef, onWheel: handleWheel, className: cn("relative shrink-0 overflow-hidden rounded border border-gray-200 bg-white shadow-sm", className), "data-testid": "version-preview-card", style: { width: PREVIEW_WIDTH, height: PREVIEW_HEIGHT }, "aria-hidden": "true", children: isInViewport ? (_jsx(VersionPreviewCardContent, { itemDescriptor: itemDescriptor, version: version, iframe: iframe, setIframe: setIframe })) : (_jsx("div", { className: "absolute inset-0 bg-gray-50" })) }));
}
function VersionPreviewCardContent({ itemDescriptor, version, iframe, setIframe, }) {
    const editContext = useEditContext();
    const [loaded, setLoaded] = useState(false);
    const descriptor = useMemo(() => ({
        id: itemDescriptor.id,
        language: itemDescriptor.language,
        version,
        database: itemDescriptor.database,
    }), [itemDescriptor.database, itemDescriptor.id, itemDescriptor.language, version]);
    const pageView = usePageViewContext({
        pageItemDescriptor: descriptor,
        itemsRepository: editContext?.itemsRepository,
        configuration: editContext?.configuration,
        parheliaSettings: editContext?.parheliaSettings,
    });
    const previewSrc = pageView.editUrl || pageView.previewUrl;
    useEffect(() => {
        setLoaded(false);
        setIframe(null);
    }, [previewSrc]);
    if (!previewSrc) {
        return (_jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-gray-50 text-[10px] text-gray-400", children: "No preview" }));
    }
    return (_jsxs(_Fragment, { children: [!loaded && (_jsx("div", { className: "absolute inset-0 z-10 animate-pulse bg-gray-100" })), _jsx("iframe", { ref: setIframe, title: `Version ${version} preview`, src: previewSrc, tabIndex: -1, loading: "lazy", onLoad: () => setLoaded(true), className: "pointer-events-none origin-top-left border-0", style: {
                    width: SOURCE_WIDTH,
                    height: SOURCE_HEIGHT,
                    transform: `scale(${PREVIEW_SCALE})`,
                } }, previewSrc), loaded && iframe && editContext?.itemsRepository && (_jsx(VersionPreviewDiffOverlay, { iframe: iframe, pageItem: descriptor, scale: PREVIEW_SCALE }))] }));
}
function scrollIframeToFirstTarget(iframe, fieldTargets, componentTargets) {
    const win = iframe.contentWindow;
    if (!win)
        return;
    const currentScroll = win.scrollY;
    let topMost = null;
    for (const target of componentTargets) {
        const rect = findComponentRectById(iframe, target.visualId);
        if (!rect)
            continue;
        const docTop = rect.top + currentScroll;
        if (topMost == null || docTop < topMost)
            topMost = docTop;
    }
    for (const target of fieldTargets) {
        for (const element of findFieldElements(iframe, target.descriptor)) {
            const docTop = element.getBoundingClientRect().top + currentScroll;
            if (topMost == null || docTop < topMost)
                topMost = docTop;
        }
    }
    if (topMost == null)
        return;
    win.scrollTo({
        top: Math.max(0, topMost - SCROLL_PADDING_TOP),
        left: 0,
        behavior: "instant",
    });
}
function findScrollParent(element) {
    let parent = element.parentElement;
    while (parent) {
        const style = window.getComputedStyle(parent);
        if (/(auto|scroll|overlay)/.test(style.overflowY)) {
            return parent;
        }
        parent = parent.parentElement;
    }
    return null;
}
function VersionPreviewDiffOverlay({ iframe, pageItem, scale, }) {
    const editContext = useEditContext();
    const itemsRepository = editContext?.itemsRepository;
    const previousVersionCache = useRef(new Map());
    const [targets, setTargets] = useState([]);
    const [componentTargets, setComponentTargets] = useState([]);
    useEffect(() => {
        if (!itemsRepository) {
            setTargets([]);
            setComponentTargets([]);
            return;
        }
        let cancelled = false;
        const resolveTargets = async () => {
            const nextTargets = await resolveVersionDiffTargets({
                iframe,
                pageItem,
                getItem: itemsRepository.getItem,
                baseline: { type: "previous" },
                previousVersionCache: previousVersionCache.current,
            });
            if (cancelled)
                return;
            scrollIframeToFirstTarget(iframe, nextTargets.fieldTargets, nextTargets.componentTargets);
            setTargets(nextTargets.fieldTargets);
            setComponentTargets(nextTargets.componentTargets);
        };
        resolveTargets();
        return () => {
            cancelled = true;
        };
    }, [
        iframe,
        itemsRepository,
        itemsRepository?.revision,
        pageItem.database,
        pageItem.id,
        pageItem.language,
        pageItem.version,
    ]);
    if (targets.length === 0 && componentTargets.length === 0)
        return null;
    return (_jsxs("div", { className: "pointer-events-none absolute inset-0", children: [componentTargets.map((target) => (_jsx(MiniComponentHighlight, { iframe: iframe, scale: scale, target: target }, target.id))), targets.map((target) => (_jsx(MiniFieldHighlight, { iframe: iframe, scale: scale, target: target }, fieldKey(target.descriptor))))] }));
}
function MiniComponentHighlight({ iframe, scale, target, }) {
    const [rect, setRect] = useState(null);
    useEffect(() => {
        const iframeDoc = iframe.contentWindow?.document;
        if (!iframeDoc?.body) {
            setRect(null);
            return;
        }
        let rafId = null;
        const calculateRect = () => {
            setRect(findComponentRectById(iframe, target.visualId));
        };
        const scheduleUpdate = () => {
            if (rafId != null)
                window.cancelAnimationFrame(rafId);
            rafId = window.requestAnimationFrame(() => {
                rafId = null;
                calculateRect();
            });
        };
        calculateRect();
        const timeoutId = window.setTimeout(calculateRect, 300);
        const scrollContainer = iframeDoc.scrollingElement || iframeDoc.body;
        const iframeWindow = iframe.contentWindow;
        scrollContainer?.addEventListener("scroll", scheduleUpdate, {
            passive: true,
        });
        iframeWindow?.addEventListener("scroll", scheduleUpdate, { passive: true });
        iframeWindow?.addEventListener("resize", scheduleUpdate);
        return () => {
            if (rafId != null)
                window.cancelAnimationFrame(rafId);
            window.clearTimeout(timeoutId);
            scrollContainer?.removeEventListener("scroll", scheduleUpdate);
            iframeWindow?.removeEventListener("scroll", scheduleUpdate);
            iframeWindow?.removeEventListener("resize", scheduleUpdate);
        };
    }, [iframe, target.visualId]);
    if (!rect)
        return null;
    return (_jsx("div", { className: versionDiffHighlightClasses.componentMini, "data-testid": "version-preview-diff-highlight", style: {
            top: rect.top * scale,
            left: rect.left * scale,
            width: Math.max(rect.width * scale, 2),
            height: Math.max(rect.height * scale, 2),
        } }));
}
function MiniFieldHighlight({ iframe, scale, target, }) {
    const [rects, setRects] = useState([]);
    useEffect(() => {
        const iframeDoc = iframe.contentWindow?.document;
        if (!iframeDoc?.body) {
            setRects([]);
            return;
        }
        let rafId = null;
        const calculateRects = () => {
            const elements = findFieldElements(iframe, target.descriptor);
            const nextRects = [];
            elements.forEach((element) => {
                if (target.hasDeletionOnlyChange) {
                    nextRects.push(element.getBoundingClientRect());
                    return;
                }
                target.ranges
                    .filter(([start, end]) => start < end)
                    .forEach(([start, end]) => {
                    const startData = getTextNodeAtOffset(element, start);
                    const endData = getTextNodeAtOffset(element, end);
                    if (!startData || !endData)
                        return;
                    try {
                        const textRange = iframeDoc.createRange();
                        textRange.setStart(startData.node, startData.offset);
                        textRange.setEnd(endData.node, endData.offset);
                        nextRects.push(...Array.from(textRange.getClientRects()));
                    }
                    catch {
                        nextRects.push(element.getBoundingClientRect());
                    }
                });
            });
            setRects(nextRects);
        };
        const scheduleUpdate = () => {
            if (rafId != null)
                window.cancelAnimationFrame(rafId);
            rafId = window.requestAnimationFrame(() => {
                rafId = null;
                calculateRects();
            });
        };
        calculateRects();
        const timeoutId = window.setTimeout(calculateRects, 300);
        const scrollContainer = iframeDoc.scrollingElement || iframeDoc.body;
        const iframeWindow = iframe.contentWindow;
        scrollContainer?.addEventListener("scroll", scheduleUpdate, {
            passive: true,
        });
        iframeWindow?.addEventListener("scroll", scheduleUpdate, { passive: true });
        iframeWindow?.addEventListener("resize", scheduleUpdate);
        return () => {
            if (rafId != null)
                window.cancelAnimationFrame(rafId);
            window.clearTimeout(timeoutId);
            scrollContainer?.removeEventListener("scroll", scheduleUpdate);
            iframeWindow?.removeEventListener("scroll", scheduleUpdate);
            iframeWindow?.removeEventListener("resize", scheduleUpdate);
        };
    }, [iframe, target]);
    if (rects.length === 0)
        return null;
    return (_jsx(_Fragment, { children: rects.map((rect, index) => (_jsx("div", { className: target.hasDeletionOnlyChange
                ? versionDiffHighlightClasses.deletionOnlyMini
                : versionDiffHighlightClasses.textMini, "data-testid": "version-preview-diff-highlight", style: {
                top: rect.top * scale,
                left: rect.left * scale,
                width: Math.max(rect.width * scale, 2),
                height: Math.max(rect.height * scale, 2),
            } }, index))) }));
}
//# sourceMappingURL=VersionPreviewCard.js.map