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

const t = key => game.i18n.localize(key)
const f = (key, data = {}) => game.i18n.format(key, data)
const PENDING_DAMAGE_FLAG = "pendingDamageBonus"
const PENDING_DEFENSE_FLAG = "pendingDefenseFumble"

function formatActionLabel(baseLabel, actionLabel) {
  const trimmedBase = `${baseLabel ?? ""}`.trim()
  if (!trimmedBase) return actionLabel
  return trimmedBase.toLowerCase().endsWith(actionLabel.toLowerCase()) ? trimmedBase : `${trimmedBase} ${actionLabel}`
}

function normalizeGenericActionLabel(baseLabel, genericLabel) {
  const trimmedBase = `${baseLabel ?? ""}`.trim()
  if (!trimmedBase) return genericLabel

  const normalizedWords = trimmedBase
    .split(/\s+/)
    .filter(Boolean)
    .map(word => word.toLowerCase())

  return normalizedWords.every(word => word === genericLabel.toLowerCase()) ? genericLabel : trimmedBase
}

function maxDieByRank(items, property) {
  const rank = { d12: 0, d10: 1, d8: 2, d6: 3, d4: 4, d2: 5, 0: 6, depleted: 7 }
  return [...items].sort((left, right) => (rank[left.system[property]] ?? 99) - (rank[right.system[property]] ?? 99))[0] ?? null
}

export default class MGNEActor extends Actor {
  async setPendingDamageBonus(contextId, multiplier = 2) {
    await this.setFlag(SYSTEM_ID, PENDING_DAMAGE_FLAG, { contextId, multiplier })
  }

  async consumePendingDamageBonus(contextId) {
    const bonus = this.getFlag(SYSTEM_ID, PENDING_DAMAGE_FLAG)
    if (!bonus || bonus.contextId !== contextId) return null
    await this.unsetFlag(SYSTEM_ID, PENDING_DAMAGE_FLAG)
    return bonus
  }

  async setPendingDefenseFumble() {
    await this.setFlag(SYSTEM_ID, PENDING_DEFENSE_FLAG, { active: true })
  }

  async consumePendingDefenseFumble() {
    const pending = this.getFlag(SYSTEM_ID, PENDING_DEFENSE_FLAG)
    if (!pending?.active) return null
    await this.unsetFlag(SYSTEM_ID, PENDING_DEFENSE_FLAG)
    return pending
  }

  getEquippedArmorItems() {
    return this.items.filter(item => ["armor", "shield"].includes(item.type) && item.system.equipped && !item.system.broken && item.system.armorDie !== "0")
  }

  getArmorRollFormula() {
    const dice = this.getEquippedArmorItems().map(item => `1${item.system.armorDie}`)
    return dice.length ? dice.join(" + ") : "0"
  }

  async degradeArmorStep(steps = 1) {
    const item = maxDieByRank(this.getEquippedArmorItems(), "armorDie")
    if (!item) return null

    const nextDie = MGNERoll.stepDownDie(item.system.armorDie, steps, SYSTEM.armorDice)
    const updates = { "system.armorDie": nextDie }
    if (nextDie === "0") {
      updates["system.broken"] = true
      updates["system.equipped"] = false
    }

    await item.update(updates)
    return { item, nextDie }
  }

  async rollAbility(abilityId, options = {}) {
    return MGNERoll.promptCheck({
      actor: this,
      abilityId,
      label: options.label ?? f("MGNE.Roll.CheckLabel", { ability: SYSTEM.abilities[abilityId]?.label ?? abilityId }),
      baseDR: options.baseDR ?? 12,
      rollType: options.rollType ?? "check",
      item: options.item ?? null,
    })
  }

  async rollDefense() {
    const result = await this.rollAbility("agility", { label: t("MGNE.Roll.DefenseCheck"), rollType: "defense" })
    if (result?.fumble) await this.setPendingDefenseFumble()
    return result
  }

  async rollArmorSave() {
    return MGNERoll.rollArmorSave(this)
  }

  async rollWeapon(itemId) {
    const item = this.items.get(itemId)
    if (!item) return null
    if (item.system.broken) {
      ui.notifications.warn(f("MGNE.Notification.ItemBroken", { item: item.name }))
      return null
    }

    const abilityId = item.system.category === "ranged" ? "presence" : "strength"
    const result = await this.rollAbility(abilityId, {
      label: f("MGNE.Roll.ItemAttackLabel", { item: item.name }),
      rollType: "attack",
      item,
    })

    if (!result) return null
    if (result.fumble) await item.update({ "system.broken": true })
    if (result.critical) {
      await this.setPendingDamageBonus(item.id)
    }
    return result
  }

  async rollDamage(itemId) {
    const item = this.items.get(itemId)
    if (!item) return null
    return MGNERoll.rollDamage({
      actor: this,
      item,
      targetActor: MGNERoll.getFirstTargetActor(),
    })
  }

  async rollProfileAttack() {
    const attackBaseLabel = normalizeGenericActionLabel(this.system.attack?.label ?? t("MGNE.Common.Attack"), t("MGNE.Common.Attack"))
    const attackLabel = formatActionLabel(attackBaseLabel, t("MGNE.Common.Attack"))
    // Creatures have no ability scores; ability value defaults to 0 via roll.mjs
    const result = await MGNERoll.promptCheck({
      actor: this,
      abilityId: "strength",
      label: attackLabel,
      rollType: "attack",
    })

    if (result?.critical) {
      await this.setPendingDamageBonus("profile-attack")
    }

    return result
  }

  async rollProfileDamage() {
    const attackBaseLabel = normalizeGenericActionLabel(this.system.attack?.label ?? t("MGNE.Common.Attack"), t("MGNE.Common.Attack"))
    const damageLabel = attackBaseLabel === t("MGNE.Common.Attack")
      ? t("MGNE.Common.Damage")
      : formatActionLabel(attackBaseLabel, t("MGNE.Common.Damage"))
    return MGNERoll.rollFlatDamage({
      actor: this,
      label: damageLabel,
      formula: this.system.attack?.damage ?? "1",
      targetActor: MGNERoll.getFirstTargetActor(),
    })
  }

  async rollResonancePerDay({ resetUsed = true, apply = true } = {}) {
    const resonanceRoll = await (new Roll("1d4")).evaluate()
    const presence = this.system.abilities?.presence?.value ?? 0
    const max = Math.max(1, presence + resonanceRoll.total)
    const updates = { "system.resonance.max": max }
    if (resetUsed) updates["system.resonance.used"] = 0
    if (apply) await this.update(updates)
    return { resonanceRoll, max }
  }

  async rollResonation(itemId) {
    const item = this.items.get(itemId)
    if (!item || item.type !== "resonance-core") return null
    if (item.system.burnedOut || item.system.usageDie === "depleted") {
      ui.notifications.warn(f("MGNE.Notification.ItemBurnedOut", { item: item.name }))
      return null
    }
    if (this.system.resonance?.blocked) {
      ui.notifications.warn(f("MGNE.Notification.ResonationBlocked", { actor: this.name }))
      return null
    }
    if ((this.system.resonance?.used ?? 0) >= (this.system.resonance?.max ?? 0)) {
      ui.notifications.warn(f("MGNE.Notification.ResonancePerDayReached", { actor: this.name }))
      return null
    }

    const result = await this.rollAbility("presence", {
      label: f("MGNE.Roll.InvocationLabel", { item: item.name }),
      rollType: "resonance",
      item,
    })

    if (!result) return null

    await this.update({ "system.resonance.used": (this.system.resonance.used ?? 0) + 1 })

    if (!result.success) {
      const feedbackRoll = await (new Roll("1d2")).evaluate()
      await this.applyDamage(feedbackRoll.total, { sourceItem: item, ignoreArmor: true, chat: true })
      await this.update({ "system.resonance.blocked": true })
      ui.notifications.warn(f("MGNE.Notification.ResonationFeedbackBlocked", { actor: this.name }))
    }

    return result
  }

  async rollMorale() {
    return MGNERoll.rollMorale(this)
  }

  async toggleItemEquipped(itemId) {
    const item = this.items.get(itemId)
    if (!item) return null
    if (item.system.broken) {
      ui.notifications.warn(f("MGNE.Notification.ItemBroken", { item: item.name }))
      return null
    }
    return item.update({ "system.equipped": !item.system.equipped })
  }

  async rollUsage(itemId) {
    const item = this.items.get(itemId)
    if (!item) return null
    return item.rollUsage()
  }

  async rollDurability(itemId) {
    const item = this.items.get(itemId)
    if (!item) return null
    return MGNERoll.rollDurability(item)
  }

  async quickRest() {
    const roll = await (new Roll("1d4")).evaluate()
    const hp = this.system.hp?.value ?? 0
    const hpMax = this.system.hp?.max ?? hp
    const healed = Math.max(0, Math.min(hpMax, hp + roll.total) - hp)
    const newHp = Math.min(hpMax, hp + roll.total)

    await this.update({ "system.hp.value": newHp })
    await MGNERoll.createRestCard({
      actor: this,
      label: f("MGNE.Roll.QuickRestLabel", { actor: this.name }),
      subtitle: t("MGNE.Character.QuickRestHelp"),
      roll,
      outcome: f("MGNE.Roll.RestoredHP", { amount: healed }),
      specialText: f("MGNE.Roll.HPNowMax", { hp: newHp, max: hpMax }),
    })

    return { roll, healed, newHp }
  }

  async fullRest() {
    const hp = this.system.hp?.value ?? 0
    const hpMax = this.system.hp?.max ?? hp
    const healRoll = await (new Roll("1d6")).evaluate()
    const omenDie = this.system.omens?.die ?? "d2"
    const omenRoll = await (new Roll(`1${omenDie}`)).evaluate()
    const healed = Math.max(0, Math.min(hpMax, hp + healRoll.total) - hp)
    const newHp = Math.min(hpMax, hp + healRoll.total)

    await this.update({
      "system.hp.value": newHp,
      "system.omens.current": omenRoll.total,
      "system.resonance.blocked": false,
    })

    await MGNERoll.createRestCard({
      actor: this,
      label: f("MGNE.Roll.FullRestLabel", { actor: this.name }),
      subtitle: t("MGNE.Character.FullRestHelp"),
      roll: healRoll,
      outcome: f("MGNE.Roll.RestoredHP", { amount: healed }),
      specialText: `${f("MGNE.Roll.HPNowMax", { hp: newHp, max: hpMax })} ${f("MGNE.Roll.OmensReset", {
        omens: omenRoll.total,
        die: omenDie.toUpperCase(),
        roll: omenRoll.total,
      })}`,
    })

    return { healRoll, omenRoll, healed, newHp, omens: omenRoll.total }
  }

  async syncArtifact(itemId) {
    const item = this.items.get(itemId)
    if (!item || item.type !== "artifact") return null

    if (item.system.synchronized) {
      return item.update({
        "system.synchronized": false,
        "system.synchronizedTo": "",
      })
    }

    const syncLimit = Math.max(0, this.system.syncLimit ?? this.system.abilities?.toughness?.value ?? 0)
    const used = this.system.artifactSync?.used ?? 0
    if (used >= syncLimit) {
      ui.notifications.warn(f("MGNE.Notification.CannotSyncMore", { actor: this.name }))
      return null
    }

    await this.update({ "system.artifactSync.used": used + 1 })
    return item.update({
      "system.synchronized": true,
      "system.synchronizedTo": this.id,
    })
  }

  async resetDaily() {
    const omenDie = this.system.omens?.die ?? "d2"
    const omenRoll = await (new Roll(`1${omenDie}`)).evaluate()
    const { max: resonanceMax } = await this.rollResonancePerDay({ resetUsed: false, apply: false })

    await this.update({
      "system.omens.current": omenRoll.total,
      "system.resonance.max": resonanceMax,
      "system.resonance.used": 0,
      "system.resonance.blocked": false,
      "system.artifactSync.used": 0,
      "system.survival.salvationUsed": false,
    })

    const syncedArtifacts = this.items.filter(item => item.type === "artifact" && item.system.synchronized)
    for (const artifact of syncedArtifacts) {
      await artifact.update({
        "system.synchronized": false,
        "system.synchronizedTo": "",
      })
    }
  }

  async applyDamage(amount, { sourceActor = null, sourceItem = null, ignoreArmor = false, critical = false, chat = true } = {}) {
    const defenseFumble = await this.consumePendingDefenseFumble()
    const incomingDamage = defenseFumble ? amount * 2 : amount
    const hp = this.system.hp?.value ?? 0
    let armorRoll = null
    let absorbed = 0
    if (!ignoreArmor) {
      const formula = this.getArmorRollFormula()
      if (formula !== "0") {
        armorRoll = await (new Roll(formula)).evaluate()
        absorbed = armorRoll.total
      }
    }

    let appliedDamage = Math.max(0, incomingDamage - absorbed)
    let nextHp = hp - appliedDamage
    let breakText = ""
    let defenseFumbleText = ""
    let criticalArmorText = ""

    if (this.type === "character" && hp > 0 && nextHp < 0 && !(this.system.survival?.salvationUsed)) {
      nextHp = 0
      appliedDamage = hp
      await this.update({ "system.survival.salvationUsed": true })
    }

    if (nextHp <= 0 && hp > 0) {
      const breakRoll = await (new Roll("1d4")).evaluate()
      breakText = SYSTEM.tables.breakResults[breakRoll.total] ?? ""
    }

    await this.update({ "system.hp.value": nextHp })

    if (defenseFumble) {
      const degraded = await this.degradeArmorStep()
      defenseFumbleText = degraded
        ? f("MGNE.Roll.DefenseFumbleApplied", { item: degraded.item.name, die: degraded.nextDie.toUpperCase() })
        : t("MGNE.Roll.DefenseFumbleNoArmor")
    }

    if (critical) {
      const degraded = await this.degradeArmorStep()
      criticalArmorText = degraded
        ? f("MGNE.Roll.ArmorDegradedCritical", { item: degraded.item.name, die: degraded.nextDie.toUpperCase() })
        : t("MGNE.Roll.ArmorNothingToDegrade")
    }

    if (chat) {
      await MGNERoll.applyDamageCard({
        actor: this,
        sourceActor,
        sourceItem,
        amount: incomingDamage,
        armorRoll,
        appliedDamage,
        newHp: nextHp,
        breakText,
        defenseFumbleText,
        criticalArmorText,
      })
    }

    return {
      amount: incomingDamage,
      absorbed,
      appliedDamage,
      newHp: nextHp,
      breakText,
      defenseFumbleText,
      criticalArmorText,
      summary: f("MGNE.Chat.DamageSummary", { absorbed, appliedDamage, hp: nextHp }),
    }
  }
}
