"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useTargeted = exports.DraggedResizeHandleContext = exports.DraggedPanelSideContext = exports.DraggedWidgetIdContext = exports.DragManagerContext = exports.DragManager = exports.DragProvider = exports.useDraggedItemId = exports.useIsDraggedType = exports.useIsDraggedItem = exports.useDraggedItem = exports.useDragItem = exports.usePanelTarget = exports.useTabTarget = exports.useTarget = exports.useDragToolSettings = exports.useDragResizeHandle = exports.useDragPanelGrip = exports.useDragWidget = exports.useDragTab = void 0;
/*---------------------------------------------------------------------------------------------
 * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
 * See LICENSE.md in the project root for license terms and full copyright notice.
 *--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Base
 */
const React = require("react");
const lodash_1 = require("lodash");
const core_bentley_1 = require("@itwin/core-bentley");
const core_react_1 = require("@itwin/core-react");
const NineZone_1 = require("./NineZone");
const DropTargetState_1 = require("../state/DropTargetState");
/** @internal */
function useDragTab(args) {
    const { tabId, onDrag, onDragEnd } = args;
    const item = React.useMemo(() => {
        return {
            type: "tab",
            id: tabId,
        };
    }, [tabId]);
    const handleDrag = React.useCallback((_item, info) => {
        const dragBy = info.lastPointerPosition.getOffsetTo(info.pointerPosition);
        onDrag && onDrag(dragBy);
    }, [onDrag]);
    const handleDragEnd = React.useCallback((_item, info, target) => {
        if (!onDragEnd)
            return;
        let tabTarget;
        if (target && (0, DropTargetState_1.isTabDragDropTargetState)(target)) {
            tabTarget = target;
        }
        else {
            const tabInfo = info;
            const newFloatingWidgetId = (0, NineZone_1.getUniqueId)();
            const size = tabInfo.widgetSize;
            tabTarget = {
                type: "floatingWidget",
                newFloatingWidgetId,
                size,
            };
        }
        onDragEnd(tabTarget);
    }, [onDragEnd]);
    const onDragStart = useDragItem({
        item,
        onDrag: handleDrag,
        onDragEnd: handleDragEnd,
    });
    const handleDragStart = React.useCallback(({ initialPointerPosition, pointerPosition, widgetSize, }) => {
        onDragStart({
            initialPointerPosition,
            pointerPosition,
            lastPointerPosition: initialPointerPosition,
            widgetSize,
        });
    }, [onDragStart]);
    return handleDragStart;
}
exports.useDragTab = useDragTab;
/** @internal */
function useDragWidget(args) {
    const { widgetId, onDragStart, onDrag, onDragEnd } = args;
    const dragManager = React.useContext(exports.DragManagerContext);
    const widgetItem = React.useMemo(() => {
        return {
            type: "widget",
            id: widgetId,
        };
    }, [widgetId]);
    const handleDragStart = React.useCallback((_item, info) => {
        onDragStart &&
            onDragStart((id) => {
                dragManager.handleDragUpdate({
                    type: "widget",
                    id,
                });
            }, info.initialPointerPosition, info.pointerPosition);
    }, [dragManager, onDragStart]);
    const handleDrag = React.useCallback((_item, info) => {
        const dragBy = info.lastPointerPosition.getOffsetTo(info.pointerPosition);
        onDrag && onDrag(dragBy);
    }, [onDrag]);
    const handleDragEnd = React.useCallback((_item, _info, target) => {
        if (!onDragEnd)
            return;
        let widgetTarget;
        if (target && (0, DropTargetState_1.isWidgetDragDropTargetState)(target)) {
            widgetTarget = target;
        }
        else {
            widgetTarget = {
                type: "window",
            };
        }
        onDragEnd(widgetTarget);
    }, [onDragEnd]);
    const isDragItem = React.useCallback((item, dragged) => {
        return !!item && defaultIsDragItem(item, dragged);
    }, []);
    const onItemDragStart = useDragItem({
        item: widgetItem,
        isDragItem,
        onDragStart: handleDragStart,
        onDrag: handleDrag,
        onDragEnd: handleDragEnd,
    });
    const handleWidgetDragStart = React.useCallback(({ initialPointerPosition, pointerPosition }) => {
        onItemDragStart({
            initialPointerPosition,
            pointerPosition,
            lastPointerPosition: initialPointerPosition,
        });
    }, [onItemDragStart]);
    return handleWidgetDragStart;
}
exports.useDragWidget = useDragWidget;
/** @internal */
function useDragPanelGrip(args) {
    const { side, onDrag, onDragEnd } = args;
    const gripItem = React.useMemo(() => {
        return {
            type: "panelGrip",
            id: side,
        };
    }, [side]);
    const handleDrag = React.useCallback((_item, info) => {
        onDrag && onDrag(info.pointerPosition, info.lastPointerPosition);
    }, [onDrag]);
    const handleDragEnd = React.useCallback(() => {
        onDragEnd && onDragEnd();
    }, [onDragEnd]);
    const onItemDragStart = useDragItem({
        item: gripItem,
        onDrag: handleDrag,
        onDragEnd: handleDragEnd,
    });
    const handleDragStart = React.useCallback(({ initialPointerPosition }) => {
        onItemDragStart({
            initialPointerPosition,
            pointerPosition: initialPointerPosition,
            lastPointerPosition: initialPointerPosition,
        });
    }, [onItemDragStart]);
    return handleDragStart;
}
exports.useDragPanelGrip = useDragPanelGrip;
/** @internal */
function useDragResizeHandle(args) {
    const { handle, onDrag, widgetId } = args;
    const resizeHandleItem = React.useMemo(() => {
        return {
            type: "resizeHandle",
            id: handle,
            widgetId,
        };
    }, [handle, widgetId]);
    const isDragItem = React.useCallback((item, draggedItem) => {
        return (!!item &&
            isResizeHandleDragItem(draggedItem) &&
            defaultIsDragItem(item, draggedItem) &&
            item.widgetId === draggedItem.widgetId);
    }, []);
    const handleDrag = React.useCallback((_item, info) => {
        onDrag && onDrag(info.pointerPosition);
    }, [onDrag]);
    const onItemDragStart = useDragItem({
        item: resizeHandleItem,
        isDragItem,
        onDrag: handleDrag,
    });
    const handleDragStart = React.useCallback(({ initialPointerPosition }) => {
        onItemDragStart({
            initialPointerPosition,
            pointerPosition: initialPointerPosition,
            lastPointerPosition: initialPointerPosition,
        });
    }, [onItemDragStart]);
    return handleDragStart;
}
exports.useDragResizeHandle = useDragResizeHandle;
/** @internal */
function useDragToolSettings(args) {
    const { newFloatingWidgetId } = args;
    const item = React.useMemo(() => {
        return {
            type: "widget",
            id: newFloatingWidgetId,
        };
    }, [newFloatingWidgetId]);
    const onDragStart = useDragItem({
        item,
    });
    const handleDragStart = React.useCallback(({ initialPointerPosition }) => {
        onDragStart({
            initialPointerPosition,
            pointerPosition: initialPointerPosition,
            lastPointerPosition: initialPointerPosition,
        });
    }, [onDragStart]);
    return handleDragStart;
}
exports.useDragToolSettings = useDragToolSettings;
/** @internal */
function useTarget(target) {
    const dragManager = React.useContext(exports.DragManagerContext);
    const [targeted, setTargeted] = React.useState(false);
    const targetedRef = React.useRef(false);
    const onTargeted = React.useCallback((doTarget) => {
        const isTargeted = dragManager.isTargeted(target);
        if (doTarget && !isTargeted) {
            dragManager.handleTargetChanged(target);
        }
        else if (!doTarget && isTargeted) {
            dragManager.handleTargetChanged(undefined);
        }
        setTargeted(doTarget);
    }, [dragManager, target]);
    const ref = React.useRef(null);
    React.useEffect(() => {
        return dragManager.onDrag.addListener((_item, info) => {
            const targetedElement = document.elementFromPoint(info.pointerPosition.x, info.pointerPosition.y);
            const newTargeted = targetedElement === ref.current;
            newTargeted !== targetedRef.current && onTargeted(newTargeted);
            targetedRef.current = newTargeted;
        });
    }, [onTargeted, dragManager]);
    React.useEffect(() => {
        return dragManager.onDragEnd.addListener(() => {
            targetedRef.current && onTargeted(false);
            targetedRef.current = false;
        });
    }, [onTargeted, dragManager]);
    return [ref, targeted];
}
exports.useTarget = useTarget;
/** @internal */
function useTabTarget(args) {
    const { tabIndex, widgetId } = args;
    const target = React.useMemo(() => ({
        type: "tab",
        tabIndex,
        widgetId,
    }), [tabIndex, widgetId]);
    return useTarget(target);
}
exports.useTabTarget = useTabTarget;
/** @internal */
function usePanelTarget(args) {
    const { side, newWidgetId } = args;
    const target = React.useMemo(() => ({
        type: "panel",
        side,
        newWidgetId,
    }), [side, newWidgetId]);
    return useTarget(target);
}
exports.usePanelTarget = usePanelTarget;
function defaultIsDragItem(item, dragged) {
    return dragged.type === item.type && dragged.id === item.id;
}
/** @internal */
function useDragItem(args) {
    const dragManager = React.useContext(exports.DragManagerContext);
    const { item, isDragItem, onDragStart, onDrag, onDragEnd } = args;
    const handleDragStart = React.useCallback((info) => {
        item &&
            dragManager.handleDragStart({
                item,
                info,
            });
    }, [dragManager, item]);
    React.useEffect(() => {
        return dragManager.onDragStart.addListener((draggedItem, info, target) => {
            const handleEvent = isDragItem
                ? isDragItem(item, draggedItem)
                : defaultIsDragItem(item, draggedItem);
            if (!handleEvent)
                return;
            onDragStart && onDragStart(draggedItem, info, target);
        });
    }, [dragManager, onDragStart, item, isDragItem]);
    React.useEffect(() => {
        return dragManager.onDrag.addListener((draggedItem, info, target) => {
            const handleEvent = isDragItem
                ? isDragItem(item, draggedItem)
                : defaultIsDragItem(item, draggedItem);
            if (!handleEvent)
                return;
            onDrag && onDrag(draggedItem, info, target);
        });
    }, [dragManager, onDrag, item, isDragItem]);
    React.useEffect(() => {
        return dragManager.onDragEnd.addListener((draggedItem, info, target) => {
            const handleEvent = isDragItem
                ? isDragItem(item, draggedItem)
                : defaultIsDragItem(item, draggedItem);
            if (!handleEvent)
                return;
            onDragEnd && onDragEnd(draggedItem, info, target);
        });
    }, [dragManager, onDragEnd, item, isDragItem]);
    return handleDragStart;
}
exports.useDragItem = useDragItem;
/** @internal */
function useDraggedItem() {
    const dragManager = React.useContext(exports.DragManagerContext);
    const [draggedItem, setDraggedItem] = React.useState(dragManager.draggedItem?.item);
    React.useEffect(() => {
        return dragManager.onDragStart.addListener((item) => {
            setDraggedItem(item);
        });
    }, [dragManager]);
    React.useEffect(() => {
        return dragManager.onDragUpdate.addListener((item) => {
            setDraggedItem(item);
        });
    }, [dragManager]);
    React.useEffect(() => {
        return dragManager.onDragEnd.addListener(() => {
            setDraggedItem(undefined);
        });
    }, [dragManager]);
    return draggedItem;
}
exports.useDraggedItem = useDraggedItem;
/** @internal */
function useIsDraggedItem(item) {
    const draggedItem = useDraggedItem();
    return (!!draggedItem &&
        draggedItem.id === item.id &&
        draggedItem.type === item.type);
}
exports.useIsDraggedItem = useIsDraggedItem;
/** @internal */
function useIsDraggedType(type) {
    const draggedItem = useDraggedItem();
    return !!draggedItem && draggedItem.type === type;
}
exports.useIsDraggedType = useIsDraggedType;
/** @internal */
function useDraggedItemId(type) {
    const draggedItem = useDraggedItem();
    if (draggedItem && draggedItem.type === type) {
        return draggedItem.id;
    }
    return undefined;
}
exports.useDraggedItemId = useDraggedItemId;
/** @internal */
function DragProvider(props) {
    const dragManager = React.useRef(new DragManager());
    React.useEffect(() => {
        const mouseMove = (e) => {
            dragManager.current.handleDrag(e.clientX, e.clientY);
        };
        document.addEventListener("mousemove", mouseMove);
        return () => {
            document.removeEventListener("mousemove", mouseMove);
        };
    }, []);
    React.useEffect(() => {
        const mouseUp = () => {
            dragManager.current.handleDragEnd();
        };
        document.addEventListener("mouseup", mouseUp);
        return () => {
            document.removeEventListener("mouseup", mouseUp);
        };
    }, []);
    return (React.createElement(exports.DragManagerContext.Provider, { value: dragManager.current },
        React.createElement(DraggedWidgetIdProvider, null,
            React.createElement(DraggedPanelSideProvider, null,
                React.createElement(DraggedResizeHandleProvider, null, props.children)))));
}
exports.DragProvider = DragProvider;
function DraggedWidgetIdProvider(props) {
    const draggedWidgetId = useDraggedItemId("widget");
    return (React.createElement(exports.DraggedWidgetIdContext.Provider, { value: draggedWidgetId }, props.children));
}
function DraggedPanelSideProvider(props) {
    const draggedSide = useDraggedItemId("panelGrip");
    return (React.createElement(exports.DraggedPanelSideContext.Provider, { value: draggedSide }, props.children));
}
function DraggedResizeHandleProvider(props) {
    const draggedHandle = useDraggedItemId("resizeHandle");
    return (React.createElement(exports.DraggedResizeHandleContext.Provider, { value: draggedHandle }, props.children));
}
function isResizeHandleDragItem(item) {
    return item.type === "resizeHandle";
}
/** @internal */
class DragManager {
    constructor() {
        this._onDragStartEmitter = new core_bentley_1.BeEvent();
        this._onDragUpdateEmitter = new core_bentley_1.BeEvent();
        this._onDragEmitter = new core_bentley_1.BeEvent();
        this._onDragEndEmitter = new core_bentley_1.BeEvent();
        this._onTargetChangedEmitter = new core_bentley_1.BeEvent();
    }
    get draggedItem() {
        return this._dragged;
    }
    isTargeted(target) {
        if (!this._dragged)
            return false;
        if (!this._dragged.target)
            return false;
        return (0, lodash_1.isEqual)(this._dragged.target, target);
    }
    get onDragStart() {
        return this._onDragStartEmitter;
    }
    get onDragUpdate() {
        return this._onDragUpdateEmitter;
    }
    get onDrag() {
        return this._onDragEmitter;
    }
    get onDragEnd() {
        return this._onDragEndEmitter;
    }
    get onTargetChanged() {
        return this._onTargetChangedEmitter;
    }
    handleDragStart({ item, info }) {
        this._dragged = {
            item,
            info,
            target: undefined,
        };
        this._onDragStartEmitter.raiseEvent(this._dragged.item, this._dragged.info, this._dragged.target);
    }
    handleDragUpdate(item) {
        if (!this._dragged)
            return;
        this._dragged.item = item;
        this._onDragUpdateEmitter.raiseEvent(this._dragged.item, this._dragged.info, this._dragged.target);
    }
    handleDrag(x, y) {
        if (!this._dragged)
            return;
        this._dragged.info.lastPointerPosition = this._dragged.info.pointerPosition;
        this._dragged.info.pointerPosition = new core_react_1.Point(x, y);
        this._onDragEmitter.raiseEvent(this._dragged.item, this._dragged.info, this._dragged.target);
    }
    handleDragEnd() {
        if (!this._dragged)
            return;
        const item = this._dragged.item;
        const info = this._dragged.info;
        const target = this._dragged.target;
        this._dragged = undefined;
        this._onDragEndEmitter.raiseEvent(item, info, target);
        target && this._onTargetChangedEmitter.raiseEvent(undefined);
    }
    handleTargetChanged(target) {
        if (!this._dragged)
            return;
        this._dragged.target = target;
        this._onTargetChangedEmitter.raiseEvent(target);
    }
}
exports.DragManager = DragManager;
/** @internal */
exports.DragManagerContext = React.createContext(null);
exports.DragManagerContext.displayName = "nz:DragManagerContext";
/** @internal */
exports.DraggedWidgetIdContext = React.createContext(undefined);
exports.DraggedWidgetIdContext.displayName = "nz:DraggedWidgetIdContext";
/** @internal */
exports.DraggedPanelSideContext = React.createContext(undefined);
exports.DraggedPanelSideContext.displayName = "nz:DraggedPanelSideContext";
/** @internal */
exports.DraggedResizeHandleContext = React.createContext(undefined);
exports.DraggedResizeHandleContext.displayName = "nz:DraggedResizeHandleContext";
/** @internal */
function useTargeted() {
    const dragManager = React.useContext(exports.DragManagerContext);
    const [targeted, setTargeted] = React.useState();
    React.useEffect(() => {
        return dragManager.onTargetChanged.addListener((t) => {
            setTargeted(t);
        });
    }, [dragManager]);
    return targeted;
}
exports.useTargeted = useTargeted;
//# sourceMappingURL=DragManager.js.map