/**
 * Snap-to-Grid Store
 *
 * Optional grid snapping for node positioning.
 * When enabled, nodes snap to the nearest grid point during drag.
 */

import { atom } from 'jotai';
import type { NodePosition } from './types';

// =============================================================================
// Configuration
// =============================================================================

/** Whether grid snapping is enabled */
export const snapEnabledAtom = atom<boolean>(false);

/** Grid size in pixels (default: 20) */
export const snapGridSizeAtom = atom<number>(20);

/** Whether snap is temporarily disabled (e.g. holding modifier key) */
export const snapTemporaryDisableAtom = atom<boolean>(false);

// =============================================================================
// Derived
// =============================================================================

/** Whether snapping is currently active (enabled AND not temporarily disabled) */
export const isSnappingActiveAtom = atom((get) => {
    return get(snapEnabledAtom) && !get(snapTemporaryDisableAtom);
});

// =============================================================================
// Snap Functions (pure — no atoms, usable anywhere)
// =============================================================================

/**
 * Snap a position to the nearest grid point.
 */
export function snapToGrid(pos: NodePosition, gridSize: number): NodePosition {
    return {
        x: Math.round(pos.x / gridSize) * gridSize,
        y: Math.round(pos.y / gridSize) * gridSize,
    };
}

/**
 * Snap a position if snapping is active, otherwise return as-is.
 */
export function conditionalSnap(
    pos: NodePosition,
    gridSize: number,
    isActive: boolean,
): NodePosition {
    return isActive ? snapToGrid(pos, gridSize) : pos;
}

/**
 * Get snap guide lines for the current position.
 * Returns the X and Y grid lines closest to the position.
 */
export function getSnapGuides(
    pos: NodePosition,
    gridSize: number,
    tolerance: number = 5,
): { x: number | null; y: number | null } {
    const snappedX = Math.round(pos.x / gridSize) * gridSize;
    const snappedY = Math.round(pos.y / gridSize) * gridSize;

    return {
        x: Math.abs(pos.x - snappedX) < tolerance ? snappedX : null,
        y: Math.abs(pos.y - snappedY) < tolerance ? snappedY : null,
    };
}

// =============================================================================
// Action Atoms
// =============================================================================

/** Toggle snap on/off */
export const toggleSnapAtom = atom(null, (get, set) => {
    set(snapEnabledAtom, !get(snapEnabledAtom));
});

/** Set grid size */
export const setGridSizeAtom = atom(null, (_get, set, size: number) => {
    set(snapGridSizeAtom, Math.max(5, Math.min(200, size)));
});

// =============================================================================
// Alignment Guides (node-to-node snapping)
// =============================================================================

/** Whether node alignment guides are enabled */
export const snapAlignmentEnabledAtom = atom<boolean>(true);

/** Toggle alignment guides on/off */
export const toggleAlignmentGuidesAtom = atom(null, (get, set) => {
    set(snapAlignmentEnabledAtom, !get(snapAlignmentEnabledAtom));
});

/** Active alignment guides during drag — written by useNodeDrag, read by AlignmentGuides */
export interface AlignmentGuideData {
    verticalGuides: number[];
    horizontalGuides: number[];
}

export const alignmentGuidesAtom = atom<AlignmentGuideData>({
    verticalGuides: [],
    horizontalGuides: [],
});

/** Clear alignment guides (call on drag end) */
export const clearAlignmentGuidesAtom = atom(null, (_get, set) => {
    set(alignmentGuidesAtom, { verticalGuides: [], horizontalGuides: [] });
});

// =============================================================================
// Alignment Functions (pure — no atoms, usable anywhere)
// =============================================================================

export interface NodeRect {
    x: number;
    y: number;
    width: number;
    height: number;
}

/**
 * Find alignment guides for a dragged node against a set of other nodes.
 * Checks center-X, left-edge, right-edge, center-Y, top-edge, bottom-edge.
 *
 * Returns arrays of X and Y world coordinates where guides should be drawn.
 *
 * @param dragged - The node being dragged (current position)
 * @param others  - Other nodes to check alignment against
 * @param tolerance - Max distance in px to trigger a guide (default: 5)
 */
export function findAlignmentGuides(
    dragged: NodeRect,
    others: NodeRect[],
    tolerance: number = 5,
): AlignmentGuideData {
    const verticals = new Set<number>();
    const horizontals = new Set<number>();

    const dragCX = dragged.x + dragged.width / 2;
    const dragCY = dragged.y + dragged.height / 2;
    const dragRight = dragged.x + dragged.width;
    const dragBottom = dragged.y + dragged.height;

    for (const other of others) {
        const otherCX = other.x + other.width / 2;
        const otherCY = other.y + other.height / 2;
        const otherRight = other.x + other.width;
        const otherBottom = other.y + other.height;

        // Vertical guides (X alignment)
        if (Math.abs(dragCX - otherCX) < tolerance) verticals.add(otherCX);        // center-center
        if (Math.abs(dragged.x - other.x) < tolerance) verticals.add(other.x);     // left-left
        if (Math.abs(dragRight - otherRight) < tolerance) verticals.add(otherRight);// right-right
        if (Math.abs(dragged.x - otherRight) < tolerance) verticals.add(otherRight);// left-right
        if (Math.abs(dragRight - other.x) < tolerance) verticals.add(other.x);     // right-left
        if (Math.abs(dragCX - other.x) < tolerance) verticals.add(other.x);        // center-left
        if (Math.abs(dragCX - otherRight) < tolerance) verticals.add(otherRight);   // center-right

        // Horizontal guides (Y alignment)
        if (Math.abs(dragCY - otherCY) < tolerance) horizontals.add(otherCY);       // center-center
        if (Math.abs(dragged.y - other.y) < tolerance) horizontals.add(other.y);    // top-top
        if (Math.abs(dragBottom - otherBottom) < tolerance) horizontals.add(otherBottom);// bottom-bottom
        if (Math.abs(dragged.y - otherBottom) < tolerance) horizontals.add(otherBottom);// top-bottom
        if (Math.abs(dragBottom - other.y) < tolerance) horizontals.add(other.y);   // bottom-top
        if (Math.abs(dragCY - other.y) < tolerance) horizontals.add(other.y);       // center-top
        if (Math.abs(dragCY - otherBottom) < tolerance) horizontals.add(otherBottom);// center-bottom
    }

    return {
        verticalGuides: Array.from(verticals),
        horizontalGuides: Array.from(horizontals),
    };
}
