import KeyCode from '../_util/KeyCode';
import PropTypes from '../_util/vue-types';
import classnames from 'classnames';
import classes from 'component-classes';
import { Item as MenuItem, ItemGroup as MenuItemGroup } from '../vc-menu';
import warning from 'warning';
import Vue from 'vue';
import Option from './Option';
import OptGroup from './OptGroup';
import {
  hasProp,
  getSlotOptions,
  getPropsData,
  getValueByProp as getValue,
  getComponentFromProp,
  getEvents,
  getClass,
  getStyle,
  getAttrs,
  getOptionProps,
  getSlots,
} from '../_util/props-util';
import getTransitionProps from '../_util/getTransitionProps';
import { cloneElement } from '../_util/vnode';
import BaseMixin from '../_util/BaseMixin';
import proxyComponent from '../_util/proxyComponent';
import ref from 'vue-ref';
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';

Vue.use(ref, { name: 'ant-ref' });
const SELECT_EMPTY_VALUE_KEY = 'RC_SELECT_EMPTY_VALUE_KEY';

const noop = () => 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],
  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(menu => menu),
    // onChange: noop,
    // onFocus: noop,
    // onBlur: noop,
    // onSelect: noop,
    // onSearch: noop,
    // onDeselect: noop,
    // onInputKeydown: noop,
  },
  model: {
    prop: 'value',
    event: 'change',
  },
  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 = [];
  },
  data() {
    const props = getOptionProps(this);
    const optionsInfo = this.getOptionsInfoFromProps(props);
    warning(
      this.__propsSymbol__,
      'Replace slots.default with props.children and pass props.__propsSymbol__',
    );
    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,
      ...this.getDerivedStateFromProps(props, state),
    };
  },

  mounted() {
    this.$nextTick(() => {
      this.autoFocus && this.focus();
    });
  },
  watch: {
    __propsSymbol__() {
      Object.assign(this.$data, this.getDerivedStateFromProps(getOptionProps(this), this.$data));
    },
  },
  updated() {
    this.$nextTick(() => {
      if (isMultipleOrTags(this.$props)) {
        const inputNode = this.getInputDOMNode();
        const mirrorNode = this.getInputMirrorDOMNode();
        if (inputNode.value && inputNode.value && mirrorNode) {
          inputNode.style.width = '';
          inputNode.style.width = `${mirrorNode.clientWidth + 10}px`;
        } else if (inputNode) {
          inputNode.style.width = '';
        }
      }
      this.forcePopupAlign();
    });
  },
  beforeDestroy() {
    this.clearFocusTime();
    this.clearBlurTime();
    if (this.dropdownContainer) {
      document.body.removeChild(this.dropdownContainer);
      this.dropdownContainer = null;
    }
  },
  methods: {
    getDerivedStateFromProps(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.data || child.data.slot !== undefined) {
          return;
        }
        if (getSlotOptions(child).isSelectOptGroup) {
          this.getOptionsFromChildren(child.componentOptions.children, 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'),
        };
      });
      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(event) {
      const { tokenSeparators } = this.$props;
      const val = event.target.value;
      if (
        isMultipleOrTags(this.$props) &&
        tokenSeparators.length &&
        includesSeparators(val, tokenSeparators)
      ) {
        const nextValue = this.getValueByInput(val);
        if (nextValue !== undefined) {
          this.fireChange(nextValue);
        }
        this.setOpenState(false, 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
        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 props = this.$props;
      if (props.disabled) {
        return;
      }
      const state = this.$data;
      const keyCode = event.keyCode;
      if (isMultipleOrTags(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
        event.preventDefault();
      } else if (keyCode === KeyCode.ESC) {
        if (state._open) {
          this.setOpenState(false);
          event.preventDefault();
          event.stopPropagation();
        }
        return;
      }

      if (this.getRealOpenState(state) && 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];
      this.fireSelect(selectedValue);
      if (isMultipleOrTags(props)) {
        if (findIndexInValueBySingleValue(value, selectedValue) !== -1) {
          return;
        }
        value = value.concat([selectedValue]);
      } else {
        if (
          lastValue !== undefined &&
          lastValue === selectedValue &&
          selectedValue !== this.$data._backfillValue
        ) {
          this.setOpenState(false, true);
          return;
        }
        value = [selectedValue];
        this.setOpenState(false, true);
      }
      this.fireChange(value);
      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) {
        this.removeSelected(getValuePropValue(item));
        return;
      }
      if (domEvent.type === 'click') {
        this.removeSelected(getValuePropValue(item));
      }
      if (this.autoClearSearchValue) {
        this.setInputValue('', false);
      }
    },

    onArrowClick(e) {
      e.stopPropagation();
      e.preventDefault();
      if (!this.disabled) {
        this.setOpenState(!this.$data._open, !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, 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 label = getLabelFromPropsValue(this.$props.value, value);
        if (label !== undefined) {
          defaultLabel = label;
        }
      }
      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 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 && !state._value[0])) {
        hidden = false;
      }
      const placeholder = props.placeholder;
      if (placeholder) {
        const p = {
          on: {
            mousedown: preventDefaultEvent,
            click: this.onPlaceholderClick,
          },
          attrs: 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() {
      this.clearBlurTime();
      if (this.disabled) {
        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 = '';
            this.$nextTick(() => {
              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));
      }, 10);
    },
    inputFocus(e) {
      if (this.$props.disabled) {
        e.preventDefault();
        return;
      }
      this.clearBlurTime();
      if (!isMultipleOrTagsOrCombobox(this.$props) && e.target === this.getInputDOMNode()) {
        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 = getAttrs(this);
      const defaultInput = <input id={attrs.id} autoComplete="off" />;

      const inputElement = props.getInputElement ? props.getInputElement() : defaultInput;
      const inputCls = classnames(getClass(inputElement), {
        [`${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
      inputElement.data = inputElement.data || {};
      return (
        <div class={`${props.prefixCls}-search__field__wrap`} onClick={this.inputClick}>
          {cloneElement(inputElement, {
            props: {
              disabled: props.disabled,
              value: inputValue,
            },
            attrs: {
              ...(inputElement.data.attrs || {}),
              disabled: props.disabled,
              value: inputValue,
            },
            domProps: {
              value: inputValue,
            },
            class: inputCls,
            directives: [
              {
                name: 'ant-ref',
                value: this.saveInputRef,
              },
            ],
            on: {
              input: this.onInputChange,
              keydown: chaining(
                this.onInputKeydown,
                inputEvents.keydown,
                this.$listeners.inputKeydown,
              ),
              focus: chaining(this.inputFocus, inputEvents.focus),
              blur: chaining(this.inputBlur, inputEvents.blur),
            },
          })}
          <span
            {...{
              directives: [
                {
                  name: 'ant-ref',
                  value: this.saveInputMirrorRef,
                },
              ],
            }}
            // ref='inputMirrorRef'
            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, needFocus) {
      const { $props: props, $data: state } = this;
      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('', false);
      }
      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;
      }
    },

    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('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, filterOption, notFoundContent } = this.$props;
      const menuItems = [];
      const childrenKeys = [];
      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)
          );
        });
        value.forEach(singleValue => {
          const key = singleValue;
          const menuItem = (
            <MenuItem
              style={UNSELECTABLE_STYLE}
              {...{ attrs: UNSELECTABLE_ATTRIBUTE }}
              value={key}
              key={key}
              role="option"
            >
              {key}
            </MenuItem>
          );
          options.push(menuItem);
          menuItems.push(menuItem);
        });
        if (inputValue) {
          const notFindInputItem = menuItems.every(option => {
            // this.filterOption return true has two meaning,
            // 1, some one exists after filtering
            // 2, filterOption is set to false
            // condition 2 does not mean the option has same value with inputValue
            const filterFn = () => getValuePropValue(option) === inputValue;
            if (filterOption !== false) {
              return !this._filterOption(inputValue, option, filterFn);
            }
            return !filterFn();
          });
          if (notFindInputItem) {
            const p = {
              attrs: UNSELECTABLE_ATTRIBUTE,
              key: inputValue,
              props: {
                value: inputValue,
                role: 'option',
              },
              style: UNSELECTABLE_STYLE,
            };
            options.unshift(<MenuItem {...p}>{inputValue}</MenuItem>);
          }
        }
      }

      if (!options.length && notFoundContent) {
        const p = {
          attrs: UNSELECTABLE_ATTRIBUTE,
          key: 'NOT_FOUND',
          props: {
            value: 'NOT_FOUND',
            disabled: true,
            role: 'option',
          },
          style: UNSELECTABLE_STYLE,
        };
        options = [<MenuItem {...p}>{notFoundContent}</MenuItem>];
      }
      return 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.data || child.data.slot !== undefined) {
          return;
        }
        if (getSlotOptions(child).isSelectOptGroup) {
          let label = getComponentFromProp(child, 'label');
          let key = child.key;
          if (!key && typeof label === 'string') {
            key = label;
          } else if (!label && key) {
            label = key;
          }
          const childChildren = getSlots(child).default;
          // 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.data}>
                  {subChild.componentOptions.children}
                </MenuItem>
              );
            });

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

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

          return;
        }
        warning(
          getSlotOptions(child).isSelectOption,
          'the children of `Select` should be `Select.Option` or `Select.OptGroup`, ' +
            `instead of \`${getSlotOptions(child).name || getSlotOptions(child)}\`.`,
        );

        const childValue = getValuePropValue(child);

        validateOptionValue(childValue, this.$props);
        if (this._filterOption(inputValue, child)) {
          const p = {
            attrs: {
              ...UNSELECTABLE_ATTRIBUTE,
              ...getAttrs(child),
            },
            key: childValue,
            props: {
              value: childValue,
              ...getPropsData(child),
              role: 'option',
            },
            style: UNSELECTABLE_STYLE,
            on: getEvents(child),
            class: getClass(child),
          };
          const menuItem = <MenuItem {...p}>{child.componentOptions.children}</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 = getComponentFromProp(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;
          }
          maxTagPlaceholderEl = (
            <li
              style={UNSELECTABLE_STYLE}
              {...{ attrs: UNSELECTABLE_ATTRIBUTE }}
              role="presentation"
              onMousedown={preventDefaultEvent}
              class={`${prefixCls}-selection__choice ${prefixCls}-selection__choice__disabled`}
              key="maxTagPlaceholder"
              title={toTitle(content)}
            >
              <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`;
            return (
              <li
                style={UNSELECTABLE_STYLE}
                {...{ attrs: UNSELECTABLE_ATTRIBUTE }}
                onMousedown={preventDefaultEvent}
                class={choiceClassName}
                role="presentation"
                key={singleValue || SELECT_EMPTY_VALUE_KEY}
                title={toTitle(title)}
              >
                <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',
            afterLeave: this.onChoiceAnimationLeave,
          });
          innerNode = (
            <transition-group {...transitionProps}>{selectedValueNodes}</transition-group>
          );
        } else {
          innerNode = <ul>{selectedValueNodes}</ul>;
        }
      }
      return (
        <div
          class={className}
          {...{
            directives: [
              {
                name: 'ant-ref',
                value: this.saveTopCtrlRef,
              },
            ],
          }}
          onClick={this.topCtrlContainerClick}
        >
          {this.getPlaceholderElement()}
          {innerNode}
        </div>
      );
    },
    renderArrow(multiple) {
      const { showArrow, loading, prefixCls } = this.$props;
      const inputIcon = getComponentFromProp(this, 'inputIcon');
      if (!showArrow) {
        return null;
      }
      // if loading  have loading icon
      if (multiple && !loading) {
        return null;
      }
      const defaultIcon = loading ? (
        <i class={`${prefixCls}-arrow-loading`} />
      ) : (
        <i class={`${prefixCls}-arrow-icon`} />
      );
      return (
        <span
          key="arrow"
          class={`${prefixCls}-arrow`}
          style={UNSELECTABLE_STYLE}
          {...{ attrs: UNSELECTABLE_ATTRIBUTE }}
          onClick={this.onArrowClick}
        >
          {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 = getComponentFromProp(this, 'clearIcon');
      const clear = (
        <span
          key="clear"
          class={`${prefixCls}-selection__clear`}
          onMousedown={preventDefaultEvent}
          style={UNSELECTABLE_STYLE}
          {...{ attrs: 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) {
      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() {
      if (this._focused || this.disabled) {
        return;
      }
      this._focused = true;
      this.updateFocusClassName();
      this.$emit('focus');
    },
    selectionRefBlur() {
      this._focused = false;
      this.updateFocusClassName();
      this.$emit('blur');
    },
  },

  render() {
    const props = this.$props;
    const multiple = isMultipleOrTags(props);
    const state = this.$data;
    const { disabled, prefixCls } = props;
    const ctrlNode = this.renderTopControlNode();
    const { _open: open, _inputValue: inputValue, _value: value } = this.$data;
    if (open) {
      this._options = this.renderFilterOptions();
    }
    const realOpen = this.getRealOpenState();
    const options = this._options || [];
    const { $listeners } = this;
    const { mouseenter = noop, mouseleave = noop, popupScroll = noop } = $listeners;
    const selectionProps = {
      props: {},
      attrs: {
        role: 'combobox',
        'aria-autocomplete': 'list',
        'aria-haspopup': 'true',
        'aria-expanded': realOpen,
        'aria-controls': this.$data._ariaId,
      },
      on: {
        click: this.selectionRefClick,
      },
      class: `${prefixCls}-selection ${prefixCls}-selection--${multiple ? 'multiple' : 'single'}`,
      directives: [
        {
          name: 'ant-ref',
          value: this.saveSelectionRef,
        },
      ],
      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 = {
      [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`]: !props.showArrow,
    };
    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}
        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={popupScroll}
        onPopupFocus={this.onPopupFocus}
        onMouseenter={mouseenter}
        onMouseleave={mouseleave}
        showAction={props.showAction}
        menuItemSelectedIcon={getComponentFromProp(this, 'menuItemSelectedIcon')}
        {...{
          directives: [
            {
              name: 'ant-ref',
              value: this.saveSelectTriggerRef,
            },
          ],
        }}
        dropdownRender={props.dropdownRender}
        ariaId={this.$data._ariaId}
      >
        <div
          {...{
            directives: [
              {
                name: 'ant-ref',
                value: this.saveRootRef,
              },
            ],
          }}
          style={getStyle(this)}
          class={classnames(rootCls)}
          onMousedown={this.markMouseDown}
          onMouseup={this.markMouseLeave}
          onMouseout={this.markMouseLeave}
          // tabindex='-1'
          // onBlur={this.onOuterBlur}
          // onFocus={this.onOuterFocus}
        >
          <div {...selectionProps}>
            {ctrlNode}
            {this.renderClear()}
            {this.renderArrow(!!multiple)}
          </div>
        </div>
      </SelectTrigger>
    );
  },
};
export { Select };
export default proxyComponent(Select);
