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

/**
 * D&D 5e style Roll system for Prism RPG
 * Simple 1d20 + modifier, with advantage/disadvantage
 */
export default class PrismRPGRoll extends Roll {
  static CHAT_TEMPLATE = "systems/fvtt-prism-rpg/templates/chat-message.hbs"

  /**
   * Calculate D&D 5e style ability modifier from ability score
   * @param {number} abilityScore The ability score value (3-18+)
   * @returns {number} The ability modifier
   */
  static getAbilityModifier(abilityScore) {
    return Math.floor((abilityScore - 10) / 2)
  }

  // Getters for roll data
  get type() {
    return this.options.type
  }

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

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

  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 modifier() {
    return this.options.modifier
  }

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

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

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

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

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

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

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

  /**
   * D&D 5e style dice roll prompt
   * Formula: 1d20 + modifier, or damage dice + modifier
   * Advantage: 2d20kh, Disadvantage: 2d20kl
   * @param {Object} options Roll options
   * @returns {Promise<PrismRPGRoll|null>} The roll result or null if cancelled
   */
  static async prompt(options = {}) {
    let dice = "1d20"
    let hasModifier = true
    let hasAdvantage = true
    let isDamageRoll = false

    // Determine roll type and modifiers
    switch (options.rollType) {
      case "characteristic":
        options.rollName = options.rollTarget.name
        // Calculate D&D 5e modifier from characteristic value
        options.rollTarget.value = this.getAbilityModifier(options.rollTarget.value)
        break

      case "sub-attribute":
        options.rollName = options.rollTarget.name
        // Sub-attribute value is already a modifier (calculated in prepareDerivedData)
        break

      case "challenge":
        options.rollName = game.i18n.localize(`PRISMRPG.Label.${options.rollTarget.rollKey}`)
        break

      case "save":
        options.rollName = `${game.i18n.localize(`PRISMRPG.Label.${options.rollTarget.rollKey}`)} Save`
        // Calculate D&D 5e saving throw: ability modifier + proficiency bonus
        // Get the characteristic value from rollTarget
        const charValue = options.rollTarget.characteristicValue
        const abilityMod = this.getAbilityModifier(charValue)
        const saveBonus = options.rollTarget.saveBonus || 0
        // Store separate values for display
        options.rollTarget.abilityModifier = abilityMod
        options.rollTarget.saveProficiency = saveBonus
        // Add the save bonus (proficiency) stored in saves
        options.rollTarget.value = abilityMod + saveBonus
        break

      case "skill":
        options.rollName = options.rollTarget.name
        // D&D 5e style: sub-attribute modifier + proficiency bonus
        // Default to first sub-attribute, will be recalculated if player chooses different one
        const proficiency = options.rollTarget.proficiencyBonus || 0
        options.rollTarget.value = options.rollTarget.subAttribute1Value + proficiency
        break

      case "weapon-attack":
        options.rollName = options.rollTarget.name
        // Default to STR for melee, DEX for ranged (will be updated by dialog choice)
        if (options.rollTarget.weapon.system.weaponType === "melee") {
          options.rollTarget.value = options.rollTarget.strMod +
            options.rollTarget.weapon.system.bonuses.attackBonus
        } else {
          options.rollTarget.value = options.rollTarget.dexMod +
            options.rollTarget.weapon.system.bonuses.attackBonus
        }
        break

      case "spell":
      case "spell-attack":
      case "spell-power":
      case "spell-cast":
        options.rollName = options.rollTarget.name
        // Find best mental characteristic (INT, WIS, CHA)
        const actor = game.actors.get(options.actorId)
        const intMod = this.getAbilityModifier(actor.system.characteristics.int.value)
        const wisMod = this.getAbilityModifier(actor.system.characteristics.wis.value)
        const chaMod = this.getAbilityModifier(actor.system.characteristics.cha.value)
        const bestMentalMod = Math.max(intMod, wisMod, chaMod)
        options.rollTarget.value = bestMentalMod

        // Store which characteristic is being used
        if (bestMentalMod === intMod) {
          options.rollTarget.mentalCharacteristic = "INT"
          options.rollTarget.mentalCharValue = actor.system.characteristics.int.value
        } else if (bestMentalMod === wisMod) {
          options.rollTarget.mentalCharacteristic = "WIS"
          options.rollTarget.mentalCharValue = actor.system.characteristics.wis.value
        } else {
          options.rollTarget.mentalCharacteristic = "CHA"
          options.rollTarget.mentalCharValue = actor.system.characteristics.cha.value
        }
        break

      case "miracle":
      case "miracle-attack":
      case "miracle-power":
        options.rollName = options.rollTarget.name
        options.rollTarget.value = options.rollTarget.actorModifiers.levelMiracleModifier +
          options.rollTarget.actorModifiers.chaMiracleModifier
        break

      case "monster-attack":
        options.rollName = options.rollTarget.name
        options.rollTarget.value = options.rollTarget.attackModifier
        break

      case "monster-defense":
        options.rollName = options.rollTarget.name
        options.rollTarget.value = options.rollTarget.defenseModifier
        break

      case "monster-skill":
        options.rollName = game.i18n.localize(`PRISMRPG.Label.${options.rollTarget.rollKey}`)
        break

      default:
        if (options.rollType.includes("weapon-damage")) {
          isDamageRoll = true
          hasAdvantage = false
          options.rollName = options.rollTarget.weapon.name
          // Default to STR for melee, DEX for ranged (will be updated by dialog choice)
          if (options.rollTarget.weapon.system.weaponType === "melee") {
            options.rollTarget.value = options.rollTarget.strMod +
              options.rollTarget.weapon.system.bonuses.damageBonus
          } else {
            options.rollTarget.value = options.rollTarget.dexMod +
              options.rollTarget.weapon.system.bonuses.damageBonus
          }

          // Use the weapon's damage dice
          dice = options.rollTarget.weapon.system.damage || "1d6"
          dice = dice.replace(/E/gi, "")
        } else if (options.rollType.includes("monster-damage")) {
          isDamageRoll = true
          hasAdvantage = false
          options.rollName = options.rollTarget.name
          options.rollTarget.value = options.rollTarget.damageModifier
          dice = options.rollTarget.damageDice.replace(/E/gi, "")
        } else if (options.rollType === "granted") {
          hasModifier = false
          hasAdvantage = false
          options.rollName = `Granted ${options.rollTarget.rollKey}`
          dice = options.rollTarget.formula
        }
    }

    // Setup dialog
    const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
    const choiceModifier = SYSTEM.CHOICE_MODIFIERS
    const choiceAdvantage = SYSTEM.ADVANTAGE_CHOICES
    const attackerAimChoices = SYSTEM.ATTACKER_AIM_CHOICES

    // For weapon damage rolls, skip dialog and roll directly
    if (options.rollType.includes("weapon-damage")) {
      // Just roll the weapon's damage dice, no modifiers
      const finalFormula = dice

      const rollData = {
        type: options.rollType,
        rollType: options.rollType,
        target: options.rollTarget,
        rollName: options.rollName,
        actorId: options.actorId,
        actorName: options.actorName,
        actorImage: options.actorImage,
        rollMode: "publicroll",
        hasTarget: options.hasTarget,
        titleFormula: finalFormula
      }

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

      // Execute the roll
      let roll = new this(finalFormula, options.data, rollData)
      await roll.evaluate()

      // Store results
      const duplicatedRollTarget = foundry.utils.duplicate(options.rollTarget)
      roll.options.resultType = "success"
      roll.options.rollTotal = roll.total
      roll.options.rollTarget = duplicatedRollTarget
      roll.options.titleFormula = finalFormula
      roll.options.rollData = foundry.utils.duplicate(rollData)

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

      return roll
    }

    let dialogContext = {
      rollType: options.rollType,
      rollTarget: options.rollTarget,
      rollName: options.rollName,
      actorName: options.actorName,
      rollModes,
      hasModifier,
      hasAdvantage,
      isDamageRoll,
      baseValue: options.rollTarget.value || 0,
      dice,
      choiceModifier,
      choiceAdvantage,
      attackerAimChoices,
      hasTarget: options.hasTarget,
      modifier: "+0",
      advantage: "none",
      config: SYSTEM
    }

    const content = await foundry.applications.handlebars.renderTemplate(
      "systems/fvtt-prism-rpg/templates/roll-dialog-v2.hbs",
      dialogContext
    )

    let position = game.user.getFlag(SYSTEM.id, "roll-dialog-pos") || { top: -1, left: -1 }
    const label = game.i18n.localize("PRISMRPG.Roll.roll")

    const rollContext = await foundry.applications.api.DialogV2.wait({
      window: { title: "Roll dialog" },
      classes: ["prismrpg"],
      content,
      position,
      buttons: [{
        label: label,
        callback: (event, button, dialog) => {
          game.user.setFlag(SYSTEM.id, "roll-dialog-pos", foundry.utils.duplicate(dialog.position))
          const output = Array.from(button.form.elements).reduce((obj, input) => {
            if (input.name) {
              if (input.type === "checkbox") {
                obj[input.name] = input.checked
              } else {
                obj[input.name] = input.value
              }
            }
            return obj
          }, {})
          return output
        },
      }],
      rejectClose: false
    })

    if (rollContext === null) return

    // Build D&D 5e formula: 1d20 + modifier
    let finalFormula = dice
    let totalModifier = 0

    if (hasModifier) {
      let bonus = Number(options.rollTarget.value) || 0

      // Recalculate bonus if player chose different attribute for weapon attack/damage
      if (rollContext.attackAttribute && options.rollTarget.weapon) {
        const chosenMod = rollContext.attackAttribute === "str" ? options.rollTarget.strMod : options.rollTarget.dexMod
        const weaponBonus = options.rollTarget.weapon.system.bonuses.attackBonus || 0
        const damageBonus = options.rollTarget.weapon.system.bonuses.damageBonus || 0

        if (options.rollType === "weapon-attack") {
          bonus = chosenMod + weaponBonus
        } else if (options.rollType.includes("weapon-damage")) {
          bonus = chosenMod + damageBonus
        }
      }

      // Recalculate bonus if player chose different sub-attribute for skill
      if (rollContext.skillSubAttribute && options.rollType === "skill") {
        const chosenSubAttrValue = rollContext.skillSubAttribute === options.rollTarget.subAttribute1 ?
          options.rollTarget.subAttribute1Value :
          options.rollTarget.subAttribute2Value
        const proficiencyBonus = options.rollTarget.proficiencyBonus || 0
        bonus = chosenSubAttrValue + proficiencyBonus
      }

      let extraModifier = rollContext.modifier === "" ? 0 : Number.parseInt(rollContext.modifier, 10)
      totalModifier = bonus + extraModifier

      // Apply aiming modifier for ranged attacks
      if (rollContext.attackerAim && rollContext.attackerAim !== "0") {
        const aimModifier = Number.parseInt(rollContext.attackerAim, 10)
        totalModifier += aimModifier
      }

      if (totalModifier !== 0) {
        finalFormula = totalModifier > 0 ?
          `${dice} + ${totalModifier}` :
          `${dice} - ${Math.abs(totalModifier)}`
      }
    }

    // Apply advantage/disadvantage
    if (rollContext.advantage === "advantage" && !isDamageRoll) {
      finalFormula = finalFormula.replace(dice, `2${dice}kh`)
    } else if (rollContext.advantage === "disadvantage" && !isDamageRoll) {
      finalFormula = finalFormula.replace(dice, `2${dice}kl`)
    }

    // Special ranged weapon modifiers
    if (rollContext.letItFly) {
      // Let it Fly: Pure D20E (replace with 1d20 if it was modified)
      finalFormula = finalFormula.replace(/2d20k[hl]/, "1d20")
    }
    if (rollContext.pointBlank) {
      // Point Blank: Add special advantage or bonus (implement based on your rules)
      // This could add advantage or a flat bonus
    }

    // Handle spell upcast
    let upcastLevel = 0
    let totalManaCost = 0
    let totalAPC = 0
    let manaUpkeep = 0
    let mentalCharacteristic = null
    let mentalCharValue = null

    if (options.rollType === "spell-cast") {
      upcastLevel = rollContext.upcastLevel ? Number.parseInt(rollContext.upcastLevel, 10) : 0
      totalManaCost = options.rollTarget.system.manaCost + upcastLevel
      totalAPC = options.rollTarget.system.apc + upcastLevel
      manaUpkeep = options.rollTarget.system.manaUpkeep

      // Get mental characteristic info from rollTarget
      mentalCharacteristic = options.rollTarget.mentalCharacteristic
      mentalCharValue = options.rollTarget.mentalCharValue
    }

    // Store skill sub-attribute information
    let skillSubAttribute = null
    let skillSubAttributeLabel = null
    let skillSubAttributeValue = null
    if (options.rollType === "skill" && rollContext.skillSubAttribute) {
      skillSubAttribute = rollContext.skillSubAttribute
      const subAttrConfig = SYSTEM.SUB_ATTRIBUTES?.[skillSubAttribute]
      if (subAttrConfig) {
        skillSubAttributeLabel = game.i18n.localize(subAttrConfig.label)
      }
      skillSubAttributeValue = rollContext.skillSubAttribute === options.rollTarget.subAttribute1 ?
        options.rollTarget.subAttribute1Value :
        options.rollTarget.subAttribute2Value
    }

    const rollData = {
      type: options.rollType,
      rollType: options.rollType,
      target: options.rollTarget,
      rollName: options.rollName,
      actorId: options.actorId,
      actorName: options.actorName,
      actorImage: options.actorImage,
      rollMode: rollContext.visibility,
      hasTarget: options.hasTarget,
      titleFormula: finalFormula,
      upcastLevel,
      totalManaCost,
      totalAPC,
      manaUpkeep,
      mentalCharacteristic,
      mentalCharValue,
      skillSubAttribute,
      skillSubAttributeLabel,
      skillSubAttributeValue,
      ...rollContext,
    }

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

    // Handle mana spending for spell-cast
    if (options.rollType === "spell-cast" && totalManaCost > 0) {
      const actor = game.actors.get(options.actorId)
      const currentMana = actor.system.manaPoints.value

      // Check if enough mana
      if (currentMana < totalManaCost) {
        ui.notifications.error(
          `Not enough Mana! Need ${totalManaCost}, but only have ${currentMana} Mana points.`
        )
        return null
      }

      // Spend mana
      await actor.update({
        "system.manaPoints.value": currentMana - totalManaCost
      })

      ui.notifications.info(
        `Spent ${totalManaCost} Mana (${currentMana} → ${currentMana - totalManaCost})`
      )
    }

    // Execute the roll
    let roll = new this(finalFormula, options.data, rollData)
    await roll.evaluate()

    // Store results - duplicate rollTarget to properly serialize weapon Item
    const duplicatedRollTarget = foundry.utils.duplicate(options.rollTarget)
    roll.options.resultType = "success"
    roll.options.rollTotal = roll.total
    roll.options.rollTarget = duplicatedRollTarget
    roll.options.titleFormula = finalFormula
    roll.options.rollData = foundry.utils.duplicate(rollData)

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

    return roll
  }

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

  async _getChatCardData(isPrivate) {
    const cardData = {
      css: [SYSTEM.id, "dice-roll"],
      data: this.data,
      diceTotal: this.dice.reduce((t, d) => t + d.total, 0),
      isGM: game.user.isGM,
      formula: this.formula,
      titleFormula: this.titleFormula,
      rollName: this.rollName,
      rollType: this.type,
      rollTarget: this.rollTarget,
      total: this.rollTotal,
      isFailure: this.isFailure,
      actorId: this.actorId,
      actingCharName: this.actorName,
      actingCharImg: this.actorImage,
      resultType: this.resultType,
      hasTarget: this.hasTarget,
      targetName: this.targetName,
      rollData: this.rollData,
      isPrivate: isPrivate
    }
    cardData.cssClass = cardData.css.join(" ")
    cardData.tooltip = isPrivate ? "" : await this.getTooltip()
    return cardData
  }

  async toMessage(messageData = {}, { rollMode, create = true } = {}) {
    super.toMessage(
      {
        isFailure: this.resultType === "failure",
        rollType: this.type,
        rollTarget: this.rollTarget,
        actingCharName: this.actorName,
        actingCharImg: this.actorImage,
        hasTarget: this.hasTarget,
        targetName: this.targetName,
        rollData: this.rollData,
        ...messageData,
      },
      { rollMode: rollMode },
    )
  }
}
