import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  DestroyRef,
  effect,
  inject,
  Input,
  OnInit
} from '@angular/core';
import {UtilityService} from "../../shared/_services/utility.service";
import {FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators} from "@angular/forms";
import {NgClass, NgTemplateOutlet, TitleCasePipe} from "@angular/common";
import {NgbActiveModal, NgbNav, NgbNavContent, NgbNavItem, NgbNavLink, NgbNavOutlet} from "@ng-bootstrap/ng-bootstrap";
import {TranslocoDirective} from "@jsverse/transloco";
import {AccountService} from "../../_services/account.service";
import {Chapter} from "../../_models/chapter";
import {LibraryType} from "../../_models/library/library";
import {setupLanguageSettings, TypeaheadSettings} from "../../typeahead/_models/typeahead-settings";
import {Tag} from "../../_models/tag";
import {Language} from "../../_models/metadata/language";
import {Person, PersonRole} from "../../_models/metadata/person";
import {Genre} from "../../_models/metadata/genre";
import {AgeRatingDto} from "../../_models/metadata/age-rating-dto";
import {ImageService} from "../../_services/image.service";
import {UploadService} from "../../_services/upload.service";
import {MetadataService} from "../../_services/metadata.service";
import {ActionService} from "../../_services/action.service";
import {DownloadService} from '../../shared/_services/download.service';
import {DownloadEntityType} from '../../shared/_models/download-queue-item';
import {SettingItemComponent} from "../../settings/_components/setting-item/setting-item.component";
import {TypeaheadComponent} from "../../typeahead/_components/typeahead.component";
import {concat, forkJoin, Observable, of, tap} from "rxjs";
import {map} from "rxjs/operators";
import {EntityTitleComponent} from "../../cards/entity-title/entity-title.component";
import {SettingButtonComponent} from "../../settings/_components/setting-button/setting-button.component";
import {CoverImageChooserComponent} from "../../cards/cover-image-chooser/cover-image-chooser.component";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {CompactNumberPipe} from "../../_pipes/compact-number.pipe";
import {MangaFormat} from "../../_models/manga-format";
import {DefaultDatePipe} from "../../_pipes/default-date.pipe";
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
import {BytesPipe} from "../../_pipes/bytes.pipe";
import {ImageComponent} from "../../shared/image/image.component";
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
import {ReadTimePipe} from "../../_pipes/read-time.pipe";
import {ChapterService} from "../../_services/chapter.service";
import {AgeRating} from "../../_models/metadata/age-rating";
import {BreakpointService} from "../../_services/breakpoint.service";
import {ActionItem} from "../../_models/actionables/action-item";
import {Action} from "../../_models/actionables/action";
import {ActionFactoryService} from "../../_services/action-factory.service";
import {modalDeleted, modalSaved} from "../../_models/modal/modal-result";
import {Tabs} from "../../_models/tabs";
import {TabTitlePipe} from "../../_pipes/tab-title.pipe";
import {
  EditExternalMetadataFormComponent
} from "../../shared/_components/edit-external-metadata-form/edit-external-metadata-form.component";
import {NULL_DATE} from "../../_pipes/date-year-range.pipe";


const blackList = [Action.Edit, Action.IncognitoRead, Action.AddToReadingList];

@Component({
  selector: 'app-edit-chapter-modal',
  imports: [
    FormsModule,
    NgbNav,
    NgbNavContent,
    NgbNavLink,
    TranslocoDirective,
    NgbNavOutlet,
    ReactiveFormsModule,
    NgbNavItem,
    SettingItemComponent,
    NgTemplateOutlet,
    NgClass,
    TypeaheadComponent,
    EntityTitleComponent,
    TitleCasePipe,
    SettingButtonComponent,
    CoverImageChooserComponent,
    CompactNumberPipe,
    DefaultDatePipe,
    UtcToLocalTimePipe,
    BytesPipe,
    ImageComponent,
    SafeHtmlPipe,
    ReadTimePipe,
    TabTitlePipe,
    EditExternalMetadataFormComponent,
  ],
  templateUrl: './edit-chapter-modal.component.html',
  styleUrl: './edit-chapter-modal.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class EditChapterModalComponent implements OnInit {

  protected readonly modal = inject(NgbActiveModal);
  public readonly utilityService = inject(UtilityService);
  public readonly imageService = inject(ImageService);
  private readonly uploadService = inject(UploadService);
  private readonly metadataService = inject(MetadataService);
  private readonly cdRef = inject(ChangeDetectorRef);
  protected readonly accountService = inject(AccountService);
  private readonly destroyRef = inject(DestroyRef);
  private readonly actionFactoryService = inject(ActionFactoryService);
  private readonly actionService = inject(ActionService);
  private readonly downloadService = inject(DownloadService);
  private readonly chapterService = inject(ChapterService);
  protected readonly breakpointService = inject(BreakpointService);

  @Input({required: true}) chapter!: Chapter;
  @Input({required: true}) libraryType!: LibraryType;
  @Input({required: true}) libraryId!: number;
  @Input({required: true}) seriesId!: number;

  activeId = Tabs.General;
  editForm: FormGroup = new FormGroup({});
  selectedCover: string = '';
  coverImageReset = false;

  tagsSettings: TypeaheadSettings<Tag> = new TypeaheadSettings();
  languageSettings: TypeaheadSettings<Language> | null = null;
  peopleSettings: {[PersonRole: string]: TypeaheadSettings<Person>} = {};
  genreSettings: TypeaheadSettings<Genre> = new TypeaheadSettings();

  tags: Tag[] = [];
  genres: Genre[] = [];
  ageRatings: Array<AgeRatingDto> = [];

  tasks = this.actionFactoryService.getActionablesForSettingsPage(
    this.actionFactoryService.getChapterActions(this.seriesId, this.libraryId, this.libraryType), blackList);
  /**
   * A copy of the chapter from init. This is used to compare values for name fields to see if lock was modified
   */
  initChapter!: Chapter;
  imageUrls: Array<string> = [];
  size: number = 0;

  get WebLinks() {
    if (this.chapter.webLinks === '') return [];
    return this.chapter.webLinks.split(',');
  }

  constructor() {
    effect(() => {
      if (!this.accountService.hasAdminRole()) {
        this.activeId = Tabs.Info;
        this.cdRef.markForCheck();
      }
    });
  }


  ngOnInit() {
    this.initChapter = Object.assign({}, this.chapter);
    this.imageUrls.push(this.imageService.getChapterCoverImage(this.chapter.id));

    this.size = (<Chapter>this.chapter).files.reduce((sum, v) => sum + v.bytes, 0);

    this.editForm.addControl('titleName', new FormControl(this.chapter.titleName, []));
    this.editForm.addControl('sortOrder', new FormControl(Math.max(0, this.chapter.sortOrder), [Validators.required, Validators.min(0)]));
    this.editForm.addControl('summary', new FormControl(this.chapter.summary || '', []));
    this.editForm.addControl('language', new FormControl(this.chapter.language, []));
    this.editForm.addControl('isbn', new FormControl(this.chapter.isbn, []));
    this.editForm.addControl('ageRating', new FormControl(this.chapter.ageRating, []));

    if (this.chapter.releaseDate !== NULL_DATE) {
      this.editForm.addControl('releaseDate', new FormControl(this.chapter.releaseDate.substring(0, 10), []));
    } else {
      this.editForm.addControl('releaseDate', new FormControl('', []));
    }


    this.editForm.addControl('genres', new FormControl(this.chapter.genres, []));
    this.editForm.addControl('tags', new FormControl(this.chapter.tags, []));


    this.editForm.addControl('coverImageIndex', new FormControl(0, []));
    this.editForm.addControl('coverImageLocked', new FormControl(this.chapter.coverImageLocked, []));

    this.metadataService.getAllValidLanguages().pipe(
      tap(validLanguages => {
        this.languageSettings = setupLanguageSettings(true, this.utilityService, validLanguages, this.chapter.language);
        this.cdRef.markForCheck();
      }),
    ).subscribe();

    this.metadataService.getAllAgeRatings().subscribe(ratings => {
      this.ageRatings = ratings;
      this.cdRef.markForCheck();
    });

    this.editForm.get('titleName')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
      this.chapter.titleNameLocked = true;
      this.cdRef.markForCheck();
    });

    this.editForm.get('sortOrder')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
      this.chapter.sortOrderLocked = true;
      this.cdRef.markForCheck();
    });

    this.editForm.get('isbn')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
      this.chapter.isbnLocked = true;
      this.cdRef.markForCheck();
    });

    this.editForm.get('ageRating')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
      this.chapter.ageRatingLocked = true;
      this.cdRef.markForCheck();
    });

    this.editForm.get('summary')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
      this.chapter.summaryLocked = true;
      this.cdRef.markForCheck();
    });

    this.editForm.get('releaseDate')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
      this.chapter.releaseDateLocked = true;
      this.cdRef.markForCheck();
    });

    this.setupTypeaheads();

  }


  close() {
    this.modal.dismiss();
  }

  save() {
    const model = this.editForm.getRawValue();
    const selectedIndex = this.editForm.get('coverImageIndex')?.value || 0;

    // Patch in data from the model that is not typeahead (as those are updated during setting)
    if (model.releaseDate === '') {
      this.chapter.releaseDate = NULL_DATE;
    } else {
      this.chapter.releaseDate = model.releaseDate + 'T00:00:00';
    }

    this.chapter.ageRating = parseInt(model.ageRating + '', 10) as AgeRating;
    this.chapter.sortOrder = model.sortOrder;
    this.chapter.titleName = model.titleName;
    this.chapter.summary = model.summary;
    this.chapter.isbn = model.isbn;
    this.chapter.aniListId = model.aniListId;
    this.chapter.comicVineId = model.comicVineId;
    this.chapter.malId = model.malId;
    this.chapter.hardcoverId = model.hardcoverId;
    this.chapter.metronId = model.metronId;
    this.chapter.language = model.language;


    const apis = [
      this.chapterService.updateChapter(this.chapter)
    ];

    if (selectedIndex > 0 || this.coverImageReset) {
      apis.push(this.uploadService.updateChapterCoverImage(this.chapter.id, this.selectedCover, !this.coverImageReset));
    }

    concat(...apis).subscribe(results => {
      const needsCoverUpdate = selectedIndex > 0 || this.coverImageReset;
      this.modal.close(modalSaved(model, needsCoverUpdate));
    });
  }

  unlock(b: any, field: string) {
    if (b) {
      b[field] = !b[field];
    }
    this.cdRef.markForCheck();
  }

  async runTask(action: ActionItem<Chapter>) {
    switch (action.action) {

      case Action.MarkAsRead:
        this.actionService.markChapterAsRead(this.libraryId, this.seriesId, this.chapter, (p) => {
          this.chapter.pagesRead = p.pagesRead;
          this.cdRef.markForCheck();
        });
        break;
      case Action.MarkAsUnread:
        this.actionService.markChapterAsUnread(this.libraryId, this.seriesId, this.chapter, (p) => {
          this.chapter.pagesRead = 0;
          this.cdRef.markForCheck();
        });
        break;
      case Action.Delete:
        await this.actionService.deleteChapter(this.chapter.id, (b) => {
          if (!b) return;
          this.modal.close(modalDeleted(this.chapter));
        });
        break;
      case Action.Download:
        this.downloadService.download(DownloadEntityType.Chapter, this.chapter, this.libraryId, this.seriesId);
        break;
    }
  }

  setupTypeaheads() {
    forkJoin([
      this.setupTagSettings(),
      this.setupGenreTypeahead(),
      this.setupPersonTypeahead(),
    ]).subscribe(results => {
      this.cdRef.markForCheck();
    });
  }

  setupTagSettings() {
    this.tagsSettings.minCharacters = 0;
    this.tagsSettings.multiple = true;
    this.tagsSettings.id = 'tags';
    this.tagsSettings.unique = true;
    this.tagsSettings.showLocked = true;
    this.tagsSettings.addIfNonExisting = true;


    this.tagsSettings.compareFn = (options: Tag[], filter: string) => {
      return options.filter(m => this.utilityService.filter(m.title, filter));
    }
    this.tagsSettings.fetchFn = (filter: string) => this.metadataService.getAllTags()
      .pipe(map(items => this.tagsSettings.compareFn(items, filter)));

    this.tagsSettings.addTransformFn = ((title: string) => {
      return {id: 0, title: title };
    });
    this.tagsSettings.selectionCompareFn = (a: Tag, b: Tag) => {
      return a.title.toLowerCase() == b.title.toLowerCase();
    }
    this.tagsSettings.compareFnForAdd = (options: Tag[], filter: string) => {
      return options.filter(m => this.utilityService.filterMatches(m.title, filter));
    }
    this.tagsSettings.trackByIdentityFn = (index, value) => value.title + (value.id + '');

    if (this.chapter.tags) {
      this.tagsSettings.savedData = this.chapter.tags;
    }
    return of(true);
  }

  setupGenreTypeahead() {
    this.genreSettings.minCharacters = 0;
    this.genreSettings.multiple = true;
    this.genreSettings.id = 'genres';
    this.genreSettings.unique = true;
    this.genreSettings.showLocked = true;
    this.genreSettings.addIfNonExisting = true;
    this.genreSettings.fetchFn = (filter: string) => {
      return this.metadataService.getAllGenres()
        .pipe(map(items => this.genreSettings.compareFn(items, filter)));
    };
    this.genreSettings.compareFn = (options: Genre[], filter: string) => {
      return options.filter(m => this.utilityService.filter(m.title, filter));
    }
    this.genreSettings.compareFnForAdd = (options: Genre[], filter: string) => {
      return options.filter(m => this.utilityService.filterMatches(m.title, filter));
    }
    this.genreSettings.selectionCompareFn = (a: Genre, b: Genre) => {
      return a.title.toLowerCase() == b.title.toLowerCase();
    }

    this.genreSettings.addTransformFn = ((title: string) => {
      return {id: 0, title: title };
    });
    this.genreSettings.trackByIdentityFn = (index, value) => value.title + (value.id + '');

    if (this.chapter.genres) {
      this.genreSettings.savedData = this.chapter.genres;
    }
    return of(true);
  }


  updateFromPreset(id: string, presetField: Array<Person> | undefined, role: PersonRole) {
    const personSettings = this.createBlankPersonSettings(id, role)

    if (presetField && presetField.length > 0) {
      const fetch = personSettings.fetchFn as ((filter: string) => Observable<Person[]>);
      return fetch('').pipe(map(people => {
        const presetIds = presetField.map(p => p.id);
        personSettings.savedData = people.filter(person => presetIds.includes(person.id));
        this.peopleSettings[role] = personSettings;
        this.metadataService.updatePerson(this.chapter, personSettings.savedData as Person[], role);
        this.cdRef.markForCheck();
        return true;
      }));
    }

    this.peopleSettings[role] = personSettings;
    return of(true);

  }

  setupPersonTypeahead() {
    this.peopleSettings = {};

    return forkJoin([
      this.updateFromPreset('writer', this.chapter.writers, PersonRole.Writer),
      this.updateFromPreset('character', this.chapter.characters, PersonRole.Character),
      this.updateFromPreset('colorist', this.chapter.colorists, PersonRole.Colorist),
      this.updateFromPreset('cover-artist', this.chapter.coverArtists, PersonRole.CoverArtist),
      this.updateFromPreset('editor', this.chapter.editors, PersonRole.Editor),
      this.updateFromPreset('inker', this.chapter.inkers, PersonRole.Inker),
      this.updateFromPreset('letterer', this.chapter.letterers, PersonRole.Letterer),
      this.updateFromPreset('penciller', this.chapter.pencillers, PersonRole.Penciller),
      this.updateFromPreset('publisher', this.chapter.publishers, PersonRole.Publisher),
      this.updateFromPreset('imprint', this.chapter.imprints, PersonRole.Imprint),
      this.updateFromPreset('translator', this.chapter.translators, PersonRole.Translator),
      this.updateFromPreset('teams', this.chapter.teams, PersonRole.Team),
      this.updateFromPreset('locations', this.chapter.locations, PersonRole.Location),
    ]).pipe(map(_ => {
      return of(true);
    }));
  }

  fetchPeople(role: PersonRole, filter: string) {
    return this.metadataService.getAllPeople().pipe(map(people => {
      return people.filter(p => this.utilityService.filter(p.name, filter));
    }));
  }

  createBlankPersonSettings(id: string, role: PersonRole) {
    let personSettings = new TypeaheadSettings<Person>();
    personSettings.minCharacters = 0;
    personSettings.multiple = true;
    personSettings.showLocked = true;
    personSettings.unique = true;
    personSettings.addIfNonExisting = true;
    personSettings.id = id;
    personSettings.compareFn = (options: Person[], filter: string) => {
      return options.filter(m => this.utilityService.filter(m.name, filter));
    }
    personSettings.compareFnForAdd = (options: Person[], filter: string) => {
      return options.filter(m => this.utilityService.filterMatches(m.name, filter));
    }

    personSettings.selectionCompareFn = (a: Person, b: Person) => {
      return a.name == b.name;
    }
    personSettings.fetchFn = (filter: string) => {
      return this.fetchPeople(role, filter).pipe(map(items => personSettings.compareFn(items, filter)));
    };

    personSettings.addTransformFn = ((title: string) => {
      return {id: 0, name: title, aliases: [], role: role, description: '', coverImage: '', coverImageLocked: false, primaryColor: '', secondaryColor: '' };
    });

    personSettings.trackByIdentityFn = (index, value) => value.name + (value.id + '');

    return personSettings;
  }

  updateTags(tags: Tag[]) {
    this.tags = tags;
    this.chapter.tags = tags;
    this.cdRef.markForCheck();
  }

  updateGenres(genres: Genre[]) {
    this.genres = genres;
    this.chapter.genres = genres;
    this.cdRef.markForCheck();
  }

  updatePerson(persons: Person[], role: PersonRole) {
    this.metadataService.updatePerson(this.chapter, persons, role);
    this.chapter.locationLocked = true;
    this.cdRef.markForCheck();
  }

  updateLanguage(language: Array<Language>) {
    if (language.length === 0) {
      this.chapter.language = '';
      return;
    }
    this.chapter.language = language[0].isoCode;
    this.chapter.languageLocked = true;
    this.cdRef.markForCheck();
  }

  updateSelectedIndex(index: number) {
    this.editForm.patchValue({
      coverImageIndex: index
    });
    this.cdRef.markForCheck();
  }

  updateSelectedImage(url: string) {
    this.selectedCover = url;
    this.cdRef.markForCheck();
  }

  handleReset() {
    this.coverImageReset = true;
    this.editForm.patchValue({
      coverImageLocked: false
    });
    this.cdRef.markForCheck();
  }

  getPersonsSettings(role: PersonRole) {
    return this.peopleSettings[role];
  }

  protected readonly Tabs = Tabs;
  protected readonly Action = Action;
  protected readonly PersonRole = PersonRole;
  protected readonly MangaFormat = MangaFormat;
}
