import { useEffect, useState, useCallback, useRef } from 'react';
import { createUID, focusableSelector, getActiveElement } from '../utils';
import useDirection from './useDirection';
const interactiveNodeNames = ['button', 'a', 'input', 'li', 'legend'];
const useActiveDescendant = ({ focusEl, scope, scopeSelector, selector, orientation = 'vertical', focusDescendantEl, clearFocusDescendant, focusReturnEl, clearFocusReturn, currentDescendantId, onClick, preventInitialScroll, pauseDescendantEvaluation = false, clearPreventScroll }, dependencyArray = []) => {
    const [resetId, setResetId] = useState(0);
    const previousActiveId = useRef('');
    const paused = useRef(pauseDescendantEvaluation);
    const [focusDescendantElIndex, setFocusDescendantElIndex] = useState(null);
    const [currentIndex, setCurrentIndex] = useState(null);
    const [descendants, setDescendants] = useState();
    const { rtl } = useDirection();
    const clearDescendants = useCallback(() => {
        descendants?.forEach(node => {
            node.setAttribute('data-current', 'false');
        });
    }, [descendants]);
    const clearThenSetDescendants = useCallback((setVal) => {
        clearDescendants();
        setDescendants(setVal
            ? Array.from(setVal).filter((item) => item instanceof HTMLElement)
            : null);
    }, [descendants]);
    const setIdsAndOwns = useCallback(({ clear } = { clear: false }) => {
        const ownedIds = [];
        if (descendants && descendants.length) {
            descendants.forEach(node => {
                node.id = node.id || createUID();
                ownedIds.push(node.id);
            });
        }
        if (clear) {
            const currentOwnedIds = focusEl?.getAttribute('aria-owns')?.split(' ');
            const clearedIds = currentOwnedIds?.filter(id => !ownedIds.includes(id));
            focusEl?.setAttribute('aria-owns', clearedIds?.join(' ') || '');
        }
        else {
            focusEl?.setAttribute('aria-owns', ownedIds.join(' '));
        }
    }, [focusEl, descendants]);
    const updateDescendants = useCallback(() => {
        if (pauseDescendantEvaluation)
            return;
        let hasScope = scope;
        if (hasScope && hasScope instanceof HTMLElement) {
            if (scopeSelector) {
                hasScope = hasScope.querySelector(scopeSelector);
            }
            if (!hasScope) {
                clearThenSetDescendants(null);
                return;
            }
            if (selector) {
                const setVal = hasScope.querySelectorAll(selector);
                clearThenSetDescendants(setVal);
            }
            else {
                const setVal = hasScope.querySelectorAll(focusableSelector);
                clearThenSetDescendants(setVal);
            }
        }
        else {
            clearThenSetDescendants(null);
        }
        if (currentIndex === null) {
            setCurrentIndex(0);
        }
    }, [scope, scopeSelector, selector, pauseDescendantEvaluation, currentIndex]);
    // Update paused ref
    useEffect(() => {
        paused.current = pauseDescendantEvaluation;
    }, [pauseDescendantEvaluation]);
    // Toggle active scope data attr.
    useEffect(() => {
        if (!scope || !focusEl)
            return;
        scope.setAttribute('data-active-scope', getActiveElement() === focusEl ? 'true' : 'false');
        const onFocus = () => {
            scope.setAttribute('data-active-scope', 'true');
        };
        const onBlur = () => {
            scope.setAttribute('data-active-scope', 'false');
        };
        focusEl.addEventListener('focus', onFocus);
        focusEl.addEventListener('blur', onBlur);
        return () => {
            focusEl.removeEventListener('focus', onFocus);
            focusEl.removeEventListener('blur', onBlur);
        };
    }, [scope, focusEl]);
    // Update descendants when un-paused
    useEffect(() => {
        if (!pauseDescendantEvaluation) {
            updateDescendants();
        }
    }, [pauseDescendantEvaluation]);
    // Update descendants & reset current index when dependencies change
    useEffect(() => {
        /**
         * 0 second timeout added because descendantScope needs to be
         * up to date before running query after dependencyArray change
         */
        const timeoutId = setTimeout(() => {
            updateDescendants();
            if (!paused.current) {
                setCurrentIndex(0);
            }
        }, 0);
        return () => clearTimeout(timeoutId);
    }, [...dependencyArray]);
    // Set IDs and aria-owns
    useEffect(() => {
        if (!pauseDescendantEvaluation) {
            setIdsAndOwns();
            setResetId(Math.random());
        }
        else {
            clearDescendants();
            setIdsAndOwns({ clear: true });
        }
    }, [pauseDescendantEvaluation, focusEl, descendants]);
    // Bind focus el keyDown
    useEffect(() => {
        const nextIndex = () => {
            // Focus next or first
            clearFocusReturn?.();
            clearPreventScroll?.();
            if (currentIndex !== null && currentIndex + 1 < descendants.length) {
                setCurrentIndex(currentIndex + 1);
            }
            else {
                setCurrentIndex(0);
            }
        };
        const prevIndex = () => {
            // Focus previous or last
            clearFocusReturn?.();
            clearPreventScroll?.();
            if (currentIndex !== null && currentIndex - 1 > -1) {
                setCurrentIndex(currentIndex - 1);
            }
            else {
                setCurrentIndex(descendants.length - 1);
            }
        };
        const onKeyDown = (e) => {
            if (descendants?.length) {
                if (['ArrowDown', 'ArrowUp'].includes(e.key) && orientation === 'vertical') {
                    e.preventDefault();
                }
                if (['ArrowLeft', 'ArrowRight'].includes(e.key) && orientation === 'horizontal') {
                    e.preventDefault();
                }
                // 0 second timeout to execute after explicitly defined onKeyDown event handlers.
                setTimeout(() => {
                    switch (e.key) {
                        case 'ArrowDown':
                            if (orientation === 'vertical') {
                                nextIndex();
                            }
                            break;
                        case 'ArrowUp':
                            if (orientation === 'vertical') {
                                prevIndex();
                            }
                            break;
                        case 'ArrowRight':
                            if (orientation === 'horizontal') {
                                if (rtl) {
                                    prevIndex();
                                }
                                else {
                                    nextIndex();
                                }
                            }
                            break;
                        case 'ArrowLeft':
                            if (orientation === 'horizontal') {
                                if (rtl) {
                                    nextIndex();
                                }
                                else {
                                    prevIndex();
                                }
                            }
                            break;
                        case 'Enter':
                            // Click focused item
                            if (currentIndex !== null) {
                                if (onClick) {
                                    onClick(descendants[currentIndex]);
                                    break;
                                }
                                const nodeName = descendants[currentIndex].nodeName.toLowerCase();
                                if (interactiveNodeNames.includes(nodeName)) {
                                    descendants[currentIndex].click();
                                }
                                else {
                                    descendants[currentIndex]
                                        .querySelector(`${interactiveNodeNames.join(',')}`)
                                        ?.click();
                                }
                            }
                            break;
                        default:
                            break;
                    }
                }, 0);
            }
        };
        // Do not rebind once / if `currentDescendantId` (deprecated) control is defined
        if (!pauseDescendantEvaluation && focusEl && descendants?.length && !currentDescendantId) {
            focusEl.addEventListener('keydown', onKeyDown);
        }
        return () => {
            focusEl?.removeEventListener('keydown', onKeyDown);
        };
    }, [focusEl, currentIndex, descendants, pauseDescendantEvaluation]);
    // Handle independent control update (deprecated)
    useEffect(() => {
        if (!pauseDescendantEvaluation && descendants && currentDescendantId) {
            descendants.forEach((node, index) => {
                if (node.id === currentDescendantId) {
                    setCurrentIndex(index);
                }
            });
        }
    }, [currentDescendantId, descendants, pauseDescendantEvaluation]);
    // Set and scroll to current descendant
    useEffect(() => {
        if (paused.current) {
            return;
        }
        const nextIndex = focusDescendantElIndex || currentIndex;
        const focusReturnId = focusReturnEl?.id;
        let focusReturnIndex;
        const focusDescendantElId = focusDescendantEl?.id;
        let focusDescendantIndex;
        let foundFocusDescendantEl = false;
        if (descendants && descendants.length) {
            // clear previous & catch focusReturn / focusDescendantEl index
            descendants.forEach((node, index) => {
                if (focusDescendantElIndex === null && node.id === focusDescendantElId) {
                    focusDescendantIndex = index;
                    foundFocusDescendantEl = true;
                    setFocusDescendantElIndex(index);
                }
                if (node.id === focusReturnId) {
                    focusReturnIndex = index;
                }
                node.setAttribute('data-current', 'false');
            });
            // early return for focusReturn / focusDescendantEl
            if (focusReturnIndex && focusReturnIndex !== currentIndex) {
                setCurrentIndex(focusReturnIndex);
                clearFocusReturn?.();
                return;
            }
            if (foundFocusDescendantEl && focusDescendantIndex !== undefined) {
                // hard reset to continue focusDescendantEl process even if it is the active descendant
                setResetId(Math.random());
                setCurrentIndex(focusDescendantIndex);
                return;
            }
            // set new
            if (nextIndex !== null && descendants[nextIndex]) {
                const itemEl = descendants[nextIndex];
                itemEl.setAttribute('data-current', 'true');
                focusEl?.setAttribute('aria-activedescendant', itemEl.id);
                // scroll to element
                if (itemEl.id !== previousActiveId.current && !preventInitialScroll) {
                    const scrollTo = Element.prototype.scrollIntoViewIfNeeded ?? Element.prototype.scrollIntoView;
                    scrollTo?.call(itemEl, false);
                }
                // focusDescendantEl cleanup
                if (focusDescendantElIndex !== null) {
                    setFocusDescendantElIndex(null);
                    clearFocusDescendant?.();
                }
                previousActiveId.current = itemEl.id;
            }
        }
        return () => {
            focusEl?.removeAttribute('aria-activedescendant');
        };
    }, [descendants, currentIndex, focusDescendantEl, focusEl, resetId]);
    return {
        activeDescendant: currentIndex !== null && descendants ? descendants[currentIndex] : undefined,
        descendants: descendants || null
    };
};
export const useLazyDescendant = ({ loading, descendants, previousActiveDescendant, activeDescendant, focusReturnEl, setFocusReturnEl, scrollEl }) => {
    // Bump scroll & set return element when descendant focused in loading state.
    useEffect(() => {
        if (loading && descendants && descendants.length && activeDescendant) {
            const descendantList = [...descendants];
            const lastItem = descendantList.pop();
            const lastId = lastItem?.id || undefined;
            let timeoutId;
            if (lastId === activeDescendant.id) {
                if (scrollEl) {
                    scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.offsetHeight;
                }
                if (focusReturnEl?.id !== activeDescendant.id) {
                    // Set focus return to AD on 'nextTick', to avoid reset of AD on update
                    timeoutId = setTimeout(() => {
                        setFocusReturnEl(lastItem);
                    }, 0);
                }
            }
            // If not last item
            else if (focusReturnEl?.id !== activeDescendant.id) {
                // Set focus return to AD on 'nextTick' to avoid reset of AD on update
                // Do not D.R.Y to avoid conflicting calls with immediately prior elements.
                timeoutId = setTimeout(() => {
                    setFocusReturnEl(activeDescendant);
                }, 0);
            }
            return () => clearTimeout(timeoutId);
        }
    }, [loading, descendants, previousActiveDescendant, activeDescendant, scrollEl, focusReturnEl]);
};
export default useActiveDescendant;
//# sourceMappingURL=useActiveDescendant.js.map