import { Component } from '@angular/core';
import {
  CdkDragDrop,
  moveItemInArray,
  transferArrayItem,
} from '@angular/cdk/drag-drop';
import { Spell } from 'src/interfaces/spell';
import { DataService } from 'src/services/data/data.service';
import { ModalService } from 'src/services/modal/modal.service';
import { SpellsService } from 'src/services/spells/spells.service';
import { SpellModalComponent } from 'src/app/journal/spell-modal/spell-modal.component';
import { FullSpellcardComponent } from 'src/app/shared-components/full-spellcard/full-spellcard.component';
import { CustomSpellsModalComponent } from './custom-spells-modal/custom-spells-modal.component';

@Component({
  selector: 'app-journal-spellcards',
  templateUrl: './journal-spellcards.component.html',
  styleUrls: ['./journal-spellcards.component.scss'],
})
export class JournalSpellcardsComponent {
  public level0: Spell[] = [];
  public level1: Spell[] = [];
  public level2: Spell[] = [];
  public level3: Spell[] = [];
  public level4: Spell[] = [];
  public level5: Spell[] = [];
  public level6: Spell[] = [];
  public level7: Spell[] = [];
  public level8: Spell[] = [];
  public level9: Spell[] = [];

  public showSpellList: boolean[] = [
    true,
    true,
    true,
    true,
    true,
    true,
    true,
    true,
    true,
    true,
  ];

  public draggingIndex: number | undefined;

  public constructor(
    private dataAccessor: DataService,
    private modalAccessor: ModalService,
    public spellsService: SpellsService,
  ) {
    this.loadSpells();
    this.hideEmptySpelllists();
  }

  ///////// FUNCTIONS //////////

  public getUsedIDs(level: number): number[] {
    const usedIDs: number[] = [];
    this.getSpellList(level).forEach((spell) => {
      usedIDs.push(spell.id);
    });
    return usedIDs;
  }

  public showFullSpellcard(
    spell: Spell,
    level: number,
    spellIndex: number,
  ): void {
    const favorites = this.dataAccessor.favoriteSpells;
    const alreadyInFavorites = favorites.some(
      (currentSpell) => currentSpell.id === spell.id,
    );
    this.modalAccessor.openModal(FullSpellcardComponent, {
      spell: spell,
      isFromDashboard: false,
      alreadyInFavorites: alreadyInFavorites,
    });
    const resultSubscription = this.modalAccessor.result$.subscribe(
      (result) => {
        resultSubscription.unsubscribe();
        if (result.state === 'delete') {
          this.spellsService.deleteCustomSpell(spell);
          this.dataAccessor.deleteCustomSpell(spell);
          this.dataAccessor.removeFavoriteSpell(spell);
          this.getSpellList(level).splice(spellIndex, 1);
          this.updateSpellsInDatabase(level);
        } else if (result.state === 'remove') {
          this.dataAccessor.removeFavoriteSpell(spell);
          this.getSpellList(level).splice(spellIndex, 1);
          this.updateSpellsInDatabase(level);
        } else if (result.state === 'update') {
          setTimeout(() => {
            this.openSpellModificationModal(level, spellIndex);
          }, 100);
        } else if (result.state === 'add') {
          this.dataAccessor.addFavoriteSpell(spell);
        }
      },
    );
  }

  /**
   * Opens a modal to modify an existing spell. The spell is then updated in the dataAccessor, local level and favorites.
   * @param level The current level of the spell.
   * @param index The index where the spell is located in the spell list.
   */
  public openSpellModificationModal(level: number, index: number): void {
    this.modalAccessor.openModal(SpellModalComponent, {
      spell:
        index !== undefined
          ? JSON.parse(JSON.stringify(this.getSpellList(level)![index]))
          : undefined,
      isModification: true,
    });
    const resultSubscription = this.modalAccessor.result$.subscribe(
      (result) => {
        if (result.state === 'update') {
          // level was not modified
          if (level === result.data.level) {
            this.updateSpell(result.data, level, index);
            this.dataAccessor.updateFavoriteSpell(result.data);
            this.dataAccessor.updateCustomSpell(result.data);
          } else {
            // level was modified
            this.getSpellList(level).splice(index, 1);
            this.addSpell(result.data, result.data.level);
          }
        }
        resultSubscription.unsubscribe();
      },
    );
  }

  /**
   * In this modal new spells can be created. This can be completely new spells or
   * modified official spells. If successful, the spell is added to the spell list,
   * sent to the daService and sent to the spellsService which in return sends it to the dataBase
   * @param level
   * @param isBasedOnOfficialSpell
   * @param spell
   */
  public openSpellCreationModal(
    level: number,
    isBasedOnOfficialSpell: boolean,
    spell?: Spell,
  ): void {
    this.modalAccessor.openModal(SpellModalComponent, {
      spell: isBasedOnOfficialSpell ? spell : undefined,
      level: level,
      id: this.dataAccessor.customSpellId,
      isBasedOnOfficialSpell: isBasedOnOfficialSpell,
      classes: [this.dataAccessor.characterData.class],
    });
    const resultSubscription = this.modalAccessor.result$.subscribe(
      (result) => {
        if (result.state === 'add') {
          this.addSpell(result.data, level);
          // this.spellsService.addCustomSpell(result.data);
          this.dataAccessor.addCustomSpell(result.data);
        } else {
        }
        resultSubscription.unsubscribe();
      },
    );
  }

  /**
   * Opens the modal to manage custom spells. Here, custom spells can be deleted.
   */
  public openManageCustomSpellsModal(): void {
    this.modalAccessor.openModal(CustomSpellsModalComponent, {
      spells: this.dataAccessor.customSpells,
    });
    const resultSubscription = this.modalAccessor.result$.subscribe(
      (result) => {
        if (result.state === 'delete') {
          result.data.forEach((spell: Spell) => {
            this.deleteCustomSpell(spell);
          });
        }
        resultSubscription.unsubscribe();
      },
    );
  }

  /**
   * Deletes a custom spell from the custom spells list.
   * It is deleted in the spells and data service.
   * It is also removed from the prepared and favorite spells list if present.
   * @param spell
   */
  public deleteCustomSpell(spell: Spell): void {
    this.dataAccessor.deleteCustomSpell(spell);
    this.spellsService.deleteCustomSpell(spell);
    let list = this.getSpellList(spell.level);
    const index = list.findIndex((spellList) => spellList.id === spell.id);
    if (index > -1) {
      list.splice(index, 1);
      this.dataAccessor.removeFavoriteSpell(spell);
      this.updateSpellsInDatabase(spell.level);
    }
  }

  /**
   * Adds a given spell to the spelllist of a specific level.
   * @param spell The spell to add.
   * @param level The level the spell needs to be added to.
   */
  public addSpellToSpelllist(spell: Spell, level: number): void {
    this.addSpell(spell, level);
  }

  /**
   * Adds a given spell to the spell list specified by level.
   * Also updates the spell list in the database.
   * @param spell The spell to add.
   * @param level The level to add the spell to.
   */
  public addSpell(spell: Spell, level: number) {
    this.getSpellList(level).push(spell);
    this.updateSpellsInDatabase(level);
  }

  /**
   * Overrides a spell in a given spell list, specified by the index.
   * @param spell The new spell.
   * @param level The level to add the spell to.
   * @param index The index at which the spell should be overridden.
   */
  public updateSpell(spell: Spell, level: number, index: number): void {
    this.getSpellList(level)![index] = spell;
    this.updateSpellsInDatabase(level);
  }

  /**
   * Returns the reference to the spell list specified by the level.
   * @param level Specifies the level of the spell list.
   * @returns Returns the spell list specified by the level.
   */
  public getSpellList(level: number): Spell[] {
    switch (level) {
      case 0:
        return this.level0;
      case 1:
        return this.level1;
      case 2:
        return this.level2;
      case 3:
        return this.level3;
      case 4:
        return this.level4;
      case 5:
        return this.level5;
      case 6:
        return this.level6;
      case 7:
        return this.level7;
      case 8:
        return this.level8;
      case 9:
        return this.level9;
      default:
        return [];
    }
  }

  public toggleSpellList(index: number) {
    this.showSpellList[index] = !this.showSpellList[index];
  }

  public drop(event: CdkDragDrop<any[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data,
        event.previousIndex,
        event.currentIndex,
      );
      this.updateSpellsInDatabase(this.getIndex(event.previousContainer.id));
      // update database with one level
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex,
      );
      this.updateSpellsInDatabase(this.getIndex(event.previousContainer.id));

      // Update level of moved spell
      const movedSpell = event.container.data[event.currentIndex];
      const newContainer = event.container.id;

      switch (newContainer) {
        case 'cdk-drop-list-0':
          movedSpell.level = 0;
          this.updateSpellsInDatabase(0);
          if (movedSpell.isCustom) {
            this.dataAccessor.updateCustomSpell(movedSpell);
          }
          break;
        case 'cdk-drop-list-1':
          movedSpell.level = 1;
          this.updateSpellsInDatabase(1);
          if (movedSpell.isCustom) {
            this.dataAccessor.updateCustomSpell(movedSpell);
          }
          break;
        case 'cdk-drop-list-2':
          movedSpell.level = 2;
          this.updateSpellsInDatabase(2);
          if (movedSpell.isCustom) {
            this.dataAccessor.updateCustomSpell(movedSpell);
          }
          break;
        case 'cdk-drop-list-3':
          movedSpell.level = 3;
          this.updateSpellsInDatabase(3);
          if (movedSpell.isCustom) {
            this.dataAccessor.updateCustomSpell(movedSpell);
          }
          break;
        case 'cdk-drop-list-4':
          movedSpell.level = 4;
          this.updateSpellsInDatabase(4);
          if (movedSpell.isCustom) {
            this.dataAccessor.updateCustomSpell(movedSpell);
          }
          break;
        case 'cdk-drop-list-5':
          movedSpell.level = 5;
          this.updateSpellsInDatabase(5);
          if (movedSpell.isCustom) {
            this.dataAccessor.updateCustomSpell(movedSpell);
          }
          break;
        case 'cdk-drop-list-6':
          movedSpell.level = 6;
          this.updateSpellsInDatabase(6);
          if (movedSpell.isCustom) {
            this.dataAccessor.updateCustomSpell(movedSpell);
          }
          break;
        case 'cdk-drop-list-7':
          movedSpell.level = 7;
          this.updateSpellsInDatabase(7);
          if (movedSpell.isCustom) {
            this.dataAccessor.updateCustomSpell(movedSpell);
          }
          break;
        case 'cdk-drop-list-8':
          movedSpell.level = 8;
          this.updateSpellsInDatabase(8);
          if (movedSpell.isCustom) {
            this.dataAccessor.updateCustomSpell(movedSpell);
          }
          break;
        case 'cdk-drop-list-9':
          movedSpell.level = 9;
          this.updateSpellsInDatabase(9);
          if (movedSpell.isCustom) {
            this.dataAccessor.updateCustomSpell(movedSpell);
          }
          break;
      }
    }
  }

  private getIndex(id: string): number {
    switch (id) {
      case 'cdk-drop-list-0':
        return 0;
      case 'cdk-drop-list-1':
        return 1;
      case 'cdk-drop-list-2':
        return 2;
      case 'cdk-drop-list-3':
        return 3;
      case 'cdk-drop-list-4':
        return 4;
      case 'cdk-drop-list-5':
        return 5;
      case 'cdk-drop-list-6':
        return 6;
      case 'cdk-drop-list-7':
        return 7;
      case 'cdk-drop-list-8':
        return 8;
      case 'cdk-drop-list-9':
        return 9;
      default:
        return -1;
    }
  }

  public dragStart(index: number) {
    this.draggingIndex = index;
  }

  public dragEnd(event: any) {
    if (event.event.target.classList.contains('removal-card')) {
      this.getSpellList(this.draggingIndex!).splice(
        event.source.element.nativeElement.id,
        1,
      );
      this.updateSpellsInDatabase(this.draggingIndex!);
    }
    this.draggingIndex = undefined;
  }

  private loadSpells(): void {
    this.level0 = this.dataAccessor.spellLevel0;
    this.level1 = this.dataAccessor.spellLevel1;
    this.level2 = this.dataAccessor.spellLevel2;
    this.level3 = this.dataAccessor.spellLevel3;
    this.level4 = this.dataAccessor.spellLevel4;
    this.level5 = this.dataAccessor.spellLevel5;
    this.level6 = this.dataAccessor.spellLevel6;
    this.level7 = this.dataAccessor.spellLevel7;
    this.level8 = this.dataAccessor.spellLevel8;
    this.level9 = this.dataAccessor.spellLevel9;
  }

  private hideEmptySpelllists(): void {
    this.showSpellList = [
      this.level0.length > 0,
      this.level1.length > 0,
      this.level2.length > 0,
      this.level3.length > 0,
      this.level4.length > 0,
      this.level5.length > 0,
      this.level6.length > 0,
      this.level7.length > 0,
      this.level8.length > 0,
      this.level9.length > 0,
    ];
  }

  private updateSpellsInDatabase(level: number): void {
    switch (level) {
      case 0:
        this.dataAccessor.spellLevel0 = this.level0;
        break;
      case 1:
        this.dataAccessor.spellLevel1 = this.level1;
        break;
      case 2:
        this.dataAccessor.spellLevel2 = this.level2;
        break;
      case 3:
        this.dataAccessor.spellLevel3 = this.level3;
        break;
      case 4:
        this.dataAccessor.spellLevel4 = this.level4;
        break;
      case 5:
        this.dataAccessor.spellLevel5 = this.level5;
        break;
      case 6:
        this.dataAccessor.spellLevel6 = this.level6;
        break;
      case 7:
        this.dataAccessor.spellLevel7 = this.level7;
        break;
      case 8:
        this.dataAccessor.spellLevel8 = this.level8;
        break;
      case 9:
        this.dataAccessor.spellLevel9 = this.level9;
        break;
    }
  }
}
