text: import Vue from 'vue';
  import debounce from 'throttle-debounce/debounce';
  import merge from 'ttd-element/src/utils/merge';
  import { hasClass, addClass, removeClass } from 'ttd-element/src/utils/dom';
  import { orderBy, getColumnById, getRowIdentity, getColumnByKey } from './util';
  
  const sortData = (data, states) => {
    const sortingColumn = states.sortingColumn;
    if (!sortingColumn || typeof sortingColumn.sortable === 'string') {
      return data;
    }
    return orderBy(data, states.sortProp, states.sortOrder, sortingColumn.sortMethod, sortingColumn.sortBy);
  };
  
  const getKeysMap = function(array, rowKey) {
    const arrayMap = {};
    (array || []).forEach((row, index) => {
      arrayMap[getRowIdentity(row, rowKey)] = { row, index };
    });
    return arrayMap;
  };
  
  const toggleRowSelection = function(states, row, selected) {
    let changed = false;
    const selection = states.selection;
    const index = selection.indexOf(row);
    if (typeof selected === 'undefined') {
      if (index === -1) {
        selection.push(row);
        changed = true;
      } else {
        selection.splice(index, 1);
        changed = true;
      }
    } else {
      if (selected && index === -1) {
        selection.push(row);
        changed = true;
      } else if (!selected && index > -1) {
        selection.splice(index, 1);
        changed = true;
      }
    }
  
    return changed;
  };
  
  const toggleRowExpansion = function(states, row, expanded) {
    let changed = false;
    const expandRows = states.expandRows;
    if (typeof expanded !== 'undefined') {
      const index = expandRows.indexOf(row);
      if (expanded) {
        if (index === -1) {
          expandRows.push(row);
          changed = true;
        }
      } else {
        if (index !== -1) {
          expandRows.splice(index, 1);
          changed = true;
        }
      }
    } else {
      const index = expandRows.indexOf(row);
      if (index === -1) {
        expandRows.push(row);
        changed = true;
      } else {
        expandRows.splice(index, 1);
        changed = true;
      }
    }
  
    return changed;
  };
  
  const TableStore = function(table, initialState = {}) {
    if (!table) {
      throw new Error('Table is required.');
    }
    this.table = table;
  
    this.states = {
      rowKey: null,
      _columns: [],
      originColumns: [],
      columns: [],
      fixedColumns: [],
      rightFixedColumns: [],
      leafColumns: [],
      fixedLeafColumns: [],
      rightFixedLeafColumns: [],
      leafColumnsLength: 0,
      fixedLeafColumnsLength: 0,
      rightFixedLeafColumnsLength: 0,
      isComplex: false,
      filteredData: null,
      data: null,
      sortingColumn: null,
      sortProp: null,
      sortOrder: null,
      isAllSelected: false,
      selection: [],
      reserveSelection: false,
      selectable: null,
      currentRow: null,
      hoverRow: null,
      filters: {},
      expandRows: [],
      defaultExpandAll: false,
      selectOnIndeterminate: false
    };
  
    for (let prop in initialState) {
      if (initialState.hasOwnProperty(prop) && this.states.hasOwnProperty(prop)) {
        this.states[prop] = initialState[prop];
      }
    }
  };
  
  TableStore.prototype.mutations = {
    setData(states, data) {
      const dataInstanceChanged = states._data !== data;
      states._data = data;
  
      Object.keys(states.filters).forEach((columnId) => {
        const values = states.filters[columnId];
        if (!values || values.length === 0) return;
        const column = getColumnById(this.states, columnId);
        if (column && column.filterMethod) {
          data = data.filter((row) => {
            return values.some(value => column.filterMethod.call(null, value, row, column));
          });
        }
      });
  
      states.filteredData = data;
      states.data = sortData((data || []), states);
  
      this.updateCurrentRow();
  
      const rowKey = states.rowKey;
  
      if (!states.reserveSelection) {
        if (dataInstanceChanged) {
          this.clearSelection();
        } else {
          this.cleanSelection();
        }
        this.updateAllSelected();
      } else {
        if (rowKey) {
          const selection = states.selection;
          const selectedMap = getKeysMap(selection, rowKey);
  
          states.data.forEach((row) => {
            const rowId = getRowIdentity(row, rowKey);
            const rowInfo = selectedMap[rowId];
            if (rowInfo) {
              selection[rowInfo.index] = row;
            }
          });
  
          this.updateAllSelected();
        } else {
          console.warn('WARN: rowKey is required when reserve-selection is enabled.');
        }
      }
  
      const defaultExpandAll = states.defaultExpandAll;
      if (defaultExpandAll) {
        this.states.expandRows = (states.data || []).slice(0);
      } else if (rowKey) {
        // update expandRows to new rows according to rowKey
        const ids = getKeysMap(this.states.expandRows, rowKey);
        let expandRows = [];
        for (const row of states.data) {
          const rowId = getRowIdentity(row, rowKey);
          if (ids[rowId]) {
            expandRows.push(row);
          }
        }
        this.states.expandRows = expandRows;
      } else {
        // clear the old rows
        this.states.expandRows = [];
      }
  
      Vue.nextTick(() => this.table.updateScrollY());
    },
  
    changeSortCondition(states, options) {
      states.data = sortData((states.filteredData || states._data || []), states);
  
      const { $el, highlightCurrentRow } = this.table;
      if ($el && highlightCurrentRow) {
        const data = states.data;
        const tr = $el.querySelector('tbody').children;
        const rows = [].filter.call(tr, row => hasClass(row, 'el-table__row'));
        const row = rows[data.indexOf(states.currentRow)];
  
        [].forEach.call(rows, row => removeClass(row, 'current-row'));
        addClass(row, 'current-row');
      }
  
      if (!options || !options.silent) {
        this.table.$emit('sort-change', {
          column: this.states.sortingColumn,
          prop: this.states.sortProp,
          order: this.states.sortOrder
        });
      }
  
      Vue.nextTick(() => this.table.updateScrollY());
    },
  
    sort(states, options) {
      const { prop, order } = options;
      if (prop) {
        states.sortProp = prop;
        states.sortOrder = order || 'ascending';
        Vue.nextTick(() => {
          for (let i = 0, length = states.columns.length; i < length; i++) {
            let column = states.columns[i];
            if (column.property === states.sortProp) {
              column.order = states.sortOrder;
              states.sortingColumn = column;
              break;
            }
          }
  
          if (states.sortingColumn) {
            this.commit('changeSortCondition');
          }
        });
      }
    },
  
    filterChange(states, options) {
      let { column, values, silent, multi } = options;
      if (values && !Array.isArray(values)) {
        values = [values];
      }
      const filters = {};
  
      if (multi) {
        column.forEach(col => {
          states.filters[col.id] = values;
          filters[col.columnKey || col.id] = values;
        });
      } else {
        const prop = column.property;
  
        if (prop) {
          states.filters[column.id] = values;
          filters[column.columnKey || column.id] = values;
        }
      }
  
      let data = states._data;
  
      Object.keys(states.filters).forEach((columnId) => {
        const values = states.filters[columnId];
        if (!values || values.length === 0) return;
        const column = getColumnById(this.states, columnId);
        if (column && column.filterMethod) {
          data = data.filter((row) => {
            return values.some(value => column.filterMethod.call(null, value, row, column));
          });
        }
      });
  
      states.filteredData = data;
      states.data = sortData(data, states);
  
      if (!silent) {
        this.table.$emit('filter-change', filters);
      }
  
      Vue.nextTick(() => this.table.updateScrollY());
    },
  
    insertColumn(states, column, index, parent) {
      let array = states._columns;
      if (parent) {
        array = parent.children;
        if (!array) array = parent.children = [];
      }
  
      if (typeof index !== 'undefined') {
        array.splice(index, 0, column);
      } else {
        array.push(column);
      }
  
      if (column.type === 'selection') {
        states.selectable = column.selectable;
        states.reserveSelection = column.reserveSelection;
      }
  
      if (this.table.$ready) {
        this.updateColumns(); // hack for dynamics insert column
        this.scheduleLayout();
      }
    },
  
    removeColumn(states, column, parent) {
      let array = states._columns;
      if (parent) {
        array = parent.children;
        if (!array) array = parent.children = [];
      }
      if (array) {
        array.splice(array.indexOf(column), 1);
      }
  
      if (this.table.$ready) {
        this.updateColumns(); // hack for dynamics remove column
        this.scheduleLayout();
      }
    },
  
    setHoverRow(states, row) {
      states.hoverRow = row;
    },
  
    setCurrentRow(states, row) {
      const oldCurrentRow = states.currentRow;
      states.currentRow = row;
  
      if (oldCurrentRow !== row) {
        this.table.$emit('current-change', row, oldCurrentRow);
      }
    },
  
    rowSelectedChanged(states, row) {
      const changed = toggleRowSelection(states, row);
      const selection = states.selection;
  
      if (changed) {
        const table = this.table;
        table.$emit('selection-change', selection ? selection.slice() : []);
        table.$emit('select', selection, row);
      }
  
      this.updateAllSelected();
    },
  
    toggleAllSelection: debounce(10, function(states) {
      const data = states.data || [];
      if (data.length === 0) return;
      const selection = this.states.selection;
      // when only some rows are selected (but not all), select or deselect all of them
      // depending on the value of selectOnIndeterminate
      const value = states.selectOnIndeterminate
        ? !states.isAllSelected
        : !(states.isAllSelected || selection.length);
      let selectionChanged = false;
  
      data.forEach((item, index) => {
        if (states.selectable) {
          if (states.selectable.call(null, item, index) && toggleRowSelection(states, item, value)) {
            selectionChanged = true;
          }
        } else {
          if (toggleRowSelection(states, item, value)) {
            selectionChanged = true;
          }
        }
      });
  
      const table = this.table;
      if (selectionChanged) {
        table.$emit('selection-change', selection ? selection.slice() : []);
      }
      table.$emit('select-all', selection);
      states.isAllSelected = value;
    })
  };
  
  const doFlattenColumns = (columns) => {
    const result = [];
    columns.forEach((column) => {
      if (column.children) {
        result.push.apply(result, doFlattenColumns(column.children));
      } else {
        result.push(column);
      }
    });
    return result;
  };
  
  TableStore.prototype.updateColumns = function() {
    const states = this.states;
    const _columns = states._columns || [];
    states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left');
    states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right');
  
    if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
      _columns[0].fixed = true;
      states.fixedColumns.unshift(_columns[0]);
    }
  
    const notFixedColumns = _columns.filter(column => !column.fixed);
    states.originColumns = [].concat(states.fixedColumns).concat(notFixedColumns).concat(states.rightFixedColumns);
  
    const leafColumns = doFlattenColumns(notFixedColumns);
    const fixedLeafColumns = doFlattenColumns(states.fixedColumns);
    const rightFixedLeafColumns = doFlattenColumns(states.rightFixedColumns);
  
    states.leafColumnsLength = leafColumns.length;
    states.fixedLeafColumnsLength = fixedLeafColumns.length;
    states.rightFixedLeafColumnsLength = rightFixedLeafColumns.length;
  
    states.columns = [].concat(fixedLeafColumns).concat(leafColumns).concat(rightFixedLeafColumns);
    states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0;
  };
  
  TableStore.prototype.isSelected = function(row) {
    return (this.states.selection || []).indexOf(row) > -1;
  };
  
  TableStore.prototype.clearSelection = function() {
    const states = this.states;
    states.isAllSelected = false;
    const oldSelection = states.selection;
    if (states.selection.length) {
      states.selection = [];
    }
    if (oldSelection.length > 0) {
      this.table.$emit('selection-change', states.selection ? states.selection.slice() : []);
    }
  };
  
  TableStore.prototype.setExpandRowKeys = function(rowKeys) {
    const expandRows = [];
    const data = this.states.data;
    const rowKey = this.states.rowKey;
    if (!rowKey) throw new Error('[Table] prop row-key should not be empty.');
    const keysMap = getKeysMap(data, rowKey);
    rowKeys.forEach((key) => {
      const info = keysMap[key];
      if (info) {
        expandRows.push(info.row);
      }
    });
  
    this.states.expandRows = expandRows;
  };
  
  TableStore.prototype.toggleRowSelection = function(row, selected) {
    const changed = toggleRowSelection(this.states, row, selected);
    if (changed) {
      this.table.$emit('selection-change', this.states.selection ? this.states.selection.slice() : []);
    }
  };
  
  TableStore.prototype.toggleRowExpansion = function(row, expanded) {
    const changed = toggleRowExpansion(this.states, row, expanded);
    if (changed) {
      this.table.$emit('expand-change', row, this.states.expandRows);
      this.scheduleLayout();
    }
  };
  
  TableStore.prototype.isRowExpanded = function(row) {
    const { expandRows = [], rowKey } = this.states;
    if (rowKey) {
      const expandMap = getKeysMap(expandRows, rowKey);
      return !!expandMap[getRowIdentity(row, rowKey)];
    }
    return expandRows.indexOf(row) !== -1;
  };
  
  TableStore.prototype.cleanSelection = function() {
    const selection = this.states.selection || [];
    const data = this.states.data;
    const rowKey = this.states.rowKey;
    let deleted;
    if (rowKey) {
      deleted = [];
      const selectedMap = getKeysMap(selection, rowKey);
      const dataMap = getKeysMap(data, rowKey);
      for (let key in selectedMap) {
        if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
          deleted.push(selectedMap[key].row);
        }
      }
    } else {
      deleted = selection.filter((item) => {
        return data.indexOf(item) === -1;
      });
    }
  
    deleted.forEach((deletedItem) => {
      selection.splice(selection.indexOf(deletedItem), 1);
    });
  
    if (deleted.length) {
      this.table.$emit('selection-change', selection ? selection.slice() : []);
    }
  };
  
  TableStore.prototype.clearFilter = function(columnKeys) {
    const states = this.states;
    const { tableHeader, fixedTableHeader, rightFixedTableHeader } = this.table.$refs;
    let panels = {};
  
    if (tableHeader) panels = merge(panels, tableHeader.filterPanels);
    if (fixedTableHeader) panels = merge(panels, fixedTableHeader.filterPanels);
    if (rightFixedTableHeader) panels = merge(panels, rightFixedTableHeader.filterPanels);
  
    const keys = Object.keys(panels);
    if (!keys.length) return;
  
    if (typeof columnKeys === 'string') {
      columnKeys = [columnKeys];
    }
    if (Array.isArray(columnKeys)) {
      const columns = columnKeys.map(key => getColumnByKey(states, key));
      keys.forEach(key => {
        const column = columns.find(col => col.id === key);
        if (column) {
          panels[key].filteredValue = [];
        }
      });
      this.commit('filterChange', {
        column: columns,
        value: [],
        silent: true,
        multi: true
      });
    } else {
      keys.forEach(key => {
        panels[key].filteredValue = [];
      });
  
      states.filters = {};
  
      this.commit('filterChange', {
        column: {},
        values: [],
        silent: true
      });
    }
  };
  
  TableStore.prototype.clearSort = function() {
    const states = this.states;
    if (!states.sortingColumn) return;
    states.sortingColumn.order = null;
    states.sortProp = null;
    states.sortOrder = null;
  
    this.commit('changeSortCondition', {
      silent: true
    });
  };
  
  TableStore.prototype.updateAllSelected = function() {
    const states = this.states;
    const { selection, rowKey, selectable, data } = states;
    if (!data || data.length === 0) {
      states.isAllSelected = false;
      return;
    }
  
    let selectedMap;
    if (rowKey) {
      selectedMap = getKeysMap(states.selection, rowKey);
    }
  
    const isSelected = function(row) {
      if (selectedMap) {
        return !!selectedMap[getRowIdentity(row, rowKey)];
      } else {
        return selection.indexOf(row) !== -1;
      }
    };
  
    let isAllSelected = true;
    let selectedCount = 0;
    for (let i = 0, j = data.length; i < j; i++) {
      const item = data[i];
      const isRowSelectable = selectable && selectable.call(null, item, i);
      if (!isSelected(item)) {
        if (!selectable || isRowSelectable) {
          isAllSelected = false;
          break;
        }
      } else {
        selectedCount++;
      }
    }
  
    if (selectedCount === 0) isAllSelected = false;
  
    states.isAllSelected = isAllSelected;
  };
  
  TableStore.prototype.scheduleLayout = function(updateColumns) {
    if (updateColumns) {
      this.updateColumns();
    }
    this.table.debouncedUpdateLayout();
  };
  
  TableStore.prototype.setCurrentRowKey = function(key) {
    const states = this.states;
    const rowKey = states.rowKey;
    if (!rowKey) throw new Error('[Table] row-key should not be empty.');
    const data = states.data || [];
    const keysMap = getKeysMap(data, rowKey);
    const info = keysMap[key];
    states.currentRow = info ? info.row : null;
  };
  
  TableStore.prototype.updateCurrentRow = function() {
    const states = this.states;
    const table = this.table;
    const data = states.data || [];
    const oldCurrentRow = states.currentRow;
  
    if (data.indexOf(oldCurrentRow) === -1) {
      if (states.rowKey && oldCurrentRow) {
        let newCurrentRow = null;
        for (let i = 0; i < data.length; i++) {
          const item = data[i];
          if (item && item[states.rowKey] === oldCurrentRow[states.rowKey]) {
            newCurrentRow = item;
            break;
          }
        }
        if (newCurrentRow) {
          states.currentRow = newCurrentRow;
          return;
        }
      }
      states.currentRow = null;
  
      if (states.currentRow !== oldCurrentRow) {
        table.$emit('current-change', null, oldCurrentRow);
      }
    }
  };
  
  TableStore.prototype.commit = function(name, ...args) {
    const mutations = this.mutations;
    if (mutations[name]) {
      mutations[name].apply(this, [this.states].concat(args));
    } else {
      throw new Error(`Action not found: ${name}`);
    }
  };
  
  export default TableStore;
  