import { Component } from '@angular/core';
import { DataService } from 'src/services/data/data.service';
import { ModalService } from 'src/services/modal/modal.service';
import { DetailsService } from 'src/services/details/details.service';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Spell } from 'src/interfaces/spell';
import { SpellDetailsComponent } from './spell-details/spell-details.component';
import { SpellModalComponent } from 'src/app/journal/spell-modal/spell-modal.component';
import { FullSpellcardComponent } from 'src/app/shared-components/full-spellcard/full-spellcard.component';
import { Observable, OperatorFunction } from 'rxjs';
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';

@Component({
  selector: 'spell-table',
  templateUrl: './spell-table.component.html',
  styleUrls: ['./spell-table.component.scss'],
})
export class SpellTableComponent {
  public spellAttackBonus: string = '0';
  public spellSaveDC: number = 0;
  private spellcastingAttribute: string | undefined;
  private spellcastingAttributeModifier: number = 0;
  private proficiencyBonus: number = 2;

  public spells!: Spell[];
  private preparedSpells!: Spell[];
  private preparedSpellsNames: string[] = [];
  public newSpellName: string = '';
  public showInput: boolean = false;

  public attributes: any = {
    strength: 'STR',
    dexterity: 'DEX',
    constitution: 'CON',
    intelligence: 'INT',
    wisdom: 'WIS',
    charisma: 'CHA',
  };

  public areas: any = {
    cone: 'Kegel',
    sphere: 'Kugel',
    circle: 'Kreis',
    line: 'Linie',
    square: 'Quadrat',
    cube: 'Würfel',
  };

  public constructor(
    public dataAccessor: DataService,
    private modalAccessor: ModalService,
    public detailsAccessor: DetailsService
  ) {}

  public ngOnInit(): void {
    this.spells = this.dataAccessor.favoriteSpells;
    this.preparedSpells = this.dataAccessor.getAllPreparedSpells();
    this.preparedSpellsNames = this.preparedSpells.map((spell) => spell.name);
    this.subscribeToData();
  }

  public showFullSpellcard(spellIndex: number): void {
    this.modalAccessor.openModal(FullSpellcardComponent, {
      spell: this.spells[spellIndex],
      isFromDashboard: true,
    });
    const resultSubscription = this.modalAccessor.result$.subscribe(
      (result) => {
        resultSubscription.unsubscribe();
        if (result.state === 'delete') {
          this.spells.splice(spellIndex, 1);
          this.updateSpellsInDatabase();
        } else if (result.state !== 'cancel') {
          throw new Error('Unexpected result state, please send a bug report.');
        }
      }
    );
  }

  // LEGACY CODE
  public openDetailsPanel(index: number): void {
    this.detailsAccessor.openPanel(SpellDetailsComponent, {
      spell: this.spells[index],
      modifiers: {
        attackBonus: this.spellAttackBonus,
        saveDC: this.spellSaveDC,
      },
    });
    const resultSubscription = this.detailsAccessor.result$.subscribe(
      (result) => {
        if (result.state === 'delete') {
          this.deleteSpell(index);
        } else if (result.state === 'update') {
          this.openModal(true, index);
        } else {
          throw new Error('DND-Error: Unknown state: ' + result.state);
        }
        resultSubscription.unsubscribe();
      }
    );
  }
  //

  public openModal(isUpdate: boolean, index?: number): void {
    this.modalAccessor.openModal(SpellModalComponent, {
      spell:
        index !== undefined
          ? JSON.parse(JSON.stringify(this.spells[index]))
          : undefined,
      isUpdate: isUpdate,
    });
    const resultSubscription = this.modalAccessor.result$.subscribe(
      (result) => {
        if (result.state === 'update') {
          this.updateSpell(result.data, index!);
        } else if (result.state === 'add') {
          this.addSpell(result.data);
        } else {
          throw new Error('DND-Error: Unknown state: ' + result.state);
        }
        resultSubscription.unsubscribe();
      }
    );
  }

  public toggleInput(): void {
    this.showInput = !this.showInput;
    this.newSpellName = '';
  }

  public onSpellSelect(spellname: any): void {
    const newSpell = this.preparedSpells.filter(
      (spell) => spell.name === spellname
    );
    if (newSpell.length !== 1) {
      throw new Error('Spell not found.');
    } else {
      this.addSpell(newSpell[0]);
    }
    this.newSpellName = '';
  }

  public addSpell(spell: Spell) {
    this.spells.push(spell);
    this.updateSpellsInDatabase();
  }

  public updateSpell(spell: Spell, index: number): void {
    this.spells[index] = spell;
    this.updateSpellsInDatabase();
  }

  public deleteSpell(index: number): void {
    this.spells.splice(index, 1);
    this.updateSpellsInDatabase();
  }

  // utils

  public dropSpells(event: CdkDragDrop<string[]>): void {
    moveItemInArray(this.spells, event.previousIndex, event.currentIndex);
    this.updateSpellsInDatabase();
  }

  public updateSpellsInDatabase(): void {
    this.dataAccessor.favoriteSpells = this.spells;
  }

  private computeSpellAttackBonusAndSaveDC(): void {
    let attackBonus =
      this.spellcastingAttributeModifier + this.proficiencyBonus;
    this.spellSaveDC = 8 + attackBonus;
    if (attackBonus >= 0) {
      this.spellAttackBonus = '+' + attackBonus;
    } else {
      this.spellAttackBonus = attackBonus.toString();
    }
  }

  private subscribeToData(): void {
    // TODO: this.dataAccessor.getSpellcastingAttribute() oder so
    this.spellcastingAttribute = 'wisdom';

    this.dataAccessor.proficiency$.subscribe((value) => {
      this.proficiencyBonus = value;
      if (this.spellcastingAttribute) {
        this.computeSpellAttackBonusAndSaveDC();
      }
    });
    // TODO: Modify depending on the actual spellcasting attribute
    this.dataAccessor.wisdom$.subscribe((value) => {
      this.spellcastingAttributeModifier = Math.floor((value.value - 10) / 2);
      this.computeSpellAttackBonusAndSaveDC();
    });
  }

  public search: OperatorFunction<string, readonly string[]> = (
    text$: Observable<string>
  ) =>
    text$.pipe(
      debounceTime(200),
      distinctUntilChanged(),
      map((term) =>
        term.length < 2
          ? []
          : this.preparedSpellsNames
              .filter((v) => v.toLowerCase().indexOf(term.toLowerCase()) > -1)
              .filter(
                (v) =>
                  !this.spells.some(
                    (spell) => spell.name.toLowerCase() === v.toLowerCase()
                  )
              )
              .slice(0, 5)
      )
    );
}
