import React, { Component, Key, MouseEventHandler, ReactElement } from 'react';
import { findDOMNode } from 'react-dom';
import Icon from '../icon';
import { ColumnProps, TableStateFilters } from './interface';
import Select, { LabeledValue, OptionProps } from '../select';
import { filterByInputValue, getColumnKey } from './util';
import Checkbox from '../checkbox/Checkbox';
import { SelectMode } from '../select/enum';
import ConfigContext, { ConfigContextValue } from '../config-provider/ConfigContext';

const { Option, OptGroup } = Select;
const PAIR_SPLIT = ':';
const VALUE_SPLIT = '、';
const OPTION_OR = 'option-or';
export const VALUE_OR = 'OR';

function pairValue<T>(column: ColumnProps<T>, value = '') {
  const { filters } = column;
  const found = filters && filters.find(filter => String(filter.value) === value);
  return {
    key: `${getColumnKey(column)}${PAIR_SPLIT}${value}`,
    label: [column.filterTitle || column.title, PAIR_SPLIT, ' ', found ? found.text : value],
  };
}

function barPair(value: string, index: number) {
  return {
    key: `${value}${PAIR_SPLIT}${index}`,
    label: [value],
  };
}

export interface FilterSelectProps<T> {
  prefixCls?: string;
  placeholder?: string;
  dataSource?: T[];
  filters?: string[];
  columnFilters?: TableStateFilters<T>;
  columns?: ColumnProps<T>[];
  onFilter?: (column: ColumnProps<T>, nextFilters: string[]) => void;
  onChange?: (filters?: any[]) => void;
  onClear?: () => void;
  multiple?: boolean;
  getPopupContainer?: (triggerNode?: Element) => HTMLElement;
}

export interface FilterSelectState<T> {
  columns: ColumnProps<T>[];
  filters: string[];
  columnFilters: TableStateFilters<T>;
  selectColumn?: ColumnProps<T>;
  inputValue: string;
  checked: any[];
}

function removeDoubleOr(filters: LabeledValue[]): LabeledValue[] {
  return filters.filter(({ label }, index) => label !== VALUE_OR || label !== filters[index + 1]);
}

export default class FilterSelect<T> extends Component<FilterSelectProps<T>, FilterSelectState<T>> {
  static get contextType(): typeof ConfigContext {
    return ConfigContext;
  }

  static displayName = 'FilterSelect';

  context: ConfigContextValue;

  constructor(props) {
    super(props);
    this.state = {
      columns: this.getColumnsWidthFilters(),
      filters: props.filters || [],
      columnFilters: props.columnFilters || {},
      inputValue: '',
      selectColumn: undefined,
      checked: [],
    };
  }

  state: FilterSelectState<T>;

  rcSelect: any;

  columnRefs: any = {};

  componentWillReceiveProps(nextProps: FilterSelectProps<T>) {
    this.setState({
      columns: this.getColumnsWidthFilters(nextProps),
    });
    if (nextProps.filters) {
      this.setState({
        filters: nextProps.filters,
      });
    }
    if (nextProps.columnFilters) {
      this.setState({
        columnFilters: nextProps.columnFilters,
      });
    }
  }

  getPrefixCls() {
    const { prefixCls } = this.props;
    return `${prefixCls}-filter-select`;
  }

  handleDropdownMouseDown: MouseEventHandler<any> = e => {
    e.preventDefault();
    this.rcSelect.focus();
  };

  render() {
    const { placeholder, getPopupContainer } = this.props;
    const { inputValue } = this.state;
    const prefixCls = this.getPrefixCls();
    const multiple = this.isMultiple();
    return (
      <div className={prefixCls}>
        <div className={`${prefixCls}-icon`}>
          <Icon type="filter_list" />
        </div>
        <Select
          ref={this.saveRef}
          mode={SelectMode.tags}
          filterOption={false}
          onChange={this.handleChange}
          onSelect={multiple ? this.handleSelect : undefined}
          onInput={this.handleInput}
          onInputKeyDown={this.handleInputKeyDown}
          onClear={this.handleClear}
          value={this.getValue()}
          placeholder={placeholder}
          notFoundContent={false}
          showNotFindInputItem={false}
          showNotFindSelectedItem={false}
          dropdownMatchSelectWidth={false}
          defaultActiveFirstOption={!inputValue}
          dropdownStyle={{ minWidth: 256 }}
          onDropdownMouseDown={this.handleDropdownMouseDown}
          dropdownClassName={`${prefixCls}-dropdown`}
          getRootDomNode={this.getRootDomNode}
          showCheckAll={false}
          onChoiceItemClick={this.handleChoiceItemClick}
          getPopupContainer={getPopupContainer}
          allowClear
          labelInValue
          blurChange
          border={false}
        >
          {this.getOptions()}
        </Select>
        <div className={`${prefixCls}-columns`}>{this.renderColumnsTitle()}</div>
      </div>
    );
  }

  renderColumnsTitle() {
    const { columns } = this.state;
    this.columnRefs = {};
    return columns.map(col => {
      const key = getColumnKey(col);
      return (
        <span ref={this.saveColumnRef.bind(this, key)} key={key}>
          {col.filterTitle || col.title}
        </span>
      );
    });
  }

  isMultiple() {
    const { selectColumn } = this.state;
    if (selectColumn) {
      return selectColumn.filterMultiple;
    }
    return false;
  }

  saveRef = (node: any) => {
    if (node) {
      this.rcSelect = node.rcSelect;
    }
  };

  saveColumnRef = (key: Key, node: any) => {
    if (node) {
      this.columnRefs[key] = node;
    }
  };

  handleInputKeyDown = (e: any) => {
    const { value } = e.target;
    const { filters, columnFilters, selectColumn } = this.state;
    let filterText = value;
    if (selectColumn && value) {
      filterText = value.split(this.getColumnTitle(selectColumn)).slice(1);
    }
    if (e.keyCode === 13 && filterText[0]) {
      if (selectColumn) {
        const key = getColumnKey(selectColumn);
        if (key) {
          const { filters: columFilters } = selectColumn;
          columnFilters[key] = filterText;
          const found = columFilters && columFilters.find(filter => filter.text === filterText[0]);
          const filterValue = found ? String(found.value) : filterText[0];
          this.fireColumnFilterChange(key, [filterValue]);
        }
      } else {
        filters.push(value);
        this.fireChange(filters);
      }
      this.setState({
        inputValue: '',
        filters,
        columnFilters,
        selectColumn: undefined,
      });
      this.rcSelect.setState({
        inputValue: '',
      });
    }
  };

  handleInput = (value: string) => {
    let { selectColumn } = this.state;
    if (selectColumn) {
      if (value.indexOf(this.getColumnTitle(selectColumn)) === -1) {
        selectColumn = undefined;
      }
    }
    this.setState({
      selectColumn,
      inputValue: value,
    });
  };

  handleChoiceItemClick = ({ key }: LabeledValue) => {
    const pair = key.split(PAIR_SPLIT);
    if (pair.length > 1) {
      const columnKey = pair.shift();
      const selectColumn = this.findColumn(columnKey as string);
      if (selectColumn && selectColumn.filterMultiple) {
        const { filters } = selectColumn;
        const checked = pair
          .join(PAIR_SPLIT)
          .split(VALUE_SPLIT)
          .map(text => {
            const found = filters && filters.find(filter => filter.text === text);
            return found ? found.value : text;
          });
        this.setState({
          selectColumn,
          checked,
        });
      }
    }
  };

  handleSelect = ({ key }: LabeledValue) => {
    const { checked, selectColumn } = this.state;
    if (key === '__ok__') {
      this.handleMultiCheckConfirm();
    } else if (key !== `${selectColumn && selectColumn.title}:`) {
      const index = checked.indexOf(key);
      if (index === -1) {
        checked.push(key);
      } else {
        checked.splice(index, 1);
      }
      this.setState(
        {
          checked,
        },
        () => {
          if (selectColumn) {
            const { columnFilters } = this.state;
            const columnKey = getColumnKey(selectColumn);
            if (columnKey) {
              const filters = columnFilters[columnKey];
              if (!filters || !filters.length) {
                this.rcSelect.setState({
                  inputValue: this.getColumnTitle(selectColumn),
                });
              }
            }
          }
        },
      );
    }
    return false;
  };

  handleMultiCheckConfirm = () => {
    const { selectColumn, checked } = this.state;
    if (selectColumn) {
      const columnKey = getColumnKey(selectColumn);
      if (columnKey) {
        this.fireColumnFilterChange(columnKey, checked);
        this.setState({
          selectColumn: undefined,
          checked: [],
        });
        this.rcSelect.setState({
          inputValue: '',
        });
      }
    }
  };

  handleClear = () => {
    this.setState({ selectColumn: undefined });
  };

  handleChange = (changedValue: LabeledValue[]) => {
    const { state, rcSelect } = this;
    const { selectColumn, inputValue, columnFilters } = state;
    let { filters } = state;
    const all = this.getValue();
    let change = false;
    if (changedValue.length > all.length) {
      const value = changedValue.pop();
      if (inputValue && value && value.key === inputValue) {
        change = true;
        if (selectColumn && !selectColumn.filterMultiple && value) {
          const columnKey = getColumnKey(selectColumn);
          const columnTitle = this.getColumnTitle(selectColumn);
          const val = rcSelect.state.inputValue || value.key;
          if (columnKey) {
            this.fireColumnFilterChange(columnKey, [val.split(`${columnTitle}`)[1]]);
          }
        } else {
          filters.push(value.label as string);
        }
        this.setState({
          selectColumn: undefined,
          inputValue: '',
          filters,
        });
      } else if (value && value.label === OPTION_OR) {
        filters.push(VALUE_OR);
        change = true;
        this.setState({
          filters,
        });
      } else if (selectColumn) {
        if (!selectColumn.filterMultiple) {
          const columnKey = getColumnKey(selectColumn);
          if (rcSelect.state.inputValue && value && columnKey) {
            this.fireColumnFilterChange(columnKey, (selectColumn.filters && !selectColumn.filters.length && !inputValue) ? [] : [value.key]);
          }
          this.setState({
            selectColumn: undefined,
          });
        } else {
          this.setState({
            selectColumn: undefined,
            checked: [],
          });
          rcSelect.setState({
            inputValue: '',
          });
        }
      } else if (value) {
        const column = this.findColumn(value.key);
        const columnFilter = columnFilters[value.key];
        if (column && (!columnFilter || !columnFilter.length)) {
          rcSelect.setState({
            inputValue: this.getColumnTitle(column),
          });
        }
        this.setState({
          selectColumn: column,
        });
      }
    } else {
      filters = this.changeValue(changedValue, rcSelect.state.value);
      if (state.filters.length !== filters.length) {
        change = true;
      }
      this.setState({
        inputValue: '',
        filters,
      });
    }
    if (change) {
      this.fireChange(filters);
    }
  };

  fireChange(filters: any[]) {
    const { onChange } = this.props;
    if (typeof onChange === 'function') {
      onChange(filters);
    }
  }

  fireColumnFilterChange(columnKey: string | number, value: any[]) {
    const col = this.findColumn(columnKey);
    const { onFilter } = this.props;
    if (col && onFilter) {
      onFilter(col, value || null);
    }
  }

  changeValue(changedValue: LabeledValue[], oldValue: any[]): string[] {
    const { state } = this;
    const changedColumnKeys: any[] = [];
    const changedColumnFilters = state.columnFilters;
    const columnFiltersValues = this.getColumnFiltersValues();
    if (changedValue.length) {
      const len = columnFiltersValues.length;
      if (len > 0) {
        const index = oldValue.findIndex(
          (item, i) => item !== (changedValue[i] && changedValue[i].key),
        );
        if (index < columnFiltersValues.length) {
          const deleted = changedValue.splice(0, len - 1);
          if (deleted.length < 2 && changedValue[0] && changedValue[0].label === VALUE_OR) {
            changedValue.shift();
          }
          let value = columnFiltersValues[index];
          if (value === VALUE_OR) {
            value = columnFiltersValues[index + 1];
          }
          const columnKey = Object.keys(value)[0];
          const columnFilters = changedColumnFilters[columnKey].slice();
          const column = this.findColumn(columnKey);
          if (column) {
            const { filters } = column;
            value[columnKey].split(VALUE_SPLIT).forEach((text: string) => {
              const found = filters && filters.find(filter => filter.text === text);
              const filterIndex = columnFilters.indexOf(found ? found.value : text);
              if (filterIndex !== -1) {
                columnFilters.splice(filterIndex, 1);
                changedColumnFilters[columnKey] = columnFilters;
                if (changedColumnKeys.indexOf(columnKey) === -1) {
                  changedColumnKeys.push(columnKey);
                }
              }
            });
          }
        } else {
          changedValue.splice(0, len);
        }
      }
      changedColumnKeys.forEach(key => {
        this.fireColumnFilterChange(key, changedColumnFilters[key]);
      });
    } else {
      const { onClear } = this.props;
      if (onClear) {
        onClear();
      }
    }
    return removeDoubleOr(changedValue).map(item => {
      const label: any = item.label;
      if (label.constructor === Array) {
        return label && label[0];
      }
      return label;
    });
  }

  getColumnFiltersValues() {
    const values: any[] = [];
    const { columnFilters } = this.state;
    Object.keys(columnFilters).forEach(c => {
      const filteredValue = columnFilters[c];
      const column = this.findColumn(c);
      if (filteredValue && filteredValue.length && column) {
        const { filters } = column;
        values.push({
          [c]: filteredValue
            .map(value => {
              const found =
                filters && filters.find(filter => String(filter.value) === String(value));
              return found ? found.text : value;
            })
            .join(VALUE_SPLIT),
        });
      }
    });
    return values;
  }

  getValue() {
    const { filters } = this.state;
    return this.getColumnFiltersValues()
      .map(this.toValueString)
      .concat(filters.map(barPair));
  }

  getInputFilterOptions(inputValue: string) {
    const { columns, dataSource } = this.props;
    const options: ReactElement<OptionProps>[] = [];
    if (dataSource && columns) {
      const values: { [x: string]: boolean } = {};
      filterByInputValue<T>(
        dataSource,
        columns,
        inputValue,
        (record: T, column: ColumnProps<T>) => {
          const { dataIndex } = column;
          if (dataIndex) {
            const value = (record as any)[dataIndex].toString();
            if (!values[value]) {
              values[value] = true;
              options.push(
                <Option key={value} value={value}>
                  {value}
                </Option>,
              );
            }
          }
        },
      );
    }
    return options;
  }

  getOptions() {
    const { state } = this;
    const { selectColumn, inputValue, columns, checked, columnFilters } = state;
    if (selectColumn) {
      if (inputValue && inputValue.split(PAIR_SPLIT)[1]) {
        return null;
      }
      const { filters, filterMultiple } = selectColumn;
      const columnKey = getColumnKey(selectColumn);
      if (filters) {
        return filters
          .filter(filter => !filter.children)
          .map((filter, i) => {
            const value = String(filter.value);
            let text: any = filter.text;
            if (filterMultiple && columnKey) {
              let _checked = columnFilters[columnKey];
              if (_checked && !checked.length) {
                state.checked = _checked.slice();
              } else {
                _checked = checked;
              }
              text = [
                <Checkbox key="ck" className="multiple" checked={_checked.indexOf(value) !== -1} />,
                text,
              ];
            }
            return (
              <Option key={`filter-${String(i)}`} value={value}>
                {text}
              </Option>
            );
          })
          .concat(
            filterMultiple ? (
              <OptGroup key="ok">
                <Option value="__ok__" className={`${this.getPrefixCls()}-ok-btn`}>
                  确认
                </Option>
              </OptGroup>
            ) : (
              []
            ),
          );
      }
    } else if (inputValue) {
      return this.getInputFilterOptions(inputValue);
    } else {
      const { filters } = this.state;
      const { multiple } = this.props;
      const { length } = filters;
      const value = this.getColumnFiltersValues();
      const keys = value.map(item => Object.keys(item)[0]);
      const options = columns.reduce((opts: any[], column, i) => {
        const key = getColumnKey(column, i);
        if (keys.indexOf(key as string) === -1 || column.filterMultiple) {
          opts.push(
            <Option key={`column-${key}`} value={key}>
              <span>{column.filterTitle || column.title}</span>
            </Option>,
          );
        }
        return opts;
      }, []);
      if (multiple && (length ? filters[length - 1] !== VALUE_OR : value.length)) {
        return [
          <OptGroup key="or">
            <Option value={OPTION_OR}>OR</Option>
          </OptGroup>,
          <OptGroup key="all">{options}</OptGroup>,
        ];
      }
      return options;
    }
  }

  getColumnsWidthFilters(props = this.props) {
    return (props.columns || []).filter(column => column.filters instanceof Array);
  }

  findColumn(myKey: string | number) {
    const { columns } = this.state;
    return columns.find(c => getColumnKey(c) === myKey);
  }

  toValueString = (item: any) => {
    const key = Object.keys(item)[0];
    const col = this.findColumn(key);
    if (col) {
      return pairValue(col, item[key]);
    }
    return '';
  };

  getRootDomNode = (): HTMLElement => {
    const { getPrefixCls } = this.context;
    return (findDOMNode(this) as HTMLElement).querySelector(
      `.${getPrefixCls('select')}-search__field`,
    ) as HTMLElement;
  };

  getColumnTitle(column: ColumnProps<T>) {
    const columnKey = getColumnKey(column);
    if (columnKey) {
      return `${this.columnRefs[columnKey].textContent}${PAIR_SPLIT}`;
    }
    return '';
  }
}