import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { forwardRef, createContext, useMemo, useContext, useCallback, useState, useEffect, useRef } from 'react';
import Progress from '../Progress';
import { useConsolidatedRef, useDirection, useFocusWithin, useI18n, useOS, useUID } from '../../hooks';
import Flex from '../Flex';
import Link from '../Link';
import Button from '../Button';
import Actions from '../Actions';
import { cap } from '../../utils';
import VisuallyHiddenText from '../VisuallyHiddenText';
import { StyledStandardTreeParent, StyledToggleIcon, StyledToggleIconWrapper, StyledStandardTreeItemSubTree, StyledStandardTreeLeaf, StyledStandardTree, StyledNodeInteraction, StyledStandardTreeNode, StyledNodeActions, StyledLabelContent, StyledNodeIcon, StyledNodeText, StyledLoadMoreButton, StyledSecondaryText, StyledLabelContainer } from './StandardTree.styles';
import helpers from './helpers';
const StandardTreeContext = createContext({
    currentNodeId: undefined,
    lined: false,
    contentRenderer: () => undefined,
    onNodeClick: () => { },
    onNodeToggle: () => { },
    firstNodeId: undefined,
    lastNodeId: undefined,
    focusedNodeId: undefined,
    changeFocusedNodeId: () => { },
    selectableParents: true
});
const NodeRenderer = ({ id, label, secondary, icon, depth, hasParentSibling, nodes, expanded = false, loading = false, subTree, actions, onClick, onPreview, onLoadMore, href }) => {
    const { currentNodeId, contentRenderer, onNodeClick, onNodeToggle, onNodeHover, lined, focusedNodeId, changeFocusedNodeId, firstNodeId, lastNodeId, selectableParents } = useContext(StandardTreeContext);
    const current = currentNodeId === id;
    const focusedEl = focusedNodeId === id;
    const firstNode = firstNodeId === id;
    const lastNode = lastNodeId === id;
    const { start, end } = useDirection();
    const { macintosh } = useOS();
    const loadMoreRef = useRef(null);
    const t = useI18n();
    const ariaCurrent = useMemo(() => {
        return href ? 'page' : true;
    }, [href]);
    const tabIndex = useMemo(() => {
        let index = -1;
        if (focusedNodeId) {
            index = focusedEl ? 0 : -1;
        }
        else if (currentNodeId && current) {
            index = 0;
        }
        else if (currentNodeId && nodes && helpers.getNode(nodes, currentNodeId)) {
            index = current ? 0 : -1;
        }
        else {
            index = firstNode ? 0 : -1;
        }
        return index;
    }, [focusedNodeId, currentNodeId, current, focusedEl]);
    const handleParentClick = useCallback((e) => {
        onNodeClick?.(id, e);
        changeFocusedNodeId(id);
    }, [id, onNodeClick, changeFocusedNodeId]);
    const handleParentToggle = useCallback((e) => {
        e.preventDefault();
        e.stopPropagation();
        onNodeToggle?.(id, e);
        changeFocusedNodeId(id);
    }, [id, onNodeToggle, changeFocusedNodeId]);
    const onKeyDown = useCallback((e) => {
        if (['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key))
            e.preventDefault();
        if (e.key === 'Enter' && !href)
            e.preventDefault();
        if (href && onPreview && e.getModifierState('Alt') && e.code === 'KeyP') {
            onPreview({ href });
        }
        switch (e.key) {
            case 'ArrowUp':
                changeFocusedNodeId(id, 'up');
                break;
            case 'ArrowDown':
                changeFocusedNodeId(id, 'down');
                break;
            case `Arrow${cap(end)}`:
                if (nodes) {
                    if (expanded) {
                        changeFocusedNodeId(id, 'right');
                    }
                    else {
                        onNodeToggle?.(id);
                    }
                }
                break;
            case `Arrow${cap(start)}`:
                if (expanded) {
                    onNodeToggle?.(id);
                }
                else {
                    changeFocusedNodeId(id, 'left');
                }
                break;
            case 'Enter':
            case ' ':
                onLoadMore?.(id);
                onClick?.(id, e);
                if (nodes) {
                    if (selectableParents) {
                        handleParentClick(e);
                    }
                    else {
                        handleParentToggle(e);
                    }
                }
                else {
                    onNodeClick?.(id, e);
                    changeFocusedNodeId(id);
                }
                break;
            case 'Home':
                if (firstNodeId)
                    changeFocusedNodeId(firstNodeId);
                break;
            case 'End':
                if (lastNodeId)
                    changeFocusedNodeId(lastNodeId);
                break;
            default:
        }
    }, [id, end, nodes, expanded, actions, firstNodeId, lastNodeId, changeFocusedNodeId]);
    const actionsJSX = useMemo(() => {
        // since Actions may render a frag of buttons and we need a wrapper for flex space-between.
        return actions ? (_jsx(Flex, { as: StyledNodeActions, container: true, children: _jsx(Actions, { items: actions.map(action => {
                    return {
                        ...action,
                        compact: true,
                        onKeyDown: (e) => {
                            if (e.key === 'Enter')
                                e.stopPropagation();
                        }
                    };
                }), menuAt: 4, menuButtonProps: {
                    onClick: (e) => {
                        e.stopPropagation();
                        e.preventDefault();
                    },
                    onKeyDown: (e) => {
                        e.stopPropagation();
                        if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
                            e.preventDefault();
                        }
                    }
                } }) })) : null;
    }, [actions]);
    const elRef = useRef(null);
    useEffect(() => {
        if (focusedEl) {
            elRef?.current?.focus();
        }
    }, [focusedEl]);
    const loadMoreButton = onLoadMore ? (_jsx(Flex, { container: { alignItems: 'center' }, children: _jsx(Button, { as: StyledLoadMoreButton, variant: 'link', "aria-label": label, onClick: (e) => {
                onLoadMore?.(id);
                onClick?.(id, e);
            }, ref: loadMoreRef, children: t('load_more') }) })) : undefined;
    const labelContent = useMemo(() => {
        if (contentRenderer) {
            const content = contentRenderer({
                id,
                label,
                secondary,
                icon,
                nodes,
                expanded,
                loading,
                actions,
                onClick,
                onLoadMore,
                href
            });
            return !nodes && (onClick ?? onNodeClick) ? (_jsx(Flex, { container: { alignItems: 'center', justify: 'between', gap: 2 }, as: StyledNodeInteraction, forwardedAs: href ? 'a' : 'div', role: 'treeitem', "aria-selected": false, "aria-current": current ? ariaCurrent : undefined, tabIndex: tabIndex, onClick: (e) => {
                    onClick?.(id, e);
                    onNodeClick?.(id, e);
                    changeFocusedNodeId(id);
                }, href: href, onKeyDown: onKeyDown, ref: elRef, children: content })) : (content);
        }
        const internal = href ? (_jsxs(Flex, { container: { alignItems: 'center', gap: 0.5 }, as: StyledLabelContent, children: [icon && _jsx(StyledNodeIcon, { name: icon }), _jsxs(Flex, { container: { direction: 'column', pad: [0, 0.5, 0, 0.5] }, children: [_jsx(Link, { forwardedAs: 'span', href: href, previewable: !!onPreview, onPreview: onPreview, children: label }), secondary && _jsx(StyledSecondaryText, { variant: 'secondary', children: secondary }), _jsx(VisuallyHiddenText, { children: t('preview_link_instruction', [macintosh ? 'option' : 'alt']) })] })] })) : (
        // Wrapping in a div for handling of flex space-between when there are node actions.
        _jsxs(Flex, { container: { alignItems: 'center', gap: 0.5 }, as: StyledLabelContent, children: [icon && _jsx(StyledNodeIcon, { name: icon }), _jsxs(Flex, { container: { direction: 'column', pad: [0, 0, 0, 0.5] }, children: [_jsx(StyledNodeText, { children: label }), secondary && _jsx(StyledSecondaryText, { variant: 'secondary', children: secondary })] })] }));
        return !nodes && (onClick ?? onNodeClick) ? (_jsxs(Flex, { container: { alignItems: 'center', justify: 'between', gap: 2 }, as: StyledNodeInteraction, forwardedAs: href ? 'a' : 'div', role: 'treeitem', "aria-selected": false, "aria-current": current ? ariaCurrent : undefined, tabIndex: tabIndex, onClick: (e) => {
                onClick?.(id, e);
                onNodeClick?.(id, e);
                changeFocusedNodeId(id);
            }, href: href, onKeyDown: onKeyDown, ref: elRef, children: [loadMoreButton ?? internal, actionsJSX] })) : (internal);
    }, [
        id,
        label,
        icon,
        nodes,
        actions,
        onClick,
        onNodeClick,
        current,
        focusedEl,
        tabIndex,
        changeFocusedNodeId,
        onKeyDown
    ]);
    return (_jsx(StyledStandardTreeNode, { id: id, style: {
            '--depth': depth,
            '--has-parent': depth ? 1 : 0,
            '--has-parent-sibling': hasParentSibling ? 1 : 0,
            '--first-visible': firstNode ? 1 : 0,
            '--last-visible': lastNode ? 1 : 0
        }, "aria-busy": loading, children: nodes ? (_jsxs(_Fragment, { children: [_jsxs(Flex, { container: { alignItems: 'center', justify: 'between', gap: 2 }, as: StyledStandardTreeParent, forwardedAs: href ? 'a' : 'div', role: 'treeitem', "aria-selected": false, "aria-current": current ? ariaCurrent : undefined, tabIndex: tabIndex, variant: 'text', onClick: selectableParents ? handleParentClick : handleParentToggle, onMouseOver: onNodeHover ? (e) => onNodeHover(id, e) : undefined, href: selectableParents ? href : undefined, "aria-expanded": expanded, onKeyDown: onKeyDown, ref: elRef, children: [_jsxs(Flex, { container: { alignItems: 'center' }, as: StyledLabelContainer, children: [selectableParents ? (_jsx(Button, { as: 'span', variant: 'simple', tabIndex: -1, icon: true, compact: true, onClick: handleParentToggle, children: _jsx(StyledToggleIcon, { name: 'caret-right' }) })) : (_jsx(Flex, { as: StyledToggleIconWrapper, container: { justify: 'center', alignItems: 'center' }, children: _jsx(StyledToggleIcon, { name: 'caret-right' }) })), labelContent] }), actionsJSX] }), _jsxs(StyledStandardTreeItemSubTree, { lined: lined, id: `${id}-subtree`, children: [subTree, loading && _jsx(Progress, { variant: 'ring', placement: 'inline' })] })] })) : (_jsx(StyledStandardTreeLeaf, { onMouseOver: onNodeHover ? (e) => onNodeHover(id, e) : undefined, children: labelContent })) }));
};
const StandardTreeWithNodes = forwardRef(function StandardTreeWithNodes({ nodes: initialNodes, currentNodeId, onNodeClick, onNodeFocus, onNodeHover, onNodeToggle, onLoadMore, contentRenderer, lined = false, selectableParents = true, ...restProps }, ref) {
    const loadMoreId = useUID();
    const t = useI18n();
    const nodes = [
        ...helpers.mapTree(initialNodes, node => {
            if (node.onLoadMore && !node.loading) {
                return {
                    ...node,
                    onLoadMore: undefined,
                    nodes: [
                        ...(node.nodes ?? []),
                        {
                            id: `${loadMoreId}-${node.id}`,
                            label: `${t('load_more')} - ${node.label}`,
                            onLoadMore: () => {
                                node.onLoadMore?.(node.id);
                            }
                        }
                    ]
                };
            }
            return node;
        }),
        ...(onLoadMore
            ? [
                {
                    id: loadMoreId,
                    label: `${t('load_more')}`,
                    onLoadMore
                }
            ]
            : [])
    ];
    const [focusedNodeId, setFocusedNodedId] = useState();
    const treeRef = useConsolidatedRef(ref);
    const lastNodeId = useMemo(() => {
        return helpers.getDeepestNode(nodes, nodes[nodes.length - 1].id)?.id;
    }, [nodes]);
    const changeFocusedNodeId = useCallback((id, type) => {
        switch (type) {
            case 'up': {
                const previousNode = helpers.getPreviousNode(nodes, id);
                if (previousNode)
                    setFocusedNodedId(previousNode.id);
                break;
            }
            case 'down': {
                const nextNode = helpers.getNextNode(nodes, id);
                if (nextNode)
                    setFocusedNodedId(nextNode.id);
                break;
            }
            case 'left': {
                const parentNode = helpers.getParentNode(nodes, id);
                if (parentNode)
                    setFocusedNodedId(parentNode.id);
                break;
            }
            case 'right': {
                const childNode = helpers.getFirstChildNode(nodes, id);
                if (childNode)
                    setFocusedNodedId(childNode.id);
                break;
            }
            default: {
                if (id !== focusedNodeId)
                    setFocusedNodedId(id);
                break;
            }
        }
    }, [nodes]);
    useEffect(() => {
        if (focusedNodeId)
            onNodeFocus?.(focusedNodeId);
    }, [focusedNodeId]);
    const onFocusChange = (focused) => {
        if (!focused)
            setFocusedNodedId('');
    };
    useFocusWithin([treeRef], onFocusChange);
    return (_jsx(StandardTreeContext.Provider, { value: useMemo(() => ({
            currentNodeId,
            focusedNodeId,
            lastNodeId,
            firstNodeId: nodes[0].id,
            changeFocusedNodeId,
            onNodeClick,
            onNodeHover,
            onNodeToggle,
            contentRenderer,
            lined,
            selectableParents
        }), [
            currentNodeId,
            onNodeClick,
            onNodeHover,
            lined,
            focusedNodeId,
            lastNodeId,
            onNodeToggle,
            contentRenderer,
            selectableParents
        ]), children: _jsx(StyledStandardTree, { ...restProps, ref: treeRef, nodes: nodes, nodeRenderer: NodeRenderer }) }));
});
const StandardTree = forwardRef(function StandardTree(props, ref) {
    return props.nodes.length > 0 ? _jsx(StandardTreeWithNodes, { ...props, ref: ref }) : null;
});
export default StandardTree;
//# sourceMappingURL=StandardTree.js.map