import { NestedTableState } from './NestedTableState';
import { action, computed, makeObservable } from 'mobx';
import { MultiCollectionSupport, TaskState } from '@wix/bex-core';
import { TableVirtualState } from '../TableVirtualState';
import {
  NestedTableFlatKeyedItem,
  NestedTableFlatModeSectionState,
} from './NestedTableFlatModeSectionState';
import { addEventListener } from '@wix/bex-core/util';
import { NestedTableFlatModeStateBIReporter } from './NestedTableFlatModeStateBIReporter';

export interface NestedTableFlatStateBaseParams<C extends string> {
  wrapper: NestedTableState<C>;
}

export class NestedTableFlatModeState<C extends string = string> {
  readonly wrapper;
  readonly virtual;

  readonly initTask = new TaskState();
  readonly refreshTask = new TaskState();

  readonly sections;
  readonly multi;
  readonly bi: NestedTableFlatModeStateBIReporter<C>;

  readonly initSearchState;

  constructor(params: NestedTableFlatStateBaseParams<C>) {
    const { wrapper } = params;
    this.wrapper = wrapper;

    const { levels, query } = wrapper;

    this.bi = new NestedTableFlatModeStateBIReporter(this);

    this.sections = Array.from(
      levels
        .map((levelDescriptor) => {
          return new NestedTableFlatModeSectionState({
            levelDescriptor,
            query,
            flatModeState: this,
          });
        })
        .reduce((map, section) => {
          const query = section.collection.getCurrentQuery();
          const existingSection = map.get(query);
          if (existingSection) {
            existingSection.levelDescriptors.push(section.levelDescriptor);
          } else {
            map.set(query, section);
            section.levelDescriptors.push(section.levelDescriptor);
          }
          return map;
        }, new Map<unknown, NestedTableFlatModeSectionState<C, any, any>>())
        .values(),
    );

    this.multi = new MultiCollectionSupport<{}>({
      collections: this.sections.map(({ collection }) => collection),
    });

    this.virtual = new TableVirtualState({
      table: this,
    });

    this.initSearchState = this._getSearchInitState();

    makeObservable(this, {
      keyedItems: computed,
    });
  }

  get toolbar() {
    return this.wrapper.toolbar;
  }

  get tableState() {
    return this.wrapper.tableState;
  }

  get keyedItems(): NestedTableFlatKeyedItem<C, any, any>[] {
    return this.sections.reduce((acc, section) => {
      const {
        collection: {
          result: { keyedItems },
        },
      } = section;

      return [
        ...acc,
        ...keyedItems.map((keyedItem) => ({
          ...keyedItem,
          item: {
            state: section,
            originalKeyedItem: keyedItem,
          },
        })),
      ];
    }, [] as NestedTableFlatKeyedItem<C, any, any>[]);
  }

  get showEmptyState() {
    return this.multi.showEmptyState;
  }

  get hasAvailableItems() {
    return this.multi.hasAvailableItems;
  }

  get resultOriginQuerySearch() {
    return this.multi.resultOriginQuerySearch;
  }

  get hasNonPersistentActiveFilters() {
    return this.multi.hasNonPersistentActiveFilters;
  }

  get showErrorState() {
    const { initTask, refreshTask, multi } = this;
    return (
      initTask.status.isError ||
      refreshTask.status.isError ||
      multi.showErrorState
    );
  }

  get showLoadingState() {
    return (
      this.initTask.status.isIdle ||
      this.initTask.status.isLoading ||
      this.refreshTask.status.isLoading ||
      this.multi.showLoadingState
    );
  }

  retryErrorState = action(() => {
    const { initTask, refreshTask } = this;

    if (initTask.status.isError) {
      initTask.runOnce();
    } else if (refreshTask.status.isError) {
      refreshTask.runOnce();
    } else {
      this.multi.retryErrorState();
    }
  });

  get errorStatus() {
    const { initTask, refreshTask, multi } = this;
    return initTask.errorStatus || refreshTask.errorStatus || multi.errorStatus;
  }

  get query() {
    return this.wrapper.query;
  }

  get levels() {
    return this.wrapper.levels;
  }

  async _populateCacheAndSearch() {
    const { wrapper, bi } = this;

    const reportSearchResults = bi._createReportSearchResults();
    const fetchAll = wrapper._attemptFetchAllAndPopulateCache();
    fetchAll.init();
    await fetchAll.initTask.status.promise;
    await this.toolbar.collection._runSearch(true);
    await Promise.all(
      this.sections.map(({ collection }) => collection._runSearch(true)),
    );
    reportSearchResults();
  }

  _runSearch = action(async () => {
    const { query, refreshTask } = this;
    if (query.search.isEmpty) {
      return;
    }

    refreshTask.reset();

    refreshTask.runOnce(() => this._populateCacheAndSearch());
  });

  _getSearchInitState() {
    const initTask = new TaskState();

    const disposers = [
      ...this.sections.map(({ collection }) =>
        addEventListener(
          collection.hooks,
          'beforeInitialFetch',
          () => initTask.status.promise,
        ),
      ),
    ];

    return {
      initTask,
      init: () => {
        initTask.runOnce(async () => {
          await this._populateCacheAndSearch();
        });

        return () => {
          disposers.forEach((d) => d());
        };
      },
    };
  }

  init({ isUsingCache = false } = {}) {
    const { initTask, wrapper, multi, sections, initSearchState } = this;

    const { query } = wrapper;

    const disposers = [
      wrapper.init(),
      initSearchState.init(),
      multi.init(),
      addEventListener(query.search.events, 'change', this._runSearch),
      ...sections.map((section) => section.init()),
    ];

    if (!isUsingCache && initTask.status.isSuccess) {
      this._runSearch();
    }

    initTask.runOnce(async () => {
      wrapper.initTask.runOnce();
      initSearchState.initTask.runOnce();
      multi.initTask.runOnce();

      await wrapper.initTask.status.promise;
      await initSearchState.initTask.status.promise;
      await multi.initTask.status.promise;
    });

    return () => {
      disposers.forEach((d) => d());
    };
  }

  _refreshAll = action(() => {
    this.sections.forEach(({ collection }) => {
      collection.clearResultAndMoveToStart();
    });
  });
}
