
import { SYSTEM } from "../config/system.mjs"
import CthulhuEternalUtils from "../utils.mjs"

export default class CthulhuEternalRoll extends Roll {
  /**
   * The HTML template path used to render dice checks of this type
   * @type {string}
   */
  static CHAT_TEMPLATE = "systems/fvtt-cthulhu-eternal/templates/chat-message.hbs"

  get type() {
    return this.options.type
  }

  get isDamage() {
    return this.type === ROLL_TYPE.DAMAGE
  }

  get target() {
    return this.options.target
  }

  get value() {
    return this.options.value
  }

  get actorId() {
    return this.options.actorId
  }

  get actorName() {
    return this.options.actorName
  }

  get actorImage() {
    return this.options.actorImage
  }

  get help() {
    return this.options.help
  }

  get gene() {
    return this.options.gene
  }

  get modifier() {
    return this.options.modifier
  }

  get resultType() {
    return this.options.resultType
  }

  get isFailure() {
    return this.resultType === "failure"
  }

  get hasTarget() {
    return this.options.hasTarget
  }

  get realDamage() {
    return this.options.realDamage
  }

  get weapon() {
    return this.options.weapon
  }

  get isLowWP() {
    return this.options.isLowWP
  }

  get isZeroWP() {
    return this.options.isZeroWP
  }

  get isExhausted() {
    return this.options.isExhausted
  }

  get isNudgedRoll() {
    return this.options.isNudgedRoll
  }

  get wpCost() {
    return this.options.wpCost
  }

  static updateResourceDialog(options) {
    let rating = 0
    if (options.rollItem.enableHand) {
      rating += options.rollItem.hand
    }
    if (options.rollItem.enableStowed) {
      rating += options.rollItem.stowed
    }
    if (options.rollItem.enableStorage) {
      rating += options.rollItem.storage
    }
    let multiplier = Number($(`.roll-skill-multiplier`).val())
    options.initialScore = rating
    options.percentScore = rating * multiplier
    $(".resource-score").text(`${rating} (${options.percentScore}%)`)
  }

  static buildSelectiveFireChoices(actor, weapon) {
    if (!weapon?.system?.hasSelectiveFire) {
      return {}
    }
    // Loop thru the selective fire choices and build the choices object when enough ammo in the weapon
    let choices = {}
    for (let choiceKey in SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES) {
      let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[choiceKey]
      if (choice.ammoUsed > 0 && choice.ammoUsed <= weapon.system.ammo.value) {
        choices[choiceKey] = choice
      }
    }
    // If no choices available, warn the user
    if (Object.keys(choices).length === 0) {
      ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoSelectiveFireChoices"))
      return {}
    }
    return choices
  }

  static async processWeaponDamage(actor, options) {
    let isLethal = false
    let weapon = options.rollItem
    options.isNudge = false

    // Selective fire management
    if (weapon.system.hasSelectiveFire && weapon.selectiveFireChoice) {
      let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[weapon.selectiveFireChoice]
      weapon.system.selectiveFireChoiceLabel = choice.label // Store the choice in the weapon
      weapon.system.lethality = choice.lethality // Override lethality
      weapon.system.killRadius = choice.killRadius // Override kill radius
    }

    let targets = []
    if (game?.combat?.combatants) {
      for (let c of game.combat.combatants) {
        if (c.actorId !== actor.id) {
          targets.push({ id: c.id, name: c.name, actorId: c.actorId, tokenId: c.token?.id })
        }
      }
    }
    // Fall back to scene tokens if not in combat
    if (targets.length === 0 && canvas?.scene) {
      for (let tokenDoc of canvas.scene.tokens) {
        if (tokenDoc.actorId !== actor.id) {
          targets.push({ name: tokenDoc.name, actorId: tokenDoc.actorId, tokenId: tokenDoc.id })
        }
      }
    }

    if (weapon.system.lethality > 0) {
      let lethalityRoll = new Roll("1d100")
      await lethalityRoll.evaluate()
      let lethalScore = (options?.previousResultType === "successCritical") ? weapon.system.lethality * 2 : weapon.system.lethality
      isLethal = (lethalityRoll.total <= lethalScore)
      let wounds = Math.floor(lethalityRoll.total / 10) + (lethalityRoll.total % 10)
      let msgData = {
        actorId: actor.id,
        weapon,
        wounds,
        lethalScore,
        isLethal,
        ammoUsed: weapon?.ammoUsed || 0,
        rollResult: lethalityRoll.total,
        combatants: targets
      }
      let msg = await ChatMessage.create({
        user: game.user.id,
        content: await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-lethal-damage.hbs", msgData),
        speaker: ChatMessage.getSpeaker({ actor: actor }),
      }, { rollMode: options.rollMode, create: true })
      await msg.setFlag("fvtt-cthulhu-eternal", "woundData", msgData)
      return
    }

    // If the weapon is not lethal, we can proceed with the regular damage roll
    let formula = weapon?.damageFormula || weapon.system.damage || "0"
    if (weapon.system.applyDamageBonus) {
      formula += ` + ${actor.system?.damageBonus}`
    }
    if (options?.previousResultType === "successCritical") {
      formula = `( ${formula} ) * 2`
    }

    let damageRoll = new Roll(formula)
    await damageRoll.evaluate()
    let msgData = {
      actorId: actor.id,
      weapon,
      formula,
      ammoUsed: weapon?.ammoUsed || 0,
      rollResult: damageRoll.total,
      combatants: targets
    }
    let msg = await ChatMessage.create({
      user: game.user.id,
      content: await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-regular-damage.hbs", msgData),
      speaker: ChatMessage.getSpeaker({ actor: actor }),
    }, { rollMode: options.rollMode, create: true })
    await msg.setFlag("fvtt-cthulhu-eternal", "woundData", msgData)
  }


  static computeWeaponModifiers(rollData) {
    let modifier = SYSTEM.WEAPON_MELEE_TARGET_MOVE[rollData.meleeTargetMoveChoice]?.modifier || 0
    modifier += SYSTEM.WEAPON_RANGED_RANGE[rollData.rangedRangeChoice]?.modifier || 0
    modifier += SYSTEM.WEAPON_RANGED_TARGET_MOVE[rollData.rangedTargetMoveChoice]?.modifier || 0
    modifier += SYSTEM.WEAPON_VISIBILITY[rollData.visibilityChoice]?.modifier || 0
    modifier += SYSTEM.WEAPON_ATTACKER_STATE[rollData.attackerStateChoice]?.modifier || 0
    modifier += SYSTEM.WEAPON_TARGET_SIZE[rollData.targetSizeChoice]?.modifier || 0
    modifier += (rollData.aimingLastRoundFlag) ? 20 : 0
    modifier += (rollData.aimingWithSightFlag) ? 20 : 0
    return modifier
  }

  static async processAmmoUsed(actor, weapon) {
    let ammoUsed = weapon.system.weaponType.includes("ranged") ? 1 : 0 // Default ammo used for melee weapons is 0
    // Selective fire management
    if (weapon.system.hasSelectiveFire && weapon.selectiveFireChoice) {
      let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[weapon.selectiveFireChoice]
      if (choice.ammoUsed > weapon.system.ammo.value) {
        ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
        return
      }
      ammoUsed = choice.ammoUsed // Override ammo used
    }
    ammoUsed = Number(ammoUsed)
    if (ammoUsed > 0) {
      await actor.updateEmbeddedDocuments("Item", [{
        _id: weapon._id,
        "system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed)
      }])
    }
    weapon.ammoUsed = ammoUsed
  }

  /**
   * Prompt the user with a dialog to configure and execute a roll.
   *
   * @param {Object} options Configuration options for the roll.
   * @param {string} options.rollType The type of roll being performed.
   * @param {string} options.rollTarget The target of the roll.
   * @param {string} options.actorId The ID of the actor performing the roll.
   * @param {string} options.actorName The name of the actor performing the roll.
   * @param {string} options.actorImage The image of the actor performing the roll.
   * @param {boolean} options.hasTarget Whether the roll has a target.
   * @param {Object} options.data Additional data for the roll.
   *
   * @returns {Promise<Object|null>} The roll result or null if the dialog was cancelled.
   */
  static async prompt(options = {}) {
    let formula = "1d100"
    let hasModifier = true
    let hasMultiplier = false
    const nudgeEnabled = game.settings.get("fvtt-cthulhu-eternal", "settings-nudge")
    options.isNudge = nudgeEnabled
    let actor = game.actors.get(options.actorId)

    let target = CthulhuEternalUtils.getTarget()

    switch (options.rollType) {
      case "skill":
        options.initialScore = options.rollItem.system.computeScore()
        break
      case "luck":
        hasModifier = false
        options.initialScore = 50
        options.isNudge = false
        break
      case "san":
      case "char":
        options.initialScore = options.rollItem.targetScore
        options.isNudge = nudgeEnabled && (options.rollType !== "san")
        break
      case "resource":
        hasModifier = false
        hasMultiplier = true
        options.initialScore = options.rollItem.targetScore
        options.totalRating = options.rollItem.targetScore
        options.percentScore = options.rollItem.targetScore * 5
        options.rollItem.enableHand = true
        options.rollItem.enableStowed = true
        options.rollItem.enableStorage = true
        options.isNudge = false
        break
      case "damage":
        return this.processWeaponDamage(actor, options)
      case "weapon": {
        let era = game.settings.get("fvtt-cthulhu-eternal", "settings-era")
        if (era !== options.rollItem.system.settings) {
          ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.WrongEra"))
          return
        }
        if (!SYSTEM.WEAPON_SKILL_MAPPING[era]?.[options.rollItem.system.weaponType]) {
          ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponType"))
          return
        }
        /*if (!target) {
          ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.AttackNoTarget"))
        }*/
        // Check if the weapon has enouth ammo in case of a firearm
        if (options.rollItem.system.isFireArm() && options.rollItem.system.ammo.value <= 0) {
          ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
          return
        }
        options.weapon = options.rollItem

        options.rollItem = CthulhuEternalUtils.getWeaponSkill(actor, options.rollItem, era)
        options.initialScore = options.rollItem.system.skillTotal
        if (options.weapon.system.state === "junk") {
          options.isJunk = true
          options.initialScore = Math.max(0, options.initialScore - 20) // -20% for junk weapons
        }
        if (options.weapon.system.state === "worn") {
          options.isWorn = true
        }
      }
        break
      default:
        options.initialScore = 50
        break
    }

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

    const choiceModifier = SYSTEM.MODIFIER_CHOICES
    const choiceMultiplier = SYSTEM.MULTIPLIER_CHOICES
    const choiceSelectiveFire = this.buildSelectiveFireChoices(actor, options?.weapon)

    let modifier = "+0"
    let multiplier = "5"

    let dialogContext = {
      rollType: options.rollType,
      rollItem: foundry.utils.duplicate(options.rollItem), // Object only, no class
      weapon: options?.weapon,
      isRangedWeapon: options?.weapon?.system?.isRanged(),
      initialScore: options.initialScore,
      targetScore: options.initialScore,
      isLowWP: options.isLowWP,
      isZeroWP: options.isZeroWP,
      isJunk: options.isJunk,
      isWorn: options.isWorn,
      isExhausted: options.isExhausted,
      enableHand: options.rollItem.enableHand,
      enableStowed: options.rollItem.enableStowed,
      enableStorage: options.rollItem.enableStorage,
      rollModes,
      fieldRollMode,
      choiceModifier,
      choiceMultiplier,
      choiceSelectiveFire,
      choiceMeleeTargetMove: SYSTEM.WEAPON_MELEE_TARGET_MOVE,
      choiceRangedRange: SYSTEM.WEAPON_RANGED_RANGE,
      choiceRangedTargetMove: SYSTEM.WEAPON_RANGED_TARGET_MOVE,
      choiceVisibility: SYSTEM.WEAPON_VISIBILITY,
      choiceAttackerState: SYSTEM.WEAPON_ATTACKER_STATE,
      choiceTargetSize: SYSTEM.WEAPON_TARGET_SIZE,
      selectiveFireChoice: "shortburst",
      meleeTargetMoveChoice: "normal",
      rangedRangeChoice: "normal",
      rangedTargetMoveChoice: "normal",
      visibilityChoice: "clear",
      attackerStateChoice: "normal",
      targetSizeChoice: "normal",
      aimingLastRoundFlag: false,
      aimingWithSightFlag: false,
      modifier,
      formula,
      targetName: target?.name,
      hasTarget: options.hasTarget,
      hasModifier,
      hasMultiplier,
      multiplier
    }
    const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/roll-dialog.hbs", dialogContext)

    const title = CthulhuEternalRoll.createTitle(options.rollType, options.rollTarget)
    const label = game.i18n.localize("CTHULHUETERNAL.Roll.roll")
    const rollContext = await foundry.applications.api.DialogV2.wait({
      window: { title: title },
      classes: ["fvtt-cthulhu-eternal"],
      content,
      buttons: [
        {
          label: label,
          callback: (event, button, dialog) => {
            const output = Array.from(button.form.elements).reduce((obj, input) => {
              if (input.name) obj[input.name] = input.value
              return obj
            }, {})
            return output
          },
        },
      ],
      actions: {
        "selectHand": (event, button, dialog) => {
          options.rollItem.enableHand = !options.rollItem.enableHand
          this.updateResourceDialog(options)
        },
        "selectStowed": (event, button, dialog) => {
          options.rollItem.enableStowed = !options.rollItem.enableStowed
          this.updateResourceDialog(options)
        },
        "selectStorage": (event, button, dialog) => {
          options.rollItem.enableStorage = !options.rollItem.enableStorage
          this.updateResourceDialog(options)
        }
      },
      rejectClose: false, // Click on Close button will not launch an error
      render: (event, dialog) => {
        $(".roll-skill-multiplier").change(event => {
          options.multiplier = Number(event.target.value)
          this.updateResourceDialog(options)
        })
        $(".aimingLastRound").change(event => {
          options.aimingLastRoundFlag = event.target.checked
          this.updateResourceDialog(options)
        })
        $(".aimingWithSight").change(event => {
          options.aimingWithSightFlag = event.target.checked
          this.updateResourceDialog(options)
        })
      }
    })

    // If the user cancels the dialog, exit
    if (rollContext === null) return

    let rollData = foundry.utils.mergeObject(foundry.utils.duplicate(options), rollContext)
    // If we have a target, get its data
    rollData.targetId = target?.id
    rollData.targetName = target?.name
    rollData.rollMode = rollContext.visibility

    // Update target score
    if (options.rollType === "resource") {
      rollData.targetScore = options.initialScore * Number(rollContext.multiplier)
    } else {
      let totalModifier = this.computeWeaponModifiers(rollData) + Number(rollData.modifier)
      rollData.totalModifier = Math.min(totalModifier, 40)
      rollData.targetScore = Math.min(Math.max(options.initialScore + Number(rollData.totalModifier), 0), 100)
      if (rollData.isLowWP || rollData.isExhausted) {
        rollData.targetScore -= 20
      }
      if (rollData.isZeroWP) {
        rollData.targetScore = 0
      }
      rollData.targetScore = Math.min(Math.max(rollData.targetScore, 0), 100)
    }
    if (rollData.targetScore === undefined || rollData.targetScore === null) {
      rollData.targetScore = options.initialScore
      rollData.modifier = "0"
    }

    if (options.rollType === "weapon") {
      await this.processAmmoUsed(actor, rollData.weapon)
    }

    if (Hooks.call("fvtt-cthulhu-eternal.preRoll", options, rollData) === false) return

    const roll = new this(formula, options.data, rollData)
    await roll.evaluate()

    await roll.displayRollResult(roll, options, rollData)

    if (Hooks.call("fvtt-cthulhu-eternal.Roll", options, rollData, roll) === false) return


    return roll
  }

  async displayRollResult(formula, options, rollData) {

    // Compute the result quality
    let resultType = "failure"
    let dec = Math.floor(this.total / 10)
    let unit = this.total - (dec * 10)
    if (this.total <= rollData.targetScore) {
      resultType = "success"
      // Detect if decimal == unit in the dire total result
      if (dec === unit || this.total === 1) {
        resultType = "successCritical"
      }
    } else {
      // Detect if decimal == unit in the dire total result
      if (dec === unit || this.total === 100) {
        resultType = "failureCritical"
      }
    }
    if (rollData.weapon?.system?.state === "junk" && this.total > 95) {
      this.options.isDestroyed = true
      rollData.isDestroyed = true
      resultType = "failureCritical"
    }
    // As per the rules, a roll of 100 is always a failure, even if the target is above 100
    if (this.total === 100) {
      resultType = "failureCritical"
    }
    // A roll of 1 is always a critical success, even if the target is 1
    if (this.total === 1) {
      resultType = "successCritical"
    }
    if (rollData.targetScore <= 0) {
      resultType = "failure"
      if (this.total === 1) {
        resultType = "success"
      }
    }

    this.options.resultType = resultType
    if (this.options.isNudgedRoll) {
      this.options.isSuccess = resultType === "success" || resultType === "successCritical"
      this.options.isFailure = resultType === "failure" || resultType === "failureCritical"
      this.options.isCritical = false
    } else {
      this.options.isSuccess = resultType === "success" || resultType === "successCritical"
      this.options.isFailure = resultType === "failure" || resultType === "failureCritical"
      this.options.isCritical = resultType === "successCritical" || resultType === "failureCritical"
    }
    rollData.resultType = resultType

    // Compute WP cost to nudge a failure into a success (minimum roll needed = targetScore)
    if (this.options.isFailure && options.isNudge && !this.options.isNudgedRoll && rollData.targetScore > 0 && this.total > rollData.targetScore) {
      const actor = game.actors.get(options.actorId)
      const wpAvailable = actor?.system?.wp?.value ?? 0
      const wpCostToSucceed = Math.ceil((this.total - rollData.targetScore) / 5)
      this.options.wpCostToSucceed = (wpCostToSucceed <= wpAvailable) ? wpCostToSucceed : 0
    } else {
      this.options.wpCostToSucceed = 0
    }

    this.options.isLowWP = rollData.isLowWP
    this.options.isZeroWP = rollData.isZeroWP
    this.options.isExhausted = rollData.isExhausted
    rollData.isSuccess = this.options.isSuccess
    rollData.isFailure = this.options.isFailure
    rollData.isCritical = this.options.isCritical
    rollData.resultType = resultType
    rollData.rollResult = this.total
    rollData.total = this.total
    this.options.rollData = foundry.utils.duplicate(rollData)

    // Keep track of the last defense roll for the actor
    if (game.combat && (rollData?.weapon?.system.type === "melee" || (rollData?.rollItem.type === "skill" && rollData?.rollItem.name?.toLowerCase().includes(game.i18n.localize("CTHULHUETERNAL.Skill.dodgeName").toLowerCase())))) {
      let actor = game.actors.get(options.actorId)
      rollData.round = game.combat.round
      actor.setLastDefenseRoll(foundry.utils.duplicate(rollData))
    }

    if (game.combat && rollData?.weapon) { // An attack roll
      rollData.isAttackRoll = true
    }

    // Now check if we have a target for the current roll, and if the target has done its defense roll this round
    if (rollData.targetId) {
      let token = game.scenes.current.tokens.get(rollData.targetId)
      let defender = token.actor
      let lastDefenseRoll = defender?.getLastDefenseRoll()
      // Now compare opposition
      this.compareRolls(rollData, lastDefenseRoll)
      rollData.defenseRoll = lastDefenseRoll
    }

  }

  compareRolls(attackRoll, defenseRoll) {
    if (!defenseRoll || defenseRoll.round !== game?.combat?.round) {
      // ui.notifications.info(game.i18n.localize("CTHULHUETERNAL.Notifications.NoDefenseRoll"))
      return
    }
    if (attackRoll.isFailure) {
      attackRoll.attackSucess = false
    }
    if (attackRoll.isSuccess && defenseRoll.isFailure) {
      attackRoll.attackSucess = true
    }
    if (attackRoll.isSuccess && defenseRoll.isSuccess) {
      if (attackRoll.isCritical && !defenseRoll.isCritical) {
        attackRoll.attackSucess = true
      }
      else if (!attackRoll.isCritical && defenseRoll.isCritical) {
        attackRoll.attackSucess = false
      }
      else {
        // Both are normal success, compare the roll results
        if (attackRoll.total >= defenseRoll.total) {
          // Attack successful
          attackRoll.attackSucess = true
        } else {
          // Defense successful
          attackRoll.attackSucess = false
        }
      }
    }
  }

  /**
   * Creates a title based on the given type.
   *
   * @param {string} type The type of the roll.
   * @param {string} target The target of the roll.
   * @returns {string} The generated title.
   */
  static createTitle(type, target) {
    switch (type) {
      case "luck":
        return `${game.i18n.localize("CTHULHUETERNAL.Label.titleLuck")}`
      case "skill":
        return `${game.i18n.localize("CTHULHUETERNAL.Label.titleSkill")}`
      case "weapon":
        return `${game.i18n.localize("CTHULHUETERNAL.Label.titleWeapon")}`
      case "char":
        return `${game.i18n.localize("CTHULHUETERNAL.Label.titleCharacteristic")}`
      case "san":
        return `${game.i18n.localize("CTHULHUETERNAL.Label.titleSAN")}`
      case "resource":
        return `${game.i18n.localize("CTHULHUETERNAL.Label.titleResource")}`
      default:
        return game.i18n.localize("CTHULHUETERNAL.Label.titleStandard")
    }
  }

  /** @override */
  async render(chatOptions = {}) {
    let chatData = await this._getChatCardData(chatOptions.isPrivate)
    return await foundry.applications.handlebars.renderTemplate(this.constructor.CHAT_TEMPLATE, chatData)
  }

  /**
   * Generates the data required for rendering a roll chat card.
   *
   * @param {boolean} isPrivate Indicates if the chat card is private.
   * @returns {Promise<Object>} A promise that resolves to an object containing the chat card data.
   * @property {Array<string>} css - CSS classes for the chat card.
   * @property {Object} data - The data associated with the roll.
   * @property {number} diceTotal - The total value of the dice rolled.
   * @property {boolean} isGM - Indicates if the user is a Game Master.
   * @property {string} formula - The formula used for the roll.
   * @property {number} total - The total result of the roll.
   * @property {boolean} isFailure - Indicates if the roll is a failure.
   * @property {string} actorId - The ID of the actor performing the roll.
   * @property {string} actingCharName - The name of the character performing the roll.
   * @property {string} actingCharImg - The image of the character performing the roll.
   * @property {string} resultType - The type of result (e.g., success, failure).
   * @property {boolean} hasTarget - Indicates if the roll has a target.
   * @property {string} targetName - The name of the target.
   * @property {number} targetArmor - The armor value of the target.
   * @property {number} realDamage - The real damage dealt.
   * @property {boolean} isPrivate - Indicates if the chat card is private.
   * @property {string} cssClass - The combined CSS classes as a single string.
   * @property {string} tooltip - The tooltip text for the chat card.
   */
  async _getChatCardData(isPrivate) {
    let cardData = foundry.utils.duplicate(this.options)
    cardData.css = [SYSTEM.id, "dice-roll"]
    cardData.data = this.data
    cardData.diceTotal = this.dice.reduce((t, d) => t + d.total, 0)
    cardData.isGM = game.user.isGM
    cardData.formula = this.formula
    cardData.total = this.total
    cardData.actorId = this.actorId
    cardData.actingCharName = this.actorName
    cardData.actingCharImg = this.actorImage
    cardData.resultType = this.resultType
    cardData.hasTarget = this.hasTarget
    cardData.targetName = this.targetName
    cardData.targetArmor = this.targetArmor
    cardData.realDamage = this.realDamage
    cardData.isPrivate = isPrivate
    cardData.weapon = this.weapon
    cardData.isLowWP = this.isLowWP
    cardData.isZeroWP = this.isZeroWP
    cardData.isExhausted = this.isExhausted
    cardData.isNudgedRoll = this.isNudgedRoll
    cardData.wpCost = this.wpCost

    cardData.cssClass = cardData.css.join(" ")
    cardData.tooltip = isPrivate ? "" : await this.getTooltip()
    return cardData
  }

  /**
   * Converts the roll result to a chat message.
   *
   * @param {Object} [messageData={}] Additional data to include in the message.
   * @param {Object} options Options for message creation.
   * @param {string} options.rollMode The mode of the roll (e.g., public, private).
   * @param {boolean} [options.create=true] Whether to create the message.
   * @returns {Promise} - A promise that resolves when the message is created.
   */
  async toMessage(messageData = {}, { rollMode, messageMode, create = true } = {}) {
    let rollData = this.options.rollData || this.options
    let rollItem = this.options.rollItem

    // Determine skill progression before calling super.toMessage so _getChatCardData picks it up
    let skillMarkedForProgress = false
    if (rollData.resultType?.includes("failure") && rollItem?.type === "skill") {
      if (rollItem.system.diceEvolved && !rollItem.system.rollFailed && !rollItem.system.isAdversary) {
        skillMarkedForProgress = true
      }
    }
    this.options.skillMarkedForProgress = skillMarkedForProgress

    let rollMsg = await super.toMessage(
      {
        isFailure: this.resultType === "failure",
        actingCharName: this.actorName,
        actingCharImg: this.actorImage,
        hasTarget: this.hasTarget,
        realDamage: this.realDamage,
        ...messageData,
      },
      { messageMode: messageMode ?? rollMode },
    )
    await rollMsg.setFlag("fvtt-cthulhu-eternal", "rollData", rollData)

    // Apply skill rollFailed flag after the message is created
    if (skillMarkedForProgress) {
      const actor = game.actors.get(rollData.actorId)
      await actor.updateEmbeddedDocuments("Item", [{
        _id: rollItem._id,
        "system.rollFailed": true
      }])
    }

    // If the roll is a SAN roll, we propose to select the SAN loss
    if (rollData.rollType === "san") {
      let msgData = {
        rollItem: rollItem,
        rollData: rollData
      }
      // Get array of gamemaster ID
      let msg = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-san-request.hbs", msgData)
      let chatMsg = await ChatMessage.create({
        user: game.user.id,
        content: msg,
        speaker: ChatMessage.getSpeaker({ actor: rollData.actor }),
        whisper: game.users.filter(u => u.isGM).map(u => u.id),
      })
      await chatMsg.setFlag("fvtt-cthulhu-eternal", "rollData", rollData)
    }
  }

}
