import { Position } from "../../xyflow/react.js";
import { GRID_SPACING } from "../../constants.js";
import { resolveCollisions } from "../../utils/index.js";
import { getExpandedSize } from "../../utils/collapse.js";
import { CONTAINER_SEQUENCE_GAP_PX, collectLinearDownstreamSiblings, fitContainersAndPushSiblings, getContainerPlacement, getContainerSafeArea, getNodeDimensions as container_js_getNodeDimensions, placeContainerNode } from "../../utils/container.js";
import { snapUpToGrid } from "../../utils/NodeUtils.js";
const TOP_LEVEL_INSERTION_GAP_PX = 5 * GRID_SPACING;
function getOriginalEdge(previewNode) {
    return previewNode?.data?.originalEdge ?? null;
}
function getPrimaryPreviewHandlePosition(previewNode, previewConnections) {
    const primaryConnection = previewConnections.find((connection)=>!connection.addNewNodeAsSource) ?? previewConnections[0];
    const handlePosition = primaryConnection?.addNewNodeAsSource ? previewNode.data?.outputHandlePosition : previewNode.data?.inputHandlePosition;
    return handlePosition;
}
function alignNodeToPreview(node, previewNode, previewConnections, nodeManifest) {
    const previewSize = container_js_getNodeDimensions(previewNode);
    const nodeSize = container_js_getNodeDimensions(node, getExpandedSize(nodeManifest?.display.shape));
    if (previewSize.width === nodeSize.width && previewSize.height === nodeSize.height) return node;
    const previewHandlePosition = getPrimaryPreviewHandlePosition(previewNode, previewConnections);
    const previewCenterX = previewNode.position.x + previewSize.width / 2;
    const previewCenterY = previewNode.position.y + previewSize.height / 2;
    if (previewHandlePosition === Position.Left) return {
        ...node,
        position: {
            x: previewNode.position.x,
            y: previewCenterY - nodeSize.height / 2
        }
    };
    if (previewHandlePosition === Position.Right) return {
        ...node,
        position: {
            x: previewNode.position.x + previewSize.width - nodeSize.width,
            y: previewCenterY - nodeSize.height / 2
        }
    };
    if (previewHandlePosition === Position.Top) return {
        ...node,
        position: {
            x: previewCenterX - nodeSize.width / 2,
            y: previewNode.position.y
        }
    };
    if (previewHandlePosition === Position.Bottom) return {
        ...node,
        position: {
            x: previewCenterX - nodeSize.width / 2,
            y: previewNode.position.y + previewSize.height - nodeSize.height
        }
    };
    return {
        ...node,
        position: {
            x: previewCenterX - nodeSize.width / 2,
            y: previewCenterY - nodeSize.height / 2
        }
    };
}
function resolveScopedCollisions(nodes, insertedNode, options) {
    const siblingNodes = nodes.filter((node)=>node.parentId === insertedNode.parentId);
    const resolvedSiblings = resolveCollisions(siblingNodes, options);
    const resolvedSiblingById = new Map(resolvedSiblings.map((node)=>[
            node.id,
            node
        ]));
    const resolvedNodes = nodes.map((node)=>resolvedSiblingById.get(node.id) ?? node);
    return {
        nodes: resolvedNodes,
        insertedNode: resolvedSiblingById.get(insertedNode.id)
    };
}
function shiftForEdgeInsertion({ nodes, edges, previewNode, insertedNode, getNodeDimensions }) {
    const originalEdge = getOriginalEdge(previewNode);
    if (!originalEdge) return null;
    const targetNode = nodes.find((node)=>node.id === originalEdge.target);
    if (!targetNode || targetNode.parentId !== insertedNode.parentId) return null;
    const insertedSize = getNodeDimensions(insertedNode);
    let insertedX = insertedNode.position.x;
    const sourceNode = nodes.find((node)=>node.id === originalEdge.source);
    if (sourceNode && sourceNode.parentId === insertedNode.parentId) {
        const sourceSize = getNodeDimensions(sourceNode);
        const requiredInsertedLeft = sourceNode.position.x + sourceSize.width + TOP_LEVEL_INSERTION_GAP_PX;
        if (insertedX < requiredInsertedLeft) insertedX = snapUpToGrid(requiredInsertedLeft);
    }
    const isBackEdge = void 0 !== sourceNode && sourceNode.parentId === insertedNode.parentId && sourceNode.position.x >= targetNode.position.x;
    const requiredTargetLeft = insertedX + insertedSize.width + TOP_LEVEL_INSERTION_GAP_PX;
    const downstreamShift = !isBackEdge && targetNode.position.x < requiredTargetLeft ? snapUpToGrid(requiredTargetLeft - targetNode.position.x) : 0;
    const chainIds = downstreamShift > 0 ? new Set(collectLinearDownstreamSiblings({
        startNodeId: targetNode.id,
        parentId: insertedNode.parentId,
        nodes,
        edges
    })) : new Set();
    chainIds.delete(originalEdge.source);
    const insertedXChanged = insertedX !== insertedNode.position.x;
    if (!insertedXChanged && 0 === chainIds.size) return null;
    const updatedInsertedNode = insertedXChanged ? {
        ...insertedNode,
        position: {
            x: insertedX,
            y: insertedNode.position.y
        }
    } : insertedNode;
    const updatedNodes = nodes.map((node)=>{
        if (node.id === insertedNode.id) return updatedInsertedNode;
        if (chainIds.has(node.id)) return {
            ...node,
            position: {
                x: node.position.x + downstreamShift,
                y: node.position.y
            }
        };
        return node;
    });
    return {
        nodes: updatedNodes,
        insertedNode: updatedInsertedNode
    };
}
function placeAddedNode({ nodes, edges, previewNode, insertedNode, layout, ignoredNodeTypes }) {
    const { getNodeDimensions, isContainerNode, getContainerFitGeometry } = layout;
    const placement = getContainerPlacement(previewNode);
    if (placement) {
        const containerNode = nodes.find((node)=>node.id === placement.containerId);
        if (!containerNode) return resolveScopedCollisions(nodes, insertedNode, {
            ignoredNodeTypes,
            getNodeDimensions
        });
        if (!isContainerNode(containerNode)) return resolveScopedCollisions(nodes, insertedNode, {
            ignoredNodeTypes,
            getNodeDimensions
        });
        const placedNodes = placeContainerNode({
            nodes,
            insertedNode,
            placement,
            edges,
            getNodeDimensions,
            safeArea: getContainerSafeArea(containerNode),
            getContainerFitGeometry,
            ignoredNodeTypes
        });
        const placedInsertedNode = placedNodes.find((node)=>node.id === insertedNode.id);
        const resolved = resolveScopedCollisions(placedNodes, placedInsertedNode, {
            ignoredNodeTypes,
            getNodeDimensions
        });
        if (resolved.insertedNode.parentId) {
            const refitted = fitContainersAndPushSiblings({
                nodes: resolved.nodes,
                containerIds: [
                    resolved.insertedNode.parentId
                ],
                getContainerFitGeometry,
                getNodeDimensions,
                ignoredNodeTypes,
                gap: CONTAINER_SEQUENCE_GAP_PX
            });
            return {
                nodes: refitted,
                insertedNode: refitted.find((node)=>node.id === resolved.insertedNode.id) ?? resolved.insertedNode
            };
        }
        return resolved;
    }
    const shifted = shiftForEdgeInsertion({
        nodes,
        edges,
        previewNode,
        insertedNode,
        getNodeDimensions
    });
    const placementResult = resolveScopedCollisions(shifted?.nodes ?? nodes, shifted?.insertedNode ?? insertedNode, {
        ignoredNodeTypes,
        getNodeDimensions
    });
    if (!placementResult.insertedNode.parentId) return placementResult;
    const fittedNodes = fitContainersAndPushSiblings({
        nodes: placementResult.nodes,
        containerIds: [
            placementResult.insertedNode.parentId
        ],
        getContainerFitGeometry,
        getNodeDimensions,
        ignoredNodeTypes,
        gap: CONTAINER_SEQUENCE_GAP_PX
    });
    return {
        nodes: fittedNodes,
        insertedNode: fittedNodes.find((node)=>node.id === placementResult.insertedNode.id) ?? placementResult.insertedNode
    };
}
export { alignNodeToPreview, getOriginalEdge, placeAddedNode };
