import { __decorate } from "tslib";
/* eslint-disable class-methods-use-this */
import { html, svg } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { property, queryAll } from 'lit/decorators.js';
import { iconWrapper } from '../ui/icons/Icons.js';
import { ElementPositioning } from '../ui/lib/ElementPositioning.js';
import UiList from '../ui/list/UiList.js';
import '../define/ui/ui-list-item.js';
import '../define/ui/ui-divider.js';
export const arrowRight = iconWrapper(svg `<path d="M0 0h24v24H0V0z" fill="none"/><path d="M10 17l5-5-5-5v10z"/>`);
export const check = iconWrapper(svg `<path d="M0 0h24v24H0V0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/>`);
/**
 * @fires close - when the menu closes.
 * @fires trigger - when a command is triggered
 */
export default class ContextualMenuElement extends UiList {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    set items(value) {
        // 
    }
    get items() {
        return Array.from(this._items);
    }
    constructor() {
        super();
        /**
         * The timeout after which the sub menu become visible or is removed from the menu.
         */
        this.subMenuTimeout = 300;
        this.subTriggerHandler = this.subTriggerHandler.bind(this);
        this.addEventListener('mousemove', this.mouseoverHandler.bind(this));
    }
    connectedCallback() {
        super.connectedCallback();
        if (!this.hasAttribute('role')) {
            this.setAttribute('role', "menu");
        }
        if (!this.hasAttribute('aria-label')) {
            this.setAttribute('aria-label', "Context menu");
        }
    }
    close() {
        this.dispatchEvent(new Event('close'));
    }
    /**
     * Triggest nested menu when hovering an item that has a nested menu.
     */
    mouseoverHandler(e) {
        if (e.defaultPrevented) {
            return;
        }
        e.preventDefault();
        const path = e.composedPath();
        const target = path.find(i => i.localName === 'ui-list-item');
        if (!target) {
            return;
        }
        const { nested, cmd } = target.dataset;
        if (nested === 'true') {
            this.buildNested(cmd);
        }
        else if (this.nestedIdValue) {
            this.removeNested();
        }
    }
    /**
     * Builds a nested menu for a command.
     * @param id The id of the command
     */
    buildNested(id) {
        if (!id || id === this.nestedIdValue) {
            return;
        }
        if (this.nestedIdValue) {
            this.clearNested();
        }
        const cmd = this.commands?.find((item) => item.id === id);
        if (!cmd || !cmd.hasChildren) {
            return;
        }
        this.nestedIdValue = id;
        if (this.nestedTimeout) {
            clearTimeout(this.nestedTimeout);
        }
        this.nestedTimeout = setTimeout(() => {
            this.nestedTimeout = undefined;
            this.createNested();
        }, this.subMenuTimeout);
    }
    /**
     * Adds the nested menu to the local DOM.
     */
    createNested(opts = {}) {
        const id = this.nestedIdValue;
        if (!id) {
            return;
        }
        const ref = (this.shadowRoot.querySelector(`[data-cmd="${id}"]`));
        // const box = ref.getBoundingClientRect();
        const cmd = this.commands?.find((item) => item.id === id);
        if (!cmd) {
            return;
        }
        const { children } = cmd;
        const menu = document.createElement('contextual-menu');
        // TODO: create a type definition for processed internally menu items
        menu.commands = children;
        menu.store = this.store;
        menu.target = this.target;
        menu.workspace = this.workspace;
        menu.customData = this.customData;
        const styles = ElementPositioning.positionOverlay(menu, ref, {
            horizontal: 'left',
            vertical: 'top',
            noOverlap: true,
            verticalOffset: -56,
            horizontalOffset: -4,
        });
        if (styles.top) {
            menu.style.top = styles.top;
        }
        if (styles.left) {
            menu.style.left = styles.left;
        }
        menu.parentCommand = id;
        menu.addEventListener('close', this.handleCloseNested.bind(this));
        menu.addEventListener('trigger', this.subTriggerHandler);
        this.shadowRoot?.append(menu);
        if (opts.keyboard) {
            menu.focus({ selectFirst: true });
        }
        else {
            menu.focus();
        }
    }
    handleCloseNested() {
        this.clearNested();
    }
    /**
     * Triggers the remove nested menu action.
     */
    removeNested() {
        if (this.removingTimeout) {
            return;
        }
        this.removingTimeout = setTimeout(() => {
            this.removingTimeout = undefined;
            this.clearNested();
        }, this.subMenuTimeout);
    }
    focus(options) {
        const { activeListItem } = this;
        if (activeListItem) {
            activeListItem.focus(options);
        }
        else if (options && options.selectFirst) {
            this.focusAfterUpdate();
        }
        else {
            super.focus(options);
        }
    }
    /**
     * Waits for the update to complete and focuses on the first item
     * or self when there are no items.
     */
    async focusAfterUpdate(options) {
        await this.updateComplete;
        if (this.items.length) {
            this.activeListItem = this.items[0];
            this.activateListItem(this.activeListItem);
        }
        else {
            super.focus(options);
        }
    }
    /**
     * Removes the nested menu from the DOM.
     */
    clearNested() {
        if (!this.nestedIdValue) {
            return;
        }
        this.nestedIdValue = undefined;
        const menu = this.shadowRoot?.querySelector('contextual-menu');
        if (menu) {
            this.shadowRoot?.removeChild(menu);
        }
        this.focus();
    }
    /**
     * Opens the menu when the current focused item has a sub menu.
     */
    moveRight() {
        const { activeListItem } = this;
        if (!activeListItem) {
            return;
        }
        const { nested, cmd } = activeListItem.dataset;
        if (nested === 'true') {
            this.clearNested();
            this.nestedIdValue = cmd;
            this.createNested({ keyboard: true });
        }
    }
    /**
     * Closes the menu when this is a sub menu.
     */
    moveLeft() {
        if (!this.parentCommand) {
            return;
        }
        this.close();
    }
    /**
     * A handler for the sub-menu `trigger` event.
     * Retargets the event to the parent.
     * It adds `parent` to the detail, when the parent (currently selected command) has
     * the execute function.
     */
    subTriggerHandler(e) {
        if (!e.detail.parent) {
            const id = this.nestedIdValue;
            const cmd = this.commands?.find((item) => item.id === id);
            if (!cmd) {
                return;
            }
            if (typeof cmd.execute === 'function') {
                e.detail.parent = cmd;
            }
        }
        this.dispatchEvent(new CustomEvent('trigger', {
            detail: e.detail,
        }));
    }
    handleKeydown(e) {
        e.stopPropagation();
        e.stopImmediatePropagation();
        super.handleKeydown(e);
        if (e.code === 'Escape' || e.code === 'Tab') {
            this.close();
        }
        else if (['Enter', 'Space'].includes(e.code)) {
            e.preventDefault();
            this.activateFromEvent(e);
        }
        else if (e.code === 'ArrowRight') {
            e.preventDefault();
            this.moveRight();
        }
        else if (e.code === 'ArrowLeft') {
            e.preventDefault();
            this.moveLeft();
        }
    }
    activateFromEvent(e) {
        const path = e.composedPath();
        const item = path.find(i => i.localName === 'ui-list-item');
        if (!item) {
            return;
        }
        this.activate(item.dataset.cmd);
    }
    activate(id) {
        const { commands } = this;
        if (!id || !commands) {
            return;
        }
        const index = commands.findIndex((item) => item.id === id);
        const cmd = commands[index];
        if (!cmd) {
            return;
        }
        if (cmd.hasChildren) {
            this.buildNested(id);
        }
        else {
            const detail = {
                command: cmd,
                item: index,
            };
            this.dispatchEvent(new CustomEvent('trigger', {
                detail,
            }));
        }
    }
    render() {
        const { commands } = this;
        if (!Array.isArray(commands) || !commands.length) {
            return html ``;
        }
        return html `
      ${commands.map((item) => this.renderMenuItem(item))}
    `;
    }
    renderMenuItem(item) {
        switch (item.type) {
            case 'normal': return this.renderMenuEntry(item);
            case 'separator': return this.renderSeparator(item);
            case 'label': return this.renderLabel(item);
            case 'radio': return this.renderRadioEntry(item);
            default: return '';
        }
    }
    renderSeparator(item) {
        const { store, target, workspace, customData } = this;
        if (!store || !target || !workspace) {
            return html ``;
        }
        const visible = item.isVisible(store, target, workspace, customData);
        const classes = {
            'menu-divider': true,
            hidden: !visible,
        };
        return html `
    <ui-divider type="inset" class="${classMap(classes)}" data-cmd="${item.id}"></ui-divider>
    `;
    }
    renderLabel(item) {
        const { store, target, workspace, customData } = this;
        if (!store || !target || !workspace) {
            return html ``;
        }
        const visible = item.isVisible(store, target, workspace, customData);
        const classes = {
            'menu-section-label': true,
            hidden: !visible,
        };
        return html `
    <div class="${classMap(classes)}" data-cmd="${item.id}">${item.label}</div>
    `;
    }
    renderMenuEntry(item) {
        const { store, target, workspace, customData } = this;
        if (!store || !target || !workspace) {
            return html ``;
        }
        item.beforeRenderCallback(store, target, workspace, customData);
        const visible = item.isVisible(store, target, workspace, customData);
        const enabled = item.isEnabled(store, target, workspace, customData);
        const classes = {
            item: true,
            disabled: !enabled,
            hidden: !visible,
        };
        const { title, icon, label, id, hasChildren } = item;
        return html `
    <ui-list-item 
      role="menuitem" 
      class="${classMap(classes)}" 
      ?disabled="${!visible || !enabled}"
      aria-hidden="${!visible}"
      title="${ifDefined(title)}"
      data-cmd="${id}"
      data-nested="${hasChildren}"
      aria-haspopup="${hasChildren}"
    >
      ${this.renderMenuEntryIcon(icon)}
      <span class="menu-label">${label}</span>
      ${hasChildren ? this.renderChildIcon() : ''}
    </ui-list-item>
    `;
    }
    renderRadioEntry(item) {
        const { store, target, workspace, customData } = this;
        if (!store || !target || !workspace) {
            return html ``;
        }
        item.beforeRenderCallback(store, target, workspace, customData);
        const visible = item.isVisible(store, target, workspace, customData);
        const enabled = item.isEnabled(store, target, workspace, customData);
        const checked = item.isChecked(store, target, workspace, customData);
        const classes = {
            item: true,
            disabled: !enabled,
            hidden: !visible,
        };
        const { title, label, id, hasChildren } = item;
        return html `
    <ui-list-item
      role="menuitem"
      class="${classMap(classes)}" 
      ?disabled="${!visible || !enabled}"
      aria-hidden="${!visible}"
      title="${ifDefined(title)}"
      data-cmd="${id}"
      data-nested="${hasChildren}"
      data-type="radio"
      aria-haspopup="${hasChildren}"
    >
      ${this.renderCheckMark(checked)}
      <span class="menu-label">${label}</span>
      ${hasChildren ? this.renderChildIcon() : ''}
    </ui-list-item>
    `;
    }
    /**
     * @param icon The icon is not required.
     */
    renderMenuEntryIcon(icon) {
        if (!icon) {
            return html ``;
        }
        return html `
    <div class="menu-icon" slot="start">${icon}</div>
    `;
    }
    /**
     * @return The template for sub-menu icon
     */
    renderChildIcon() {
        return html `
    <div class="sub-menu-icon" slot="end">${arrowRight}</div>
    `;
    }
    /**
     * @return The template for the menu entry checked mark.
     */
    renderCheckMark(checked) {
        if (!checked) {
            return html `<div class="menu-icon" data-state="unchecked" slot="start"></div>`;
        }
        return html `
      <div class="menu-icon" data-state="checked" slot="start">${check}</div>
    `;
    }
}
__decorate([
    queryAll('ui-list-item')
], ContextualMenuElement.prototype, "_items", void 0);
__decorate([
    property({ type: Array })
], ContextualMenuElement.prototype, "commands", void 0);
__decorate([
    property({ type: Object })
], ContextualMenuElement.prototype, "store", void 0);
__decorate([
    property({ type: Object })
], ContextualMenuElement.prototype, "target", void 0);
__decorate([
    property({ type: Object })
], ContextualMenuElement.prototype, "workspace", void 0);
__decorate([
    property()
], ContextualMenuElement.prototype, "customData", void 0);
__decorate([
    property({ type: Number })
], ContextualMenuElement.prototype, "subMenuTimeout", void 0);
__decorate([
    property({ type: String })
], ContextualMenuElement.prototype, "parentCommand", void 0);
//# sourceMappingURL=ContextualMenuElement.js.map