import { SYSTEM } from "../config/system.mjs"
import PrismRPGRoll from "../documents/roll.mjs"
import PrismRPGUtils from "../utils.mjs"

export default class PrismRPGCharacter 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
      }, {}),
    )

    // Sub-Attributes (derived from two parent characteristics)
    const subAttributeField = (label) => {
      const schema = {
        value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
      }
      return new fields.SchemaField(schema, { label })
    }
    schema.subAttributes = new fields.SchemaField(
      Object.values(SYSTEM.SUB_ATTRIBUTES).reduce((obj, subAttr) => {
        obj[subAttr.id] = subAttributeField(subAttr.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, min: 0 }),
      max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
      temp: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })

    schema.armorPoints = new fields.SchemaField({
      value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })

    schema.actionPoints = new fields.SchemaField({
      value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })

    schema.biodata = new fields.SchemaField({
      level: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }),
      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 })
    })

    schema.developmentPoints = new fields.SchemaField({
      total: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      remaining: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })
    schema.manaPoints = 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 }),
      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 }),
    })

    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
      }, {}),
    )

    // Sub-attribute choices for movement rating and burden selectors
    const subAttributeChoices = () =>
      Object.values(SYSTEM.SUB_ATTRIBUTES).reduce((obj, s) => { obj[s.id] = s.label; return obj }, {})

    schema.movementRating = new fields.SchemaField({
      subAttribute: new fields.StringField({ required: true, initial: "stamina", choices: subAttributeChoices }),
      other: new fields.NumberField({ ...requiredInteger, initial: 0 }),
      reduction: new fields.NumberField({ ...requiredInteger, initial: 0 })
    })

    schema.burden = new fields.SchemaField({
      subAttribute: new fields.StringField({ required: true, initial: "vigor", choices: subAttributeChoices }),
      other: new fields.NumberField({ ...requiredInteger, initial: 0 })
    })

    return schema
  }

  /** @override */
  static LOCALIZATION_PREFIXES = ["PRISMRPG.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();

    // Calculate action points max based on level
    const level = this.biodata.level
    let actionPointsMax = 4
    if (level >= 3 && level <= 5) {
      actionPointsMax = 5
    } else if (level >= 6 && level <= 8) {
      actionPointsMax = 6
    } else if (level >= 9 && level <= 10) {
      actionPointsMax = 7
    }
    // Set max action points (but don't override if already set to a higher value)
    if (this.actionPoints.max < actionPointsMax) {
      this.actionPoints.max = actionPointsMax
    }

    // Calculate sub-attributes from parent characteristics
    // Sub-attribute = lowest ability modifier between the two parent characteristics
    for (let subAttrKey in SYSTEM.SUB_ATTRIBUTES) {
      const subAttr = SYSTEM.SUB_ATTRIBUTES[subAttrKey]
      const parent1Value = this.characteristics[subAttr.parents[0]].value
      const parent2Value = this.characteristics[subAttr.parents[1]].value
      // Calculate D&D 5e style ability modifiers: (ability - 10) / 2
      const parent1Bonus = Math.floor((parent1Value - 10) / 2)
      const parent2Bonus = Math.floor((parent2Value - 10) / 2)
      // Take the lowest modifier
      this.subAttributes[subAttrKey].value = Math.min(parent1Bonus, parent2Bonus)
    }

    // Calculate save modifier locally (not stored)
    const saveModifier = Math.floor((Number(this.biodata.level) / 5))

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

    let dexDef = SYSTEM.CHARACTERISTICS_TABLES.dex.find(s => s.value === this.characteristics.dex.value)
    this.challenges.agility.value = dexDef.challenge

    let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find(s => s.value === this.characteristics.wis.value)

    let conDef = SYSTEM.CHARACTERISTICS_TABLES.con.find(s => s.value === this.characteristics.con.value)
    this.challenges.dying.value = conDef.stabilization_dice

    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.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) {
    const hasTarget = false
    let roll = await PrismRPGRoll.prompt({
      rollType,
      rollTarget,
      actorId: this.parent.id,
      actorName: this.parent.name,
      actorImage: this.parent.img,
      hasTarget,
      target: false
    })
    if (!roll) return null

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

  /**
   * Rolls initiative for the character: 1d20 + initiative modifier
   * @param {string} combatId - Optional combat ID to update
   * @param {string} combatantId - Optional combatant ID to update
   * @returns {Promise<Roll|null>} The initiative roll or null if cancelled
   */
  async rollInitiative(combatId = undefined, combatantId = undefined) {
    // Get the initiative sub-attribute modifier
    const initiativeModifier = this.subAttributes.initiative.value

    // Create the roll formula: 1d20 + initiative modifier
    const formula = `1d20 + ${initiativeModifier}`

    // Roll the initiative
    let initRoll = new Roll(formula)
    await initRoll.evaluate()

    // Create the chat message
    let msg = await initRoll.toMessage({
      flavor: `${game.i18n.localize("PRISMRPG.Label.initiative")} - ${this.parent.name}`,
      speaker: ChatMessage.getSpeaker({ actor: this.parent })
    })

    // Wait for 3D dice animation if enabled
    if (game?.dice3d) {
      await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
    }

    // Update the combatant's initiative if in combat
    if (combatId && combatantId) {
      let combat = game.combats.get(combatId)
      if (combat) {
        await combat.updateEmbeddedDocuments("Combatant", [{
          _id: combatantId,
          initiative: initRoll.total
        }])
      }
    }

    return initRoll
  }

}
