import _isString from "lodash/isString";
import _isObject from "lodash/isObject";
import _isFunction from "lodash/isFunction";
import _includes from "lodash/includes";
import _isNumber from "lodash/isNumber";
import _isEmpty from "lodash/isEmpty";
import _assign from "lodash/assign";
import _isUndefined from "lodash/isUndefined";
import _difference from "lodash/difference";
import _get from "lodash/get";
import _isEqual from "lodash/isEqual";
import BaseFoundation from '../base/foundation';
import { findAncestorKeys, calcCheckedKeysForUnchecked, calcCheckedKeysForChecked, calcCheckedKeys, findDescendantKeys, normalizeKeyList } from '../tree/treeUtil';
import { filter, convertDataToEntities, normalizedArr, isValid, calcMergeType, getKeysByValuePath, getKeyByPos } from './util';
import { strings } from './constants';
import isEnterPress from '../utils/isEnterPress';
import { ESC_KEY } from '../utils/keyCode';
export default class CascaderFoundation extends BaseFoundation {
  constructor(adapter) {
    super(Object.assign({}, adapter));
    this.handleKeyDown = e => {
      if (e.key === ESC_KEY) {
        const isOpen = this.getState('isOpen');
        isOpen && this.close(e);
      }
    };
    this.updateSearching = isSearching => {
      this._adapter.updateStates({
        isSearching: false
      });
    };
    this.handleTagRemoveByKey = key => {
      var _a, _b;
      const {
        keyEntities
      } = this.getStates();
      const {
        disabled
      } = this.getProps();
      if (disabled) {
        /* istanbul ignore next */
        return;
      }
      const removedItem = (_a = keyEntities[key]) !== null && _a !== void 0 ? _a : {};
      !((_b = removedItem === null || removedItem === void 0 ? void 0 : removedItem.data) === null || _b === void 0 ? void 0 : _b.disable) && this._handleMultipleSelect(removedItem);
      this._adapter.rePositionDropdown();
    };
    this.handleTagRemoveInTrigger = pos => {
      const {
        treeData
      } = this.getStates();
      const key = getKeyByPos(pos, treeData);
      this.handleTagRemoveByKey(key);
    };
  }
  init() {
    const isOpen = this.getProp('open') || this.getProp('defaultOpen');
    this.collectOptions(true);
    this._adapter.updateLoadingKeyRefValue(new Set());
    this._adapter.updateLoadedKeyRefValue(new Set());
    if (isOpen && !this._isDisabled()) {
      this.open();
    }
  }
  _setEmptyContentMinWidth() {
    const {
      style
    } = this.getProps();
    let width;
    if (style && _isNumber(style.width)) {
      width = style.width;
    } else if (style && _isString(style.width) && !style.width.includes('%')) {
      width = style.width;
    } else {
      width = this._adapter.getTriggerWidth();
    }
    this._adapter.setEmptyContentMinWidth(width);
  }
  destroy() {
    this._adapter.unregisterClickOutsideHandler();
  }
  _isDisabled() {
    return this.getProp('disabled');
  }
  _isFilterable() {
    return Boolean(this.getProp('filterTreeNode')); // filter can be boolean or function
  }
  _notifyChange(item) {
    const {
      onChangeWithObject,
      multiple
    } = this.getProps();
    const valueProp = onChangeWithObject ? [] : 'value';
    if (multiple) {
      const valuePath = [];
      // @ts-ignore
      item.forEach(checkedKey => {
        const valuePathItem = this.getItemPropPath(checkedKey, valueProp);
        valuePath.push(valuePathItem);
      });
      this._adapter.notifyChange(valuePath);
    } else {
      const valuePath = _isUndefined(item) || !('key' in item) ? [] : this.getItemPropPath(item.key, valueProp);
      this._adapter.notifyChange(valuePath);
    }
  }
  _isLeaf(item) {
    if (this.getProp('loadData')) {
      return Boolean(item.isLeaf);
    }
    return !item.children || !item.children.length;
  }
  _clearInput() {
    this._adapter.updateInputValue('');
  }
  // Scenes that may trigger blur:
  //  1、clickOutSide
  _notifyBlur(e) {
    this._adapter.notifyBlur(e);
  }
  // Scenes that may trigger focus:
  //  1、click selection
  _notifyFocus(e) {
    this._adapter.notifyFocus(e);
  }
  _isOptionDisabled(key, keyEntities) {
    const isDisabled = findAncestorKeys([key], keyEntities, true).some(item => keyEntities[item].data.disabled);
    return isDisabled;
  }
  // prop: is array, return all data
  getItemPropPath(selectedKey, prop, keyEntities) {
    const searchMap = keyEntities || this.getState('keyEntities');
    const selectedItem = searchMap[selectedKey];
    let path = [];
    if (!selectedItem) {
      // do nothing
    } else if (selectedItem._notExist) {
      path = selectedItem.path;
    } else {
      const keyPath = selectedItem.path;
      path = Array.isArray(prop) ? keyPath.map(key => searchMap[key].data) : keyPath.map(key => searchMap[key].data[prop]);
    }
    return path;
  }
  _getCacheValue(keyEntities) {
    const {
      selectedKeys
    } = this.getStates();
    const selectedKey = Array.from(selectedKeys)[0];
    let cacheValue;
    /* selectedKeys does not match keyEntities */
    if (_isEmpty(keyEntities[selectedKey])) {
      if (_includes(selectedKey, 'not-exist-')) {
        /* Get the value behind not-exist- */
        const targetValue = selectedKey.match(/not-exist-(\S*)/)[1];
        if (_isEmpty(keyEntities[targetValue])) {
          cacheValue = targetValue;
        } else {
          /**
           * 典型的场景是: 假设我们选中了 0-0 这个节点，此时 selectedKeys=Set('0-0')，
           * 输入框会显示 0-0 的 label。当 treeData 发生更新，假设此时 0-0 在 treeData
           * 中不存在，则 selectedKeys=Set('not-exist-0-0')，此时输入框显示的是 0-0，
           * 也就是显示 not-exist- 后的内容。当treeData再次更新，假设此时 0-0 在 treeData
           * 中存在，则 selectedKeys=Set('0-0')，此时输入框显示 0-0 的 label。 这个地
           * 方做的操作就是，为了例子中第二次更新后 0-0 label 能够正常显示。
           */
          /**
           * The typical scenario is: suppose we select the 0-0 node, at this time
           *  selectedKeys=Set('0-0'), the input box will display a 0-0 label. When
           *  treeData is updated, assuming 0-0 does not exist in treeData at this
           *  time, then selectedKeys=Set('not-exist-0-0'), at this time the input
           *  box displays 0-0, which means not-exist -After the content. When treeData
           *  is updated again, assuming that 0-0 exists in treeData at this time,
           *  then selectedKeys=Set('0-0'), and the input box displays a label of
           *  0-0 at this time. The operation done here is for the 0-0 label to be
           *  displayed normally after the second update in the example.
           */
          cacheValue = keyEntities[targetValue].valuePath;
        }
      } else {
        cacheValue = selectedKey;
      }
      /* selectedKeys match keyEntities */
    } else {
      /* selectedKeys match keyEntities */
      cacheValue = keyEntities[selectedKey].valuePath;
    }
    return cacheValue;
  }
  collectOptions() {
    let init = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    const {
      treeData,
      value,
      defaultValue
    } = this.getProps();
    const keyEntities = convertDataToEntities(treeData);
    this._adapter.rePositionDropdown();
    let cacheValue;
    /* when mount */
    if (init) {
      cacheValue = defaultValue;
    } else if (!_isEmpty(keyEntities)) {
      cacheValue = this._getCacheValue(keyEntities);
    }
    const selectedValue = !this._isControlledComponent() ? cacheValue : _isUndefined(value) ? [] : value;
    if (isValid(selectedValue)) {
      this.updateSelectedKey(selectedValue, keyEntities);
    } else {
      this._adapter.updateStates({
        keyEntities
      });
    }
  }
  // call when props.value change
  handleValueChange(value) {
    const {
      keyEntities
    } = this.getStates();
    this.updateSelectedKey(value, keyEntities);
  }
  /**
   * When single selection, the clear objects of
   * selectedKeys, activeKeys, filteredKeys, input, etc.
   */
  _getClearSelectedKey(filterable) {
    const updateStates = {};
    const {
      searchPlaceholder,
      placeholder,
      multiple
    } = this.getProps();
    updateStates.selectedKeys = new Set([]);
    updateStates.activeKeys = new Set([]);
    updateStates.filteredKeys = new Set([]);
    if (filterable && !multiple) {
      updateStates.inputPlaceHolder = searchPlaceholder || placeholder || '';
      updateStates.inputValue = '';
    }
    return updateStates;
  }
  updateSelectedKey(value, keyEntities) {
    const {
      changeOnSelect,
      onChangeWithObject,
      multiple
    } = this.getProps();
    const {
      activeKeys,
      loading,
      keyEntities: keyEntityState,
      selectedKeys: selectedKeysState
    } = this.getStates();
    const loadingKeys = this._adapter.getLoadingKeyRefValue();
    const filterable = this._isFilterable();
    const loadingActive = [...activeKeys].filter(i => loadingKeys.has(i));
    const normalizedValue = normalizedArr(value);
    const valuePath = onChangeWithObject && _isObject(normalizedValue[0]) ? normalizedValue.map(i => i.value) : normalizedValue;
    const selectedKeys = getKeysByValuePath(valuePath);
    let updateStates = {};
    const selectedKey = selectedKeys.length > 0 ? selectedKeys[0] : undefined;
    const selectedItem = selectedKey ? keyEntities[selectedKey] : undefined;
    if (selectedItem) {
      /**
       * When changeOnSelect is turned on, or the target option is a leaf option,
       * the option is considered to be selected, even if the option is disabled
       */
      if (changeOnSelect || this._isLeaf(selectedItem.data)) {
        updateStates.selectedKeys = new Set([selectedKey]);
        if (!loadingActive.length) {
          updateStates.activeKeys = new Set(selectedItem.path);
        }
        if (filterable && !multiple) {
          const displayText = this.renderDisplayText(selectedKey, keyEntities);
          updateStates.inputPlaceHolder = displayText;
          /*
           *  displayText should not be assign to inputValue,
           *  cause inputValue should only change by user enter
           */
          // updateStates.inputValue = displayText;
        }
        /**
         * If selectedKeys does not meet the update conditions,
         * and state.selectedKeys is the same as selectedKeys
         * at this time, state.selectedKeys should be cleared.
         * A typical scenario is:
         * The originally selected node is the leaf node, but
         * after props.treeData is dynamically updated, the node
         * is a non-leaf node. At this point, selectedKeys should
         * be cleared.
         */
      } else if (_isEqual(selectedKeys, Array.from(selectedKeysState))) {
        updateStates = this._getClearSelectedKey(filterable);
      }
    } else if (value && value.length) {
      const val = valuePath[valuePath.length - 1];
      const key = `not-exist-${val}`;
      const optionNotExist = {
        data: {
          label: val,
          value: val
        },
        key,
        path: valuePath,
        _notExist: true
      };
      updateStates.selectedKeys = new Set([key]);
      if (filterable && !multiple) {
        const displayText = this._defaultRenderText(valuePath);
        updateStates.inputPlaceHolder = displayText;
        /*
         *  displayText should not be assign to inputValue,
         *  cause inputValue should only change by user enter
         */
        // updateStates.inputValue = displayText;
      }
      keyEntities[key] = optionNotExist;
      // Fix: 1155, if the data is loaded asynchronously to update treeData, the emptying operation should not be done when entering the updateSelectedKey method
    } else if (loading) {
      // Use assign to avoid overwriting the'not-exist- * 'property of keyEntities after asynchronous loading
      // Overwriting'not-exist- * 'will cause selectionContent to be emptied unexpectedly when clicking on a dropDown item
      updateStates.keyEntities = _assign(keyEntityState, keyEntities);
      this._adapter.updateStates(updateStates);
      return;
    } else {
      updateStates = this._getClearSelectedKey(filterable);
    }
    updateStates.keyEntities = keyEntities;
    this._adapter.updateStates(updateStates);
  }
  open() {
    const filterable = this._isFilterable();
    const {
      multiple
    } = this.getProps();
    this._adapter.openMenu();
    if (filterable) {
      this._clearInput();
      !multiple && this.toggle2SearchInput(true);
    }
    if (this._isControlledComponent()) {
      this.reCalcActiveKeys();
    }
    this._adapter.notifyDropdownVisibleChange(true);
    this._adapter.registerClickOutsideHandler(e => this.close(e));
    this._setEmptyContentMinWidth();
  }
  reCalcActiveKeys() {
    const {
      selectedKeys,
      activeKeys,
      keyEntities
    } = this.getStates();
    const selectedKey = [...selectedKeys][0];
    const selectedItem = keyEntities[selectedKey];
    if (!selectedItem) {
      return;
    }
    const newActiveKeys = new Set(selectedItem.path);
    if (!_isEqual(newActiveKeys, activeKeys)) {
      this._adapter.updateStates({
        activeKeys: newActiveKeys
      });
    }
  }
  close(e, key) {
    const {
      multiple
    } = this.getProps();
    this._adapter.closeMenu();
    this._adapter.notifyDropdownVisibleChange(false);
    this._adapter.unregisterClickOutsideHandler();
    if (this._isFilterable()) {
      const {
        selectedKeys,
        isSearching
      } = this.getStates();
      let inputValue = '';
      if (key && !multiple) {
        inputValue = this.renderDisplayText(key);
      } else if (selectedKeys.size && !multiple) {
        inputValue = this.renderDisplayText([...selectedKeys][0]);
      }
      this._adapter.updateStates({
        inputValue
      });
      !multiple && this.toggle2SearchInput(false);
      !multiple && this._adapter.updateFocusState(false);
    }
    this._notifyBlur(e);
  }
  focus() {
    const {
      filterTreeNode
    } = this.getProps();
    if (filterTreeNode) {
      this._adapter.focusInput();
    }
    this._adapter.updateFocusState(true);
  }
  blur() {
    const {
      filterTreeNode
    } = this.getProps();
    if (filterTreeNode) {
      this._adapter.blurInput();
    }
    this._adapter.updateFocusState(false);
  }
  toggle2SearchInput(isShow) {
    if (isShow) {
      this._adapter.toggleInputShow(isShow, () => this.focus());
    } else {
      this._adapter.toggleInputShow(isShow, () => undefined);
    }
  }
  handleItemClick(e, item) {
    const isDisabled = this._isDisabled();
    if (isDisabled) {
      return;
    }
    this.handleSingleSelect(e, item);
    this._adapter.rePositionDropdown();
  }
  handleItemHover(e, item) {
    const isDisabled = this._isDisabled();
    if (isDisabled) {
      return;
    }
    this.handleShowNextByHover(item);
  }
  handleShowNextByHover(item) {
    const {
      keyEntities
    } = this.getStates();
    const {
      data,
      key
    } = item;
    const isLeaf = this._isLeaf(data);
    const activeKeys = keyEntities[key].path;
    this._adapter.updateStates({
      activeKeys: new Set(activeKeys)
    });
    if (!isLeaf) {
      this.notifyIfLoadData(item);
    }
  }
  onItemCheckboxClick(item) {
    const isDisabled = this._isDisabled();
    if (isDisabled) {
      return;
    }
    this._handleMultipleSelect(item);
    this._adapter.rePositionDropdown();
  }
  handleClick(e) {
    const isDisabled = this._isDisabled();
    const isFilterable = this._isFilterable();
    const {
      isOpen
    } = this.getStates();
    if (isDisabled) {
      return;
    } else if (!isOpen) {
      this.open();
      this._notifyFocus(e);
    } else if (isOpen && !isFilterable) {
      this.close(e);
    }
  }
  /**
   * A11y: simulate selection click
   */
  /* istanbul ignore next */
  handleSelectionEnterPress(keyboardEvent) {
    if (isEnterPress(keyboardEvent)) {
      this.handleClick(keyboardEvent);
    }
  }
  toggleHoverState(bool) {
    this._adapter.toggleHovering(bool);
  }
  _defaultRenderText(path, displayRender) {
    const separator = this.getProp('separator');
    if (displayRender && typeof displayRender === 'function') {
      return displayRender(path);
    } else {
      return path.join(separator);
    }
  }
  renderDisplayText(targetKey, keyEntities) {
    const renderFunc = this.getProp('displayRender');
    const displayProp = this.getProp('displayProp');
    const displayPath = this.getItemPropPath(targetKey, displayProp, keyEntities);
    return this._defaultRenderText(displayPath, renderFunc);
  }
  handleNodeLoad(item) {
    const {
      data,
      key
    } = item;
    const prevLoadingKeys = new Set(this._adapter.getLoadingKeyRefValue());
    const prevLoadedKeys = new Set(this._adapter.getLoadedKeyRefValue());
    const newLoadedKeys = prevLoadedKeys.add(key);
    const newLoadingKeys = new Set([...prevLoadingKeys]);
    newLoadingKeys.delete(key);
    // onLoad should trigger before internal setState to avoid `loadData` trigger twice.
    this._adapter.notifyOnLoad(newLoadedKeys, data);
    this._adapter.updateLoadingKeyRefValue(newLoadingKeys);
    this._adapter.updateLoadedKeyRefValue(newLoadedKeys);
    this._adapter.updateStates({
      loadingKeys: newLoadingKeys,
      loadedKeys: newLoadedKeys
    });
  }
  notifyIfLoadData(item) {
    const {
      data,
      key
    } = item;
    this._adapter.updateStates({
      loading: false
    });
    if (!data.isLeaf && !data.children && this.getProp('loadData')) {
      const loadedKeys = this._adapter.getLoadedKeyRefValue();
      const loadingKeys = new Set(this._adapter.getLoadingKeyRefValue());
      if (loadedKeys.has(key) || loadingKeys.has(key)) {
        return;
      }
      this._adapter.updateStates({
        loading: true
      });
      const {
        keyEntities
      } = this.getStates();
      const optionPath = this.getItemPropPath(key, [], keyEntities);
      const newLoadingKeys = loadingKeys.add(key);
      this._adapter.updateLoadingKeyRefValue(newLoadingKeys);
      this._adapter.updateStates({
        loadingKeys: newLoadingKeys
      });
      this._adapter.notifyLoadData(optionPath, this.handleNodeLoad.bind(this, item));
    }
  }
  handleSingleSelect(e, item) {
    const {
      changeOnSelect: allowChange,
      filterLeafOnly,
      multiple,
      enableLeafClick
    } = this.getProps();
    const {
      keyEntities,
      selectedKeys,
      isSearching
    } = this.getStates();
    const filterable = this._isFilterable();
    const {
      data,
      key
    } = item;
    const isLeaf = this._isLeaf(data);
    const activeKeys = keyEntities[key].path;
    const selectedKey = [key];
    const hasChanged = key !== [...selectedKeys][0];
    if (!isLeaf && !allowChange && !isSearching) {
      this._adapter.updateStates({
        activeKeys: new Set(activeKeys)
      });
      this.notifyIfLoadData(item);
      return;
    }
    if (multiple) {
      this._adapter.updateStates({
        activeKeys: new Set(activeKeys)
      });
      if (isLeaf && enableLeafClick) {
        this.onItemCheckboxClick(item);
      }
    } else {
      this._adapter.notifySelect(data.value);
      if (hasChanged) {
        this._notifyChange(item);
        this.notifyIfLoadData(item);
        if (this._isControlledComponent()) {
          this._adapter.updateStates({
            activeKeys: new Set(activeKeys)
          });
          if (isLeaf) {
            this.close(e);
          }
          return;
        }
        this._adapter.updateStates({
          activeKeys: new Set(activeKeys),
          selectedKeys: new Set(selectedKey)
        });
        const displayText = this.renderDisplayText(key);
        if (filterable) {
          this._adapter.updateInputPlaceHolder(displayText);
        }
        if (isLeaf) {
          this.close(e, key);
        } else if (!filterLeafOnly && isSearching) {
          this.close(e, key);
        }
      } else {
        this.close(e);
      }
    }
  }
  _handleMultipleSelect(item) {
    const {
      checkRelation
    } = this.getProps();
    if (checkRelation === strings.RELATED) {
      this._handleRelatedMultipleSelect(item);
    } else if (checkRelation === 'unRelated') {
      this._handleUnRelatedMultipleSelect(item);
    }
    this._adapter.updateStates({
      inputValue: ''
    });
  }
  _handleRelatedMultipleSelect(item) {
    const {
      key
    } = item;
    const {
      checkedKeys,
      keyEntities,
      resolvedCheckedKeys
    } = this.getStates();
    const {
      autoMergeValue,
      max,
      disableStrictly,
      leafOnly
    } = this.getProps();
    // prev checked status
    const prevCheckedStatus = checkedKeys.has(key);
    // next checked status
    const curCheckedStatus = disableStrictly ? this.calcCheckedStatus(!prevCheckedStatus, key) : !prevCheckedStatus;
    // calculate all key of nodes that are checked or half checked
    const {
      checkedKeys: curCheckedKeys,
      halfCheckedKeys: curHalfCheckedKeys
    } = disableStrictly ? this.calcNonDisabledCheckedKeys(key, curCheckedStatus) : this.calcCheckedKeys(key, curCheckedStatus);
    const mergeType = calcMergeType(autoMergeValue, leafOnly);
    const isLeafOnlyMerge = mergeType === strings.LEAF_ONLY_MERGE_TYPE;
    const isNoneMerge = mergeType === strings.NONE_MERGE_TYPE;
    const curResolvedCheckedKeys = new Set(normalizeKeyList(curCheckedKeys, keyEntities, isLeafOnlyMerge));
    const curRealCheckedKeys = isNoneMerge ? curCheckedKeys : curResolvedCheckedKeys;
    if (_isNumber(max)) {
      if (!isNoneMerge) {
        // When it exceeds max, the quantity is allowed to be reduced, and no further increase is allowed
        if (resolvedCheckedKeys.size < curResolvedCheckedKeys.size && curResolvedCheckedKeys.size > max) {
          const checkedEntities = [];
          curResolvedCheckedKeys.forEach(itemKey => {
            checkedEntities.push(keyEntities[itemKey]);
          });
          this._adapter.notifyOnExceed(checkedEntities);
          return;
        }
      } else {
        // When it exceeds max, the quantity is allowed to be reduced, and no further increase is allowed
        if (checkedKeys.size < curCheckedKeys.size && curCheckedKeys.size > max) {
          const checkedEntities = [];
          curCheckedKeys.forEach(itemKey => {
            checkedEntities.push(keyEntities[itemKey]);
          });
          this._adapter.notifyOnExceed(checkedEntities);
          return;
        }
      }
    }
    if (!this._isControlledComponent()) {
      this._adapter.updateStates({
        checkedKeys: curCheckedKeys,
        halfCheckedKeys: curHalfCheckedKeys,
        resolvedCheckedKeys: curResolvedCheckedKeys
      });
    }
    // The click event during multiple selection will definitely cause the checked state of node to change,
    // so there is no need to judge the value to change.
    this._notifyChange(curRealCheckedKeys);
    if (curCheckedStatus) {
      this._notifySelect(curRealCheckedKeys);
    }
  }
  _handleUnRelatedMultipleSelect(item) {
    const {
      key
    } = item;
    const {
      checkedKeys,
      keyEntities
    } = this.getStates();
    const {
      max
    } = this.getProps();
    const newCheckedKeys = new Set(checkedKeys);
    let targetStatus;
    const prevCheckedStatus = checkedKeys.has(key);
    if (prevCheckedStatus) {
      newCheckedKeys.delete(key);
      targetStatus = false;
    } else {
      // 查看是否超出 max
      if (_isNumber(max)) {
        if (checkedKeys.size >= max) {
          const checkedEntities = [];
          checkedKeys.forEach(itemKey => {
            checkedEntities.push(keyEntities[itemKey]);
          });
          this._adapter.notifyOnExceed(checkedEntities);
          return;
        }
      }
      newCheckedKeys.add(key);
      targetStatus = true;
    }
    if (!this._isControlledComponent()) {
      this._adapter.updateStates({
        checkedKeys: newCheckedKeys
      });
    }
    this._notifyChange(newCheckedKeys);
    if (targetStatus) {
      this._notifySelect(newCheckedKeys);
    }
  }
  calcNonDisabledCheckedKeys(eventKey, targetStatus) {
    const {
      keyEntities,
      disabledKeys
    } = this.getStates();
    const checkedKeys = new Set(this.getState('checkedKeys'));
    const descendantKeys = normalizeKeyList(findDescendantKeys([eventKey], keyEntities, false), keyEntities, true);
    const hasDisabled = descendantKeys.some(key => disabledKeys.has(key));
    if (!hasDisabled) {
      return this.calcCheckedKeys(eventKey, targetStatus);
    }
    const nonDisabled = descendantKeys.filter(key => !disabledKeys.has(key));
    const newCheckedKeys = targetStatus ? [...nonDisabled, ...checkedKeys] : _difference(normalizeKeyList([...checkedKeys], keyEntities, true), nonDisabled);
    return calcCheckedKeys(newCheckedKeys, keyEntities);
  }
  calcCheckedStatus(targetStatus, eventKey) {
    if (!targetStatus) {
      return targetStatus;
    }
    const {
      checkedKeys,
      keyEntities,
      disabledKeys
    } = this.getStates();
    const descendantKeys = normalizeKeyList(findDescendantKeys([eventKey], keyEntities, false), keyEntities, true);
    const hasDisabled = descendantKeys.some(key => disabledKeys.has(key));
    if (!hasDisabled) {
      return targetStatus;
    }
    const nonDisabledKeys = descendantKeys.filter(key => !disabledKeys.has(key));
    const allChecked = nonDisabledKeys.every(key => checkedKeys.has(key));
    return !allChecked;
  }
  _notifySelect(keys) {
    const {
      keyEntities
    } = this.getStates();
    const values = [];
    keys.forEach(key => {
      var _a, _b;
      const valueItem = (_b = (_a = keyEntities[key]) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.value;
      valueItem !== undefined && values.push(valueItem);
    });
    const formatValue = values.length === 1 ? values[0] : values;
    this._adapter.notifySelect(formatValue);
  }
  /**
   * calculate all key of nodes that are checked or half checked
   * @param {string} key key of node
   * @param {boolean} curCheckedStatus checked status of node
   */
  calcCheckedKeys(key, curCheckedStatus) {
    const {
      keyEntities
    } = this.getStates();
    const checkedKeys = new Set(this.getState('checkedKeys'));
    const halfCheckedKeys = new Set(this.getState('halfCheckedKeys'));
    return curCheckedStatus ? calcCheckedKeysForChecked(key, keyEntities, checkedKeys, halfCheckedKeys) : calcCheckedKeysForUnchecked(key, keyEntities, checkedKeys, halfCheckedKeys);
  }
  handleInputChange(sugInput) {
    this._adapter.updateInputValue(sugInput);
    const {
      keyEntities
    } = this.getStates();
    const {
      treeNodeFilterProp,
      filterTreeNode,
      filterLeafOnly
    } = this.getProps();
    let filteredKeys = [];
    if (sugInput) {
      filteredKeys = Object.values(keyEntities).filter(item => {
        const {
          key,
          _notExist,
          data
        } = item;
        if (_notExist) {
          return false;
        }
        const filteredPath = this.getItemPropPath(key, treeNodeFilterProp);
        return filter(sugInput, data, filterTreeNode, filteredPath);
      }).filter(item => filterTreeNode && !filterLeafOnly || this._isLeaf(item)).map(item => item.key);
    }
    this._adapter.updateStates({
      isSearching: Boolean(sugInput),
      filteredKeys: new Set(filteredKeys)
    });
    this._adapter.notifyOnSearch(sugInput);
    this._adapter.rePositionDropdown();
  }
  handleClear() {
    const {
      isSearching
    } = this.getStates();
    const {
      searchPlaceholder,
      placeholder,
      multiple
    } = this.getProps();
    const isFilterable = this._isFilterable();
    const isControlled = this._isControlledComponent();
    const newState = {};
    if (multiple) {
      newState.isSearching = false;
      this._adapter.updateInputValue('');
      this._adapter.notifyOnSearch('');
      newState.checkedKeys = new Set([]);
      newState.halfCheckedKeys = new Set([]);
      newState.selectedKeys = new Set([]);
      newState.activeKeys = new Set([]);
      newState.resolvedCheckedKeys = new Set([]);
      this._adapter.notifyChange([]);
    } else {
      // if click clearBtn when not searching, clear selected and active values as well
      if (isFilterable && isSearching) {
        newState.isSearching = false;
        this._adapter.updateInputValue('');
        this._adapter.notifyOnSearch('');
      } else {
        if (isFilterable) {
          newState.inputValue = '';
          newState.inputPlaceHolder = searchPlaceholder || placeholder || '';
          this._adapter.updateInputValue('');
          this._adapter.notifyOnSearch('');
        }
        if (!isControlled) {
          newState.selectedKeys = new Set([]);
        }
        newState.activeKeys = new Set([]);
        newState.filteredKeys = new Set([]);
        this._adapter.notifyChange([]);
      }
    }
    this._adapter.updateStates(newState);
    this._adapter.notifyClear();
    this._adapter.rePositionDropdown();
  }
  /**
   * A11y: simulate clear button click
   */
  /* istanbul ignore next */
  handleClearEnterPress(keyboardEvent) {
    if (isEnterPress(keyboardEvent)) {
      this.handleClear();
    }
  }
  getRenderData() {
    const {
      keyEntities,
      isSearching
    } = this.getStates();
    const isFilterable = this._isFilterable();
    if (isSearching && isFilterable) {
      return this.getFilteredData();
    }
    return Object.values(keyEntities).filter(item => item.parentKey === null && !item._notExist)
    // @ts-ignore
    .sort((a, b) => parseInt(a.ind, 10) - parseInt(b.ind, 10));
  }
  getFilteredData() {
    const {
      treeNodeFilterProp,
      filterSorter
    } = this.getProps();
    const {
      filteredKeys,
      keyEntities,
      inputValue
    } = this.getStates();
    const filteredList = [];
    const filteredKeyArr = [...filteredKeys];
    filteredKeyArr.forEach(key => {
      const item = keyEntities[key];
      if (!item) {
        return;
      }
      const pathData = this.getItemPropPath(key, []);
      const itemSearchPath = pathData.map(item => item[treeNodeFilterProp]);
      const isDisabled = this._isOptionDisabled(key, keyEntities);
      filteredList.push({
        data: item.data,
        pathData,
        key,
        disabled: isDisabled,
        searchText: itemSearchPath
      });
    });
    if (_isFunction(filterSorter)) {
      filteredList.sort((a, b) => {
        return filterSorter(a.pathData, b.pathData, inputValue);
      });
    }
    return filteredList;
  }
  handleListScroll(e, ind) {
    const {
      activeKeys,
      keyEntities
    } = this.getStates();
    const lastActiveKey = [...activeKeys][activeKeys.size - 1];
    const data = lastActiveKey ? _get(keyEntities, [lastActiveKey, 'data'], null) : null;
    this._adapter.notifyListScroll(e, {
      panelIndex: ind,
      activeNode: data
    });
  }
}