import _isObject from "lodash/isObject";
import _noop from "lodash/noop";
import _isNumber from "lodash/isNumber";
import _isFunction from "lodash/isFunction";
import _isEmpty from "lodash/isEmpty";
import _isString from "lodash/isString";
import _isEqual from "lodash/isEqual";
import _isSet from "lodash/isSet";
import React, { Fragment } from 'react';
import cls from 'classnames';
import PropTypes from 'prop-types';
import CascaderFoundation from '@douyinfe/semi-foundation/lib/es/cascader/foundation';
import { cssClasses, strings } from '@douyinfe/semi-foundation/lib/es/cascader/constants';
import { numbers as popoverNumbers } from '@douyinfe/semi-foundation/lib/es/popover/constants';
import '@douyinfe/semi-foundation/lib/es/cascader/cascader.css';
import { IconClear, IconChevronDown } from '@douyinfe/semi-icons';
import { convertDataToEntities, calcMergeType, getKeyByValuePath } from '@douyinfe/semi-foundation/lib/es/cascader/util';
import { calcCheckedKeys, normalizeKeyList, calcDisabledKeys } from '@douyinfe/semi-foundation/lib/es/tree/treeUtil';
import ConfigContext from '../configProvider/context';
import BaseComponent from '../_base/baseComponent';
import Input from '../input/index';
import Popover from '../popover/index';
import Item from './item';
import Trigger from '../trigger';
import Tag from '../tag';
import TagInput from '../tagInput';
import { getDefaultPropsFromGlobalConfig, isSemiIcon } from '../_utils';
const prefixcls = cssClasses.PREFIX;
const resetkey = 0;
class Cascader extends BaseComponent {
  constructor(props) {
    super(props);
    // ref method
    this.search = value => {
      this.handleInputChange(value);
    };
    this.handleInputChange = value => {
      this.foundation.handleInputChange(value);
    };
    this.handleTagRemoveInTrigger = pos => {
      this.foundation.handleTagRemoveInTrigger(pos);
    };
    this.handleTagClose = (tagChildren, e, tagKey) => {
      // When value has not changed, prevent clicking tag closeBtn to close tag
      e.preventDefault();
      this.foundation.handleTagRemoveByKey(tagKey);
    };
    this.renderTagItem = (nodeKey, idx) => {
      const {
        keyEntities,
        disabledKeys
      } = this.state;
      const {
        size,
        disabled,
        displayProp,
        displayRender,
        disableStrictly
      } = this.props;
      if (keyEntities[nodeKey]) {
        const isDisabled = disabled || keyEntities[nodeKey].data.disabled || disableStrictly && disabledKeys.has(nodeKey);
        const tagCls = cls(`${prefixcls}-selection-tag`, {
          [`${prefixcls}-selection-tag-disabled`]: isDisabled
        });
        // custom render tags
        if (_isFunction(displayRender)) {
          return displayRender(keyEntities[nodeKey], idx);
          // default render tags
        } else {
          return /*#__PURE__*/React.createElement(Tag, {
            size: size === 'default' ? 'large' : size,
            key: `tag-${nodeKey}-${idx}`,
            color: "white",
            tagKey: nodeKey,
            className: tagCls,
            closable: true,
            onClose: this.handleTagClose
          }, keyEntities[nodeKey].data[displayProp]);
        }
      }
      return null;
    };
    this.onRemoveInTagInput = v => {
      this.foundation.handleTagRemoveByKey(v);
    };
    this.handleItemClick = (e, item) => {
      this.foundation.handleItemClick(e, item);
    };
    this.handleItemHover = (e, item) => {
      this.foundation.handleItemHover(e, item);
    };
    this.onItemCheckboxClick = item => {
      this.foundation.onItemCheckboxClick(item);
    };
    this.handleListScroll = (e, ind) => {
      this.foundation.handleListScroll(e, ind);
    };
    this.renderContent = () => {
      const {
        inputValue,
        isSearching,
        activeKeys,
        selectedKeys,
        checkedKeys,
        halfCheckedKeys,
        loadedKeys,
        loadingKeys
      } = this.state;
      const {
        filterTreeNode,
        dropdownClassName,
        dropdownStyle,
        loadData,
        emptyContent,
        separator,
        topSlot,
        bottomSlot,
        showNext,
        multiple,
        filterRender,
        virtualizeInSearch,
        expandIcon
      } = this.props;
      const searchable = Boolean(filterTreeNode) && isSearching;
      const popoverCls = cls(dropdownClassName, `${prefixcls}-popover`);
      const renderData = this.foundation.getRenderData();
      const isEmpty = !renderData || !renderData.length;
      const realDropDownStyle = isEmpty ? Object.assign(Object.assign({}, dropdownStyle), {
        minWidth: this.state.emptyContentMinWidth
      }) : dropdownStyle;
      const content = /*#__PURE__*/React.createElement("div", {
        className: popoverCls,
        role: "listbox",
        style: realDropDownStyle,
        onKeyDown: this.foundation.handleKeyDown,
        ref: this.optionContainerEl
      }, topSlot, /*#__PURE__*/React.createElement(Item, {
        activeKeys: activeKeys,
        selectedKeys: selectedKeys,
        separator: separator,
        loadedKeys: loadedKeys,
        loadingKeys: loadingKeys,
        onItemClick: this.handleItemClick,
        onItemHover: this.handleItemHover,
        showNext: showNext,
        onItemCheckboxClick: this.onItemCheckboxClick,
        onListScroll: this.handleListScroll,
        searchable: searchable,
        keyword: inputValue,
        emptyContent: emptyContent,
        loadData: loadData,
        data: renderData,
        multiple: multiple,
        checkedKeys: checkedKeys,
        halfCheckedKeys: halfCheckedKeys,
        filterRender: filterRender,
        virtualize: virtualizeInSearch,
        expandIcon: expandIcon
      }), bottomSlot);
      return content;
    };
    this.renderPlusN = hiddenTag => {
      const {
        disabled,
        showRestTagsPopover,
        restTagsPopoverProps
      } = this.props;
      const plusNCls = cls(`${prefixcls}-selection-n`, {
        [`${prefixcls}-selection-n-disabled`]: disabled
      });
      const renderPlusNChildren = /*#__PURE__*/React.createElement("span", {
        className: plusNCls
      }, "+", hiddenTag.length);
      return showRestTagsPopover ? (/*#__PURE__*/React.createElement(Popover, Object.assign({
        content: hiddenTag,
        showArrow: true,
        trigger: "hover",
        position: "top",
        autoAdjustOverflow: true
      }, restTagsPopoverProps), renderPlusNChildren)) : renderPlusNChildren;
    };
    this.renderMultipleTags = () => {
      const {
        autoMergeValue,
        maxTagCount,
        checkRelation
      } = this.props;
      const {
        checkedKeys,
        resolvedCheckedKeys
      } = this.state;
      const realKeys = this.mergeType === strings.NONE_MERGE_TYPE || checkRelation === strings.UN_RELATED ? checkedKeys : resolvedCheckedKeys;
      const displayTag = [];
      const hiddenTag = [];
      [...realKeys].forEach((checkedKey, idx) => {
        const notExceedMaxTagCount = !_isNumber(maxTagCount) || maxTagCount >= idx + 1;
        const item = this.renderTagItem(checkedKey, idx);
        if (notExceedMaxTagCount) {
          displayTag.push(item);
        } else {
          hiddenTag.push(item);
        }
      });
      return /*#__PURE__*/React.createElement(React.Fragment, null, displayTag, !_isEmpty(hiddenTag) && this.renderPlusN(hiddenTag));
    };
    this.renderDisplayText = () => {
      const {
        displayProp,
        separator,
        displayRender
      } = this.props;
      const {
        selectedKeys
      } = this.state;
      let displayText = '';
      if (selectedKeys.size) {
        const displayPath = this.foundation.getItemPropPath([...selectedKeys][0], displayProp);
        if (displayRender && typeof displayRender === 'function') {
          displayText = displayRender(displayPath);
        } else {
          displayText = displayPath.map((path, index) => (/*#__PURE__*/React.createElement(Fragment, {
            key: `${path}-${index}`
          }, index < displayPath.length - 1 ? (/*#__PURE__*/React.createElement(React.Fragment, null, path, separator)) : path)));
        }
      }
      return displayText;
    };
    this.renderSelectContent = () => {
      const {
        placeholder,
        filterTreeNode,
        multiple,
        searchPosition
      } = this.props;
      const {
        checkedKeys
      } = this.state;
      const searchable = Boolean(filterTreeNode) && searchPosition === strings.SEARCH_POSITION_TRIGGER;
      if (!searchable) {
        if (multiple) {
          if (checkedKeys.size === 0) {
            return /*#__PURE__*/React.createElement("span", {
              className: `${prefixcls}-selection-placeholder`
            }, placeholder);
          }
          return this.renderMultipleTags();
        } else {
          const displayText = this.renderDisplayText();
          const spanCls = cls({
            [`${prefixcls}-selection-placeholder`]: !displayText
          });
          return /*#__PURE__*/React.createElement("span", {
            className: spanCls
          }, displayText ? displayText : placeholder);
        }
      }
      const input = multiple ? this.renderTagInput() : this.renderInput();
      return input;
    };
    this.renderSuffix = () => {
      const {
        suffix
      } = this.props;
      const suffixWrapperCls = cls({
        [`${prefixcls}-suffix`]: true,
        [`${prefixcls}-suffix-text`]: suffix && _isString(suffix),
        [`${prefixcls}-suffix-icon`]: isSemiIcon(suffix)
      });
      return /*#__PURE__*/React.createElement("div", {
        className: suffixWrapperCls,
        "x-semi-prop": "suffix"
      }, suffix);
    };
    this.renderPrefix = () => {
      const {
        prefix,
        insetLabel,
        insetLabelId
      } = this.props;
      const labelNode = prefix || insetLabel;
      const prefixWrapperCls = cls({
        [`${prefixcls}-prefix`]: true,
        // to be doublechecked
        [`${prefixcls}-inset-label`]: insetLabel,
        [`${prefixcls}-prefix-text`]: labelNode && _isString(labelNode),
        [`${prefixcls}-prefix-icon`]: isSemiIcon(labelNode)
      });
      return /*#__PURE__*/React.createElement("div", {
        className: prefixWrapperCls,
        id: insetLabelId,
        "x-semi-prop": "prefix,insetLabel"
      }, labelNode);
    };
    this.renderCustomTrigger = () => {
      var _a;
      const {
        disabled,
        triggerRender,
        multiple
      } = this.props;
      const {
        selectedKeys,
        inputValue,
        inputPlaceHolder,
        resolvedCheckedKeys,
        checkedKeys,
        keyEntities
      } = this.state;
      let realValue;
      if (multiple) {
        if (this.mergeType === strings.NONE_MERGE_TYPE) {
          realValue = new Set();
          checkedKeys.forEach(key => {
            var _a;
            realValue.add((_a = keyEntities[key]) === null || _a === void 0 ? void 0 : _a.pos);
          });
        } else {
          realValue = new Set();
          resolvedCheckedKeys.forEach(key => {
            var _a;
            realValue.add((_a = keyEntities[key]) === null || _a === void 0 ? void 0 : _a.pos);
          });
        }
      } else {
        realValue = (_a = keyEntities[[...selectedKeys][0]]) === null || _a === void 0 ? void 0 : _a.pos;
      }
      return /*#__PURE__*/React.createElement(Trigger, {
        value: realValue,
        inputValue: inputValue,
        onChange: this.handleInputChange,
        onClear: this.handleClear,
        placeholder: inputPlaceHolder,
        disabled: disabled,
        triggerRender: triggerRender,
        componentName: 'Cascader',
        componentProps: Object.assign({}, this.props),
        onSearch: this.handleInputChange,
        onRemove: this.handleTagRemoveInTrigger
      });
    };
    this.handleMouseOver = () => {
      this.foundation.toggleHoverState(true);
    };
    this.handleMouseLeave = () => {
      this.foundation.toggleHoverState(false);
    };
    this.handleClear = e => {
      e && e.stopPropagation();
      this.foundation.handleClear();
    };
    /**
     * A11y: simulate clear button click
     */
    /* istanbul ignore next */
    this.handleClearEnterPress = e => {
      e && e.stopPropagation();
      this.foundation.handleClearEnterPress(e);
    };
    this.showClearBtn = () => {
      const {
        showClear,
        disabled,
        multiple
      } = this.props;
      const {
        selectedKeys,
        isOpen,
        isHovering,
        checkedKeys,
        inputValue
      } = this.state;
      const hasValue = selectedKeys.size;
      const multipleWithHaveValue = multiple && checkedKeys.size;
      return showClear && (inputValue || hasValue || multipleWithHaveValue) && !disabled && (isOpen || isHovering);
    };
    this.renderClearBtn = () => {
      const clearCls = cls(`${prefixcls}-clearbtn`);
      const {
        clearIcon
      } = this.props;
      const allowClear = this.showClearBtn();
      if (allowClear) {
        return /*#__PURE__*/React.createElement("div", {
          className: clearCls,
          onClick: this.handleClear,
          onKeyPress: this.handleClearEnterPress,
          role: "button",
          tabIndex: 0
        }, clearIcon ? clearIcon : /*#__PURE__*/React.createElement(IconClear, null));
      }
      return null;
    };
    this.renderArrow = () => {
      const {
        arrowIcon
      } = this.props;
      const showClearBtn = this.showClearBtn();
      if (showClearBtn) {
        return null;
      }
      return arrowIcon ? (/*#__PURE__*/React.createElement("div", {
        className: cls(`${prefixcls}-arrow`),
        "x-semi-prop": "arrowIcon"
      }, arrowIcon)) : null;
    };
    this.renderSelection = () => {
      const {
        disabled,
        multiple,
        filterTreeNode,
        style,
        size,
        className,
        validateStatus,
        prefix,
        suffix,
        insetLabel,
        triggerRender,
        showClear,
        id,
        borderless
      } = this.props;
      const {
        isOpen,
        isFocus,
        isInput,
        checkedKeys
      } = this.state;
      const filterable = Boolean(filterTreeNode);
      const useCustomTrigger = typeof triggerRender === 'function';
      const classNames = useCustomTrigger ? cls(className) : cls(prefixcls, className, {
        [`${prefixcls}-borderless`]: borderless,
        [`${prefixcls}-focus`]: isFocus || isOpen && !isInput,
        [`${prefixcls}-disabled`]: disabled,
        [`${prefixcls}-single`]: true,
        [`${prefixcls}-filterable`]: filterable,
        [`${prefixcls}-error`]: validateStatus === 'error',
        [`${prefixcls}-warning`]: validateStatus === 'warning',
        [`${prefixcls}-small`]: size === 'small',
        [`${prefixcls}-large`]: size === 'large',
        [`${prefixcls}-with-prefix`]: prefix || insetLabel,
        [`${prefixcls}-with-suffix`]: suffix
      });
      const mouseEvent = showClear ? {
        onMouseEnter: () => this.handleMouseOver(),
        onMouseLeave: () => this.handleMouseLeave()
      } : {};
      const sectionCls = cls(`${prefixcls}-selection`, {
        [`${prefixcls}-selection-multiple`]: multiple && !_isEmpty(checkedKeys)
      });
      const inner = useCustomTrigger ? this.renderCustomTrigger() : [/*#__PURE__*/React.createElement(Fragment, {
        key: 'prefix'
      }, prefix || insetLabel ? this.renderPrefix() : null), /*#__PURE__*/React.createElement(Fragment, {
        key: 'selection'
      }, /*#__PURE__*/React.createElement("div", {
        className: sectionCls
      }, this.renderSelectContent())), /*#__PURE__*/React.createElement(Fragment, {
        key: 'suffix'
      }, suffix ? this.renderSuffix() : null), /*#__PURE__*/React.createElement(Fragment, {
        key: 'clearbtn'
      }, this.renderClearBtn()), /*#__PURE__*/React.createElement(Fragment, {
        key: 'arrow'
      }, this.renderArrow())];
      /**
       * Reasons for disabling the a11y eslint rule:
       * The following attributes(aria-controls,aria-expanded) will be automatically added by Tooltip, no need to declare here
       */
      return /*#__PURE__*/React.createElement("div", Object.assign({
        className: classNames,
        style: style,
        ref: this.triggerRef,
        onClick: e => this.foundation.handleClick(e),
        onKeyPress: e => this.foundation.handleSelectionEnterPress(e),
        "aria-invalid": this.props['aria-invalid'],
        "aria-errormessage": this.props['aria-errormessage'],
        "aria-label": this.props['aria-label'],
        "aria-labelledby": this.props['aria-labelledby'],
        "aria-describedby": this.props['aria-describedby'],
        "aria-required": this.props['aria-required'],
        id: id,
        onKeyDown: this.foundation.handleKeyDown
      }, mouseEvent, {
        // eslint-disable-next-line jsx-a11y/role-has-required-aria-props
        role: "combobox",
        tabIndex: 0
      }, this.getDataAttr(this.props)), inner);
    };
    this.state = {
      emptyContentMinWidth: null,
      disabledKeys: new Set(),
      isOpen: props.defaultOpen,
      /* By changing rePosKey, the dropdown position can be refreshed */
      rePosKey: resetkey,
      /* A data structure for storing cascader data items */
      keyEntities: {},
      /* Selected and show tick icon */
      selectedKeys: new Set([]),
      /* The key of the activated node */
      activeKeys: new Set([]),
      /* The key of the filtered node */
      filteredKeys: new Set([]),
      /* Value of input box */
      inputValue: '',
      /* Is searching */
      isSearching: false,
      /* The placeholder of input box */
      inputPlaceHolder: props.searchPlaceholder || props.placeholder,
      /* Cache props */
      prevProps: {},
      /* Is hovering */
      isHovering: false,
      /* Key of checked node, when multiple */
      checkedKeys: new Set([]),
      /* Key of half checked node, when multiple */
      halfCheckedKeys: new Set([]),
      /* Auto merged checkedKeys or leaf checkedKeys, when multiple */
      resolvedCheckedKeys: new Set([]),
      /* Keys of loaded item */
      loadedKeys: new Set(),
      /* Keys of loading item */
      loadingKeys: new Set(),
      /* Mark whether this rendering has triggered asynchronous loading of data */
      loading: false,
      showInput: false
    };
    this.options = {};
    this.isEmpty = false;
    this.mergeType = calcMergeType(props.autoMergeValue, props.leafOnly);
    this.inputRef = /*#__PURE__*/React.createRef();
    this.triggerRef = /*#__PURE__*/React.createRef();
    this.optionsRef = /*#__PURE__*/React.createRef();
    this.optionContainerEl = /*#__PURE__*/React.createRef();
    this.clickOutsideHandler = null;
    this.foundation = new CascaderFoundation(this.adapter);
    this.loadingKeysRef = /*#__PURE__*/React.createRef();
    this.loadedKeysRef = /*#__PURE__*/React.createRef();
  }
  get adapter() {
    var _this = this;
    const filterAdapter = {
      updateInputValue: value => {
        this.setState({
          inputValue: value
        });
      },
      updateInputPlaceHolder: value => {
        this.setState({
          inputPlaceHolder: value
        });
      },
      focusInput: () => {
        const {
          preventScroll
        } = this.props;
        if (this.inputRef && this.inputRef.current) {
          // TODO: check the reason
          this.inputRef.current.focus({
            preventScroll
          });
        }
      },
      blurInput: () => {
        if (this.inputRef && this.inputRef.current) {
          this.inputRef.current.blur();
        }
      }
    };
    const cascaderAdapter = {
      registerClickOutsideHandler: cb => {
        const clickOutsideHandler = e => {
          const triggerDom = this.triggerRef && this.triggerRef.current;
          const optionsDom = this.optionContainerEl && this.optionContainerEl.current;
          const target = e.target;
          const path = e.composedPath && e.composedPath() || [target];
          if (optionsDom && (!optionsDom.contains(target) || !optionsDom.contains(target.parentNode)) && triggerDom && !triggerDom.contains(target) && !(path.includes(triggerDom) || path.includes(optionsDom))) {
            cb(e);
          }
        };
        this.clickOutsideHandler = clickOutsideHandler;
        document.addEventListener('mousedown', clickOutsideHandler, false);
      },
      unregisterClickOutsideHandler: () => {
        document.removeEventListener('mousedown', this.clickOutsideHandler, false);
      },
      rePositionDropdown: () => {
        let {
          rePosKey
        } = this.state;
        rePosKey = rePosKey + 1;
        this.setState({
          rePosKey
        });
      }
    };
    return Object.assign(Object.assign(Object.assign(Object.assign({}, super.adapter), filterAdapter), cascaderAdapter), {
      setEmptyContentMinWidth: minWidth => {
        this.setState({
          emptyContentMinWidth: minWidth
        });
      },
      getTriggerWidth: () => {
        const el = this.triggerRef.current;
        return el && el.getBoundingClientRect().width;
      },
      updateStates: states => {
        this.setState(Object.assign({}, states));
      },
      openMenu: () => {
        this.setState({
          isOpen: true
        });
      },
      closeMenu: cb => {
        this.setState({
          isOpen: false
        }, () => {
          cb && cb();
        });
      },
      updateSelection: selectedKeys => this.setState({
        selectedKeys
      }),
      notifyChange: value => {
        this.props.onChange && this.props.onChange(value);
      },
      notifySelect: selected => {
        this.props.onSelect && this.props.onSelect(selected);
      },
      notifyOnSearch: input => {
        this.props.onSearch && this.props.onSearch(input);
      },
      notifyFocus: function () {
        _this.props.onFocus && _this.props.onFocus(...arguments);
      },
      notifyBlur: function () {
        _this.props.onBlur && _this.props.onBlur(...arguments);
      },
      notifyDropdownVisibleChange: visible => {
        this.props.onDropdownVisibleChange(visible);
      },
      toggleHovering: bool => {
        this.setState({
          isHovering: bool
        });
      },
      notifyLoadData: (selectedOpt, callback) => {
        const {
          loadData
        } = this.props;
        if (loadData) {
          new Promise(resolve => {
            loadData(selectedOpt).then(() => {
              /** Why update loading status & call callback function in setTimeout?
               *  loadData func will update treeData, treeData change may trigger
               *  selectedKeys & activeKeys change. For Loading data asynchronously，
               *  activeKeys should not change， Its implementation depends on loading
               *  & loadedKeys. The update time of Loading & loadedKeys(in callback func)
               *  should be later than the update time of treeData(in loaData func)
               *  In React 18, we need to use setTimeout to ensure the above time requirements.
               * */
              setTimeout(() => {
                callback();
                this.setState({
                  loading: false
                });
                resolve();
              });
            });
          });
        }
      },
      notifyOnLoad: (newLoadedKeys, data) => {
        const {
          onLoad
        } = this.props;
        onLoad && onLoad(newLoadedKeys, data);
      },
      notifyListScroll: (e, _ref) => {
        let {
          panelIndex,
          activeNode
        } = _ref;
        this.props.onListScroll(e, {
          panelIndex,
          activeNode
        });
      },
      notifyOnExceed: data => this.props.onExceed(data),
      notifyClear: () => this.props.onClear(),
      toggleInputShow: (showInput, cb) => {
        this.setState({
          showInput
        }, () => {
          cb();
        });
      },
      updateFocusState: isFocus => {
        this.setState({
          isFocus
        });
      },
      updateLoadingKeyRefValue: keys => {
        this.loadingKeysRef.current = keys;
      },
      getLoadingKeyRefValue: () => {
        return this.loadingKeysRef.current;
      },
      updateLoadedKeyRefValue: keys => {
        this.loadedKeysRef.current = keys;
      },
      getLoadedKeyRefValue: () => {
        return this.loadedKeysRef.current;
      }
    });
  }
  static getDerivedStateFromProps(props, prevState) {
    const {
      multiple,
      value,
      defaultValue,
      onChangeWithObject,
      leafOnly,
      autoMergeValue,
      checkRelation,
      searchPlaceholder,
      placeholder
    } = props;
    const {
      prevProps
    } = prevState;
    let keyEntities = prevState.keyEntities || {};
    const newState = {};
    const newPlaceholder = searchPlaceholder || placeholder;
    if (newPlaceholder !== prevState.inputPlaceHolder) {
      newState.inputPlaceHolder = newPlaceholder;
    }
    const needUpdate = name => {
      const firstInProps = _isEmpty(prevProps) && name in props;
      const nameHasChange = prevProps && !_isEqual(prevProps[name], props[name]);
      return firstInProps || nameHasChange;
    };
    const needUpdateData = () => {
      const firstInProps = !prevProps && 'treeData' in props;
      const treeDataHasChange = prevProps && prevProps.treeData !== props.treeData;
      return firstInProps || treeDataHasChange;
    };
    const getRealKeys = (realValue, keyEntities) => {
      // normalizedValue is used to save the value in two-dimensional array format
      let normalizedValue = [];
      if (Array.isArray(realValue)) {
        normalizedValue = Array.isArray(realValue[0]) ? realValue : [realValue];
      } else {
        if (realValue !== undefined) {
          normalizedValue = [[realValue]];
        }
      }
      // formatValuePath is used to save value of valuePath
      const formatValuePath = [];
      normalizedValue.forEach(valueItem => {
        const formatItem = onChangeWithObject && _isObject(valueItem[0]) ? valueItem.map(i => i === null || i === void 0 ? void 0 : i.value) : valueItem;
        formatItem.length > 0 && formatValuePath.push(formatItem);
      });
      // formatKeys is used to save key of value
      const formatKeys = formatValuePath.reduce((acc, cur) => {
        const key = getKeyByValuePath(cur);
        keyEntities[key] && acc.push(key);
        return acc;
      }, []);
      return formatKeys;
    };
    if (multiple) {
      const needUpdateTreeData = needUpdate('treeData') || needUpdateData();
      const needUpdateValue = needUpdate('value') || _isEmpty(prevProps) && defaultValue;
      // when value and treedata need updated
      if (needUpdateTreeData || needUpdateValue) {
        // update state.keyEntities
        if (needUpdateTreeData) {
          newState.treeData = props.treeData;
          keyEntities = convertDataToEntities(props.treeData);
          newState.keyEntities = keyEntities;
        }
        let realKeys = prevState.checkedKeys;
        // when data was updated
        if (needUpdateValue) {
          const realValue = needUpdate('value') ? value : defaultValue;
          realKeys = getRealKeys(realValue, keyEntities);
        } else {
          // needUpdateValue is false
          // if treeData is updated & Cascader is controlled, realKeys should be recalculated
          if (needUpdateTreeData && 'value' in props) {
            const realValue = value;
            realKeys = getRealKeys(realValue, keyEntities);
          }
        }
        if (_isSet(realKeys)) {
          realKeys = [...realKeys];
        }
        if (checkRelation === strings.RELATED) {
          const calRes = calcCheckedKeys(realKeys, keyEntities);
          const checkedKeys = new Set(calRes.checkedKeys);
          const halfCheckedKeys = new Set(calRes.halfCheckedKeys);
          // disableStrictly
          if (props.disableStrictly) {
            newState.disabledKeys = calcDisabledKeys(keyEntities);
          }
          const isLeafOnlyMerge = calcMergeType(autoMergeValue, leafOnly) === strings.LEAF_ONLY_MERGE_TYPE;
          newState.checkedKeys = checkedKeys;
          newState.halfCheckedKeys = halfCheckedKeys;
          newState.resolvedCheckedKeys = new Set(normalizeKeyList(checkedKeys, keyEntities, isLeafOnlyMerge));
        } else {
          newState.checkedKeys = new Set(realKeys);
        }
        newState.prevProps = props;
      }
    }
    return newState;
  }
  componentDidMount() {
    this.foundation.init();
  }
  componentWillUnmount() {
    this.foundation.destroy();
  }
  componentDidUpdate(prevProps) {
    // multiple mode uses getDerivedStateFromProps to sync keyEntities,
    // so we also need to sync filteredKeys when treeData updates during searching.
    if (this.props.multiple) {
      if (!_isEqual(prevProps.treeData, this.props.treeData)) {
        this.foundation.recalculateFilteredKeys();
      }
      return;
    }
    let isOptionsChanged = false;
    if (!_isEqual(prevProps.treeData, this.props.treeData)) {
      isOptionsChanged = true;
      this.foundation.collectOptions();
    }
    if (prevProps.value !== this.props.value && !isOptionsChanged) {
      this.foundation.handleValueChange(this.props.value);
    }
  }
  renderTagInput() {
    const {
      size,
      disabled,
      placeholder,
      maxTagCount,
      showRestTagsPopover,
      restTagsPopoverProps,
      checkRelation
    } = this.props;
    const {
      inputValue,
      checkedKeys,
      keyEntities,
      resolvedCheckedKeys,
      inputPlaceHolder
    } = this.state;
    const tagInputcls = cls(`${prefixcls}-tagInput-wrapper`);
    const realKeys = this.mergeType === strings.NONE_MERGE_TYPE || checkRelation === strings.UN_RELATED ? checkedKeys : resolvedCheckedKeys;
    return /*#__PURE__*/React.createElement(TagInput, {
      className: tagInputcls,
      ref: this.inputRef,
      disabled: disabled,
      size: size,
      value: [...realKeys],
      showRestTagsPopover: showRestTagsPopover,
      restTagsPopoverProps: restTagsPopoverProps,
      maxTagCount: maxTagCount,
      renderTagItem: this.renderTagItem,
      inputValue: inputValue,
      onInputChange: this.handleInputChange,
      // TODO Modify logic, not modify type
      onRemove: this.onRemoveInTagInput,
      placeholder: inputPlaceHolder,
      expandRestTagsOnClick: false
    });
  }
  renderInput() {
    const {
      size,
      disabled
    } = this.props;
    const inputcls = cls(`${prefixcls}-input`);
    const {
      inputValue,
      inputPlaceHolder,
      showInput
    } = this.state;
    const inputProps = {
      disabled,
      value: inputValue,
      className: inputcls,
      onChange: this.handleInputChange
    };
    const wrappercls = cls({
      [`${prefixcls}-search-wrapper`]: true,
      [`${prefixcls}-search-wrapper-${size}`]: size !== 'default'
    });
    const displayText = this.renderDisplayText();
    const spanCls = cls({
      [`${prefixcls}-selection-placeholder`]: !displayText,
      [`${prefixcls}-selection-text-hide`]: showInput && inputValue,
      [`${prefixcls}-selection-text-inactive`]: showInput && !inputValue
    });
    return /*#__PURE__*/React.createElement("div", {
      className: wrappercls
    }, /*#__PURE__*/React.createElement("span", {
      className: spanCls
    }, displayText ? displayText : inputPlaceHolder), showInput && /*#__PURE__*/React.createElement(Input, Object.assign({
      ref: this.inputRef,
      size: size
    }, inputProps)));
  }
  close() {
    this.foundation.close();
  }
  open() {
    this.foundation.open();
  }
  focus() {
    this.foundation.focus();
  }
  blur() {
    this.foundation.blur();
  }
  render() {
    const {
      zIndex,
      getPopupContainer,
      autoAdjustOverflow,
      stopPropagation,
      mouseLeaveDelay,
      mouseEnterDelay,
      position,
      motion,
      dropdownMargin
    } = this.props;
    const {
      isOpen,
      rePosKey
    } = this.state;
    const {
      direction
    } = this.context;
    const content = this.renderContent();
    const selection = this.renderSelection();
    const pos = position !== null && position !== void 0 ? position : direction === 'rtl' ? 'bottomRight' : 'bottomLeft';
    return /*#__PURE__*/React.createElement(Popover, {
      getPopupContainer: getPopupContainer,
      zIndex: zIndex,
      motion: motion,
      margin: dropdownMargin,
      ref: this.optionsRef,
      content: content,
      visible: isOpen,
      trigger: "custom",
      rePosKey: rePosKey,
      position: pos,
      autoAdjustOverflow: autoAdjustOverflow,
      stopPropagation: stopPropagation,
      mouseLeaveDelay: mouseLeaveDelay,
      mouseEnterDelay: mouseEnterDelay,
      afterClose: () => this.foundation.updateSearching(false)
    }, selection);
  }
}
Cascader.__SemiComponentName__ = "Cascader";
Cascader.contextType = ConfigContext;
Cascader.propTypes = {
  'aria-labelledby': PropTypes.string,
  'aria-invalid': PropTypes.bool,
  'aria-errormessage': PropTypes.string,
  'aria-describedby': PropTypes.string,
  'aria-required': PropTypes.bool,
  'aria-label': PropTypes.string,
  arrowIcon: PropTypes.node,
  borderless: PropTypes.bool,
  clearIcon: PropTypes.node,
  changeOnSelect: PropTypes.bool,
  defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
  disabled: PropTypes.bool,
  dropdownClassName: PropTypes.string,
  dropdownStyle: PropTypes.object,
  dropdownMargin: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
  emptyContent: PropTypes.node,
  motion: PropTypes.bool,
  /* show search input, if passed in a function, used as custom filter */
  filterTreeNode: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
  filterLeafOnly: PropTypes.bool,
  placeholder: PropTypes.string,
  searchPlaceholder: PropTypes.string,
  size: PropTypes.oneOf(strings.SIZE_SET),
  style: PropTypes.object,
  className: PropTypes.string,
  treeData: PropTypes.arrayOf(PropTypes.shape({
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    label: PropTypes.any
  })),
  treeNodeFilterProp: PropTypes.string,
  suffix: PropTypes.node,
  prefix: PropTypes.node,
  insetLabel: PropTypes.node,
  insetLabelId: PropTypes.string,
  id: PropTypes.string,
  displayProp: PropTypes.string,
  displayRender: PropTypes.func,
  onChange: PropTypes.func,
  onSearch: PropTypes.func,
  onSelect: PropTypes.func,
  onBlur: PropTypes.func,
  onFocus: PropTypes.func,
  children: PropTypes.node,
  getPopupContainer: PropTypes.func,
  zIndex: PropTypes.number,
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array]),
  validateStatus: PropTypes.oneOf(strings.VALIDATE_STATUS),
  showNext: PropTypes.oneOf([strings.SHOW_NEXT_BY_CLICK, strings.SHOW_NEXT_BY_HOVER]),
  stopPropagation: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
  showClear: PropTypes.bool,
  defaultOpen: PropTypes.bool,
  autoAdjustOverflow: PropTypes.bool,
  onDropdownVisibleChange: PropTypes.func,
  triggerRender: PropTypes.func,
  onListScroll: PropTypes.func,
  onChangeWithObject: PropTypes.bool,
  bottomSlot: PropTypes.node,
  topSlot: PropTypes.node,
  multiple: PropTypes.bool,
  autoMergeValue: PropTypes.bool,
  maxTagCount: PropTypes.number,
  showRestTagsPopover: PropTypes.bool,
  restTagsPopoverProps: PropTypes.object,
  max: PropTypes.number,
  separator: PropTypes.string,
  onExceed: PropTypes.func,
  onClear: PropTypes.func,
  loadData: PropTypes.func,
  onLoad: PropTypes.func,
  loadedKeys: PropTypes.array,
  disableStrictly: PropTypes.bool,
  leafOnly: PropTypes.bool,
  enableLeafClick: PropTypes.bool,
  clickToSelect: PropTypes.bool,
  preventScroll: PropTypes.bool,
  position: PropTypes.string,
  searchPosition: PropTypes.string,
  remote: PropTypes.bool
};
Cascader.defaultProps = getDefaultPropsFromGlobalConfig(Cascader.__SemiComponentName__, {
  borderless: false,
  leafOnly: false,
  arrowIcon: /*#__PURE__*/React.createElement(IconChevronDown, null),
  stopPropagation: true,
  motion: true,
  defaultOpen: false,
  zIndex: popoverNumbers.DEFAULT_Z_INDEX,
  showClear: false,
  autoClearSearchValue: true,
  changeOnSelect: false,
  disableStrictly: false,
  autoMergeValue: true,
  multiple: false,
  filterTreeNode: false,
  filterLeafOnly: true,
  showRestTagsPopover: false,
  restTagsPopoverProps: {},
  separator: ' / ',
  size: 'default',
  treeNodeFilterProp: 'label',
  displayProp: 'label',
  treeData: [],
  showNext: strings.SHOW_NEXT_BY_CLICK,
  onExceed: _noop,
  onClear: _noop,
  onDropdownVisibleChange: _noop,
  onListScroll: _noop,
  enableLeafClick: false,
  clickToSelect: false,
  'aria-label': 'Cascader',
  searchPosition: strings.SEARCH_POSITION_TRIGGER,
  checkRelation: strings.RELATED,
  remote: false
});
export default Cascader;