import React, {FunctionComponent, useContext, useEffect, useRef, useState} from "react";
import _ from "lodash";
import {
    getCardXYFromIndex,
    getIndexOfCellInGrid,
    getRelativeClientXY,
    getXYFromIndex
} from "/imports/ui/hooks/useDragAndDrop";
import {useGesture} from "@use-gesture/react";

export namespace DragAndDrop {

    type OnMovedHandler = (from: number, to: number) => any

    type Grid = {
        container: { ref: React.RefObject<HTMLElement | null> }
    }

    type GridContext =
        | { status: "no-context" }
        | {
            status: "ok"
            cellSize: number
            length: number
            gridWidth: number
            gridHeight: number
            margin: number
            onMoved?: OnMovedHandler
            containerEl: HTMLElement | null
        }

    export const GridContext = React.createContext<GridContext>({ status: "no-context" });

    type GridProps = {
        cellSize: number
        length: number
        columns: number
        margin: number
        children: (grid: Grid) => JSX.Element
        onMoved?: OnMovedHandler
        deps?: any[]
    }

    export const Grid: FunctionComponent<GridProps> = ({ cellSize, length, columns, margin, children, onMoved  }) => {
        if(!_.isFunction(children)) {
            throw new Error("child of Grid must be a function")
        }

        const containerRef = useRef<HTMLElement>(null);

        const [, lastY] = getXYFromIndex(length - 1, columns);

        const [context, setContext] = useState<GridContext>({
            status: "ok",
            cellSize,
            gridWidth: columns,
            gridHeight: lastY + 1,
            length,
            margin,
            onMoved,
            containerEl: containerRef.current
        });

        useEffect(() => {
            setContext(context => ({ ...context, cellSize, length, gridWidth: columns, gridHeight: lastY + 1, margin, containerEl: containerRef.current }))
        }, [ cellSize, length, columns, lastY, margin, containerRef.current ])

        if(context.status !== "ok") return null;

        return <GridContext.Provider value={context}>
            <div className="position-relative" style={{ height: context.gridHeight * cellSize + (context.gridHeight - 1) * margin }}>
                {children({
                    container: {
                        ref: containerRef
                    }
                })}
            </div>
        </GridContext.Provider>
    }

    type DraggableProps = {
        index: number
    }

    export const Draggable: FunctionComponent<DraggableProps> = ({ index, children }) => {
        const gridContext = useContext(GridContext);

        const [movement, setMovement] = useState([0, 0]);
        const hasMovement = movement[0] || movement[1];

        let x = 0, y = 0;
        if(gridContext.status === "ok") {
            [x, y] = getCardXYFromIndex(index, gridContext.gridWidth, gridContext.cellSize, gridContext.margin);
        }

        const bind = useGesture({
            onDrag: ({ movement }) => setMovement(movement),
            onDragEnd: ({ xy: [mx, my] }) => {
                setMovement([0, 0]);
                if(gridContext.status === "ok") {
                    let x = 0, y = 0;
                    if(gridContext.containerEl) {
                        [x, y] = getRelativeClientXY(mx, my, gridContext.containerEl);
                    }
                    gridContext.onMoved?.call(undefined, index, getIndexOfCellInGrid(x, y, gridContext.gridWidth, gridContext.cellSize))
                }
            }
        })

        return <div
            {...bind()}
            style={{
                position: "absolute",
                top: 0,
                left: 0,
                zIndex: hasMovement ? 9999:"initial",
                touchAction: "none",
                transform: `translate3d(${x + movement[0]}px, ${y + movement[1]}px, ${0}px)` }}>
            {children}
        </div>
    }

}