var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { IS_FIREFOX } from '@blockeditor/global/env';
import { assertExists, assertInstanceOf } from '@blockeditor/global/utils';
import { ShadowlessElement, WithDisposable } from '@blockeditor/lit';
import { html } from 'lit';
import { customElement, query, queryAll, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { toggleEmbedCardCreateModal } from '../../../../_common/components/embed-card/modal/index.js';
import { BlockHubIcon, CrossIcon } from '../../../../_common/icons/index.js';
import { asyncFocusRichText, buildPath, calcDropTarget, getClosestBlockElementByPoint, getDocPageByEditorHost, getEdgelessPageByEditorHost, getHoveringNote, getImageFilesFromLocal, getModelByBlockComponent, isInsideDocEditor, Point, Rect, stopPropagation, } from '../../../../_common/utils/index.js';
import { ImageService } from '../../../../image-block/image-service.js';
import { addImageBlocks, addSiblingImageBlock, } from '../../../../image-block/utils.js';
import { DocPageBlockComponent } from '../../../../page-block/doc/doc-page-block.js';
import { autoScroll } from '../../../../page-block/text-selection/utils.js';
import { getClosestNoteBlock } from '../../drag-handle/utils.js';
import { BlockHubMenu, } from './../components/block-hub-menu.js';
import { BOTTOM_OFFSET, RIGHT_OFFSET, TOP_DISTANCE, TRANSITION_DELAY, } from './../config.js';
import { styles } from './../styles.js';
let BlockHub = class BlockHub extends WithDisposable(ShadowlessElement) {
    constructor(host) {
        super();
        /**
         * A function that returns all blocks that are allowed to be moved to
         */
        this._expanded = false;
        this._isGrabbing = false;
        this._visibleCardType = null;
        this._showTooltip = true;
        this._maxHeight = 2000;
        this._currentClientX = 0;
        this._currentClientY = 0;
        this._isCardListVisible = false;
        this._lastDroppingTarget = null;
        this._lastDroppingType = 'none';
        this._lastDraggingFlavour = null;
        this._timer = null;
        this._rafID = 0;
        this._getHoveringNoteState = (point) => {
            const state = {
                scale: 1,
            };
            if (isInsideDocEditor(this._editorHost)) {
                const closestNoteBlock = getClosestNoteBlock(this._editorHost, this._pageBlockElement, point);
                if (closestNoteBlock) {
                    state.rect = Rect.fromDOMRect(closestNoteBlock.getBoundingClientRect());
                }
            }
            else {
                state.scale = this._pageBlockElement.surface.viewport.zoom;
                const container = getHoveringNote(point);
                if (container) {
                    state.rect = Rect.fromDOM(container);
                    state.container = container;
                }
            }
            return state;
        };
        /**
         * This is currently a workaround, as the height of the _blockHubIconsContainer is determined by the height of its
         * content, and if its child's opacity is set to 0 during a transition, its height won't change, causing the background
         * to exceeds its actual visual height. So currently we manually set the height of those whose opacity is 0 to 0px.
         */
        this._onTransitionStart = (_) => {
            if (this._timer) {
                clearTimeout(this._timer);
            }
            if (!this._expanded) {
                // when the _blockHubMenuContainer is unexpanded, should cancel the vertical padding making it a square
                this._blockHubMenuContainer.style.padding = '0 4px';
                this._timer = window.setTimeout(() => {
                    this._blockHubIconsContainer.style.overflow = 'hidden';
                }, TRANSITION_DELAY);
            }
            else {
                this._blockHubMenuContainer.style.padding = '4px';
                this._timer = window.setTimeout(() => {
                    this._blockHubIconsContainer.style.overflow = 'unset';
                }, TRANSITION_DELAY);
            }
        };
        this._onBlockHubButtonClick = (_) => {
            this._expanded = !this._expanded;
            if (!this._expanded) {
                this._hideCardList();
            }
        };
        this._onMouseDown = (e) => {
            if (IS_FIREFOX) {
                this._currentClientX = e.clientX;
                this._currentClientY = e.clientY;
            }
        };
        this._onDragStart = (event) => {
            this._showTooltip = false;
            // DragEvent that doesn't dispatch manually, is expected to have dataTransfer property
            assertExists(event.dataTransfer);
            event.dataTransfer.effectAllowed = 'move';
            const blockHubElement = event.target;
            const data = {
                flavour: blockHubElement.getAttribute('affine-flavour'),
                type: blockHubElement.getAttribute('affine-type') ?? undefined,
            };
            event.dataTransfer.setData('affine/block-hub', JSON.stringify(data));
            this._lastDraggingFlavour = data.flavour;
            this._pageBlockElement.selection.clear();
        };
        this._onDrag = (e) => {
            this._hideCardList();
            let x = e.clientX;
            let y = e.clientY;
            if (IS_FIREFOX) {
                // In Firefox, `pageX` and `pageY` are always set to 0.
                // Refs: https://stackoverflow.com/questions/13110349/pagex-and-pagey-are-always-set-to-0-in-firefox-during-the-ondrag-event.
                x = this._currentClientX;
                y = this._currentClientY;
            }
            if (!this._indicator ||
                (this._indicator.rect &&
                    this._indicator.rect.left === x &&
                    this._indicator.rect.top === y)) {
                return;
            }
            const point = new Point(x, y);
            const { container, rect: noteRect, scale, } = this._getHoveringNoteState(point.clone());
            if (!noteRect) {
                this._resetDropState();
                return;
            }
            let element = null;
            element = getClosestBlockElementByPoint(point, { container, rect: noteRect, snapToEdge: { x: false, y: true } }, scale);
            if (!element) {
                const { min, max } = noteRect;
                if (x >= min.x && x <= max.x && y >= min.y) {
                    const lastBlock = this._pageBlockElement.model.lastChild();
                    if (lastBlock) {
                        const lastElement = this._editorHost.view.viewFromPath('block', [
                            lastBlock.id,
                        ]);
                        element = lastElement;
                    }
                }
            }
            if (!element) {
                // if (this._mouseRoot.mode === 'page') {
                //   return;
                // }
                this._resetDropState();
                return;
            }
            let type = 'none';
            let rect = null;
            let lastModelState = null;
            const model = getModelByBlockComponent(element);
            const result = calcDropTarget(point, model, element, [], scale, this._lastDraggingFlavour);
            if (result) {
                type = result.type;
                rect = result.rect;
                lastModelState = result.modelState;
            }
            const runner = () => {
                // only support auto scroll in page mode now
                if (this._pageBlockElement instanceof DocPageBlockComponent) {
                    const result = autoScroll(this._pageBlockElement.viewportElement, point.y);
                    if (!result) {
                        this._clearRaf();
                        return;
                    }
                    this._rafID = requestAnimationFrame(runner);
                }
                else {
                    this._clearRaf();
                }
            };
            this._rafID = requestAnimationFrame(runner);
            this._lastDroppingType = type;
            this._indicator.rect = rect;
            this._lastDroppingTarget = lastModelState;
        };
        this._onDragOver = (e) => {
            e.preventDefault();
            return false;
        };
        this._onDragOverDocument = (e) => {
            if (!IS_FIREFOX) {
                throw new Error('FireFox only');
            }
            this._currentClientX = e.clientX;
            this._currentClientY = e.clientY;
        };
        this._onDragEnd = (_) => {
            this._showTooltip = true;
            this._isGrabbing = false;
            this._lastDraggingFlavour = null;
            this._resetDropState();
        };
        this._resetDropState = () => {
            this._lastDroppingType = 'none';
            this._indicator.rect = null;
            this._lastDroppingTarget = null;
        };
        this._onDrop = async (e) => {
            assertExists(e.dataTransfer);
            if (!e.dataTransfer.getData('affine/block-hub'))
                return;
            const page = this._editorHost.page;
            const point = this._indicator?.rect?.min ?? new Point(e.clientX, e.clientY);
            const lastModelState = this._lastDroppingTarget;
            const lastType = this._lastDroppingType;
            const dataTransfer = e.dataTransfer;
            assertExists(dataTransfer);
            const data = dataTransfer.getData('affine/block-hub');
            const models = [];
            const props = JSON.parse(data);
            const isDatabase = props.flavour === 'affine:database';
            // TO DO: fix image loading state for block hub
            if (props.flavour === 'affine:image' &&
                props.type === 'image' &&
                lastModelState &&
                lastType !== 'none' &&
                lastType !== 'database') {
                const imageFiles = await getImageFilesFromLocal();
                const imageService = this._editorHost.spec.getService('affine:image');
                assertExists(imageService);
                assertInstanceOf(imageService, ImageService);
                const maxFileSize = imageService.maxFileSize;
                addSiblingImageBlock(this._editorHost, imageFiles, maxFileSize, lastModelState.model);
            }
            else if (props.flavour === 'affine:bookmark') {
                if (lastModelState && lastType !== 'none' && lastType !== 'database') {
                    const model = lastModelState.model;
                    const parentModel = page.getParent(model);
                    assertExists(parentModel);
                    const index = parentModel.children.indexOf(model) + (lastType === 'after' ? 1 : 0);
                    await toggleEmbedCardCreateModal(this._editorHost, 'Links', 'The added link will be displayed as a card view.', {
                        mode: 'page',
                        parentModel,
                        index,
                    });
                }
            }
            else {
                models.push(props);
            }
            // In some cases, like cancel bookmark initial modal, there will be no models.
            if (!models.length)
                return;
            let parentModel;
            let focusId;
            if (lastModelState && lastType !== 'none') {
                const { model } = lastModelState;
                page.captureSync();
                if (lastType === 'database') {
                    const ids = page.addBlocks(models, model);
                    focusId = ids[0];
                    parentModel = model;
                }
                else {
                    const parent = page.getParent(model);
                    assertExists(parent);
                    const ids = page.addSiblingBlocks(model, models, lastType);
                    focusId = ids[0];
                    parentModel = parent;
                }
                // database init basic structure
                if (isDatabase) {
                    const service = this._pageBlockElement.std.spec?.getService('affine:database');
                    service.initDatabaseBlock(page, model, model.id, 'table');
                }
            }
            if (isInsideDocEditor(this._editorHost)) {
                if (focusId) {
                    asyncFocusRichText(this._editorHost, page, focusId)?.catch(console.error);
                }
                return;
            }
            // In edgeless mode.
            const pageBlockElement = this
                ._pageBlockElement;
            let noteId;
            if (focusId && parentModel) {
                const targetNoteBlock = this._editorHost.view.viewFromPath('block', buildPath(parentModel));
                assertExists(targetNoteBlock);
                noteId = targetNoteBlock.model.id;
            }
            else {
                // Creates new note block on blank area.
                const result = pageBlockElement.addNewNote(models, point, isDatabase ? { width: 752 } : undefined);
                noteId = result.noteId;
                focusId = result.ids[0];
                const model = page.getBlockById(focusId);
                assertExists(model);
                if (isDatabase) {
                    const service = pageBlockElement.std.spec?.getService('affine:database');
                    service.initDatabaseBlock(page, model, model.id, 'table');
                }
            }
            pageBlockElement.setSelection(noteId, true, focusId, point);
        };
        this._onClickCard = async (_, blockHubElement) => {
            const affineType = blockHubElement.getAttribute('affine-type');
            assertExists(affineType);
            const data = {
                flavour: blockHubElement.getAttribute('affine-flavour') ?? '',
                type: affineType ?? undefined,
            };
            const page = this._editorHost.page;
            const models = [];
            const defaultNoteBlock = page.root?.children.findLast(block => block.flavour === 'affine:note') ??
                page.addBlock('affine:note', {}, page.root?.id);
            // add to end
            let lastId;
            // TO DO: fix image loading state for blockhub
            if (data.flavour === 'affine:image' && data.type === 'image') {
                const imageFiles = await getImageFilesFromLocal();
                const imageService = this._editorHost.spec.getService('affine:image');
                assertExists(imageService);
                assertInstanceOf(imageService, ImageService);
                const maxFileSize = imageService.maxFileSize;
                const blockIds = addImageBlocks(this._editorHost, imageFiles, maxFileSize, page, defaultNoteBlock);
                lastId = blockIds[blockIds.length - 1];
            }
            else if (data.flavour === 'affine:bookmark') {
                await toggleEmbedCardCreateModal(this._editorHost, 'Links', 'The added link will be displayed as a card view.', {
                    mode: 'page',
                    parentModel: defaultNoteBlock,
                });
            }
            else {
                models.push(data);
            }
            models.forEach(model => {
                lastId = page.addBlock(model.flavour ?? 'affine:paragraph', model, defaultNoteBlock);
            });
            lastId && (await asyncFocusRichText(this._editorHost, page, lastId));
        };
        this._onClickOutside = (e) => {
            const target = e.target;
            if (target instanceof HTMLElement && !target.closest('affine-block-hub')) {
                this._hideCardList();
            }
        };
        this._onCardMouseDown = (_) => {
            this._isGrabbing = true;
        };
        this._onCardMouseUp = (_) => {
            this._isGrabbing = false;
        };
        this._onBlankMenuMouseDown = () => {
            this._isGrabbing = true;
        };
        this._onBlankMenuMouseUp = () => {
            this._isGrabbing = false;
        };
        this._onBlockHubMenuMouseOver = (e) => {
            const menu = e.currentTarget;
            const cardType = menu.getAttribute('type');
            assertExists(cardType);
            this._isCardListVisible = true;
            this._visibleCardType = cardType;
        };
        this._onBlockHubEntryMouseOver = () => {
            this._isCardListVisible = false;
        };
        this._onResize = () => {
            const boundingClientRect = document.body.getBoundingClientRect();
            this._maxHeight = boundingClientRect.height - TOP_DISTANCE - BOTTOM_OFFSET;
        };
        this._editorHost = host;
    }
    connectedCallback() {
        super.connectedCallback();
        const disposables = this._disposables;
        disposables.addFromEvent(this, 'dragstart', this._onDragStart);
        disposables.addFromEvent(this, 'drag', this._onDrag);
        disposables.addFromEvent(this, 'dragend', this._onDragEnd);
        disposables.addFromEvent(this._editorHost, 'dragover', this._onDragOver);
        disposables.addFromEvent(this._editorHost, 'drop', (e) => {
            this._onDrop(e).catch(console.error);
        });
        disposables.addFromEvent(this, 'mousedown', this._onMouseDown);
        if (IS_FIREFOX) {
            disposables.addFromEvent(this._editorHost, 'dragover', this._onDragOverDocument);
        }
        this._onResize();
    }
    firstUpdated() {
        const disposables = this._disposables;
        this._blockHubCards.forEach(card => {
            disposables.addFromEvent(card, 'mousedown', this._onCardMouseDown);
            disposables.addFromEvent(card, 'mouseup', this._onCardMouseUp);
            disposables.addFromEvent(card, 'click', e => {
                this._onClickCard(e, card).catch(console.error);
            });
        });
        for (const blockHubMenu of this._blockHubMenus) {
            disposables.addFromEvent(blockHubMenu, 'mouseover', this._onBlockHubMenuMouseOver);
            if (blockHubMenu.getAttribute('type') === 'blank') {
                disposables.addFromEvent(blockHubMenu, 'mousedown', this._onBlankMenuMouseDown);
                disposables.addFromEvent(blockHubMenu, 'mouseup', this._onBlankMenuMouseUp);
            }
        }
        disposables.addFromEvent(this._blockHubMenuEntry, 'mouseover', this._onBlockHubEntryMouseOver);
        disposables.addFromEvent(document, 'click', this._onClickOutside);
        disposables.addFromEvent(this._blockHubButton, 'click', this._onBlockHubButtonClick);
        disposables.addFromEvent(this._blockHubButton, 'mousedown', e => {
            // Prevent input from losing focus
            e.preventDefault();
        });
        disposables.addFromEvent(this._blockHubIconsContainer, 'transitionstart', this._onTransitionStart);
        disposables.addFromEvent(window, 'resize', this._onResize);
        this._indicator = (document.querySelector('affine-drag-indicator'));
        if (!this._indicator) {
            this._indicator = (document.createElement('affine-drag-indicator'));
            document.body.appendChild(this._indicator);
        }
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this._disposables.dispose();
    }
    toggleMenu() {
        this._expanded = !this._expanded;
        if (!this._expanded)
            this._hideCardList();
    }
    get _pageBlockElement() {
        const pageElement = isInsideDocEditor(this._editorHost)
            ? getDocPageByEditorHost(this._editorHost)
            : getEdgelessPageByEditorHost(this._editorHost);
        return pageElement;
    }
    _hideCardList() {
        if (this._visibleCardType) {
            this._visibleCardType = null;
            this._isCardListVisible = false;
        }
    }
    _clearRaf() {
        if (this._rafID) {
            cancelAnimationFrame(this._rafID);
            this._rafID = 0;
        }
    }
    render() {
        const blockHubMenu = BlockHubMenu(this._expanded, this._isGrabbing, this._visibleCardType, this._isCardListVisible, this._showTooltip, this._maxHeight);
        const classes = classMap({
            'icon-expanded': this._expanded,
            'new-icon-in-edgeless': !isInsideDocEditor(this._editorHost) && !this._expanded,
            'new-icon': true,
        });
        return html `
      <div
        class="block-hub-menu-container blockeditor-overlay"
        @pointerdown=${stopPropagation}
        ?expanded=${this._expanded}
        style="bottom: ${BOTTOM_OFFSET}px; right: ${RIGHT_OFFSET}px;"
      >
        ${blockHubMenu}
        <div class=${classes} role="menuitem" style="cursor:pointer;">
          ${this._expanded
            ? CrossIcon
            : [
                BlockHubIcon,
                html `<affine-tooltip tip-position="left"
                  >Insert blocks</affine-tooltip
                >`,
            ]}
        </div>
      </div>
    `;
    }
};
BlockHub.styles = styles;
__decorate([
    state()
], BlockHub.prototype, "_expanded", void 0);
__decorate([
    state()
], BlockHub.prototype, "_isGrabbing", void 0);
__decorate([
    state()
], BlockHub.prototype, "_visibleCardType", void 0);
__decorate([
    state()
], BlockHub.prototype, "_showTooltip", void 0);
__decorate([
    state()
], BlockHub.prototype, "_maxHeight", void 0);
__decorate([
    queryAll('.card-container')
], BlockHub.prototype, "_blockHubCards", void 0);
__decorate([
    queryAll('.block-hub-icon-container[type]')
], BlockHub.prototype, "_blockHubMenus", void 0);
__decorate([
    query('.new-icon')
], BlockHub.prototype, "_blockHubButton", void 0);
__decorate([
    query('.block-hub-icons-container')
], BlockHub.prototype, "_blockHubIconsContainer", void 0);
__decorate([
    query('.block-hub-menu-container')
], BlockHub.prototype, "_blockHubMenuContainer", void 0);
__decorate([
    query('[role="menuitem"]')
], BlockHub.prototype, "_blockHubMenuEntry", void 0);
BlockHub = __decorate([
    customElement('affine-block-hub')
], BlockHub);
export { BlockHub };
//# sourceMappingURL=block-hub.js.map