import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Store as NgxsStore } from '@ngxs/store';
import * as actions from './search.actions';
import * as selectors from './search.selectors';
import { switchMap, filter, map, first, tap } from 'rxjs/operators';
import { SharedSelectors } from 'apps/sales/src/app/core/store/selectors/shared.selectors';
import { combineLatest } from 'rxjs';
import { selectFiltersToFiltersDictionary, primaryFiltersToFiltersDictionary } from './mappers';
import { Observable } from 'rxjs';
import { isNullOrUndefined } from 'util';
import { SelectFilter } from 'apps/sales/src/app/modules/filter';
import { PrimaryFilterOption } from 'apps/sales/src/app/modules/shelf/defs';
import { SearchProcessState } from './defs';

@Injectable({ providedIn: 'root' })
export class SearchStateFacade {
  get process$() {
    return this.getProcessId$().pipe(
      switchMap((id) => this.getProcess$(id)),
      filter((process) => !isNullOrUndefined(process))
    );
  }

  get input$() {
    return this.getProcessId$().pipe(switchMap((id) => this.getInput$(id)));
  }

  get result$() {
    return this.getProcessId$().pipe(switchMap((id) => this.getResult$(id)));
  }

  get hits$() {
    return this.getProcessId$().pipe(switchMap((id) => this.getHits$(id)));
  }

  get fetching$() {
    return this.getProcessId$().pipe(switchMap((id) => this.getFetching$(id)));
  }

  get state$() {
    return this.getProcessId$().pipe(switchMap((id) => this.getState$(id)));
  }

  get hasError$() {
    return this.getProcessId$().pipe(switchMap((id) => this.getHasError$(id)));
  }

  get hasResults$() {
    return this.getProcessId$().pipe(switchMap((id) => this.getHasResult$(id)));
  }

  get pendingInput$() {
    return this.getProcessId$().pipe(switchMap((id) => this.getPendingInput$(id)));
  }

  get showNoResultError$() {
    return this.getProcessId$().pipe(
      switchMap((id) => combineLatest([this.getState$(id), this.getHasResult$(id)])),
      map(([state, hasResults]) => (!hasResults && state === SearchProcessState.FETCHED ? true : false))
    );
  }

  get primaryFilters$(): Observable<PrimaryFilterOption[]> {
    return this.process$.pipe(switchMap((process) => this.getPrimaryFilters$(process.id)));
  }

  get selectFilters$(): Observable<SelectFilter[]> {
    return this.process$.pipe(
      filter((process) => !isNullOrUndefined(process) && !isNullOrUndefined(process.filters)),
      switchMap((process) => this.getSelectedFilters$(process.id))
    );
  }

  get hasActiveFilters$(): Observable<boolean> {
    return this.selectedFilter$.pipe(
      map((selectedFilters) => Object.values(selectedFilters)),
      map((filterValues) => filterValues.reduce((acc, curr) => [...acc, curr], [])),
      map((flattenedFilters) => !!flattenedFilters.length)
    );
  }

  get selectedFilterChangedFromDefault$() {
    return this.selectFilters$.pipe(
      map((selectFilters) => {
        return selectFilters.some((f) => {
          if (f.options) {
            return f.options.some((o) => {
              return isNullOrUndefined(o.initial_selected_state) ? o.selected !== true : o.selected !== o.initial_selected_state;
            });
          }

          return false;
        });
      })
    );
  }

  get primaryFilterChangedFromDefault$() {
    return this.primaryFilters$.pipe(
      map((primaryFilters) => {
        return primaryFilters.some((pf) =>
          isNullOrUndefined(pf.initial_selected_state) ? pf.selected !== false : pf.selected !== pf.initial_selected_state
        );
      })
    );
  }

  get filtersChangedFromDefault$(): Observable<boolean> {
    return combineLatest([this.selectedFilterChangedFromDefault$, this.primaryFilterChangedFromDefault$]).pipe(
      map(([selectFiltersChanged, primaryFiltersChanged]) => {
        return selectFiltersChanged || primaryFiltersChanged;
      })
    );
  }

  get selectedFilter$(): Observable<{ [key: string]: string }> {
    return combineLatest([this.selectFilters$, this.primaryFilters$]).pipe(
      map(([selectFilters, primaryFilters]) => {
        const selectFilterDictionary = selectFiltersToFiltersDictionary(selectFilters);
        const primaryFiltersDictionary = primaryFiltersToFiltersDictionary(primaryFilters);

        const mergedFilters = {
          ...selectFilterDictionary,
          ...primaryFiltersDictionary,
        };

        const selectedFilters = {};
        for (const f in mergedFilters) {
          if (!!mergedFilters[f]) {
            selectedFilters[f] = mergedFilters[f];
          }
        }

        return selectedFilters;
      })
    );
  }

  constructor(private store: Store<any>, private ngxsStore: NgxsStore) {}

  getProcessId$() {
    return this.ngxsStore.select(SharedSelectors.getCurrentProcess).pipe(
      filter((process) => !!process && !!process.id),
      map((currentProcess) => currentProcess.id)
    );
  }

  getProcessId() {
    return this.getProcessId$().pipe(first()).toPromise();
  }

  getProcess$(id: number) {
    return this.store.select(selectors.selectProcess, id).pipe(
      tap((process) => {
        if (isNullOrUndefined(process)) {
          this.store.dispatch(actions.addSearchProcess({ id }));
        }
      }),
      filter((process) => !isNullOrUndefined(process))
    );
  }

  getInput$(id: number) {
    return this.store.select(selectors.selectInput, id);
  }

  getResult$(id: number) {
    return this.store.select(selectors.selectResult, id);
  }

  getHits$(id: number) {
    return this.store.select(selectors.selectHits, id);
  }

  getState$(id: number) {
    return this.store.select(selectors.selectState, id);
  }

  getFetching$(id: number) {
    return this.store.select(selectors.selectFetching, id);
  }

  getHasResult$(id: number) {
    return this.store.select(selectors.selectHasResults, id);
  }

  getHasError$(id: number) {
    return this.store.select(selectors.selectHasError, id);
  }

  getSelectedFilters$(id: number): Observable<SelectFilter[]> {
    return this.store.select(selectors.selectSelectedFilters, id);
  }

  getPrimaryFilters$(id: number): Observable<PrimaryFilterOption[]> {
    return this.store.select(selectors.selectPrimaryFilters, id);
  }

  getPendingInput$(id: number) {
    return this.store.select(selectors.selectPendingInput, id);
  }

  async setInput(input: string, id?: number) {
    if (id) {
      return this.store.dispatch(actions.setInput({ input, id }));
    }

    const processId = await this.getProcessId();

    this.store.dispatch(actions.setInput({ input, id: processId }));
  }

  async setPendingInput(input: string, id?: number) {
    if (id) {
      return this.store.dispatch(actions.setPendingInput({ input, id }));
    }

    const processId = await this.getProcessId();

    this.store.dispatch(actions.setPendingInput({ input, id: processId }));
  }

  async setSelectedFilters(filters: SelectFilter[], id?: number) {
    if (id) {
      return this.store.dispatch(actions.setSelectFilters({ filters, id }));
    }

    const processId = await this.getProcessId();

    this.store.dispatch(actions.setSelectFilters({ filters, id: processId }));
  }

  async setPrimaryFilters(primaryFilter: PrimaryFilterOption, id?: number) {
    let updatedFilters: PrimaryFilterOption[];
    let processId = id;

    if (!id) {
      processId = await this.getProcessId();
    }

    const currentPrimaryFilters = await this.getPrimaryFilters$(processId).pipe(first()).toPromise();

    const indexOfUpdatedFilter = currentPrimaryFilters.findIndex((f) => f.id === primaryFilter.id);

    updatedFilters = [
      ...currentPrimaryFilters.slice(0, indexOfUpdatedFilter),
      primaryFilter,
      ...currentPrimaryFilters.slice(indexOfUpdatedFilter + 1, currentPrimaryFilters.length + 1),
    ];

    return this.store.dispatch(actions.setPrimaryFilters({ filters: updatedFilters, id: processId }));
  }

  async resetPrimaryFilters(id?: number) {
    let processId = id;
    if (typeof processId !== 'number') {
      processId = await this.getProcessId();
    }

    this.store.dispatch(actions.resetPrimaryFilters({ id: processId }));
  }

  async resetSelectedFilters(id?: number) {
    let processId = id;
    if (typeof processId !== 'number') {
      processId = await this.getProcessId();
    }

    this.store.dispatch(actions.resetSelectFilters({ id: processId }));
  }

  async fetchResult(data: { id?: number; isNewSearch?: boolean }) {
    let processId = data.id;

    if (typeof processId !== 'number') {
      processId = await this.getProcessId();
    }

    if (data.isNewSearch) {
      return this.store.dispatch(actions.fetchResult({ id: processId, skip: 0 }));
    }

    this.store.dispatch(actions.fetchResult({ id: processId }));
  }

  async clearError(id?: number) {
    let processId = id;
    if (typeof processId !== 'number') {
      processId = await this.getProcessId();
    }
    this.store.dispatch(actions.clearError({ id: processId }));
  }

  async clearResult(id?: number) {
    let processId = id;
    if (typeof processId !== 'number') {
      processId = await this.getProcessId();
    }
    this.store.dispatch(actions.clearResults({ id: processId }));
  }

  async fetchFilters(id?: number) {
    let processId = id;

    if (typeof processId !== 'number') {
      processId = await this.getProcessId();
    }

    this.store.dispatch(actions.fetchFilters({ id: processId }));
  }

  async reloadResults(id?: number) {
    let processId = id;

    if (typeof processId !== 'number') {
      processId = await this.getProcessId();
    }

    this.store.dispatch(actions.reloadResults({ id: processId }));
  }
}
