import { Injectable, OnDestroy } from '@angular/core';
import { Observable, BehaviorSubject, Subject, combineLatest } from 'rxjs';
import { SelectFilter, Filter, SelectFilterOption } from '../../filter';
import { startWith, map, filter, takeUntil, take, distinctUntilChanged } from 'rxjs/operators';
import { SearchStateFacade } from '../../../store/customer';
import { flatten } from '../../../shared/utils';
import { isNullOrUndefined } from 'util';
import { cloneFilter } from '../../filter/utils';

@Injectable({ providedIn: 'root' })
export class ShelfFilterService implements OnDestroy {
  destroy$ = new Subject();

  private _pendingFilters$: BehaviorSubject<Filter[]> = new BehaviorSubject<Filter[]>(null);
  public filters$: Observable<SelectFilter[]>;
  public lastFilterGroupChanged$ = new BehaviorSubject<Filter>(undefined);
  public resetFilters$ = new Subject<void>();
  public overlayClosed$ = new Subject<void>();

  constructor(private searchStateFacade: SearchStateFacade) {
    this.initService();
  }

  get hasSelectedFilters$(): Observable<boolean> {
    return this.searchStateFacade.selectFilters$.pipe(map(this.computeHasSelectedFilters));
  }

  get pendingFilter$(): Observable<Filter[]> {
    return this._pendingFilters$.asObservable();
  }

  set pendingFilters(filters: Filter[]) {
    this._pendingFilters$.next(filters);
  }

  get hasSelectedPendingFilters$(): Observable<boolean> {
    return this.pendingFilter$.pipe(map(this.computeHasSelectedFilters));
  }

  get filtersChangedFromDefault$(): Observable<boolean> {
    return this.pendingFilter$.pipe(
      filter((pendingFilters) => !isNullOrUndefined(pendingFilters)),
      map((pendingFilters) => {
        return pendingFilters.some(
          (f) =>
            f.options &&
            f.options.some((o) =>
              isNullOrUndefined(o.initial_selected_state) ? o.selected !== false : o.selected !== o.initial_selected_state
            )
        );
      })
    );
  }

  get pendingFiltersShouldUpdate$() {
    return combineLatest([
      this.filters$.pipe(
        filter((filters) => !isNullOrUndefined(filters)),
        distinctUntilChanged((a, b) => this.isJsonEqual(a, b))
      ),
      this.resetFilters$.pipe(startWith()),
    ]);
  }

  initService() {
    this.filters$ = this.searchStateFacade.selectFilters$;
    this.initPendingFilters();
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  initPendingFilters() {
    this.setInitialPendingFilters();

    this.pendingFiltersShouldUpdate$
      .pipe(
        takeUntil(this.destroy$),
        map(([filters]) => filters)
      )
      .subscribe(this.updatePendingFilters);
  }

  updateFilters(updatedFilters: SelectFilter[]) {
    this.searchStateFacade.setSelectedFilters(updatedFilters);
  }

  resetPrimaryFilters(): void {
    this.searchStateFacade.resetPrimaryFilters();
  }

  resetSelectedFilters() {
    this.searchStateFacade.resetSelectedFilters();
  }

  public overlayClosed() {
    this.resetFilters$.next();
    this.overlayClosed$.next();
  }

  private setInitialPendingFilters() {
    this.filters$
      .pipe(
        filter((filters) => !isNullOrUndefined(filters)),
        take(1),
        takeUntil(this.destroy$)
      )
      .subscribe(this.updatePendingFilters);
  }

  private updatePendingFilters = (filters: SelectFilter[]) => {
    this.pendingFilters = filters.map(cloneFilter);
    this.setInitialFilterGroupLastChanged(filters);
  };

  private setInitialFilterGroupLastChanged(filters: SelectFilter[]) {
    if (!this.lastFilterGroupChanged$.value) {
      this.lastFilterGroupChanged$.next(filters[0]);
    }
  }

  private computeHasSelectedFilters(filters: SelectFilter[]): boolean {
    const flattenedFilters = (flatten(filters, 'options') as unknown) as SelectFilterOption[];

    if (flattenedFilters.some((f) => !!f.selected)) {
      return true;
    }
    return false;
  }

  private isJsonEqual(oldValue: any, newValue: any): boolean {
    return JSON.stringify(oldValue) === JSON.stringify(newValue);
  }
}
