import { Component, OnInit, Input, OnDestroy, ViewChild, Output, EventEmitter, ElementRef, ChangeDetectorRef } from '@angular/core';
import { RemissionListDataSource } from './remission-list.datasource';
import { RemissionService, RemissionProcess, RemissionProduct, RemissionFilter } from '@isa/remission';
import { RemissionHelperService } from '../../services/remission-helper.service';
import { Subject, of, Subscription, Observable } from 'rxjs';
import { takeUntil, filter, take, catchError, share, debounceTime, first, map, switchMap, tap } from 'rxjs/operators';
import { isNullOrUndefined } from 'util';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { Select, Store } from '@ngxs/store';
import { RemissionSelectors } from 'apps/sales/src/app/core/store/selectors/remission.selectors';
import { SetRemissionIsLoading } from 'apps/sales/src/app/core/store/actions/remission.actions';

@Component({
  selector: 'app-remission-list',
  templateUrl: './remission-list.component.html',
  styleUrls: ['./remission-list.component.scss'],
})
export class RemissionListComponent implements OnInit, OnDestroy {
  @ViewChild('scroller') scroller: CdkVirtualScrollViewport;
  @ViewChild('remissionListContainer')
  remissionListContainer: ElementRef;
  @Input() started = false;
  @Input() hits: number;
  @Input() remissionProcess: RemissionProcess;
  @Input() refetchProducts$: Subject<void> = new Subject<void>();
  @Input() fetchLastProducts$: Subject<void> = new Subject<void>();
  @Input() isLoading$: Observable<boolean>;

  @Output() fullListLoaded = new EventEmitter();
  @Output() scroll = new EventEmitter<void>();
  @Output() updateShippingDocument = new EventEmitter<void>();
  @Select(RemissionSelectors.getRemissionProcess) remissionProcess$: Observable<RemissionProcess>;
  @Select(RemissionSelectors.getRemissionActiveFilters)
  selectedFilters$: Observable<{ [key: string]: [] }>;

  destroy$ = new Subject();
  subscribedToProducts = false;
  topScrolledOffset = 0;
  stopViewportScroll = false;
  prevRangeStart: number;
  prevRangeEnd: number;
  showScrollOverlay = false;
  stopListScrolling = true;
  products: RemissionProduct[] = [];
  containerHeight = 0;

  throttle = 0;
  scrollDistance = 1;
  scrollUpDistance = 2;
  page = 0;
  pageSize = 10;
  totalHits = 0;
  isItRefetch = false;
  isFullLoad = false;
  isFullListLoaded = false;
  isLoading = false;

  private productSubscription: Subscription;

  constructor(
    private remissionService: RemissionService,
    private remissionHelper: RemissionHelperService,
    private store: Store,
    private cdr: ChangeDetectorRef
  ) {}

  ngOnInit() {
    this.fetchProductPaged(this.page)
      .pipe(take(1))
      .subscribe((data) => {
        this.subscribeToRemissionProducts();
      });
    this.subscribeToRefetchProducts();
    this.subscribeToFetchLastProducts();
    this.subscribeToResetCurrentPage();
    this.subscribeToRefetchAllProducts();
  }

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

  subscribeToResetCurrentPage() {
    this.selectedFilters$.pipe(takeUntil(this.destroy$)).subscribe(() => this.resetPagesFetched());
  }

  private resetPagesFetched() {
    this.page = 0;
    this.isFullListLoaded = false;
    this.isFullLoad = false;
  }

  subscribeToRefetchAllProducts() {
    this.remissionHelper.refetchProducts$.pipe(takeUntil(this.destroy$)).subscribe(() => {
      this.refetchLoadedProducts(10);
    });
  }

  subscribeToFetchLastProducts() {
    this.fetchLastProducts$.pipe(takeUntil(this.destroy$)).subscribe((_) => {
      // Increase number of takes to show all residual products (currently: 10)
      const takeParam = this.pageSize;
      const skipParam = Math.max(0, this.totalHits - takeParam);

      if (this.products.length > this.pageSize + (this.totalHits % this.pageSize)) {
        this.products = this.cleanResidualProducts(this.products);
      }

      this.fetchProductsBySkipAndTake(skipParam, takeParam).subscribe();
    });
  }

  cleanResidualProducts(products: RemissionProduct[]) {
    return products.filter((product) => !product.isResidual);
  }

  private filterUniqueProducts(products: RemissionProduct[]) {
    return products.filter((product, i, allProducts) => allProducts.findIndex((t) => t.id === product.id) === i);
  }

  subscribeToRefetchProducts() {
    this.refetchProducts$.pipe(debounceTime(10), takeUntil(this.destroy$)).subscribe((_) => this.fetchNewProducts());
  }

  onScrollDown() {
    this.scroll.emit();

    if (this.isFullListLoaded || this.isFullLoad) {
      return;
    }
    this.fetchNewProducts();
  }

  private fetchNewProducts() {
    this.page += 1;

    this.fetchProductPaged(this.page).subscribe();
  }

  onUp() {
    this.scroll.emit();
  }

  fetchProductsBySkipAndTake(skip: number, takeParam: number) {
    return this.handleNewProductsFetch(
      this.remissionService.updateRemissionFilter({
        remissionProcessId: this.remissionProcess.id,
        changes: { skip, take: takeParam },
      })
    );
  }

  fetchProductPaged(page: number) {
    return this.handleNewProductsFetch(
      this.remissionService.updateRemissionFilter({
        remissionProcessId: this.remissionProcess.id,
        changes: { skip: page * this.pageSize, take: this.pageSize },
      })
    );
  }

  handleNewProductsFetch(remissionFilter$: Observable<RemissionFilter>) {
    return remissionFilter$.pipe(
      filter((data) => !isNullOrUndefined(data)),
      take(1),
      catchError((error) => {
        console.error(error);
        return of(undefined);
      })
    );
  }

  refetchLoadedProducts(lengthChange: number) {
    this.isItRefetch = true;
    return this.remissionService
      .updateRemissionFilter({
        remissionProcessId: this.remissionProcess.id,
        changes: { skip: 0, take: this.products.length + lengthChange },
      })
      .pipe(
        filter((data) => !isNullOrUndefined(data)),
        take(1),
        catchError((error) => {
          console.error(error);
          return of(undefined);
        })
      );
  }

  subscribeToRemissionProducts() {
    if (this.productSubscription) {
      this.productSubscription.unsubscribe();
    }
    this.productSubscription = this.remissionProcess$
      .pipe(
        filter(
          (process) =>
            !isNullOrUndefined(process) &&
            !isNullOrUndefined(process.id) &&
            !isNullOrUndefined(process.filter.source) &&
            !isNullOrUndefined(process.filter.target)
        ),
        take(1),
        map((process) => process.id),
        tap((_) => this.store.dispatch(new SetRemissionIsLoading(true))),
        switchMap((remissionProcessId) =>
          this.remissionService.getRemissionProducts({ remissionProcessId }).pipe(
            filter((data) => !isNullOrUndefined(data)),
            debounceTime(250),
            catchError((_) => {
              this.store.dispatch(new SetRemissionIsLoading(false));
              return [
                {
                  hits: 0,
                  completed: true,
                },
              ];
            })
          )
        ),
        takeUntil(this.destroy$)
      )
      .subscribe((result: { skip?: number; take?: number; hits?: number; items: RemissionProduct[]; completed: boolean }) => {
        this.totalHits = result.hits;

        this.updateFullListLoaded({
          totalHits: this.totalHits,
          numberOfProductsFetched: this.products.length,
        });

        this.isLoading = false;

        if ((this.page === 0 || result.skip === 0) && result.completed && result.hits > 0 && result.items) {
          if (!result.items.length) {
            this.products = [...this.products];
          } else {
            this.products = [...result.items];
          }
        } else {
          if ((this.page === 0 || result.skip === 0) && !this.isFullLoad) {
            this.products = [];
          }
          this.remissionHelper.setRemissionListHits(result.hits || -1);
          if (result.items && result.items.length > 0) {
            if (!this.isItRefetch && !this.isFullLoad) {
              this.products = this.filterUniqueProducts([...this.products, ...result.items]);
            } else if (this.isItRefetch && !this.isFullLoad) {
              this.products.splice(result.skip, result.items.length, ...result.items);
              this.isItRefetch = false;
            } else if (this.isFullLoad) {
              this.products = this.filterUniqueProducts([...this.products, ...result.items]);
              this.isFullLoad = false;
              this.isFullListLoaded = true;
              this.cdr.detectChanges();
              this.fullListLoaded.emit();
            }
          }
        }

        this.store.dispatch(new SetRemissionIsLoading(false));
        this.cdr.detectChanges();
      });
  }

  public loadFullList() {
    let obs$;
    if (this.isFullListLoaded) {
      obs$ = of();
      this.fullListLoaded.emit();
    } else {
      this.isLoading = true;
      this.isFullLoad = true;

      obs$ = this.remissionService
        .updateRemissionFilter({
          remissionProcessId: this.remissionProcess.id,
          changes: { skip: 0, take: this.totalHits },
        })
        .pipe(
          filter((data) => !isNullOrUndefined(data)),
          take(1),
          share(),
          catchError((error) => {
            console.error(error);
            return of(undefined);
          })
        );
      obs$.subscribe();
    }

    return obs$;
  }

  public setFullListLoadedToFalse() {
    this.isFullListLoaded = false;
  }

  private updateFullListLoaded(params: { totalHits: number; numberOfProductsFetched: number }) {
    if (this.page === 0) {
      return;
    }
    this.page = Math.floor(params.numberOfProductsFetched / this.pageSize);

    const allProductsFetched = params.numberOfProductsFetched >= this.totalHits;
    allProductsFetched ? (this.isFullListLoaded = true) : (this.isFullListLoaded = false);
  }

  deleteProduct(product: RemissionProduct) {
    this.remissionProcess$
      .pipe(
        map((process) => process.id),
        first(),
        switchMap((remissionProcessId) =>
          this.remissionService.deleteProductFromRemissionList({
            remissionProcessId,
            remissionProductId: product.id,
          })
        )
      )
      .subscribe((result) => {
        if (result.result) {
          this.products = this.products.filter((existingProduct) => existingProduct.id !== product.id);
          this.hits = this.products.length;
        }
      });
  }
}
