import { DataSource, CollectionViewer } from '@angular/cdk/collections';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { Store } from '@ngxs/store';
import { take, takeUntil, debounceTime } from 'rxjs/operators';
import { OrderItemListItemDTO } from '@swagger/oms';
import { GoodsInService } from 'apps/sales/src/app/core/services/goods-in.service';
import { GoodsInSearch } from 'apps/sales/src/app/core/models/GoodsInSearch.model';
import { SetGoodsInOrders } from 'apps/sales/src/app/core/store/actions/goods-in.actions';

export class GoodsInSearchResultsDataSource extends DataSource<OrderItemListItemDTO | undefined> {
  private pageSize = 10;
  private fetchedPages = new Set<number>();
  private cachedData = Array.from<OrderItemListItemDTO>({ length: 10 });
  private dataStream = new BehaviorSubject<(OrderItemListItemDTO | undefined)[]>(this.cachedData);
  destroy$ = new Subject();
  public loading = true;
  public results = false;
  public allHits = 0;
  public firstLoad = true;

  constructor(private search: GoodsInSearch, private store: Store, private goodsInService: GoodsInService, private useCache: Boolean) {
    super();
  }
  connect(collectionViewer: CollectionViewer): Observable<(OrderItemListItemDTO | undefined)[]> {
    collectionViewer.viewChange.pipe(takeUntil(this.destroy$)).subscribe((range) => {
      const startPage = this.getPageForIndex(range.start);
      const endPage = this.getPageForIndex(range.end);
      for (let i = startPage; i <= endPage; i++) {
        if (this.firstLoad && i === 0) {
          return;
        }
        this.fetchPage(i);
      }
    });

    this.dataStream.pipe(takeUntil(this.destroy$), debounceTime(1000)).subscribe((i) => {
      this.store.dispatch(new SetGoodsInOrders([...i]));
    });

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

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

  public getPageForIndex(index: number): number {
    return Math.floor(index / this.pageSize);
  }

  public getPrevItem(index: number) {
    if (index < 1) {
      return null;
    }
    if (!this.cachedData) {
      return null;
    }
    return this.cachedData[index - 1];
  }

  public getNextItem(index: number) {
    if (!index) {
      return null;
    }
    if (!this.cachedData) {
      return null;
    }
    return this.cachedData[index + 1];
  }

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

    const shelfSearch$ =
      page === 0 && this.useCache
        ? this.goodsInService.searchCachedOrders()
        : this.goodsInService.search(this.search.input, page * this.pageSize, this.pageSize);

    shelfSearch$.pipe(take(1)).subscribe(({ results, hits }: { results: OrderItemListItemDTO[]; hits: number }) => {
      this.loading = false;
      this.results = true;

      if (page === 0) {
        this.cachedData = Array.from<OrderItemListItemDTO>({
          length: hits,
        });
        this.allHits = hits;
      }

      this.cachedData.splice(page * this.pageSize, this.pageSize, ...results);
      this.dataStream.next(this.cachedData);

      if (page === 0) {
        // dispatch immediately on first page load
        this.firstLoad = false;
        this.store.dispatch(new SetGoodsInOrders(this.cachedData));
      }
    });
  }
}
