import { DisposableGroup, noop } from '@blockeditor/global/utils';
import { handleNativeRangeAtPoint, resetNativeSelection, } from '../../../../_common/utils/index.js';
import { Bound, ConnectorElement, GroupElement, ShapeElement, TextElement, Vec, } from '../../../../surface-block/index.js';
import { getElementsFromGroup } from '../../../../surface-block/managers/group-manager.js';
import { isConnectorAndBindingsAllSelected } from '../../connector-manager.js';
import { edgelessElementsBound } from '../../utils/bound-utils.js';
import { calPanDelta } from '../../utils/panning-utils.js';
import { isCanvasElement, isFrameBlock, isNoteBlock, } from '../../utils/query.js';
import { addText, mountFrameTitleEditor, mountGroupTitleEditor, mountShapeTextEditor, mountTextElementEditor, } from '../../utils/text.js';
import { prepareClipboardData } from '../clipboard.js';
import { EdgelessToolController } from './index.js';
export var DefaultModeDragType;
(function (DefaultModeDragType) {
    /** Moving selected contents */
    DefaultModeDragType["ContentMoving"] = "content-moving";
    /** Expanding the dragging area, select the content covered inside */
    DefaultModeDragType["Selecting"] = "selecting";
    /** Native range dragging inside active note block */
    DefaultModeDragType["NativeEditing"] = "native-editing";
    /** Default void state */
    DefaultModeDragType["None"] = "none";
    /** Dragging preview */
    DefaultModeDragType["PreviewDragging"] = "preview-dragging";
    /** press alt/option key to clone selected  */
    DefaultModeDragType["AltCloning"] = "alt-cloning";
})(DefaultModeDragType || (DefaultModeDragType = {}));
export class DefaultToolController extends EdgelessToolController {
    constructor() {
        super(...arguments);
        this.tool = {
            type: 'default',
        };
        this.enableHover = true;
        this.dragType = DefaultModeDragType.None;
        this._dragStartPos = [0, 0];
        this._dragLastPos = [0, 0];
        this._dragStartModelCoord = [0, 0];
        this._dragLastModelCoord = [0, 0];
        this._lastMoveDelta = [0, 0];
        this._lock = false;
        // Do not select the text, when click again after activating the note.
        this._isDoubleClickedOnMask = false;
        this._alignBound = new Bound();
        this._selectedBounds = [];
        this._toBeMoved = [];
        this._frames = new Set();
        this._autoPanTimer = null;
        this._dragging = false;
        this._draggingAreaDisposables = null;
        this._updateSelectingState = () => {
            const { surface, tools, selectionManager } = this._edgeless;
            const { viewport } = surface;
            const startX = this._dragStartModelCoord[0];
            const startY = this._dragStartModelCoord[1];
            // Should convert the last drag position to model coordinate
            const [curX, curY] = viewport.toModelCoord(this._dragLastPos[0], this._dragLastPos[1]);
            const x = Math.min(startX, curX);
            const y = Math.min(startY, curY);
            const w = Math.abs(startX - curX);
            const h = Math.abs(startY - curY);
            const bound = new Bound(x, y, w, h);
            const elements = surface.pickByBound(bound);
            const set = new Set(tools.shiftKey ? [...elements, ...selectionManager.elements] : elements);
            this._setSelectionState(Array.from(set).map(element => element.id), false);
            // Record the last model coordinate for dragging area updating
            this._dragLastModelCoord = [curX, curY];
            this._forceUpdateSelection(this.dragType, true);
            this._edgeless.slots.draggingAreaUpdated.emit();
        };
        this._panViewport = (delta) => {
            const { viewport } = this._edgeless.surface;
            viewport.applyDeltaCenter(delta[0], delta[1]);
        };
        this._stopAutoPanning = () => {
            if (this._autoPanTimer) {
                clearTimeout(this._autoPanTimer);
                this._autoPanTimer = null;
            }
        };
        this._clearDraggingAreaDisposable = () => {
            if (this._draggingAreaDisposables) {
                this._draggingAreaDisposables.dispose();
                this._draggingAreaDisposables = null;
            }
        };
        this._startAutoPanning = (delta) => {
            this._panViewport(delta);
            this._stopAutoPanning();
            this._autoPanTimer = window.setInterval(() => {
                this._panViewport(delta);
                this._updateSelectingState();
            }, 30);
        };
        this._clearSelectingState = () => {
            this._stopAutoPanning();
            this._clearDraggingAreaDisposable();
            this._dragging = false;
            this._dragLastPos = [0, 0];
            this._dragStartModelCoord = [0, 0];
            this._dragLastModelCoord = [0, 0];
            this._edgeless.slots.draggingAreaUpdated.emit();
        };
    }
    get draggingArea() {
        if (this.dragType === DefaultModeDragType.Selecting) {
            const [startX, startY] = this._surface.toViewCoord(this._dragStartModelCoord[0], this._dragStartModelCoord[1]);
            const [endX, endY] = this._surface.toViewCoord(this._dragLastModelCoord[0], this._dragLastModelCoord[1]);
            return {
                start: new DOMPoint(startX, startY),
                end: new DOMPoint(endX, endY),
            };
        }
        return null;
    }
    get selection() {
        return this._edgeless.selectionManager;
    }
    get state() {
        return this.selection.selections;
    }
    _pick(x, y, options) {
        const { surface } = this._edgeless;
        return surface.pickTopWithGroup(surface.viewport.toModelCoord(x, y), options);
    }
    _setNoneSelectionState() {
        if (this.selection.empty)
            return;
        this.selection.clear();
        resetNativeSelection(null);
    }
    _setSelectionState(elements, editing) {
        this.selection.set({
            elements,
            editing,
        });
    }
    _handleClickOnSelected(element, e) {
        const { selectedIds, selections } = this.selection;
        const editing = selections[0]?.editing ?? false;
        // click the inner area of active text and note element
        if (selectedIds.length === 1 && selectedIds[0] === element.id && editing) {
            return;
        }
        // handle single note block click
        if (!e.keys.shift && selectedIds.length === 1 && isNoteBlock(element)) {
            if ((selectedIds[0] === element.id && !editing) ||
                (editing && selectedIds[0] !== element.id)) {
                // issue #1809
                // If the previously selected element is a noteBlock and is in an active state,
                // then the currently clicked noteBlock should also be in an active state when selected.
                this._setSelectionState([element.id], true);
                requestAnimationFrame(() => {
                    handleNativeRangeAtPoint(e.raw.clientX, e.raw.clientY);
                });
                return;
            }
        }
        this.selection.set({
            // hold shift key to multi select or de-select element
            elements: e.keys.shift
                ? this.selection.has(element.id)
                    ? selectedIds.filter(id => id !== element.id)
                    : [...selectedIds, element.id]
                : [element.id],
            editing: false,
        });
    }
    _handleSurfaceDragMove(selected, initialBound, delta) {
        if (!this._lock) {
            this._lock = true;
            this._page.captureSync();
        }
        const { surface } = this._edgeless;
        const bound = initialBound.clone();
        bound.x += delta[0];
        bound.y += delta[1];
        if (selected instanceof ConnectorElement) {
            surface.connector.updateXYWH(selected, bound);
        }
        this._surface.updateElement(selected.id, {
            xywh: bound.serialize(),
        });
    }
    _handleBlockDragMove(block, initialBound, delta) {
        const bound = initialBound.clone();
        bound.x += delta[0];
        bound.y += delta[1];
        this._surface.updateElement(block.id, {
            xywh: bound.serialize(),
        });
    }
    _isInSelectedRect(viewX, viewY) {
        const selected = this.selection.elements;
        if (!selected.length)
            return false;
        const commonBound = edgelessElementsBound(selected);
        const [modelX, modelY] = this._surface.toModelCoord(viewX, viewY);
        if (commonBound && commonBound.isPointInBound([modelX, modelY])) {
            return true;
        }
        return false;
    }
    _isDraggable(element) {
        return !(element instanceof ConnectorElement &&
            !isConnectorAndBindingsAllSelected(element, this._toBeMoved));
    }
    _forceUpdateSelection(type, dragging = false, delta = [0, 0]) {
        this._edgeless.slots.selectedRectUpdated.emit({
            type: type === DefaultModeDragType.Selecting ? 'select' : 'move',
            delta: Vec.toPoint(delta),
            dragging,
        });
    }
    onContainerPointerDown() {
        noop();
    }
    onContainerClick(e) {
        const selected = this._pick(e.x, e.y);
        if (selected) {
            this._handleClickOnSelected(selected, e);
        }
        else if (!e.keys.shift) {
            this._setNoneSelectionState();
        }
        this._isDoubleClickedOnMask = false;
    }
    onContainerContextMenu() {
        // repairContextMenuRange(e);
        noop();
    }
    onContainerDblClick(e) {
        if (this._page.readonly) {
            const viewport = this._surface.viewport;
            if (viewport.zoom === 1) {
                // Fit to Screen
                const { centerX, centerY, zoom } = this._edgeless.getFitToScreenData();
                viewport.setViewport(zoom, [centerX, centerY], true);
            }
            else {
                // Zoom to 100% and Center
                const [x, y] = viewport.toModelCoord(e.x, e.y);
                viewport.setViewport(1, [x, y], true);
            }
            return;
        }
        const selected = this._pick(e.x, e.y, {
            pierce: true,
            expand: 10,
        });
        if (!selected) {
            addText(this._edgeless, e);
            return;
        }
        else {
            const [modelX, modelY] = this._edgeless.surface.viewport.toModelCoord(e.x, e.y);
            if (selected instanceof TextElement) {
                mountTextElementEditor(selected, this._edgeless, {
                    x: modelX,
                    y: modelY,
                });
                return;
            }
            if (selected instanceof ShapeElement) {
                mountShapeTextEditor(selected, this._edgeless);
                return;
            }
            if (isFrameBlock(selected)) {
                mountFrameTitleEditor(selected, this._edgeless);
                return;
            }
            if (selected instanceof GroupElement &&
                selected.titleBound.containsPoint([modelX, modelY])) {
                mountGroupTitleEditor(selected, this._edgeless);
                return;
            }
        }
        if (e.raw.target &&
            e.raw.target instanceof HTMLElement &&
            e.raw.target.classList.contains('affine-note-mask')) {
            this.onContainerClick(e);
            this._isDoubleClickedOnMask = true;
            return;
        }
    }
    onContainerTripleClick() {
        if (this._isDoubleClickedOnMask)
            return;
    }
    _determineDragType(e) {
        // Is dragging started from current selected rect
        if (this._isInSelectedRect(e.x, e.y)) {
            return this.selection.editing
                ? DefaultModeDragType.NativeEditing
                : DefaultModeDragType.ContentMoving;
        }
        else {
            const selected = this._pick(e.x, e.y);
            if (selected) {
                this._setSelectionState([selected.id], false);
                return DefaultModeDragType.ContentMoving;
            }
            else {
                return DefaultModeDragType.Selecting;
            }
        }
    }
    async _cloneContent() {
        this._lock = true;
        const { _edgeless } = this;
        const { clipboardController } = _edgeless;
        const data = JSON.parse(JSON.stringify(await prepareClipboardData(this._toBeMoved, _edgeless.std)));
        const bound = edgelessElementsBound(this._toBeMoved);
        const [elements, blocks] = await clipboardController.createElementsFromClipboardData(data, bound.center);
        this._toBeMoved = [...elements, ...blocks];
        this._setSelectionState(this._toBeMoved.map(el => el.id), false);
    }
    _addFrames() {
        this.selection.elements.forEach(ele => {
            if (isFrameBlock(ele)) {
                this._frames.add(ele);
            }
            else {
                const frame = this._edgeless.surface.frame.selectFrame([ele]);
                if (frame) {
                    this._frames.add(frame);
                }
            }
        });
    }
    async onContainerDragStart(e) {
        // Determine the drag type based on the current state and event
        let dragType = this._determineDragType(e);
        this.prepareMovedElements();
        // If alt key is pressed and content is moving, clone the content
        if (e.keys.alt && dragType === DefaultModeDragType.ContentMoving) {
            dragType = DefaultModeDragType.AltCloning;
            await this._cloneContent();
        }
        this._filterConnectedConnector();
        this._toBeMoved.forEach(ele => {
            ele.stash('xywh');
        });
        // Connector needs to be updated first
        this._toBeMoved.sort((a, _) => (a instanceof ConnectorElement ? -1 : 1));
        this._addFrames();
        // Set up drag state
        this.initializeDragState(e, dragType);
    }
    _filterConnectedConnector() {
        this._toBeMoved = this._toBeMoved.filter(ele => {
            if (ele instanceof ConnectorElement && ele.source.id && ele.target.id) {
                if (!!this._toBeMoved.find(e => e.id === ele.source.id) &&
                    !!this._toBeMoved.find(e => e.id === ele.target.id)) {
                    return false;
                }
            }
            return true;
        });
    }
    prepareMovedElements() {
        const elements = this.selection.elements;
        const toBeMoved = new Set(elements);
        elements.forEach(element => {
            if (isFrameBlock(element)) {
                this._surface.frame
                    .getElementsInFrame(element)
                    .forEach(ele => toBeMoved.add(ele));
            }
            else if (element instanceof GroupElement) {
                getElementsFromGroup(element).forEach(ele => toBeMoved.add(ele));
            }
        });
        this._toBeMoved = Array.from(toBeMoved);
    }
    initializeDragState(e, dragType) {
        const { x, y } = e;
        this.dragType = dragType;
        this._dragging = true;
        this._dragStartPos = [x, y];
        this._dragLastPos = [x, y];
        const [startX, startY] = this._surface.toModelCoord(x, y);
        this._dragStartModelCoord = [startX, startY];
        this._dragLastModelCoord = [startX, startY];
        this._alignBound = this._surface.snap.setupAlignables(this._toBeMoved);
        this._selectedBounds = this._toBeMoved.map(element => Bound.deserialize(element.xywh));
        // If the drag type is selecting, set up the dragging area disposable group
        // If the viewport updates when dragging, should update the dragging area and selection
        if (this.dragType === DefaultModeDragType.Selecting) {
            this._clearDraggingAreaDisposable();
            this._draggingAreaDisposables = new DisposableGroup();
            this._draggingAreaDisposables.add(this._edgeless.slots.viewportUpdated.on(() => {
                if (this.dragType === DefaultModeDragType.Selecting &&
                    this._dragging &&
                    !this._autoPanTimer) {
                    this._updateSelectingState();
                }
            }));
        }
    }
    onContainerDragMove(e) {
        const { surface } = this._edgeless;
        const { viewport } = surface;
        const zoom = viewport.zoom;
        switch (this.dragType) {
            case DefaultModeDragType.Selecting: {
                // Record the last drag pointer position for auto panning and view port updating
                this._dragLastPos = [e.x, e.y];
                this._updateSelectingState();
                const moveDelta = calPanDelta(viewport, e);
                if (moveDelta) {
                    this._startAutoPanning(moveDelta);
                }
                else {
                    this._stopAutoPanning();
                }
                break;
            }
            case DefaultModeDragType.AltCloning:
            case DefaultModeDragType.ContentMoving: {
                if (this._toBeMoved.every(ele => {
                    return !this._isDraggable(ele);
                })) {
                    return;
                }
                const dx = (e.x - this._dragStartPos[0]) / zoom;
                const dy = (e.y - this._dragStartPos[1]) / zoom;
                const curBound = this._alignBound.clone();
                curBound.x += dx;
                curBound.y += dy;
                const alignRst = surface.snap.align(curBound);
                const delta = [dx + alignRst.dx, dy + alignRst.dy];
                this._toBeMoved.forEach((element, index) => {
                    if (isCanvasElement(element)) {
                        if (!this._isDraggable(element))
                            return;
                        this._handleSurfaceDragMove(element, this._selectedBounds[index], delta);
                    }
                    else {
                        this._handleBlockDragMove(element, this._selectedBounds[index], delta);
                    }
                });
                const frame = surface.frame.selectFrame(this._toBeMoved);
                frame
                    ? surface.frame.setHighlight(frame)
                    : surface.frame.clearHighlight();
                this._forceUpdateSelection(this.dragType, true, Vec.sub(delta, this._lastMoveDelta));
                this._lastMoveDelta = delta;
                break;
            }
            case DefaultModeDragType.NativeEditing: {
                // TODO reset if drag out of note
                break;
            }
        }
    }
    onContainerDragEnd() {
        this._page.transact(() => {
            this._toBeMoved.forEach(el => {
                el.pop('xywh');
            });
        });
        if (this._lock) {
            this._page.captureSync();
            this._lock = false;
        }
        if (this.selection.editing) {
            return;
        }
        const { surface } = this._edgeless;
        this._dragStartPos = [0, 0];
        this._dragLastPos = [0, 0];
        this._selectedBounds = [];
        this._lastMoveDelta = [0, 0];
        surface.snap.cleanupAlignables();
        surface.frame.clearHighlight();
        this._addFrames();
        this._frames.clear();
        this._toBeMoved = [];
        this._forceUpdateSelection(this.dragType);
        this._clearSelectingState();
        this.dragType = DefaultModeDragType.None;
    }
    onContainerMouseMove() {
        noop();
    }
    onContainerMouseOut(_) {
        noop();
    }
    onPressShiftKey(_) {
        noop();
    }
    beforeModeSwitch() {
        this._stopAutoPanning();
        this._clearDraggingAreaDisposable();
        noop();
    }
    afterModeSwitch() {
        noop();
    }
}
//# sourceMappingURL=default-tool.js.map