import { useCallback, useEffect, useRef, useState } from 'react';

const REORDER_ATTR = 'data-reorder-key';

type UseReorderableListOptions = {
    onReorder: (fromIndex: number, toIndex: number) => void;
    getItemKey: (index: number) => string;
    itemCount: number;
};

export function useReorderableList({ onReorder, getItemKey, itemCount }: UseReorderableListOptions) {
    const [dragIndex, setDragIndex] = useState<number | null>(null);
    const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
    const touchDragIndexRef = useRef<number | null>(null);
    const dragOverIndexRef = useRef<number | null>(null);
    const listRef = useRef<HTMLDivElement>(null);
    const onReorderRef = useRef(onReorder);

    useEffect(() => {
        onReorderRef.current = onReorder;
    }, [onReorder]);

    useEffect(() => {
        dragOverIndexRef.current = dragOverIndex;
    }, [dragOverIndex]);

    const clearDragState = useCallback(() => {
        setDragIndex(null);
        setDragOverIndex(null);
        touchDragIndexRef.current = null;
        dragOverIndexRef.current = null;
    }, []);

    const resolveIndexFromKey = useCallback(
        (key: string | null) => {
            if (!key) return null;
            for (let i = 0; i < itemCount; i++) {
                if (getItemKey(i) === key) return i;
            }
            return null;
        },
        [getItemKey, itemCount]
    );

    const commitReorder = useCallback(
        (fromIndex: number | null, toIndex: number | null) => {
            if (fromIndex === null || toIndex === null || fromIndex === toIndex) {
                clearDragState();
                return;
            }
            onReorderRef.current(fromIndex, toIndex);
            clearDragState();
        },
        [clearDragState]
    );

    const handleDrop = useCallback(
        (targetIndex: number) => {
            const fromIndex = touchDragIndexRef.current ?? dragIndex;
            commitReorder(fromIndex, targetIndex);
        },
        [dragIndex, commitReorder]
    );

    useEffect(() => {
        const container = listRef.current;
        if (!container) return;

        const onTouchMove = (e: TouchEvent) => {
            if (touchDragIndexRef.current === null) return;
            e.preventDefault();

            const touch = e.touches[0];
            const element = document.elementFromPoint(touch.clientX, touch.clientY);
            const itemEl = element?.closest(`[${REORDER_ATTR}]`);
            const key = itemEl?.getAttribute(REORDER_ATTR) ?? null;
            const targetIndex = resolveIndexFromKey(key);

            if (targetIndex !== null) {
                dragOverIndexRef.current = targetIndex;
                setDragOverIndex(targetIndex);
            }
        };

        const onTouchEnd = () => {
            commitReorder(touchDragIndexRef.current, dragOverIndexRef.current);
        };

        container.addEventListener('touchmove', onTouchMove, { passive: false });
        container.addEventListener('touchend', onTouchEnd);
        container.addEventListener('touchcancel', onTouchEnd);

        return () => {
            container.removeEventListener('touchmove', onTouchMove);
            container.removeEventListener('touchend', onTouchEnd);
            container.removeEventListener('touchcancel', onTouchEnd);
        };
    }, [commitReorder, resolveIndexFromKey, itemCount]);

    const getDragHandleProps = (index: number) => ({
        onTouchStart: (e: React.TouchEvent) => {
            e.stopPropagation();
            touchDragIndexRef.current = index;
            setDragIndex(index);
        },
        style: { touchAction: 'none' } as const,
    });

    const getItemDropProps = (index: number) => ({
        [REORDER_ATTR]: getItemKey(index),
        onDragOver: (e: React.DragEvent) => {
            e.preventDefault();
            e.dataTransfer.dropEffect = 'move';
            setDragOverIndex(index);
        },
        onDragLeave: (e: React.DragEvent) => {
            if (!e.currentTarget.contains(e.relatedTarget as Node)) {
                setDragOverIndex((prev) => (prev === index ? null : prev));
            }
        },
        onDrop: (e: React.DragEvent) => {
            e.preventDefault();
            handleDrop(index);
        },
    });

    const getItemDragProps = (index: number) => ({
        ...getItemDropProps(index),
        draggable: true as const,
        onDragStart: (e: React.DragEvent) => {
            setDragIndex(index);
            e.dataTransfer.effectAllowed = 'move';
            e.dataTransfer.setData('text/plain', getItemKey(index));
        },
        onDragEnd: clearDragState,
    });

    return {
        listRef,
        dragIndex,
        dragOverIndex,
        getDragHandleProps,
        getItemDropProps,
        getItemDragProps,
        clearDragState,
    };
};
