import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import Icon from '../../icon/index';
import DropdownOption from './DropdownOption';
import DropdownOptionGroup from './DropdownOptionGroup';
import DropdownInfiniteOptions from './DropdownInfiniteOptions';
var debounce = require('lodash.debounce');
// @require '../style/index.scss'

/**
 * 下拉组件
 */
class Dropdown extends React.Component {

  static propTypes = {
    style: PropTypes.object,
    className: PropTypes.string,
    /**
     * 是否可以启用文字输入框搜索选项
     */
    searchable: PropTypes.bool,
    /**
     * 当hover时是否展示下拉
     */
    hoverable: PropTypes.bool,
    /**
     * @transformable
     * options属性是一个数组用于传入所有下拉选项
     */
    options: PropTypes.arrayOf(DropdownOption.propTypes.option),
    /**
     * @transformable
     * 选中的选项的value值,可动态设置dropdown的值
     */
    value: PropTypes.any,
    /**
     * 是否在下拉列表的被选中的项的右侧显示选中icon
     */
    showSelectedIcon: PropTypes.bool,
    /**
     * 当选中的值发生变化时调用
     * callback(newValue,oldValue)
     */
    onChange: PropTypes.func,
    /**
     * 用户离开dropdown组件时回调
     * callback(newName)
     */
    onBlur: PropTypes.func,
    /**
     * 用户进入dropdown组件时回调
     */
    onFocus: PropTypes.func,
    /**
     * 下拉菜单收起时回调
     */
    onCloseDropDown: PropTypes.func,
    /**
     * 当前dropdown组件是否不可用
     */
    disabled: PropTypes.bool,
    /**
     * 没有选中时的placeholder提示
     */
    placeholder: PropTypes.string,
    /**
     * 是否可以清除dropdown所选的值，让它不选择任何一个
     */
    cleanable: PropTypes.bool,
    /**
     * 是否要隐藏边框
     */
    noBorder: PropTypes.bool,
    /**
     * 是否要隐藏右边的下拉icon
     */
    noIcon: PropTypes.bool,
    /**
     * 自定义右边的下拉icon
     */
    icon: PropTypes.any,
    /**
     * 下拉菜单的width px
     */
    dropdownWidth: PropTypes.number,
    /** 
     * 搜索关键词变化时回调 
     */
    onSearch: PropTypes.func,
  };

  static defaultProps = {
    searchable: false,
    hoverable: false,
    cleanable: false,
    noBorder: false,
    showSelectedIcon: false,
    noIcon: false,
    disabled: false,
  };

  static childContextTypes = {
    dropdown: PropTypes.object,
  };

  getChildContext() {
    return { dropdown: this };
  }

  constructor(props) {
    super(props);
    let { options, value } = this.props;
    let selectedOption = this.handlePropOptions(options, value);
    this.state = {
      // 当前选中
      selectedOption,
      // 搜索关键字
      searchKeyword: selectedOption ? selectedOption.search : '',
      // 当前是否正在选择中
      showDropdown: false,
      // 搜索框第一次获焦
      firstFocus: false,
    };
  }

  componentWillReceiveProps(nextProps) {
    let { options, value, onSearch } = nextProps;
    let prevSelectedOption = this.state.selectedOption;
    let selectedOption;

    if (value === this.props.value && prevSelectedOption) {
      // 如果value没有变化，则state中的value更高优先
      value = prevSelectedOption.value;
    }

    if (onSearch && prevSelectedOption) {
      // 开启远程搜索情况下，之前选择过，直接使用原选中值，
      // 避免搜索过程中options发生变化后，无法通过value遍历找到原选中值
      selectedOption = prevSelectedOption;
    } else {
      selectedOption = this.handlePropOptions(options, value);

      if (onSearch && selectedOption) {
        // 初始进入，未做选择的情况下，使用默认value值
        this.state.searchKeyword = selectedOption.search;
      }
    }

    this.setState({
      selectedOption,
      searchKeyword: onSearch ? this.state.searchKeyword : (selectedOption ? selectedOption.search :  '')
    });
  }

  /**
   * 处理传入的 options 属性
   * 当 option.display === undefined 时把 option.display = option.value;
   * 当 option.search === undefined 时把 option.search = option.value;
   * @param options
   * @param defaultValue
   * @return selectedOption
   */
  handlePropOptions = (options, defaultValue) => {
    let selectedOption = undefined;
    if (Array.isArray(options)) {
      options.forEach(option => {
        if (option.options) { // 递归处理选项组
          selectedOption = this.handlePropOptions(option.options, defaultValue);
        } else {
          if (option.display === undefined) {
            option.display = option.value;
          }
          if (option.search === undefined) {
            option.search = String(option.value);
          }
          if (option.value === defaultValue) {
            selectedOption = option;
          }
        }
      });
    }
    return selectedOption;
  }

  onSelect = (option) => {
    const { onChange, onBlur } = this.props;
    const { selectedOption: oldSelectedOption } = this.state;
    this.closeDropdown();
    this.setState({
      selectedOption: option,
      searchKeyword: option.search,
    }, () => {
      if (onChange) {
        onChange(option.value, oldSelectedOption ? oldSelectedOption.value : null, option);
      }
      if (typeof onBlur === 'function') {
        onBlur(option.value);
      }
    });
  };

  onSearch = (event) => {
    const searchKeyword = event ? event.currentTarget.value : this.state.searchKeyword;

    if (this.props.onSearch) {
      this.debounceSearchChange(searchKeyword);
    }

    this.setState({
      showDropdown: true,
      searchKeyword,
      firstFocus: false,
    });
  };

  debounceSearchChange = debounce((searchKeyword) => {
    this.handleSearchChange(searchKeyword);
  }, 300)

  handleComposition = (event) => {
    const text = event.target.value;
    if (event.type === 'compositionend') {
      this.isOnComposition = false;
      this.handleSearchChange(text);
    } else {
      this.isOnComposition = true;
    }
  }

  handleSearchChange = (val) => {
   if (this.prevSearchKeyword === val || this.isOnComposition) return;

   this.prevSearchKeyword = val;

   const { onSearch } = this.props;

   if (onSearch) {
     onSearch(val);
   }
  }

  toggleDropdown = (event) => {
    const { showDropdown } = this.state;
    if (showDropdown && event.target === this.input) {
      return;
    }
    if (showDropdown === false) {
      this.showDropdown();
    } else {
      this.closeDropdown();
    }
  };

  showDropdown = () => {
    const { onFocus, searchable } = this.props;
    if (typeof onFocus === 'function') {
      onFocus();
    }
    const state = {
      showDropdown: true,
    };
    if (searchable) {
      state.firstFocus = true;
      if (this.input) {
        this.input.focus();
      }
    }
    this.setState(state);
  };

  closeDropdown = () => {
    const { searchable, onCloseDropDown } = this.props;
    const { selectedOption } = this.state;
    const state = {
      showDropdown: false,
      searchKeyword: selectedOption ? selectedOption.search : '',
    };

    if (searchable) {
      state.firstFocus = false;
      if (this.input) {
        this.input.blur();
      }
    }
    this.setState(state);
    if (typeof onCloseDropDown === 'function') {
      onCloseDropDown();
    }
  };

  cleanValue = (event) => {
    event.stopPropagation();
    const { onChange } = this.props;
    if (onChange) {
      onChange(null, this.state.selectedOption.value);
    }
    this.setState({
      selectedOption: undefined,
    });
  };

  getValue = () => {
    const { selectedOption } = this.state;

    return selectedOption ? selectedOption.value : null;
  }

  /**
   * 渲染主体显示的内容
   */
  renderSelect() {
    const { options, searchable, placeholder, disabled } = this.props;
    const { selectedOption, searchKeyword } = this.state;
    let elem;
    if (!options) {
      // 加载中
      elem = (
        <div className="im-dropdown-wrap-loading">
          <i className="im-dropdown-wrap-loading-icon"> </i>
        </div>
      );
    } else if (searchable) {
      // 可以搜索
      elem = (
        <input
          className="im-dropdown-wrap-text im-dropdown-wrap-input"
          type="text" value={searchKeyword}
          disabled={disabled}
          placeholder={placeholder}
          onChange={this.onSearch}
          onCompositionEnd={this.handleComposition}
          onCompositionStart={this.handleComposition}
          onCompositionupdate={this.handleComposition}
          ref={(input) => this.input = input}
        />
      );
    } else {
      // 直接显示文字
      elem = (
        <div className="im-dropdown-wrap-text">
          {
            selectedOption ?
              selectedOption.display :
              <span className="im-dropdown-wrap-text-placeholder">{placeholder}</span>
          }
        </div>
      );
    }
    return elem;
  }
  /**
   * 渲染清除按钮
   */
  renderClean() {
    const { cleanable } = this.props;
    const { selectedOption } = this.state;
    return cleanable && selectedOption && <Icon className="clean-icon" type="close" onClick={this.cleanValue} />;
  }
  /**
   * 渲染下拉箭头
   */
  renderArrow() {
    const { icon, noIcon } = this.props;
    return !noIcon && (icon || <span className="im-dropdown-wrap-arrow-icon" />);
  }
  /**
   * 渲染所有选项
   */
  renderOptions() {
    const { dropdownWidth, options, children } = this.props;
    const { showDropdown } = this.state;
    return (
      <div
        className="im-dropdown-dropdown"
        style={{
          display: showDropdown ? 'block' : 'none',
          width: dropdownWidth,
        }}
      >{
        <ul className="im-dropdown-dropdown-list">{
          children || Array.isArray(options) && options.map((option) => this.renderOption(option))
        }</ul>
      }</div>
    );
  }
  /**
   * 渲染选项组或选项
   */
  renderOption(option, disabled) {
    const { label, options, value, hidden } = option;
    if (hidden) {
      return null;
    }

    let elem;
    let optionKey;
    if (label) {
      elem = (
        <DropdownOptionGroup key={label} label={label}>{
          options.map((opt) => this.renderOption(opt))
        }</DropdownOptionGroup>
      );
    } else {
      if (value && typeof value === 'object') {
        optionKey = typeof value.key !== 'undefined' ? value.key : JSON.stringify(value);
      } else {
        optionKey = value;
      }
      elem = (
        <DropdownOption
          key={optionKey}
          option={option}
          disabled={disabled}
        />
      );
    }
    return elem;
  }

  render() {
    const {
      className, style, hoverable,
      noBorder, disabled,
    } = this.props;
    return (
      <div
        className={classNames('im-dropdown', className)}
        style={style}
        onMouseLeave={this.closeDropdown}
      >
        <div
          className={classNames({
            'im-dropdown-wrap': true,
            disabled,
          })}
          onClick={disabled ? null : this.toggleDropdown}
          onMouseEnter={hoverable && !disabled ? this.showDropdown : null}
          style={noBorder ? { border: 'none' } : null}
        >
          {this.renderSelect()}
          {this.renderClean()}
          {this.renderArrow()}
        </div>
        {this.renderOptions()}
      </div>
    );
  }
}

Dropdown.Option = DropdownOption;
Dropdown.OptionGroup = DropdownOptionGroup;
Dropdown.DropdownInfiniteOptions = DropdownInfiniteOptions;

export default Dropdown;

