import { useCallback, useContext, useMemo } from 'react';
import { useFocusWithin } from '../use-focus-within';
import { KEY_VALUES, KEYS } from './constants';
import { getKeyActionFn, getDummyItem, getElementId } from './utils';
import { ListNavigationContext } from './list-navigation-context';
const noop = () => {};
const defaultProps = {
  onKeyDown: noop,
  onFocus: noop,
  onBlur: noop
};
export function useListItemNavigation({
  id,
  onToggleSelected = noop,
  onToggleFocused = noop,
  componentProps = defaultProps,
  isSelected = false
}) {
  const context = useContext(ListNavigationContext);
  const {
    items,
    itemsMap,
    refs,
    setBlurred,
    setFocused,
    isDisabled
  } = context;
  // When new item is added in a list for one render cycle it is not present in
  // a context because of useEffect.
  // This dummy item instance prevents item from being undefined and consumers
  // from accessing its property as optional (like item?.isFocused).
  const item = itemsMap.get(id) || getDummyItem(id);
  const {
    onFocus,
    onBlur
  } = useFocusWithin({
    onFocus: event => {
      if (isDisabled || item.isDisabled) {
        return;
      }
      setFocused(id);
      onToggleFocused(true);
      componentProps.onFocus?.(event);
    },
    onBlur: event => {
      if (isDisabled || item.isDisabled) {
        return;
      }
      setBlurred(id);
      onToggleFocused(false);
      componentProps.onBlur?.(event);
    },
    onChildFocus: event => {
      if (isDisabled || item.isDisabled) {
        return;
      }
      // tree items are nested
      event.stopPropagation();
      onToggleFocused(true);
      setFocused(id);
    }
  });
  const setRef = useCallback(value => {
    refs.current.set(id, value);
  }, [id]);
  const onKeyDownHandler = useCallback(event => {
    const key = event.key;
    componentProps.onKeyDown?.(event);
    if (!KEY_VALUES.includes(key)) {
      return;
    }

    // allow interaction with action items of the item
    if (key === KEYS.ENTER && context.refs.current.get(id) !== event.target) {
      return;
    }
    if (item.isDisabled || isDisabled) {
      return;
    }
    const actionFn = getKeyActionFn(key);
    if (actionFn) {
      actionFn({
        item,
        context,
        onToggleSelected,
        isSelected
      });
    }
    event.stopPropagation();
    event.preventDefault();
  }, [componentProps.onKeyDown, item, context, onToggleSelected, isSelected, isDisabled]);
  const toggleSelected = useCallback(() => {
    context.toggleSelected(id);
  }, [id, context]);
  const listItemAttrs = useMemo(() => {
    // it should be possible to navigate to action items inside the list item,
    // but not to the next list item with the Tab key
    function getTabIndex() {
      if (item.isDisabled || isDisabled) {
        return -1;
      }
      if (context.focused) {
        return item.isFocused ? 0 : -1;
      }
      if (context.lastFocused && context.lastFocusedIsPresent) {
        return context.lastFocused === item.id ? 0 : -1;
      }
      return 0;
    }
    return {
      id: getElementId(context.listId, item.id),
      tabIndex: getTabIndex(),
      role: 'option',
      'aria-disabled': item.isDisabled || isDisabled,
      'aria-selected': item.isSelected || !!isSelected,
      onFocus,
      onBlur,
      onKeyDown: onKeyDownHandler
    };
  }, [item, items, itemsMap, isDisabled, onFocus, onBlur, onKeyDownHandler, context.focused, context.lastFocused, context.lastFocusedIsPresent, context.listId]);
  const api = {
    item,
    listItemAttrs,
    ref: setRef,
    toggleSelected
  };
  return useMemo(() => api, Object.values(api));
}