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

const t = key => game.i18n.localize(key)
const f = (key, data = {}) => game.i18n.format(key, data)

function joinParts(parts) {
  return parts.filter(Boolean).join(" ")
}

function getChatModeLabel(mode) {
  return game.i18n.localize(`MGNE.Chat.Mode.${mode}`) || mode
}

function pickRandom(list = []) {
  if (!list.length) return ""
  return list[Math.floor(Math.random() * list.length)]
}

function stepDownDie(die, steps = 1, track = SYSTEM.usageDice) {
  let current = die
  for (let step = 0; step < steps; step += 1) {
    const index = track.indexOf(current)
    if (index === -1) return current
    current = track[Math.min(index + 1, track.length - 1)]
  }
  return current
}

function getFirstTargetActor() {
  const target = game.user?.targets ? Array.from(game.user.targets)[0] : null
  return target?.actor ?? null
}

function serializeForm(form) {
  return Array.from(form.elements).reduce((acc, element) => {
    if (!element.name) return acc
    acc[element.name] = element.type === "checkbox" ? element.checked : element.value
    return acc
  }, {})
}

function numericOptions(min, max, current = null) {
  const resolvedMin = Math.min(min, Number.isFinite(current) ? current : min)
  const resolvedMax = Math.max(max, Number.isFinite(current) ? current : max)
  return Array.from({ length: (resolvedMax - resolvedMin) + 1 }, (_, index) => {
    const value = resolvedMin + index
    return { value, label: String(value) }
  })
}

async function renderCard(context) {
  const outcomeClass = `${context.outcome ?? ""}`
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-|-$/g, "")
  const eyebrow = context.eyebrow ?? getChatModeLabel(context.mode ?? "generic")
  const normalizedEyebrow = `${eyebrow}`.trim().toLowerCase()
  const normalizedLabel = `${context.label ?? ""}`.trim().toLowerCase()

  // Render dice tooltip HTML if a roll was provided
  const diceTooltip = context._roll ? await context._roll.render() : null

  return foundry.applications.handlebars.renderTemplate(`systems/${SYSTEM_ID}/templates/chat-message.hbs`, {
    ...context,
    modeClass: context.mode ?? "generic",
    eyebrow: "",
    outcomeClass,
    diceTooltip,
  })
}

export default class MGNERoll {
  static async promptCheck({ actor, abilityId, label, baseDR = 12, modifier = 0, rollType = "check", item = null }) {
    const abilityLabel = SYSTEM.abilities[abilityId]?.label ?? abilityId
    const content = await foundry.applications.handlebars.renderTemplate(`systems/${SYSTEM_ID}/templates/roll-dialog.hbs`, {
      actorName: actor.name,
      label,
      abilityLabel,
      baseDR,
      modifier,
      drOptions: numericOptions(6, 20, baseDR),
      modifierOptions: numericOptions(-6, 6, modifier),
      omens: actor.system.omens?.current ?? 0,
      rollType,
    })

    const dialogData = await foundry.applications.api.DialogV2.wait({
      window: { title: label },
      classes: ["mgne", "roll-dialog"],
      content,
      buttons: [
        {
          label: t("MGNE.Common.Roll"),
          icon: "fa-solid fa-dice-d20",
          callback: (_event, button) => serializeForm(button.form),
        },
      ],
      rejectClose: false,
    })

    if (!dialogData) return null

    const dialogModifier = Number.parseInt(dialogData.modifier ?? 0, 10) || 0
    const spendOmen = Boolean(dialogData.spendOmen)
    // Re-read omens after dialog close to avoid race condition (omen could have changed)
    const currentOmensAfterDialog = actor.system.omens?.current ?? 0
    const canSpendOmen = spendOmen && currentOmensAfterDialog > 0
    const dr = (Number.parseInt(dialogData.dr ?? baseDR, 10) || baseDR) - (canSpendOmen ? 4 : 0)
    const abilityValue = actor.system.abilities?.[abilityId]?.value ?? 0
    const sign = dialogModifier >= 0 ? "+" : "-"
    const formula = dialogModifier === 0 ? `1d20 + ${abilityValue}` : `1d20 + ${abilityValue} ${sign} ${Math.abs(dialogModifier)}`
    const roll = await (new Roll(formula)).evaluate()
    const natural = roll.dice?.[0]?.results?.[0]?.result ?? roll.total

    if (canSpendOmen) {
      await actor.update({ "system.omens.current": Math.max(0, currentOmensAfterDialog - 1) })
    }

    const critical = natural === 20
    const fumble = natural === 1
    const success = critical || (!fumble && roll.total >= dr)
    const outcome = critical ? t("MGNE.Roll.OutcomeCriticalSuccess") : fumble ? t("MGNE.Roll.OutcomeFumble") : success ? t("MGNE.Roll.OutcomeSuccess") : t("MGNE.Roll.OutcomeFailure")

    let specialText = ""
    if (critical && rollType === "resonance") specialText = pickRandom(SYSTEM.tables.eucatastrophe)
    else if (fumble && rollType === "resonance") specialText = pickRandom(SYSTEM.tables.catastrophe)
    else if (critical) {
      specialText = rollType === "attack"
        ? t("MGNE.Roll.CriticalAttack")
        : rollType === "defense"
          ? t("MGNE.Roll.DefenseCritical")
          : pickRandom(SYSTEM.tables.triumphs)
    } else if (fumble) {
      specialText = rollType === "attack" ? t("MGNE.Roll.AttackFumble") : rollType === "defense" ? t("MGNE.Roll.DefenseFumble") : pickRandom(SYSTEM.tables.mishaps)
    }

    const actorOmens = actor.system.omens?.current ?? 0
    let omenNeutralizeReminder = ""
    let omenRerollReminder = ""
    if (actorOmens > 0) {
      if (critical) omenNeutralizeReminder = f("MGNE.Roll.OmenNeutralizeCrit", { omens: actorOmens })
      else if (fumble) omenNeutralizeReminder = f("MGNE.Roll.OmenNeutralizeFumble", { omens: actorOmens })
      else omenRerollReminder = f("MGNE.Roll.OmenRerollReminder", { omens: actorOmens })
    }

    const showDamageButton = rollType === "attack" && (success || critical) && !!item
    const showOmenRerollButton = Boolean(omenRerollReminder)
    const contentHtml = await renderCard({
      mode: "check",
      actorName: actor.name,
      actorImg: actor.img,
      label,
      subtitle: f("MGNE.Roll.CheckSubtitle", { ability: abilityLabel, dr }),
      formula: roll.formula,
      total: roll.total,
      outcome,
      specialText,
      omenNeutralizeReminder,
      omenRerollReminder,
      showDamageButton,
      damageActorId: showDamageButton ? actor.id : null,
      damageItemId: showDamageButton ? item.id : null,
      damageFormula: showDamageButton ? (item.system.damage || "1") : null,
      damageCritical: showDamageButton && critical,
      showOmenRerollButton,
      omenRerollActorId: showOmenRerollButton ? actor.id : null,
      omenRerollAbilityId: showOmenRerollButton ? abilityId : null,
      omenRerollLabel: showOmenRerollButton ? label : null,
      omenRerollBaseDR: showOmenRerollButton ? baseDR : null,
      omenRerollModifier: showOmenRerollButton ? dialogModifier : 0,
      omenRerollRollType: showOmenRerollButton ? rollType : null,
      omenRerollItemId: showOmenRerollButton ? item?.id : null,
      _roll: roll,
    })

    await ChatMessage.create({
      speaker: ChatMessage.getSpeaker({ actor }),
      rolls: [roll],
      content: contentHtml,
    })

    return { roll, success, critical, fumble, outcome, specialText, dr, abilityId, item }
  }

  static async rollMorale(actor) {
    const target = actor.system.morale ?? 7
    const roll = await (new Roll("2d6")).evaluate()
    const broken = roll.total >= target
    const contentHtml = await renderCard({
      mode: "morale",
      actorName: actor.name,
      actorImg: actor.img,
      label: t("MGNE.Roll.MoraleCheck"),
      subtitle: f("MGNE.Roll.TargetSubtitle", { target }),
      formula: roll.formula,
      total: roll.total,
      outcome: broken ? t("MGNE.Roll.OutcomeBroken") : t("MGNE.Roll.OutcomeSteady"),
      specialText: broken ? t("MGNE.Roll.MoraleBrokenText") : "",
      _roll: roll,
    })

    await ChatMessage.create({
      speaker: ChatMessage.getSpeaker({ actor }),
      rolls: [roll],
      content: contentHtml,
    })

    return { roll, broken }
  }

  static async rollDamage({ actor, item }) {
    const damageBonus = await actor.consumePendingDamageBonus(item.id)
    const multiplier = damageBonus?.multiplier ?? 1
    const baseFormula = item.system.damage || "1"
    const formula = multiplier > 1 ? `${multiplier} * (${baseFormula})` : baseFormula

    const actorOmens = actor.system.omens?.current ?? 0
    let maximize = false
    if (actorOmens > 0) {
      const choice = await foundry.applications.api.DialogV2.wait({
        window: { title: f("MGNE.Roll.ItemDamageLabel", { item: item.name }) },
        classes: ["mgne", "roll-dialog"],
        content: `<section class="mgne-roll-dialog"><p>${f("MGNE.RollDialog.OmenMaximizePrompt", { omens: actorOmens })}</p></section>`,
        buttons: [
          { action: "roll", label: t("MGNE.Common.Roll"), icon: "fa-solid fa-dice", callback: () => "roll" },
          { action: "maximize", label: t("MGNE.RollDialog.SpendOmenMaximize"), icon: "fa-solid fa-star", callback: () => "maximize" },
        ],
        rejectClose: false,
      })
      if (choice === null) return null
      maximize = choice === "maximize"
    }

    const roll = await (new Roll(formula)).evaluate(maximize ? { maximize: true } : {})
    if (maximize) {
      // Re-read omens after dialog to avoid overwriting concurrent changes
      const currentOmens = actor.system.omens?.current ?? 0
      await actor.update({ "system.omens.current": Math.max(0, currentOmens - 1) })
    }
    const isCritical = multiplier > 1
    const contentHtml = await renderCard({
      mode: "damage",
      actorName: actor.name,
      actorImg: actor.img,
      label: `${item.name} Damage`,
      subtitle: null,
      formula: roll.formula,
      total: roll.total,
      outcome: t("MGNE.Roll.OutcomeRolled"),
      specialText: isCritical ? t("MGNE.Roll.CriticalDamageApplied") : "",
      omenMaximized: maximize,
      showApplyButton: true,
      damageTotal: roll.total,
      damageCritical: isCritical,
      _roll: roll,
    })

    await ChatMessage.create({
      speaker: ChatMessage.getSpeaker({ actor }),
      rolls: [roll],
      content: contentHtml,
    })

    return { roll }
  }

  static async rollFlatDamage({ actor, label, formula }) {
    const damageBonus = await actor.consumePendingDamageBonus("profile-attack")
    const multiplier = damageBonus?.multiplier ?? 1
    const baseFormula = formula || "1"
    const resolvedFormula = multiplier > 1 ? `${multiplier} * (${baseFormula})` : baseFormula
    const roll = await (new Roll(resolvedFormula)).evaluate()
    const isCritical = multiplier > 1
    const contentHtml = await renderCard({
      mode: "damage",
      actorName: actor.name,
      actorImg: actor.img,
      label,
      subtitle: null,
      formula: roll.formula,
      total: roll.total,
      outcome: t("MGNE.Roll.OutcomeRolled"),
      specialText: isCritical ? t("MGNE.Roll.CriticalDamageApplied") : "",
      showApplyButton: true,
      damageTotal: roll.total,
      damageCritical: isCritical,
      _roll: roll,
    })

    await ChatMessage.create({
      speaker: ChatMessage.getSpeaker({ actor }),
      rolls: [roll],
      content: contentHtml,
    })

    return { roll }
  }

  static async rollArmorSave(actor) {
    const formula = actor.getArmorRollFormula()
    const items = actor.getEquippedArmorItems()

    if (formula === "0") {
      ui.notifications.warn(t("MGNE.Notification.NoArmorEquipped"))
      return null
    }

    const roll = await (new Roll(formula)).evaluate()
    const armorNames = items.map(i => i.name).join(" + ")

    const contentHtml = await renderCard({
      mode: "armor",
      actorName: actor.name,
      actorImg: actor.img,
      label: t("MGNE.Roll.ArmorSave"),
      subtitle: armorNames,
      formula: roll.formula,
      total: roll.total,
      outcome: f("MGNE.Roll.ArmorAbsorbed", { amount: roll.total }),
      _roll: roll,
    })

    await ChatMessage.create({
      speaker: ChatMessage.getSpeaker({ actor }),
      rolls: [roll],
      content: contentHtml,
    })

    return roll
  }

  static async rollUsage(item) {
    const currentDie = item.system.usageDie
    if (!currentDie || currentDie === "depleted") {
      ui.notifications.warn(f("MGNE.Notification.ItemDepleted", { item: item.name }))
      return null
    }

    const roll = await (new Roll(`1${currentDie}`)).evaluate()
    const depleted = roll.total <= 2
    const nextDie = depleted ? stepDownDie(currentDie) : currentDie
    const updates = { "system.usageDie": nextDie }

    if (item.type === "resonance-core" && nextDie === "depleted") updates["system.burnedOut"] = true
    await item.update(updates)

    const contentHtml = await renderCard({
      mode: "usage",
      actorName: item.parent?.name ?? item.name,
      actorImg: item.img,
      label: f("MGNE.Roll.ItemUsageLabel", { item: item.name }),
      subtitle: f("MGNE.Roll.CurrentDie", { die: currentDie.toUpperCase() }),
      formula: roll.formula,
      total: roll.total,
      outcome: depleted ? f("MGNE.Roll.DowngradedTo", { die: nextDie.toUpperCase() }) : t("MGNE.Roll.NoChange"),
      specialText: depleted && nextDie === "depleted" ? t("MGNE.Roll.ItemNowDepleted") : "",
      _roll: roll,
    })

    await ChatMessage.create({
      speaker: ChatMessage.getSpeaker({ actor: item.parent ?? null }),
      rolls: [roll],
      content: contentHtml,
    })

    return { roll, depleted, nextDie }
  }

  static async rollDurability(item) {
    const currentDie = item.system.durabilityDie
    if (!currentDie || currentDie === "depleted") {
      ui.notifications.warn(f("MGNE.Notification.ItemDurabilityDepleted", { item: item.name }))
      return null
    }

    const roll = await (new Roll(`1${currentDie}`)).evaluate()
    const degraded = roll.total <= 2
    const nextDie = degraded ? stepDownDie(currentDie) : currentDie
    const nowBroken = degraded && nextDie === "depleted"
    const updates = { "system.durabilityDie": nextDie }
    if (nowBroken) {
      updates["system.broken"] = true
      if ("equipped" in (item.system ?? {})) updates["system.equipped"] = false
    }
    await item.update(updates)

    const contentHtml = await renderCard({
      mode: "durability",
      actorName: item.parent?.name ?? item.name,
      actorImg: item.img,
      label: f("MGNE.Roll.DurabilityLabel", { item: item.name }),
      subtitle: f("MGNE.Roll.CurrentDie", { die: currentDie.toUpperCase() }),
      formula: roll.formula,
      total: roll.total,
      outcome: degraded
        ? f("MGNE.Roll.DowngradedTo", { die: nextDie.toUpperCase() })
        : t("MGNE.Roll.NoChange"),
      specialText: nowBroken ? t("MGNE.Roll.ItemNowBroken") : "",
      _roll: roll,
    })

    await ChatMessage.create({
      speaker: ChatMessage.getSpeaker({ actor: item.parent ?? null }),
      rolls: [roll],
      content: contentHtml,
    })

    return { roll, degraded, nextDie, nowBroken }
  }

  static async applyDamageCard({ actor, sourceActor = null, sourceItem = null, amount, armorRoll = null, appliedDamage, newHp, breakText = "", defenseFumbleText = "", criticalArmorText = "" }) {
    const contentHtml = await renderCard({
      mode: "apply-damage",
      actorName: actor.name,
      actorImg: actor.img,
      label: f("MGNE.Roll.TakesDamageLabel", { actor: actor.name }),
      subtitle: sourceItem
        ? (sourceActor
          ? f("MGNE.Roll.DamageSourceWithActor", { item: sourceItem.name, actor: sourceActor.name })
          : f("MGNE.Roll.DamageSourceItem", { item: sourceItem.name }))
        : t("MGNE.Roll.DirectDamage"),
      formula: armorRoll?.formula ?? "",
      total: amount,
      outcome: f("MGNE.Roll.HPNow", { hp: newHp }),
      specialText: joinParts([
        defenseFumbleText,
        armorRoll ? f("MGNE.Roll.ArmorAbsorbed", { amount: armorRoll.total }) : "",
        f("MGNE.Roll.AppliedDamageText", { amount: appliedDamage }),
        criticalArmorText,
        breakText ? f("MGNE.Roll.BreakText", { text: breakText }) : "",
      ]),
      _roll: armorRoll ?? null,
    })

    await ChatMessage.create({
      speaker: ChatMessage.getSpeaker({ actor }),
      rolls: armorRoll ? [armorRoll] : [],
      content: contentHtml,
    })
  }

  static async createRestCard({ actor, label, subtitle, roll, outcome, specialText = "" }) {
    return this.createActionCard({ mode: "rest", actor, label, subtitle, roll, outcome, specialText })
  }

  static async createActionCard({ mode = "action", actor = null, label, subtitle = "", roll, outcome, specialText = "" }) {
    const contentHtml = await renderCard({
      mode,
      actorName: actor?.name ?? "",
      actorImg: actor?.img ?? "",
      label,
      subtitle,
      formula: roll.formula,
      total: roll.total,
      outcome,
      specialText,
      _roll: roll,
    })

    await ChatMessage.create({
      speaker: ChatMessage.getSpeaker({ actor }),
      rolls: [roll],
      content: contentHtml,
    })
  }

  static getFirstTargetActor() {
    return getFirstTargetActor()
  }

  static stepDownDie(die, steps = 1, track = SYSTEM.usageDice) {
    return stepDownDie(die, steps, track)
  }
}
