// (C) 2025 GoodData Corporation
/**
 * Recursively finds an item in the menu tree that matches the predicate.
 * @internal
 */
export const findItem = (items, predicate) => {
    for (const item of items) {
        if (predicate(item)) {
            return item;
        }
        if ((item.type === "interactive" || item.type === "group") && item.subItems !== undefined) {
            const foundItemInSubMenu = findItem(item.subItems, predicate);
            if (foundItemInSubMenu) {
                return foundItemInSubMenu;
            }
        }
    }
    return undefined;
};
/**
 * Gets a menu item by its ID.
 * @internal
 */
export const getItem = (items, itemId) => {
    return findItem(items, (item) => item.id === itemId);
};
/**
 * Finds an interactive menu item that matches the predicate.
 * @internal
 */
export const findInteractiveItem = (items, predicate) => {
    const foundItem = findItem(items, (item) => item.type === "interactive" && predicate(item));
    return foundItem?.type === "interactive" ? foundItem : undefined;
};
/**
 * Finds a content menu item that matches the predicate.
 * @internal
 */
export const findContentItem = (items, predicate) => {
    const foundItem = findItem(items, (item) => item.type === "content" && predicate(item));
    return foundItem?.type === "content" ? foundItem : undefined;
};
/**
 * Gets an interactive menu item by its ID.
 * @internal
 */
export const getInteractiveItem = (items, itemId) => {
    return findInteractiveItem(items, (item) => item.id === itemId);
};
/**
 * Gets a content menu item by its ID.
 * @internal
 */
export const getContentItem = (items, itemId) => {
    return findContentItem(items, (item) => item.id === itemId);
};
/**
 * Gets a focusable (either interactive or content) menu item by its ID.
 * @internal
 */
export const getFocusableItem = (items, itemId) => {
    return getContentItem(items, itemId) || getInteractiveItem(items, itemId);
};
/**
 * Gets all items under a specific interactive parent item.
 * @internal
 */
export const getItemsByInteractiveParent = (items, parentId) => {
    const isRootLevel = parentId === undefined;
    return isRootLevel ? items : findInteractiveItem(items, (item) => item.id === parentId)?.subItems;
};
/**
 * Gets the interactive parent of a menu item.
 * @internal
 */
export const getItemInteractiveParent = (items, itemId) => {
    const parent = findItem(items, (item) => {
        if (item.type === "static" ||
            (item.type === "interactive" && item.subItems === undefined) ||
            item.type === "content") {
            return false;
        }
        return item.subItems?.some((subMenuItem) => subMenuItem.id === itemId) ?? false;
    });
    if (parent?.type === "interactive") {
        return parent;
    }
    if (parent?.type === "group") {
        return getItemInteractiveParent(items, parent.id);
    }
    return undefined;
};
/**
 * Gets all sibling items of a menu item.
 * @internal
 */
export const getSiblingItems = (items, itemId) => {
    // If itemId is provided but the item doesn't exist, return undefined
    if (!getItem(items, itemId)) {
        return undefined;
    }
    return getItemsByInteractiveParent(items, getItemInteractiveParent(items, itemId)?.id);
};
/**
 * Gets all next sibling items of a menu item with wraparound.
 * @internal
 */
export const getNextSiblings = (items, itemId) => {
    const siblingItems = getSiblingItems(items, itemId);
    const itemIndex = siblingItems?.findIndex((item) => item.id === itemId) ?? -1;
    if (itemIndex === -1 || !siblingItems) {
        return [];
    }
    // Wraparound
    return [...siblingItems.slice(itemIndex + 1), ...siblingItems.slice(0, itemIndex)];
};
/**
 * Gets all previous sibling items of a menu item with wraparound.
 * @internal
 */
export const getPreviousSiblings = (items, itemId) => {
    const siblingItems = getSiblingItems(items, itemId);
    const itemIndex = siblingItems?.findIndex((item) => item.id === itemId) ?? -1;
    if (itemIndex === -1 || !siblingItems) {
        return [];
    }
    // Wraparound
    return [...siblingItems.slice(0, itemIndex).reverse(), ...siblingItems.slice(itemIndex + 1).reverse()];
};
/**
 * Gets the closest focusable sibling item in the specified direction.
 * @internal
 */
export const getClosestFocusableSibling = (args) => {
    const { items, isItemFocusable, itemId, direction } = args;
    const unwrappedItems = unwrapGroupItems(items);
    switch (direction) {
        case "forward":
            if (itemId === undefined) {
                return unwrappedItems.find(isItemFocusable);
            }
            return getNextSiblings(unwrappedItems, itemId).find(isItemFocusable);
        case "backward":
            if (itemId === undefined) {
                return [...unwrappedItems].reverse().find(isItemFocusable);
            }
            return getPreviousSiblings(unwrappedItems, itemId).find(isItemFocusable);
    }
};
/**
 * Unwraps items from group containers into a flat array.
 * @internal
 */
export function unwrapGroupItems(items) {
    const result = [];
    for (const item of items) {
        if (item.type === "group") {
            result.push(...unwrapGroupItems(item.subItems));
            continue;
        }
        if (item.type === "interactive" && item.subItems !== undefined) {
            result.push({ ...item, subItems: unwrapGroupItems(item.subItems) });
            continue;
        }
        result.push(item);
    }
    return result;
}
