import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { Handle, Position, useNodeId, useStore, useUpdateNodeInternals } from "@xyflow/react";
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { canvasEventBus } from "../../utils/CanvasEventBus.js";
import { cx } from "../../utils/CssUtil.js";
import { calculateGridAlignedHandlePositions, pixelToPercent } from "./ButtonHandleStyleUtils.js";
import { HandleButton } from "./HandleButton.js";
import { HandleLabel } from "./HandleLabel.js";
import { HandleNotch } from "./HandleNotch.js";
import { useButtonHandleSizeAndPosition } from "./useButtonHandleSizeAndPosition.js";
const SIDE_TO_POSITION = {
    top: Position.Top,
    right: Position.Right,
    bottom: Position.Bottom,
    left: Position.Left
};
const NEXT_SIDE_CLOCKWISE = {
    top: 'right',
    right: 'bottom',
    bottom: 'left',
    left: 'top'
};
const SmartHandleContext = /*#__PURE__*/ createContext(null);
function SmartHandleProvider({ children, nodeWidth: _nodeWidth, nodeHeight: _nodeHeight }) {
    const registrationsRef = useRef(new Map());
    const [version, setVersion] = useState(0);
    const registerHandle = useCallback((nodeId, registration)=>{
        if (!nodeId) return;
        const current = registrationsRef.current.get(nodeId) ?? [];
        const existingIndex = current.findIndex((r)=>r.handleId === registration.handleId && r.handleType === registration.handleType);
        const existing = existingIndex >= 0 ? current[existingIndex] : void 0;
        if (existing) {
            if (existing.computedPosition === registration.computedPosition && existing.configOrder === registration.configOrder) return;
            current[existingIndex] = registration;
        } else {
            current.push(registration);
            registrationsRef.current.set(nodeId, current);
        }
        setVersion((v)=>v + 1);
    }, []);
    const unregisterHandle = useCallback((nodeId, handleId, handleType)=>{
        if (!nodeId) return;
        const current = registrationsRef.current.get(nodeId) ?? [];
        const filtered = current.filter((r)=>!(r.handleId === handleId && r.handleType === handleType));
        if (filtered.length === current.length) return;
        registrationsRef.current.set(nodeId, filtered);
        setVersion((v)=>v + 1);
    }, []);
    const getRegistrations = useCallback(()=>registrationsRef.current, []);
    const contextValue = useMemo(()=>({
            registerHandle,
            unregisterHandle,
            getRegistrations,
            version
        }), [
        registerHandle,
        unregisterHandle,
        getRegistrations,
        version
    ]);
    return /*#__PURE__*/ jsx(SmartHandleContext.Provider, {
        value: contextValue,
        children: children
    });
}
function useSmartHandleContext() {
    return useContext(SmartHandleContext);
}
function getHandleIndexOnSide(registrations, nodeId, handleId, handleType, position) {
    if (!nodeId) return {
        index: 0,
        total: 1
    };
    const nodeRegistrations = registrations.get(nodeId) ?? [];
    const handlesOnSide = nodeRegistrations.filter((r)=>r.computedPosition === position);
    const sorted = [
        ...handlesOnSide
    ].sort((a, b)=>a.configOrder - b.configOrder);
    const index = sorted.findIndex((r)=>r.handleId === handleId && r.handleType === handleType);
    return {
        index: index >= 0 ? index : 0,
        total: sorted.length
    };
}
function getNodeBounds(node) {
    const width = node.measured?.width || node.width || 150;
    const height = node.measured?.height || node.height || 40;
    const x = node.position.x;
    const y = node.position.y;
    return {
        x,
        y,
        width,
        height,
        centerX: x + width / 2,
        centerY: y + height / 2
    };
}
function calculateOptimalSide(currentBounds, connectedBounds) {
    const dx = connectedBounds.centerX - currentBounds.centerX;
    const dy = connectedBounds.centerY - currentBounds.centerY;
    const aspectRatio = currentBounds.width / currentBounds.height;
    const angle = Math.atan2(dy, dx);
    const degrees = 180 / Math.PI * angle;
    const threshold = 180 / Math.PI * Math.atan(1 / aspectRatio);
    if (degrees >= -threshold && degrees < threshold) return 'right';
    if (degrees >= threshold && degrees < 180 - threshold) return 'bottom';
    if (degrees >= 180 - threshold || degrees < -(180 - threshold)) return 'left';
    return 'top';
}
function findHandleConnections(edges, nodeId, handleId, handleType) {
    const connections = [];
    for (const edge of edges)if ('source' === handleType) {
        if (edge.source === nodeId) {
            const handleMatches = handleId ? edge.sourceHandle === handleId : !edge.sourceHandle;
            if (handleMatches) connections.push({
                connectedNodeId: edge.target,
                isSource: true
            });
        }
    } else if (edge.target === nodeId) {
        const handleMatches = handleId ? edge.targetHandle === handleId : !edge.targetHandle;
        if (handleMatches) connections.push({
            connectedNodeId: edge.source,
            isSource: false
        });
    }
    return connections;
}
const selectNodes = (state)=>state.nodes;
const selectEdges = (state)=>state.edges;
function findOptimalSidesForHandleType(edges, nodeId, handleType, currentBounds, nodeMap) {
    const handleSides = new Map();
    const relevantEdges = edges.filter((edge)=>'source' === handleType ? edge.source === nodeId : edge.target === nodeId);
    for (const edge of relevantEdges){
        const handleId = 'source' === handleType ? edge.sourceHandle : edge.targetHandle;
        const connectedNodeId = 'source' === handleType ? edge.target : edge.source;
        const connectedNode = nodeMap.get(connectedNodeId);
        if (connectedNode) {
            const connectedBounds = getNodeBounds(connectedNode);
            const optimalSide = calculateOptimalSide(currentBounds, connectedBounds);
            handleSides.set(handleId, optimalSide);
        }
    }
    return handleSides;
}
function useSmartPosition(nodeId, handleId, handleType, defaultPosition, hasSourceHandles) {
    const nodes = useStore(selectNodes);
    const edges = useStore(selectEdges);
    return useMemo(()=>{
        if (!nodeId) return defaultPosition;
        const nodeMap = new Map();
        for (const node of nodes)nodeMap.set(node.id, node);
        const currentNode = nodeMap.get(nodeId);
        if (!currentNode) return defaultPosition;
        const connections = findHandleConnections(edges, nodeId, handleId, handleType);
        if (0 === connections.length) return defaultPosition;
        const currentBounds = getNodeBounds(currentNode);
        let optimalSide;
        if (1 === connections.length) {
            const firstConnection = connections[0];
            if (!firstConnection) return defaultPosition;
            const connectedNode = nodeMap.get(firstConnection.connectedNodeId);
            if (!connectedNode) return defaultPosition;
            const connectedBounds = getNodeBounds(connectedNode);
            optimalSide = calculateOptimalSide(currentBounds, connectedBounds);
        } else {
            let totalX = 0;
            let totalY = 0;
            let count = 0;
            for (const conn of connections){
                const connectedNode = nodeMap.get(conn.connectedNodeId);
                if (connectedNode) {
                    const bounds = getNodeBounds(connectedNode);
                    totalX += bounds.centerX;
                    totalY += bounds.centerY;
                    count++;
                }
            }
            if (0 === count) return defaultPosition;
            const avgBounds = {
                x: totalX / count,
                y: totalY / count,
                width: 0,
                height: 0,
                centerX: totalX / count,
                centerY: totalY / count
            };
            optimalSide = calculateOptimalSide(currentBounds, avgBounds);
        }
        if ('source' === handleType && 'left' === optimalSide) optimalSide = 'top';
        else if ('target' === handleType && 'right' === optimalSide && hasSourceHandles) optimalSide = 'bottom';
        if ('target' === handleType && hasSourceHandles) {
            const sourceSides = findOptimalSidesForHandleType(edges, nodeId, 'source', currentBounds, nodeMap);
            const adjustedSourceSides = new Set();
            for (const side of sourceSides.values())adjustedSourceSides.add('left' === side ? 'top' : side);
            adjustedSourceSides.add('right');
            if (adjustedSourceSides.has(optimalSide)) {
                let newSide = NEXT_SIDE_CLOCKWISE[optimalSide];
                let attempts = 0;
                while((adjustedSourceSides.has(newSide) || 'right' === newSide) && attempts < 3){
                    newSide = NEXT_SIDE_CLOCKWISE[newSide];
                    attempts++;
                }
                optimalSide = newSide;
            }
        }
        return SIDE_TO_POSITION[optimalSide];
    }, [
        nodeId,
        nodes,
        edges,
        handleId,
        handleType,
        defaultPosition,
        hasSourceHandles
    ]);
}
function SmartHandle({ type, id, defaultPosition = 'source' === type ? Position.Right : Position.Left, handleType = 'source' === type ? 'output' : 'input', style, className, nodeWidth, nodeHeight, label, labelIcon, labelBackgroundColor, showButton = false, selected = false, showNotches = true, onAction, visible = true, configOrder, ...rest }) {
    const nodeId = useNodeId();
    const updateNodeInternals = useUpdateNodeInternals();
    const [isHovered, setIsHovered] = useState(false);
    const smartHandleContext = useSmartHandleContext();
    const handleRef = useRef(null);
    const prevPositionRef = useRef(null);
    const contextVersion = smartHandleContext?.version ?? 0;
    const hasSourceHandles = useMemo(()=>{
        if (!nodeId || !smartHandleContext) return false;
        const registrations = smartHandleContext.getRegistrations();
        const nodeRegistrations = registrations.get(nodeId) ?? [];
        return nodeRegistrations.some((r)=>'source' === r.handleType);
    }, [
        nodeId,
        smartHandleContext,
        contextVersion
    ]);
    const computedPosition = useSmartPosition(nodeId, id, type, defaultPosition, hasSourceHandles);
    const isVertical = computedPosition === Position.Top || computedPosition === Position.Bottom;
    const registerHandle = smartHandleContext?.registerHandle;
    const unregisterHandle = smartHandleContext?.unregisterHandle;
    useEffect(()=>{
        if (!nodeId || !registerHandle || !unregisterHandle) return;
        registerHandle(nodeId, {
            handleId: id,
            handleType: type,
            computedPosition,
            configOrder: configOrder ?? 0
        });
        return ()=>{
            unregisterHandle(nodeId, id, type);
        };
    }, [
        nodeId,
        id,
        type,
        computedPosition,
        registerHandle,
        unregisterHandle,
        configOrder
    ]);
    useEffect(()=>{
        if (!nodeId) return;
        if (prevPositionRef.current !== computedPosition) {
            prevPositionRef.current = computedPosition;
            requestAnimationFrame(()=>{
                updateNodeInternals(nodeId);
            });
        }
    }, [
        nodeId,
        computedPosition,
        updateNodeInternals
    ]);
    const { index, total } = useMemo(()=>{
        if (!nodeId || !smartHandleContext) return {
            index: 0,
            total: 1
        };
        const registrations = smartHandleContext.getRegistrations();
        return getHandleIndexOnSide(registrations, nodeId, id, type, computedPosition);
    }, [
        nodeId,
        id,
        type,
        computedPosition,
        contextVersion
    ]);
    const positionPercent = useMemo(()=>{
        if (total <= 1) return 50;
        const relevantSize = isVertical ? nodeWidth : nodeHeight;
        if (relevantSize && relevantSize > 0) {
            const gridPositions = calculateGridAlignedHandlePositions(relevantSize, total);
            const pixelPosition = gridPositions[index] ?? relevantSize / 2;
            return pixelToPercent(pixelPosition, relevantSize);
        }
        return (index + 1) / (total + 1) * 100;
    }, [
        index,
        total,
        isVertical,
        nodeWidth,
        nodeHeight
    ]);
    const { width, height, top, bottom, left, right, transform } = useButtonHandleSizeAndPosition({
        position: computedPosition,
        positionPercent,
        numHandles: total
    });
    const handleButtonClick = useCallback((event)=>{
        event.stopPropagation();
        if (!nodeId) return;
        const actionEvent = {
            handleId: id ?? '',
            nodeId,
            handleType,
            position: computedPosition,
            originalEvent: event
        };
        onAction?.(actionEvent);
        canvasEventBus.emit('handle:action', {
            handleId: id ?? '',
            nodeId,
            handleType,
            position: computedPosition
        });
    }, [
        id,
        nodeId,
        handleType,
        computedPosition,
        onAction
    ]);
    return /*#__PURE__*/ jsx(Handle, {
        ref: handleRef,
        type: type,
        position: computedPosition,
        id: id,
        className: cx('flex! items-center! justify-center! border-0! rounded-none! bg-transparent!', visible ? 'cursor-crosshair! pointer-events-auto!' : 'cursor-default! pointer-events-none!', className),
        isConnectable: visible && 'artifact' !== handleType,
        onMouseEnter: ()=>setIsHovered(true),
        onMouseLeave: ()=>setIsHovered(false),
        onMouseDown: ()=>setIsHovered(false),
        style: {
            width,
            height,
            top,
            bottom,
            left,
            right,
            transform,
            ...style
        },
        ...rest,
        children: visible && /*#__PURE__*/ jsxs(Fragment, {
            children: [
                onAction && 'source' === type ? /*#__PURE__*/ jsx(HandleButton, {
                    visible: showButton,
                    labelVisible: visible,
                    position: computedPosition,
                    onAction: handleButtonClick,
                    handleRef: handleRef,
                    label: label,
                    labelIcon: labelIcon,
                    labelBackgroundColor: labelBackgroundColor
                }) : label && /*#__PURE__*/ jsx(HandleLabel, {
                    position: computedPosition,
                    backgroundColor: labelBackgroundColor,
                    label: label,
                    labelIcon: labelIcon
                }),
                /*#__PURE__*/ jsx(HandleNotch, {
                    handleType: handleType,
                    isVertical: isVertical,
                    selected: selected,
                    hovered: isHovered,
                    showNotch: showNotches
                })
            ]
        })
    });
}
function SmartSourceHandle(props) {
    return /*#__PURE__*/ jsx(SmartHandle, {
        type: "source",
        handleType: "output",
        ...props
    });
}
function SmartTargetHandle(props) {
    return /*#__PURE__*/ jsx(SmartHandle, {
        type: "target",
        handleType: "input",
        ...props
    });
}
export { SmartHandle, SmartHandleProvider, SmartSourceHandle, SmartTargetHandle };
