import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import React, { useState, useImperativeHandle, useCallback } from 'react';
import kebabCase from 'lodash/kebabCase';
import trim from 'lodash/trim';
import isFunction from 'lodash/isFunction';
import isUndefined from 'lodash/isUndefined';
import omit from 'lodash/omit';
import find from 'lodash/find';
import { findNodeOfTree, filterNodesOfTree } from '../utils/treeUtils';
import { KEY_VALUES, useClassNames, shallowEqual, reactToString, placementPolyfill } from '../utils';
var defaultNodeKeys = {
  valueKey: 'value',
  childrenKey: 'children'
};
export function createConcatChildrenFunction(node, nodeValue, nodeKeys) {
  if (nodeKeys === void 0) {
    nodeKeys = defaultNodeKeys;
  }

  var _nodeKeys = nodeKeys,
      valueKey = _nodeKeys.valueKey,
      childrenKey = _nodeKeys.childrenKey;
  return function (data, children) {
    if (nodeValue) {
      node = findNodeOfTree(data, function (item) {
        return nodeValue === item[valueKey];
      });
    }

    node[childrenKey] = children;
    return data.concat([]);
  };
}
export function shouldDisplay(label, searchKeyword) {
  if (!trim(searchKeyword)) {
    return true;
  }

  var keyword = searchKeyword.toLocaleLowerCase();

  if (typeof label === 'string' || typeof label === 'number') {
    return ("" + label).toLocaleLowerCase().indexOf(keyword) >= 0;
  } else if ( /*#__PURE__*/React.isValidElement(label)) {
    var nodes = reactToString(label);
    return nodes.join('').toLocaleLowerCase().indexOf(keyword) >= 0;
  }

  return false;
}

/**
 * The className of the assembled Toggle is on the Picker.
 */
export function usePickerClassName(props) {
  var _withClassPrefix;

  var name = props.name,
      classPrefix = props.classPrefix,
      className = props.className,
      placement = props.placement,
      appearance = props.appearance,
      cleanable = props.cleanable,
      block = props.block,
      disabled = props.disabled,
      countable = props.countable,
      readOnly = props.readOnly,
      plaintext = props.plaintext,
      hasValue = props.hasValue,
      rest = _objectWithoutPropertiesLoose(props, ["name", "classPrefix", "className", "placement", "appearance", "cleanable", "block", "disabled", "countable", "readOnly", "plaintext", "hasValue"]);

  var _useClassNames = useClassNames(classPrefix),
      withClassPrefix = _useClassNames.withClassPrefix,
      merge = _useClassNames.merge;

  var classes = merge(className, withClassPrefix(name, appearance, 'toggle-wrapper', (_withClassPrefix = {}, _withClassPrefix["placement-" + kebabCase(placementPolyfill(placement))] = placement, _withClassPrefix['read-only'] = readOnly, _withClassPrefix['has-value'] = hasValue, _withClassPrefix.cleanable = hasValue && cleanable, _withClassPrefix.block = block, _withClassPrefix.disabled = disabled, _withClassPrefix.countable = countable, _withClassPrefix.plaintext = plaintext, _withClassPrefix)));
  var usedClassNamePropKeys = Object.keys(omit(props, [].concat(Object.keys(rest || {}), ['disabled', 'readOnly', 'plaintext'])));
  return [classes, usedClassNamePropKeys];
}

/**
 * Handling keyboard events...
 * @param event Keyboard event object
 * @param events Event callback functions
 */
export function onMenuKeyDown(event, events) {
  var down = events.down,
      up = events.up,
      enter = events.enter,
      del = events.del,
      esc = events.esc,
      right = events.right,
      left = events.left;

  switch (event.key) {
    // down
    case KEY_VALUES.DOWN:
      down === null || down === void 0 ? void 0 : down(event);
      event.preventDefault();
      break;
    // up

    case KEY_VALUES.UP:
      up === null || up === void 0 ? void 0 : up(event);
      event.preventDefault();
      break;
    // enter

    case KEY_VALUES.ENTER:
      enter === null || enter === void 0 ? void 0 : enter(event);
      event.preventDefault();
      break;
    // delete

    case KEY_VALUES.BACKSPACE:
      del === null || del === void 0 ? void 0 : del(event);
      break;
    // esc | tab

    case KEY_VALUES.ESC:
    case KEY_VALUES.TAB:
      esc === null || esc === void 0 ? void 0 : esc(event);
      break;
    // left arrow

    case KEY_VALUES.LEFT:
      left === null || left === void 0 ? void 0 : left(event);
      break;
    // right arrow

    case KEY_VALUES.RIGHT:
      right === null || right === void 0 ? void 0 : right(event);
      break;

    default:
  }
}

/**
 * A hook that manages the focus state of the option.
 * @param defaultFocusItemValue
 * @param props
 */
export var useFocusItemValue = function useFocusItemValue(defaultFocusItemValue, props) {
  var _props$valueKey = props.valueKey,
      valueKey = _props$valueKey === void 0 ? 'value' : _props$valueKey,
      _props$focusableQuery = props.focusableQueryKey,
      focusableQueryKey = _props$focusableQuery === void 0 ? '[data-key][aria-disabled="false"]' : _props$focusableQuery,
      _props$defaultLayer = props.defaultLayer,
      defaultLayer = _props$defaultLayer === void 0 ? 0 : _props$defaultLayer,
      data = props.data,
      target = props.target,
      rtl = props.rtl,
      callback = props.callback;

  var _useState = useState(defaultFocusItemValue),
      focusItemValue = _useState[0],
      setFocusItemValue = _useState[1];

  var _useState2 = useState(defaultLayer),
      layer = _useState2[0],
      setLayer = _useState2[1];

  var _useState3 = useState([]),
      keys = _useState3[0],
      setKeys = _useState3[1];
  /**
   * Get the elements visible in all options.
   */


  var getFocusableMenuItems = useCallback(function () {
    if (!target) {
      return [];
    }

    var menu = isFunction(target) ? target() : target;
    var currentKeys = keys;

    if (currentKeys.length === 0) {
      var _Array$from;

      currentKeys = (_Array$from = Array.from(menu === null || menu === void 0 ? void 0 : menu.querySelectorAll(focusableQueryKey))) === null || _Array$from === void 0 ? void 0 : _Array$from.map(function (item) {
        var _item$dataset;

        return item === null || item === void 0 ? void 0 : (_item$dataset = item.dataset) === null || _item$dataset === void 0 ? void 0 : _item$dataset.key;
      });
      setKeys(currentKeys);
    }

    if (currentKeys.length === 0) {
      return [];
    } // 1. It is necessary to traverse the `keys` instead of `data` here to preserve the order of the array.
    // 2. The values ​​in `keys` are all string, so the corresponding value of `data` should also be converted to string


    return currentKeys.map(function (key) {
      return find(data, function (i) {
        return "" + i[valueKey] === key;
      });
    });
  }, [data, focusableQueryKey, keys, target, valueKey]);
  /**
   * Get the index of the focus item.
   */

  var findFocusItemIndex = useCallback(function (callback) {
    var items = getFocusableMenuItems();

    for (var i = 0; i < items.length; i += 1) {
      var _items$i;

      if (shallowEqual(focusItemValue, (_items$i = items[i]) === null || _items$i === void 0 ? void 0 : _items$i[valueKey])) {
        callback(items, i);
        return;
      }
    }

    callback(items, -1);
  }, [focusItemValue, getFocusableMenuItems, valueKey]);
  var focusNextMenuItem = useCallback(function (event) {
    findFocusItemIndex(function (items, index) {
      var nextIndex = index + 2 > items.length ? 0 : index + 1;
      var focusItem = items[nextIndex];

      if (!isUndefined(focusItem)) {
        setFocusItemValue(focusItem[valueKey]);
        callback === null || callback === void 0 ? void 0 : callback(focusItem[valueKey], event);
      }
    });
  }, [callback, findFocusItemIndex, valueKey]);
  var focusPrevMenuItem = useCallback(function (event) {
    findFocusItemIndex(function (items, index) {
      var nextIndex = index === 0 ? items.length - 1 : index - 1;
      var focusItem = items[nextIndex];

      if (!isUndefined(focusItem)) {
        setFocusItemValue(focusItem[valueKey]);
        callback === null || callback === void 0 ? void 0 : callback(focusItem[valueKey], event);
      }
    });
  }, [callback, findFocusItemIndex, valueKey]);
  var getSubMenuKeys = useCallback(function (nextLayer) {
    var menu = isFunction(target) ? target() : target;
    var subMenu = menu === null || menu === void 0 ? void 0 : menu.querySelector("[data-layer=\"" + nextLayer + "\"]");

    if (subMenu) {
      var _Array$from2;

      return (_Array$from2 = Array.from(subMenu.querySelectorAll(focusableQueryKey))) === null || _Array$from2 === void 0 ? void 0 : _Array$from2.map(function (item) {
        var _item$dataset2;

        return item === null || item === void 0 ? void 0 : (_item$dataset2 = item.dataset) === null || _item$dataset2 === void 0 ? void 0 : _item$dataset2.key;
      });
    }

    return null;
  }, [focusableQueryKey, target]);
  var focusNextLevelMenu = useCallback(function (event) {
    var nextLayer = layer + 1;
    var nextKeys = getSubMenuKeys(nextLayer);

    if (nextKeys) {
      setKeys(nextKeys);
      setLayer(nextLayer);
      setFocusItemValue(nextKeys[0]);
      callback === null || callback === void 0 ? void 0 : callback(nextKeys[0], event);
    }
  }, [callback, getSubMenuKeys, layer]);
  var focusPrevLevelMenu = useCallback(function (event) {
    var nextLayer = layer - 1;
    var nextKeys = getSubMenuKeys(nextLayer);

    if (nextKeys) {
      var _focusItem$parent;

      setKeys(nextKeys);
      setLayer(nextLayer);
      var focusItem = findNodeOfTree(data, function (item) {
        return item[valueKey] === focusItemValue;
      });
      var parentItemValue = focusItem === null || focusItem === void 0 ? void 0 : (_focusItem$parent = focusItem.parent) === null || _focusItem$parent === void 0 ? void 0 : _focusItem$parent[valueKey];

      if (parentItemValue) {
        setFocusItemValue(parentItemValue);
        callback === null || callback === void 0 ? void 0 : callback(parentItemValue, event);
      }
    }
  }, [callback, data, focusItemValue, getSubMenuKeys, layer, valueKey]);
  var handleKeyDown = useCallback(function (event) {
    var _onMenuKeyDown;

    onMenuKeyDown(event, (_onMenuKeyDown = {
      down: focusNextMenuItem,
      up: focusPrevMenuItem
    }, _onMenuKeyDown[rtl ? 'left' : 'right'] = focusNextLevelMenu, _onMenuKeyDown[rtl ? 'right' : 'left'] = focusPrevLevelMenu, _onMenuKeyDown));
  }, [focusNextLevelMenu, focusNextMenuItem, focusPrevLevelMenu, focusPrevMenuItem, rtl]);
  return {
    focusItemValue: focusItemValue,
    setFocusItemValue: setFocusItemValue,
    layer: layer,
    setLayer: setLayer,
    keys: keys,
    setKeys: setKeys,
    onKeyDown: handleKeyDown
  };
};

/**
 * A hook to control the toggle keyboard operation
 * @param props
 */
export var useToggleKeyDownEvent = function useToggleKeyDownEvent(props) {
  var _props$toggle = props.toggle,
      toggle = _props$toggle === void 0 ? true : _props$toggle,
      triggerRef = props.triggerRef,
      targetRef = props.targetRef,
      overlayRef = props.overlayRef,
      searchInputRef = props.searchInputRef,
      active = props.active,
      onExit = props.onExit,
      onOpen = props.onOpen,
      onClose = props.onClose,
      onKeyDown = props.onKeyDown,
      onMenuKeyDown = props.onMenuKeyDown,
      onMenuPressEnter = props.onMenuPressEnter,
      onMenuPressBackspace = props.onMenuPressBackspace;
  var handleClose = useCallback(function () {
    var _triggerRef$current, _triggerRef$current$c;

    (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : (_triggerRef$current$c = _triggerRef$current.close) === null || _triggerRef$current$c === void 0 ? void 0 : _triggerRef$current$c.call(_triggerRef$current);
    onClose === null || onClose === void 0 ? void 0 : onClose();
  }, [onClose, triggerRef]);
  var handleOpen = useCallback(function () {
    var _triggerRef$current2, _triggerRef$current2$;

    (_triggerRef$current2 = triggerRef.current) === null || _triggerRef$current2 === void 0 ? void 0 : (_triggerRef$current2$ = _triggerRef$current2.open) === null || _triggerRef$current2$ === void 0 ? void 0 : _triggerRef$current2$.call(_triggerRef$current2);
    onOpen === null || onOpen === void 0 ? void 0 : onOpen();
  }, [onOpen, triggerRef]);
  var handleToggleDropdown = useCallback(function () {
    if (active) {
      handleClose();
      return;
    }

    handleOpen();
  }, [active, handleOpen, handleClose]);
  var onToggle = useCallback(function (event) {
    if (event.target === (targetRef === null || targetRef === void 0 ? void 0 : targetRef.current)) {
      // enter
      if (toggle && event.key === KEY_VALUES.ENTER) {
        handleToggleDropdown();
      } // delete


      if (event.key === KEY_VALUES.BACKSPACE) {
        onExit === null || onExit === void 0 ? void 0 : onExit(event);
      }
    }

    if (event.key === KEY_VALUES.ESC || event.key === KEY_VALUES.TAB) {
      handleClose();
    } // Native event callback


    onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);

    if (overlayRef !== null && overlayRef !== void 0 && overlayRef.current) {
      // The keyboard operation callback on the menu.
      onMenuKeyDown === null || onMenuKeyDown === void 0 ? void 0 : onMenuKeyDown(event);

      if (event.key === KEY_VALUES.ENTER) {
        onMenuPressEnter === null || onMenuPressEnter === void 0 ? void 0 : onMenuPressEnter(event);
      }

      if (event.key === KEY_VALUES.BACKSPACE) {
        onMenuPressBackspace === null || onMenuPressBackspace === void 0 ? void 0 : onMenuPressBackspace(event);
      } // The search box gets focus when typing characters and numbers.


      if (event.key.length === 1 && /\w/.test(event.key)) {
        var _searchInputRef$curre;

        searchInputRef === null || searchInputRef === void 0 ? void 0 : (_searchInputRef$curre = searchInputRef.current) === null || _searchInputRef$curre === void 0 ? void 0 : _searchInputRef$curre.focus();
      }
    }
  }, [handleClose, handleToggleDropdown, overlayRef, onExit, onKeyDown, onMenuKeyDown, onMenuPressBackspace, onMenuPressEnter, toggle, targetRef, searchInputRef]);
  return onToggle;
};

/**
 * A hook that handles search filter options
 * @param props
 */
export function useSearch(props) {
  var labelKey = props.labelKey,
      data = props.data,
      searchBy = props.searchBy,
      callback = props.callback; // Use search keywords to filter options.

  var _useState4 = useState(''),
      searchKeyword = _useState4[0],
      setSearchKeyword = _useState4[1];
  /**
   * Index of keyword  in `label`
   * @param {node} label
   */


  var checkShouldDisplay = useCallback(function (item, keyword) {
    var label = item === null || item === void 0 ? void 0 : item[labelKey];

    var _keyword = isUndefined(keyword) ? searchKeyword : keyword;

    if (typeof searchBy === 'function') {
      return searchBy(_keyword, label, item);
    }

    return shouldDisplay(label, _keyword);
  }, [labelKey, searchBy, searchKeyword]);
  var updateFilteredData = useCallback(function (nextData) {
    setFilteredData(filterNodesOfTree(nextData, function (item) {
      return checkShouldDisplay(item);
    }));
  }, [checkShouldDisplay]);

  var _useState5 = useState(filterNodesOfTree(data, function (item) {
    return checkShouldDisplay(item);
  })),
      filteredData = _useState5[0],
      setFilteredData = _useState5[1];

  var handleSearch = function handleSearch(searchKeyword, event) {
    var filteredData = filterNodesOfTree(data, function (item) {
      return checkShouldDisplay(item, searchKeyword);
    });
    setFilteredData(filteredData);
    setSearchKeyword(searchKeyword);
    callback === null || callback === void 0 ? void 0 : callback(searchKeyword, filteredData, event);
  };

  return {
    searchKeyword: searchKeyword,
    filteredData: filteredData,
    updateFilteredData: updateFilteredData,
    setSearchKeyword: setSearchKeyword,
    checkShouldDisplay: checkShouldDisplay,
    handleSearch: handleSearch
  };
}

/**
 * A hook of the exposed method of Picker
 *
 * {
 *   root: Element;
 *   overlay: Element;
 *   target?: Element;
 *   updatePosition:() => void;
 *   open:() => void;
 *   close:() => void;
 * }
 * @param ref
 * @param params
 */
export function usePublicMethods(ref, _ref) {
  var triggerRef = _ref.triggerRef,
      overlayRef = _ref.overlayRef,
      targetRef = _ref.targetRef,
      rootRef = _ref.rootRef;
  var handleOpen = useCallback(function () {
    var _triggerRef$current3;

    (_triggerRef$current3 = triggerRef.current) === null || _triggerRef$current3 === void 0 ? void 0 : _triggerRef$current3.open();
  }, [triggerRef]);
  var handleClose = useCallback(function () {
    var _triggerRef$current4;

    (_triggerRef$current4 = triggerRef.current) === null || _triggerRef$current4 === void 0 ? void 0 : _triggerRef$current4.close();
  }, [triggerRef]);
  var handleUpdatePosition = useCallback(function () {
    var _triggerRef$current5;

    (_triggerRef$current5 = triggerRef.current) === null || _triggerRef$current5 === void 0 ? void 0 : _triggerRef$current5.updatePosition();
  }, [triggerRef]);
  useImperativeHandle(ref, function () {
    return {
      get root() {
        var _triggerRef$current6;

        return rootRef !== null && rootRef !== void 0 && rootRef.current ? rootRef === null || rootRef === void 0 ? void 0 : rootRef.current : (_triggerRef$current6 = triggerRef.current) === null || _triggerRef$current6 === void 0 ? void 0 : _triggerRef$current6.root;
      },

      get overlay() {
        return overlayRef.current;
      },

      get target() {
        return targetRef === null || targetRef === void 0 ? void 0 : targetRef.current;
      },

      updatePosition: handleUpdatePosition,
      open: handleOpen,
      close: handleClose
    };
  });
}