import React, { ReactElement, ReactNode } from 'react';
import { observer } from 'mobx-react';
import { computed, isArrayLike } from 'mobx';
import classNames from 'classnames';
import { Select, SelectProps } from '../select/Select';
import ObserverRadio from '../radio/Radio';
import ObserverCheckBox from '../check-box/CheckBox';
import autobind from '../_util/autobind';
import { ValidationMessages } from '../validator/Validator';
import Option, { OptionProps } from '../option/Option';
import OptGroup from '../option/OptGroup';
import { ViewMode } from '../radio/enum';
import { $l } from '../locale-context';
import { LabelLayout } from '../form/enum';
import TextField from '../text-field';
import Icon from '../icon';
import Button from '../button/Button';
import { FuncType } from '../button/enum';
import { OTHER_OPTION_PROPS } from '../option/normalizeOptions';

const GroupIdGen = (function* (id) {
  while (true) {
    yield `__group-${id++}__`;
  }
})(1);

export interface SelectBoxProps extends SelectProps {
  /**
   * 是否垂直显示
   */
  vertical?: boolean;
  mode?: ViewMode;
}

export class SelectBox<T extends SelectBoxProps = SelectBoxProps> extends Select<T> {
  static displayName = 'SelectBox';

  static defaultProps = {
    ...Select.defaultProps,
    suffixCls: 'select-box',
    vertical: false,
    selectAllButton: false,
    checkValueOnOptionsChange: false,
  };

  static Option = Option;

  static OptGroup = OptGroup;

  // eslint-disable-next-line camelcase
  static __IS_IN_CELL_EDITOR = true;

  get defaultValidationMessages(): ValidationMessages {
    const label = this.getProp('label');
    return {
      valueMissing: $l('SelectBox', label ? 'value_missing' : 'value_missing_no_label', {
        label,
      }),
    };
  }

  get searchable(): boolean {
    const { searchable = this.getContextConfig('selectBoxSearchable') } = this.observableProps;
    return !!searchable;
  }

  @computed
  get name(): string | undefined {
    return this.observableProps.name || GroupIdGen.next().value;
  }

  get mode(): ViewMode | undefined {
    return this.props.mode;
  }

  isSearchFieldInPopup(): boolean | undefined {
    return false;
  }

  getOmitPropsKeys(): string[] {
    return super.getOmitPropsKeys().concat([
      'vertical',
    ]);
  }

  getClassName(...props): string | undefined {
    const {
      prefixCls,
      props: { vertical },
    } = this;
    return super.getClassName(
      {
        [`${prefixCls}-vertical`]: vertical,
      },
      ...props,
    );
  }

  getWrapperClassNamesExcludeOutput(prefixCls, _): object | undefined {
    return super.getWrapperClassNamesExcludeOutput(prefixCls, false);
  }

  renderSearcher(): ReactNode {
    if (this.searchable) {
      const { placeholder } = this.props;
      return (
        <TextField
          prefix={<Icon type="search" />}
          placeholder={placeholder}
          value={this.text}
          onInput={this.handleInput}
          labelLayout={this.labelLayout}
        />
      );
    }
  }

  getOptionOtherProps(_: boolean): OptionProps {
    return {};
  }

  renderWrapper(): ReactNode {
    const { name, options, filteredOptions, textField, valueField, readOnly, disabled, mode } = this;
    const { autoFocus, onOption, optionRenderer, optionsFilter } = this.props;
    const highlight = this.getDisplayProp('highlight');
    let hasRef = false;
    const items = filteredOptions.reduce<ReactElement<any>[]>((arr, record, index, data) => {
      if (!optionsFilter || optionsFilter(record, index, data)) {
        const optionProps = onOption({ dataSet: options, record });
        const text = record.get(textField);
        const value = record.get(valueField);
        const checked = this.isChecked(this.getValue(), value);
        const children = optionRenderer
          ? optionRenderer({ dataSet: options, record, text, value })
          : text;
        const itemProps: OptionProps = {
          ...record.get(OTHER_OPTION_PROPS),
          key: index,
          dataSet: null,
          record: null,
          value,
          checked,
          name,
          onChange: this.handleItemChange,
          children,
          autoFocus: autoFocus && index === 0,
          readOnly,
          disabled,
          mode,
          noValidate: true,
          labelLayout: LabelLayout.none,
          highlight,
        };
        if (!hasRef && !disabled && !(itemProps.disabled || optionProps.disabled)) {
          itemProps.ref = this.elementReference;
          hasRef = true;
        }
        Object.assign(itemProps, this.getOptionOtherProps(checked));
        arr.push(this.renderItem(optionProps ? {
          ...optionProps,
          ...itemProps,
          className: classNames(optionProps.className, itemProps.className),
          style: {
            ...optionProps.style,
            ...itemProps.style,
          },
          disabled: itemProps.disabled || optionProps.disabled,
        } : itemProps));
      }
      return arr;
    }, []);

    return (
      <span key="wrapper" {...this.getWrapperProps()}>
        {this.renderSearcher()}
        {this.renderSelectAll()}
        {this.renderSelectItems(items)}
        {this.renderFloatLabel()}
        {
          options.paging && options.currentPage < options.totalPage && (
            <Button funcType={FuncType.flat} icon="more_horiz" onClick={this.handleQueryMore} />
          )
        }
      </span>
    );
  }

  @autobind
  renderSelectItems(items: ReactNode): ReactNode {
    const className = this.getClassName();
    const Element = this.context.formNode ? 'div' : 'form';
    return (
      <Element className={className} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
        {items}
      </Element>
    );
  }

  @autobind
  handleQueryMore() {
    const { options } = this;
    options.queryMore(options.currentPage + 1);
  }

  @autobind
  handleInput(e) {
    const { value } = e.target;
    this.setText(value);
  }

  @autobind
  handleItemChange(value, oldValue) {
    if (this.multiple) {
      const values = this.getValues();
      if (!value) {
        values.splice(values.indexOf(oldValue), 1);
      } else {
        values.push(value);
      }
      this.setValue(values);
    } else {
      this.setValue(value);
    }
  }

  isChecked(value, checkedValue) {
    if (isArrayLike(value)) {
      return value.indexOf(checkedValue) !== -1;
    }
    return value === checkedValue;
  }

  renderItem(props) {
    if (this.multiple) {
      return <ObserverCheckBox {...props} />;
    }
    return <ObserverRadio {...props} />;
  }
}

@observer
export default class ObserverSelectBox extends SelectBox<SelectBoxProps> {
  static defaultProps = SelectBox.defaultProps;

  static Option = Option;

  static OptGroup = OptGroup;

  // eslint-disable-next-line camelcase
  static __IS_IN_CELL_EDITOR = true;
}