import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component, DestroyRef,
  EventEmitter,
  HostListener,
  inject,
  Input,
  OnInit,
  Output
} from '@angular/core';
import { Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { DownloadEvent, DownloadService } from 'src/app/shared/_services/download.service';
import { UtilityService } from 'src/app/shared/_services/utility.service';
import { Chapter } from 'src/app/_models/chapter';
import { CollectionTag } from 'src/app/_models/collection-tag';
import { UserProgressUpdateEvent } from 'src/app/_models/events/user-progress-update-event';
import { MangaFormat } from 'src/app/_models/manga-format';
import { PageBookmark } from 'src/app/_models/readers/page-bookmark';
import { RecentlyAddedItem } from 'src/app/_models/recently-added-item';
import { Series } from 'src/app/_models/series';
import { User } from 'src/app/_models/user';
import { Volume } from 'src/app/_models/volume';
import { AccountService } from 'src/app/_services/account.service';
import { Action, ActionFactoryService, ActionItem } from 'src/app/_services/action-factory.service';
import { ImageService } from 'src/app/_services/image.service';
import { LibraryService } from 'src/app/_services/library.service';
import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service';
import { ScrollService } from 'src/app/_services/scroll.service';
import { BulkSelectionService } from '../bulk-selection.service';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {ImageComponent} from "../../shared/image/image.component";
import {NgbProgressbar, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {DownloadIndicatorComponent} from "../download-indicator/download-indicator.component";
import {FormsModule} from "@angular/forms";
import {MangaFormatPipe} from "../../pipe/manga-format.pipe";
import {MangaFormatIconPipe} from "../../pipe/manga-format-icon.pipe";
import {SentenceCasePipe} from "../../pipe/sentence-case.pipe";
import {CommonModule} from "@angular/common";
import {RouterLink} from "@angular/router";
import {TranslocoModule} from "@ngneat/transloco";
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";

@Component({
  selector: 'app-card-item',
  standalone: true,
  imports: [
    CommonModule,
    ImageComponent,
    NgbProgressbar,
    DownloadIndicatorComponent,
    FormsModule,
    NgbTooltip,
    MangaFormatPipe,
    MangaFormatIconPipe,
    CardActionablesComponent,
    SentenceCasePipe,
    RouterLink,
    TranslocoModule
  ],
  templateUrl: './card-item.component.html',
  styleUrls: ['./card-item.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CardItemComponent implements OnInit {

  /**
   * Card item url. Will internally handle error and missing covers
   */
  @Input() imageUrl = '';
  /**
   * Name of the card
   */
  @Input() title = '';
  /**
   * Shows below the title. Defaults to not visible
   */
  @Input() subtitle = '';
  /**
   * Any actions to perform on the card
   */
  @Input() actions: ActionItem<any>[] = [];
  /**
   * Pages Read
   */
  @Input() read = 0;
  /**
   * Total Pages
   */
  @Input() total = 0;
  /**
   * Suppress library link
   */
  @Input() suppressLibraryLink = false;
  /**
   * This is the entity we are representing. It will be returned if an action is executed.
   */
  @Input({required: true}) entity!: Series | Volume | Chapter | CollectionTag | PageBookmark | RecentlyAddedItem;
  /**
   * If the entity is selected or not.
   */
  @Input() selected: boolean = false;
  /**
   * If the entity should show selection code
   */
  @Input() allowSelection: boolean = false;
  /**
   * This will suppress the cannot read archive warning when total pages is 0
   */
  @Input() suppressArchiveWarning: boolean = false;
  /**
   * The number of updates/items within the card. If less than 2, will not be shown.
   */
  @Input() count: number = 0;
  /**
   * Additional information to show on the overlay area. Will always render.
   */
  @Input() overlayInformation: string = '';
  /**
   * Event emitted when item is clicked
   */
  @Output() clicked = new EventEmitter<string>();
  /**
   * When the card is selected.
   */
  @Output() selection = new EventEmitter<boolean>();
  /**
   * Library name item belongs to
   */
  libraryName: string | undefined = undefined;
  libraryId: number | undefined = undefined;
  /**
   * Format of the entity (only applies to Series)
   */
  format: MangaFormat = MangaFormat.UNKNOWN;
  tooltipTitle: string = this.title;

  /**
   * This is the download we get from download service.
   */
  download$: Observable<DownloadEvent | null> | null = null;

  /**
   * Handles touch events for selection on mobile devices
   */
  prevTouchTime: number = 0;
  /**
   * Handles touch events for selection on mobile devices to ensure you aren't touch scrolling
   */
  prevOffset: number = 0;
  selectionInProgress: boolean = false;

  private user: User | undefined;
  private readonly destroyRef = inject(DestroyRef);

  get MangaFormat(): typeof MangaFormat {
    return MangaFormat;
  }


  constructor(public imageService: ImageService, private libraryService: LibraryService,
    public utilityService: UtilityService, private downloadService: DownloadService,
    public bulkSelectionService: BulkSelectionService,
    private messageHub: MessageHubService, private accountService: AccountService,
    private scrollService: ScrollService, private readonly cdRef: ChangeDetectorRef,
    private actionFactoryService: ActionFactoryService) {}

  ngOnInit(): void {

    if (this.entity.hasOwnProperty('promoted') && this.entity.hasOwnProperty('title')) {
      this.suppressArchiveWarning = true;
      this.cdRef.markForCheck();
    }

    if (!this.suppressLibraryLink) {
      if (this.entity !== undefined && this.entity.hasOwnProperty('libraryId')) {
        this.libraryId = (this.entity as Series).libraryId;
        this.cdRef.markForCheck();
      }

      if (this.libraryId !== undefined && this.libraryId > 0) {
        this.libraryService.getLibraryName(this.libraryId).pipe(takeUntilDestroyed(this.destroyRef)).subscribe(name => {
          this.libraryName = name;
          this.cdRef.markForCheck();
        });
      }
    }
    this.format = (this.entity as Series).format;

    if (this.utilityService.isChapter(this.entity)) {
      const chapterTitle = this.utilityService.asChapter(this.entity).titleName;
      if (chapterTitle === '' || chapterTitle === null || chapterTitle === undefined) {
        const volumeTitle = this.utilityService.asChapter(this.entity).volumeTitle
        if (volumeTitle === '' || volumeTitle === null || volumeTitle === undefined) {
          this.tooltipTitle = (this.title).trim();
        } else {
          this.tooltipTitle = (this.utilityService.asChapter(this.entity).volumeTitle + ' ' + this.title).trim();
        }
      } else {
        this.tooltipTitle = chapterTitle;
      }
    } else if (this.utilityService.isVolume(this.entity)) {
      const vol = this.utilityService.asVolume(this.entity);
      if (vol.chapters !== undefined && vol.chapters.length > 0) {
        this.tooltipTitle = vol.chapters[0].titleName;
      }
      if (this.tooltipTitle === '') {
        this.tooltipTitle = vol.name;
      }
    } else if (this.utilityService.isSeries(this.entity)) {
      this.tooltipTitle = this.title || (this.utilityService.asSeries(this.entity).name);
    }

    this.filterSendTo();
    this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(user => {
      this.user = user;
    });

    this.messageHub.messages$.pipe(filter(event => event.event === EVENTS.UserProgressUpdate),
    map(evt => evt.payload as UserProgressUpdateEvent), takeUntilDestroyed(this.destroyRef)).subscribe(updateEvent => {
      if (this.user === undefined || this.user.username !== updateEvent.username) return;
      if (this.utilityService.isChapter(this.entity) && updateEvent.chapterId !== this.entity.id) return;
      if (this.utilityService.isVolume(this.entity) && updateEvent.volumeId !== this.entity.id) return;
      if (this.utilityService.isSeries(this.entity) && updateEvent.seriesId !== this.entity.id) return;

      // For volume or Series, we can't just take the event
      if (this.utilityService.isChapter(this.entity)) {
        const c = this.utilityService.asChapter(this.entity);
        c.pagesRead = updateEvent.pagesRead;
        this.read = updateEvent.pagesRead;
      }
      if (this.utilityService.isVolume(this.entity) || this.utilityService.isSeries(this.entity)) {
        if (this.utilityService.isVolume(this.entity)) {
          const v = this.utilityService.asVolume(this.entity);
          let sum = 0;
          const chapters = v.chapters.filter(c => c.volumeId === updateEvent.volumeId);
          chapters.forEach(chapter => {
            chapter.pagesRead = updateEvent.pagesRead;
            sum += chapter.pagesRead;
          });
          v.pagesRead = sum;
          this.read = sum;
        } else {
          return;
        }
      }

      this.cdRef.detectChanges();
    });

    this.download$ = this.downloadService.activeDownloads$.pipe(takeUntilDestroyed(this.destroyRef), map((events) => {
      if(this.utilityService.isSeries(this.entity)) return events.find(e => e.entityType === 'series' && e.subTitle === this.downloadService.downloadSubtitle('series', (this.entity as Series))) || null;
      if(this.utilityService.isVolume(this.entity)) return events.find(e => e.entityType === 'volume' && e.subTitle === this.downloadService.downloadSubtitle('volume', (this.entity as Volume))) || null;
      if(this.utilityService.isChapter(this.entity)) return events.find(e => e.entityType === 'chapter' && e.subTitle === this.downloadService.downloadSubtitle('chapter', (this.entity as Chapter))) || null;
      // Is PageBookmark[]
      if(this.entity.hasOwnProperty('length')) return events.find(e => e.entityType === 'bookmark' && e.subTitle === this.downloadService.downloadSubtitle('bookmark', [(this.entity as PageBookmark)])) || null;
      return null;
    }));

  }


  @HostListener('touchmove', ['$event'])
  onTouchMove(event: TouchEvent) {
    if (!this.allowSelection) return;

    this.selectionInProgress = false;
    this.cdRef.markForCheck();
  }

  @HostListener('touchstart', ['$event'])
  onTouchStart(event: TouchEvent) {
    if (!this.allowSelection) return;

    this.prevTouchTime = event.timeStamp;
    this.prevOffset = this.scrollService.scrollPosition;
    this.selectionInProgress = true;
  }

  @HostListener('touchend', ['$event'])
  onTouchEnd(event: TouchEvent) {
    if (!this.allowSelection) return;
    const delta = event.timeStamp - this.prevTouchTime;
    const verticalOffset = this.scrollService.scrollPosition;

    if (delta >= 300 && delta <= 1000 && (verticalOffset === this.prevOffset) && this.selectionInProgress) {
      this.handleSelection();
      event.stopPropagation();
      event.preventDefault();
    }
    this.prevTouchTime = 0;
    this.selectionInProgress = false;
  }


  handleClick(event?: any) {
    if (this.bulkSelectionService.hasSelections()) {
      this.handleSelection();
      return;
    }
    this.clicked.emit(this.title);
  }

  preventClick(event: any) {
    event.stopPropagation();
    event.preventDefault();
  }

  performAction(action: ActionItem<any>) {
    if (action.action == Action.Download) {
      if (this.utilityService.isVolume(this.entity)) {
        const volume = this.utilityService.asVolume(this.entity);
        this.downloadService.download('volume', volume);
      } else if (this.utilityService.isChapter(this.entity)) {
        const chapter = this.utilityService.asChapter(this.entity);
        this.downloadService.download('chapter', chapter);
      } else if (this.utilityService.isSeries(this.entity)) {
        const series = this.utilityService.asSeries(this.entity);
        this.downloadService.download('series', series);
      }
      return; // Don't propagate the download from a card
    }

    if (typeof action.callback === 'function') {
      action.callback(action, this.entity);
    }
  }


  isPromoted() {
    const tag = this.entity as CollectionTag;
    return tag.hasOwnProperty('promoted') && tag.promoted;
  }


  handleSelection(event?: any) {
    if (event) {
      event.stopPropagation();
    }
    this.selection.emit(this.selected);
    this.cdRef.detectChanges();
  }

  filterSendTo() {
    if (!this.actions || this.actions.length === 0) return;

    if (this.utilityService.isChapter(this.entity)) {
      this.actions = this.actionFactoryService.filterSendToAction(this.actions, this.entity as Chapter);
    } else if (this.utilityService.isVolume(this.entity)) {
      const vol = this.utilityService.asVolume(this.entity);
      this.actions = this.actionFactoryService.filterSendToAction(this.actions, vol.chapters[0]);
    } else if (this.utilityService.isSeries(this.entity)) {
      const series = (this.entity as Series);
      // if (series.format === MangaFormat.EPUB || series.format === MangaFormat.PDF) {
      //   this.actions = this.actions.filter(a => a.title !== 'Send To');
      // }
    }
  }
}
