import { Position } from "../xyflow/react.js";
import { DEFAULT_NODE_SIZE, GRID_SPACING, PREVIEW_NODE_ID } from "../constants.js";
const selectIsConnecting = (state)=>!!state.connection.inProgress;
const getAbsolutePosition = (node, nodes)=>{
    let x = node.position.x;
    let y = node.position.y;
    let currentNode = node;
    while(currentNode?.parentId){
        const parentNode = nodes.find((n)=>n.id === currentNode?.parentId);
        x += parentNode?.position.x ?? 0;
        y += parentNode?.position.y ?? 0;
        currentNode = parentNode;
    }
    return {
        x,
        y
    };
};
function getNonOverlappingPositionForDirection(nodes, newNodePosition, newNodeStyle, direction, offset = 2 * GRID_SPACING, ignoredNodeTypes = [], overflowDirection = {
    x: 'right',
    y: 'down'
}) {
    const isOverlapping = nodes.some((node)=>node.id !== PREVIEW_NODE_ID && !ignoredNodeTypes.includes(node.type ?? '') && node.position.x < newNodePosition.x + newNodeStyle.width && node.position.x + (node.measured?.width ?? 0) > newNodePosition.x && node.position.y < newNodePosition.y + newNodeStyle.height && node.position.y + (node.measured?.height ?? 0) > newNodePosition.y);
    if (isOverlapping) {
        if ('left' === direction || 'right' === direction) newNodePosition.y += 'down' === overflowDirection.y ? offset : -offset;
        else newNodePosition.x += 'right' === overflowDirection.x ? offset : -offset;
        return getNonOverlappingPositionForDirection(nodes, newNodePosition, newNodeStyle, direction, offset, ignoredNodeTypes, overflowDirection);
    }
    return newNodePosition;
}
const snapToGrid = (value)=>Math.round(value / GRID_SPACING) * GRID_SPACING;
const snapUpToGrid = (value)=>Math.ceil(value / GRID_SPACING) * GRID_SPACING;
const snapDownToGrid = (value)=>Math.floor(value / GRID_SPACING) * GRID_SPACING;
const clamp = (value, min, max)=>Math.min(Math.max(value, min), max);
function getBoxesFromNodes(nodes, margin = 0, getNodeDimensions) {
    const boxes = new Array(nodes.length);
    for(let i = 0; i < nodes.length; i++){
        const node = nodes[i];
        const nodeSize = getNodeDimensions?.(node) ?? {
            width: node.width ?? node.measured?.width ?? DEFAULT_NODE_SIZE,
            height: node.height ?? node.measured?.height ?? DEFAULT_NODE_SIZE
        };
        boxes[i] = {
            x: node.position.x - margin,
            y: node.position.y - margin,
            width: nodeSize.width + 2 * margin,
            height: nodeSize.height + 2 * margin,
            node,
            moved: false
        };
    }
    return boxes;
}
const resolveCollisions = (nodes, { maxIterations = 50, overlapThreshold = 0, margin = 2 * GRID_SPACING, ignoredNodeTypes, getNodeDimensions } = {})=>{
    const ignoredSet = new Set(ignoredNodeTypes);
    const collisionNodes = ignoredSet.size > 0 ? nodes.filter((n)=>!ignoredSet.has(n.type ?? '')) : nodes;
    const boxes = getBoxesFromNodes(collisionNodes, margin, getNodeDimensions);
    for(let iter = 0; iter < maxIterations; iter++){
        let moved = false;
        for(let i = 0; i < boxes.length; i++)for(let j = i + 1; j < boxes.length; j++){
            const A = boxes[i];
            const B = boxes[j];
            const centerAX = A.x + 0.5 * A.width;
            const centerAY = A.y + 0.5 * A.height;
            const centerBX = B.x + 0.5 * B.width;
            const centerBY = B.y + 0.5 * B.height;
            const dx = centerAX - centerBX;
            const dy = centerAY - centerBY;
            const px = (A.width + B.width) * 0.5 - Math.abs(dx);
            const py = (A.height + B.height) * 0.5 - Math.abs(dy);
            if (px > overlapThreshold && py > overlapThreshold) {
                A.moved = B.moved = moved = true;
                if (px < py) {
                    const sx = dx > 0 ? 1 : -1;
                    const moveAmount = snapToGrid(px / 2 * sx);
                    A.x += moveAmount;
                    B.x -= moveAmount;
                } else {
                    const sy = dy > 0 ? 1 : -1;
                    const moveAmount = snapToGrid(py / 2 * sy);
                    A.y += moveAmount;
                    B.y -= moveAmount;
                }
            }
        }
        if (!moved) break;
    }
    const resolvedMap = new Map();
    for (const box of boxes)if (box.moved) resolvedMap.set(box.node.id, {
        ...box.node,
        position: {
            x: snapToGrid(box.x + margin),
            y: snapToGrid(box.y + margin)
        }
    });
    return nodes.map((n)=>resolvedMap.get(n.id) ?? n);
};
function resolveHandleContext(internalNode, handleId, handlePosition, options) {
    const allHandles = [
        ...internalNode.internals.handleBounds?.source ?? [],
        ...internalNode.internals.handleBounds?.target ?? []
    ];
    const matchedHandle = allHandles.find((h)=>h.id === handleId);
    if (!matchedHandle) return;
    const peers = filterRailPeers(allHandles, handleId, handlePosition, options?.boundaryOf);
    return {
        anchor: {
            x: internalNode.internals.positionAbsolute.x + matchedHandle.x + matchedHandle.width / 2,
            y: internalNode.internals.positionAbsolute.y + matchedHandle.y + matchedHandle.height / 2
        },
        index: getHandleIndex(handleId, handlePosition, peers),
        count: peers.length
    };
}
function filterRailPeers(allHandles, handleId, handlePosition, boundaryOf) {
    const samePosition = allHandles.filter((h)=>h.position === handlePosition);
    if (!boundaryOf) return samePosition;
    const targetBoundary = boundaryOf(handleId);
    if (void 0 === targetBoundary) return samePosition;
    return samePosition.filter((h)=>{
        if (!h.id) return false;
        const peerBoundary = boundaryOf(h.id);
        return void 0 === peerBoundary || peerBoundary === targetBoundary;
    });
}
function getHandleIndex(handleId, position, allHandles) {
    const peers = allHandles.filter((h)=>h.position === position).sort((a, b)=>position === Position.Left || position === Position.Right ? a.y - b.y : a.x - b.x);
    const index = peers.findIndex((h)=>h.id === handleId);
    return -1 === index ? null : index;
}
export { clamp, getAbsolutePosition, getHandleIndex, getNonOverlappingPositionForDirection, resolveCollisions, resolveHandleContext, selectIsConnecting, snapDownToGrid, snapToGrid, snapUpToGrid };
