import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { equals } from 'ramda';
import { AddNodePosition } from './types';
import { useScrollOnDrag } from './useScrollOnDrag';
import { useDelayedDragEventCall } from './useDelayedDragEventCall';
import { getDropNodePosition, getExpandedKeys, getNodeAtPosition, isDropIndicatorNode, moveNode, slideNodePos, getDropNodeParent } from './helper';
export var useDnd = function (_a) {
    var treeData = _a.treeData, containerRef = _a.containerRef, expandNode = _a.expandNode, onLoadChildren = _a.onLoadChildren, onChange = _a.onChange, onDropProp = _a.onDrop, canDrop = _a.canDrop, canDrag = _a.canDrag;
    var _b = useState(null), dragNode = _b[0], setDragNode = _b[1];
    var _c = useState(null), dropNode = _c[0], setDropNode = _c[1];
    var _d = useState(null), dropPositionInfo = _d[0], setDropPositionInfo = _d[1];
    var _e = useScrollOnDrag(containerRef), onStartDragging = _e.onStartDragging, onEndDragging = _e.onEndDragging, getIsScrolling = _e.getIsScrolling;
    var dropOutsideHandlerRef = useRef(null);
    var removeDropOutsideHandler = useCallback(function () {
        if (dropOutsideHandlerRef.current) {
            window.removeEventListener('mousemove', dropOutsideHandlerRef.current);
            dropOutsideHandlerRef.current = null;
        }
    }, []);
    var onDragStart = function (_a) {
        var node = _a.node;
        setDragNode(node);
        onStartDragging();
        var handler = function () {
            setDragNode(null);
            setDropNode(null);
            onEndDragging();
            removeDropOutsideHandler();
        };
        dropOutsideHandlerRef.current = handler;
        window.addEventListener('mousemove', handler);
    };
    useEffect(function () {
        return removeDropOutsideHandler;
    }, [removeDropOutsideHandler]);
    var expandedKeys = useMemo(function () {
        return getExpandedKeys(treeData);
    }, [treeData]);
    var expandDropNodeAtPosition = useCallback(function (pos, expanded) {
        var node = getNodeAtPosition(treeData, pos);
        if (node && !expandedKeys.includes(node.nodeId)) {
            expandNode(pos, expanded);
            if (node.pending && onLoadChildren) {
                onLoadChildren(node);
            }
        }
    }, [expandNode, expandedKeys, onLoadChildren, treeData]);
    var expandDropNode = useCallback(function (newDropPositionInfo) {
        if (onChange && newDropPositionInfo.dropPosition === AddNodePosition.AsChild && dragNode) {
            var currentParentNodePos = slideNodePos(newDropPositionInfo.pos, dragNode.pos);
            if (currentParentNodePos.split('-').length > 1) {
                expandDropNodeAtPosition(currentParentNodePos, true);
            }
        }
    }, [dragNode, expandDropNodeAtPosition, onChange]);
    var handleSetDropNode = useCallback(function (_a) {
        var event = _a.event, node = _a.node;
        var newDropPositionInfo = getDropNodePosition(event, node, dropPositionInfo) || dropPositionInfo;
        var isNewDropPositionValid = newDropPositionInfo &&
            dragNode &&
            canDrop({
                // Either drop node itself or its parent in the tree
                nextParent: getDropNodeParent(treeData, newDropPositionInfo, dragNode.pos),
                node: dragNode.value
            });
        if (!equals(newDropPositionInfo, dropPositionInfo) && (!isDropIndicatorNode(node) || !dropNode)) {
            if (isNewDropPositionValid) {
                setDropPositionInfo(newDropPositionInfo);
            }
            expandDropNode(newDropPositionInfo);
        }
        if (node.key !== (dropNode === null || dropNode === void 0 ? void 0 : dropNode.key) && (!isDropIndicatorNode(node) || isNewDropPositionValid)) {
            setDropNode(node);
        }
    }, [dragNode, dropNode, canDrop, dropPositionInfo, expandDropNode, treeData]);
    var delayedHandleDragOver = useDelayedDragEventCall(handleSetDropNode);
    var onDragOver = function (_a) {
        var event = _a.event, node = _a.node;
        var isScrolling = getIsScrolling();
        if (!isScrolling)
            delayedHandleDragOver({ event: event, node: node });
    };
    var dropIndicatorRender = function () {
        return null;
    };
    var onDragLeave = function (_a) {
        var _b, _c;
        var event = _a.event, node = _a.node;
        var draggedChildrenNodes = ((_b = dragNode === null || dragNode === void 0 ? void 0 : dragNode.value) === null || _b === void 0 ? void 0 : _b.expanded) ? getExpandedKeys((_c = dragNode.value) === null || _c === void 0 ? void 0 : _c.children) : [];
        if (draggedChildrenNodes.includes(node.key)) {
            handleSetDropNode({ event: event, node: node });
        }
    };
    var onDrop = function () {
        if (dragNode && dropPositionInfo && isDropIndicatorNode(dropNode)) {
            var parentNode = getDropNodeParent(treeData, dropPositionInfo, dragNode.pos);
            if (canDrop({ nextParent: parentNode, node: dragNode.value })) {
                var newTreeData = moveNode(treeData, dragNode.value, dragNode.pos, dropPositionInfo);
                onDropProp(newTreeData, dragNode.value, parentNode);
            }
        }
        setDragNode(null);
        setDropNode(null);
        onEndDragging();
        removeDropOutsideHandler();
    };
    var nodeDraggable = useCallback(function (node) {
        return canDrag({ node: node.value });
    }, [canDrag]);
    return {
        treeProps: {
            onDragStart: onDragStart,
            onDragOver: onDragOver,
            onDragLeave: onDragLeave,
            onDrop: onDrop,
            dropIndicatorRender: dropIndicatorRender
        },
        nodeDraggable: nodeDraggable,
        dropNode: dropNode,
        dragNode: dragNode,
        dropPositionInfo: dropPositionInfo
    };
};
