import {
  CdkVirtualScrollViewport,
  ScrollingModule,
} from '@angular/cdk/scrolling';
import { AsyncPipe } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  Input,
  ViewChild,
  inject,
} from '@angular/core';
import { DialogService } from '@ngneat/dialog';
import {
  BehaviorSubject,
  Observable,
  combineLatest,
  debounceTime,
  map,
  mergeMap,
  scan,
  tap,
} from 'rxjs';
import {
  Ticket,
  TicketFilterArgs,
  TicketValidationState,
  Tour,
} from '../../../core/data-access/graphql/generated/generated';
import { TicketItemComponent } from '../ticket-item/ticket-item.component';
import { TicketViewComponent } from '../ticket-view/ticket-view.component';
import { TicketsService } from '../tickets.service';
import { TicketLaneHeaderComponent } from './ticket-lane-header.component';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'dks-ticket-lane',
  templateUrl: './ticket-lane.component.html',
  host: {
    class: 'rounded-sm h-full w-full grid',
  },
  styles: [
    `
      :host {
        box-shadow: var(--mat-sys-level1);
        background-color: var(--mat-sys-surface-container-low);
        grid-template-rows: auto 1fr auto;
      }
    `,
  ],
  imports: [
    TicketLaneHeaderComponent,
    TicketItemComponent,
    AsyncPipe,
    ScrollingModule,
  ],
})
export class TicketLaneComponent {
  private dialog = inject(DialogService);
  private ticketsService = inject(TicketsService);

  private ticketState$ = new BehaviorSubject<TicketValidationState | undefined>(
    undefined
  );
  private ticketFilter$ = new BehaviorSubject<TicketFilterArgs>({});

  @Input() set ticketState(ticketState: TicketValidationState) {
    this.ticketState$.next(ticketState);
  }
  get ticketState() {
    return this.ticketState$.getValue() as TicketValidationState;
  }

  @ViewChild(CdkVirtualScrollViewport)
  viewport!: CdkVirtualScrollViewport;

  TicketState = TicketValidationState;

  take = 20;
  hasNextPage = true;
  nextPage = '';

  cursor$ = new BehaviorSubject<string>('');
  tickets$: Observable<(Ticket & { tours: Tour[] })[]>;
  // ticketStateMeta$ = this.ticketState$.pipe(
  //   filter(Boolean),
  //   switchMap((ticketState) =>
  //     this.ticketsService.fetchTicketStateMeta(ticketState)
  //   )
  // );
  totalCount$ = new BehaviorSubject<number>(0);

  constructor() {
    const batchMap: Observable<Record<string, Ticket & { tours: Tour[] }>> =
      combineLatest([this.cursor$, this.ticketFilter$]).pipe(
        debounceTime(200),
        mergeMap(([cursor, filter]) =>
          this.fetchTickets(cursor, filter).pipe(
            map((tickets) => ({ tickets, cursor }))
          )
        ),
        scan((acc, { tickets, cursor }) => {
          const normalizedCursor = this.getNormalizedCursor(cursor);
          const existingTickets = Object.entries(acc)
            .filter(
              ([id]) =>
                id.split('_').at(0) !== normalizedCursor ||
                Object.keys(tickets).includes(id.split('_').at(1) ?? '')
            )
            .reduce(
              (acc, [id, ticket]) => ({
                ...acc,
                [`${normalizedCursor}_${id}`]: ticket,
              }),
              {} as Record<string, Ticket & { tours: Tour[] }>
            );

          return { ...existingTickets, ...tickets };
        }, {} as Record<string, Ticket & { tours: Tour[] }>)
      );
    this.tickets$ = batchMap.pipe(
      map((ticketNodes) =>
        Object.values(ticketNodes).sort(
          ({ createdAt: aCreated }, { createdAt: bCreated }) =>
            new Date(bCreated).getTime() - new Date(aCreated).getTime()
        )
      )
    );
  }

  openTicketView(ticket: Ticket & { tours: Tour[] }): void {
    const dialogRef = this.dialog.open(TicketViewComponent, {
      data: ticket,
      width: '40vw',
      minHeight: '400px',
    });

    dialogRef.afterClosed$
      .pipe(tap((result) => result && this.openTicketView(result)))
      .subscribe();
  }

  trackByFn(index: number, ticket: Ticket): string {
    return ticket.id;
  }

  fetchNextPage() {
    if (!this.hasNextPage) {
      return;
    }
    const start = this.viewport.getRenderedRange().start;
    const end = this.viewport.getRenderedRange().end;
    const total = this.viewport.getDataLength();

    if (this.cursor$.getValue() !== '' && start === 0) {
      this.cursor$.next('');
    } else if (total - end < 5) {
      this.cursor$.next(this.nextPage);
    }
  }

  private fetchTickets(
    cursor: string,
    filter: TicketFilterArgs
  ): Observable<Record<string, Ticket & { tours: Tour[] }>> {
    const normalizedCursor = this.getNormalizedCursor(cursor);

    return this.ticketsService
      .fetchTicketsByState(this.ticketState, cursor, filter, this.take)
      .pipe(
        tap(({ endCursor, hasNextPage }) => {
          this.nextPage = endCursor;
          this.hasNextPage = hasNextPage;
        }),
        map(({ nodes }) =>
          nodes?.reduce(
            (acc, cur) => ({
              ...acc,
              [`${normalizedCursor}_${cur.id}`]: cur as Ticket & {
                tours: Tour[];
              },
            }),
            {} as Record<string, Ticket & { tours: Tour[] }>
          )
        )
      );
  }

  private getNormalizedCursor(cursor: string) {
    return cursor === '' ? 'MA==' : cursor;
  }

  filterChanged(filter: TicketFilterArgs) {
    this.ticketFilter$.next(filter);
    this.cursor$.next('');
  }
}
