import { useCallback } from 'react';
import { Node, useReactFlow } from '@xyflow/react';
import { AppNodeType } from '../types/node';
import { getNodeBounds, SNAP_THRESHOLD } from '../utils';

export const useNodeSnapping = (setNodes: React.Dispatch<React.SetStateAction<AppNodeType[]>>) => {
    const { getZoom } = useReactFlow();

    const onNodeDrag = useCallback((event: React.MouseEvent, node: Node, nodes: Node[]) => {
        // Skip snapping if Shift key is pressed (common convention to disable snap)
        if (event.shiftKey) return;

        const zoom = getZoom();
        const SNAP = SNAP_THRESHOLD / zoom;

        // Cast to AppNodeType to access dimensions and custom properties
        const appNode = node as AppNodeType;
        // Use the dimensions from the node (React Flow updates these during drag usually, or we use our helper)
        const myBounds = getNodeBounds(appNode);
        
        // Filter candidates: exclude self and currently selected nodes (moving together)
        const otherNodes = nodes.filter(n => n.id !== node.id && !n.selected) as AppNodeType[];

        let proposedX = appNode.position.x;
        let proposedY = appNode.position.y;
        
        const w = myBounds.width;
        const h = myBounds.height;
        
        const myL = proposedX; 
        const myR = proposedX + w;
        const myC = proposedX + w / 2;
        
        const myT = proposedY; 
        const myB = proposedY + h;
        const myM = proposedY + h / 2;

        let snappedX = false;
        let snappedY = false;

        for (const other of otherNodes) {
             const otherBounds = getNodeBounds(other);
             
             // X Axis Snapping
             if (!snappedX) {
                 if (Math.abs(myL - otherBounds.x) < SNAP) { proposedX = otherBounds.x; snappedX = true; }
                 else if (Math.abs(myL - otherBounds.r) < SNAP) { proposedX = otherBounds.r; snappedX = true; }
                 else if (Math.abs(myR - otherBounds.x) < SNAP) { proposedX = otherBounds.x - w; snappedX = true; }
                 else if (Math.abs(myR - otherBounds.r) < SNAP) { proposedX = otherBounds.r - w; snappedX = true; }
                 else if (Math.abs(myC - (otherBounds.x + otherBounds.width/2)) < SNAP) { proposedX = (otherBounds.x + otherBounds.width/2) - w/2; snappedX = true; }
             }

             // Y Axis Snapping
             if (!snappedY) {
                 if (Math.abs(myT - otherBounds.y) < SNAP) { proposedY = otherBounds.y; snappedY = true; }
                 else if (Math.abs(myT - otherBounds.b) < SNAP) { proposedY = otherBounds.b; snappedY = true; }
                 else if (Math.abs(myB - otherBounds.y) < SNAP) { proposedY = otherBounds.y - h; snappedY = true; }
                 else if (Math.abs(myB - otherBounds.b) < SNAP) { proposedY = otherBounds.b - h; snappedY = true; }
                 else if (Math.abs(myM - (otherBounds.y + otherBounds.height/2)) < SNAP) { proposedY = (otherBounds.y + otherBounds.height/2) - h/2; snappedY = true; }
             }

             if (snappedX && snappedY) break;
        }

        if (snappedX || snappedY) {
            setNodes(nds => {
                // Optimization: Check if position actually changed to prevent unnecessary re-renders
                // Optimization: 检查位置是否实际发生变化，以防止不必要的重新渲染
                const currentNode = nds.find(n => n.id === node.id);
                if (!currentNode) return nds;

                // Use a small epsilon for float comparison if needed, but strict equality is usually fine for avoiding exact duplicate updates
                if (currentNode.position.x === proposedX && currentNode.position.y === proposedY) {
                    return nds;
                }

                return nds.map(n => {
                    if (n.id === node.id) {
                        return {
                            ...n,
                            position: {
                                x: proposedX,
                                y: proposedY
                            }
                        };
                    }
                    return n;
                });
            });
        }
    }, [setNodes, getZoom]);

    return { onNodeDrag };
};
