import { PointerEventState, } from '@blockeditor/block-std';
import { DisposableGroup } from '@blockeditor/global/utils';
import { isDatabaseInput, isInsideDocTitle, isInsideEdgelessTextEditor, isMiddleButtonPressed, isPinchEvent, Point, } from '../../../_common/utils/index.js';
import { normalizeWheelDeltaY } from '../../../surface-block/index.js';
import { BrushToolController } from '../controllers/tools/brush-tool.js';
import { ConnectorToolController } from '../controllers/tools/connector-tool.js';
import { DefaultToolController } from '../controllers/tools/default-tool.js';
import { EraserToolController } from '../controllers/tools/eraser-tool.js';
import { PresentToolController } from '../controllers/tools/frame-navigator-tool.js';
import { FrameToolController } from '../controllers/tools/frame-tool.js';
import { NoteToolController } from '../controllers/tools/note-tool.js';
import { PanToolController } from '../controllers/tools/pan-tool.js';
import { ShapeToolController } from '../controllers/tools/shape-tool.js';
import { TextToolController } from '../controllers/tools/text-tool.js';
import { edgelessElementsBound } from '../utils/bound-utils.js';
import { getSelectionBoxBound } from '../utils/query.js';
function shouldFilterMouseEvent(event) {
    const target = event.target;
    if (!target || !(target instanceof HTMLElement)) {
        return false;
    }
    if (target.tagName === 'INPUT') {
        return true;
    }
    if (target.tagName === 'FORMAT-QUICK-BAR') {
        return true;
    }
    if (target.tagName === 'AFFINE-DRAG-HANDLE') {
        return true;
    }
    if (target.tagName === 'EDGELESS-TOOLBAR' ||
        target.tagName === 'EDGELESS-ZOOM-TOOLBAR' ||
        target.tagName === 'ZOOM-BAR-TOGGLE-BUTTON') {
        return true;
    }
    return false;
}
export class EdgelessToolsManager {
    get dragging() {
        return this._dragging;
    }
    get selection() {
        return this.container.selectionManager;
    }
    get surface() {
        return this.container.surface;
    }
    get lastMousePos() {
        return this._lastMousePos;
    }
    get edgelessTool() {
        return this._edgelessTool;
    }
    set edgelessTool(mode) {
        this._edgelessTool = mode;
        // sync mouse mode
        this._controllers[this._edgelessTool.type].tool = this._edgelessTool;
    }
    get currentController() {
        return this._controllers[this.edgelessTool.type];
    }
    get draggingArea() {
        if (!this.currentController.draggingArea)
            return null;
        const { start, end } = this.currentController.draggingArea;
        const minX = Math.min(start.x, end.x);
        const minY = Math.min(start.y, end.y);
        const maxX = Math.max(start.x, end.x);
        const maxY = Math.max(start.y, end.y);
        return new DOMRect(minX, minY, maxX - minX, maxY - minY);
    }
    get shiftKey() {
        return this._shiftKey;
    }
    set shiftKey(pressed) {
        this._shiftKey = pressed;
        this.currentController.onPressShiftKey(pressed);
    }
    get page() {
        return this.container.page;
    }
    constructor(container, dispatcher) {
        this.container = container;
        this.dispatcher = dispatcher;
        this._edgelessTool = {
            type: 'default',
        };
        /** Latest mouse position in view coords */
        this._lastMousePos = { x: 0, y: 0 };
        this._rightClickTimer = null;
        // pressed shift key
        this._shiftKey = false;
        this._dragging = false;
        this._disposables = new DisposableGroup();
        this._add = (name, fn) => {
            this._disposables.add(this.dispatcher.add(name, fn));
        };
        this._onContainerDragStart = (e) => {
            // only allow pan tool in readonly mode
            if (this.page.readonly && this.edgelessTool.type !== 'pan')
                return;
            // do nothing when holding right-key and not in pan mode
            if (e.button === 2 && this.edgelessTool.type !== 'pan')
                return;
            return this.currentController.onContainerDragStart(e);
        };
        this._onContainerDragMove = (e) => {
            // only allow pan tool in readonly mode
            if (this.page.readonly && this.edgelessTool.type !== 'pan')
                return;
            // do nothing when holding right-key and not in pan mode
            if (e.button === 2 && this.edgelessTool.type !== 'pan')
                return;
            return this.currentController.onContainerDragMove(e);
        };
        this._onContainerDragEnd = (e) => {
            // only allow pan tool in readonly mode
            if (this.page.readonly && this.edgelessTool.type !== 'pan')
                return;
            // do nothing when holding right-key and not in pan mode
            if (e.button === 2 && this.edgelessTool.type !== 'pan')
                return;
            return this.currentController.onContainerDragEnd(e);
        };
        this._onContainerClick = (e) => {
            return this.currentController.onContainerClick(e);
        };
        this._onContainerDblClick = (e) => {
            return this.currentController.onContainerDblClick(e);
        };
        this._onContainerTripleClick = (e) => {
            return this.currentController.onContainerTripleClick(e);
        };
        this._onContainerPointerMove = (e) => {
            this._updateLastMousePos(e);
            this.container.slots.hoverUpdated.emit();
            return this._controllers[this.edgelessTool.type].onContainerMouseMove(e);
        };
        this._onContainerPointerOut = (e) => {
            return this._controllers[this.edgelessTool.type].onContainerMouseOut(e);
        };
        this._onContainerContextMenu = (e) => {
            e.event.preventDefault();
            const pointerEventState = new PointerEventState({
                event: e.event,
                rect: this.dispatcher.host.getBoundingClientRect(),
                startX: 0,
                startY: 0,
                last: null,
            });
            const edgelessTool = this.edgelessTool;
            if (edgelessTool.type !== 'pan' && !this._rightClickTimer) {
                this._rightClickTimer = {
                    edgelessTool: edgelessTool,
                    timeStamp: e.event.timeStamp,
                    timer: window.setTimeout(() => {
                        this._controllers['pan'].onContainerDragStart(pointerEventState);
                    }, 233),
                };
            }
        };
        this._onContainerPointerDown = (e) => {
            if (!isMiddleButtonPressed(e.raw)) {
                if (this.page.readonly)
                    return;
                return this.currentController.onContainerPointerDown(e);
            }
            const prevEdgelessTool = this._edgelessTool;
            const switchToPreMode = (_e) => {
                if (!isMiddleButtonPressed(_e)) {
                    this.setEdgelessTool(prevEdgelessTool);
                    document.removeEventListener('pointerup', switchToPreMode, false);
                    document.removeEventListener('pointerover', switchToPreMode, false);
                }
            };
            this.dispatcher.disposables.addFromEvent(document, 'pointerover', switchToPreMode);
            this.dispatcher.disposables.addFromEvent(document, 'pointerup', switchToPreMode);
            this.setEdgelessTool({ type: 'pan', panning: true });
        };
        this._onContainerPointerUp = (e) => {
            if (e.button === 2 && this._rightClickTimer) {
                const { timer, timeStamp, edgelessTool: edgelessTool, } = this._rightClickTimer;
                if (e.raw.timeStamp - timeStamp > 233) {
                    this.container.slots.edgelessToolUpdated.emit(edgelessTool);
                }
                else {
                    clearTimeout(timer);
                }
                this._rightClickTimer = null;
            }
        };
        this.setEdgelessTool = (edgelessTool, state = {
            elements: [],
            editing: false,
        }) => {
            const { type } = edgelessTool;
            if (this.page.readonly && type !== 'pan' && type !== 'frameNavigator') {
                return;
            }
            if (this.edgelessTool === edgelessTool)
                return;
            const lastType = this.edgelessTool.type;
            this._controllers[lastType].beforeModeSwitch(edgelessTool);
            this._controllers[edgelessTool.type].beforeModeSwitch(edgelessTool);
            if (type === 'default' &&
                (Array.isArray(state)
                    ? this.selection.isEmpty(state)
                    : state.elements.length === 0) &&
                this.selection.lastState) {
                state = this.selection.lastState;
            }
            this.selection.set(state);
            this.container.slots.edgelessToolUpdated.emit(edgelessTool);
            this._controllers[lastType].afterModeSwitch(edgelessTool);
            this._controllers[edgelessTool.type].afterModeSwitch(edgelessTool);
        };
        this._controllers = {
            default: new DefaultToolController(this.container),
            text: new TextToolController(this.container),
            shape: new ShapeToolController(this.container),
            brush: new BrushToolController(this.container),
            pan: new PanToolController(this.container),
            'affine:note': new NoteToolController(this.container),
            connector: new ConnectorToolController(this.container),
            eraser: new EraserToolController(this.container),
            frame: new FrameToolController(this.container),
            frameNavigator: new PresentToolController(this.container),
        };
        this._initMouseAndWheelEvents().catch(console.error);
    }
    _updateLastMousePos(e) {
        this._lastMousePos = {
            x: e.x,
            y: e.y,
        };
    }
    async _initMouseAndWheelEvents() {
        // due to surface initializing after one frame, the events handler should register after that.
        if (!this.container.surface) {
            await new Promise(resolve => requestAnimationFrame(resolve));
        }
        this._add('dragStart', ctx => {
            this._dragging = true;
            const event = ctx.get('pointerState');
            if (shouldFilterMouseEvent(event.raw))
                return;
            if (!isInsideDocTitle(this.container.host, event.raw.target) &&
                !isDatabaseInput(event.raw.target) &&
                !isInsideEdgelessTextEditor(this.container.host, event.raw.target)) {
                event.raw.preventDefault();
            }
            this._onContainerDragStart(event);
        });
        this._add('dragMove', ctx => {
            const event = ctx.get('pointerState');
            if (shouldFilterMouseEvent(event.raw))
                return;
            if (!isInsideDocTitle(this.container.host, event.raw.target) &&
                !isDatabaseInput(event.raw.target) &&
                !isInsideEdgelessTextEditor(this.container.host, event.raw.target)) {
                event.raw.preventDefault();
            }
            this._onContainerDragMove(event);
        });
        this._add('dragEnd', ctx => {
            this._dragging = false;
            const event = ctx.get('pointerState');
            if (!isInsideDocTitle(this.container.host, event.raw.target) &&
                !isDatabaseInput(event.raw.target) &&
                !isInsideEdgelessTextEditor(this.container.host, event.raw.target)) {
                event.raw.preventDefault();
            }
            this._onContainerDragEnd(event);
        });
        this._add('click', ctx => {
            const event = ctx.get('pointerState');
            this._onContainerClick(event);
        });
        this._add('doubleClick', ctx => {
            const event = ctx.get('pointerState');
            if (shouldFilterMouseEvent(event.raw))
                return;
            this._onContainerDblClick(event);
        });
        this._add('tripleClick', ctx => {
            const event = ctx.get('pointerState');
            if (shouldFilterMouseEvent(event.raw))
                return;
            this._onContainerTripleClick(event);
        });
        this._add('pointerMove', ctx => {
            const event = ctx.get('pointerState');
            if (shouldFilterMouseEvent(event.raw))
                return;
            if (!isInsideDocTitle(this.container.host, event.raw.target) &&
                !isDatabaseInput(event.raw.target) &&
                !isInsideEdgelessTextEditor(this.container.host, event.raw.target)) {
                event.raw.preventDefault();
            }
            this._onContainerPointerMove(event);
        });
        this._add('pointerDown', ctx => {
            const event = ctx.get('pointerState');
            this._onContainerPointerDown(event);
        });
        this._add('pointerUp', ctx => {
            const event = ctx.get('pointerState');
            this._onContainerPointerUp(event);
        });
        this._add('pointerOut', ctx => {
            const event = ctx.get('pointerState');
            this._onContainerPointerOut(event);
        });
        this._add('contextMenu', ctx => {
            const event = ctx.get('defaultState');
            this._onContainerContextMenu(event);
        });
        this._add('wheel', ctx => {
            const state = ctx.get('defaultState');
            const e = state.event;
            if (!(e instanceof WheelEvent))
                return;
            e.preventDefault();
            const container = this.container;
            const { viewport } = container.surface;
            // pan
            if (!isPinchEvent(e)) {
                const dx = e.deltaX / viewport.zoom;
                const dy = e.deltaY / viewport.zoom;
                viewport.applyDeltaCenter(dx, dy);
            }
            // zoom
            else {
                const rect = container.getBoundingClientRect();
                // Perform zooming relative to the mouse position
                const [baseX, baseY] = container.surface.toModelCoord(e.clientX - rect.x, e.clientY - rect.y);
                const zoom = normalizeWheelDeltaY(e.deltaY, viewport.zoom);
                viewport.setZoom(zoom, new Point(baseX, baseY));
            }
        });
    }
    getHoverState() {
        if (!this.currentController.enableHover) {
            return null;
        }
        const { surface } = this.container;
        const { x, y } = this._lastMousePos;
        const [modelX, modelY] = surface.toModelCoord(x, y);
        const hovered = surface.pickTop(modelX, modelY);
        if (!hovered || this.selection?.editing) {
            return null;
        }
        return {
            rect: getSelectionBoxBound(surface.viewport, edgelessElementsBound([hovered])),
            content: hovered,
        };
    }
    switchToDefaultMode(state) {
        this.setEdgelessTool({ type: 'default' }, state);
    }
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    clear() { }
    dispose() {
        this._disposables.dispose();
    }
}
//# sourceMappingURL=tools-manager.js.map