import React, {createContext, ReactNode, useContext, useId, useMemo, useRef} from "react";
import { useDrop, useDrag } from "react-dnd";

const DragAndDropContext = createContext([] as any);
const DropContext = createContext([] as any);

export interface DragAndDropProviderProps<T> {
    id?: string;
    onMove?: false|((source: T, dest: T) => void);
    children?: ReactNode;
}

export default function DragAndDropProvider<T>({ id, onMove, children }: DragAndDropProviderProps<T>) {
    const _id = useId();
    const value = useMemo(() => ([id || _id, onMove]), [_id, id, onMove]);

    return <DragAndDropContext.Provider value={value}>
        {children}
    </DragAndDropContext.Provider>;
}

export interface DragAndDropItemProps<T> {
    item?: T;
    children: ReactNode;
}

export function DragAndDropItem<T>({ item, children }: DragAndDropItemProps<T>) {
    const [ id, onMove ] = useContext(DragAndDropContext);

    //if (!id || !onMove || (item !== 0 && !item))
    //    return <>{children}</>;

    return <DragAndDropItemInner item={item}>
        {children}
    </DragAndDropItemInner>;
}

export function DragAndDropItemInner<T>({ item, children }: DragAndDropItemProps<T>) {
    const [ id, onMove ] = useContext(DragAndDropContext);
    const ref = useRef<any>(null);
    const previewRef = useRef<any>(null);

    const sym = useMemo(() => Symbol(), []);

    const [, drop] = useDrop(() => ({
        accept: id || sym,
        hover(item2: any, monitor) {
            if (!ref.current) {
                return;
            }

            const dragIndex = item2.item;
            const hoverIndex = item;

            if (dragIndex === hoverIndex) {
                return
            }

            const hoverBoundingRect = ref.current?.getBoundingClientRect();
            const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
            const clientOffset = monitor.getClientOffset()!;
            const hoverClientY = clientOffset.y - hoverBoundingRect.top;

            if (dragIndex < hoverIndex! && hoverClientY < hoverMiddleY) {
                return
            }

            if (dragIndex > hoverIndex! && hoverClientY > hoverMiddleY) {
                return
            }

            onMove(dragIndex, hoverIndex);

            item2.item = hoverIndex;
            },
    }), [item, onMove]);

    const [{ isDragging }, drag, preview] = useDrag(() => ({
        type: id || sym,
        item: () => {
            return { item }
        },
        collect: (monitor) => ({
            isDragging: monitor.isDragging(),
        }),
    }), [id, item]);

    const enabled = (item === 0 || item || null);

    if (enabled) {
        drag(ref);
        drop(preview(previewRef));
    }

    const child = React.Children.only(children) as any;
    return <DropContext.Provider value={enabled && [ref, isDragging]}>
        {React.cloneElement(child, { ref: previewRef, style: { ...(child.props.style || {}), opacity: !isDragging ? 1 : .5 } })}
    </DropContext.Provider>;
}

export function useDragContext() {
    return useContext(DropContext) || [];
}
