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

export default class LethalFantasyMonster 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.MONSTER_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.MONSTER_SAVES).reduce((obj, save) => {
        obj[save.id] = saveField(save.label)
        return obj
      }, {}),
    )

    // Resist
    const resistField = (label) => {
      const schema = {
        value: new fields.StringField({ initial: "0", required: true, nullable: false }),
      }
      return new fields.SchemaField(schema, { label })
    }
    schema.resists = new fields.SchemaField(
      Object.values(SYSTEM.MONSTER_RESIST).reduce((obj, save) => {
        obj[save.id] = resistField(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 }),
      average: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
      max: new fields.NumberField({ ...requiredInteger, initial: 1, 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 }),
      painDamage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
    })

    const attackField = (label, initialNoExplode = false) => {
      const schema = {
        key: new fields.StringField({ required: true, nullable: false, initial: `attack${label}` }),
        name: new fields.StringField({ required: true, nullable: false, initial: `Attack ${label}` }),
        attackScore: new fields.NumberField({ ...requiredInteger, initial: Number(label), min: 0 }),
        attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
        defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
        damageDice: new fields.StringField({ required: true, nullable: false, initial: "1D6" }),
        damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
        enabled: new fields.BooleanField({ initial: true, required: true, nullable: false }),
        noExplode: new fields.BooleanField({ initial: initialNoExplode, required: true, nullable: false }),
      }
      return new fields.SchemaField(schema, { label })
    }
    // Add 4 attackFields in an attack schema
    schema.attacks = new fields.SchemaField({
      attack1: attackField("1"),
      attack2: attackField("2"),
      attack3: attackField("3"),
      attack4: attackField("4"),
      attack5: attackField("5"),
      attack6: attackField("6"),
      attack7: attackField("7"),
      attack8: attackField("8")
    })

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

    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({
      alignment: new fields.StringField({ required: true, nullable: false, initial: "" }),
      vision: new fields.StringField({ required: true, nullable: false, initial: "" }),
      height: new fields.StringField({ required: true, nullable: false, initial: "" }),
      length: new fields.StringField({ required: true, nullable: false, initial: "" }),
      weight: new fields.StringField({ required: true, nullable: false, initial: "" })
    })
    schema.combat = new fields.SchemaField({
      attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      defenseModifier: 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 }),
      damageReduction: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      shieldDamageReduction: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
      shieldDefenseDice: new fields.StringField({ required: true, nullable: false, initial: "d4" })
    })
    schema.combatHTH = new fields.SchemaField({
      attack1: attackField("1", true),
      attack2: attackField("2", true)
    })

    schema.attackMode = new fields.StringField({
      required: true,
      nullable: false,
      initial: "melee",
      choices: { melee: "Melee", ranged: "Ranged" }
    })

    schema.rangedAttacks = new fields.SchemaField({
      attack1: attackField("1"),
      attack2: attackField("2"),
      attack3: attackField("3"),
      attack4: attackField("4")
    })

    schema.rangedWeaponRange = new fields.SchemaField({
      pointBlank: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
      short: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
      medium: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
      long: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
      extreme: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
      outOfSkill: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
    })

    return schema
  }

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

  /**
   * 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 = undefined, defenderTokenId = undefined, extraShieldDr = 0, d30Effects = {}) {
    const hasTarget = false

    // Ranged monster defense uses the ranged defense dialog (movement, range, size modifiers)
    if (rollType === "monster-defense" && rollTarget?.isRangedDefense === true) {
      let roll = await LethalFantasyRoll.promptRangedDefense({
        actorId: this.parent.id,
        actorName: this.parent.name,
        actorImage: this.parent.img,
        rollTarget,
        defenderId,
        defenderTokenId,
      })
      if (!roll) return null
      await roll.toMessage({}, { messageMode: roll.options.rollMode })
      return
    }

    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 prepareMonsterRoll(rollType, rollKey, rollDice = undefined, tokenId = undefined, damageModifier = undefined, defenderId = undefined, defenderTokenId = undefined, extraShieldDr = 0, d30Effects = {}) {
    let rollTarget
    switch (rollType) {
      case "monster-attack":
      case "monster-defense":
      case "monster-damage": {
        const attacksSet = this.attackMode === "ranged" ? this.rangedAttacks : this.attacks
        rollTarget = foundry.utils.duplicate(attacksSet[rollKey])
        rollTarget.rollKey = rollKey
        rollTarget.attackMode = this.attackMode
        if (rollType === "monster-defense") {
          rollTarget.isRangedDefense = game.lethalFantasy?.nextDefenseData?.isRanged ?? false
        }
        // Si damageModifier est fourni (depuis le chat), l'utiliser au lieu de celui de la fiche
        if (damageModifier !== undefined && rollType === "monster-damage") {
          rollTarget.damageModifier = damageModifier
        }
        break
      }
      case "monster-attack-hth":
      case "monster-defense-hth":
      case "monster-damage-hth":
        rollTarget = foundry.utils.duplicate(this.combatHTH[rollKey])
        rollTarget.rollKey = rollKey
        // Si damageModifier est fourni (depuis le chat), l'utiliser au lieu de celui de la fiche
        if (damageModifier !== undefined && rollType === "monster-damage-hth") {
          rollTarget.damageModifier = damageModifier
        }
        // Convertir le type de roll pour utiliser les mêmes handlers que les attaques normales
        rollType = rollType.replace("-hth", "")
        break
      case "monster-skill":
        rollTarget = foundry.utils.duplicate(this.resists[rollKey])
        rollTarget.rollKey = rollKey
        break
      case "save":
        rollTarget = foundry.utils.duplicate(this.saves[rollKey])
        rollTarget.rollKey = rollKey
        rollTarget.rollDice = rollDice
        break
      case "shield-defense": {
        // Lance directement le dé de défense du bouclier
        const formula = rollDice || this.combat.shieldDefenseDice
        const roll = new Roll(formula)
        await roll.evaluate()

        const flavor = game.i18n.localize("LETHALFANTASY.Label.shieldDefenseDice")
        await roll.toMessage({
          flavor,
          speaker: ChatMessage.getSpeaker({ actor: this.parent })
        }, { messageMode: roll.options.rollMode ?? game.settings.get("core", "rollMode") })
        return
      }
      case "weapon-damage":
      case "weapon-attack":
      case "weapon-defense": {
        let weapon = this.actor.items.find((i) => i.type === "weapon" && i.id === rollKey)
        let skill
        let skills = this.actor.items.filter((i) => i.type === "skill" && i.name.toLowerCase() === weapon.name.toLowerCase())
        if (skills.length > 0) {
          skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
        } else {
          skills = this.actor.items.filter((i) => i.type === "skill" && i.name.toLowerCase().replace(" skill", "") === weapon.name.toLowerCase())
          if (skills.length > 0) {
            skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
          } else {
            skills = this.actor.items.filter((i) => i.type === "skill" && i.system.weaponClass === weapon.system.weaponClass)
            if (skills.length > 0) {
              skill = this.getBestWeaponClassSkill(skills, rollType, 0.5)
            } else {
              skills = this.actor.items.filter((i) => i.type === "skill" && i.system.weaponClass.includes(SYSTEM.WEAPON_CATEGORIES[weapon.system.weaponClass]))
              if (skills.length > 0) {
                skill = this.getBestWeaponClassSkill(skills, rollType, 0.25)
              } else {
                ui.notifications.warn(game.i18n.localize("LETHALFANTASY.Notifications.skillNotFound"))
                return
              }
            }
          }
        }
        if (!weapon || !skill) {
          console.error("Weapon or skill not found", weapon, skill)
          ui.notifications.warn(game.i18n.localize("LETHALFANTASY.Notifications.skillNotFound"))
          return
        }
        rollTarget = skill
        rollTarget.weapon = foundry.utils.duplicate(weapon)
        rollTarget.weaponSkillModifier = skill.weaponSkillModifier
        rollTarget.rollKey = rollKey
        rollTarget.combat = foundry.utils.duplicate(this.combat)
      }
        break
      default:
        ui.notifications.error(game.i18n.localize("LETHALFANTASY.Notifications.rollTypeNotFound") + String(rollType))
        return
    }

    // In all cases
    if (rollTarget) {
      rollTarget.tokenId = tokenId
      await this.roll(rollType, rollTarget, defenderId, defenderTokenId, extraShieldDr, d30Effects)
    }
  }

  async rollInitiative(combatId = undefined, combatantId = undefined) {
    const hasTarget = false

    let maxInit = 100

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

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

  async rollProgressionDice(combatId, combatantId) {
    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
    }

    const rollModes = foundry.utils.duplicate(CONFIG.ChatMessage.modes)
    const fieldRollMode = new foundry.data.fields.StringField({
      choices: rollModes,
      blank: false,
      default: "public",
    })

    let roll = new Roll("1D12")
    await roll.evaluate()

    let msg = await roll.toMessage({ flavor: `Progression Roll for ${this.parent.name}` })
    if (game?.dice3d) {
      await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
    }

    let hasAttack = false
    const attacksSet = this.attackMode === "ranged" ? this.rangedAttacks : this.attacks
    for (let key in attacksSet) {
      let attack = attacksSet[key]
      if (attack.enabled && attack.attackScore > 0 && attack.attackScore === roll.total) {
        hasAttack = true
        const messageContent = await foundry.applications.handlebars.renderTemplate(
          "systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
          {
            success: true,
            actorName: this.parent.name,
            weaponName: attack.name,
            rollResult: roll.total
          }
        )
        await ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
        let token = combatant?.token
        this.prepareMonsterRoll("monster-attack", key, undefined, token?.id)
        if (token?.object) {
          token.object?.control({ releaseOthers: true });
          return canvas.animatePan(token.object.center);
        }
      }
    }
    // Check Hand To Hand attacks as well
    if (!hasAttack) {
      for (let key in this.combatHTH) {
        let attack = this.combatHTH[key]
        if (attack.enabled && attack.attackScore > 0 && attack.attackScore === roll.total) {
          hasAttack = true
          const messageContent = await foundry.applications.handlebars.renderTemplate(
            "systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
            {
              success: true,
              actorName: this.parent.name,
              weaponName: `${attack.name} (HTH)`,
              rollResult: roll.total
            }
          )
          await ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
          let token = combatant?.token
          this.prepareMonsterRoll("monster-attack-hth", key, undefined, token?.id)
          if (token?.object) {
            token.object?.control({ releaseOthers: true });
            return canvas.animatePan(token.object.center);
          }
        }
      }
    }
    if (!hasAttack) {
      const messageContent = await foundry.applications.handlebars.renderTemplate(
        "systems/fvtt-lethal-fantasy/templates/progression-message.hbs",
        {
          success: false,
          actorName: this.parent.name,
          rollResult: roll.total
        }
      )
      await ChatMessage.create({ content: messageContent, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
    }

  }

}
