import { SYSTEM } from "../config/system.mjs"
import D30Roll from "./d30-roll.mjs"

/**
 * 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 (e.g., RESOURCE, DAMAGE, ATTACK, SAVE).
 * @param {string} options.rollValue The initial value or formula for the roll.
 * @param {string} options.rollTarget The target of the roll.
 * @param {"="|"+"|"++"|"-"|"--"} options.rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal 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.target The target of the roll, if any.
 * @param {Object} options.data Additional data for the roll.
 *
 * @returns {Promise<Object|null>} The roll result or null if the dialog was cancelled.
 */
export async function prompt(options = {}) {
  try {
    let dice = "1D20"
    let maxValue = 20
    let baseFormula = "1D20"
    let modifierFormula = "1D0"
    let hasModifier = true
    let hasChangeDice = false
    let hasD30 = false
    let hasFavor = false
    let hasMaxValue = true
    let hasGrantedDice = false
    let pointBlank = false
    let letItFly = false
    let saveSpell = game.lethalFantasy?.spellDefense ?? false
    let beyondSkill = false
    let hasStaticModifier = false
    let hasExplode = true
    let actor = game.actors.get(options.actorId)

    if (options.rollType === "challenge" || options.rollType === "save") {
      options.rollName = game.i18n.localize(`LETHALFANTASY.Label.${options.rollTarget.rollKey}`)
      hasD30 = options.rollType === "save"
      if (options.rollTarget.rollKey === "dying") {
        dice = options.rollTarget.value
        hasModifier = false
        hasChangeDice = true
        hasFavor = true
      } else {
        dice = "1D20"
        hasFavor = true
      }

    } else if (options.rollType === "granted") {
      hasD30 = false
      options.rollName = `Granted ${options.rollTarget.rollKey}`
      dice = options.rollTarget.formula
      baseFormula = options.rollTarget.formula
      hasModifier = false
      hasMaxValue = false
      hasChangeDice = false
      hasFavor = false

    } else if (options.rollType === "monster-attack" || options.rollType === "monster-defense") {
      hasD30 = true
      options.rollName = options.rollTarget.name
      dice = "1D20"
      baseFormula = "D20"
      hasModifier = true
      hasChangeDice = false
      hasFavor = true
      if (options.rollType === "monster-attack") {
        options.rollTarget.value = options.rollTarget.attackModifier
        options.rollTarget.charModifier = 0
      } else {
        options.rollTarget.value = options.rollTarget.defenseModifier
        options.rollTarget.charModifier = 0
        options.isRangedDefense = options.rollTarget.isRangedDefense ?? false
      }

    } else if (options.rollType === "monster-skill") {
      options.rollName = game.i18n.localize(`LETHALFANTASY.Label.${options.rollTarget.rollKey}`)
      dice = "1D20"
      baseFormula = "D20"
      hasModifier = true
      hasFavor = true
      hasChangeDice = false

    } else if (options.rollType === "skill") {
      options.rollName = options.rollTarget.name
      hasD30 = true
      dice = "1D20"
      baseFormula = "D20"
      hasModifier = true
      hasFavor = true
      hasChangeDice = false
      options.rollTarget.value = Math.floor(options.rollTarget.system.skillTotal / 10)

    } else if (options.rollType === "weapon-attack" || options.rollType === "weapon-defense") {
      hasD30 = true
      options.rollName = options.rollTarget.name
      dice = "1D20"
      baseFormula = "D20"
      hasModifier = true
      hasChangeDice = false
      hasFavor = true
      if (options.rollType === "weapon-attack") {
        if (options.rollTarget.weapon.system.weaponType === "melee") {
          options.rollTarget.value = options.rollTarget.combat.attackModifier + options.rollTarget.weaponSkillModifier + options.rollTarget.weapon.system.bonuses.attackBonus
          options.rollTarget.charModifier = options.rollTarget.combat.attackModifier
        } else {
          options.rollTarget.value = options.rollTarget.combat.rangedAttackModifier + options.rollTarget.weaponSkillModifier + options.rollTarget.weapon.system.bonuses.attackBonus
          options.rollTarget.charModifier = options.rollTarget.combat.rangedAttackModifier
        }
      } else {
        // For defense, check if it's a ranged defense
        const defenseModifier = options.rollTarget.isRangedDefense
          ? options.rollTarget.combat.rangedDefenseModifier
          : options.rollTarget.combat.defenseModifier
        options.rollTarget.value = defenseModifier + options.rollTarget.weaponSkillModifier + options.rollTarget.weapon.system.bonuses.defenseBonus + options.rollTarget.armorDefense
        options.rollTarget.charModifier = defenseModifier
        // Store isRanged flag for D30 lookup
        options.isRangedDefense = options.rollTarget.isRangedDefense
      }

    } else if (options.rollType === "spell" || options.rollType === "spell-attack" || options.rollType === "spell-power") {
      hasD30 = true
      options.rollName = options.rollTarget.name
      dice = "1D20"
      baseFormula = "D20"
      hasModifier = true
      hasChangeDice = false
      hasFavor = true
      options.rollTarget.value = options.rollTarget.actorModifiers.levelSpellModifier + options.rollTarget.actorModifiers.intSpellModifier
      options.rollTarget.charModifier = options.rollTarget.actorModifiers.intSpellModifier
      hasStaticModifier = options.rollType === "spell-power"
      //hasModifier = options.rollType !== "spell-attack"
      if (hasStaticModifier) {
        options.rollTarget.staticModifier = options.rollTarget.actorLevel
      } else {
        options.rollTarget.staticModifier = 0
      }

    } else if (options.rollType === "miracle" || options.rollType === "miracle-attack" || options.rollType === "miracle-power") {
      hasD30 = true
      options.rollName = options.rollTarget.name
      dice = "1D20"
      baseFormula = "D20"
      hasChangeDice = false
      hasFavor = true
      options.rollTarget.value = options.rollTarget.actorModifiers.levelMiracleModifier + options.rollTarget.actorModifiers.chaMiracleModifier
      options.rollTarget.charModifier = options.rollTarget.actorModifiers.chaMiracleModifier
      hasStaticModifier = options.rollType === "miracle-power"
      //hasModifier = options.rollType !== "miracle-attack"
      if (hasStaticModifier) {
        options.rollTarget.staticModifier = options.rollTarget.actorLevel
      } else {
        options.rollTarget.staticModifier = 0
      }

    } else if (options.rollType === "shield-roll") {
      hasD30 = false
      options.rollName = "Shield Defense"
      dice = options.rollTarget.system.defense.toUpperCase()
      baseFormula = dice
      hasModifier = true
      hasChangeDice = false
      hasMaxValue = false
      hasExplode = false
      hasFavor = true
      options.rollTarget.value = 0

    } else if (options.rollType.includes("weapon-damage")) {
      options.rollName = options.rollTarget.name
      options.isDamage = true
      hasModifier = true
      hasChangeDice = false
      let damageBonus = (options.rollTarget.weapon.system.applyStrengthDamageBonus) ? options.rollTarget.combat.damageModifier : 0
      options.rollTarget.value = damageBonus + options.rollTarget.weaponSkillModifier + options.rollTarget.weapon.system.bonuses.damageBonus
      options.rollTarget.charModifier = damageBonus
      dice = options.rollTarget.weapon.system.damage.damageM
      if (/NE$/i.test(dice)) {
        hasMaxValue = false
        hasExplode = false
      }
      dice = dice.replace(/NE$/i, "").replace("E", "")
      baseFormula = dice

    } else if (options.rollType.includes("monster-damage")) {
      options.rollName = options.rollTarget.name
      options.isDamage = true
      hasModifier = true
      hasChangeDice = false
      options.rollTarget.value = options.rollTarget.damageModifier
      options.rollTarget.charModifier = 0
      dice = options.rollTarget.damageDice
      dice = dice.replace("E", "")
      baseFormula = dice
      if (options.rollTarget.noExplode) {
        hasMaxValue = false
        hasExplode = false
      }
    }


    if (options.rollType === "save" && (options.rollTarget.rollKey === "pain" || options.rollTarget.rollKey === "paincourage")) {
      dice = options.rollTarget.rollDice
      baseFormula = options.rollTarget.rollDice
      hasModifier = false
    }

    const rollModes = foundry.utils.duplicate(CONFIG.ChatMessage.modes);


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

    const choiceModifier = SYSTEM.CHOICE_MODIFIERS
    const choiceDice = SYSTEM.CHOICE_DICE
    const choiceFavor = SYSTEM.FAVOR_CHOICES

    let modifier = "+0"
    let targetName

    // True for any ranged attack: PC weapon (ranged type) or monster attack (ranged mode)
    const isRangedAttack = (options.rollType === "weapon-attack" && options.rollTarget?.weapon?.system?.weaponType === "ranged")
      || (options.rollType === "monster-attack" && options.rollTarget?.attackMode === "ranged")

    let dialogContext = {
      rollType: options.rollType,
      rollTarget: options.rollTarget,
      rollName: options.rollName,
      actorName: options.actorName,
      rollModes,
      hasModifier,
      hasFavor,
      hasChangeDice,
      pointBlank,
      baseValue: options.rollTarget.value,
      attackerAimChoices: SYSTEM.ATTACKER_AIM_CHOICES,
      attackerAim: "0",
      changeDice: `${dice}`,
      fieldRollMode,
      choiceModifier,
      choiceDice,
      choiceFavor,
      baseFormula,
      dice,
      hasTarget: options.hasTarget,
      modifier,
      saveSpell,
      favor: "none",
      targetName,
      isRangedAttack
    }
    let rollContext
    if (options.rollContext) {
      rollContext = foundry.utils.duplicate(options.rollContext)
      hasGrantedDice = !!rollContext.hasGrantedDice
      pointBlank = !!rollContext.pointBlank
      beyondSkill = !!rollContext.beyondSkill
      letItFly = !!rollContext.letItFly
      saveSpell = !!rollContext.saveSpell
      const _rawMode = rollContext.rollMode || game.settings.get("core", "rollMode")
      const _modeMap = { publicroll: "public", gmroll: "gm", blindroll: "blind", selfroll: "self" }
      rollContext.visibility ||= _modeMap[_rawMode] ?? _rawMode ?? "public"
      rollContext.modifier ||= modifier
      rollContext.favor ||= "none"
      rollContext.changeDice ||= `${dice}`
      rollContext.attackerAim ||= "0"
    } else {
      const content = await foundry.applications.handlebars.renderTemplate("systems/fvtt-lethal-fantasy/templates/roll-dialog.hbs", dialogContext)

      let position = game.user.getFlag(SYSTEM.id, "roll-dialog-pos") || { top: -1, left: -1 }
      const label = game.i18n.localize("LETHALFANTASY.Roll.roll")
      rollContext = await foundry.applications.api.DialogV2.wait({
        window: { title: "Roll dialog" },
        classes: ["lethalfantasy"],
        content,
        position,
        buttons: [
          {
            action: "roll",
            type: "button",
            label: label,
            callback: (event, button, dialog) => {
              log("Roll context", event, button, dialog)
              let position = dialog?.position
              game.user.setFlag(SYSTEM.id, "roll-dialog-pos", foundry.utils.duplicate(position))
              const output = Array.from(button.form.elements).reduce((obj, input) => {
                if (input.name) obj[input.name] = input.value
                return obj
              }, {})
              return output
            },
          },
        ],
        actions: {
          "selectGranted": (event, button) => {
            hasGrantedDice = event.target.checked
          },
          "selectBeyondSkill": (event, button) => {
            beyondSkill = button.checked
          },
          "selectPointBlank": (event, button) => {
            pointBlank = button.checked
          },
          "selectLetItFly": (event, button) => {
            letItFly = button.checked
          },
          "saveSpellCheck": (event, button) => {
            saveSpell = button.checked
          },
          "gotoToken": (event, button) => {
            let tokenId = $(button).data("tokenId")
            let token = canvas.tokens?.get(tokenId)
            if (token) {
              canvas.animatePan({ x: token.x, y: token.y, duration: 200 })
              canvas.tokens.releaseAll()
              token.control({ releaseOthers: true })
            }
          }
        },
        rejectClose: false // Click on Close button will not launch an error
      })
    }

    // If the user cancels the dialog, exit
    if (rollContext === null) return
    log("rollContext", rollContext, hasGrantedDice)
    rollContext.saveSpell = saveSpell // Update fucking flag

    let fullModifier = 0
    let titleFormula = ""
    dice = rollContext.changeDice || dice
    if (hasModifier) {
      let bonus = Number(options.rollTarget.value)
      fullModifier = rollContext.modifier === "" ? 0 : parseInt(rollContext.modifier, 10) + bonus
      fullModifier += (rollContext.saveSpell) ? (options.rollTarget.actorModifiers?.saveModifier ?? 0) : 0
      if (Number(rollContext.attackerAim) > 0) {
        fullModifier += Number(rollContext.attackerAim)
      }

      if (fullModifier === 0) {
        modifierFormula = "0"
      } else {
        let modAbs = Math.abs(fullModifier)
        modifierFormula = `D${modAbs + 1} - 1`
      }
      if (hasStaticModifier) {
        modifierFormula += ` + ${options.rollTarget.staticModifier}`
      }
      let sign = fullModifier < 0 ? "-" : "+"
      if (hasExplode) {
        titleFormula = `${dice}E ${sign} ${modifierFormula}`
      } else {
        titleFormula = `${dice} ${sign} ${modifierFormula}`
      }
    } else {
      modifierFormula = "0"
      fullModifier = 0
      baseFormula = `${dice}`
      if (hasExplode) {
        titleFormula = `${dice}E`
      } else {
        titleFormula = `${dice}`
      }
    }

    // Latest addition : favor choice at point blank range
    if (pointBlank) {
      rollContext.favor = "favor"
    }
    if (beyondSkill) {
      rollContext.favor = "disfavor"
    }

    // Specific pain case
    if (options.rollType === "save" && options.rollTarget.rollKey === "pain" || options.rollTarget.rollKey === "paincourage") {
      baseFormula = options.rollTarget.rollDice
      titleFormula = `${dice}`
      modifierFormula = "0"
      fullModifier = 0
    }

    // Specific pain/poison/contagion case
    if (options.rollType === "save" && (options.rollTarget.rollKey === "poison" || options.rollTarget.rollKey === "contagion")) {
      hasD30 = false
      hasStaticModifier = true
      modifierFormula = ` + ${Math.abs(fullModifier)}`
      titleFormula = `${dice}E + ${Math.abs(fullModifier)}`
    }

    if (letItFly) {
      baseFormula = "1D20"
      titleFormula = `1D20E`
      modifierFormula = "0"
      fullModifier = 0
      hasFavor = false
      hasExplode = true
      rollContext.favor = "none"
    }

    const maxMatch = baseFormula ? baseFormula.match(/\d+$/) : null
    maxValue = maxMatch ? Number(maxMatch[0]) : 0

    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,
      isDamage: options.isDamage,
      pointBlank,
      beyondSkill,
      letItFly,
      hasGrantedDice,
      titleFormula,
      targetName,
      ...rollContext,
    }

    /**
     * A hook event that fires before the roll is made.
     * @function
     * @memberof hookEvents
     * @param {Object} options          Options for the roll.
     * @param {Object} rollData         All data related to the roll.
     * @returns {boolean}               Explicitly return `false` to prevent roll to be made.
     */
    if (Hooks.call("fvtt-lethal-fantasy.preRoll", options, rollData) === false) return

    let rollBase = new this(baseFormula, options.data, rollData)
    const rollModifier = new Roll(modifierFormula, options.data, rollData)
    await rollModifier.evaluate()
    await rollBase.evaluate()

    let rollFavor
    let badResult
    if (rollContext.favor === "favor") {
      rollFavor = new this(baseFormula, options.data, rollData)
      await rollFavor.evaluate()
      log("Favor dice", {
        rollBaseTotal: rollBase.total,
        rollFavorTotal: rollFavor.total,
        rollBaseResults: rollBase.dice.map(d => d.results.map(r => r.result)),
        rollFavorResults: rollFavor.dice.map(d => d.results.map(r => r.result)),
        baseFormula
      })
      if (game?.dice3d) {
        game.dice3d.showForRoll(rollFavor, game.user, true)
      }
      if (Number(rollFavor.result) > Number(rollBase.result)) {
        badResult = rollBase.result
        rollBase = rollFavor
      } else {
        badResult = rollFavor.result
      }
      rollFavor = null
    }

    if (rollContext.favor === "disfavor") {
      rollFavor = new this(baseFormula, options.data, rollData)
      await rollFavor.evaluate()
      log("Disfavor dice", {
        rollBaseTotal: rollBase.total,
        rollFavorTotal: rollFavor.total,
        rollBaseResults: rollBase.dice.map(d => d.results.map(r => r.result)),
        rollFavorResults: rollFavor.dice.map(d => d.results.map(r => r.result)),
        baseFormula
      })
      if (game?.dice3d) {
        game.dice3d.showForRoll(rollFavor, game.user, true)
      }
      if (Number(rollFavor.result) < Number(rollBase.result)) {
        badResult = rollBase.result
        rollBase = rollFavor
      } else {
        badResult = rollFavor.result
      }
      rollFavor = null
    }

    if (options.forceNoD30) {
      hasD30 = false
    }

    if (hasD30) {
      let rollD30 = await new Roll("1D30").evaluate()
      if (game?.dice3d) {
        game.dice3d.showForRoll(rollD30, game.user, true)
      }
      options.D30result = rollD30.total

      // Compute isRanged for D30: covers defense (isRangedDefense), monster ranged attacks (attackMode),
      // and PC weapon attacks (isRangedAttack or weaponType)
      const isRangedForD30 = options.isRangedDefense
        || options.rollTarget?.attackMode === "ranged"
        || options.rollTarget?.isRangedAttack === true
        || options.rollTarget?.weapon?.system?.weaponType === "ranged"
      const d30Message = D30Roll.getResult(
        rollD30.total,
        options.rollType,
        options.rollTarget?.weapon,
        { isRanged: isRangedForD30, isSpellSave: saveSpell }
      )
      options.D30message = d30Message
    }

    let rollTotal = 0
    let diceResults = []
    let resultType
    let diceSum = 0

    let singleDice = `1D${maxValue}`
    for (let i = 0; i < rollBase.dice.length; i++) {
      const dieResults = rollBase.dice[i].results
      const resultCount = dieResults.length
      for (let j = 0; j < resultCount; j++) {
        let diceResult = dieResults[j].result
        diceResults.push({ dice: `${singleDice.toUpperCase()}`, value: diceResult })
        diceSum += diceResult
        if (hasMaxValue) {
          while (diceResult === maxValue) {
            let r = await new Roll(baseFormula).evaluate()
            diceResult = r.dice[0].results[0].result
            diceResults.push({ dice: `${singleDice.toUpperCase()}-1`, value: diceResult - 1 })
            diceSum += (diceResult - 1)
            // Add to DieTerm results so DSN/Foundry display shows explosion dice
            dieResults.push({ result: diceResult, active: true })
          }
        }
      }
    }

    if (hasGrantedDice && options.rollTarget.grantedDice && options.rollTarget.grantedDice !== "") {
      titleFormula += ` + ${options.rollTarget.grantedDice.toUpperCase()}`
      let grantedRoll = new Roll(options.rollTarget.grantedDice)
      await grantedRoll.evaluate()
      if (game?.dice3d) {
        await game.dice3d.showForRoll(grantedRoll, game.user, true)
      }
      diceResults.push({ dice: `${options.rollTarget.grantedDice.toUpperCase()}`, value: grantedRoll.total })
      rollTotal += grantedRoll.total
    }

    if (fullModifier !== 0) {
      diceResults.push({ dice: `${rollModifier.formula.toUpperCase()}`, value: rollModifier.total })
      if (fullModifier < 0) {
        rollTotal += Math.max(diceSum - rollModifier.total, 0)
      } else {
        rollTotal += diceSum + rollModifier.total
      }
    } else {
      rollTotal += diceSum
    }

    rollBase.options.resultType = resultType
    rollBase.options.rollTotal = rollTotal
    rollBase.options.diceResults = diceResults
    rollBase.options.rollTarget = options.rollTarget
    rollBase.options.titleFormula = titleFormula
    rollBase.options.D30result = options.D30result
    rollBase.options.D30message = options.D30message
    rollBase.options.badResult = badResult
    rollBase.options.rollType = options.rollType
    rollBase.options.actorId = options.actorId
    rollBase.options.actorName = options.actorName
    rollBase.options.actorImage = options.actorImage
    rollBase.options.rollData = foundry.utils.duplicate(rollData)
    rollBase.options.defenderId = options.defenderId
    rollBase.options.defenderTokenId = options.defenderTokenId
    rollBase.options.extraShieldDr = options.extraShieldDr || 0
    rollBase.options.damageTier = options.damageTier || "standard"
    rollBase.options.d30Bleed = options.d30Bleed || false
    rollBase.options.d30DamageMultiplier = options.d30DamageMultiplier || 1
    rollBase.options.d30DrMultiplier = options.d30DrMultiplier || 1

    /**
     * A hook event that fires after the roll has been made.
     * @function
     * @memberof hookEvents
     * @param {Object} options          Options for the roll.
     * @param {Object} rollData         All data related to the roll.
     * @param {LethalFantasyRoll} roll        The resulting roll.
     * @returns {boolean}               Explicitly return `false` to prevent roll to be made.
     */
    if (Hooks.call("fvtt-lethal-fantasy.Roll", options, rollData, rollBase) === false) return

    return rollBase
  } finally {
    // Clear one-shot flag so it doesn't leak to subsequent non-spell saves
    if (game.lethalFantasy) game.lethalFantasy.spellDefense = false
  }
}
