import { VermineUtils } from "../roll.mjs";
import { VermineExchange } from "../exchange.mjs";
import { VermineTraits } from "../traits.mjs";

const { HandlebarsApplicationMixin } = foundry.applications.api;

export default class RollDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {

  #actor;

  get title() {
    return game.i18n.localize("VERMINE.roll");
  }

  static DEFAULT_OPTIONS = {
    classes: ["vermine-roll"],
    tag: "form",
    window: {
      icon: "fas fa-dice-d10",
      resizable: false
    },
    position: {
      width: 520,
      height: 600
    },
    actions: {
      roll: RollDialog.#onRoll,
      cancel: RollDialog.#onCancel,
      giveToGroup: RollDialog.#onGiveToGroup
    }
  };

  static PARTS = {
    main: { template: "systems/vermine2047/templates/dialogs/roll-dialog.hbs" }
  };

  static async create(data = {}) {
    if (data.actor instanceof Actor) {
      return new RollDialog({ actor: data.actor, label: data.label, rolltype: data.rolltype, weapon: data.weapon, locks: data.locks, defense: data.defense });
    }
    const actorId = data.actorId ?? game.user.character?.id ?? canvas.tokens.controlled[0]?.actor?.id;
    if (!actorId || typeof actorId !== "string") {
      ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected"));
      return null;
    }
    const actor = await game.actors.get(actorId);
    if (!actor) {
      ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected"));
      return null;
    }
    return new RollDialog({ actor, label: data.label, rolltype: data.rolltype, weapon: data.weapon, locks: data.locks, defense: data.defense });
  }

  constructor(options = {}) {
    super(options);
    this.#actor = options.actor;
    this.label = options.label ?? null;
    this.rolltype = options.rolltype ?? null;
    this.weapon = options.weapon ?? null;
    this.locks = options.locks ?? null;
    this.defense = options.defense ?? null;
  }

  async _prepareContext() {
    const actor = this.#actor;
    const isCharacter = actor.type === "character";
    const group = isCharacter ? RollDialog.#findGroup(actor) : null;
    const groupPool = group?.system?.experience?.dice || 0;
    const intimidant = isCharacter ? VermineTraits.equippedItems(actor, 'intimidant')[0] ?? null : null;
    return {
      actor,
      system: actor.system,
      config: CONFIG.VERMINE,
      label: this.label,
      rollType: this.rolltype,
      labelKey: this.label,
      speakerId: actor.id,
      ability: null,
      help: false,
      specialty: false,
      availableSpecialties: actor.items.filter(i => i.type === "specialty"),
      availableItems: actor.items.filter(i => i.type === "item"),
      hasIntimidant: Boolean(intimidant),
      intimidantName: intimidant?.name ?? "",
      locks: this.locks,
      experience: {
        hasExperience: isCharacter,
        individual: isCharacter ? actor.system?.experience?.dice || 0 : 0,
        groupPool,
        pullOptions: [0, 1, 2].filter(v => v <= Math.min(2, groupPool)),
        canPull: Boolean(group) && (game.user.isGM || group.isOwner),
        canGive: isCharacter && (game.user.isGM || actor.isOwner)
      }
    };
  }

  async _onRender(context, options) {
    this.element.dataset.actorId = this.#actor.id;

    // Mode verrouillé (défense en échange de coups) : présélectionne et bloque
    // caractéristique + difficulté, présélectionne la compétence.
    if (this.locks) {
      this.#applyLocks(this.locks);
    }

    for (const inp of this.element.querySelectorAll("[data-roll]")) {
      inp.addEventListener("change", this.#onInputChange.bind(this));
    }

    const ability = this.element.querySelector("#ability");
    if (ability) {
      ability.addEventListener("change", this.#onChangeAbility.bind(this));
      const selfControl = this.element.querySelector("#self_control");
      if (selfControl) selfControl.max = ability.value;
    }

    const selfControl = this.element.querySelector("#self_control");
    if (selfControl) {
      selfControl.addEventListener("change", this.#onChangeSelfControl.bind(this));
    }

    this.element.querySelector("#difficulty")?.addEventListener("change", () => this.#updateUI());
    this.element.querySelector("#handicap")?.addEventListener("change", () => this.#updateUI());
    this.element.querySelector("#human-totem")?.addEventListener("change", () => this.#updateUI());
    this.element.querySelector("#adapted-totem")?.addEventListener("change", () => this.#updateUI());

    this.#displaySpecialties();
    this.#refreshExperienceUI();
    this.#updateUI();

    if (ability?.value !== "0") {
      this.element.querySelector("#self_control")?.dispatchEvent(new Event("change"));
    }
  }

  // ── Getters ──────────────────────────────────────────────────────────

  get #el() { return this.element; }

  #getAbility() { return this.#el.querySelector("#ability"); }
  #getSkill() { return this.#el.querySelector("#skill"); }
  #getDifficulty() { return this.#el.querySelector("#difficulty"); }
  #getHandicap() { return this.#el.querySelector("#handicap"); }
  #getSelfCtrl() { return this.#el.querySelector("#self_control"); }
  #getExperiencePull() { return parseInt(this.#el.querySelector("#experience-pull")?.value, 10) || 0; }

  /** Clé de la compétence sélectionnée (data-label) ou null. */
  getSelectedSkillKey() {
    const sel = this.#getSkill();
    return sel?.options[sel.selectedIndex]?.dataset?.label ?? null;
  }

  /** Outil générique sélectionné (radio usingTools) ou null. */
  getSelectedTool() {
    const checked = this.#el.querySelector("input[name='usingTools']:checked");
    if (!checked || checked.value === "0") return null;
    return this.#actor.items.find(i => i.name === checked.value) ?? null;
  }

  /** Bonus d'outillage : +1D, ou +2D si l'outil possède le Trait Pratique. */
  #getTooling() {
    const tool = this.getSelectedTool();
    if (!tool) return 0;
    return VermineTraits.hasTrait(tool, 'pratique') ? 2 : 1;
  }

  /** Malus temporaires saisis manuellement (Trait Malus). */
  #getTemporaryMalus() {
    return parseInt(this.#el.querySelector("#temporary-malus")?.value, 10) || 0;
  }

  /** Case « Brandit » (Trait Intimidant). */
  getIntimidant() {
    return this.#el.querySelector("#intimidant")?.checked ?? false;
  }

  /** Valeur de Handicap brute du sélecteur (0-2). */
  #getBaseHandicap() {
    const sel = this.#getHandicap();
    return Math.max(0, (parseInt(sel?.value, 10) || 1) - 1);
  }

  /** Nombre de Handicaps annulés par l'outil Ponctuel sélectionné. */
  getPonctuelUsed() {
    const tool = this.getSelectedTool();
    const ponctuel = tool ? VermineTraits.traitValue(tool, 'ponctuel') : 0;
    if (ponctuel <= 0) return 0;
    return Math.min(ponctuel, this.#getBaseHandicap());
  }

  /**
   * Trouve le premier Groupe dont le personnage est membre.
   * @param {Actor} actor
   * @returns {Actor|null}
   */
  static #findGroup(actor) {
    return game.actors.filter(a => a.type === "group" && (a.system?.members ?? []).includes(actor.id))[0] ?? null;
  }

  /** Décomposition du pool de dés pour affichage dans le message. */
  getPoolBreakdown() {
    const abil = this.#getAbility();
    const ability = parseInt(abil?.options[abil?.selectedIndex]?.value, 10) || 0;
    const skill = this.#getSkill();
    const skillPool = parseInt(skill?.options[skill?.selectedIndex]?.dataset?.pool, 10) || 0;
    const selfControl = parseInt(this.#getSelfCtrl()?.value, 10) || 0;
    const specChecked = this.hasSpecialtySelected();
    const helped = this.#el.querySelector("#helped")?.checked;
    const group = parseInt(this.#el.querySelector("#group")?.value, 10) || 0;
    const experience = this.#getExperiencePull();
    const specialty = specChecked ? 1 : 0;
    const help = helped ? 1 : 0;
    const tooling = this.#getTooling();
    const wound = VermineExchange.woundMalus(this.#actor);
    const tempMalus = this.#getTemporaryMalus();
    const psychology = this.getIntimidant() && this.getSelectedSkillKey() === 'psychology' ? 1 : 0;
    const malus = wound + tempMalus + psychology;
    const total = Math.max(0, ability + skillPool + selfControl + specialty + help + tooling + group + experience - malus);
    return { ability, skill: skillPool, selfControl, specialty, help, tooling, group, experience, wound, tempMalus, psychology, malus, total };
  }

  getDicePool() {
    return this.getPoolBreakdown().total;
  }

  getSpecialtyName() {
    const checked = this.#el.querySelector("input[name='usingSpecialization']:checked");
    return checked && checked.value !== "aucune" ? checked.value : null;
  }

  getDifficultySelect() {
    const sel = this.#getDifficulty();
    const idx = sel?.selectedIndex ?? 0;
    return parseInt(sel?.options[idx]?.value, 10) || 7;
  }

  getReroll() {
    const sel = this.#getSkill();
    const idx = sel?.selectedIndex ?? 0;
    const base = parseInt(sel?.options[idx]?.dataset?.reroll, 10) || 0;
    // Trait Intimidant (hors Psychologie) : Relance 1D tant que l'objet est brandi.
    if (this.getIntimidant() && this.getSelectedSkillKey() !== 'psychology') return base + 1;
    return base;
  }

  getHandicapSelect() {
    // Trait Ponctuel : l'outil sélectionné annule jusqu'à (n) Handicaps.
    return this.#getBaseHandicap() - this.getPonctuelUsed();
  }

  getSkillCategory() {
    const sel = this.#getSkill();
    const idx = sel?.selectedIndex ?? 0;
    return sel?.options[idx]?.dataset?.category ?? null;
  }

  getSkillLevel() {
    const sel = this.#getSkill();
    const idx = sel?.selectedIndex ?? 0;
    const val = sel?.options[idx]?.value;
    return val ? parseInt(val, 10) : null;
  }

  hasSpecialtySelected() {
    const checked = this.#el.querySelector("input[name='usingSpecialization']:checked");
    return checked && checked.value !== "aucune";
  }

  getRollType() {
    const sel = this.#getSkill();
    return sel?.value ? "skill" : "ability";
  }

  getLabel() {
    const type = this.getRollType();
    const sel = type === "skill" ? this.#getSkill() : this.#getAbility();
    const idx = sel?.selectedIndex ?? 0;
    const raw = sel?.options[idx]?.dataset?.label ?? "";
    if (!raw) return "";
    const base = type === "skill" ? "SKILLS" : "ABILITIES";
    const localized = game.i18n.localize(`${base}.${raw}.name`);
    return localized === `${base}.${raw}.name` ? raw : localized;
  }

  getSelfControl() {
    return parseInt(this.#getSelfCtrl()?.value, 10) || 0;
  }

  getMaxEffort() {
    const sel = this.#getAbility();
    const ability = parseInt(sel?.value, 10) || 0;
    const effort = this.#actor?.system?.attributes?.effort?.value || 0;
    return Math.min(ability, effort);
  }

  getTotems() {
    return {
      human: this.#el.querySelector("#human-totem")?.checked ?? false,
      adapted: this.#el.querySelector("#adapted-totem")?.checked ?? false
    };
  }

  getKeepTotem() {
    return this.#el.querySelector("#keep-totem-select")?.value ?? null;
  }

  // ── UI ───────────────────────────────────────────────────────────────

  #applyLocks(locks) {
    const ability = this.#getAbility();
    if (ability && locks.ability) {
      const opt = [...ability.options].find(o => o.dataset.label === locks.ability);
      if (opt) {
        opt.selected = true;
        ability.disabled = true;
        const scoreEl = this.#el.querySelector("#abilityScore");
        if (scoreEl) scoreEl.value = opt.value;
        const sc = this.#getSelfCtrl();
        if (sc) sc.max = opt.value;
      }
    }
    const skill = this.#getSkill();
    if (skill && locks.skill) {
      const opt = [...skill.options].find(o => o.dataset.label === locks.skill);
      if (opt) opt.selected = true;
    }
    const difficulty = this.#getDifficulty();
    if (difficulty && locks.difficulty !== undefined && locks.difficulty !== null) {
      const locked = parseInt(locks.difficulty, 10);
      let opt = [...difficulty.options].find(o => parseInt(o.value, 10) === locked);
      if (!opt) {
        // Difficulté hors des options (ex: 9 + 2) : ajoute une option dédiée.
        opt = document.createElement("option");
        opt.value = String(locked);
        opt.textContent = `${game.i18n.localize("VERMINE.difficulty")} (${locked})`;
        difficulty.appendChild(opt);
      }
      opt.selected = true;
      difficulty.disabled = true;
    }
    const handicap = this.#getHandicap();
    if (handicap && locks.handicap !== undefined && locks.handicap !== null) {
      const locked = parseInt(locks.handicap, 10);
      let opt = [...handicap.options].find(o => parseInt(o.value, 10) === locked + 1);
      if (!opt) {
        // Handicap supérieur aux options (ex: Rapide 3) : option dédiée.
        opt = document.createElement("option");
        opt.value = String(locked + 1);
        opt.textContent = game.i18n.localize("VERMINE.handicap") + ` (${locked})`;
        handicap.appendChild(opt);
      }
      opt.selected = true;
    }
  }

  #displaySpecialties() {
    for (const el of this.#el.querySelectorAll("[data-spec-skill]")) {
      el.style.display = "inline";
    }
  }

  #calculateBonusCount() {
    const b = this.getPoolBreakdown();
    return b.specialty + b.help + b.tooling + b.group + b.selfControl + b.experience - b.malus;
  }

  #refreshExperienceUI() {
    const actor = this.#actor;
    const group = RollDialog.#findGroup(actor);
    const individual = actor.system?.experience?.dice || 0;
    const groupPool = group?.system?.experience?.dice || 0;
    const indEl = this.#el.querySelector("#exp-individual");
    if (indEl) indEl.textContent = individual;
    const grpEl = this.#el.querySelector("#exp-group");
    if (grpEl) grpEl.textContent = groupPool;
    const pull = this.#el.querySelector("#experience-pull");
    if (pull) {
      const max = Math.min(2, groupPool);
      const current = Math.min(parseInt(pull.value, 10) || 0, max);
      pull.innerHTML = "";
      for (let i = 0; i <= max; i++) {
        const opt = document.createElement("option");
        opt.value = String(i);
        opt.textContent = `${i}D`;
        if (i === current) opt.selected = true;
        pull.appendChild(opt);
      }
    }
  }

  #updateUI() {
    const total = this.getDicePool();
    const totalEl = this.#el.querySelector("#dice-pool-total");
    if (totalEl) totalEl.textContent = `${total}D`;

    const bonusEl = this.#el.querySelector("#total-bonus");
    if (bonusEl) bonusEl.textContent = this.#calculateBonusCount();

    const diffSel = this.#getDifficulty();
    const diffEl = this.#el.querySelector("#current-difficulty");
    if (diffEl && diffSel) {
      const idx = diffSel.selectedIndex;
      const val = diffSel.options[idx].value;
      const lbl = diffSel.options[idx].text.split(" ")[0];
      diffEl.textContent = `${lbl} (${val})`;
    }

    const handSel = this.#getHandicap();
    const handEl = this.#el.querySelector("#current-handicap");
    if (handEl && handSel) {
      let text = handSel.options[handSel.selectedIndex].text;
      const used = this.getPonctuelUsed();
      if (used > 0) text += ` (${game.i18n.localize("VERMINE.ponctuel")} -${used})`;
      handEl.textContent = text;
    }

    const abilSel = this.#getAbility();
    const abilValEl = this.#el.querySelector("#abilityScoreValue");
    if (abilSel && abilValEl) {
      const idx = abilSel.selectedIndex;
      abilValEl.textContent = idx > 0 ? abilSel.options[idx].value : "0";
    }

    const specChecked = this.#el.querySelector("input[name='usingSpecialization']:checked");
    const specEl = this.#el.querySelector(".current-specialty");
    if (specEl && specChecked) {
      specEl.textContent = specChecked.value === "aucune"
        ? game.i18n.localize("VERMINE.none")
        : specChecked.value;
    }
  }

  // ── Event handlers ───────────────────────────────────────────────────

  #onInputChange() {
    this.#updateUI();
  }

  #onChangeAbility(ev) {
    const sel = ev.currentTarget;
    const score = sel.options[sel.selectedIndex]?.value ?? "0";
    const scoreEl = this.#el.querySelector("#abilityScore");
    if (scoreEl) scoreEl.value = score;
    const sc = this.#getSelfCtrl();
    if (sc) sc.max = score;
    this.#updateUI();
  }

  #onChangeSelfControl(ev) {
    const valEl = this.#el.querySelector("#self_control_value");
    if (valEl) valEl.textContent = ev.currentTarget.value;
  }

  static async #onCancel(event, target) {
    this.close();
  }

  static async #onGiveToGroup(event, target) {
    const actor = this.#actor;
    if (actor.type !== "character") return;
    const individual = actor.system?.experience?.dice || 0;
    if (individual < 1) {
      ui.notifications.warn(game.i18n.localize("VERMINE.error_no_experience_dice"));
      return;
    }
    if (!game.user.isGM && !actor.isOwner) {
      ui.notifications.warn(game.i18n.localize("VERMINE.error_no_permission"));
      return;
    }
    const group = RollDialog.#findGroup(actor);
    if (!group) {
      ui.notifications.warn(game.i18n.localize("VERMINE.error_no_group"));
      return;
    }
    const groupPool = group.system?.experience?.dice || 0;
    await actor.update({ "system.experience.dice": individual - 1 });
    await group.update({ "system.experience.dice": groupPool + 1 });
    this.#refreshExperienceUI();
  }

  static async #onRoll(event, target) {
    const selfCtrl = this.getSelfControl();
    if (selfCtrl > 0) {
      const current = this.#actor?.system?.attributes?.self_control?.value ?? 0;
      if (current < selfCtrl) {
        ui.notifications.warn(game.i18n.localize("VERMINE.error_not_enough_self_control"));
        return;
      }
    }

    const abilityVal = this.#el.querySelector('[name="ability"]')?.value;
    if (!abilityVal || abilityVal === "0") {
      ui.notifications.warn(game.i18n.localize("VERMINE.error_select_ability"));
      return;
    }

    if (selfCtrl > 0) {
      const newVal = this.#actor.system.attributes.self_control.value - selfCtrl;
      await this.#actor.update({ "system.attributes.self_control.value": newVal });
    }

    // Transfert d'Expérience : puiser dans la Réserve de Groupe (1-2D).
    const pulled = this.#getExperiencePull();
    if (pulled > 0) {
      const group = RollDialog.#findGroup(this.#actor);
      if (!group) {
        ui.notifications.warn(game.i18n.localize("VERMINE.error_no_group"));
        return;
      }
      if (!game.user.isGM && !group.isOwner) {
        ui.notifications.warn(game.i18n.localize("VERMINE.error_no_permission"));
        return;
      }
      const groupPool = group.system?.experience?.dice || 0;
      if (groupPool < pulled) {
        ui.notifications.warn(game.i18n.localize("VERMINE.error_no_group_dice"));
        return;
      }
      await group.update({ "system.experience.dice": groupPool - pulled });
    }

    // Trait Ponctuel : consomme (n) charges de l'outil sélectionné pour annuler
    // les Handicaps ; l'objet perd définitivement le Trait si la valeur tombe à 0.
    const ponctuelUsed = this.getPonctuelUsed();
    const tool = this.getSelectedTool();
    if (tool && ponctuelUsed > 0) {
      const current = VermineTraits.traitValue(tool, 'ponctuel');
      const remaining = current - ponctuelUsed;
      if (remaining <= 0) {
        await tool.update({ "system.traits.ponctuel": null });
      } else {
        await tool.update({ "system.traits.ponctuel.value": remaining });
      }
    }

    const intimidant = this.getIntimidant();

    const rollParams = {
      actor: this.#actor,
      NoD: this.getDicePool(),
      Reroll: this.getReroll(),
      difficulty: this.getDifficultySelect(),
      handicap: this.getHandicapSelect(),
      rollLabel: this.getLabel(),
      totems: this.getTotems(),
      self_control: selfCtrl,
      max_effort: this.getMaxEffort(),
      keepTotem: this.getKeepTotem(),
      skillCategory: this.getSkillCategory(),
      skillLevel: this.getSkillLevel(),
      hasSpecialty: this.hasSpecialtySelected(),
      poolBreakdown: this.getPoolBreakdown(),
      specialtyName: this.getSpecialtyName(),
      weapon: this.weapon?.toObject() ?? null,
      targets: [...game.user.targets].map(t => t.name),
      messageFlags: this.defense ? { "vermine-defense": this.defense } : null
    };
    if (intimidant) {
      rollParams.intimidant = { used: true, psychology: this.getSelectedSkillKey() === 'psychology' };
    }
    if (ponctuelUsed > 0) rollParams.ponctuelUsed = ponctuelUsed;

    // Fermeture immédiate du dialogue : le jet (et l'animation 3D) continue
    // en arrière-plan sans bloquer la fermeture.
    this.close();

    await VermineUtils.roll(rollParams);
  }

}
