import { Injectable } from '@angular/core';
import { DomainCatalogService } from '@domain/catalog';
import { Observable, Subject } from 'rxjs';
import { debounceTime, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';

import { UiFilter } from '@ui/filter';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ItemDTO } from '@swagger/cat';

export interface ArticleSearchState {
  processId: number;
  filter: UiFilter;
  searchState: '' | 'fetching' | 'empty' | 'error';
  items: ItemDTO[];
  hits: number;
  selectedItemIds: number[];
}

@Injectable()
export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
  get processId() {
    return this.get((s) => s.processId);
  }

  filter$ = this.select((s) => s.filter);

  get filter() {
    return this.get((s) => s.filter);
  }

  items$ = this.select((s) => s.items);

  get items() {
    return this.get((s) => s.items);
  }

  get friendlyName() {
    return this.filter.input
      ?.find((f) => f.group === 'main')
      .input?.find((f) => f)
      .toStringValue();
  }

  fetching$ = this.select((s) => s.searchState === 'fetching');

  searchStarted = new Subject<{ clear?: boolean; reload?: boolean }>();

  searchCompleted = new Subject<ArticleSearchState>();

  searchboxHint$ = this.select((s) => (s.searchState === 'empty' ? 'Keine Suchergebnisse' : undefined));

  selectedItemIds$ = this.select((s) => s.selectedItemIds);

  get selectedItemIds() {
    return this.get((s) => s.selectedItemIds);
  }

  hits$ = this.select((s) => s.hits);

  get hits() {
    return this.get((s) => s.hits);
  }

  constructor(private catalog: DomainCatalogService) {
    super({
      filter: undefined,
      hits: 0,
      items: [],
      processId: 0,
      searchState: '',
      selectedItemIds: [],
    });
    this.setDefaultFilter();
  }

  setProcess(processId: number) {
    this.patchState({ processId });
  }

  setItems(items: ItemDTO[]) {
    this.patchState({ items });
  }

  setHits(hits: number) {
    this.patchState({ hits });
  }

  async setDefaultFilter(defaultQueryParams?: Record<string, string>) {
    const filter = await this.catalog
      .getSettings()
      .pipe(map((settings) => UiFilter.create(settings)))
      .toPromise();

    if (!!defaultQueryParams) {
      filter?.fromQueryParams(defaultQueryParams);
    }

    this.setFilter(filter);
  }

  setFilter(filter: UiFilter) {
    this.patchState({ filter });
  }

  setSelected({ selected, itemId }: { selected: boolean; itemId: number }) {
    const included = this.selectedItemIds.includes(itemId);

    if (!included && selected) {
      this.patchState({
        selectedItemIds: [...this.selectedItemIds, itemId],
      });
    } else if (included && !selected) {
      this.patchState({
        selectedItemIds: this.selectedItemIds.filter((id) => id !== itemId),
      });
    }
  }

  search = this.effect((options$: Observable<{ clear?: boolean }>) =>
    options$.pipe(
      tap((options) => {
        this.patchState({
          searchState: 'fetching',
          items: options.clear ? [] : this.items,
        });
      }),
      debounceTime(250),
      withLatestFrom(this.filter$, this.items$),
      switchMap(([options, filter, items]) =>
        this.catalog
          .search({
            queryToken: {
              ...filter.getQueryToken(),
              skip: items.length,
              take: 25,
              friendlyName: this.friendlyName,
            },
          })
          .pipe(
            tapResponse(
              (res) => {
                const searchState = res.hits ? '' : 'empty';
                if (options.clear) {
                  this.patchState({
                    hits: res.hits,
                    items: res.result,
                    searchState,
                  });
                } else {
                  const items = this.get((s) => s.items);
                  this.patchState({
                    hits: res.hits,
                    items: [...items, ...res.result],
                    searchState,
                  });
                }
                this.searchCompleted.next(this.get());
              },
              (err) => {
                this.patchState({ hits: 0, searchState: 'error', items: [] });
                this.searchCompleted.next(this.get());
              }
            )
          )
      )
    )
  );
}
