/**
 * Donjon & Cie - Systeme FoundryVTT
 *
 * Donjon & Cie est un jeu de role edite par John Doe.
 * Ce systeme FoundryVTT est une implementation independante et n'est pas
 * affilie a John Doe.
 *
 * @author    LeRatierBretonnien
 * @copyright 2025–2026 LeRatierBretonnien
 * @license   CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
 */

import { DonjonEtCieUtility } from "./donjon-et-cie-utility.mjs";

export class DonjonEtCieMacros {
  static MISSION_PACK_TABLES = [
    { key: "melee", name: "Armes de corps a corps", multiple: false },
    { key: "ranged", name: "Armes a distance", multiple: false },
    { key: "armor", name: "Armures", multiple: false },
    { key: "misc", name: "Encas et equipement divers", multiple: true }
  ];

  static #normalizeName(value) {
    return String(value ?? "")
      .normalize("NFD")
      .replace(/\p{Diacritic}/gu, "")
      .replace(/['’]/g, " ")
      .replace(/\s+/g, " ")
      .trim()
      .toLowerCase();
  }

  static #getMissionPackLabel(key) {
    return game.i18n.localize(`DNC.Macro.MissionPack.${key}`);
  }

  static #getDefaultMissionPackActorId(actors) {
    const controlledActor = canvas?.tokens?.controlled?.[0]?.actor ?? null;
    if (controlledActor?.type === "employe") return controlledActor.id;
    return actors[0]?.id ?? "";
  }

  static #getMissionPackActorOptions() {
    return game.actors
      .filter((actor) => actor.type === "employe")
      .sort((a, b) => a.name.localeCompare(b.name, "fr", { sensitivity: "base" }))
      .map((actor) => ({
        value: actor.id,
        label: actor.name
      }));
  }

  static async #resolveMissionPackActor(target = null) {
    if (target?.documentName === "Actor") return target;
    if (target?.actor?.documentName === "Actor") return target.actor;
    if (typeof target === "string" && target) {
      const document = await fromUuid(target);
      if (document?.documentName === "Actor") return document;
      if (document?.actor?.documentName === "Actor") return document.actor;
    }

    const controlledTokens = canvas?.tokens?.controlled ?? [];
    if (controlledTokens.length === 1 && controlledTokens[0]?.actor) return controlledTokens[0].actor;
    if (controlledTokens.length > 1) {
      ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnMultipleTokens"));
      return null;
    }

    return game.user.character ?? null;
  }

  static async #getRandomTableDocuments() {
    const worldTables = game.tables?.contents ?? [];
    if (worldTables.length) return worldTables;

    const pack = game.packs.get("fvtt-donjon-et-cie.random-tables");
    return pack ? pack.getDocuments() : [];
  }

  static async #findRollTableByName(name) {
    const normalizedName = this.#normalizeName(name);
    const worldTable = game.tables.find((table) => this.#normalizeName(table.name) === normalizedName);
    if (worldTable) return worldTable;

    const packTables = await this.#getRandomTableDocuments();
    return packTables.find((table) => this.#normalizeName(table.name) === normalizedName) ?? null;
  }

  static async #getEquipmentDocuments() {
    const pack = game.packs.get("fvtt-donjon-et-cie.equipment");
    const packDocuments = pack ? await pack.getDocuments() : [];
    return [...(game.items?.contents ?? []), ...packDocuments];
  }

  static async #findItemByName(name) {
    const normalizedName = this.#normalizeName(name);
    const documents = await this.#getEquipmentDocuments();
    return documents.find((item) => this.#normalizeName(item.name) === normalizedName) ?? null;
  }

  static #extractUuidTargets(text) {
    const matches = [...String(text ?? "").matchAll(/@UUID\[([^\]]+)\](?:\{([^}]+)\})?/g)];
    return matches.map((match) => ({
      uuid: match[1],
      label: match[2] ?? ""
    }));
  }

  static #extractPlainTextEntries(text) {
    const rawText = String(text ?? "")
      .replace(/<[^>]+>/g, " ")
      .replace(/\s+/g, " ")
      .trim();

    if (!rawText) return [];

    return rawText
      .split(",")
      .map((entry) => entry.trim())
      .filter(Boolean)
      .filter((entry) => !/^dotation\s+\d+$/i.test(entry));
  }

  static async #resolveTableResultEntries(result, { multiple = false } = {}) {
    if (!result) {
      return {
        display: game.i18n.localize("DNC.Macro.MissionPack.NoResult"),
        entries: []
      };
    }

    if (result.type === "document" && result.documentCollection && result.documentId) {
      const uuid = result.documentCollection.includes(".")
        ? `Compendium.${result.documentCollection}.Item.${result.documentId}`
        : `Item.${result.documentId}`;
      const document = await fromUuid(uuid);
      const label = document?.name ?? result.text ?? "";
      return {
        display: label,
        entries: label ? [{ name: label, document }] : []
      };
    }

    const uuidTargets = this.#extractUuidTargets(result.text);
    if (uuidTargets.length) {
      const entries = [];
      for (const target of uuidTargets) {
        const document = await fromUuid(target.uuid);
        const name = document?.name ?? target.label;
        if (!name) continue;
        entries.push({ name, document });
      }

      return {
        display: entries.map((entry) => entry.name).join(", "),
        entries
      };
    }

    const names = multiple
      ? this.#extractPlainTextEntries(result.text)
      : [result.text].map((entry) => String(entry ?? "").trim()).filter(Boolean);

    return {
      display: names.join(", "),
      entries: names.map((name) => ({ name, document: null }))
    };
  }

  static #toEmbeddedItemData(item) {
    const data = foundry.utils.deepClone(item.toObject());
    delete data._id;
    delete data.folder;
    delete data.sort;
    return data;
  }

  /**
   * Open the GM-only mission pack dialog.
   * @returns {Promise<object|null>}
   */
  static async openMissionPackDialog() {
    if (!game.user.isGM) {
      ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnGMOnly"));
      return null;
    }

    const actorOptions = this.#getMissionPackActorOptions();
    if (!actorOptions.length) {
      ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnNoEmployees"));
      return null;
    }

    const selectedActorId = this.#getDefaultMissionPackActorId(actorOptions.map((option) => game.actors.get(option.value)).filter(Boolean));
    const content = await foundry.applications.handlebars.renderTemplate(
      "systems/fvtt-donjon-et-cie/templates/dialogs/mission-pack-dialog.hbs",
      {
        actorOptions,
        selectedActorId
      }
    );

    return foundry.applications.api.DialogV2.wait({
      window: {
        title: game.i18n.localize("DNC.Macro.MissionPack.DialogTitle"),
        icon: "fa-solid fa-box-open"
      },
      classes: ["dnc-roll-dialog"],
      content,
      modal: false,
      buttons: [
        {
          action: "grant",
          label: game.i18n.localize("DNC.Macro.MissionPack.DialogAction"),
          icon: "fa-solid fa-box-open",
          default: true,
          callback: async (event, button) => {
            const actorId = button.form.elements.actorId?.value ?? "";
            const actor = actorId ? game.actors.get(actorId) : null;
            if (!actor) {
              ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnNoActor"));
              return null;
            }
            return this.grantMissionPack(actor);
          }
        }
      ],
      rejectClose: false
    });
  }

  /**
   * Draw the initial mission pack for the resolved actor and add the resulting items.
   * @param {Actor|string|null} target Resolved actor, token, or UUID. Defaults to selected token or user character.
   * @returns {Promise<object|null>}
   */
  static async grantMissionPack(target = null) {
    const actor = await this.#resolveMissionPackActor(target);
    if (!actor) {
      ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnNoActor"));
      return null;
    }

    if (actor.type !== "employe") {
      ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnInvalidActor"));
      return null;
    }

    const draws = [];
    const embeddedItems = [];
    let missingCount = 0;

    for (const spec of this.MISSION_PACK_TABLES) {
      const table = await this.#findRollTableByName(spec.name);
      if (!table) {
        draws.push({
          label: this.#getMissionPackLabel(spec.key),
          display: game.i18n.format("DNC.Macro.MissionPack.TableMissing", { table: spec.name }),
          addedNames: [],
          addedSummary: "",
          missingNames: [],
          missingSummary: "",
          failed: true
        });
        continue;
      }

      const draw = await table.draw({ displayChat: false });
      const result = draw.results?.[0] ?? null;
      const resolved = await this.#resolveTableResultEntries(result, { multiple: spec.multiple });
      const addedNames = [];
      const missingNames = [];

      for (const entry of resolved.entries) {
        const item = entry.document ?? await this.#findItemByName(entry.name);
        if (!item) {
          missingNames.push(entry.name);
          missingCount += 1;
          continue;
        }

        embeddedItems.push(this.#toEmbeddedItemData(item));
        addedNames.push(item.name);
      }

      draws.push({
        label: this.#getMissionPackLabel(spec.key),
        display: resolved.display || game.i18n.localize("DNC.Macro.MissionPack.NoResult"),
        addedNames,
        addedSummary: addedNames.join(", "),
        missingNames,
        missingSummary: missingNames.join(", "),
        failed: false
      });
    }

    const createdItems = embeddedItems.length
      ? await actor.createEmbeddedDocuments("Item", embeddedItems, { renderSheet: false })
      : [];

    const content = await foundry.applications.handlebars.renderTemplate(
      "systems/fvtt-donjon-et-cie/templates/chat/mission-pack-card.hbs",
      {
        title: game.i18n.localize("DNC.Macro.MissionPack.Title"),
        actorName: actor.name,
        createdCount: createdItems.length,
        missingCount,
        draws
      }
    );

    await ChatMessage.create({
      speaker: ChatMessage.getSpeaker({ actor }),
      user: game.user.id,
      content
    });

    if (createdItems.length && !missingCount) {
      ui.notifications.info(game.i18n.format("DNC.Macro.MissionPack.Success", {
        actor: actor.name,
        count: createdItems.length
      }));
    } else if (createdItems.length) {
      ui.notifications.warn(game.i18n.format("DNC.Macro.MissionPack.Partial", {
        actor: actor.name,
        count: createdItems.length,
        missing: missingCount
      }));
    } else {
      ui.notifications.warn(game.i18n.localize("DNC.Macro.MissionPack.WarnNothingAdded"));
    }

    return {
      actor,
      createdItems,
      missingCount,
      draws
    };
  }
}
