import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { debounceTime, take, takeUntil } from 'rxjs/operators';
import { DataSource, CollectionViewer } from '@angular/cdk/collections';
import { Store } from '@ngxs/store';
import { ItemDTO } from '@swagger/cat';
import { Product } from '../../../../core/models/product.model';
import { ProductMapping } from '../../../../core/mappings/product.mapping';
import { ProductService } from '../../../../core/services/product.service';
import { SetProducts, SetProductCachedResults } from '../../../../core/store/actions/product.actions';
import { AppState } from 'apps/sales/src/app/core/store/state/app.state';
import { Search } from 'apps/sales/src/app/core/models/search.model';

export class SearchDataSource extends DataSource<Product | undefined> {
  static readonly pageSize = 20;
  public cachedData = Array.from<Product>({ length: 0 });
  private cachedItemsDTO = Array.from<ItemDTO>({ length: 0 });
  private fetchedPages = new Set<number>();
  private productMapping = new ProductMapping();
  private dataStream = new BehaviorSubject<(Product | undefined)[]>(this.cachedData);
  dataStreamDTO = new BehaviorSubject<(ItemDTO | undefined)[]>(this.cachedItemsDTO);
  loading = true;
  results = false;
  destroy$ = new Subject();

  static getPageForIndex(index: number): number {
    return Math.floor(index / SearchDataSource.pageSize);
  }

  constructor(
    private searchService: ProductService,
    private search: Search,
    private store: Store,
    private filters: any[],
    private areFiltersNegative: boolean,
    private skipCache: boolean
  ) {
    super();
  }
  connect(collectionViewer: CollectionViewer): Observable<(Product | undefined)[]> {
    collectionViewer.viewChange.pipe(takeUntil(this.destroy$)).subscribe((range) => {
      const startPage = SearchDataSource.getPageForIndex(range.start);
      const endPage = SearchDataSource.getPageForIndex(range.end - 1);
      for (let i = startPage; i <= endPage; i++) {
        this.fetchPage(i);
      }
    });

    this.dataStreamDTO.pipe(takeUntil(this.destroy$), debounceTime(1000)).subscribe((i) => {
      const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
      this.store.dispatch(new SetProducts([...i], this.search, currentProcessId));
    });

    this.fetchPage(0);
    return this.dataStream;
  }

  disconnect(): void {
    this.dataStream.complete();
    this.dataStreamDTO.complete();
    this.destroy$.next();
  }

  getProductsUpTo(page: number) {
    for (let i = 0; i < page; i++) {
      this.fetchPage(i);
    }
  }

  private fetchPage(page: number) {
    if (page === 0) {
      this.results = false;
    }
    if (this.fetchedPages.has(page)) {
      return;
    }
    this.fetchedPages.add(page);
    this.loading = true;

    const productSearch$ =
      page === 0 && !this.skipCache
        ? this.searchService.getCachedProductResults()
        : this.searchService.searchItemsWithPagination(
            this.search,
            page * SearchDataSource.pageSize,
            SearchDataSource.pageSize,
            this.filters,
            this.areFiltersNegative
          );

    productSearch$.pipe(take(1)).subscribe((data) => {
      if (page === 0 && this.skipCache) {
        this.store.dispatch(new SetProductCachedResults(data));
      }
      if (!data || !data.result) {
        this.loading = false;
        this.results = true;
        return;
      }
      this.loading = false;
      this.results = true;

      if (page === 0) {
        this.cachedData = Array.from<Product>({ length: data.hits });
        this.cachedItemsDTO = Array.from<ItemDTO>({ length: data.hits });
      }
      this.cachedItemsDTO.splice(page * SearchDataSource.pageSize, SearchDataSource.pageSize, ...data.result);
      this.cachedData.splice(
        page * SearchDataSource.pageSize,
        SearchDataSource.pageSize,
        ...data.result.map((item, i) => this.productMapping.fromItemDTO(item))
      );
      this.dataStream.next(this.cachedData);
      this.dataStreamDTO.next(this.cachedItemsDTO);
      if (page === 0) {
        // dispatch immediately on first page load
        const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
        this.store.dispatch(new SetProducts([...this.cachedItemsDTO], this.search, currentProcessId, true));
      }
    });
  }
}
