"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.useTreeNodeNavigation = useTreeNodeNavigation;
var _react = require("react");
var _useFocusWithin = require("../use-focus-within");
var _constants = require("./constants");
var _utils = require("./utils");
var _treeNavigationContext = require("./tree-navigation-context");
const noop = () => {};
const defaultProps = {
  onKeyDown: noop,
  onFocus: noop,
  onBlur: noop
};
function useTreeNodeNavigation({
  id,
  onToggleExpanded = noop,
  onToggleSelected = noop,
  onToggleFocused = noop,
  componentProps = defaultProps,
  isSelected = false,
  onCustomButtonKeyDown = noop,
  getKeyActionFn = _utils.getKeyActionFnDefault,
  ignoreKeys = [_constants.KEYS.TAB],
  // ignoring [TAB] allows to navigate to the next element, close menu etc.
  preventDefault = true
}) {
  const context = (0, _react.useContext)(_treeNavigationContext.TreeNavigationContext);
  const {
    nodes,
    nodesMap,
    refs,
    setBlurred,
    setFocused,
    isDisabled,
    focused
  } = context;
  // When new item is added in a tree for one render cycle it is not present in
  // a context because of useEffect.
  // This dummy node instance prevents node from being undefined and consumers
  // from accessing its property as optional (like node?.isFocused).
  const node = nodesMap.get(id) || (0, _utils.getDummyNode)(id);
  const {
    onFocus,
    onBlur
  } = (0, _useFocusWithin.useFocusWithin)({
    onFocus: event => {
      if (isDisabled || node.isDisabled) {
        return;
      }
      // tree nodes are nested
      event.stopPropagation();
      setFocused(id);
      onToggleFocused(true);
      componentProps.onFocus?.(event);
    },
    onBlur: event => {
      if (isDisabled || node.isDisabled) {
        return;
      }
      // tree nodes are nested
      event.stopPropagation();
      setBlurred(id);
      onToggleFocused(false);
      componentProps.onBlur?.(event);
    },
    onChildFocus: event => {
      if (isDisabled || node.isDisabled) {
        return;
      }
      // tree nodes are nested
      event.stopPropagation();
      onToggleFocused(true);
      setFocused(id);
    }
  });
  const isFirstFocusable = (0, _react.useMemo)(() => {
    return !isDisabled && nodes.indexOf(node) === 0;
  }, [node, nodes, isDisabled]);
  (0, _react.useEffect)(() => {
    if (!isDisabled && focused?.id) {
      const elementToFocus = refs.current.get(focused.id);
      if (document.activeElement !== elementToFocus) {
        elementToFocus?.focus();
      }
    }
  }, [refs, focused?.id, isDisabled]);
  const setRef = (0, _react.useCallback)(value => {
    refs.current.set(id, value);
  }, [id]);
  const onKeyDownHandler = (0, _react.useCallback)(event => {
    const key = event.key;
    componentProps.onKeyDown?.(event);
    if (!_constants.KEY_VALUES.includes(key)) {
      return;
    }

    // allow interaction with action items of the node
    if (key === _constants.KEYS.ENTER && context.refs.current.get(id) !== event.target) {
      return;
    }

    // allow to focus on elements outside of tree nodes by default
    if (ignoreKeys?.includes(key)) {
      return;
    }
    if (node.isDisabled || isDisabled) {
      return;
    }
    const actionFn = getKeyActionFn(key, node, context);
    if (actionFn) {
      if (Array.isArray(actionFn)) {
        for (const fn of actionFn) {
          fn({
            node,
            context,
            onToggleExpanded,
            onToggleSelected,
            isSelected,
            onCustomButtonKeyDown,
            button: key
          });
        }
      } else {
        actionFn({
          node,
          context,
          onToggleExpanded,
          onToggleSelected,
          isSelected,
          onCustomButtonKeyDown,
          button: key
        });
      }
    }

    // tree nodes are nested
    event.stopPropagation();
    if (preventDefault) {
      event.preventDefault();
    }
  }, [componentProps.onKeyDown, node, context, onToggleExpanded, onToggleSelected, isSelected, isDisabled, preventDefault]);
  const toggleExpanded = (0, _react.useCallback)(expanded => {
    const actionFn = expanded ? _utils.expandNode : _utils.collapseNode;
    if (actionFn) {
      actionFn({
        node,
        context,
        onToggleExpanded,
        onToggleSelected,
        isSelected,
        onCustomButtonKeyDown
      });
    }
  }, [node, context, onToggleExpanded, onToggleSelected, isSelected]);
  const toggleSelected = (0, _react.useCallback)(() => {
    (0, _utils.toggleSelect)({
      node,
      context,
      onToggleExpanded: noop,
      onToggleSelected: noop,
      isSelected,
      onCustomButtonKeyDown
    });
  }, [node, context, isSelected]);
  const treeItemAttrs = (0, _react.useMemo)(() => {
    return {
      // it should be possible to navigate to action items inside the tree item,
      // but not to the next tree item
      tabIndex: node.isDisabled || isDisabled || context.focused && !node.isFocused ? -1 : 0,
      role: 'treeitem',
      'aria-disabled': node.isDisabled || isDisabled,
      'aria-selected': node.isFocused || !!isSelected,
      'aria-level': node.parents.length + 1,
      'aria-setsize': (0, _utils.getSetSize)(node, nodes, nodesMap),
      'aria-posinset': (0, _utils.getPositionInSet)(node, nodes, nodesMap),
      onFocus,
      onBlur,
      onKeyDown: onKeyDownHandler
    };
  }, [node, nodes, nodesMap, isDisabled, onFocus, onBlur, onKeyDownHandler]);
  const api = {
    node,
    treeItemAttrs,
    ref: setRef,
    toggleExpanded,
    toggleSelected,
    setFocused,
    setBlurred,
    isFirstFocusable,
    isDisabled
  };
  return (0, _react.useMemo)(() => api, Object.values(api));
}