import * as React from 'react';
import { IStoreState } from '../index.data';
import { Provider } from 'unistore/react';
import { Store } from 'unistore';
import SpreadSheet from '../SpreadSheet';
import { validate, initStore, getCommands, getQueries } from './helper';
import { Props } from './index.data';
import ErrorBoundary from '../common/ErrorBoundery';
import RuntimeContext, { SpreadSheetRuntime } from '../RuntimeContext';
import NamedFormulaManager from '../formula-manager/NamedFormulaManager';
import PositionedFormulaManager from '../formula-manager/PositionedFormulaManager';
import { dissoc } from 'valor-app-utils';
import { rowNoTemplates } from '../constants';
import { getNormalizedState } from '../helper';

/**
 * 以后对外提供controller都在这里实现
 */
class SpreadSheetProvider extends React.Component<Props> {
  store: Store<IStoreState>;
  // commandManager: CommandManager<IStoreState>;
  commands: ReturnType<typeof getCommands>;
  queries: ReturnType<typeof getQueries>;
  runtime: SpreadSheetRuntime;
  // 包含ij/rowNo的state
  getNormalizedState(shouldRecalculate: boolean = false) {
    shouldRecalculate && this.commands.recalculate();
    return getNormalizedState(this.store.getState(), this.props.rowNoTemplates);
  }

  constructor(props: Props) {
    super(props);
    validate(props);
    const store = initStore(props);
    const formulaConfig = props.formulaConfig || { userFormula: 'byPosition' };
    const formulaManager =
      props.formulaConfig && props.formulaConfig.userFormula === 'byName'
        ? new NamedFormulaManager(store, dissoc(formulaConfig, 'userFormula'))
        : new PositionedFormulaManager(store, dissoc(formulaConfig, 'userFormula'));
    this.runtime = new SpreadSheetRuntime(
      store,
      formulaManager,
      props.checkableConfig,
      props.rowNoTemplates || rowNoTemplates,
      false,
      !!this.props.disableAutoCalculate,
    );
    this.store = store;

    // 供外部调用
    this.commands = this.runtime.commands;
    this.queries = this.runtime.queries;
    this.getNormalizedState = this.getNormalizedState.bind(this);
    this.batchUpdate = this.batchUpdate.bind(this);

    this.notifySelectionChanged = this.notifySelectionChanged.bind(this);
  }

  /**
   * 非常重要!
   * 它确保了 外部组件不会刷新 SpreadSheetProvider, 从而避免了 错误地重新初始化store
   * 相关思考: https://github.com/g770728y/valor-blog/issues/11#issuecomment-572336775
   **/
  shouldComponentUpdate() {
    return false;
  }

  setDisableAutoCalculate(disableAutoCalculate: boolean) {
    if (disableAutoCalculate !== this.runtime.disableAutoCalculate) {
      this.runtime.disableAutoCalculate = disableAutoCalculate;
      if (!disableAutoCalculate) {
        this.commands.recalculate();
      }
    }
  }

  batchUpdate(updateFn: () => void) {
    try {
      if (this.runtime.disableAutoCalculate) {
        this.runtime.batchMode = true;
        updateFn();
        this.commands.recalculate();
      } else {
        updateFn();
      }
    } finally {
      this.runtime.batchMode = false;
    }
  }

  componentWillUnmount() {
    this.runtime.dispose();
    this.store.unsubscribe(this.notifySelectionChanged);
  }

  componentDidMount() {
    this.store.subscribe(this.notifySelectionChanged);
  }

  notifySelectionChanged(state: IStoreState) {
    const { selectionType, selectedRowRange, selectedColumnRange, selectedCellRange } = state;
    this.props.onSelectionChanged &&
      this.props.onSelectionChanged({
        selectionType,
        selectedRowRange,
        selectedColumnRange,
        selectedCellRange,
      });
  }

  render() {
    return (
      <Provider store={this.store}>
        <RuntimeContext.Provider value={this.runtime}>
          <ErrorBoundary>
            <SpreadSheet />
          </ErrorBoundary>
        </RuntimeContext.Provider>
      </Provider>
    );
  }
}

export default SpreadSheetProvider;
