/**
 * Hook for snapping pixel coordinates to the nearest data point.
 *
 * This uses the Recharts scale hooks to convert between pixel and data coordinates,
 * enabling "magnetic" annotation placement that snaps to actual data points.
 *
 * NOTE: This imports from 'recharts' using hooks that are not yet published.
 * TypeScript/ESLint errors are expected until recharts 3.8 is released.
 *
 * The hook detects this by checking if the inverse scale returns a value that differs
 * significantly from the pixel position when converted back. If the roundtrip produces
 * a very different value, it indicates snapping occurred, and we use that. Otherwise,
 * we keep the original coordinate for smooth freeform positioning.
 */
import { useCallback } from 'react';
import { getRelativeCoordinate, useXAxisInverseDataSnapScale, useYAxisInverseDataSnapScale, useXAxisInverseTickSnapScale, useYAxisInverseTickSnapScale, useXAxisScale, useYAxisScale, } from 'recharts';
/**
 * Hook that provides a function to snap pixel coordinates to the nearest data point.
 *
 * When snap-to-data is enabled, this will:
 * 1. Convert pixel coordinates to data values using inverse scale
 * 2. Find the closest data point in the domain (categories)
 * 3. Convert back to pixel coordinates for display
 *
 * @example
 * ```tsx
 * const snapToData = useSnapToData({ enabled: true });
 *
 * const handleMouseMove = (x: number, y: number) => {
 *   const snapped = snapToData(x, y);
 *   setPosition({ x: snapped.x, y: snapped.y });
 * };
 * ```
 */
export function useSnap(snapMode) {
    // Get inverse scale functions for converting pixels -> data
    const xInverseDataSnapScale = useXAxisInverseDataSnapScale();
    const yInverseDataSnapScale = useYAxisInverseDataSnapScale();
    const xInverseTickSnapScale = useXAxisInverseTickSnapScale();
    const yInverseTickSnapScale = useYAxisInverseTickSnapScale();
    // And forward scales for data -> pixels
    const scaleX = useXAxisScale();
    const scaleY = useYAxisScale();
    return useCallback((e) => {
        if (xInverseDataSnapScale == null ||
            yInverseDataSnapScale == null ||
            xInverseTickSnapScale == null ||
            yInverseTickSnapScale == null ||
            scaleX == null ||
            scaleY == null) {
            /*
             * Scales not available yet (e.g., outside of chart context, or chart not fully initialized)
             * Just return zeroes to avoid errors
             */
            return {
                interactionCoordinate: { x: 0, y: 0 },
                snappedCoordinate: undefined,
                dataPoint: undefined,
            };
        }
        /*
         * Here we get pixel coordinates relative to the chart area (accounting for transforms)
         * These are exact mouse interaction coordinates.
         * Next, we need to "snap" these to the nearest data point if snapMode is enabled.
         */
        const interactionCoordinate = getRelativeCoordinate(e);
        let snappedDataPoint = undefined;
        if (snapMode === 'data') {
            snappedDataPoint = {
                x: xInverseDataSnapScale(interactionCoordinate.relativeX),
                y: yInverseDataSnapScale(interactionCoordinate.relativeY),
            };
        }
        else if (snapMode === 'tick') {
            snappedDataPoint = {
                x: xInverseTickSnapScale(interactionCoordinate.relativeX),
                y: yInverseTickSnapScale(interactionCoordinate.relativeY),
            };
        }
        if (snappedDataPoint == null) {
            // If snapping failed for some reason, just return the original interaction coordinates
            return {
                interactionCoordinate: {
                    x: interactionCoordinate.relativeX,
                    y: interactionCoordinate.relativeY,
                },
                snappedCoordinate: undefined,
                dataPoint: undefined,
            };
        }
        /*
         * Converts snapped data point back to pixel coordinates.
         * Some components will read the snapped data point to display values, while others will use the snapped pixel coordinates for positioning.
         * We return here both the snapped data point (for display) and the snapped pixel coordinates (for positioning) for convenience.
         * If you are using ReferenceLine/ReferenceDot, you can skip this step and use the snappedDataPoint directly.
         */
        const snappedX = scaleX(snappedDataPoint.x);
        const snappedY = scaleY(snappedDataPoint.y);
        if (snappedX == null || snappedY == null) {
            // If the snapped data point is outside the chart area, just return the original interaction coordinates
            return {
                interactionCoordinate: {
                    x: interactionCoordinate.relativeX,
                    y: interactionCoordinate.relativeY,
                },
                snappedCoordinate: undefined,
                dataPoint: undefined,
            };
        }
        return {
            interactionCoordinate: {
                x: interactionCoordinate.relativeX,
                y: interactionCoordinate.relativeY,
            },
            snappedCoordinate: { x: snappedX, y: snappedY },
            dataPoint: snappedDataPoint,
        };
    }, [
        snapMode,
        xInverseDataSnapScale,
        yInverseDataSnapScale,
        xInverseTickSnapScale,
        yInverseTickSnapScale,
        scaleX,
        scaleY,
    ]);
}
