import { Position } from "../xyflow/react.js";
import { DEFAULT_NODE_SIZE, GRID_SPACING, PREVIEW_EDGE_ID, PREVIEW_NODE_ID } from "../constants.js";
import { getNodeDimensions } from "./container.js";
import { getAbsolutePosition, getNonOverlappingPositionForDirection, resolveHandleContext } from "./NodeUtils.js";
const PREVIEW_EDGE_STYLE = {
    strokeDasharray: '5,5',
    opacity: 0.8,
    stroke: 'var(--canvas-selection-indicator)',
    strokeWidth: 2
};
function isPreviewEdge(edge) {
    return edge.id === PREVIEW_EDGE_ID || edge.source === PREVIEW_NODE_ID || edge.target === PREVIEW_NODE_ID;
}
function getOppositePosition(position) {
    switch(position){
        case Position.Left:
            return Position.Right;
        case Position.Right:
            return Position.Left;
        case Position.Top:
            return Position.Bottom;
        case Position.Bottom:
            return Position.Top;
        default:
            return Position.Right;
    }
}
function calculatePositionFromDrop(dropPosition, handlePosition, previewNodeSize) {
    switch(handlePosition){
        case Position.Left:
            return {
                x: dropPosition.x - previewNodeSize.width,
                y: dropPosition.y - previewNodeSize.height / 2
            };
        case Position.Right:
            return {
                x: dropPosition.x,
                y: dropPosition.y - previewNodeSize.height / 2
            };
        case Position.Top:
            return {
                x: dropPosition.x - previewNodeSize.width / 2,
                y: dropPosition.y - previewNodeSize.height
            };
        case Position.Bottom:
            return {
                x: dropPosition.x - previewNodeSize.width / 2,
                y: dropPosition.y
            };
        default:
            return {
                x: dropPosition.x,
                y: dropPosition.y - previewNodeSize.height / 2
            };
    }
}
function calculateCenteredPosition(centerPosition, previewNodeSize) {
    return {
        x: centerPosition.x - previewNodeSize.width / 2,
        y: centerPosition.y - previewNodeSize.height / 2
    };
}
function computeSpreadOffset(handle, size) {
    const index = handle?.index ?? null;
    const count = handle?.count ?? 1;
    if (null === index || count <= 1) return 0;
    if (index < Math.floor(count / 2)) return -size;
    if (index >= Math.ceil(count / 2)) return size;
    return 0;
}
function calculateAutoPosition(sourceNode, handlePosition, previewNodeSize, existingNodes, offset = 5 * GRID_SPACING, ignoredNodeTypes = [], handle) {
    const sourceAbsolutePosition = sourceNode.parentId ? getAbsolutePosition(sourceNode, existingNodes) : sourceNode.position;
    const sourceSize = getNodeDimensions(sourceNode);
    const anchorX = handle?.anchor.x ?? sourceAbsolutePosition.x + sourceSize.width / 2;
    const anchorY = handle?.anchor.y ?? sourceAbsolutePosition.y + sourceSize.height / 2;
    const ignoredNodeIds = new Set();
    let parentId = sourceNode.parentId;
    while(parentId){
        ignoredNodeIds.add(parentId);
        parentId = existingNodes.find((node)=>node.id === parentId)?.parentId;
    }
    const nodesWithAbsolutePositions = existingNodes.filter((node)=>node.id !== PREVIEW_NODE_ID && !ignoredNodeIds.has(node.id)).map((node)=>({
            ...node,
            position: node.parentId ? getAbsolutePosition(node, existingNodes) : node.position
        }));
    let initialPosition;
    let direction;
    switch(handlePosition){
        case Position.Left:
            initialPosition = {
                x: sourceAbsolutePosition.x - previewNodeSize.width - offset,
                y: anchorY - previewNodeSize.height / 2
            };
            direction = 'left';
            break;
        case Position.Right:
            initialPosition = {
                x: sourceAbsolutePosition.x + sourceSize.width + offset,
                y: anchorY - previewNodeSize.height / 2 + computeSpreadOffset(handle, previewNodeSize.height)
            };
            direction = 'right';
            break;
        case Position.Top:
            initialPosition = {
                x: anchorX - previewNodeSize.width / 2 + computeSpreadOffset(handle, previewNodeSize.width / 2),
                y: sourceAbsolutePosition.y - previewNodeSize.height - offset
            };
            direction = 'top';
            break;
        case Position.Bottom:
            initialPosition = {
                x: anchorX - previewNodeSize.width / 2 + computeSpreadOffset(handle, previewNodeSize.width / 2),
                y: sourceAbsolutePosition.y + sourceSize.height + offset
            };
            direction = 'bottom';
            break;
        default:
            initialPosition = {
                x: sourceAbsolutePosition.x + sourceSize.width + offset,
                y: anchorY - previewNodeSize.height / 2
            };
            direction = 'right';
    }
    const overflowDirection = {
        x: anchorX >= sourceAbsolutePosition.x + sourceSize.width / 2 ? 'right' : 'left',
        y: anchorY >= sourceAbsolutePosition.y + sourceSize.height / 2 ? 'down' : 'up'
    };
    return getNonOverlappingPositionForDirection(nodesWithAbsolutePositions, initialPosition, previewNodeSize, direction, offset, ignoredNodeTypes, overflowDirection);
}
function createPreviewNode(options) {
    const { sourceNodeId, sourceHandleId, reactFlowInstance, position, data, sourceHandleType = 'source', previewNodeSize = {
        width: DEFAULT_NODE_SIZE,
        height: DEFAULT_NODE_SIZE
    }, handlePosition = Position.Right, ignoredNodeTypes = [], positionMode = 'drop', sourceBoundaryOf } = options;
    const sourceNode = reactFlowInstance.getNode(sourceNodeId);
    if (!sourceNode) {
        console.warn(`Source node ${sourceNodeId} not found`);
        return null;
    }
    const treatPreviewAsSource = 'target' === sourceHandleType;
    const existingNodes = reactFlowInstance.getNodes().filter((n)=>n.id !== PREVIEW_NODE_ID);
    const internalNode = void 0 === position ? reactFlowInstance.getInternalNode(sourceNodeId) : void 0;
    const handle = internalNode ? resolveHandleContext(internalNode, sourceHandleId, handlePosition, {
        boundaryOf: sourceBoundaryOf
    }) : void 0;
    const nodePosition = position ? 'center' === positionMode ? calculateCenteredPosition(position, previewNodeSize) : calculatePositionFromDrop(position, handlePosition, previewNodeSize) : calculateAutoPosition(sourceNode, handlePosition, previewNodeSize, existingNodes, void 0, ignoredNodeTypes, handle);
    const handleFacingSource = getOppositePosition(handlePosition);
    const finalData = {
        ...data ?? {}
    };
    finalData.inputHandlePosition = treatPreviewAsSource ? handlePosition : handleFacingSource;
    finalData.outputHandlePosition = treatPreviewAsSource ? handleFacingSource : handlePosition;
    const previewNode = {
        id: PREVIEW_NODE_ID,
        type: 'preview',
        position: nodePosition,
        ...previewNodeSize,
        selected: true,
        data: finalData
    };
    const previewSourceAndTargetData = treatPreviewAsSource ? {
        source: PREVIEW_NODE_ID,
        sourceHandle: 'output',
        target: sourceNodeId,
        targetHandle: sourceHandleId
    } : {
        source: sourceNodeId,
        sourceHandle: sourceHandleId,
        target: PREVIEW_NODE_ID,
        targetHandle: 'input'
    };
    const previewEdge = {
        id: PREVIEW_EDGE_ID,
        ...previewSourceAndTargetData,
        type: 'default',
        style: PREVIEW_EDGE_STYLE
    };
    return {
        node: previewNode,
        edge: previewEdge
    };
}
function applyPreviewToReactFlow(preview, reactFlowInstance) {
    setTimeout(()=>{
        reactFlowInstance.setNodes((nodes)=>[
                ...nodes.filter((n)=>n.id !== PREVIEW_NODE_ID).map((n)=>({
                        ...n,
                        selected: false
                    })),
                preview.node
            ]);
        reactFlowInstance.setEdges((edges)=>[
                ...edges.filter((edge)=>!isPreviewEdge(edge)),
                preview.edge
            ]);
    }, 0);
}
function removePreviewFromReactFlow(reactFlowInstance) {
    reactFlowInstance.setNodes((nodes)=>nodes.filter((n)=>n.id !== PREVIEW_NODE_ID));
    reactFlowInstance.setEdges((edges)=>edges.filter((edge)=>!isPreviewEdge(edge)));
}
export { PREVIEW_EDGE_STYLE, applyPreviewToReactFlow, createPreviewNode, getOppositePosition, isPreviewEdge, removePreviewFromReactFlow };
