import { Resource } from '@ngxp/rest';
import { isEmpty } from 'lodash-es';
import { BehaviorSubject, Observable, first, map, tap, withLatestFrom } from 'rxjs';
import { EMPTY_STRING, isNotEmpty } from '../tech.util';
import { LinkRelationName, ListItemResource, SearchResourceServiceConfig } from './resource.model';
import { ResourceRepository } from './resource.repository';
import {
  ListResource,
  StateResource,
  createEmptyStateResource,
  createStateResource,
} from './resource.util';

/**
 * B = Type of baseresource
 * T = Type of listresource
 * I = Type of items in listresource
 */
export class ResourceSearchService<
  B extends Resource,
  T extends ListResource,
  I extends ListItemResource,
> {
  readonly listResource: BehaviorSubject<StateResource<T>> = new BehaviorSubject(
    createEmptyStateResource(),
  );
  readonly searchBy: BehaviorSubject<string> = new BehaviorSubject<string>(EMPTY_STRING);
  readonly selectedResource: BehaviorSubject<I> = new BehaviorSubject<I>(null);

  constructor(
    private config: SearchResourceServiceConfig<B>,
    private repository: ResourceRepository,
  ) {}

  public getResultList(): Observable<StateResource<T>> {
    return this.selectListResource().pipe(
      withLatestFrom(this.selectSearchBy(), this.config.baseResource),
      tap(([listResource, searchBy, baseResource]) => {
        this.handleChanges(listResource, searchBy, baseResource);
      }),
      map(([listResource]) => listResource),
    );
  }

  private selectListResource(): Observable<StateResource<T>> {
    return this.listResource.asObservable();
  }

  private selectSearchBy(): Observable<string> {
    return this.searchBy.asObservable();
  }

  handleChanges(listResource: StateResource<T>, searchBy: string, baseResource: B): void {
    if (listResource.loading) this.doSearch(searchBy, baseResource);
  }

  doSearch(searchBy: string, baseResource: B): void {
    if (isNotEmpty(searchBy)) {
      this.dispatchSearch(baseResource, this.config.searchLinkRel, searchBy);
    }
    if (isEmpty(searchBy)) {
      this.dispatchClearSearchList();
    }
  }

  dispatchSearch(baseResource: B, linkRel: LinkRelationName, searchBy: string): void {
    this.repository
      .search<T>(baseResource, linkRel, searchBy)
      .pipe(first())
      .subscribe((result: T) => this.dispatchSearchSuccess(result));
  }

  private dispatchSearchSuccess(result: T): void {
    this.listResource.next(createStateResource(result));
  }

  public clearResultList(): void {
    this.dispatchClearSearchList();
  }

  private dispatchClearSearchList(): void {
    this.listResource.next(createEmptyStateResource());
  }

  public search(searchBy: string): void {
    this.dispatchInitSearch(searchBy);
  }

  private dispatchInitSearch(searchBy: string): void {
    this.searchBy.next(searchBy);
    this.listResource.next({ ...this.listResource.value, loading: true });
  }

  public getSelectedResult(): Observable<I> {
    return this.selectedResource.asObservable();
  }

  public selectResult(selected: I): void {
    this.selectedResource.next(selected);
  }

  public clearSelectedResult(): void {
    this.selectedResource.next(null);
  }
}