import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { forwardRef, useCallback, useEffect, useState, useRef, useMemo } from 'react';
import { useConsolidatedRef, useUID, useActiveDescendant, useLazyDescendant, useI18n, usePrevious, useDirection, useTestIds } from '../../hooks';
import { cap, withTestIds } from '../../utils';
import VisuallyHiddenText from '../VisuallyHiddenText';
import Button from '../Button/Button';
import Icon from '../Icon/Icon';
import FlyoutMenuList from './FlyoutMenuList';
import MenuContext from './Menu.context';
import { StyledMenu, StyledMenuListWrapper } from './Menu.styles';
import { getMenuTestIds } from './Menu.test-ids';
import MenuList from './MenuList';
const Menu = forwardRef(function Menu(props, ref) {
    const uid = useUID();
    const { testId, id = uid, items = [], itemLayout = 'stacked', onCreateNew, mode = 'action', accent, scrollAt = 7, emptyText, onItemClick, onItemActive, onItemExpand, loadMore, onItemCollapse, loading = false, currentItemId, header, footer, variant = 'drill-down', focusControlEl, arrowNavigationUnsupported, 'aria-describedby': ariaDescribedBy, 'aria-label': label, role = 'menu', listId, pauseDescendantEvaluation, menuList: customMenuList, ...restProps } = props;
    const testIds = useTestIds(testId, getMenuTestIds);
    const t = useI18n();
    const { end: endDirection, start: startDirection } = useDirection();
    const radioName = useUID();
    const menuRef = useConsolidatedRef(ref);
    const previousItemCount = useRef(0);
    const [activeDescendantUpdateId, setActiveDescendantUpdateId] = useState(0);
    const [focusDescendant, setFocusDescendant] = useState(null);
    const [flyOutActiveIdStack, setFlyoutActiveIdStack] = useState([id]);
    const [focusReturnEl, setFocusReturnEl] = useState();
    const [preventInitialScroll, setPreventInitialScroll] = useState(false);
    const [flyoutFocusReturnStack, setFlyoutFocusReturnStack] = useState([]);
    const menuAsFocusControl = !focusControlEl;
    const activeFlyoutSelector = useMemo(() => `fieldset[data-flyout-menu-id="${flyOutActiveIdStack[flyOutActiveIdStack.length - 1]}"]`, [flyOutActiveIdStack]);
    const focusControl = useMemo(() => {
        return focusControlEl || menuRef.current;
    }, [focusControlEl, menuRef.current]);
    const pushFlyoutId = useCallback((flyoutId) => {
        setFlyoutActiveIdStack([...flyOutActiveIdStack, flyoutId]);
    }, [flyOutActiveIdStack]);
    useEffect(() => {
        setFlyoutActiveIdStack([...flyOutActiveIdStack, id]);
    }, [id]);
    const updateParentDescendantStack = useCallback((item) => {
        setFlyoutFocusReturnStack([...flyoutFocusReturnStack, item]);
    }, [flyoutFocusReturnStack]);
    const getScopedItemId = useCallback((itemId) => {
        return btoa(encodeURIComponent(itemId));
    }, [id]);
    const getUnscopedItemId = useCallback((itemId) => {
        return decodeURIComponent(atob(itemId));
    }, [id]);
    const updateActiveDescendants = useCallback(({ preventScroll = false } = {}) => {
        setPreventInitialScroll(preventScroll);
        setActiveDescendantUpdateId(Math.random());
    }, []);
    const uadConfig = useMemo(() => ({
        focusEl: focusControl,
        scope: menuRef.current,
        scopeSelector: variant === 'drill-down'
            ? 'fieldset[aria-hidden="false"]'
            : activeFlyoutSelector,
        selector: `[role="${role === 'menu' ? 'menuitem' : 'option'}"], legend`,
        focusDescendantEl: focusDescendant,
        clearFocusDescendant: () => {
            setFocusDescendant(null);
        },
        focusReturnEl,
        clearFocusReturn: () => {
            setFocusReturnEl(null);
        },
        currentDescendantId: currentItemId ? getScopedItemId(currentItemId) : undefined,
        preventInitialScroll,
        clearPreventScroll: () => {
            setPreventInitialScroll(false);
        },
        pauseDescendantEvaluation
    }), [
        focusControl,
        menuRef.current,
        activeFlyoutSelector,
        focusReturnEl,
        focusDescendant,
        currentItemId,
        preventInitialScroll,
        pauseDescendantEvaluation
    ]);
    const { activeDescendant, descendants } = useActiveDescendant(uadConfig, [
        activeDescendantUpdateId
    ]);
    const previousActiveDescendant = usePrevious(activeDescendant);
    useLazyDescendant({
        loading,
        descendants,
        previousActiveDescendant,
        activeDescendant,
        focusReturnEl,
        setFocusReturnEl,
        scrollEl: uadConfig.scope?.querySelector(uadConfig.scopeSelector)?.querySelector('ul')
    });
    // ## Bind Menu-specific navigation keyDown.
    useEffect(() => {
        const expandOrCollapse = (action = null) => {
            if (activeDescendant) {
                // expand
                if (activeDescendant.dataset.expand === 'true' && action !== 'collapse') {
                    activeDescendant.querySelector('span[aria-hidden="true"]')?.click();
                    return;
                }
                // collapse
                if (action !== 'expand') {
                    if (variant === 'flyout' &&
                        flyOutActiveIdStack.length > 1 &&
                        flyoutFocusReturnStack.length > 0) {
                        setFlyoutActiveIdStack([...flyOutActiveIdStack].slice(0, -1));
                        const parentDescendant = flyoutFocusReturnStack.pop();
                        if (parentDescendant !== undefined)
                            setFocusReturnEl(parentDescendant);
                        updateActiveDescendants();
                    }
                    else if (activeDescendant.dataset.collapse === 'true')
                        activeDescendant.click();
                }
            }
        };
        const additionalKeydown = (e) => {
            switch (e.key) {
                case `Arrow${cap(endDirection)}`: {
                    if (arrowNavigationUnsupported)
                        break;
                    expandOrCollapse('expand');
                    break;
                }
                case `Arrow${cap(startDirection)}`: {
                    if (arrowNavigationUnsupported)
                        break;
                    expandOrCollapse('collapse');
                    break;
                }
                case 'Escape': {
                    if (variant === 'flyout' &&
                        flyOutActiveIdStack.length > 1 &&
                        flyoutFocusReturnStack.length > 0) {
                        e.preventDefault();
                        e.stopPropagation();
                        setFlyoutActiveIdStack([...flyOutActiveIdStack].slice(0, -1));
                        const parentDescendant = flyoutFocusReturnStack.pop();
                        if (parentDescendant !== undefined)
                            setFocusReturnEl(parentDescendant);
                        updateActiveDescendants();
                    }
                    break;
                }
                default:
                    break;
            }
            if (arrowNavigationUnsupported && (e.key === ' ' || e.key === 'Spacebar') && e.shiftKey) {
                e.preventDefault();
                expandOrCollapse();
            }
        };
        if (activeDescendant)
            onItemActive?.(getUnscopedItemId(activeDescendant.id));
        focusControl?.addEventListener('keydown', additionalKeydown);
        return () => focusControl?.removeEventListener('keydown', additionalKeydown);
    }, [focusControl, activeDescendant, flyOutActiveIdStack]);
    // ## Update useActiveDescendant on change of items, not selection of items.
    useEffect(() => {
        // Next tick for DOM updates.
        const timeoutId = setTimeout(() => {
            if (items.length === previousItemCount.current) {
                const previousDescendantIds = descendants?.map(node => node.id);
                const newDescendants = uadConfig.scope
                    ?.querySelector(uadConfig.scopeSelector)
                    ?.querySelectorAll(uadConfig.selector);
                if (!newDescendants) {
                    updateActiveDescendants();
                    return;
                }
                const scopedDescendants = Array.from(newDescendants);
                if (scopedDescendants?.length !== previousDescendantIds?.length) {
                    updateActiveDescendants();
                    return;
                }
                const itemsUnchanged = scopedDescendants.every((node, index) => node.id === previousDescendantIds[index]);
                if (!itemsUnchanged) {
                    updateActiveDescendants();
                }
                return;
            }
            previousItemCount.current = items.length;
            updateActiveDescendants();
        }, 0);
        return () => clearTimeout(timeoutId);
    }, [items]);
    const contextValue = useMemo(() => {
        return {
            componentId: id,
            'aria-label': label,
            mode,
            arrowNavigationUnsupported,
            onItemClick: (clickedId, event) => {
                if (menuAsFocusControl) {
                    menuRef.current?.focus();
                }
                onItemClick?.(clickedId, event);
            },
            onItemActive,
            onItemExpand,
            onItemCollapse,
            itemLayout,
            accent,
            scrollAt,
            emptyText,
            radioName,
            loadMore,
            loading,
            variant,
            focusControl,
            updateActiveDescendants,
            setFocusReturnEl,
            setFocusDescendant,
            getScopedItemId,
            pushFlyoutId,
            flyOutActiveIdStack,
            updateParentDescendantStack
        };
    }, [
        id,
        label,
        mode,
        arrowNavigationUnsupported,
        onItemClick,
        onItemActive,
        onItemExpand,
        onItemCollapse,
        itemLayout,
        accent,
        scrollAt,
        emptyText,
        radioName,
        loadMore,
        loading,
        variant,
        focusControl,
        updateActiveDescendants,
        setFocusReturnEl,
        setFocusDescendant,
        getScopedItemId,
        pushFlyoutId,
        flyOutActiveIdStack,
        updateParentDescendantStack
    ]);
    const menuList = customMenuList ??
        (variant === 'drill-down' ? (_jsx(MenuList, { items: items, id: listId, menuRole: role, "aria-multiselectable": mode === 'multi-select', "aria-labelledby": label ? `${id}-menuLabel` : undefined })) : (_jsx(FlyoutMenuList, { items: items, menuRole: role })));
    return (_jsxs(StyledMenu, { "data-testid": testIds.root, id: id, "aria-describedby": menuAsFocusControl ? `${id}-menuDescription` : undefined, ...restProps, ref: menuRef, tabIndex: menuAsFocusControl ? 0 : undefined, children: [menuAsFocusControl && (_jsx("span", { id: `${id}-menuDescription`, hidden: true, children: (`${t('menu_selection_instructions')} ` && ariaDescribedBy) || '' })), label && _jsx(VisuallyHiddenText, { id: `${id}-menuLabel`, children: label }), header && _jsx("header", { "data-popover-scroll-el": true, children: header }), _jsx(StyledMenuListWrapper, { "data-popover-scroll-el": true, children: _jsx(MenuContext.Provider, { value: contextValue, children: menuList }) }), (onCreateNew || footer) && (_jsx("footer", { "data-popover-scroll-el": true, children: onCreateNew ? (_jsxs(Button, { "data-testid": testIds.createNew, variant: 'link', onClick: onCreateNew, children: [_jsx(Icon, { name: 'plus' }), " ", t('create_new')] })) : (footer) }))] }));
});
export default withTestIds(Menu, getMenuTestIds);
//# sourceMappingURL=Menu.js.map