import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property, queryAssignedElements, } from 'lit/decorators.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { getOverflowAncestors } from '@floating-ui/utils/dom';
import { $ } from '@mdui/jq/$.js';
import '@mdui/jq/methods/height.js';
import '@mdui/jq/methods/is.js';
import '@mdui/jq/methods/width.js';
import { isFunction } from '@mdui/jq/shared/helper.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { DefinedController } from '@mdui/shared/controllers/defined.js';
import { watch } from '@mdui/shared/decorators/watch.js';
import { animateTo, stopAnimations } from '@mdui/shared/helpers/animate.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { getDuration, getEasing } from '@mdui/shared/helpers/motion.js';
import { observeResize } from '@mdui/shared/helpers/observeResize.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { style } from './style.js';
/**
 * @summary 下拉组件
 *
 * ```html
 * <mdui-dropdown>
 * ..<mdui-button slot="trigger">open dropdown</mdui-button>
 * ..<mdui-menu>
 * ....<mdui-menu-item>Item 1</mdui-menu-item>
 * ....<mdui-menu-item>Item 2</mdui-menu-item>
 * ..</mdui-menu>
 * </mdui-dropdown>
 * ```
 *
 * @event open - 下拉组件开始打开时，事件被触发。可以通过调用 `event.preventDefault()` 阻止下拉组件打开
 * @event opened - 下拉组件打开动画完成时，事件被触发
 * @event close - 下拉组件开始关闭时，事件被触发。可以通过调用 `event.preventDefault()` 阻止下拉组件关闭
 * @event closed - 下拉组件关闭动画完成时，事件被触发
 *
 * @slot - 下拉组件的内容
 * @slot trigger - 触发下拉组件的元素，例如 [`<mdui-button>`](/docs/2/components/button) 元素
 *
 * @csspart trigger - 触发下拉组件的元素的容器，即 `trigger` slot 的容器
 * @csspart panel - 下拉组件内容的容器
 *
 * @cssprop --z-index - 组件的 CSS `z-index` 值
 */
let Dropdown = class Dropdown extends MduiElement {
    constructor() {
        super();
        /**
         * 是否打开下拉组件
         */
        this.open = false;
        /**
         * 是否禁用下拉组件
         */
        this.disabled = false;
        /**
         * 下拉组件的触发方式，支持多个值，用空格分隔。可选值包括：
         *
         * * `click`：点击触发
         * * `hover`：鼠标悬浮触发
         * * `focus`：聚焦触发
         * * `contextmenu`：鼠标右键点击、或触摸长按触发
         * * `manual`：仅能通过编程方式打开和关闭下拉组件，不能再指定其他触发方式
         */
        this.trigger = 'click';
        /**
         * 下拉组件内容的位置。可选值包括：
         *
         * * `auto`：自动判断位置
         * * `top-start`：上方左对齐
         * * `top`：上方居中
         * * `top-end`：上方右对齐
         * * `bottom-start`：下方左对齐
         * * `bottom`：下方居中
         * * `bottom-end`：下方右对齐
         * * `left-start`：左侧顶部对齐
         * * `left`：左侧居中
         * * `left-end`：左侧底部对齐
         * * `right-start`：右侧顶部对齐
         * * `right`：右侧居中
         * * `right-end`：右侧底部对齐
         */
        this.placement = 'auto';
        /**
         * 点击 [`<mdui-menu-item>`](/docs/2/components/menu#menu-item-api) 后，下拉组件是否保持打开状态
         */
        this.stayOpenOnClick = false;
        /**
         * 鼠标悬浮触发下拉组件打开的延时，单位为毫秒
         */
        this.openDelay = 150;
        /**
         * 鼠标悬浮触发下拉组件关闭的延时，单位为毫秒
         */
        this.closeDelay = 150;
        /**
         * 是否在触发下拉组件的光标位置打开下拉组件，常用于打开鼠标右键菜单
         */
        this.openOnPointer = false;
        this.panelRef = createRef();
        this.definedController = new DefinedController(this, {
            relatedElements: [''],
        });
        this.onDocumentClick = this.onDocumentClick.bind(this);
        this.onDocumentKeydown = this.onDocumentKeydown.bind(this);
        this.onWindowScroll = this.onWindowScroll.bind(this);
        this.onMouseLeave = this.onMouseLeave.bind(this);
        this.onFocus = this.onFocus.bind(this);
        this.onClick = this.onClick.bind(this);
        this.onContextMenu = this.onContextMenu.bind(this);
        this.onMouseEnter = this.onMouseEnter.bind(this);
        this.onPanelClick = this.onPanelClick.bind(this);
    }
    get triggerElement() {
        return this.triggerElements[0];
    }
    // 这些属性变更时，需要更新样式
    async onPositionChange() {
        // 如果是打开状态，则更新 panel 的位置
        if (this.open) {
            await this.definedController.whenDefined();
            this.updatePositioner();
        }
    }
    async onOpenChange() {
        const hasUpdated = this.hasUpdated;
        // 默认为关闭状态。因此首次渲染时，且为关闭状态，不执行
        if (!this.open && !hasUpdated) {
            return;
        }
        await this.definedController.whenDefined();
        if (!hasUpdated) {
            await this.updateComplete;
        }
        const easingLinear = getEasing(this, 'linear');
        const easingEmphasizedDecelerate = getEasing(this, 'emphasized-decelerate');
        const easingEmphasizedAccelerate = getEasing(this, 'emphasized-accelerate');
        // 打开
        // 要区分是否首次渲染，首次渲染时不触发事件，不执行动画；非首次渲染，触发事件，执行动画
        if (this.open) {
            if (hasUpdated) {
                const eventProceeded = this.emit('open', { cancelable: true });
                if (!eventProceeded) {
                    return;
                }
            }
            // dropdown 打开时，尝试把焦点放到 panel 中
            const focusablePanel = this.panelElements.find((panel) => isFunction(panel.focus));
            setTimeout(() => {
                focusablePanel?.focus();
            });
            const duration = getDuration(this, 'medium4');
            await stopAnimations(this.panelRef.value);
            this.panelRef.value.hidden = false;
            this.updatePositioner();
            await Promise.all([
                animateTo(this.panelRef.value, [
                    { transform: `${this.getCssScaleName()}(0.45)` },
                    { transform: `${this.getCssScaleName()}(1)` },
                ], {
                    duration: hasUpdated ? duration : 0,
                    easing: easingEmphasizedDecelerate,
                }),
                animateTo(this.panelRef.value, [{ opacity: 0 }, { opacity: 1, offset: 0.125 }, { opacity: 1 }], {
                    duration: hasUpdated ? duration : 0,
                    easing: easingLinear,
                }),
            ]);
            if (hasUpdated) {
                this.emit('opened');
            }
        }
        else {
            const eventProceeded = this.emit('close', { cancelable: true });
            if (!eventProceeded) {
                return;
            }
            // dropdown 关闭时，如果不支持 focus 触发，且焦点在 dropdown 内，则焦点回到 trigger 上
            if (!this.hasTrigger('focus') &&
                isFunction(this.triggerElement?.focus) &&
                (this.contains(document.activeElement) ||
                    this.contains(document.activeElement?.assignedSlot ?? null))) {
                this.triggerElement.focus();
            }
            const duration = getDuration(this, 'short4');
            await stopAnimations(this.panelRef.value);
            await Promise.all([
                animateTo(this.panelRef.value, [
                    { transform: `${this.getCssScaleName()}(1)` },
                    { transform: `${this.getCssScaleName()}(0.45)` },
                ], { duration, easing: easingEmphasizedAccelerate }),
                animateTo(this.panelRef.value, [{ opacity: 1 }, { opacity: 1, offset: 0.875 }, { opacity: 0 }], { duration, easing: easingLinear }),
            ]);
            // 可能关闭 dropdown 时该元素已经不存在了（比如页面直接跳转了）
            if (this.panelRef.value) {
                this.panelRef.value.hidden = true;
            }
            this.emit('closed');
        }
    }
    connectedCallback() {
        super.connectedCallback();
        this.definedController.whenDefined().then(() => {
            document.addEventListener('pointerdown', this.onDocumentClick);
            document.addEventListener('keydown', this.onDocumentKeydown);
            this.overflowAncestors = getOverflowAncestors(this.triggerElement);
            this.overflowAncestors.forEach((ancestor) => {
                ancestor.addEventListener('scroll', this.onWindowScroll);
            });
            // triggerElement 的尺寸变化时，重新调整 panel 的位置
            this.observeResize = observeResize(this.triggerElement, () => {
                this.updatePositioner();
            });
        });
    }
    disconnectedCallback() {
        // 移除组件时，如果关闭动画正在进行中，则会导致关闭动画无法执行完成，最终组件无法隐藏
        // 具体场景为 vue 的 <keep-alive> 中切换走，再切换回来时，面板仍然打开着
        if (!this.open && this.panelRef.value) {
            this.panelRef.value.hidden = true;
        }
        super.disconnectedCallback();
        document.removeEventListener('pointerdown', this.onDocumentClick);
        document.removeEventListener('keydown', this.onDocumentKeydown);
        this.overflowAncestors?.forEach((ancestor) => {
            ancestor.removeEventListener('scroll', this.onWindowScroll);
        });
        this.observeResize?.unobserve();
    }
    firstUpdated(changedProperties) {
        super.firstUpdated(changedProperties);
        this.addEventListener('mouseleave', this.onMouseLeave);
        this.definedController.whenDefined().then(() => {
            this.triggerElement.addEventListener('focus', this.onFocus);
            this.triggerElement.addEventListener('click', this.onClick);
            this.triggerElement.addEventListener('contextmenu', this.onContextMenu);
            this.triggerElement.addEventListener('mouseenter', this.onMouseEnter);
        });
    }
    render() {
        return html `<slot name="trigger" part="trigger" class="trigger"></slot><slot ${ref(this.panelRef)} part="panel" class="panel" hidden @click="${this.onPanelClick}"></slot>`;
    }
    /**
     * 获取 dropdown 打开、关闭动画的 CSS scaleX 或 scaleY
     */
    getCssScaleName() {
        return this.animateDirection === 'horizontal' ? 'scaleX' : 'scaleY';
    }
    /**
     * 在 document 上点击时，根据条件判断是否要关闭 dropdown
     */
    onDocumentClick(e) {
        if (this.disabled || !this.open) {
            return;
        }
        const path = e.composedPath();
        // 点击 dropdown 外部区域，直接关闭
        if (!path.includes(this)) {
            this.open = false;
        }
        // 当包含 contextmenu 且不包含 click 时，点击 trigger，关闭
        if (this.hasTrigger('contextmenu') &&
            !this.hasTrigger('click') &&
            path.includes(this.triggerElement)) {
            this.open = false;
        }
    }
    /**
     * 在 document 上按下按键时，根据条件判断是否要关闭 dropdown
     */
    onDocumentKeydown(event) {
        if (this.disabled || !this.open) {
            return;
        }
        // 按下 ESC 键时，关闭 dropdown
        if (event.key === 'Escape') {
            this.open = false;
            return;
        }
        // 按下 Tab 键时，关闭 dropdown
        if (event.key === 'Tab') {
            // 如果不支持 focus 触发，则焦点回到 trigger 上（这个会在 onOpenChange 中执行 ）这里只需阻止默认的 Tab 行为
            if (!this.hasTrigger('focus') && isFunction(this.triggerElement?.focus)) {
                event.preventDefault();
            }
            this.open = false;
        }
    }
    onWindowScroll() {
        window.requestAnimationFrame(() => this.onPositionChange());
    }
    hasTrigger(trigger) {
        const triggers = this.trigger.split(' ');
        return triggers.includes(trigger);
    }
    onFocus() {
        if (this.disabled || this.open || !this.hasTrigger('focus')) {
            return;
        }
        this.open = true;
    }
    onClick(e) {
        // e.button 为 0 时，为鼠标左键点击。忽略鼠标中间和右键
        if (this.disabled || e.button || !this.hasTrigger('click')) {
            return;
        }
        // 支持 hover 或 focus 触发时，点击时，不关闭 dropdown
        if (this.open && (this.hasTrigger('hover') || this.hasTrigger('focus'))) {
            return;
        }
        this.pointerOffsetX = e.offsetX;
        this.pointerOffsetY = e.offsetY;
        this.open = !this.open;
    }
    onPanelClick(e) {
        if (!this.disabled &&
            !this.stayOpenOnClick &&
            $(e.target).is('mdui-menu-item')) {
            this.open = false;
        }
    }
    onContextMenu(e) {
        if (this.disabled || !this.hasTrigger('contextmenu')) {
            return;
        }
        e.preventDefault();
        this.pointerOffsetX = e.offsetX;
        this.pointerOffsetY = e.offsetY;
        this.open = true;
    }
    onMouseEnter() {
        // 不做 open 状态的判断，因为可以延时打开和关闭
        if (this.disabled || !this.hasTrigger('hover')) {
            return;
        }
        window.clearTimeout(this.closeTimeout);
        if (this.openDelay) {
            this.openTimeout = window.setTimeout(() => {
                this.open = true;
            }, this.openDelay);
        }
        else {
            this.open = true;
        }
    }
    onMouseLeave() {
        // 不做 open 状态的判断，因为可以延时打开和关闭
        if (this.disabled || !this.hasTrigger('hover')) {
            return;
        }
        window.clearTimeout(this.openTimeout);
        this.closeTimeout = window.setTimeout(() => {
            this.open = false;
        }, this.closeDelay || 50);
    }
    // 更新 panel 的位置
    updatePositioner() {
        const $panel = $(this.panelRef.value);
        const $window = $(window);
        const panelElements = this.panelElements;
        const panelRect = {
            width: Math.max(...(panelElements?.map((panel) => panel.offsetWidth) ?? [])),
            height: panelElements
                ?.map((panel) => panel.offsetHeight)
                .reduce((total, height) => total + height, 0),
        };
        // 在光标位置触发时，假设 triggerElement 的宽高为 0，位置位于光标位置
        const triggerClientRect = this.triggerElement.getBoundingClientRect();
        const triggerRect = this.openOnPointer
            ? {
                top: this.pointerOffsetY + triggerClientRect.top,
                left: this.pointerOffsetX + triggerClientRect.left,
                width: 0,
                height: 0,
            }
            : triggerClientRect;
        // dropdown 与屏幕边界至少保留 8px 间距
        const screenMargin = 8;
        let transformOriginX;
        let transformOriginY;
        let top;
        let left;
        let placement = this.placement;
        // 自动判断 dropdown 的方位
        // 优先级为 bottom>top>right>left，start>center>end
        if (placement === 'auto') {
            const windowWidth = $window.width();
            const windowHeight = $window.height();
            let position;
            let alignment;
            if (windowHeight - triggerRect.top - triggerRect.height >
                panelRect.height + screenMargin) {
                // 下方放得下，放下方
                position = 'bottom';
            }
            else if (triggerRect.top > panelRect.height + screenMargin) {
                // 上方放得下，放上方
                position = 'top';
            }
            else if (windowWidth - triggerRect.left - triggerRect.width >
                panelRect.width + screenMargin) {
                // 右侧放得下，放右侧
                position = 'right';
            }
            else if (triggerRect.left > panelRect.width + screenMargin) {
                // 左侧放得下，放左侧
                position = 'left';
            }
            else {
                // 默认放下方
                position = 'bottom';
            }
            if (['top', 'bottom'].includes(position)) {
                if (windowWidth - triggerRect.left > panelRect.width + screenMargin) {
                    // 左对齐放得下，左对齐
                    alignment = 'start';
                }
                else if (triggerRect.left + triggerRect.width / 2 >
                    panelRect.width / 2 + screenMargin &&
                    windowWidth - triggerRect.left - triggerRect.width / 2 >
                        panelRect.width / 2 + screenMargin) {
                    // 居中对齐放得下，居中对齐
                    alignment = undefined;
                }
                else if (triggerRect.left + triggerRect.width >
                    panelRect.width + screenMargin) {
                    // 右对齐放得下，右对齐
                    alignment = 'end';
                }
                else {
                    // 默认左对齐
                    alignment = 'start';
                }
            }
            else {
                if (windowHeight - triggerRect.top > panelRect.height + screenMargin) {
                    // 顶部对齐放得下，顶部对齐
                    alignment = 'start';
                }
                else if (triggerRect.top + triggerRect.height / 2 >
                    panelRect.height / 2 + screenMargin &&
                    windowHeight - triggerRect.top - triggerRect.height / 2 >
                        panelRect.height / 2 + screenMargin) {
                    // 居中对齐放得下，居中对齐
                    alignment = undefined;
                }
                else if (triggerRect.top + triggerRect.height >
                    panelRect.height + screenMargin) {
                    // 底部对齐放得下，底部对齐
                    alignment = 'end';
                }
                else {
                    // 默认顶部对齐
                    alignment = 'start';
                }
            }
            placement = alignment
                ? [position, alignment].join('-')
                : position;
        }
        // 根据 placement 计算 panel 的位置和方向
        const [position, alignment] = placement.split('-');
        this.animateDirection = ['top', 'bottom'].includes(position)
            ? 'vertical'
            : 'horizontal';
        switch (position) {
            case 'top':
                transformOriginY = 'bottom';
                top = triggerRect.top - panelRect.height;
                break;
            case 'bottom':
                transformOriginY = 'top';
                top = triggerRect.top + triggerRect.height;
                break;
            default:
                transformOriginY = 'center';
                switch (alignment) {
                    case 'start':
                        top = triggerRect.top;
                        break;
                    case 'end':
                        top = triggerRect.top + triggerRect.height - panelRect.height;
                        break;
                    default:
                        top =
                            triggerRect.top + triggerRect.height / 2 - panelRect.height / 2;
                        break;
                }
                break;
        }
        switch (position) {
            case 'left':
                transformOriginX = 'right';
                left = triggerRect.left - panelRect.width;
                break;
            case 'right':
                transformOriginX = 'left';
                left = triggerRect.left + triggerRect.width;
                break;
            default:
                transformOriginX = 'center';
                switch (alignment) {
                    case 'start':
                        left = triggerRect.left;
                        break;
                    case 'end':
                        left = triggerRect.left + triggerRect.width - panelRect.width;
                        break;
                    default:
                        left =
                            triggerRect.left + triggerRect.width / 2 - panelRect.width / 2;
                        break;
                }
                break;
        }
        $panel.css({
            top,
            left,
            transformOrigin: [transformOriginX, transformOriginY].join(' '),
        });
    }
};
Dropdown.styles = [componentStyle, style];
__decorate([
    property({
        type: Boolean,
        reflect: true,
        converter: booleanConverter,
    })
], Dropdown.prototype, "open", void 0);
__decorate([
    property({
        type: Boolean,
        reflect: true,
        converter: booleanConverter,
    })
], Dropdown.prototype, "disabled", void 0);
__decorate([
    property({ reflect: true })
], Dropdown.prototype, "trigger", void 0);
__decorate([
    property({ reflect: true })
], Dropdown.prototype, "placement", void 0);
__decorate([
    property({
        type: Boolean,
        reflect: true,
        converter: booleanConverter,
        attribute: 'stay-open-on-click',
    })
], Dropdown.prototype, "stayOpenOnClick", void 0);
__decorate([
    property({ type: Number, reflect: true, attribute: 'open-delay' })
], Dropdown.prototype, "openDelay", void 0);
__decorate([
    property({ type: Number, reflect: true, attribute: 'close-delay' })
], Dropdown.prototype, "closeDelay", void 0);
__decorate([
    property({
        type: Boolean,
        reflect: true,
        converter: booleanConverter,
        attribute: 'open-on-pointer',
    })
], Dropdown.prototype, "openOnPointer", void 0);
__decorate([
    queryAssignedElements({ slot: 'trigger', flatten: true })
], Dropdown.prototype, "triggerElements", void 0);
__decorate([
    queryAssignedElements({ flatten: true })
], Dropdown.prototype, "panelElements", void 0);
__decorate([
    watch('placement', true),
    watch('openOnPointer', true)
], Dropdown.prototype, "onPositionChange", null);
__decorate([
    watch('open')
], Dropdown.prototype, "onOpenChange", null);
Dropdown = __decorate([
    customElement('mdui-dropdown')
], Dropdown);
export { Dropdown };
