import { useEffect, useCallback, useMemo } from 'react';
import { documentIsAvailable, isInstance } from '../utils';
import useFocusWithin from './useFocusWithin';
import useDirection from './useDirection';
const upDown = { ArrowDown: 'next', ArrowUp: 'prev' };
const leftRight = { ArrowRight: 'next', ArrowLeft: 'prev' };
const rtlLeftRight = { ArrowLeft: 'next', ArrowRight: 'prev' };
const focusable = `
  a[href],
  button:enabled,
  input[type='checkbox']:enabled,
  input[type='radio']:enabled
`;
const getInitialFocusIndex = (el, selector, initialFocusElement) => {
    const elements = Array.from(el.querySelectorAll(selector));
    const index = elements.findIndex(focusEl => focusEl === initialFocusElement);
    return index === -1 ? undefined : index;
};
/**
 * @example useArrows(ref, { cycle, selector });
 * @param ref - A reference to the element that will be navigated through. [React RefObject](https://reactjs.org/docs/refs-and-the-dom.html)
 */
const useArrows = (ref, { cycle = true, selector = focusable, dir = 'up-down', allowTabFocus = true, updateTabIndex = true, initialFocusElement } = {}, 
/** Pass this prop in the internal dependencies when list is dynamic */
dependencies = []) => {
    const { rtl } = useDirection();
    const configuredBindings = useMemo(() => {
        const effectiveLeftRight = rtl ? rtlLeftRight : leftRight;
        if (dir === 'up-down')
            return upDown;
        if (dir === 'left-right')
            return effectiveLeftRight;
        return { ...upDown, ...effectiveLeftRight };
    }, [dir, rtl]);
    const setTabIndexes = useCallback((el, focused) => {
        // don't set tabindex -1 for first element, or the initialFocusElement, or an element with a checked input inside of it to be able to focus it
        const focusableElements = Array.from(el.querySelectorAll(selector));
        if (focusableElements.length > 0) {
            let focusIndex = focusableElements.findIndex(focusEl => focusEl === initialFocusElement);
            if (focusIndex === -1)
                focusIndex = 0;
            focusableElements[focusIndex].tabIndex =
                allowTabFocus && !focused ? 0 : -1;
            focusableElements.splice(focusIndex, 1);
            focusableElements.forEach(item => {
                if (isInstance(item, HTMLElement)) {
                    const checked = item.checked;
                    if (!checked)
                        item.tabIndex = -1;
                    const includesChecked = item.querySelector('input[type="radio"]:checked');
                    if (includesChecked)
                        item.tabIndex = 0;
                }
            });
        }
    }, [selector, initialFocusElement, allowTabFocus, ...dependencies]);
    useFocusWithin([ref], focused => {
        if (ref.current && updateTabIndex)
            setTabIndexes(ref.current, focused);
    });
    const listener = useCallback((e) => {
        const el = ref.current;
        if (!el)
            return;
        const pressedKeyRole = configuredBindings[e.key];
        if (!documentIsAvailable)
            return;
        const items = Array.from(el.querySelectorAll(selector)).filter((item) => isInstance(item, HTMLElement));
        if (!items.length)
            return;
        const rootNode = el.getRootNode();
        if (!isInstance(rootNode, Document) && !isInstance(rootNode, ShadowRoot))
            return;
        // If focus is inside the container but on a non-selector element (e.g. a drag handle button),
        // do not steal focus — the element is intentionally handling its own keyboard interaction.
        if (el.contains(rootNode.activeElement) &&
            !items.some(item => item === rootNode.activeElement))
            return;
        if (e.key === 'Home' || e.key === 'End' || pressedKeyRole)
            e.preventDefault();
        const focusIdx = items.findIndex(item => item === rootNode.activeElement);
        const lastIdx = items.length - 1;
        let newFocusIdx;
        if (e.key === 'Home' || (pressedKeyRole === 'next' && focusIdx === -1)) {
            newFocusIdx = initialFocusElement
                ? getInitialFocusIndex(el, selector, initialFocusElement) ?? 0
                : 0;
        }
        else if (e.key === 'End' || (pressedKeyRole === 'prev' && focusIdx === -1)) {
            newFocusIdx = initialFocusElement
                ? getInitialFocusIndex(el, selector, initialFocusElement) ?? lastIdx
                : lastIdx;
        }
        else if (e.key === 'Enter') {
            if (updateTabIndex) {
                setTabIndexes(el, true);
            }
            return;
        }
        else if (pressedKeyRole === 'next') {
            if (focusIdx === lastIdx) {
                if (!cycle)
                    return;
                newFocusIdx = 0;
            }
            else {
                newFocusIdx = focusIdx + 1;
            }
        }
        else if (pressedKeyRole === 'prev') {
            if (focusIdx === 0) {
                if (!cycle)
                    return;
                newFocusIdx = lastIdx;
            }
            else {
                newFocusIdx = focusIdx - 1;
            }
        }
        else {
            return;
        }
        items[newFocusIdx].focus();
    }, [
        ref.current,
        cycle,
        selector,
        initialFocusElement,
        configuredBindings,
        updateTabIndex,
        setTabIndexes,
        ...dependencies
    ]);
    useEffect(() => {
        const el = ref.current;
        if (!el)
            return;
        if (updateTabIndex)
            setTimeout(() => {
                setTabIndexes(el, false);
            }, 0);
        el.addEventListener('keydown', listener);
        return () => {
            el.removeEventListener('keydown', listener);
        };
    }, [ref.current, selector, listener, initialFocusElement, ...dependencies]);
};
export default useArrows;
//# sourceMappingURL=useArrows.js.map