import { Directive, inject, OnDestroy } from '@angular/core';
import { PageDataService } from 'src/app/core/base/page.data.service';
import { Subject } from 'rxjs';
import { Logger } from 'src/app/service/logger.service';
import { QueryResult } from 'src/app/model/entities/query-result';
import { SpinnerService } from 'src/app/service/spinner.service';
import { FilterService } from 'src/app/service/filter.service';
import { Filter } from 'src/app/model/graphql/filter/filter.model';
import { Sort } from 'src/app/model/graphql/filter/sort.model';
import { takeUntil } from 'rxjs/operators';
import { PaginatorState } from 'primeng/paginator';
import { PageColumns } from 'src/app/core/base/page.columns';
import {
  TableColumn,
  TableRequireAnyPermissions,
} from 'src/app/modules/shared/components/table/table.model';

const logger = new Logger('PageBase');

@Directive()
export abstract class PageBase<
  T,
  S extends PageDataService<T> = PageDataService<T>,
  C extends PageColumns<T> = PageColumns<T>,
> implements OnDestroy
{
  protected spinner = inject(SpinnerService);
  protected filterService = inject(FilterService);

  protected dataService = inject(PageDataService) as S;
  protected columnsService = inject(PageColumns) as C;

  result: QueryResult<T> = {
    nodes: [],
    count: 0,
    totalCount: 0,
  };

  requiredPermissions!: TableRequireAnyPermissions;

  private _loading = true;
  get loading() {
    return this._loading;
  }

  set loading(value) {
    this._loading = value;
    if (value) this.spinner.show();
    else this.spinner.hide();
  }

  private _filter: Filter[] = [];
  get filter() {
    return this._filter;
  }

  set filter(value) {
    this._filter = value;
    this.filterService.filter$.next(value);
  }

  private _sort: Sort[] = [];
  get sort() {
    return this._sort;
  }

  set sort(value) {
    this._sort = value;
    this.filterService.sort$.next(value);
  }

  private _skip: number = 0;
  get skip() {
    return this._skip;
  }

  set skip(value) {
    this._skip = value;
    this.filterService.skip$.next(value);
  }

  private _take: number = 10;
  get take() {
    return this._take;
  }

  set take(value) {
    this._take = value;
    this.filterService.take$.next(value);
  }

  first: number = 0;

  get rowsPerPageOptions() {
    return this.filterService.rowsPerPageOptions;
  }

  columns: TableColumn<T>[] =
    'get' in this.columnsService ? this.columnsService.get() : [];

  protected unsubscribe$ = new Subject<void>();

  protected constructor(
    useQueryParams = false,
    permissions?: TableRequireAnyPermissions
  ) {
    this.subscribeToFilterService();
    this.filterService.reset({
      withQueryParams: useQueryParams,
      withHideDeleted: true,
    });
    this.requiredPermissions = permissions ?? {};

    this.dataService
      .onChange()
      .pipe(takeUntil(this.unsubscribe$))
      .subscribe(() => {
        this.load(true);
      });
  }

  ngOnDestroy(): void {
    logger.trace('Destroy component');
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }

  subscribeToFilterService(): void {
    this.filterService.onLoad.subscribe(() => {
      this.load();
    });

    this.filterService.filter$
      .pipe(takeUntil(this.unsubscribe$))
      .subscribe(filter => {
        this._filter = filter;
      });

    this.filterService.sort$
      .pipe(takeUntil(this.unsubscribe$))
      .subscribe(sort => {
        this._sort = sort;
      });

    this.filterService.skip$
      .pipe(takeUntil(this.unsubscribe$))
      .subscribe(skip => {
        this._skip = skip;
      });

    this.filterService.take$
      .pipe(takeUntil(this.unsubscribe$))
      .subscribe(take => {
        if (take && Object.prototype.hasOwnProperty.call(take, 'showAll')) {
          this._take = 0;
          return;
        }
        this._take = take as number;
      });
  }

  onPaginateEvent(event: PaginatorState): void {
    if (event.rows !== undefined && event.rows !== null) {
      this.filterService.take$.next(event.rows);
    }

    if (event.first !== undefined && event.first !== null)
      this.filterService.skip$.next(event.first);

    this.filterService.onLoad.emit();
  }

  abstract load(silent?: boolean): void;
}
