import { TransitionGroup, withDirectives } from 'vue';
import KeyCode from '../_util/KeyCode';
import PropTypes from '../_util/vue-types';
import classnames from '../_util/classNames';
import classes from 'component-classes';
import { Item as MenuItem, ItemGroup as MenuItemGroup } from '../vc-menu';
import warning from 'warning';
import Option from './Option';
import OptGroup from './OptGroup';
import {
  hasProp,
  getPropsData,
  getValueByProp as getValue,
  getComponent,
  getEvents,
  getOptionProps,
  getSlot,
  findDOMNode,
} from '../_util/props-util';
import getTransitionProps from '../_util/getTransitionProps';
import { cloneElement } from '../_util/vnode';
import BaseMixin from '../_util/BaseMixin';
import SelectTrigger from './SelectTrigger';
import {
  defaultFilterFn,
  findFirstMenuItem,
  findIndexInValueBySingleValue,
  generateUUID,
  getLabelFromPropsValue,
  getMapKey,
  getPropValue,
  getValuePropValue,
  includesSeparators,
  isCombobox,
  isMultipleOrTags,
  isMultipleOrTagsOrCombobox,
  isSingleMode,
  preventDefaultEvent,
  saveRef,
  splitBySeparators,
  toArray,
  toTitle,
  UNSELECTABLE_ATTRIBUTE,
  UNSELECTABLE_STYLE,
  validateOptionValue,
} from './util';
import { SelectPropTypes } from './PropTypes';
import contains from '../vc-util/Dom/contains';
import { isIE, isEdge } from '../_util/env';
import isValid from '../_util/isValid';
import { getDataAndAriaProps } from '../_util/util';
import antInput from '../_util/antInputDirective';

const SELECT_EMPTY_VALUE_KEY = 'RC_SELECT_EMPTY_VALUE_KEY';

const noop = () => null;

// Where el is the DOM element you'd like to test for visibility
function isHidden(node) {
  return !node || node.offsetParent === null;
}

function chaining(...fns) {
  return function(...args) {
    // eslint-disable-line
    // eslint-disable-line
    for (let i = 0; i < fns.length; i++) {
      if (fns[i] && typeof fns[i] === 'function') {
        fns[i].apply(chaining, args);
      }
    }
  };
}
const Select = {
  inheritAttrs: false,
  Option,
  OptGroup,
  name: 'Select',
  mixins: [BaseMixin],
  inheritAttrs: false,
  props: {
    ...SelectPropTypes,
    prefixCls: SelectPropTypes.prefixCls.def('rc-select'),
    defaultOpen: PropTypes.bool.def(false),
    labelInValue: SelectPropTypes.labelInValue.def(false),
    defaultActiveFirstOption: SelectPropTypes.defaultActiveFirstOption.def(true),
    showSearch: SelectPropTypes.showSearch.def(true),
    allowClear: SelectPropTypes.allowClear.def(false),
    placeholder: SelectPropTypes.placeholder.def(''),
    // showArrow: SelectPropTypes.showArrow.def(true),
    dropdownMatchSelectWidth: PropTypes.bool.def(true),
    dropdownStyle: SelectPropTypes.dropdownStyle.def(() => ({})),
    dropdownMenuStyle: PropTypes.object.def(() => ({})),
    optionFilterProp: SelectPropTypes.optionFilterProp.def('value'),
    optionLabelProp: SelectPropTypes.optionLabelProp.def('value'),
    notFoundContent: PropTypes.any.def('Not Found'),
    backfill: PropTypes.bool.def(false),
    showAction: SelectPropTypes.showAction.def(['click']),
    combobox: PropTypes.bool.def(false),
    tokenSeparators: PropTypes.arrayOf(PropTypes.string).def([]),
    autoClearSearchValue: PropTypes.bool.def(true),
    tabindex: PropTypes.any.def(0),
    dropdownRender: PropTypes.func.def(({ menuNode }) => menuNode),
    // onChange: noop,
    // onFocus: noop,
    // onBlur: noop,
    // onSelect: noop,
    // onSearch: noop,
    // onDeselect: noop,
    // onInputKeydown: noop,
  },
  created() {
    this.saveInputRef = saveRef(this, 'inputRef');
    this.saveInputMirrorRef = saveRef(this, 'inputMirrorRef');
    this.saveTopCtrlRef = saveRef(this, 'topCtrlRef');
    this.saveSelectTriggerRef = saveRef(this, 'selectTriggerRef');
    this.saveRootRef = saveRef(this, 'rootRef');
    this.saveSelectionRef = saveRef(this, 'selectionRef');
    this._focused = false;
    this._mouseDown = false;
    this._options = [];
    this._empty = false;
  },
  data() {
    const props = getOptionProps(this);
    const optionsInfo = this.getOptionsInfoFromProps(props);
    warning(
      this.__propsSymbol__,
      'Replace slots.default with props.children and pass props.__propsSymbol__',
    );
    if (props.tags && typeof props.filterOption !== 'function') {
      const isDisabledExist = Object.keys(optionsInfo).some(key => optionsInfo[key].disabled);
      warning(
        !isDisabledExist,
        'Please avoid setting option to disabled in tags mode since user can always type text as tag.',
      );
    }
    const state = {
      _value: this.getValueFromProps(props, true), // true: use default value
      _inputValue: props.combobox
        ? this.getInputValueForCombobox(
            props,
            optionsInfo,
            true, // use default value
          )
        : '',
      _open: props.defaultOpen,
      _optionsInfo: optionsInfo,
      _backfillValue: '',
      // a flag for aviod redundant getOptionsInfoFromProps call
      _skipBuildOptionsInfo: true,
      _ariaId: generateUUID(),
    };
    return {
      ...state,
      // _mirrorInputValue: state._inputValue, // https://github.com/vueComponent/ant-design-vue/issues/1458
      ...this.getDerivedState(props, state),
    };
  },

  mounted() {
    this.$nextTick(() => {
      // when defaultOpen is true, we should auto focus search input
      // https://github.com/ant-design/ant-design/issues/14254
      if (this.autofocus || this._open) {
        this.focus();
      }
      // this.setState({
      //   _ariaId: generateUUID(),
      // });
    });
  },
  watch: {
    __propsSymbol__() {
      Object.assign(this.$data, this.getDerivedState(getOptionProps(this), this.$data));
    },
  },
  updated() {
    this.$nextTick(() => {
      this.updateInputWidth();
      this.forcePopupAlign();
    });
  },
  beforeUnmount() {
    this.clearFocusTime();
    this.clearBlurTime();
    this.clearComboboxTime();
    if (this.dropdownContainer) {
      document.body.removeChild(this.dropdownContainer);
      this.dropdownContainer = null;
    }
  },
  methods: {
    updateInputWidth() {
      if (isMultipleOrTags(this.$props)) {
        const inputNode = this.getInputDOMNode();
        const mirrorNode = this.getInputMirrorDOMNode();
        if (inputNode && inputNode.value && mirrorNode) {
          inputNode.style.width = '';
          inputNode.style.width = `${mirrorNode.clientWidth + 10}px`;
        } else if (inputNode) {
          inputNode.style.width = '';
        }
      }
    },
    getDerivedState(nextProps, prevState) {
      const optionsInfo = prevState._skipBuildOptionsInfo
        ? prevState._optionsInfo
        : this.getOptionsInfoFromProps(nextProps, prevState);

      const newState = {
        _optionsInfo: optionsInfo,
        _skipBuildOptionsInfo: false,
      };

      if ('open' in nextProps) {
        newState._open = nextProps.open;
      }

      if ('value' in nextProps) {
        const value = this.getValueFromProps(nextProps);
        newState._value = value;
        if (nextProps.combobox) {
          newState._inputValue = this.getInputValueForCombobox(nextProps, optionsInfo);
        }
      }
      return newState;
    },
    getOptionsFromChildren(children = [], options = []) {
      children.forEach(child => {
        if (!child) {
          return;
        }
        if (child.type?.isSelectOptGroup) {
          this.getOptionsFromChildren(getSlot(child), options);
        } else {
          options.push(child);
        }
      });
      return options;
    },
    getInputValueForCombobox(props, optionsInfo, useDefaultValue) {
      let value = [];
      if ('value' in props && !useDefaultValue) {
        value = toArray(props.value);
      }
      if ('defaultValue' in props && useDefaultValue) {
        value = toArray(props.defaultValue);
      }
      if (value.length) {
        value = value[0];
      } else {
        return '';
      }
      let label = value;
      if (props.labelInValue) {
        label = value.label;
      } else if (optionsInfo[getMapKey(value)]) {
        label = optionsInfo[getMapKey(value)].label;
      }
      if (label === undefined) {
        label = '';
      }
      return label;
    },

    getLabelFromOption(props, option) {
      return getPropValue(option, props.optionLabelProp);
    },

    getOptionsInfoFromProps(props, preState) {
      const options = this.getOptionsFromChildren(this.$props.children);
      const optionsInfo = {};
      options.forEach(option => {
        const singleValue = getValuePropValue(option);
        optionsInfo[getMapKey(singleValue)] = {
          option,
          value: singleValue,
          label: this.getLabelFromOption(props, option),
          title: getValue(option, 'title'),
          disabled: getValue(option, 'disabled'),
        };
      });
      if (preState) {
        // keep option info in pre state value.
        const oldOptionsInfo = preState._optionsInfo;
        const value = preState._value;
        if (value) {
          value.forEach(v => {
            const key = getMapKey(v);
            if (!optionsInfo[key] && oldOptionsInfo[key] !== undefined) {
              optionsInfo[key] = oldOptionsInfo[key];
            }
          });
        }
      }
      return optionsInfo;
    },

    getValueFromProps(props, useDefaultValue) {
      let value = [];
      if ('value' in props && !useDefaultValue) {
        value = toArray(props.value);
      }
      if ('defaultValue' in props && useDefaultValue) {
        value = toArray(props.defaultValue);
      }
      if (props.labelInValue) {
        value = value.map(v => {
          return v.key;
        });
      }
      return value;
    },

    onInputChange(e) {
      const { value: val } = e.target;
      const { tokenSeparators } = this.$props;
      if (
        isMultipleOrTags(this.$props) &&
        tokenSeparators.length &&
        includesSeparators(val, tokenSeparators)
      ) {
        const nextValue = this.getValueByInput(val);
        if (nextValue !== undefined) {
          this.fireChange(nextValue);
        }
        this.setOpenState(false, { needFocus: true });
        this.setInputValue('', false);
        return;
      }
      this.setInputValue(val);
      this.setState({
        _open: true,
      });
      if (isCombobox(this.$props)) {
        this.fireChange([val]);
      }
    },

    onDropdownVisibleChange(open) {
      if (open && !this._focused) {
        this.clearBlurTime();
        this.timeoutFocus();
        this._focused = true;
        this.updateFocusClassName();
      }
      this.setOpenState(open);
    },

    // combobox ignore
    onKeyDown(event) {
      const { _open: open } = this.$data;
      const { disabled } = this.$props;
      if (disabled) {
        return;
      }
      const keyCode = event.keyCode;
      if (open && !this.getInputDOMNode()) {
        this.onInputKeydown(event);
      } else if (keyCode === KeyCode.ENTER || keyCode === KeyCode.DOWN) {
        // vue state是同步更新，onKeyDown在onMenuSelect后会再次调用，单选时不在调用setOpenState
        // https://github.com/vueComponent/ant-design-vue/issues/1142
        if (keyCode === KeyCode.ENTER && !isMultipleOrTags(this.$props)) {
          this.maybeFocus(true);
        } else if (!open) {
          this.setOpenState(true);
        }
        event.preventDefault();
      } else if (keyCode === KeyCode.SPACE) {
        // Not block space if popup is shown
        if (!open) {
          this.setOpenState(true);
          event.preventDefault();
        }
      }
    },

    onInputKeydown(event) {
      const { disabled, combobox, defaultActiveFirstOption } = this.$props;
      if (disabled) {
        return;
      }
      const state = this.$data;
      const isRealOpen = this.getRealOpenState(state);
      const keyCode = event.keyCode;
      if (isMultipleOrTags(this.$props) && !event.target.value && keyCode === KeyCode.BACKSPACE) {
        event.preventDefault();
        const { _value: value } = state;
        if (value.length) {
          this.removeSelected(value[value.length - 1]);
        }
        return;
      }
      if (keyCode === KeyCode.DOWN) {
        if (!state._open) {
          this.openIfHasChildren();
          event.preventDefault();
          event.stopPropagation();
          return;
        }
      } else if (keyCode === KeyCode.ENTER && state._open) {
        // Aviod trigger form submit when select item
        // https://github.com/ant-design/ant-design/issues/10861
        // https://github.com/ant-design/ant-design/issues/14544
        if (isRealOpen || !combobox) {
          event.preventDefault();
        }
        // Hard close popup to avoid lock of non option in combobox mode
        if (isRealOpen && combobox && defaultActiveFirstOption === false) {
          this.comboboxTimer = setTimeout(() => {
            this.setOpenState(false);
          });
        }
      } else if (keyCode === KeyCode.ESC) {
        if (state._open) {
          this.setOpenState(false);
          event.preventDefault();
          event.stopPropagation();
        }
        return;
      }

      if (isRealOpen && this.selectTriggerRef) {
        const menu = this.selectTriggerRef.getInnerMenu();
        if (menu && menu.onKeyDown(event, this.handleBackfill)) {
          event.preventDefault();
          event.stopPropagation();
        }
      }
    },

    onMenuSelect({ item }) {
      if (!item) {
        return;
      }
      let value = this.$data._value;
      const props = this.$props;
      const selectedValue = getValuePropValue(item);
      const lastValue = value[value.length - 1];
      let skipTrigger = false;

      if (isMultipleOrTags(props)) {
        if (findIndexInValueBySingleValue(value, selectedValue) !== -1) {
          skipTrigger = true;
        } else {
          value = value.concat([selectedValue]);
        }
      } else {
        if (
          !isCombobox(props) &&
          lastValue !== undefined &&
          lastValue === selectedValue &&
          selectedValue !== this.$data._backfillValue
        ) {
          this.setOpenState(false, { needFocus: true, fireSearch: false });
          skipTrigger = true;
        } else {
          value = [selectedValue];
          this.setOpenState(false, { needFocus: true, fireSearch: false });
        }
      }
      if (!skipTrigger) {
        this.fireChange(value);
      }
      if (!skipTrigger) {
        this.fireSelect(selectedValue);
        const inputValue = isCombobox(props) ? getPropValue(item, props.optionLabelProp) : '';

        if (props.autoClearSearchValue) {
          this.setInputValue(inputValue, false);
        }
      }
    },

    onMenuDeselect({ item, domEvent }) {
      if (domEvent.type === 'keydown' && domEvent.keyCode === KeyCode.ENTER) {
        const menuItemDomNode = findDOMNode(item);
        // https://github.com/ant-design/ant-design/issues/20465#issuecomment-569033796
        if (!isHidden(menuItemDomNode)) {
          this.removeSelected(getValuePropValue(item));
        }
        return;
      }

      if (domEvent.type === 'click') {
        this.removeSelected(getValuePropValue(item));
      }

      if (this.autoClearSearchValue) {
        this.setInputValue('');
      }
    },

    onArrowClick(e) {
      e.stopPropagation();
      e.preventDefault();
      this.clearBlurTime();
      if (!this.disabled) {
        this.setOpenState(!this.$data._open, { needFocus: !this.$data._open });
      }
    },

    onPlaceholderClick() {
      if (this.getInputDOMNode() && this.getInputDOMNode()) {
        this.getInputDOMNode().focus();
      }
    },

    onPopupFocus() {
      // fix ie scrollbar, focus element again
      this.maybeFocus(true, true);
    },

    onClearSelection(event) {
      const props = this.$props;
      const state = this.$data;
      if (props.disabled) {
        return;
      }
      const { _inputValue: inputValue, _value: value } = state;
      event.stopPropagation();
      if (inputValue || value.length) {
        if (value.length) {
          this.fireChange([]);
        }
        this.setOpenState(false, { needFocus: true });
        if (inputValue) {
          this.setInputValue('');
        }
      }
    },

    onChoiceAnimationLeave() {
      this.forcePopupAlign();
    },

    getOptionInfoBySingleValue(value, optionsInfo) {
      let info;
      optionsInfo = optionsInfo || this.$data._optionsInfo;
      if (optionsInfo[getMapKey(value)]) {
        info = optionsInfo[getMapKey(value)];
      }
      if (info) {
        return info;
      }
      let defaultLabel = value;
      if (this.$props.labelInValue) {
        const valueLabel = getLabelFromPropsValue(this.$props.value, value);
        const defaultValueLabel = getLabelFromPropsValue(this.$props.defaultValue, value);
        if (valueLabel !== undefined) {
          defaultLabel = valueLabel;
        } else if (defaultValueLabel !== undefined) {
          defaultLabel = defaultValueLabel;
        }
      }
      const defaultInfo = {
        option: (
          <Option value={value} key={value}>
            {value}
          </Option>
        ),
        value,
        label: defaultLabel,
      };
      return defaultInfo;
    },

    getOptionBySingleValue(value) {
      const { option } = this.getOptionInfoBySingleValue(value);
      return option;
    },

    getOptionsBySingleValue(values) {
      return values.map(value => {
        return this.getOptionBySingleValue(value);
      });
    },

    getValueByLabel(label) {
      if (label === undefined) {
        return null;
      }
      let value = null;
      Object.keys(this.$data._optionsInfo).forEach(key => {
        const info = this.$data._optionsInfo[key];
        const { disabled } = info;
        if (disabled) {
          return;
        }
        const oldLable = toArray(info.label);
        if (oldLable && oldLable.join('') === label) {
          value = info.value;
        }
      });
      return value;
    },

    getVLBySingleValue(value) {
      if (this.$props.labelInValue) {
        return {
          key: value,
          label: this.getLabelBySingleValue(value),
        };
      }
      return value;
    },

    getVLForOnChange(vlsS) {
      let vls = vlsS;
      if (vls !== undefined) {
        if (!this.labelInValue) {
          vls = vls.map(v => v);
        } else {
          vls = vls.map(vl => ({
            key: vl,
            label: this.getLabelBySingleValue(vl),
          }));
        }
        return isMultipleOrTags(this.$props) ? vls : vls[0];
      }
      return vls;
    },

    getLabelBySingleValue(value, optionsInfo) {
      const { label } = this.getOptionInfoBySingleValue(value, optionsInfo);
      return label;
    },

    getDropdownContainer() {
      if (!this.dropdownContainer) {
        this.dropdownContainer = document.createElement('div');
        document.body.appendChild(this.dropdownContainer);
      }
      return this.dropdownContainer;
    },

    getPlaceholderElement() {
      const { $props: props, $data: state } = this;
      let hidden = false;
      if (state._inputValue) {
        hidden = true;
      }
      const value = state._value;
      if (value.length) {
        hidden = true;
      }
      if (isCombobox(props) && value.length === 1 && state._value && !isValid(state._value[0])) {
        hidden = false;
      }
      const placeholder = props.placeholder;
      if (placeholder) {
        const p = {
          onMousedown: preventDefaultEvent,
          onClick: this.onPlaceholderClick,
          ...UNSELECTABLE_ATTRIBUTE,
          style: {
            display: hidden ? 'none' : 'block',
            ...UNSELECTABLE_STYLE,
          },
          class: `${props.prefixCls}-selection__placeholder`,
        };
        return <div {...p}>{placeholder}</div>;
      }
      return null;
    },
    inputClick(e) {
      if (this.$data._open) {
        this.clearBlurTime();
        e.stopPropagation();
      } else {
        this._focused = false;
      }
    },
    inputBlur(e) {
      const target = e.relatedTarget || document.activeElement;

      // https://github.com/vueComponent/ant-design-vue/issues/999
      // https://github.com/vueComponent/ant-design-vue/issues/1223
      if (
        (isIE || isEdge) &&
        (e.relatedTarget === this.$refs.arrow ||
          (target &&
            this.selectTriggerRef &&
            findDOMNode(this.selectTriggerRef.getInnerMenu()) === target) ||
          contains(e.target, target))
      ) {
        e.target.focus();
        e.preventDefault();
        return;
      }
      if (this.disabled) {
        e.preventDefault();
        return;
      }
      this.blurTimer = setTimeout(() => {
        this._focused = false;
        this.updateFocusClassName();
        const props = this.$props;
        let { _value: value } = this.$data;
        const { _inputValue: inputValue } = this.$data;
        if (
          isSingleMode(props) &&
          props.showSearch &&
          inputValue &&
          props.defaultActiveFirstOption
        ) {
          const options = this._options || [];
          if (options.length) {
            const firstOption = findFirstMenuItem(options);
            if (firstOption) {
              value = [getValuePropValue(firstOption)];
              this.fireChange(value);
            }
          }
        } else if (isMultipleOrTags(props) && inputValue) {
          if (this._mouseDown) {
            // need update dropmenu when not blur
            this.setInputValue('');
          } else {
            // why not use setState?
            this.$data._inputValue = '';
            if (this.getInputDOMNode && this.getInputDOMNode()) {
              this.getInputDOMNode().value = '';
            }
          }
          const tmpValue = this.getValueByInput(inputValue);
          if (tmpValue !== undefined) {
            value = tmpValue;
            this.fireChange(value);
          }
        }
        // if click the rest space of Select in multiple mode
        if (isMultipleOrTags(props) && this._mouseDown) {
          this.maybeFocus(true, true);
          this._mouseDown = false;
          return;
        }
        this.setOpenState(false);
        this.__emit('blur', this.getVLForOnChange(value));
      }, 200);
    },
    inputFocus(e) {
      if (this.$props.disabled) {
        e.preventDefault();
        return;
      }
      this.clearBlurTime();

      // In IE11, onOuterFocus will be trigger twice when focus input
      // First one: e.target is div
      // Second one: e.target is input
      // other browser only trigger second one
      // https://github.com/ant-design/ant-design/issues/15942
      // Here we ignore the first one when e.target is div
      const inputNode = this.getInputDOMNode();
      if (inputNode && e.target === this.rootRef) {
        return;
      }
      if (!isMultipleOrTagsOrCombobox(this.$props) && e.target === inputNode) {
        return;
      }
      if (this._focused) {
        return;
      }
      this._focused = true;
      this.updateFocusClassName();
      // only effect multiple or tag mode
      if (!isMultipleOrTags(this.$props) || !this._mouseDown) {
        this.timeoutFocus();
      }
    },
    _getInputElement() {
      const props = this.$props;
      const { _inputValue: inputValue } = this.$data;
      const attrs = this.$attrs;
      const defaultInput = (
        <input {...(attrs.id !== undefined ? { id: attrs.id } : {})} autocomplete="off" />
      );

      const inputElement = props.getInputElement ? props.getInputElement() : defaultInput;
      const inputCls = classnames(inputElement.props && inputElement.props.class, {
        [`${props.prefixCls}-search__field`]: true,
      });
      const inputEvents = getEvents(inputElement);
      // https://github.com/ant-design/ant-design/issues/4992#issuecomment-281542159
      // Add space to the end of the inputValue as the width measurement tolerance
      return (
        <div class={`${props.prefixCls}-search__field__wrap`} onClick={this.inputClick}>
          {cloneElement(withDirectives(inputElement, [[antInput]]), {
            disabled: props.disabled,
            ...(inputElement.props || {}),
            disabled: props.disabled,
            value: inputValue,
            class: inputCls,
            ref: this.saveInputRef,
            onInput: this.onInputChange,
            onKeydown: chaining(
              this.onInputKeydown,
              inputEvents.onKeydown,
              this.$attrs.onInputKeydown,
            ),
            onFocus: chaining(this.inputFocus, inputEvents.onFocus),
            onBlur: chaining(this.inputBlur, inputEvents.onBlur),
          })}
          <span ref={this.saveInputMirrorRef} class={`${props.prefixCls}-search__field__mirror`}>
            {inputValue}&nbsp;
          </span>
        </div>
      );
    },

    getInputDOMNode() {
      return this.topCtrlRef
        ? this.topCtrlRef.querySelector('input,textarea,div[contentEditable]')
        : this.inputRef;
    },

    getInputMirrorDOMNode() {
      return this.inputMirrorRef;
    },

    getPopupDOMNode() {
      if (this.selectTriggerRef) {
        return this.selectTriggerRef.getPopupDOMNode();
      }
    },

    getPopupMenuComponent() {
      if (this.selectTriggerRef) {
        return this.selectTriggerRef.getInnerMenu();
      }
    },

    setOpenState(open, config = {}) {
      const { $props: props, $data: state } = this;
      const { needFocus, fireSearch } = config;
      if (state._open === open) {
        this.maybeFocus(open, !!needFocus);
        return;
      }
      this.__emit('dropdownVisibleChange', open);
      const nextState = {
        _open: open,
        _backfillValue: '',
      };
      // clear search input value when open is false in singleMode.
      if (!open && isSingleMode(props) && props.showSearch) {
        this.setInputValue('', fireSearch);
      }
      if (!open) {
        this.maybeFocus(open, !!needFocus);
      }
      this.setState(nextState, () => {
        if (open) {
          this.maybeFocus(open, !!needFocus);
        }
      });
    },

    setInputValue(inputValue, fireSearch = true) {
      if (inputValue !== this.$data._inputValue) {
        this.setState(
          {
            _inputValue: inputValue,
          },
          this.forcePopupAlign,
        );
        if (fireSearch) {
          this.__emit('search', inputValue);
        }
      }
    },
    getValueByInput(str) {
      const { multiple, tokenSeparators } = this.$props;
      let nextValue = this.$data._value;
      let hasNewValue = false;
      splitBySeparators(str, tokenSeparators).forEach(label => {
        const selectedValue = [label];
        if (multiple) {
          const value = this.getValueByLabel(label);
          if (value && findIndexInValueBySingleValue(nextValue, value) === -1) {
            nextValue = nextValue.concat(value);
            hasNewValue = true;
            this.fireSelect(value);
          }
        } else if (findIndexInValueBySingleValue(nextValue, label) === -1) {
          nextValue = nextValue.concat(selectedValue);
          hasNewValue = true;
          this.fireSelect(label);
        }
      });
      return hasNewValue ? nextValue : undefined;
    },

    getRealOpenState(state) {
      const { open: _open } = this.$props;
      if (typeof _open === 'boolean') {
        return _open;
      }

      let open = (state || this.$data)._open;
      const options = this._options || [];
      if (isMultipleOrTagsOrCombobox(this.$props) || !this.$props.showSearch) {
        if (open && !options.length) {
          open = false;
        }
      }
      return open;
    },

    focus() {
      if (isSingleMode(this.$props) && this.selectionRef) {
        this.selectionRef.focus();
      } else if (this.getInputDOMNode()) {
        this.getInputDOMNode().focus();
      }
    },

    blur() {
      if (isSingleMode(this.$props) && this.selectionRef) {
        this.selectionRef.blur();
      } else if (this.getInputDOMNode()) {
        this.getInputDOMNode().blur();
      }
    },
    markMouseDown() {
      this._mouseDown = true;
    },

    markMouseLeave() {
      this._mouseDown = false;
    },

    handleBackfill(item) {
      if (!this.backfill || !(isSingleMode(this.$props) || isCombobox(this.$props))) {
        return;
      }

      const key = getValuePropValue(item);

      if (isCombobox(this.$props)) {
        this.setInputValue(key, false);
      }

      this.setState({
        _value: [key],
        _backfillValue: key,
      });
    },

    _filterOption(input, child, defaultFilter = defaultFilterFn) {
      const { _value: value, _backfillValue: backfillValue } = this.$data;
      const lastValue = value[value.length - 1];
      if (!input || (lastValue && lastValue === backfillValue)) {
        return true;
      }
      let filterFn = this.$props.filterOption;
      if (hasProp(this, 'filterOption')) {
        if (filterFn === true) {
          filterFn = defaultFilter.bind(this);
        }
      } else {
        filterFn = defaultFilter.bind(this);
      }
      if (!filterFn) {
        return true;
      } else if (typeof filterFn === 'function') {
        return filterFn.call(this, input, child);
      } else if (getValue(child, 'disabled')) {
        return false;
      }
      return true;
    },

    timeoutFocus() {
      if (this.focusTimer) {
        this.clearFocusTime();
      }
      this.focusTimer = window.setTimeout(() => {
        // this._focused = true
        // this.updateFocusClassName()
        this.__emit('focus');
      }, 10);
    },

    clearFocusTime() {
      if (this.focusTimer) {
        clearTimeout(this.focusTimer);
        this.focusTimer = null;
      }
    },

    clearBlurTime() {
      if (this.blurTimer) {
        clearTimeout(this.blurTimer);
        this.blurTimer = null;
      }
    },

    clearComboboxTime() {
      if (this.comboboxTimer) {
        clearTimeout(this.comboboxTimer);
        this.comboboxTimer = null;
      }
    },

    updateFocusClassName() {
      const { rootRef, prefixCls } = this;
      // avoid setState and its side effect
      if (this._focused) {
        classes(rootRef).add(`${prefixCls}-focused`);
      } else {
        classes(rootRef).remove(`${prefixCls}-focused`);
      }
    },

    maybeFocus(open, needFocus) {
      if (needFocus || open) {
        const input = this.getInputDOMNode();
        const { activeElement } = document;
        if (input && (open || isMultipleOrTagsOrCombobox(this.$props))) {
          if (activeElement !== input) {
            input.focus();
            this._focused = true;
          }
        } else if (activeElement !== this.selectionRef && this.selectionRef) {
          this.selectionRef.focus();
          this._focused = true;
        }
      }
    },

    removeSelected(selectedKey, e) {
      const props = this.$props;
      if (props.disabled || this.isChildDisabled(selectedKey)) {
        return;
      }
      // Do not trigger Trigger popup
      if (e && e.stopPropagation) {
        e.stopPropagation();
      }
      const oldValue = this.$data._value;
      const value = oldValue.filter(singleValue => {
        return singleValue !== selectedKey;
      });
      const canMultiple = isMultipleOrTags(props);

      if (canMultiple) {
        let event = selectedKey;
        if (props.labelInValue) {
          event = {
            key: selectedKey,
            label: this.getLabelBySingleValue(selectedKey),
          };
        }
        this.__emit('deselect', event, this.getOptionBySingleValue(selectedKey));
      }
      this.fireChange(value);
    },

    openIfHasChildren() {
      const { $props } = this;
      if (($props.children && $props.children.length) || isSingleMode($props)) {
        this.setOpenState(true);
      }
    },
    fireSelect(value) {
      this.__emit('select', this.getVLBySingleValue(value), this.getOptionBySingleValue(value));
    },
    fireChange(value) {
      if (!hasProp(this, 'value')) {
        this.setState(
          {
            _value: value,
          },
          this.forcePopupAlign,
        );
      }
      const vls = this.getVLForOnChange(value);
      const options = this.getOptionsBySingleValue(value);
      this._valueOptions = options;
      this.__emit('update:value', vls);
      this.__emit('change', vls, isMultipleOrTags(this.$props) ? options : options[0]);
    },

    isChildDisabled(key) {
      return (this.$props.children || []).some(child => {
        const childValue = getValuePropValue(child);
        return childValue === key && getValue(child, 'disabled');
      });
    },
    forcePopupAlign() {
      if (!this.$data._open) {
        return;
      }
      if (this.selectTriggerRef && this.selectTriggerRef.triggerRef) {
        this.selectTriggerRef.triggerRef.forcePopupAlign();
      }
    },
    renderFilterOptions() {
      const { _inputValue: inputValue } = this.$data;
      const { children, tags, notFoundContent } = this.$props;
      const menuItems = [];
      const childrenKeys = [];
      let empty = false;
      let options = this.renderFilterOptionsFromChildren(children, childrenKeys, menuItems);
      if (tags) {
        // tags value must be string
        let value = this.$data._value;
        value = value.filter(singleValue => {
          return (
            childrenKeys.indexOf(singleValue) === -1 &&
            (!inputValue || String(singleValue).indexOf(String(inputValue)) > -1)
          );
        });

        // sort by length
        value.sort((val1, val2) => {
          return val1.length - val2.length;
        });

        value.forEach(singleValue => {
          const key = singleValue;
          const attrs = {
            ...UNSELECTABLE_ATTRIBUTE,
            role: 'option',
          };
          const menuItem = (
            <MenuItem style={UNSELECTABLE_STYLE} {...attrs} value={key} key={key}>
              {key}
            </MenuItem>
          );
          options.push(menuItem);
          menuItems.push(menuItem);
        });
        // ref: https://github.com/ant-design/ant-design/issues/14090
        if (inputValue && menuItems.every(option => getValuePropValue(option) !== inputValue)) {
          const p = {
            ...UNSELECTABLE_ATTRIBUTE,
            key: inputValue,
            value: inputValue,
            role: 'option',
            style: UNSELECTABLE_STYLE,
          };
          options.unshift(<MenuItem {...p}>{inputValue}</MenuItem>);
        }
      }

      if (!options.length && notFoundContent) {
        empty = true;
        const p = {
          ...UNSELECTABLE_ATTRIBUTE,
          key: 'NOT_FOUND',
          value: 'NOT_FOUND',
          disabled: true,
          role: 'option',
          style: UNSELECTABLE_STYLE,
        };
        options = [<MenuItem {...p}>{notFoundContent}</MenuItem>];
      }
      return { empty, options };
    },

    renderFilterOptionsFromChildren(children = [], childrenKeys, menuItems) {
      const sel = [];
      const props = this.$props;
      const { _inputValue: inputValue } = this.$data;
      const tags = props.tags;
      children.forEach(child => {
        if (!child) {
          return;
        }
        const type = child.type;
        if (type?.isSelectOptGroup) {
          let label = getComponent(child, 'label');
          let key = child.key;
          if (!key && typeof label === 'string') {
            key = label;
          } else if (!label && key) {
            label = key;
          }
          let childChildren = getComponent(child);
          childChildren = Array.isArray(childChildren) ? childChildren : [childChildren];
          // Match option group label
          if (inputValue && this._filterOption(inputValue, child)) {
            const innerItems = childChildren.map(subChild => {
              const childValueSub = getValuePropValue(subChild) || subChild.key;
              return (
                <MenuItem key={childValueSub} value={childValueSub} {...subChild.props}>
                  {...getSlot(subChild)}
                </MenuItem>
              );
            });

            sel.push(
              <MenuItemGroup key={key} title={label} class={child.props && child.props.class}>
                {...innerItems}
              </MenuItemGroup>,
            );

            // Not match
          } else {
            const innerItems = this.renderFilterOptionsFromChildren(
              childChildren,
              childrenKeys,
              menuItems,
            );
            if (innerItems.length) {
              sel.push(
                <MenuItemGroup key={key} title={label} {...child.props}>
                  {...innerItems}
                </MenuItemGroup>,
              );
            }
          }

          return;
        }
        warning(
          typeof type === 'object' && type.isSelectOption,
          'the children of `Select` should be `Select.Option` or `Select.OptGroup`, ',
        );

        const childValue = getValuePropValue(child);

        validateOptionValue(childValue, this.$props);
        if (this._filterOption(inputValue, child)) {
          const p = {
            ...UNSELECTABLE_ATTRIBUTE,
            key: childValue,
            value: childValue,
            ...getPropsData(child),
            role: 'option',
            style: UNSELECTABLE_STYLE,
            class: child?.class,
          };
          const menuItem = <MenuItem {...p}>{getSlot(child)}</MenuItem>;
          sel.push(menuItem);
          menuItems.push(menuItem);
        }
        if (tags) {
          childrenKeys.push(childValue);
        }
      });

      return sel;
    },

    renderTopControlNode() {
      const { $props: props } = this;
      const { _value: value, _inputValue: inputValue, _open: open } = this.$data;
      const {
        choiceTransitionName,
        prefixCls,
        maxTagTextLength,
        maxTagCount,
        maxTagPlaceholder,
        showSearch,
      } = props;
      const removeIcon = getComponent(this, 'removeIcon');
      const className = `${prefixCls}-selection__rendered`;
      // search input is inside topControlNode in single, multiple & combobox. 2016/04/13
      let innerNode = null;
      if (isSingleMode(props)) {
        let selectedValue = null;
        if (value.length) {
          let showSelectedValue = false;
          let opacity = 1;
          if (!showSearch) {
            showSelectedValue = true;
          } else if (open) {
            showSelectedValue = !inputValue;
            if (showSelectedValue) {
              opacity = 0.4;
            }
          } else {
            showSelectedValue = true;
          }
          const singleValue = value[0];
          const { label, title } = this.getOptionInfoBySingleValue(singleValue);
          selectedValue = (
            <div
              key="value"
              class={`${prefixCls}-selection-selected-value`}
              title={toTitle(title || label)}
              style={{
                display: showSelectedValue ? 'block' : 'none',
                opacity,
              }}
            >
              {label}
            </div>
          );
        }
        if (!showSearch) {
          innerNode = [selectedValue];
        } else {
          innerNode = [
            selectedValue,
            <div
              class={`${prefixCls}-search ${prefixCls}-search--inline`}
              key="input"
              style={{
                display: open ? 'block' : 'none',
              }}
            >
              {this._getInputElement()}
            </div>,
          ];
        }
      } else {
        let selectedValueNodes = [];
        let limitedCountValue = value;
        let maxTagPlaceholderEl;
        if (maxTagCount !== undefined && value.length > maxTagCount) {
          limitedCountValue = limitedCountValue.slice(0, maxTagCount);
          const omittedValues = this.getVLForOnChange(value.slice(maxTagCount, value.length));
          let content = `+ ${value.length - maxTagCount} ...`;
          if (maxTagPlaceholder) {
            content =
              typeof maxTagPlaceholder === 'function'
                ? maxTagPlaceholder(omittedValues)
                : maxTagPlaceholder;
          }
          const attrs = {
            ...UNSELECTABLE_ATTRIBUTE,
            role: 'presentation',
            title: toTitle(content),
          };
          maxTagPlaceholderEl = (
            <li
              style={UNSELECTABLE_STYLE}
              {...attrs}
              onMousedown={preventDefaultEvent}
              class={`${prefixCls}-selection__choice ${prefixCls}-selection__choice__disabled`}
              key="maxTagPlaceholder"
            >
              <div class={`${prefixCls}-selection__choice__content`}>{content}</div>
            </li>
          );
        }
        if (isMultipleOrTags(props)) {
          selectedValueNodes = limitedCountValue.map(singleValue => {
            const info = this.getOptionInfoBySingleValue(singleValue);
            let content = info.label;
            const title = info.title || content;
            if (
              maxTagTextLength &&
              typeof content === 'string' &&
              content.length > maxTagTextLength
            ) {
              content = `${content.slice(0, maxTagTextLength)}...`;
            }
            const disabled = this.isChildDisabled(singleValue);
            const choiceClassName = disabled
              ? `${prefixCls}-selection__choice ${prefixCls}-selection__choice__disabled`
              : `${prefixCls}-selection__choice`;
            // attrs 放在一起，避免动态title混乱问题，很奇怪的问题 https://github.com/vueComponent/ant-design-vue/issues/588
            const attrs = {
              ...UNSELECTABLE_ATTRIBUTE,
              role: 'presentation',
              title: toTitle(title),
            };
            return (
              <li
                style={UNSELECTABLE_STYLE}
                {...attrs}
                onMousedown={preventDefaultEvent}
                class={choiceClassName}
                key={singleValue || SELECT_EMPTY_VALUE_KEY}
              >
                <div class={`${prefixCls}-selection__choice__content`}>{content}</div>
                {disabled ? null : (
                  <span
                    onClick={event => {
                      this.removeSelected(singleValue, event);
                    }}
                    class={`${prefixCls}-selection__choice__remove`}
                  >
                    {removeIcon || <i class={`${prefixCls}-selection__choice__remove-icon`}>×</i>}
                  </span>
                )}
              </li>
            );
          });
        }
        if (maxTagPlaceholderEl) {
          selectedValueNodes.push(maxTagPlaceholderEl);
        }
        selectedValueNodes.push(
          <li class={`${prefixCls}-search ${prefixCls}-search--inline`} key="__input">
            {this._getInputElement()}
          </li>,
        );

        if (isMultipleOrTags(props) && choiceTransitionName) {
          const transitionProps = getTransitionProps(choiceTransitionName, {
            tag: 'ul',
            onAfterLeave: this.onChoiceAnimationLeave,
          });
          innerNode = <TransitionGroup {...transitionProps}>{selectedValueNodes}</TransitionGroup>;
        } else {
          innerNode = <ul>{selectedValueNodes}</ul>;
        }
      }
      return (
        <div class={className} ref={this.saveTopCtrlRef} onClick={this.topCtrlContainerClick}>
          {this.getPlaceholderElement()}
          {innerNode}
        </div>
      );
    },
    renderArrow(multiple) {
      // showArrow : Set to true if not multiple by default but keep set value.
      const { showArrow = !multiple, loading, prefixCls } = this.$props;
      const inputIcon = getComponent(this, 'inputIcon');
      if (!showArrow && !loading) {
        return null;
      }
      // if loading  have loading icon
      const defaultIcon = loading ? (
        <i class={`${prefixCls}-arrow-loading`} />
      ) : (
        <i class={`${prefixCls}-arrow-icon`} />
      );
      return (
        <span
          key="arrow"
          class={`${prefixCls}-arrow`}
          style={UNSELECTABLE_STYLE}
          {...UNSELECTABLE_ATTRIBUTE}
          onClick={this.onArrowClick}
          ref="arrow"
        >
          {inputIcon || defaultIcon}
        </span>
      );
    },
    topCtrlContainerClick(e) {
      if (this.$data._open && !isSingleMode(this.$props)) {
        e.stopPropagation();
      }
    },
    renderClear() {
      const { prefixCls, allowClear } = this.$props;
      const { _value: value, _inputValue: inputValue } = this.$data;
      const clearIcon = getComponent(this, 'clearIcon');
      const clear = (
        <span
          key="clear"
          class={`${prefixCls}-selection__clear`}
          onMousedown={preventDefaultEvent}
          style={UNSELECTABLE_STYLE}
          {...UNSELECTABLE_ATTRIBUTE}
          onClick={this.onClearSelection}
        >
          {clearIcon || <i class={`${prefixCls}-selection__clear-icon`}>×</i>}
        </span>
      );
      if (!allowClear) {
        return null;
      }
      if (isCombobox(this.$props)) {
        if (inputValue) {
          return clear;
        }
        return null;
      }
      if (inputValue || value.length) {
        return clear;
      }
      return null;
    },

    selectionRefClick() {
      //e.stopPropagation();
      if (!this.disabled) {
        const input = this.getInputDOMNode();
        if (this._focused && this.$data._open) {
          // this._focused = false;
          this.setOpenState(false, false);
          input && input.blur();
        } else {
          this.clearBlurTime();
          //this._focused = true;
          this.setOpenState(true, true);
          input && input.focus();
        }
      }
    },
    selectionRefFocus(e) {
      this.inputFocus(e);
    },
    selectionRefBlur(e) {
      if (isMultipleOrTagsOrCombobox(this.$props)) {
        e.preventDefault();
        return;
      }
      this.inputBlur(e);
    },
  },

  render() {
    const props = this.$props;
    const { class: className, style } = this.$attrs;
    const multiple = isMultipleOrTags(props);
    // Default set showArrow to true if not set (not set directly in defaultProps to handle multiple case)
    const { showArrow = true } = props;
    const state = this.$data;
    const { disabled, prefixCls, loading } = props;
    const { _open: open, _inputValue: inputValue, _value: value } = this.$data;
    if (open) {
      const filterOptions = this.renderFilterOptions();
      this._empty = filterOptions.empty;
      this._options = filterOptions.options;
    }
    const realOpen = this.getRealOpenState();
    const empty = this._empty;
    const options = this._options || [];
    const selectionProps = {
      role: 'combobox',
      'aria-autocomplete': 'list',
      'aria-haspopup': 'true',
      'aria-expanded': realOpen,
      'aria-controls': this.$data._ariaId,
      class: `${prefixCls}-selection ${prefixCls}-selection--${multiple ? 'multiple' : 'single'}`,
      key: 'selection',
    };
    //if (!isMultipleOrTagsOrCombobox(props)) {
    // selectionProps.on.keydown = this.onKeyDown;
    // selectionProps.on.focus = this.selectionRefFocus;
    // selectionProps.on.blur = this.selectionRefBlur;
    // selectionProps.attrs.tabindex = props.disabled ? -1 : props.tabindex;
    //}
    const rootCls = {
      [className]: className,
      [prefixCls]: true,
      [`${prefixCls}-open`]: open,
      [`${prefixCls}-focused`]: open || !!this._focused,
      [`${prefixCls}-combobox`]: isCombobox(props),
      [`${prefixCls}-disabled`]: disabled,
      [`${prefixCls}-enabled`]: !disabled,
      [`${prefixCls}-allow-clear`]: !!props.allowClear,
      [`${prefixCls}-no-arrow`]: !showArrow,
      [`${prefixCls}-loading`]: !!loading,
    };
    return (
      <SelectTrigger
        dropdownAlign={props.dropdownAlign}
        dropdownClassName={props.dropdownClassName}
        dropdownMatchSelectWidth={props.dropdownMatchSelectWidth}
        defaultActiveFirstOption={props.defaultActiveFirstOption}
        dropdownMenuStyle={props.dropdownMenuStyle}
        transitionName={props.transitionName}
        animation={props.animation}
        prefixCls={props.prefixCls}
        dropdownStyle={props.dropdownStyle}
        combobox={props.combobox}
        showSearch={props.showSearch}
        options={options}
        empty={empty}
        multiple={multiple}
        disabled={disabled}
        visible={realOpen}
        inputValue={inputValue}
        value={value}
        backfillValue={state._backfillValue}
        firstActiveValue={props.firstActiveValue}
        onDropdownVisibleChange={this.onDropdownVisibleChange}
        getPopupContainer={props.getPopupContainer}
        onMenuSelect={this.onMenuSelect}
        onMenuDeselect={this.onMenuDeselect}
        onPopupScroll={this.$attrs.onPopupScroll}
        onPopupFocus={this.onPopupFocus}
        onMouseenter={this.$attrs.onMouseenter}
        onMouseleave={this.$attrs.onMouseleave}
        showAction={props.showAction}
        menuItemSelectedIcon={getComponent(this, 'menuItemSelectedIcon')}
        ref={this.saveSelectTriggerRef}
        dropdownRender={props.dropdownRender}
        ariaId={this.$data._ariaId}
      >
        <div
          {...getDataAndAriaProps(this.$attrs)}
          ref={chaining(this.saveRootRef, this.saveSelectionRef)}
          style={style}
          class={classnames(rootCls)}
          onMousedown={this.markMouseDown}
          onMouseup={this.markMouseLeave}
          onMouseout={this.markMouseLeave}
          tabindex={props.disabled ? -1 : props.tabindex}
          onBlur={this.selectionRefBlur}
          onFocus={this.selectionRefFocus}
          onClick={this.selectionRefClick}
          onKeydown={isMultipleOrTagsOrCombobox(props) ? noop : this.onKeyDown}
        >
          <div {...selectionProps}>
            {this.renderTopControlNode()}
            {this.renderClear()}
            {this.renderArrow(!!multiple)}
          </div>
        </div>
      </SelectTrigger>
    );
  },
};
export { Select };
export default Select;
