"use client";
import { useCallback, useEffect, useRef } from "react";
import { IFRAME_OVERLAY_BRIDGE_DRAG_GESTURE_EVENT, } from "./overlay/IframeOverlayProvider";
import { DRAG_START_THRESHOLD_PX, getAutoScrollStep, } from "../../lib/pointerDrag";
export function useDragInsertionGesture({ iframe, pageViewContext, enabled, preventDefaultOnPointerDown = true, getStartDetail, onStart, onEnd, }) {
    const iframeRef = useRef(iframe);
    iframeRef.current = iframe;
    const pvcRef = useRef(pageViewContext);
    pvcRef.current = pageViewContext;
    const enabledRef = useRef(enabled);
    enabledRef.current = enabled;
    const preventDefaultOnPointerDownRef = useRef(preventDefaultOnPointerDown);
    preventDefaultOnPointerDownRef.current = preventDefaultOnPointerDown;
    const getStartDetailRef = useRef(getStartDetail);
    getStartDetailRef.current = getStartDetail;
    const onStartRef = useRef(onStart);
    onStartRef.current = onStart;
    const onEndRef = useRef(onEnd);
    onEndRef.current = onEnd;
    const cleanupRef = useRef(null);
    useEffect(() => () => cleanupRef.current?.(), []);
    const onPointerDown = useCallback((e) => {
        if (!enabledRef.current || e.button !== 0)
            return;
        const iframe = iframeRef.current;
        if (!iframe)
            return;
        if (preventDefaultOnPointerDownRef.current) {
            e.preventDefault();
        }
        const source = e.currentTarget;
        const pointerId = e.pointerId;
        const startX = e.clientX;
        const startY = e.clientY;
        let started = false;
        let finished = false;
        let lastX = startX;
        let lastY = startY;
        let autoScrollRaf = 0;
        // Capture the pointer on a STABLE element (the parent <html>) rather than the
        // grip: the frame menu hides the grip on dragStart, so a grip-bound capture
        // would be released the instant the drag begins - and once capture is gone,
        // pointermoves over the canvas iframe stop reaching the parent window and the
        // drag freezes. documentElement never unmounts, so every move stays routed to
        // the parent for the whole gesture.
        const captureTarget = source.ownerDocument?.documentElement ?? source;
        try {
            captureTarget.setPointerCapture(pointerId);
        }
        catch {
            // Capture is best-effort; window listeners still drive the gesture.
        }
        const controller = new AbortController();
        const { signal } = controller;
        const dispatch = (detail) => {
            iframe.dispatchEvent(new CustomEvent(IFRAME_OVERLAY_BRIDGE_DRAG_GESTURE_EVENT, { detail }));
        };
        const stopAutoScroll = () => {
            if (autoScrollRaf) {
                cancelAnimationFrame(autoScrollRaf);
                autoScrollRaf = 0;
            }
        };
        const edgeAutoScrollStep = () => {
            const rect = iframe.getBoundingClientRect();
            if (lastX < rect.left || lastX > rect.right)
                return 0;
            return getAutoScrollStep(lastY, rect.top, rect.bottom);
        };
        const runAutoScroll = () => {
            autoScrollRaf = 0;
            if (!started)
                return;
            const step = edgeAutoScrollStep();
            if (step === 0)
                return;
            pvcRef.current?.requestBridgeScrollBy?.({ y: step, behavior: "auto" });
            dispatch({
                phase: "move",
                pointerId,
                clientX: lastX,
                clientY: lastY,
                space: "client",
            });
            autoScrollRaf = requestAnimationFrame(runAutoScroll);
        };
        const updateAutoScroll = () => {
            const step = edgeAutoScrollStep();
            if (step !== 0 && !autoScrollRaf) {
                autoScrollRaf = requestAnimationFrame(runAutoScroll);
            }
            else if (step === 0) {
                stopAutoScroll();
            }
        };
        const cleanup = () => {
            stopAutoScroll();
            controller.abort();
            try {
                captureTarget.releasePointerCapture(pointerId);
            }
            catch {
                // Already released (e.g. capture never took); nothing to do.
            }
            cleanupRef.current = null;
        };
        const finish = (canceled) => {
            if (finished)
                return;
            finished = true;
            if (started) {
                dispatch({
                    phase: "end",
                    pointerId,
                    clientX: lastX,
                    clientY: lastY,
                    canceled,
                    space: "client",
                });
            }
            cleanup();
            onEndRef.current?.({ canceled, started });
        };
        // Once the drag has started it is driven by the window-level pointer
        // listeners below, which keep firing even after this hook (and the grip
        // element) unmount - which is exactly what happens when the frame menu hides
        // on dragStart (FrameMenus flips into insertion mode and stops rendering the
        // grip). Cancelling/tearing down on unmount would abort every drag the
        // instant it begins; let pointerup/pointercancel finish it. Only an
        // un-started gesture still needs teardown when the grip goes away.
        cleanupRef.current = () => {
            if (started)
                return;
            finish(true);
        };
        window.addEventListener("pointermove", (ev) => {
            if (ev.pointerId !== pointerId)
                return;
            lastX = ev.clientX;
            lastY = ev.clientY;
            if (!started) {
                const dx = ev.clientX - startX;
                const dy = ev.clientY - startY;
                if (Math.hypot(dx, dy) <= DRAG_START_THRESHOLD_PX)
                    return;
                const startDetail = getStartDetailRef.current();
                if (!startDetail) {
                    finish(true);
                    return;
                }
                started = true;
                onStartRef.current?.();
                dispatch({
                    phase: "start",
                    pointerId,
                    ...startDetail,
                    clientX: ev.clientX,
                    clientY: ev.clientY,
                    space: "client",
                });
            }
            else {
                dispatch({
                    phase: "move",
                    pointerId,
                    clientX: ev.clientX,
                    clientY: ev.clientY,
                    space: "client",
                });
                updateAutoScroll();
            }
        }, { signal });
        window.addEventListener("pointerup", (ev) => {
            if (ev.pointerId !== pointerId)
                return;
            finish(false);
        }, { signal });
        window.addEventListener("pointercancel", (ev) => {
            if (ev.pointerId !== pointerId)
                return;
            finish(true);
        }, { signal });
        captureTarget.addEventListener("lostpointercapture", (ev) => {
            if (ev.pointerId !== pointerId)
                return;
            // Losing capture before the drag starts cancels it; once started, the
            // window listeners carry it to completion regardless of capture.
            if (started)
                return;
            finish(true);
        }, { signal });
        window.addEventListener("blur", () => finish(true), { signal });
        window.addEventListener("pagehide", () => finish(true), { signal });
        document.addEventListener("visibilitychange", () => {
            if (document.visibilityState === "hidden")
                finish(true);
        }, { signal });
        window.addEventListener("keydown", (ev) => {
            if (ev.key === "Escape") {
                ev.preventDefault();
                finish(true);
            }
        }, { signal });
    }, []);
    return { onPointerDown };
}
//# sourceMappingURL=useDragInsertionGesture.js.map