/**
 * Chroniques de l'Étrange — Système FoundryVTT
 *
 * Chroniques de l'Étrange est un jeu de rôle édité par Antre-Monde Éditions.
 * Ce système FoundryVTT est une implémentation indépendante et n'est pas
 * affilié à Antre-Monde Éditions,
 * mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
 *
 * @author    LeRatierBretonnien
 * @copyright 2024–2026 LeRatierBretonnien
 * @license   CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
 */

import { MAGICS, SUBTYPES, ASPECT_NAMES, ASPECT_LABELS, ASPECT_ICONS, SYSTEM_ID } from "../../../config/constants.js"
import { rollInitiativePC } from "../../initiative.js"
import { rollForActor } from "../../rolling.js"
import { CDEBaseActorSheet } from "./base.js"

export class CDECharacterSheet extends CDEBaseActorSheet {
  static DEFAULT_OPTIONS = {
    classes: ["character"],
    actions: {
      moveMagicUp: CDECharacterSheet.#onMoveMagicUp,
      moveMagicDown: CDECharacterSheet.#onMoveMagicDown,
      recovery: CDECharacterSheet.#onRecovery,
    },
  }

  static PARTS = {
    main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-character-sheet.html" },
  }

  tabGroups = { primary: "description" }

  async _prepareContext() {
    const context = await super._prepareContext()
    context.equipments = context.items.filter((item) => item.type === "item")
    context.weapons = context.items.filter((item) => item.type === "weapon")
    context.armors = context.items.filter((item) => item.type === "armor")
    context.sanheis = context.items.filter((item) => item.type === "sanhei")
    context.ingredients = context.items.filter((item) => item.type === "ingredient")
    context.spells = context.items.filter((item) => item.type === "spell")
    context.kungfus = context.items.filter((item) => item.type === "kungfu")
    context.CDE = { MAGICS, SUBTYPES }

    // Group spells by discipline key (matches MAGICS keys)
    const spellsByDiscipline = {}
    for (const spell of context.spells) {
      const disc = spell.system?.discipline ?? "internalcinnabar"
      if (!spellsByDiscipline[disc]) spellsByDiscipline[disc] = []
      spellsByDiscipline[disc].push(spell)
    }

    // Build magicsDisplay: only include the 5 relevant specialities per magic type + grimoire
    const systemMagics = context.systemData.magics ?? {}
    const magicEntries = Object.entries(MAGICS).map(([magicKey, magicDef]) => {
      const magicData = systemMagics[magicKey] ?? {}
      return [
        magicKey,
        {
          value: magicData.value ?? 0,
          visible: magicData.visible ?? false,
          speciality: Object.fromEntries(
            Object.keys(magicDef.speciality).map((specKey) => [
              specKey,
              { check: magicData.speciality?.[specKey]?.check ?? false },
            ])
          ),
          grimoire: spellsByDiscipline[magicKey] ?? [],
        },
      ]
    })
    const order = context.systemData.magicOrder ?? []
    if (order.length > 0) {
      magicEntries.sort((a, b) => {
        const ia = order.indexOf(a[0])
        const ib = order.indexOf(b[0])
        if (ia === -1 && ib === -1) return 0
        if (ia === -1) return 1
        if (ib === -1) return -1
        return ia - ib
      })
    }
    context.magicsDisplay = Object.fromEntries(magicEntries)

    return context
  }

  _onRender(context, options) {
    super._onRender(context, options)
    this.#bindInitiativeControls()
    this.#bindPrefs()
    this.#bindRollButtons()
    this.#bindComponentRandomize()
    this.#bindDiagramZoom()
  }

  #bindInitiativeControls() {
    const buttons = this.element?.querySelectorAll(".click-initiative")
    if (!buttons?.length) return
    buttons.forEach((button) => {
      button.addEventListener("click", async () => {
        const action = button.dataset.libelId
        let initiative = this.document.system.initiative ?? 1
        if (action === "plus") {
          initiative = initiative >= 24 ? 1 : initiative + 1
          await this.document.update({ "system.initiative": initiative })
          return
        }
        if (action === "minus") {
          initiative = initiative <= 1 ? 24 : initiative - 1
          await this.document.update({ "system.initiative": initiative })
          return
        }
        if (action === "create") {
          await rollInitiativePC(this.document)
        }
      })
    })
  }

  #bindPrefs() {
    const button = this.element?.querySelector(".click-prefs")
    if (!button) return
    button.addEventListener("click", async () => {
      const current = this.document.system.prefs?.typeofthrow ?? { choice: "0", check: true }
      const html = `
        <form class="flexcol">
          <div class="form-group">
            <label>${game.i18n.localize("CDE.ThrowType")}</label>
            <select name="choice">
              <option value="0"${current.choice === "0" ? " selected" : ""}>0</option>
              <option value="1"${current.choice === "1" ? " selected" : ""}>1</option>
              <option value="2"${current.choice === "2" ? " selected" : ""}>2</option>
              <option value="3"${current.choice === "3" ? " selected" : ""}>3</option>
            </select>
          </div>
          <div class="form-group">
            <label>${game.i18n.localize("CDE.EnablePrompt")}</label>
            <input type="checkbox" name="check" ${current.check ? "checked" : ""}/>
          </div>
        </form>`
      const prefs = await foundry.applications.api.DialogV2.prompt({
        window: { title: game.i18n.localize("CDE.Preferences") },
        content: html,
        rejectClose: false,
        ok: {
          label: game.i18n.localize("CDE.Validate"),
          callback: (_ev, _btn, dialog) => {
            const root = dialog.element ?? dialog
            const choice = root.querySelector("select[name='choice']")?.value ?? "0"
            const check = root.querySelector("input[name='check']")?.checked ?? false
            return { choice, check }
          },
        },
      })
      if (prefs) {
        await this.document.update({
          "system.prefs.typeofthrow.choice": String(prefs.choice),
          "system.prefs.typeofthrow.check": !!prefs.check,
        })
      }
    })
  }

  #bindRollButtons() {
    const cells = this.element?.querySelectorAll("td.click[data-libel-id], td.click2[data-libel-id], .cde-roll-trigger[data-libel-id]")
    if (!cells?.length) return
    cells.forEach((cell) => {
      cell.addEventListener("click", (event) => {
        event.preventDefault()
        const rollKey = cell.dataset.libelId
        if (rollKey) rollForActor(this.document, rollKey)?.catch(err => console.error("Roll failed:", err))
      })
    })
  }

  #bindDiagramZoom() {
    const img = this.element?.querySelector("[data-action='zoom-diagram']")
    if (!img) return
    img.addEventListener("click", () => {
      new ImagePopout(img.src, {
        title: game.i18n.localize("CDE.NghangDiagramTitle"),
        shareable: false,
      }).render(true)
    })
  }

  static async #onMoveMagicUp(event, target) {
    const key = target.dataset.magicKey
    let order = this.document.system.magicOrder ?? []
    if (!order.length) order = [...Object.keys(MAGICS)]
    else order = [...order]
    const idx = order.indexOf(key)
    if (idx <= 0) return
    [order[idx - 1], order[idx]] = [order[idx], order[idx - 1]]
    await this.document.update({ "system.magicOrder": order })
  }

  static async #onMoveMagicDown(event, target) {
    const key = target.dataset.magicKey
    let order = this.document.system.magicOrder ?? []
    if (!order.length) order = [...Object.keys(MAGICS)]
    else order = [...order]
    const idx = order.indexOf(key)
    if (idx === -1 || idx >= order.length - 1) return
    [order[idx], order[idx + 1]] = [order[idx + 1], order[idx]]
    await this.document.update({ "system.magicOrder": order })
  }

  static async #onRecovery(event, target) {
    const actor = this.document
    const sys = actor.system

    const guardian = Number(sys.guardian ?? 0)
    if (guardian < 1 || guardian > 5) {
      ui.notifications.warn(game.i18n.localize("CDE.RecoveryNoGuardian"))
      return
    }

    const aspectName = ASPECT_NAMES[guardian - 1]
    const aspectLabel = game.i18n.localize(ASPECT_LABELS[aspectName])
    const aspectValue = sys.aspect?.[aspectName]?.value ?? 0
    if (aspectValue <= 0) {
      ui.notifications.warn(game.i18n.localize("CDE.RecoveryNoAspect"))
      return
    }

    // Show prompt for bonus dice
    const RECOVERY_PROMPT = `systems/${SYSTEM_ID}/templates/form/cde-recovery-prompt.html`
    const content = await foundry.applications.handlebars.renderTemplate(RECOVERY_PROMPT, {
      basedice: aspectValue,
    })
    const params = await foundry.applications.api.DialogV2.prompt({
      window: { title: game.i18n.localize("CDE.Recovery") },
      content,
      rejectClose: false,
      ok: {
        label: game.i18n.localize("CDE.Validate"),
        callback: (event, button, dialog) => {
          const root = dialog.element ?? dialog
          return { bonusdice: Number(root.querySelector("[name=bonusdice]")?.value ?? 0) }
        },
      },
    })
    if (params === undefined) return

    const totalDice = aspectValue + Math.max(0, Number(params.bonusdice ?? 0))
    const roll = new Roll(`${totalDice}d10`)
    await roll.evaluate()

    // Count even (pair) and odd (impair) results
    const results = roll.dice[0].results.map(r => r.result)
    const evenCount = results.filter(r => r % 2 === 0).length
    const oddCount  = results.filter(r => r % 2 !== 0).length

    // Clamp gains to configured max values. If max is 0 (unconfigured),
    // allow free accumulation (no clamp).
    const cap = (max) => (max > 0 ? max : Infinity)
    const heiYang = Math.min(
      (sys.threetreasures?.heiyang?.value ?? 0) + evenCount,
      cap(sys.threetreasures?.heiyang?.max)
    )
    const heiYin = Math.min(
      (sys.threetreasures?.heiyin?.value ?? 0) + oddCount,
      cap(sys.threetreasures?.heiyin?.max)
    )
    const san = Math.min(
      (sys.threetreasures?.dicelevel?.level0d?.san?.value ?? 0) + evenCount,
      cap(sys.threetreasures?.dicelevel?.level0d?.san?.max)
    )
    const zing = Math.min(
      (sys.threetreasures?.dicelevel?.level0d?.zing?.value ?? 0) + oddCount,
      cap(sys.threetreasures?.dicelevel?.level0d?.zing?.max)
    )

    await actor.update({
      "system.threetreasures.heiyang.value": heiYang,
      "system.threetreasures.heiyin.value": heiYin,
      "system.threetreasures.dicelevel.level0d.san.value": san,
      "system.threetreasures.dicelevel.level0d.zing.value": zing,
    })

    const rollLabel = `${game.i18n.localize("CDE.Recovery")} — ${aspectLabel}`
    const diceStr = results.join(" · ")
    const typeOfThrow = Number(sys.prefs?.typeofthrow?.choice ?? 0)
    const ROLL_MODES = ["roll", "gmroll", "blindroll", "selfroll"]
    const rollMode = ROLL_MODES[typeOfThrow] ?? "roll"
    await ChatMessage.create({
      user: game.user.id,
      speaker: ChatMessage.getSpeaker({ actor }),
      content: `
        <div class="cde-chat-recovery">
          <div class="cde-chat-recovery-header">
            <img src="${ASPECT_ICONS[aspectName]}" class="cde-chat-recovery-icon" alt="${aspectLabel}"/>
            <span class="cde-chat-recovery-title">${rollLabel}</span>
          </div>
          <div class="cde-chat-recovery-dice">
            <span class="cde-chat-recovery-dice-label">${totalDice}d10</span>
            <span class="cde-chat-recovery-dice-results">${diceStr}</span>
          </div>
          <div class="cde-chat-recovery-results">
            <div class="cde-chat-recovery-even">
              <span class="cde-chat-recovery-count">${evenCount}</span>
              <span><strong>${game.i18n.localize("CDE.RecoveryEvenLabel")}</strong></span>
              <span>→ +${evenCount} Hei Yang, +${evenCount} San</span>
            </div>
            <div class="cde-chat-recovery-odd">
              <span class="cde-chat-recovery-count">${oddCount}</span>
              <span><strong>${game.i18n.localize("CDE.RecoveryOddLabel")}</strong></span>
              <span>→ +${oddCount} Hei Yin, +${oddCount} Zing</span>
            </div>
          </div>
        </div>`,
      rolls: [roll],
      rollMode,
    })
  }

  #bindComponentRandomize() {
    const btn = this.element?.querySelector("[data-action='randomize-component']")
    if (!btn) return
    btn.addEventListener("click", async () => {
      const roll = new Roll("1d10")
      await roll.evaluate()
      const face = roll.total === 10 ? 0 : roll.total
      const COMPONENT_KEYS = { 1:"one", 2:"two", 3:"three", 4:"four", 5:"five",
                                6:"six", 7:"seven", 8:"eight", 9:"nine", 0:"zero" }
      const componentKey = COMPONENT_KEYS[face]
      const componentValue = this.document.system.component?.[componentKey]?.value ?? ""
      const label = componentValue
        ? `<strong>${componentValue}</strong>`
        : `<em>${game.i18n.localize("CDE.Component")}${face}</em>`
      const content = `
        <div class="cde-chat-random-component">
          <span class="cde-chat-component-label">${game.i18n.localize("CDE.ChanceThrowResult")}</span>
          <span class="cde-chat-component-value">${label}</span>
        </div>`
      await ChatMessage.create({
        user: game.user.id,
        speaker: ChatMessage.getSpeaker({ actor: this.document }),
        content,
        rolls: [roll],
        rollMode: game.settings.get("core", "rollMode") ?? "roll",
      })
    })
  }
}
