/**
 * Keyboard navigation allows the end user to navigate through menus using
 * arrow keys and to select items using the enter key.
 *
 * When an element is selected then the keyboard navigation state is reset
 * so that the user will begin again with the first item.
 *
 * @param childWin - Main window or child window reference.
 * @param [className] - Selector of elements to apply navigation on.
 * @example
 * applyMenuKeyboardNavigation(window, ".menu-item");
 */
export const applyMenuKeyboardNavigation = (childWin, className = ".menu-item") => {
    let selectedItem = 0;
    let selectedIcon = -1;
    new window.MutationObserver(() => {
        // Reset selectedIndex when elements added or removed
        // from the DOM. The motivation behind this was an issue
        // with Brad's search menu where the order of focus is
        // messed up when his list of result changes.
        selectedItem = 0;
    }).observe(childWin.document.body, {
        childList: true,
        characterData: false,
        subtree: true,
    });
    /**
     * Returns list of elements
     */
    const getElements = () => childWin.document.querySelectorAll(className);
    const getMenuItemIcons = (index) => {
        const menuItems = getElements();
        return menuItems[index].querySelectorAll(".menu-item-icon");
    };
    /**
     * Sets the focus on a menu item
     * @param index The index of the menu item in the list
     */
    const focusOn = (index) => {
        const menuItems = getElements();
        // Menu could have no items
        if (!menuItems.length)
            return;
        // Focus on element and scroll if needed
        menuItems[index].focus();
        // Removes the highlighting for previously selected item
        const hoverItem = childWin.document.querySelector(".menu-item-hover");
        hoverItem === null || hoverItem === void 0 ? void 0 : hoverItem.classList.remove("menu-item-hover");
    };
    const focusOnFirst = () => {
        selectedItem = 0;
        selectedIcon = -1;
        focusOn(0);
    };
    const focusOnLast = () => {
        selectedIcon = -1;
        const { length } = getElements();
        if (length) {
            selectedItem = length - 1;
            focusOn(length - 1);
        }
    };
    /**
     * Sets the focus on a menu item's icon
     * @param index The index of the menu item in the list
     * @param iconIndex The index of the menu item's icon (-1 = menu text, not icon)
     */
    const focusOnIcon = (index, iconIndex) => {
        const menuItems = getElements();
        // Menu could have no items
        if (!menuItems.length)
            return;
        const icons = getMenuItemIcons(index);
        // Defend against possible js runtime error
        if (icons.length <= iconIndex) {
            return;
        }
        // Focus on menu item text, if relevant
        if (iconIndex === -1) {
            menuItems[index].focus();
            return;
        }
        icons[iconIndex].focus();
    };
    /**
     * Simulates mouse events
     */
    const clickOnItem = () => {
        const elements = getElements();
        const selectedItemElement = elements[selectedItem];
        ["mousedown", "click", "mouseup"].forEach((event) => {
            if (selectedIcon > -1) {
                const icons = getMenuItemIcons(selectedItem);
                const selectedIconElement = icons[selectedIcon];
                if (selectedIconElement) {
                    selectedIconElement.dispatchEvent(new MouseEvent(event, {
                        view: childWin,
                        bubbles: true,
                        cancelable: true,
                        buttons: 1,
                    }));
                    return;
                }
            }
            if (selectedItemElement && selectedItemElement.dispatchEvent) {
                selectedItemElement.dispatchEvent(new MouseEvent(event, {
                    view: childWin,
                    bubbles: true,
                    cancelable: true,
                    buttons: 1,
                }));
            }
        });
    };
    /**
     * Invoked when a user presses on the arrow up key
     */
    const onArrowUp = () => {
        selectedIcon = -1;
        // Set the focus on the previous item
        selectedItem--;
        if (selectedItem < 0) {
            // If we are back to the begining
            // set the focus on last item
            focusOnLast();
            return;
        }
        focusOn(selectedItem);
    };
    /**
     * Invoked when user press the arrow down key
     */
    const onArrowDown = () => {
        selectedIcon = -1;
        // The the focus on the next item
        selectedItem++;
        // If we reached the end, then go back to first item
        if (selectedItem === getElements().length) {
            selectedItem = 0;
        }
        focusOn(selectedItem);
    };
    /**
     * Invoked when user press the arrow right key
     */
    const onArrowRight = () => {
        const menuItems = getElements();
        // Menu could have no items
        if (!menuItems.length)
            return;
        const icons = getMenuItemIcons(selectedItem);
        if (icons.length === 0) {
            return;
        }
        selectedIcon++;
        if (selectedIcon === icons.length) {
            selectedIcon = -1;
        }
        focusOnIcon(selectedItem, selectedIcon);
    };
    /**
     * Invoked when user press the arrow left key
     */
    const onArrowLeft = () => {
        const menuItems = getElements();
        // Menu could have no items
        if (!menuItems.length)
            return;
        const icons = getMenuItemIcons(selectedItem);
        if (icons.length === 0) {
            return;
        }
        selectedIcon--;
        if (selectedIcon === icons.length) {
            selectedIcon = -1;
        }
        focusOnIcon(selectedItem, selectedIcon);
    };
    const actionDictionary = {
        ArrowDown: onArrowDown,
        ArrowUp: onArrowUp,
        ArrowLeft: onArrowLeft,
        ArrowRight: onArrowRight,
        Enter: clickOnItem,
        Home: focusOnFirst,
        End: focusOnLast,
    };
    childWin.addEventListener("keydown", (e) => {
        var _a;
        const { key } = e;
        if (key in actionDictionary) {
            (_a = actionDictionary[key]) === null || _a === void 0 ? void 0 : _a.call(actionDictionary);
        }
    });
    childWin.addEventListener("blur", focusOnFirst);
    childWin.addEventListener("focus", focusOnFirst);
};
//# sourceMappingURL=keyboardNavigation.js.map