import { ACTOR_TYPES, ITEM_TYPES } from "../../constants.js"
import { Grammar } from "../../grammar.js"
import { RdDItemArme } from "../../item/arme.js"
import { CATEGORIES_COMPETENCE_COMBAT } from "../../item/base-items.js"
import { Misc } from "../../misc.js"
import { RdDTimestamp } from "../../time/rdd-timestamp.js"
import { PDFDocument } from "./pdf-lib/pdf-lib.esm.js"



const copyProperty = (actor, path) => foundry.utils.getProperty(actor, path)

// const findItem = (actor, itemType, itemName) => actor.itemTypes[itemType].find(it => Grammar.equalsInsensitive(formCompName(it.name), formCompName(itemName)))
// const findItemPos = (actor, itemType, pos) => actor.itemTypes[itemType].length <= pos ? actor.itemTypes[itemType][pos] : length
// const findProperty = (it, path) => it ? foundry.utils.getProperty(it, path) : undefined

// const findItemProperty = (actor, itemType, itemName, path) => findProperty(findItem(actor, itemType, itemName), path)
// const findItemPosProperty = (actor, itemType, pos, path) => findProperty(findItemPos(actor, itemType, pos), path)

// const findArmeProperty = (actor, pos, path) => findProperty(findItemPos(actor, ITEM_TYPES.arme, pos), path)
// const findSortProperty = (actor, pos, path) => findProperty(findItemPos(actor, ITEM_TYPES.sort, pos), path)

// const itemFormPath = (type, pos, property) => `${type}s.${pos}.${property}`


const ACTOR_TO_FORM_MAPPING = [
  { path: 'name' },
  { path: 'system.carac.taille.value' },
  { path: 'system.carac.apparence.value' },
  { path: 'system.carac.apparence.xp' },
  { path: 'system.carac.constitution.value' },
  { path: 'system.carac.constitution.xp' },
  { path: 'system.carac.force.value' },
  { path: 'system.carac.force.value' },
  { path: 'system.carac.force.xp' },
  { path: 'system.carac.agilite.value' },
  { path: 'system.carac.agilite.xp' },
  { path: 'system.carac.dexterite.value' },
  { path: 'system.carac.dexterite.xp' },
  { path: 'system.carac.vue.value' },
  { path: 'system.carac.vue.xp' },
  { path: 'system.carac.ouie.value' },
  { path: 'system.carac.ouie.xp' },
  { path: 'system.carac.odoratgout.value' },
  { path: 'system.carac.odoratgout.xp' },
  { path: 'system.carac.volonte.value' },
  { path: 'system.carac.volonte.xp' },
  { path: 'system.carac.empathie.value' },
  { path: 'system.carac.empathie.xp' },
  { path: 'system.carac.intellect.value' },
  { path: 'system.carac.intellect.xp' },
  { path: 'system.carac.reve.value' },
  { path: 'system.carac.reve.xp' },
  { path: 'system.carac.chance.value' },
  { path: 'system.carac.chance.xp' },

  { path: 'system.age' },
  { path: 'system.sexe' },
  { path: 'system.taille' },
  { path: 'system.poids' },
  { path: 'system.cheveux' },
  { path: 'system.yeux' },
  { path: 'system.beaute' },
  { path: 'system.main' },
  { path: 'system.heure' },
  { path: 'computed.hn.heure', getter: actor => (RdDTimestamp.definition(actor.system.heure)?.heure ?? 0) + 1 },
  { path: 'computed.hn.label', getter: actor => RdDTimestamp.definition(actor.system.heure)?.avecArticle },

  { path: 'system.carac.melee.value' },
  { path: 'system.carac.tir.value' },
  { path: 'system.carac.lancer.value' },
  { path: 'system.carac.derobee.value' },
  { path: 'system.sante.vie.value' },
  { path: 'system.sante.endurance.value' },

  { path: 'system.attributs.sust.value' },
  { path: 'system.attributs.sconst.value' },
  { path: 'system.attributs.encombrement.value' },
  { path: 'system.attributs.plusdom.value', getter: actor => Misc.toSignedString(actor.system.attributs.plusdom.value) },

  // , getter: actor => actor.get
]

export default class ExportPdf {

  static init() {
    Hooks.on("getActorContextOptions", (actorDirectory, menus) => { ExportPdf.onActorDirectoryMenu(actorDirectory, menus) })
  }

  static onActorDirectoryMenu(actorDirectory, menus) {
    menus.push({
      name: 'Export PDF',
      icon: '<i class="fa-regular fa-file-pdf"></i>',
      condition: target => actorDirectory.id == 'actors' && ExportPdf.$isActorPersonnage(this.$getActor(target)),
      callback: async target => await ExportPdf.exportActor(target)
    })
  }

  static $getActor(target) {
    const entryId = $(target).closest(".directory-item")?.data("entryId")
    return game.actors.get(entryId)
  }

  static $isActorPersonnage(actor) {
    return actor?.type == ACTOR_TYPES.personnage
  }

  static async exportActor(target) {
    const actor = ExportPdf.$getActor(target)
    if (!ExportPdf.$isActorPersonnage(actor)) {
      ui.notifications.error("Pas de personnage sélectionné")
      return
    }
    const templatePdf = '/systems/foundryvtt-reve-de-dragon/assets/feuille-personnage.pdf';

    const pdfBytes = await fetch(templatePdf).then(res => res.arrayBuffer())
    const pdfDoc = await PDFDocument.load(pdfBytes)

    const exporter = new ExportPdf(actor, pdfDoc)
    exporter.generateFeuillePersonnage()
  }

  constructor(actor, pdfDoc) {
    this.pdfDoc = pdfDoc
    this.form = this.pdfDoc.getForm()
    this.actor = actor
    this.allComps = this.actor.itemTypes[ITEM_TYPES.competence]
    this.comps = this.allComps
      .filter(it => !it.isNiveauBase())
      .sort(Misc.ascending(it => it.name))

    this.compsNonArmes = this.comps.filter(it => !ExportPdf.isCompCombat(it))
    this.compsArmes = this.comps.filter(it => ExportPdf.isCompCombat(it))
    this.addedComps = new Set([])
  }

  static isCompCombat(comp) {
    return CATEGORIES_COMPETENCE_COMBAT.includes(comp.system.categorie) && !Grammar.includesLowerCaseNoAccent(comp.name, "corps à corps") && !Grammar.includesLowerCaseNoAccent(comp.name, "esquive")
  }

  async generateFeuillePersonnage() {

    this.$exportActorFields()
    this.$exportCompetences()
    this.$exportArchetype()
    this.$exportArmes()
    this.$exportSorts()

    const pdfBytes = await this.pdfDoc.save();
    const filename = `rdd-${this.actor.name.slugify()}.pdf`;
    foundry.utils.saveDataToFile(pdfBytes, "application/pdf", filename);
  }

  $exportActorFields() {
    ACTOR_TO_FORM_MAPPING.forEach(async (mapping) => {
      const path = mapping.path
      const value = mapping.getter ? mapping.getter(this.actor) : copyProperty(this.actor, path)
      this.$setFormValue(path, value)
    })
  }

  $exportCompetences() {
    this.compsNonArmes
      .filter(it => !this.addedComps.has(it.id))
      .forEach(comp => {
        const formCompName = Grammar.toLowerCaseNoAccent(comp.name.replaceAll(/(\s|-|\')/g, '_'))
        this.$setFormCompetence(formCompName, comp)
      })
    const musique = this.compsNonArmes.filter(it => Grammar.includesLowerCaseNoAccent(it.name, 'musique'))
      .sort(Misc.descending(it => it.system.niveau))
      .filter(it => !this.addedComps.has(it.id))
      .find(it => true)
    if (musique) {
      this.$setFormCompetence('musique', musique)
    }
  }

  $exportArchetype() {
    this.allComps.sort(Misc.ascending(it => it.system.niveau_archetype))
      .forEach(comp => {
        let formCompName = Grammar.toLowerCaseNoAccent(comp.name.replaceAll(/(\s|-|\')/g, '_'))
        if (formCompName.includes('musique')) {
          formCompName = 'musique'
        }
        this.$setFormValue(`competences.${formCompName}.niveau_archetype`, comp.system.niveau_archetype)
      })
  }

  $setFormCompetenceArchetype(formCompName, comp, baseFormName = 'competences') {
  }

  $exportArmes() {
    const uniques = new Set([])
    const armes = this.actor.itemTypes[ITEM_TYPES.arme].map(arme => arme.getTypeAttaques()
      .map(main => {
        const compName = arme.getCompetenceAction(main)
        const dommages = RdDItemArme.valeurMain(arme.system.dommages, main)
        const forceRequise = RdDItemArme.valeurMain(arme.system.force ?? 0, main)
        const comp = this.compsArmes.find(it => Grammar.equalsInsensitive(it.name, compName)) ?? this.actor.findItemLike(compName, ITEM_TYPES.competence)
        const unique = [comp.id, arme.name, dommages, forceRequise].join('|');
        if (uniques.has(unique)) {
          return undefined
        }
        uniques.add(unique)
        return { arme: arme, comp: comp, main: main }
      }))
      .reduce((a, b) => a.concat(b))
      .filter(it => it != undefined && !it.comp.isNiveauBase())
      .sort(Misc.descending(it => it.comp.niveau))

    for (let pos = 0; pos < armes.length; pos++) {
      const it = armes[pos]
      this.$setFormArmeCompetence(pos, it.comp, it.arme, it.main, it.main)
    }

    // TODO: list comps without weapons
    // TODO: list other comps not in the standard list -- use an instance of ExportPdf to hold state/built list
    const otherComps = this.comps.filter(it => !this.addedComps.has(it.id))
    for (let pos = 0; pos < otherComps.length; pos++) {
      const comp = otherComps[pos]
      this.$setFormCompetence(pos, comp, 'competences')
      this.$setFormValue(`competences.${pos}.name`, comp.name)
    }
  }

  $setFormCompetence(formCompName, comp, baseFormName = 'competences') {

    if (this.form.getFieldMaybe(`${baseFormName}.${formCompName}.niveau`)) {
      this.addedComps.add(comp.id)
    }
    if (comp.system.niveau != comp.system.base) {
      this.$setFormValue(`${baseFormName}.${formCompName}.niveau`, comp.system.niveau)
    }
    if (comp.system.xp > 0) {
      this.$setFormValue(`${baseFormName}.${formCompName}.xp`, comp.system.xp)
    }
    if (comp.system.xp_sort > 0) {
      this.$setFormValue(`${baseFormName}.${formCompName}.xp_sort`, comp.system.xp_sort)
    }
    if (CATEGORIES_COMPETENCE_COMBAT.includes(comp.system.categorie)) {
      this.$setFormValue(`${baseFormName}.${formCompName}.init`, comp.getBaseInit())
    }
  }

  $setFormArmeCompetence(pos, comp, arme, main) {
    this.$setFormCompetence(pos, comp, 'armes')
    this.$setFormValue(`armes.${pos}.name`, arme.name)
    this.$setFormValue(`armes.${pos}.main`, main)
    this.$setFormValue(`armes.${pos}.plusdom`, RdDItemArme.valeurMain(arme.system.dommages, main))
  }

  $exportSorts() {
    const sorts = this.actor.itemTypes[ITEM_TYPES.sort].sort(Misc.ascending(s => ExportPdf.$orderDraconic(s) + s.name))

    for (let pos = 0; pos < sorts.length; pos++) {
      const sort = sorts[pos]
      this.$setFormSort(pos, sort)
    }
  }

  $setFormSort(pos, sort) {
    this.$setFormValue(`sorts.${pos}.name`, sort.name)
    this.$setFormValue(`sorts.${pos}.voie`, sort.system.draconic)
    this.$setFormValue(`sorts.${pos}.tmr`, sort.system.caseTMRSpeciale ?? sort.system.caseTMR)
    this.$setFormValue(`sorts.${pos}.diff`, sort.system.difficulte)
    this.$setFormValue(`sorts.${pos}.reve`, sort.system.ptreve)
    this.$setFormValue(`sorts.${pos}.bonuscase`, sort.system.bonuscase)
  }

  static $orderDraconic(s) {
    switch (s.system.draconic.substring(0, 1)) {
      case 'O': return 1
      case 'H': return 2
      case 'N': return 3
      case 'T': return 4
    }
    return 5
  }

  $setFormValue(path, value) {
    const hasField = this.form.getFieldMaybe(path)
    if (hasField && value != undefined) {
      const field = this.form.getTextField(path)
      field.setText(value.toString())
    }
  }
}

