import { jsx as _jsx } from "react/jsx-runtime";
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, } from "react";
import { getAccessibleIframeDocument, getAccessibleIframeWindow, } from "./iframeAccess";
import { getOverlayMetrics, getOverlayScrollMetrics, } from "./geometry";
import { SPLITTER_RESIZE_EVENT } from "../../ui/splitterResizeEvent";
const ALL_REASONS = [
    "viewport",
    "layout",
    "text",
    "adornment-data",
];
const EMPTY_METRICS = {
    scrollLeft: 0,
    scrollTop: 0,
    scrollScale: 1,
    visualScale: 1,
    viewportWidth: 0,
    viewportHeight: 0,
    scrollWidth: 0,
    scrollHeight: 0,
};
const IframeOverlayContext = createContext(null);
export function useIframeOverlay() {
    const context = useContext(IframeOverlayContext);
    if (!context) {
        throw new Error("useIframeOverlay must be used inside IframeOverlayProvider");
    }
    return context;
}
export function IframeOverlayProvider({ iframe, scrollScale = 1, visualScale = 1, mode = "scrolling", children, }) {
    const rootRef = useRef(null);
    const layerRef = useRef(null);
    const subscribersRef = useRef(new Set());
    const scrollSubscribersRef = useRef(new Set());
    const pendingReasonsRef = useRef(new Set());
    const geometryRafRef = useRef(null);
    const scrollRafRef = useRef(null);
    const lastMetricsRef = useRef(EMPTY_METRICS);
    const iframeRef = useRef(iframe);
    const scrollScaleRef = useRef(scrollScale);
    const visualScaleRef = useRef(visualScale);
    useEffect(() => {
        iframeRef.current = iframe;
    }, [iframe]);
    useEffect(() => {
        scrollScaleRef.current = scrollScale;
        visualScaleRef.current = visualScale;
    }, [scrollScale, visualScale]);
    const getMetrics = useCallback(() => getOverlayMetrics(iframeRef.current, scrollScaleRef.current, visualScaleRef.current), []);
    const getScrollMetrics = useCallback(() => getOverlayScrollMetrics(iframeRef.current, scrollScaleRef.current, visualScaleRef.current), []);
    const writeScrollPosition = useCallback((metrics) => {
        const layer = layerRef.current;
        const root = rootRef.current;
        if (!layer || !root)
            return;
        const translatedX = mode === "scrolling"
            ? -metrics.scrollLeft * metrics.scrollScale * metrics.visualScale
            : 0;
        const translatedY = mode === "scrolling"
            ? -metrics.scrollTop * metrics.scrollScale * metrics.visualScale
            : 0;
        layer.style.transform = `translate3d(${translatedX}px, ${translatedY}px, 0)`;
        root.style.setProperty("--overlay-scroll-x", `${metrics.scrollLeft * metrics.scrollScale * metrics.visualScale}px`);
        root.style.setProperty("--overlay-scroll-y", `${metrics.scrollTop * metrics.scrollScale * metrics.visualScale}px`);
    }, [mode]);
    const applyScrollMetrics = useCallback((scrollMetrics) => {
        const metrics = {
            ...lastMetricsRef.current,
            ...scrollMetrics,
        };
        lastMetricsRef.current = metrics;
        writeScrollPosition(metrics);
        return metrics;
    }, [writeScrollPosition]);
    const writeLayerMetrics = useCallback((metrics) => {
        const layer = layerRef.current;
        const root = rootRef.current;
        if (!layer || !root)
            return;
        lastMetricsRef.current = metrics;
        writeScrollPosition(metrics);
        const documentScale = metrics.scrollScale * metrics.visualScale;
        layer.style.width = `${Math.max(metrics.scrollWidth * documentScale, metrics.viewportWidth)}px`;
        layer.style.height = `${Math.max(metrics.scrollHeight * documentScale, metrics.viewportHeight)}px`;
        root.style.setProperty("--overlay-viewport-width", `${metrics.viewportWidth}px`);
        root.style.setProperty("--overlay-viewport-height", `${metrics.viewportHeight}px`);
    }, [writeScrollPosition]);
    const notifyScrollSubscribers = useCallback((metrics) => {
        scrollSubscribersRef.current.forEach((callback) => callback(metrics));
    }, []);
    const updateScrollLayer = useCallback(() => {
        applyScrollMetrics(getScrollMetrics());
        if (scrollRafRef.current != null)
            return;
        scrollRafRef.current = window.requestAnimationFrame(() => {
            scrollRafRef.current = null;
            notifyScrollSubscribers(lastMetricsRef.current);
        });
    }, [applyScrollMetrics, getScrollMetrics, notifyScrollSubscribers]);
    const flushMeasurements = useCallback(() => {
        geometryRafRef.current = null;
        const reasons = pendingReasonsRef.current.size > 0
            ? new Set(pendingReasonsRef.current)
            : new Set(ALL_REASONS);
        pendingReasonsRef.current.clear();
        const metrics = getMetrics();
        writeLayerMetrics(metrics);
        subscribersRef.current.forEach((subscriber) => {
            const shouldRun = subscriber.interests.size === 0 ||
                Array.from(reasons).some((reason) => subscriber.interests.has(reason));
            if (shouldRun)
                subscriber.callback(metrics, reasons);
        });
    }, [getMetrics, writeLayerMetrics]);
    const invalidateGeometry = useCallback((reason) => {
        const reasons = Array.isArray(reason) ? reason : [reason];
        reasons.forEach((item) => pendingReasonsRef.current.add(item));
        if (geometryRafRef.current != null)
            return;
        geometryRafRef.current = window.requestAnimationFrame(() => {
            flushMeasurements();
        });
    }, [flushMeasurements]);
    const subscribeMeasurement = useCallback((callback, interests = ALL_REASONS) => {
        const subscriber = {
            callback,
            interests: new Set(interests),
        };
        subscribersRef.current.add(subscriber);
        callback(getMetrics(), new Set(ALL_REASONS));
        return () => {
            subscribersRef.current.delete(subscriber);
        };
    }, [getMetrics]);
    const subscribeScroll = useCallback((callback) => {
        scrollSubscribersRef.current.add(callback);
        return () => {
            scrollSubscribersRef.current.delete(callback);
        };
    }, []);
    useLayoutEffect(() => {
        const metrics = getMetrics();
        writeLayerMetrics(metrics);
    }, [getMetrics, mode, scrollScale, visualScale, writeLayerMetrics]);
    useEffect(() => {
        invalidateGeometry(["viewport", "layout", "adornment-data"]);
    }, [iframe, invalidateGeometry, mode, scrollScale, visualScale]);
    useEffect(() => {
        const handleHostViewportChange = (event) => {
            if (event.type === SPLITTER_RESIZE_EVENT) {
                pendingReasonsRef.current.add("viewport");
                pendingReasonsRef.current.add("layout");
                if (geometryRafRef.current != null) {
                    window.cancelAnimationFrame(geometryRafRef.current);
                    geometryRafRef.current = null;
                }
                flushMeasurements();
                return;
            }
            invalidateGeometry(["viewport", "layout"]);
        };
        window.addEventListener("resize", handleHostViewportChange);
        window.addEventListener(SPLITTER_RESIZE_EVENT, handleHostViewportChange);
        return () => {
            window.removeEventListener("resize", handleHostViewportChange);
            window.removeEventListener(SPLITTER_RESIZE_EVENT, handleHostViewportChange);
        };
    }, [flushMeasurements, invalidateGeometry]);
    useEffect(() => {
        if (!iframe)
            return;
        let resizeObserver = null;
        try {
            resizeObserver = new ResizeObserver(() => {
                invalidateGeometry("viewport");
            });
            resizeObserver.observe(iframe);
        }
        catch {
            resizeObserver = null;
        }
        return () => {
            resizeObserver?.disconnect();
        };
    }, [iframe, invalidateGeometry]);
    useEffect(() => {
        if (!iframe)
            return;
        let cleanupBoundDocument = null;
        const bindIframeDocument = () => {
            cleanupBoundDocument?.();
            cleanupBoundDocument = null;
            const iframeDocument = getAccessibleIframeDocument(iframe);
            const iframeWindow = getAccessibleIframeWindow(iframe);
            if (!iframeDocument || !iframeWindow)
                return;
            const scrollContainers = Array.from(new Set([
                iframeDocument.scrollingElement,
                iframeDocument.documentElement,
                iframeDocument.body,
            ].filter((element) => !!element)));
            const getBoundScrollMetrics = () => ({
                scrollLeft: Math.max(iframeWindow.scrollX || 0, ...scrollContainers.map((element) => element.scrollLeft || 0)),
                scrollTop: Math.max(iframeWindow.scrollY || 0, ...scrollContainers.map((element) => element.scrollTop || 0)),
                scrollScale: scrollScaleRef.current,
                visualScale: visualScaleRef.current,
            });
            const handleScroll = () => {
                applyScrollMetrics(getBoundScrollMetrics());
                if (scrollRafRef.current != null)
                    return;
                scrollRafRef.current = window.requestAnimationFrame(() => {
                    scrollRafRef.current = null;
                    notifyScrollSubscribers(lastMetricsRef.current);
                });
            };
            if (mode === "scrolling") {
                scrollContainers.forEach((scrollContainer) => {
                    scrollContainer.addEventListener("scroll", handleScroll, {
                        passive: true,
                    });
                });
                iframeWindow.addEventListener("scroll", handleScroll, {
                    passive: true,
                });
            }
            const handleResize = () => invalidateGeometry("viewport");
            iframeWindow.addEventListener("resize", handleResize);
            const handleAssetLoad = () => invalidateGeometry("layout");
            iframeDocument.addEventListener("load", handleAssetLoad, true);
            const mutationObserver = new MutationObserver((records) => {
                const reasons = new Set();
                for (const record of records) {
                    if (record.type === "characterData") {
                        reasons.add("text");
                    }
                    else {
                        reasons.add("layout");
                    }
                }
                if (reasons.size > 0)
                    invalidateGeometry(Array.from(reasons));
            });
            mutationObserver.observe(iframeDocument, {
                attributes: true,
                childList: true,
                characterData: true,
                subtree: true,
            });
            let resizeObserver = null;
            try {
                resizeObserver = new ResizeObserver(() => invalidateGeometry("layout"));
                if (iframeDocument.documentElement) {
                    resizeObserver.observe(iframeDocument.documentElement);
                }
            }
            catch {
                resizeObserver = null;
            }
            updateScrollLayer();
            invalidateGeometry(["viewport", "layout"]);
            cleanupBoundDocument = () => {
                scrollContainers.forEach((scrollContainer) => {
                    scrollContainer.removeEventListener("scroll", handleScroll);
                });
                iframeWindow.removeEventListener("scroll", handleScroll);
                iframeWindow.removeEventListener("resize", handleResize);
                iframeDocument.removeEventListener("load", handleAssetLoad, true);
                mutationObserver.disconnect();
                resizeObserver?.disconnect();
            };
        };
        const handleIframeLoad = () => bindIframeDocument();
        bindIframeDocument();
        iframe.addEventListener("load", handleIframeLoad);
        return () => {
            if (scrollRafRef.current != null) {
                window.cancelAnimationFrame(scrollRafRef.current);
                scrollRafRef.current = null;
            }
            iframe.removeEventListener("load", handleIframeLoad);
            cleanupBoundDocument?.();
            cleanupBoundDocument = null;
        };
    }, [
        iframe,
        applyScrollMetrics,
        getMetrics,
        invalidateGeometry,
        mode,
        notifyScrollSubscribers,
        updateScrollLayer,
        writeScrollPosition,
    ]);
    useEffect(() => {
        return () => {
            if (geometryRafRef.current != null) {
                window.cancelAnimationFrame(geometryRafRef.current);
                geometryRafRef.current = null;
            }
            if (scrollRafRef.current != null) {
                window.cancelAnimationFrame(scrollRafRef.current);
                scrollRafRef.current = null;
            }
        };
    }, []);
    const contextValue = useMemo(() => ({
        iframe,
        getMetrics,
        invalidateGeometry,
        subscribeMeasurement,
        subscribeScroll,
    }), [
        iframe,
        getMetrics,
        invalidateGeometry,
        subscribeMeasurement,
        subscribeScroll,
    ]);
    return (_jsx(IframeOverlayContext.Provider, { value: contextValue, children: _jsx("div", { ref: rootRef, className: "pointer-events-none absolute inset-0 overflow-hidden", "data-testid": "iframe-overlay-root", children: _jsx("div", { ref: layerRef, className: "pointer-events-none absolute top-0 left-0 will-change-transform", "data-testid": "iframe-overlay-layer", children: children }) }) }));
}
//# sourceMappingURL=IframeOverlayProvider.js.map