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;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { property } from 'lit/decorators.js';
import { ROLE } from '../../utils/roles';
import { TAG_NAME as MENUSECTION_TAGNAME } from '../menusection/menusection.constants';
import { TAG_NAME as NAVMENUITEM_TAGNAME } from '../navmenuitem/navmenuitem.constants';
import { TAG_NAME as MENUITEM_TAGNAME } from '../menuitem/menuitem.constants';
import { TAG_NAME as MENUITEMCHECKBOX_TAGNAME } from '../menuitemcheckbox/menuitemcheckbox.constants';
import { TAG_NAME as MENUITEMRADIO_TAGNAME } from '../menuitemradio/menuitemradio.constants';
import Popover from '../popover/popover.component';
import { COLOR } from '../popover/popover.constants';
import { ACTIONS } from '../../utils/mixins/KeyToActionMixin';
import { DEFAULTS, TAG_NAME as MENU_POPOVER } from './menupopover.constants';
import styles from './menupopover.styles';
import { isValidMenuItem, isValidMenuPopover } from './menupopover.utils';
/**
 * A popover menu component that displays a list of menu items in a floating container.
 * It's designed to work in conjunction with `mdc-menubar` and `mdc-menuitem` to create
 * accessible, nested menu structures with the following features:
 * - Appears adjacent to the triggering menu item
 * - Supports keyboard navigation (arrow keys, Home, End)
 * - Manages focus trapping when open
 * - Closes on Escape key or outside click
 * - Supports both mouse and keyboard interactions
 * - Automatically handles ARIA attributes for accessibility
 *
 * The component extends `mdc-popover` and adds menu-specific behaviors and styling.
 * When nested within another `mdc-menupopover`, it automatically adjusts its behavior
 * to work as a submenu (right-aligned, shows on hover).
 *
 * The orientation of the menu popover is always set to `vertical`.
 *
 * Submenu opens when:
 * - Clicked on a menu item with a submenu
 * - Enter or Space key pressed on a menu item with a submenu
 *
 * Menu closes completely (with all sub menus) when:
 * - A menu item is clicked that does not have a submenu
 * - Enter key pressed on a menu item (not a submenu trigger), menu item radio or menu item checkbox
 * - Click outside the menu popover (on the backdrop)
 *
 * Close submenus when:
 * - Esc key pressed, only the current submenu closed
 * - Arrow Left key pressed, only the current submenu closed
 * - Open another submenu with Click, Enter or Space key,
 *   closes recursively all submenus until the selected item's submenu
 *
 * Menu does not close when:
 * - Space key pressed on a menu item radio or menu item checkbox
 *
 * @tagname mdc-menupopover
 *
 * @slot - Default slot for the menu popover content
 *
 * @event change - (React: onChange) This event is dispatched when a `menuitemcheckbox`, or `menuitemradio` changes.
 * @event action - (React: onAction) This event is dispatched when a menuItem selected and the menu closes.
 *
 * @slot default - Contains the menu items to be displayed in the popover
 *
 * @csspart popover-close - The close button of the menupopover.
 * @csspart popover-content - The content of the menupopover.
 * @csspart popover-hover-bridge - The hover bridge of the menupopover.
 */
class MenuPopover extends Popover {
    constructor() {
        super();
        /**
         * The placement of the popover.
         * - **top**
         * - **top-start**
         * - **top-end**
         * - **bottom**
         * - **bottom-start**
         * - **bottom-end**
         * - **left**
         * - **left-start**
         * - **left-end**
         * - **right**
         * - **right-start**
         * - **right-end**
         * @default bottom
         */
        this.placement = DEFAULTS.PLACEMENT;
        this.menuItems = [];
        this.menuItemsWithSubMenus = [];
        /**
         * Handles outside click events to close the popover.
         * This method checks if the click occurred outside the popover and its trigger element.
         * If so, it closes the popover by calling `closeAllMenuPopovers`.
         * It also checks if the click was on the backdrop element (if present) to close the popover.
         * @param event - The mouse event that triggered the outside click.
         */
        this.onOutsidePopoverClick = (event) => {
            if (!this.depthManager.isHostOnTop())
                return;
            const path = event.composedPath();
            const insidePopoverClick = this.contains(event.target) || path.includes(this.triggerElement) || path.includes(this);
            const clickedOnBackdrop = this.backdropElement ? path.includes(this.backdropElement) : false;
            if (!insidePopoverClick || clickedOnBackdrop) {
                this.closeAllMenuPopovers();
                if (clickedOnBackdrop) {
                    event.stopPropagation();
                }
            }
        };
        /**
         * Toggles the visibility of the popover.
         * This method checks if the trigger element has the `soft-disabled` attribute.
         * If it does, the popover will not be toggled.
         * If the popover is currently visible, it hides the popover; otherwise, it shows the popover.
         * @returns - This method does not return anything.
         */
        this.togglePopoverVisible = (event) => {
            var _a;
            if ((_a = this.triggerElement) === null || _a === void 0 ? void 0 : _a.hasAttribute('soft-disabled'))
                return;
            // Handle mouse click in the parent menupopover to hide open sibling submenus
            if (event.composedPath().find(el => el.tagName === this.tagName) === this) {
                this.handleMouseClick(event);
            }
            // Toggle visibility of the current menupopover
            if (this.isEventFromTrigger(event)) {
                if (this.visible) {
                    this.hide();
                }
                else {
                    this.show();
                }
            }
        };
        this.handleItemCreation = (event) => {
            const item = event.target;
            if (isValidMenuItem(item)) {
                // Parent menu popover should not listen on nested menu items
                event.stopImmediatePropagation();
                // `destroyed` triggered in the `disconnectedCallback` of the item which executed when the item is removed from the DOM
                // because of that the event does not bubble up to menupopover, and we need to capture the `destroyed` event on the item itself
                item.addEventListener('destroyed', this.handleItemChangeEvent);
                // `disabled` could bubble up, but it is more consistent to handle it the same way as `destroyed`
                item.addEventListener('disabled', this.handleItemChangeEvent);
            }
        };
        this.handleItemChangeEvent = (event) => {
            event.stopImmediatePropagation();
            if (event.target && event.type === 'destroyed') {
                event.target.removeEventListener('destroyed', this.handleItemChangeEvent);
                event.target.removeEventListener('disabled', this.handleItemChangeEvent);
            }
            this.resetMenuNavigation();
        };
        this.resetMenuNavigation = () => {
            // Re-collect menu items to ensure the list is up-to-date
            this.collectMenuItems();
            // Reset tab indexes to ensure at least one menu item is focusable
            const focusableMenuItem = this.menuItems.find(item => item.getAttribute('tabindex') === '0');
            if (!focusableMenuItem) {
                this.resetTabIndexes(0);
            }
        };
        /**
         * Handles keydown events for keyboard navigation within the menu popover.
         * This method allows users to navigate through the menu items using the keyboard.
         * It supports Home, End, Arrow Up, Arrow Down, Arrow Left, Arrow Right, and Escape keys.
         *
         * Also, it handles Enter key to close the menu popover
         *
         * @param event - The keyboard event that triggered the keydown action.
         * @returns - This method does not return anything.
         */
        this.handleKeyDown = (event) => {
            let isKeyHandled = false;
            this.collectMenuItems();
            const target = event.target;
            const currentIndex = this.getCurrentIndex(target);
            if (currentIndex === -1)
                return;
            this.resetTabIndexes(currentIndex);
            const action = this.getActionForKeyEvent(event, true);
            switch (action) {
                case ACTIONS.HOME: {
                    // Move focus to the first menu item
                    this.resetTabIndexAndSetFocus(0, currentIndex);
                    isKeyHandled = true;
                    break;
                }
                case ACTIONS.END: {
                    // Move focus to the last menu item
                    this.resetTabIndexAndSetFocus(this.menuItems.length - 1, currentIndex);
                    isKeyHandled = true;
                    break;
                }
                case ACTIONS.DOWN: {
                    // Move focus to the next menu item
                    const newIndex = currentIndex + 1 === this.menuItems.length ? 0 : currentIndex + 1;
                    this.resetTabIndexAndSetFocus(newIndex, currentIndex);
                    isKeyHandled = true;
                    break;
                }
                case ACTIONS.UP: {
                    // Move focus to the prev menu item
                    const newIndex = currentIndex - 1 === -1 ? this.menuItems.length - 1 : currentIndex - 1;
                    this.resetTabIndexAndSetFocus(newIndex, currentIndex);
                    isKeyHandled = true;
                    break;
                }
                case ACTIONS.RIGHT: {
                    // If there is a submenu, open it.
                    const subMenu = this.getSubMenuPopoverOfTarget(target);
                    if (subMenu) {
                        subMenu.show();
                        isKeyHandled = true;
                    }
                    break;
                }
                case ACTIONS.LEFT: {
                    // If the current popover is a submenu then close this popover.
                    if (isValidMenuPopover(this.parentElement)) {
                        this.hide();
                        this.resetTabIndexAndSetFocus(0, currentIndex);
                        isKeyHandled = true;
                    }
                    break;
                }
                case ACTIONS.ESCAPE: {
                    if (this.depthManager.isHostOnTop()) {
                        this.resetTabIndexAndSetFocus(0, currentIndex);
                        isKeyHandled = true;
                    }
                    break;
                }
                case ACTIONS.ENTER: {
                    if (!this.getSubMenuPopoverOfTarget(target) && !target.hasAttribute('soft-disabled')) {
                        this.closeAllMenuPopovers();
                        this.fireMenuItemAction(target);
                        isKeyHandled = true;
                    }
                    break;
                }
                case ACTIONS.SPACE: {
                    // Prevent page scroll when space is pressed down
                    isKeyHandled = true;
                    break;
                }
                default:
                    break;
            }
            // When menu consume any of the pressed key, we need to stop propagation
            // to prevent the event from bubbling up and being handled by parent components which might use the same key.
            if (isKeyHandled) {
                this.keyDownEventHandled();
                event.stopPropagation();
                event.preventDefault();
            }
        };
        /**
         * Handles keyup events for keyboard navigation within the menu popover.
         *
         * Some keys must be handled with keyup event to prevent default action.
         *
         * Space key closes the menu when the user presses it on a menu item,
         * but the same key will trigger a click on the menu opener button.
         * The button uses the keyup event so we have to handle it here as well
         * to prevent the menu opener action which would re-open the menu.
         *
         * @param event - The keyboard event that triggered the keydown action.
         * @returns - This method does not return anything.
         */
        this.handleKeyUp = (event) => {
            let isKeyHandled = false;
            const target = event.target;
            switch (this.getActionForKeyEvent(event)) {
                case ACTIONS.SPACE: {
                    // If the target is a menu item, trigger its click event
                    if (!target.matches(`${MENUITEMRADIO_TAGNAME}, ${MENUITEMCHECKBOX_TAGNAME}`) &&
                        !target.hasAttribute('soft-disabled')) {
                        // only close all menu popovers if the target is not opening a menu popover
                        if (!this.getSubMenuPopoverOfTarget(target)) {
                            this.closeAllMenuPopovers();
                            this.fireMenuItemAction(target);
                            isKeyHandled = true;
                        }
                    }
                    break;
                }
                default:
                    break;
            }
            // When menu consume any of the pressed key, we need to stop propagation
            // to prevent the event from bubbling up and being handled by parent components which might use the same key.
            if (isKeyHandled) {
                event.stopPropagation();
                event.preventDefault();
            }
        };
        this.addEventListener('keydown', this.handleKeyDown);
        this.addEventListener('keyup', this.handleKeyUp);
        this.addEventListener('created', this.handleItemCreation);
    }
    /**
     * Retrieves the submenu popover associated with a given target element.
     * This method checks if the target element has an `id` attribute and uses it to find the corresponding submenu popover.
     * If a submenu popover is found, it returns it; otherwise, it returns `undefined` or `null`.
     * @param target - The target element for which to find the submenu popover.
     * @returns - The submenu popover instance or `undefined`/`null` if not found.
     * @internal
     */
    getSubMenuPopoverOfTarget(target) {
        var _a, _b;
        const id = target.getAttribute('id');
        if (!id)
            return null;
        return (_b = (_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector) === null || _b === void 0 ? void 0 : _b.call(_a, `${MENU_POPOVER}[triggerid="${id}"]`);
    }
    /** @internal */
    collectMenuItems() {
        var _a;
        const slot = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot');
        const allAssignedElements = ((slot === null || slot === void 0 ? void 0 : slot.assignedElements({ flatten: true })) || []);
        this.menuItems = allAssignedElements
            .map(node => {
            if (node.tagName.toLowerCase() === MENUSECTION_TAGNAME) {
                return Array.from(node.children).filter(child => isValidMenuItem(child));
            }
            return isValidMenuItem(node) ? node : [];
        })
            .flat()
            .filter(node => !!node && !node.hasAttribute('disabled'));
        this.menuItemsWithSubMenus = this.menuItems.filter(item => {
            const submenu = this.getSubMenuPopoverOfTarget(item);
            return submenu;
        });
    }
    /** @internal */
    getOpenSubMenusOfItems(items) {
        const subMenus = [];
        if (!items || items.length === 0)
            return subMenus;
        items.forEach(item => {
            const submenu = this.getSubMenuPopoverOfTarget(item);
            if (submenu && submenu.visible) {
                subMenus.push(submenu);
            }
        });
        return subMenus;
    }
    connectedCallback() {
        super.connectedCallback();
        this.role = ROLE.MENU;
        this.ariaOrientation = DEFAULTS.ORIENTATION;
        this.backdrop = false;
        this.color = COLOR.TONAL;
        this.disableAriaExpanded = false;
        this.focusBackToTrigger = true;
        this.focusTrap = true;
        this.hideOnEscape = true;
        this.hideOnOutsideClick = true;
        this.interactive = true;
        this.showArrow = false;
        this.closeButton = false;
        this.closeButtonAriaLabel = null;
    }
    async isOpenUpdated(oldValue, newValue) {
        if (oldValue === newValue || !this.triggerElement)
            return Promise.resolve();
        // Set backdrop to true when the popover is opened with other than menu item.
        //
        // make sure backdrop is set before showing the popover, but it does not change when popover is closing, otherwise
        // `super.isOpenUpdated` will skip the backdrop cleanup
        if (newValue) {
            this.backdrop = ![MENUITEM_TAGNAME, NAVMENUITEM_TAGNAME].includes(this.triggerElement.tagName.toLowerCase());
        }
        // if the current menupopover is closed, close all submenus
        if (newValue === false) {
            this.menuItemsWithSubMenus.forEach(item => {
                const submenu = this.getSubMenuPopoverOfTarget(item);
                if (submenu) {
                    submenu.hide();
                }
            });
        }
        this.resetMenuNavigation();
        return super.isOpenUpdated(oldValue, newValue);
    }
    async firstUpdated(changedProperties) {
        await super.firstUpdated(changedProperties);
        this.collectMenuItems();
        this.resetTabIndexes(0);
    }
    /**
     * Reset all tabindex to -1 and set the tabindex of the current menu item to 0
     *
     * @param currentIndex - The index of the currently focused menu item.
     */
    resetTabIndexes(currentIndex) {
        if (this.menuItems.length > 0) {
            this.menuItems.forEach(menuitem => menuitem.setAttribute('tabindex', '-1'));
            this.menuItems[currentIndex].setAttribute('tabindex', '0');
            this.menuItems[currentIndex].focus({ preventScroll: true });
            this.menuItems[currentIndex].scrollIntoView({ block: 'nearest' });
        }
    }
    /**
     * Retrieves the current index of the menu item that triggered the event.
     * @param target - The target element that triggered the event.
     * @returns - The index of the current menu item in the `menuItems` array.
     */
    getCurrentIndex(target) {
        return this.menuItems.findIndex(node => node === target);
    }
    /**
     * Resets the tabindex of the currently focused menu item and sets focus to a new menu item.
     * @param newIndex - The index of the new menu item to focus.
     * @param oldIndex - The index of the currently focused menu item.
     * @returns - This method does not return anything.
     */
    resetTabIndexAndSetFocus(newIndex, oldIndex) {
        if (newIndex === oldIndex)
            return;
        this.menuItems[oldIndex].setAttribute('tabindex', '-1');
        this.menuItems[newIndex].setAttribute('tabindex', '0');
        this.menuItems[newIndex].focus({ preventScroll: true });
        this.menuItems[newIndex].scrollIntoView({ block: 'nearest' });
    }
    /**
     * Closes all menu popovers in the stack.
     * This method is used to ensure that when a menu item is clicked,
     * all other open popovers are closed, maintaining a clean user interface.
     * It iterates through the overlay stack and hides each popover until the stack is empty.
     *
     * @param until - The popover to close until.
     */
    closeAllMenuPopovers(until) {
        this.depthManager.popUntil(item => item !== until && isValidMenuPopover(item));
    }
    /**
     * Closes all other submenus on the same level as the target menu item.
     * This method is used to ensure that only one submenu is open at a time.
     * It finds all other menu items with submenus and closes their submenus.
     * @param target - The target menu item that was clicked.
     */
    closeOtherSubMenusOnSameLevel(target) {
        const otherMenuItemsOnSameLevel = this.menuItemsWithSubMenus.filter(item => item !== target);
        const otherOpenSubMenus = this.getOpenSubMenusOfItems(otherMenuItemsOnSameLevel);
        otherOpenSubMenus.forEach(subMenu => {
            subMenu.hide();
        });
    }
    /**
     * Fires the 'action' event on the target menu item.
     * This method is used to trigger the action associated with a menu item when it is clicked or activated.
     * It checks if the target element matches the `MENUITEM_TAGNAME` and dispatches an 'action' event.
     * @param target - The target menu item that was clicked or activated.
     */
    fireMenuItemAction(target) {
        if (target.matches(MENUITEM_TAGNAME)) {
            target.dispatchEvent(new Event('action', { bubbles: true, composed: true }));
        }
    }
    /**
     * Handles mouse click events on the menu items.
     * This method checks if the clicked element is a valid menu item and not a submenu trigger.
     * If it is, it closes all other menu popovers to ensure only one menu is open at a time.
     * @param event - The mouse event that triggered the click.
     */
    handleMouseClick(event) {
        const target = event.target;
        // if the target is not a valid menu item or if the event is not trusted (
        // e.g., triggered by keydown originally), do nothing. Pressing space and enter
        // is handled separately in the respective handler.
        if (!isValidMenuItem(target) || !event.isTrusted || target.hasAttribute('soft-disabled'))
            return;
        // If the target has a submenu, show it and close other submenus on the same level
        if (this.getSubMenuPopoverOfTarget(target)) {
            this.closeOtherSubMenusOnSameLevel(target);
            return;
        }
        this.closeAllMenuPopovers();
        this.fireMenuItemAction(target);
    }
}
MenuPopover.styles = [...Popover.styles, ...styles];
__decorate([
    property({ type: String, reflect: true }),
    __metadata("design:type", String)
], MenuPopover.prototype, "placement", void 0);
export default MenuPopover;
