import { SYSTEM } from "../config/system.mjs"
import LethalFantasyRoll from "../documents/roll.mjs"
import LethalFantasyUtils from "../utils.mjs"

export default class LethalFantasyCharacter extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const fields = foundry.data.fields
    const requiredInteger = { required: true, nullable: false, integer: true }
    const schema = {}

    schema.description = new fields.HTMLField({ required: true, textSearch: true })
    schema.notes = new fields.HTMLField({ required: true, textSearch: true })

    // Carac
    const characteristicField = (label) => {
      const schema = {
        value: new fields.NumberField({ ...requiredInteger, initial: 3, min: 0 }),
        percent: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 100 }),
        attackMod: new fields.NumberField({ ...requiredInteger, initial: 0 }),
        defenseMod: new fields.NumberField({ ...requiredInteger, initial: 0 })
      }
      return new fields.SchemaField(schema, { label })
    }

    schema.characteristics = new fields.SchemaField(
      Object.values(SYSTEM.CHARACTERISTICS).reduce((obj, characteristic) => {
        obj[characteristic.id] = characteristicField(characteristic.label)
        return obj
      }, {}),
    )

    // Save
    const saveField = (label) => {
      const schema = {
        value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
      }
      return new fields.SchemaField(schema, { label })
    }
    schema.saves = new fields.SchemaField(
      Object.values(SYSTEM.SAVES).reduce((obj, save) => {
        obj[save.id] = saveField(save.label)
        return obj
      }, {}),
    )

    // Challenges
    const challengeField = (label) => {
      const schema = {
        value: new fields.StringField({ initial: "0", required: true, nullable: false }),
      }
      return new fields.SchemaField(schema, { label })
    }
    schema.challenges = new fields.SchemaField(
      Object.values(SYSTEM.CHALLENGES).reduce((obj, save) => {
        obj[save.id] = challengeField(save.label)
        return obj
      }, {}),


    )
    const woundFieldSchema = {
      value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      duration: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      description: new fields.StringField({ initial: "", required: false, nullable: true }),
    }

    schema.hp = new fields.SchemaField({
      value: new fields.NumberField({ ...requiredInteger, initial: 1 }),
      max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
      painDamage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      wounds: new fields.ArrayField(new fields.SchemaField(woundFieldSchema), {
        initial: [{ description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 },
        { description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 },
        { description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }], min: 8
      }),
      damageResistance: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })

    schema.perception = new fields.SchemaField({
      value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      bonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })
    schema.grit = new fields.SchemaField({
      starting: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      earned: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      current: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })
    schema.luck = new fields.SchemaField({
      earned: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      current: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })
    schema.granted = new fields.SchemaField({
      attackDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES }),
      defenseDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES }),
      damageDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES })
    })

    schema.movement = new fields.SchemaField({
      walk: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      jog: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      sprint: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      run: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      armorAdjust: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
    })
    schema.jump = new fields.SchemaField({
      broad: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      running: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      vertical: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
    })
    schema.biodata = new fields.SchemaField({
      class: new fields.StringField({ required: true, initial: "untrained", choices: SYSTEM.CHAR_CLASSES }),
      level: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }),
      mortal: new fields.StringField({ required: true, initial: "mankind", choices: SYSTEM.MORTAL_CHOICES }),
      alignment: new fields.StringField({ required: true, nullable: false, initial: "" }),
      age: new fields.NumberField({ ...requiredInteger, initial: 15, min: 6 }),
      height: new fields.NumberField({ ...requiredInteger, initial: 170, min: 10 }),
      weight: new fields.StringField({ required: true, nullable: false, initial: "" }),
      eyes: new fields.StringField({ required: true, nullable: false, initial: "" }),
      hair: new fields.StringField({ required: true, nullable: false, initial: "" }),
      magicUser: new fields.BooleanField({ initial: false }),
      clericUser: new fields.BooleanField({ initial: false }),
      hpPerLevel: new fields.StringField({ required: true, nullable: false, initial: "" }),
      naturalDR: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      magicDR: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
    })

    schema.modifiers = new fields.SchemaField({
      levelSpellModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      saveModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      levelMiracleModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      intSpellModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      chaMiracleModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
    })

    schema.developmentPoints = new fields.SchemaField({
      total: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      remaining: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })
    schema.spellMiraclePoints = new fields.SchemaField({
      total: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      used: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })
    schema.aetherPoints = new fields.SchemaField({
      max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })
    schema.divinityPoints = new fields.SchemaField({
      max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })
    schema.combat = new fields.SchemaField({
      attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      rangedAttackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      rangedDefenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      defenseBonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      armorHitPoints: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      combatProgressionStart: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }),
    })

    const moneyField = (label) => {
      const schema = {
        value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
      }
      return new fields.SchemaField(schema, { label })
    }
    schema.moneys = new fields.SchemaField(
      Object.values(SYSTEM.MONEY).reduce((obj, save) => {
        obj[save.id] = moneyField(save.label)
        return obj
      }, {}),
    )

    return schema
  }

  /** @override */
  static LOCALIZATION_PREFIXES = ["LETHALFANTASY.Character"]

  static migrateData(data) {
    if (data?.biodata?.mortal) {
      if (!SYSTEM.MORTAL_CHOICES[data.biodata.mortal]) {
        for (let key in SYSTEM.MORTAL_CHOICES) {
          let mortal = SYSTEM.MORTAL_CHOICES[key]
          if (mortal.label.toLowerCase() === data.biodata.mortal.toLowerCase()) {
            data.biodata.mortal = mortal.id
          }
          if (data.biodata.mortal.toLowerCase().includes("shire")) {
            data.biodata.mortal = "halflings"
          }
          if (data.biodata.mortal.toLowerCase().includes("human")) {
            data.biodata.mortal = "mankind"
          }
        }
      }
      if (!SYSTEM.MORTAL_CHOICES[data.biodata.mortal]) {
        console.warn("Lethal Fantasy | Migrate data: Mortal not found, forced to mankind", data.biodata.mortal)
        data.biodata.mortal = "mankind"
      }
    }

    return super.migrateData(data)
  }

  prepareDerivedData() {
    super.prepareDerivedData();
    let grit = 0
    for (let c in this.characteristics) {
      if (SYSTEM.CHARACTERISTICS_MAJOR[c.id]) {
        grit += this.characteristics[c].value
      }
    }

    this.modifiers.saveModifier = Math.floor((Number(this.biodata.level) / 5))
    this.modifiers.levelSpellModifier = Math.floor((Number(this.biodata.level) / 5))
    this.modifiers.levelMiracleModifier = Math.floor((Number(this.biodata.level) / 5))

    this.grit.starting = Math.round(grit / 6)

    let strDef = SYSTEM.CHARACTERISTICS_TABLES.str.find(s => s.value === this.characteristics.str.value)
    this.challenges.str.value = strDef.challenge

    let intDef = SYSTEM.CHARACTERISTICS_TABLES.int.find(s => s.value === this.characteristics.int.value)
    this.modifiers.intSpellModifier = intDef.arkane_casting_mod

    let dexDef = SYSTEM.CHARACTERISTICS_TABLES.dex.find(s => s.value === this.characteristics.dex.value)
    this.challenges.agility.value = dexDef.challenge
    this.saves.dodge.value = dexDef.dodge + this.modifiers.saveModifier

    let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find(s => s.value === this.characteristics.wis.value)
    this.saves.will.value = wisDef.willpower_save + this.modifiers.saveModifier

    let chaDef = SYSTEM.CHARACTERISTICS_TABLES.cha.find(s => s.value === this.characteristics.cha.value)
    this.modifiers.chaMiracleModifier = chaDef.divine_miracle_bonus

    let conDef = SYSTEM.CHARACTERISTICS_TABLES.con.find(s => s.value === this.characteristics.con.value)
    this.saves.pain.value = conDef.pain_save + this.modifiers.saveModifier
    this.saves.toughness.value = conDef.toughness_save + this.modifiers.saveModifier
    this.challenges.dying.value = conDef.stabilization_dice

    this.saves.contagion.value = this.characteristics.con.value;// + this.modifiers.saveModifier
    this.saves.poison.value = this.characteristics.con.value; // + this.modifiers.saveModifier

    this.combat.attackModifier = 0
    for (let chaKey of SYSTEM.CHARACTERISTIC_ATTACK) {
      let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
      this.combat.attackModifier += chaDef.attack
    }
    this.combat.rangedAttackModifier = 0
    for (let chaKey of SYSTEM.CHARACTERISTIC_RANGED_ATTACK) {
      let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
      this.combat.rangedAttackModifier += chaDef.attack
    }

    this.combat.defenseBonus = SYSTEM.MORTAL_CHOICES[this.biodata.mortal]?.defenseBonus || 0
    this.combat.defenseModifier = this.combat.defenseBonus
    for (let chaKey of SYSTEM.CHARACTERISTIC_DEFENSE) {
      let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
      this.combat.defenseModifier += chaDef.defense
    }

    this.combat.rangedDefenseModifier = this.combat.defenseBonus
    for (let chaKey of SYSTEM.CHARACTERISTIC_RANGED_DEFENSE) {
      let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
      this.combat.rangedDefenseModifier += chaDef.defense
    }

    this.combat.damageModifier = 0
    for (let chaKey of SYSTEM.CHARACTERISTIC_DAMAGE) {
      let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
      this.combat.damageModifier += chaDef.damage
    }

  }

  /**
   * Rolls a dice for a character.
   * @param {("save"|"resource|damage")} rollType The type of the roll.
   * @param {number} rollTarget The target value for the roll. Which caracteristic or resource. If the roll is a damage roll, this is the id of the item.
   * @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=).
   * @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
   */
  async roll(rollType, rollTarget, defenderId, defenderTokenId, extraShieldDr = 0, d30Effects = {}) {
    const hasTarget = false
    let roll = await LethalFantasyRoll.prompt({
      rollType,
      rollTarget,
      actorId: this.parent.id,
      actorName: this.parent.name,
      actorImage: this.parent.img,
      hasTarget,
      target: false,
      defenderId,
      defenderTokenId,
      extraShieldDr,
      damageTier: rollTarget.damageTier || "standard",
      d30Bleed: d30Effects.d30Bleed || false,
      d30DamageMultiplier: d30Effects.d30DamageMultiplier || 1,
      d30DrMultiplier: d30Effects.d30DrMultiplier || 1
    })
    if (!roll) return null

    await roll.toMessage({}, { messageMode: roll.options.rollMode })
  }

  async rollInitiative(combatId = undefined, combatantId = undefined) {
    const hasTarget = false
    let actorClass = this.biodata.class;

    let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find((c) => c.value === this.characteristics.wis.value)
    let maxInit = Number(wisDef.init_cap) || 1000

    let roll = await LethalFantasyRoll.promptInitiative({
      actorId: this.parent.id,
      actorName: this.parent.name,
      actorImage: this.parent.img,
      combatId,
      combatantId,
      actorClass,
      maxInit,
    })
    if (!roll) return null

    await roll.toMessage({}, { messageMode: roll.options.rollMode })
  }

  async rollProgressionDice(combatId, combatantId, rollProgressionCount) {
    let combatant = game.combats.get(combatId)?.combatants?.get(combatantId)

    // Don't roll if the combatant is defeated
    if (combatant?.isDefeated) {
      ui.notifications.warn(`${this.parent.name} is defeated and cannot attack.`)
      return
    }

    // Get all weapons from the actor
    let weapons = this.parent.items.filter(i => i.type === "weapon" && i.system.weaponType === "melee")
    let weaponsChoices = weapons.map(w => { return { id: w.id, name: `${w.name} (${w.system.combatProgressionDice.toUpperCase()})`, combatProgressionDice: w.system.combatProgressionDice.toUpperCase() } })
    let rangeWeapons = this.parent.items.filter(i => i.type === "weapon" && i.system.weaponType === "ranged")
    for (let w of rangeWeapons) {
      weaponsChoices.push({ id: `${w.id}simpleAim`, name: `${w.name} (Simple Aim: ${w.system.speed.simpleAim.toUpperCase()})`, combatProgressionDice: w.system.speed.simpleAim.toUpperCase() })
      weaponsChoices.push({ id: `${w.id}carefulAim`, name: `${w.name} (Careful Aim: ${w.system.speed.carefulAim.toUpperCase()})`, combatProgressionDice: w.system.speed.carefulAim.toUpperCase() })
      weaponsChoices.push({ id: `${w.id}focusedAim`, name: `${w.name} (Focused Aim: ${w.system.speed.focusedAim.toUpperCase()})`, combatProgressionDice: w.system.speed.focusedAim.toUpperCase() })
    }
    if (this.biodata.magicUser || this.biodata.clericUser) {
      let spells = this.parent.items.filter(i => i.type === "spell" || i.type === "miracle")
      for (let s of spells) {
        let title = ""
        let formula = ""
        if (s.type === "spell") {
          let dice = LethalFantasyUtils.getLethargyDice(s.system.level)
          title = `${s.name} (Casting time: ${s.system.castingTime}, Lethargy: ${dice})`
          formula = `${s.system.castingTime}+${dice}`
        } else {
          title = `${s.name} (Prayer time: ${s.system.prayerTime})`
          formula = `${s.system.prayerTime}`
        }
        weaponsChoices.push({ id: s.id, name: title, combatProgressionDice: formula })
      }
    }

    if (weaponsChoices.length === 0) {
      ui.notifications.warn(`${this.parent.name} has no weapons or spells available for combat. Add a weapon to the character sheet first.`)
      return
    }

    let roll = await LethalFantasyRoll.promptCombatAction({
      actorId: this.parent.id,
      actorName: this.parent.name,
      actorImage: this.parent.img,
      weaponsChoices,
      combatId,
      combatantId,
      rollProgressionCount,
      type: "progression",

    })
  }

}
