import { useReactFlow, useStoreApi } from "../../xyflow/react.js";
import { useCallback, useEffect, useRef, useState } from "react";
import { BASE_CANVAS_DEFAULTS, FIT_VIEW_DELAY_MS } from "./BaseCanvas.constants.js";
const waitForNodeMeasurements = (getNodes)=>new Promise((resolve)=>{
        const startTime = Date.now();
        const timeoutInMs = 500;
        const check = ()=>{
            const currentNodes = getNodes();
            if (currentNodes.every((node)=>node.measured)) resolve();
            else if (Date.now() - startTime > timeoutInMs) resolve();
            else requestAnimationFrame(check);
        };
        check();
    });
const useAutoLayout = (nodes, initialAutoLayout, fitViewOptions)=>{
    const [isReady, setIsReady] = useState(false);
    const hasRunLayout = useRef(false);
    const timeoutRef = useRef(null);
    const reactFlow = useReactFlow();
    const nodesRef = useRef([]);
    const prevNodeIds = useRef('');
    const currentNodeIds = nodes?.map((n)=>n.id).sort().join(',') ?? '';
    const isNewNodeSet = currentNodeIds !== prevNodeIds.current && currentNodeIds?.length > 0;
    if (isNewNodeSet) {
        hasRunLayout.current = false;
        prevNodeIds.current = currentNodeIds;
    }
    nodesRef.current = nodes;
    useEffect(()=>{
        if (!initialAutoLayout) return void setIsReady(true);
        if (hasRunLayout.current && !isNewNodeSet) return;
        if (nodes?.length === 0) return;
        const runLayout = async ()=>{
            try {
                await waitForNodeMeasurements(()=>reactFlow.getNodes());
                await initialAutoLayout();
                hasRunLayout.current = true;
                timeoutRef.current = setTimeout(()=>{
                    const options = fitViewOptions || BASE_CANVAS_DEFAULTS.fitViewOptions;
                    const duration = isReady ? options.duration : 0;
                    reactFlow.fitView({
                        ...options,
                        duration
                    });
                    setIsReady(true);
                    timeoutRef.current = null;
                }, FIT_VIEW_DELAY_MS);
            } catch  {
                setIsReady(true);
            }
        };
        runLayout();
        return ()=>{
            if (timeoutRef.current) {
                clearTimeout(timeoutRef.current);
                timeoutRef.current = null;
            }
        };
    }, [
        nodes?.length,
        reactFlow,
        initialAutoLayout,
        isNewNodeSet,
        fitViewOptions,
        isReady
    ]);
    return {
        isReady
    };
};
const useFitView = (nodes, delay, fitViewOptions)=>{
    const reactFlow = useReactFlow();
    const currentViewport = reactFlow.getViewport();
    const timeoutRef = useRef(null);
    useEffect(()=>()=>{
            if (timeoutRef.current) {
                clearTimeout(timeoutRef.current);
                timeoutRef.current = null;
            }
        }, []);
    const fitView = useCallback(()=>{
        if (timeoutRef.current) clearTimeout(timeoutRef.current);
        timeoutRef.current = setTimeout(()=>{
            reactFlow.fitView({
                ...fitViewOptions ?? BASE_CANVAS_DEFAULTS.fitViewOptions,
                minZoom: currentViewport.zoom,
                maxZoom: currentViewport.zoom,
                nodes: nodes && nodes.length > 0 ? nodes : void 0
            });
            timeoutRef.current = null;
        }, delay ?? 0);
    }, [
        reactFlow,
        currentViewport,
        nodes,
        delay,
        fitViewOptions
    ]);
    return {
        fitView
    };
};
const useEnsureNodesInView = ()=>{
    const reactFlow = useReactFlow();
    const ensureNodesInView = useCallback((nodeIds, options)=>{
        const nodes = reactFlow.getNodes().filter((node)=>nodeIds.includes(node.id));
        if (0 === nodes.length) return;
        const fitViewOptions = {
            ...BASE_CANVAS_DEFAULTS.fitViewOptions,
            nodes,
            padding: options?.padding ?? BASE_CANVAS_DEFAULTS.fitViewOptions.padding,
            duration: options?.duration ?? BASE_CANVAS_DEFAULTS.fitViewOptions.duration,
            minZoom: options?.minZoom ?? BASE_CANVAS_DEFAULTS.zoom.min,
            maxZoom: options?.maxZoom ?? BASE_CANVAS_DEFAULTS.zoom.max
        };
        if (options?.maintainZoom) {
            const currentZoom = reactFlow.getViewport().zoom;
            fitViewOptions.minZoom = currentZoom;
            fitViewOptions.maxZoom = currentZoom;
        }
        reactFlow.fitView(fitViewOptions);
    }, [
        reactFlow
    ]);
    const ensureAllNodesInView = useCallback((options)=>{
        const fitViewOptions = {
            ...BASE_CANVAS_DEFAULTS.fitViewOptions,
            padding: options?.padding ?? BASE_CANVAS_DEFAULTS.fitViewOptions.padding,
            duration: options?.duration ?? BASE_CANVAS_DEFAULTS.fitViewOptions.duration,
            minZoom: options?.minZoom ?? BASE_CANVAS_DEFAULTS.zoom.min,
            maxZoom: options?.maxZoom ?? BASE_CANVAS_DEFAULTS.zoom.max
        };
        if (options?.maintainZoom) {
            const currentZoom = reactFlow.getViewport().zoom;
            fitViewOptions.minZoom = currentZoom;
            fitViewOptions.maxZoom = currentZoom;
        }
        reactFlow.fitView(fitViewOptions);
    }, [
        reactFlow
    ]);
    const centerNode = useCallback((nodeId, options)=>{
        const node = reactFlow.getInternalNode(nodeId);
        if (!node) return;
        const nodeWidth = node.measured?.width ?? node.width;
        const nodeHeight = node.measured?.height ?? node.height;
        if (!nodeWidth || !nodeHeight) return;
        const x = node.position.x + nodeWidth / 2;
        const y = node.position.y + nodeHeight / 2;
        const zoom = options?.maintainZoom ? reactFlow.getViewport().zoom : 1;
        reactFlow.setCenter(x, y, {
            zoom,
            duration: options?.duration ?? BASE_CANVAS_DEFAULTS.fitViewOptions.duration
        });
    }, [
        reactFlow
    ]);
    return {
        ensureNodesInView,
        ensureAllNodesInView,
        centerNode
    };
};
const useMaintainNodesInView = (nodeIds, fitViewOptions)=>{
    const flowStoreApi = useStoreApi();
    const reactFlowInstance = useReactFlow();
    const resizeTimeoutRef = useRef(null);
    const nodeIdsRef = useRef(nodeIds);
    useEffect(()=>{
        nodeIdsRef.current = nodeIds;
    }, [
        nodeIds
    ]);
    useEffect(()=>{
        const reactFlowContainer = flowStoreApi.getState().domNode;
        if (!reactFlowContainer) return;
        const handleResize = ()=>{
            const currentNodeIds = nodeIdsRef.current;
            if (void 0 === currentNodeIds) return;
            let nodesToMaintain = currentNodeIds;
            if (0 === currentNodeIds.length) nodesToMaintain = reactFlowInstance.getNodes().map((node)=>node.id);
            if (resizeTimeoutRef.current) clearTimeout(resizeTimeoutRef.current);
            resizeTimeoutRef.current = setTimeout(()=>{
                if (0 === nodesToMaintain.length) return;
                const currentZoom = reactFlowInstance.getViewport().zoom;
                reactFlowInstance.fitView({
                    nodes: nodesToMaintain.map((id)=>({
                            id
                        })),
                    padding: fitViewOptions?.padding ?? BASE_CANVAS_DEFAULTS.fitViewOptions.padding,
                    duration: fitViewOptions?.duration ?? BASE_CANVAS_DEFAULTS.fitViewOptions.duration,
                    minZoom: currentZoom,
                    maxZoom: currentZoom
                });
            }, BASE_CANVAS_DEFAULTS.maintainNodesInView.debounceMs);
        };
        const resizeObserver = new ResizeObserver(handleResize);
        resizeObserver.observe(reactFlowContainer);
        return ()=>{
            resizeObserver.disconnect();
            if (resizeTimeoutRef.current) clearTimeout(resizeTimeoutRef.current);
        };
    }, [
        flowStoreApi,
        reactFlowInstance,
        fitViewOptions
    ]);
};
export { useAutoLayout, useEnsureNodesInView, useFitView, useMaintainNodesInView };
