import { jsx } from 'react/jsx-runtime';
import React__default, { createContext, useState, useRef, useCallback, useMemo, useEffect, useContext } from 'react';
import { motion } from 'motion/react';
export { AnimatePresence } from 'motion/react';
import { cn } from '../../../utils/cn.js';
import { dragDropTheme } from './DragDrop.theme.js';

const DragDropContext = createContext(null);
const DragDropProvider = React__default.forwardRef(({ children }, ref) => {
    const [activeDrag, setActiveDrag] = useState(null);
    const pointerRef = useRef(null);
    const dropZones = useRef(new Map());
    const groupIdRef = useRef(`dd-${Math.random().toString(36).slice(2)}`);
    const registerDropZone = useCallback((id, element) => {
        dropZones.current.set(id, element);
    }, []);
    const unregisterDropZone = useCallback((id) => {
        dropZones.current.delete(id);
    }, []);
    const setPointer = useCallback((point) => {
        pointerRef.current = point;
    }, []);
    const contextValue = useMemo(() => ({
        activeDrag,
        setActiveDrag,
        pointerRef,
        setPointer,
        groupId: groupIdRef.current,
        registerDropZone,
        unregisterDropZone,
        dropZones: dropZones.current,
    }), [activeDrag, setActiveDrag, setPointer, registerDropZone, unregisterDropZone]);
    return (jsx(DragDropContext.Provider, { value: contextValue, children: children }));
});
DragDropProvider.displayName = 'DragDropProvider';
const useDragDropContext = () => {
    const context = useContext(DragDropContext);
    if (!context) {
        throw new Error('Draggable and Droppable must be used within DragDropProvider');
    }
    return context;
};
const Draggable = React__default.forwardRef(({ children, dragData, className, theme: customTheme = {}, onDragStart, onDrag, onDragEnd, disabled = false, }, ref) => {
    var _a;
    const context = useDragDropContext();
    const dragRef = useRef(null);
    const theme = {
        ...dragDropTheme,
        ...customTheme,
    };
    const handleDragStart = useCallback((_event, info) => {
        if (disabled)
            return;
        context.setActiveDrag(dragData);
        context.setPointer({ x: info.point.x, y: info.point.y });
        onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart(dragData, info);
    }, [dragData, onDragStart, context, disabled]);
    const handleDrag = useCallback((_event, info) => {
        if (disabled)
            return;
        context.setPointer({ x: info.point.x, y: info.point.y });
        onDrag === null || onDrag === void 0 ? void 0 : onDrag(dragData, info);
    }, [dragData, onDrag, disabled, context]);
    const handleDragEnd = useCallback((_event, info) => {
        var _a;
        // Use last known pointer position for precise hit-testing
        const point = (_a = context.pointerRef.current) !== null && _a !== void 0 ? _a : { x: info.point.x, y: info.point.y };
        context.dropZones.forEach((dropZone) => {
            const dropRect = dropZone.getBoundingClientRect();
            if (point.x >= dropRect.left &&
                point.x <= dropRect.right &&
                point.y >= dropRect.top &&
                point.y <= dropRect.bottom) {
                const dropEvent = new CustomEvent('motion-drop', {
                    detail: dragData,
                    bubbles: true
                });
                dropZone.dispatchEvent(dropEvent);
            }
        });
        context.setActiveDrag(null);
        context.setPointer(null);
        onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(dragData, info);
    }, [dragData, onDragEnd, context]);
    const isDragging = ((_a = context.activeDrag) === null || _a === void 0 ? void 0 : _a.id) === dragData.id;
    return (jsx(motion.div, { ref: (node) => {
            dragRef.current = node;
            if (typeof ref === 'function') {
                ref(node);
            }
            else if (ref) {
                ref.current = node;
            }
        }, layout: true, layoutId: dragData.id, initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.9, transition: { duration: 0.15 } }, drag: !disabled, dragMomentum: false, dragElastic: 0, dragSnapToOrigin: true, dragTransition: {
            power: 0,
            timeConstant: 0,
        }, whileDrag: {
            scale: 1.03,
            cursor: 'grabbing',
            boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
        }, transition: {
            layout: { type: 'spring', stiffness: 400, damping: 30 },
            scale: { duration: 0.1 },
            x: { type: 'spring', stiffness: 1000, damping: 50 },
            y: { type: 'spring', stiffness: 1000, damping: 50 },
        }, style: {
            position: isDragging ? 'relative' : 'relative',
            zIndex: isDragging ? 99999 : 'auto',
            isolation: isDragging ? 'isolate' : 'auto',
        }, className: cn(theme.draggableStyle, disabled && 'opacity-50 cursor-not-allowed', !disabled && 'cursor-grab active:cursor-grabbing', isDragging && '[&]:!z-[99999]', className), onDragStart: handleDragStart, onDrag: handleDrag, onDragEnd: handleDragEnd, children: children }));
});
Draggable.displayName = 'Draggable';
const Droppable = React__default.forwardRef(({ children, acceptedTypes, className, theme: customTheme = {}, onDrop, onDragEnter, onDragLeave, id, }, ref) => {
    const context = useDragDropContext();
    const [isDragOver, setIsDragOver] = useState(false);
    const dropRef = useRef(null);
    const rafIdRef = useRef(null);
    const stableId = useRef(id || `droppable-${Math.random().toString(36).substr(2, 9)}`).current;
    const theme = {
        ...dragDropTheme,
        ...customTheme,
    };
    // Register drop zone
    useEffect(() => {
        if (dropRef.current) {
            context.registerDropZone(stableId, dropRef.current);
        }
        return () => {
            context.unregisterDropZone(stableId);
        };
    }, [stableId, context]);
    // Compute drag-over state precisely based on pointer location using rAF
    useEffect(() => {
        if (!context.activeDrag) {
            if (isDragOver) {
                setIsDragOver(false);
                onDragLeave === null || onDragLeave === void 0 ? void 0 : onDragLeave();
            }
            return;
        }
        const loop = () => {
            const currentDrag = context.activeDrag;
            const point = context.pointerRef.current;
            const el = dropRef.current;
            if (!el || !currentDrag || !point) {
                if (isDragOver) {
                    setIsDragOver(false);
                    onDragLeave === null || onDragLeave === void 0 ? void 0 : onDragLeave();
                }
            }
            else {
                const dropRect = el.getBoundingClientRect();
                const matchesType = !acceptedTypes || acceptedTypes.length === 0 || acceptedTypes.includes(currentDrag.type || '');
                const over = matchesType &&
                    point.x >= dropRect.left &&
                    point.x <= dropRect.right &&
                    point.y >= dropRect.top &&
                    point.y <= dropRect.bottom;
                if (over && !isDragOver) {
                    setIsDragOver(true);
                    onDragEnter === null || onDragEnter === void 0 ? void 0 : onDragEnter(currentDrag);
                }
                else if (!over && isDragOver) {
                    setIsDragOver(false);
                    onDragLeave === null || onDragLeave === void 0 ? void 0 : onDragLeave();
                }
            }
            rafIdRef.current = requestAnimationFrame(loop);
        };
        rafIdRef.current = requestAnimationFrame(loop);
        return () => {
            if (rafIdRef.current)
                cancelAnimationFrame(rafIdRef.current);
        };
    }, [acceptedTypes, isDragOver, onDragEnter, onDragLeave, context.activeDrag]);
    // Handle drop
    useEffect(() => {
        const handleDrop = (event) => {
            const customEvent = event;
            const data = customEvent.detail;
            // Check if type is accepted
            if (acceptedTypes && acceptedTypes.length > 0) {
                if (!acceptedTypes.includes(data.type || '')) {
                    return;
                }
            }
            setIsDragOver(false);
            onDrop === null || onDrop === void 0 ? void 0 : onDrop(data);
        };
        const element = dropRef.current;
        if (element) {
            element.addEventListener('motion-drop', handleDrop);
        }
        return () => {
            if (element) {
                element.removeEventListener('motion-drop', handleDrop);
            }
        };
    }, [acceptedTypes, onDrop]);
    return (jsx(motion.div, { ref: (node) => {
            dropRef.current = node;
            if (typeof ref === 'function') {
                ref(node);
            }
            else if (ref) {
                ref.current = node;
            }
        }, style: {
            position: 'relative',
            zIndex: 0,
        }, className: cn(theme.droppableStyle, isDragOver && theme.dragOverStyle, className), animate: isDragOver ? 'dragOver' : 'idle', variants: {
            idle: { scale: 1 },
            dragOver: { scale: 1.01 }
        }, transition: { duration: 0.15 }, children: typeof children === 'function' ? children(isDragOver) : children }));
});
Droppable.displayName = 'Droppable';

export { DragDropProvider, Draggable, Droppable };
