/*---------------------------------------------------------------------------------------------
 * 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
 */
import * as React from "react";
import isEqual from "lodash/isEqual.js";
import { BeEvent } from "@itwin/core-bentley";
import { Point } from "@itwin/core-react/internal";
import { getUniqueId } from "./NineZone.js";
import { isTabDragDropTargetState, isWidgetDragDropTargetState, } from "../state/DropTargetState.js";
/** @internal */
export 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 && isTabDragDropTargetState(target)) {
            tabTarget = target;
        }
        else {
            const tabInfo = info;
            const newFloatingWidgetId = 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;
}
/** @internal */
export function useDragWidget(args) {
    const { widgetId, onDragStart, onDrag, onDragEnd } = args;
    const dragManager = React.useContext(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 && 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;
}
/** @internal */
export 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;
}
/** @internal */
export 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;
}
/** @internal */
export 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;
}
/** @internal */
export function useTarget(target) {
    const dragManager = React.useContext(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];
}
/** @internal */
export function useTabTarget(args) {
    const { tabIndex, widgetId } = args;
    const target = React.useMemo(() => ({
        type: "tab",
        tabIndex,
        widgetId,
    }), [tabIndex, widgetId]);
    return useTarget(target);
}
/** @internal */
export function usePanelTarget(args) {
    const { side, newWidgetId } = args;
    const target = React.useMemo(() => ({
        type: "panel",
        side,
        newWidgetId,
    }), [side, newWidgetId]);
    return useTarget(target);
}
function defaultIsDragItem(item, dragged) {
    return dragged.type === item.type && dragged.id === item.id;
}
/** @internal */
export function useDragItem(args) {
    const dragManager = React.useContext(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;
}
/** @internal */
export function useDraggedItem() {
    const dragManager = React.useContext(DragManagerContext);
    const subscribe = React.useCallback((onStoreChange) => {
        const listeners = [
            dragManager.onDragStart.addListener(onStoreChange),
            dragManager.onDragUpdate.addListener(onStoreChange),
            dragManager.onDragEnd.addListener(onStoreChange),
        ];
        return () => listeners.forEach((l) => l());
    }, [dragManager]);
    const getSnapshot = React.useCallback(() => {
        return dragManager.draggedItem?.item;
    }, [dragManager]);
    return React.useSyncExternalStore(subscribe, getSnapshot);
}
/** @internal */
export function useIsDraggedItem(item) {
    const draggedItem = useDraggedItem();
    return (!!draggedItem &&
        draggedItem.id === item.id &&
        draggedItem.type === item.type);
}
/** @internal */
export function useIsDraggedType(type) {
    const draggedItem = useDraggedItem();
    return !!draggedItem && draggedItem.type === type;
}
/** @internal */
export function useDraggedItemId(type) {
    const draggedItem = useDraggedItem();
    if (draggedItem && draggedItem.type === type) {
        return draggedItem.id;
    }
    return undefined;
}
/** @internal */
export 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(DragManagerContext.Provider, { value: dragManager.current },
        React.createElement(DraggedWidgetIdProvider, null,
            React.createElement(DraggedPanelSideProvider, null,
                React.createElement(DraggedResizeHandleProvider, null, props.children)))));
}
function DraggedWidgetIdProvider(props) {
    const draggedWidgetId = useDraggedItemId("widget");
    return (React.createElement(DraggedWidgetIdContext.Provider, { value: draggedWidgetId }, props.children));
}
function DraggedPanelSideProvider(props) {
    const draggedSide = useDraggedItemId("panelGrip");
    return (React.createElement(DraggedPanelSideContext.Provider, { value: draggedSide }, props.children));
}
function DraggedResizeHandleProvider(props) {
    const draggedHandle = useDraggedItemId("resizeHandle");
    return (React.createElement(DraggedResizeHandleContext.Provider, { value: draggedHandle }, props.children));
}
function isResizeHandleDragItem(item) {
    return item.type === "resizeHandle";
}
/** @internal */
export class DragManager {
    constructor() {
        this._onDragStartEmitter = new BeEvent();
        this._onDragUpdateEmitter = new BeEvent();
        this._onDragEmitter = new BeEvent();
        this._onDragEndEmitter = new BeEvent();
        this._onTargetChangedEmitter = new BeEvent();
    }
    get draggedItem() {
        return this._dragged;
    }
    isTargeted(target) {
        if (!this._dragged)
            return false;
        if (!this._dragged.target)
            return false;
        return 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 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);
    }
}
/** @internal */
export const DragManagerContext = React.createContext(null);
DragManagerContext.displayName = "nz:DragManagerContext";
/** @internal */
export const DraggedWidgetIdContext = React.createContext(undefined);
DraggedWidgetIdContext.displayName = "nz:DraggedWidgetIdContext";
/** @internal */
export const DraggedPanelSideContext = React.createContext(undefined);
DraggedPanelSideContext.displayName = "nz:DraggedPanelSideContext";
/** @internal */
export const DraggedResizeHandleContext = React.createContext(undefined);
DraggedResizeHandleContext.displayName = "nz:DraggedResizeHandleContext";
/** @internal */
export function useTargeted() {
    const dragManager = React.useContext(DragManagerContext);
    const [targeted, setTargeted] = React.useState();
    React.useEffect(() => {
        return dragManager.onTargetChanged.addListener((t) => {
            setTargeted(t);
        });
    }, [dragManager]);
    return targeted;
}
//# sourceMappingURL=DragManager.js.map