// src/config/constants.js
var SYSTEM_ID = "fvtt-chroniques-de-l-etrange";
var ACTOR_TYPES = {
  character: "character",
  npc: "npc"
};
var ITEM_TYPES = {
  item: "item",
  kungfu: "kungfu",
  spell: "spell",
  supernatural: "supernatural",
  weapon: "weapon",
  armor: "armor",
  sanhei: "sanhei",
  ingredient: "ingredient"
};
var SUBTYPES = {
  weapon: { id: "weapon", label: "CDE.Weapon" },
  armor: { id: "armor", label: "CDE.Armor" },
  sanhei: { id: "sanhei", label: "CDE.Sanhei" },
  other: { id: "other", label: "CDE.Other" }
};
var MAGICS = {
  internalcinnabar: {
    id: "internalcinnabar",
    background: "linear-grey",
    label: "CDE.InternalCinnabar",
    aspectlabel: "CDE.Metal",
    speciality: {
      essence: { label: "CDE.Essence", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp", labelicon: "Yin", labelelement: "CDE.Metal" },
      mind: { label: "CDE.Mind", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp", labelicon: "Yin", labelelement: "CDE.Water" },
      purification: { label: "CDE.Purification", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
      manipulation: { label: "CDE.Manipulation", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp", labelicon: "Yang", labelelement: "CDE.Fire" },
      aura: { label: "CDE.Aura", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp", labelicon: "Yang", labelelement: "CDE.Wood" }
    }
  },
  alchemy: {
    id: "alchemy",
    background: "linear-blue",
    label: "CDE.Alchemy",
    aspectlabel: "CDE.Water",
    speciality: {
      acupuncture: { label: "CDE.Acupuncture", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp", labelicon: "Yin", labelelement: "CDE.Metal" },
      elixirs: { label: "CDE.Elixirs", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp", labelicon: "Yin", labelelement: "CDE.Water" },
      poisons: { label: "CDE.Poisons", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
      arsenal: { label: "CDE.Arsenal", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp", labelicon: "Yang", labelelement: "CDE.Fire" },
      potions: { label: "CDE.Potions", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp", labelicon: "Yang", labelelement: "CDE.Wood" }
    }
  },
  masteryoftheway: {
    id: "masteryoftheway",
    background: "linear-brown",
    label: "CDE.MasteryOfTheWay",
    aspectlabel: "CDE.Earth",
    speciality: {
      curse: { label: "CDE.Curse", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp", labelicon: "Yin", labelelement: "CDE.Metal" },
      transfiguration: { label: "CDE.Transfiguration", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp", labelicon: "Yin", labelelement: "CDE.Water" },
      necromancy: { label: "CDE.Necromancy", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
      climatecontrol: { label: "CDE.ClimateControl", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp", labelicon: "Yang", labelelement: "CDE.Fire" },
      goldenmagic: { label: "CDE.GoldenMagic", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp", labelicon: "Yang", labelelement: "CDE.Wood" }
    }
  },
  exorcism: {
    id: "exorcism",
    background: "linear-red",
    label: "CDE.Exorcism",
    aspectlabel: "CDE.Fire",
    speciality: {
      invocation: { label: "CDE.Invocation", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp", labelicon: "Yin", labelelement: "CDE.Metal" },
      tracking: { label: "CDE.Tracking", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp", labelicon: "Yin", labelelement: "CDE.Water" },
      protection: { label: "CDE.Protection", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
      punishment: { label: "CDE.Punishment", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp", labelicon: "Yang", labelelement: "CDE.Fire" },
      domination: { label: "CDE.Domination", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp", labelicon: "Yang", labelelement: "CDE.Wood" }
    }
  },
  geomancy: {
    id: "geomancy",
    background: "linear-green",
    label: "CDE.Geomancy",
    aspectlabel: "CDE.Wood",
    speciality: {
      neutralization: { label: "CDE.Neutralization", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp", labelicon: "Yin", labelelement: "CDE.Metal" },
      divination: { label: "CDE.Divination", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp", labelicon: "Yin", labelelement: "CDE.Water" },
      earthlyprayer: { label: "CDE.EarthlyPrayer", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
      heavenlyprayer: { label: "CDE.HeavenlyPrayer", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp", labelicon: "Yang", labelelement: "CDE.Fire" },
      fungseoi: { label: "CDE.Fungseoi", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp", labelicon: "Yang", labelelement: "CDE.Wood" }
    }
  }
};
var ASPECT_LABELS = {
  metal: "CDE.Metal",
  water: "CDE.Water",
  earth: "CDE.Earth",
  fire: "CDE.Fire",
  wood: "CDE.Wood"
};
var ASPECT_ICONS = {
  metal: "systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp",
  water: "systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
  earth: "systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
  fire: "systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
  wood: "systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp"
};
var ASPECT_FACES = {
  metal: [3, 8],
  water: [1, 6],
  earth: [0, 5],
  // 0 = face "10"
  fire: [2, 7],
  wood: [4, 9]
};
var ASPECT_NAMES = ["metal", "water", "earth", "fire", "wood"];
var WU_XING_CYCLE = {
  wood: ["wood", "fire", "water", "earth", "metal"],
  fire: ["fire", "earth", "wood", "metal", "water"],
  earth: ["earth", "metal", "fire", "water", "wood"],
  metal: ["metal", "water", "earth", "wood", "fire"],
  water: ["water", "wood", "metal", "fire", "earth"]
};
var TEMPLATE_PARTIALS = [
  "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-skills.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-magics.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-nghang.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-treasures.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-items.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-kungfus.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-supernaturals.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-spells.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-kungfus.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-items.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-loksyu-app.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-tinji-app.html",
  "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-wheel-app.html"
];

// src/migration/migrator.js
var ELEMENT_LABEL_TO_KEY = {
  "m\xE9tal": "metal",
  "metal": "metal",
  "eau": "eau",
  "terre": "terre",
  "feu": "feu",
  "bois": "bois"
};
function elementKey(label = "") {
  return ELEMENT_LABEL_TO_KEY[label.toLowerCase().trim()] ?? "metal";
}
function heiKey(label = "") {
  const l = label.toLowerCase().trim();
  if (l === "yin/yang" || l === "yinyang") return "yinyang";
  if (l === "yang") return "yang";
  return "yin";
}
var SPECIALITY_TO_DISCIPLINE = {
  // internalcinnabar
  "essence": "internalcinnabar",
  "esprit": "internalcinnabar",
  "mind": "internalcinnabar",
  "purification": "internalcinnabar",
  "manipulation": "internalcinnabar",
  "aura": "internalcinnabar",
  // alchemy
  "acupuncture": "alchemy",
  "\xE9lixirs": "alchemy",
  "elixirs": "alchemy",
  "poisons": "alchemy",
  "arsenal": "alchemy",
  "potions": "alchemy",
  // masteryoftheway
  "mal\xE9diction": "masteryoftheway",
  "malediction": "masteryoftheway",
  "transfiguration": "masteryoftheway",
  "n\xE9cromancie": "masteryoftheway",
  "necromancie": "masteryoftheway",
  "contr\xF4le climatique": "masteryoftheway",
  "controle climatique": "masteryoftheway",
  "magie d'or": "masteryoftheway",
  "magie dor": "masteryoftheway",
  // exorcism
  "invocation": "exorcism",
  "pistage": "exorcism",
  "tra\xE7age": "exorcism",
  "tracage": "exorcism",
  "protection": "exorcism",
  "ch\xE2timent": "exorcism",
  "chatiment": "exorcism",
  "domination": "exorcism",
  // geomancy
  "neutralisation": "geomancy",
  "divination": "geomancy",
  "pri\xE8re terrestre": "geomancy",
  "priere terrestre": "geomancy",
  "pri\xE8re c\xE9leste": "geomancy",
  "priere celeste": "geomancy",
  "g\xE9omancie": "geomancy",
  "geomancie": "geomancy",
  "feng shui": "geomancy",
  "fungseoi": "geomancy"
};
function inferDiscipline(specialityName = "", itemName = "") {
  const key = specialityName.toLowerCase().trim();
  if (SPECIALITY_TO_DISCIPLINE[key]) return SPECIALITY_TO_DISCIPLINE[key];
  const name = itemName.toLowerCase();
  if (name.includes("exorcis")) return "exorcism";
  if (name.includes("g\xE9omanci") || name.includes("geomanci")) return "geomancy";
  if (name.includes("alchimi")) return "alchemy";
  if (name.includes("cinnabre") || name.includes("interne")) return "internalcinnabar";
  if (name.includes("ma\xEEtrise") || name.includes("maitrise") || name.includes("tao")) return "masteryoftheway";
  return "internalcinnabar";
}
function mapActivation(oldActivation = "") {
  const s = oldActivation.toLowerCase();
  if (s.includes("inflig\xE9s") || s.includes("infliges")) return "damage-inflicted";
  if (s.includes("re\xE7us") || s.includes("recus")) return "damage-received";
  if (s.includes("r\xE9action") || s.includes("reaction")) return "reaction";
  if (s.includes("d\xE9s-fastes") || s.includes("des-fastes") || s.includes("fastes")) return "dice";
  if (s.includes("aide")) return "action-aid";
  if (s.includes("attaque") && s.includes("d\xE9fense")) return "action-attack-defense";
  if (s.includes("attaque") && s.includes("defense")) return "action-attack-defense";
  if (s.includes("attaque")) return "action-attack";
  if (s.includes("d\xE9fense") || s.includes("defense")) return "action-defense";
  return "action-attack";
}
var DEFAULT_ACTOR_IMG = "icons/svg/mystery-man.svg";
var DEFAULT_ITEM_IMG = "icons/svg/item-bag.svg";
function migrateEquipmentItem(oldItem) {
  const s = oldItem.system ?? {};
  return {
    name: oldItem.name,
    type: "item",
    img: oldItem.img || DEFAULT_ITEM_IMG,
    system: {
      reference: s.reference ?? "",
      description: s.description ?? "",
      quantity: Number(s.quantity ?? 1),
      weight: Number(s.weight ?? 0),
      notes: s.notes ?? ""
    }
  };
}
function migrateKungfuItem(oldItem) {
  const s = oldItem.system ?? {};
  const techs = s.techniques ?? {};
  const migratedTechs = {};
  for (const key of ["technique1", "technique2", "technique3"]) {
    const t = techs[key] ?? {};
    migratedTechs[key] = {
      check: Boolean(t.check),
      name: t.name ?? "",
      activation: mapActivation(t.activation ?? ""),
      technique: t.technique ?? ""
    };
  }
  return {
    name: oldItem.name,
    type: "kungfu",
    img: oldItem.img || DEFAULT_ITEM_IMG,
    system: {
      reference: s.reference ?? "",
      description: s.description ?? "",
      orientation: s.orientation || "yin",
      aspect: s.aspect || "metal",
      skill: s.skill || "kungfu",
      speciality: s.speciality ?? "",
      style: s.style ?? "",
      techniques: migratedTechs,
      notes: s.notes ?? ""
    }
  };
}
function migrateSpellItem(oldItem) {
  const s = oldItem.system ?? {};
  return {
    name: oldItem.name,
    type: "spell",
    img: oldItem.img || DEFAULT_ITEM_IMG,
    system: {
      reference: s.reference ?? "",
      description: s.description ?? "",
      specialityname: s.specialityname ?? "",
      associatedelement: elementKey(s.associatedelement ?? ""),
      heiType: heiKey(s.hei ?? ""),
      heiCost: Number(s.heiCost ?? 0),
      difficulty: Number(s.difficulty ?? 0),
      realizationtimeritual: s.realizationtimeritual ?? "",
      realizationtimeaccelerated: s.realizationtimeaccelerated ?? "",
      flashback: s.flashback ?? "",
      components: s.components ?? "",
      effects: s.effects ?? "",
      examples: s.examples ?? "",
      notes: s.notes ?? "",
      discipline: inferDiscipline(s.specialityname ?? "", oldItem.name ?? "")
    }
  };
}
function migrateSupernaturalItem(oldItem) {
  const s = oldItem.system ?? {};
  const nestedRef = s.supernatural?.reference ?? "";
  return {
    name: oldItem.name,
    type: "supernatural",
    img: oldItem.img || DEFAULT_ITEM_IMG,
    system: {
      reference: s.reference || nestedRef,
      description: s.description ?? "",
      notes: s.notes ?? "",
      heiType: "yin",
      heiCost: 0,
      trigger: "",
      effects: ""
    }
  };
}
function migrateWeaponItem(oldItem) {
  const s = oldItem.system ?? {};
  return {
    name: oldItem.name,
    type: "weapon",
    img: oldItem.img || DEFAULT_ITEM_IMG,
    system: {
      reference: s.reference ?? "",
      description: s.description ?? "",
      hasSpeciality: Boolean(s.hasSpeciality),
      weaponType: s.weaponType || "melee",
      material: s.material ?? "",
      damageAspect: elementKey(s.damageAspect ?? ""),
      damageBase: Number(s.damageBase ?? 0),
      range: s.range || "contact",
      obtainLevel: Number(s.obtainLevel ?? 0),
      obtainDifficulty: Number(s.obtainDifficulty ?? 0),
      quantity: Number(s.quantity ?? 1),
      notes: s.notes ?? ""
    }
  };
}
function migrateArmorItem(oldItem) {
  const s = oldItem.system ?? {};
  return {
    name: oldItem.name,
    type: "armor",
    img: oldItem.img || DEFAULT_ITEM_IMG,
    system: {
      reference: s.reference ?? "",
      description: s.description ?? "",
      protectionValue: Number(s.protectionValue ?? 0),
      domain: s.domain ?? "",
      obtainLevel: Number(s.obtainLevel ?? 0),
      obtainDifficulty: Number(s.obtainDifficulty ?? 0),
      quantity: Number(s.quantity ?? 1),
      notes: s.notes ?? ""
    }
  };
}
function migrateSanheiItem(oldItem) {
  const s = oldItem.system ?? {};
  const props = s.properties ?? {};
  const propSchema = (p) => ({
    name: p?.name ?? "",
    heiCost: Number(p?.heiCost ?? 0),
    heiType: heiKey(p?.heiType ?? ""),
    description: p?.description ?? ""
  });
  return {
    name: oldItem.name,
    type: "sanhei",
    img: oldItem.img || DEFAULT_ITEM_IMG,
    system: {
      reference: s.reference ?? "",
      description: s.description ?? "",
      heiType: heiKey(s.heiType ?? ""),
      properties: {
        prop1: propSchema(props.prop1),
        prop2: propSchema(props.prop2),
        prop3: propSchema(props.prop3)
      },
      notes: s.notes ?? ""
    }
  };
}
function migrateIngredientItem(oldItem) {
  const s = oldItem.system ?? {};
  return {
    name: oldItem.name,
    type: "ingredient",
    img: oldItem.img || DEFAULT_ITEM_IMG,
    system: {
      reference: s.reference ?? "",
      description: s.description ?? "",
      school: s.school ?? "all",
      obtainLevel: Number(s.obtainLevel ?? 0),
      obtainDifficulty: Number(s.obtainDifficulty ?? 0),
      quantity: Number(s.quantity ?? 1),
      notes: s.notes ?? ""
    }
  };
}
function migrateItem(oldItem) {
  switch (oldItem.type) {
    case "item":
      return migrateEquipmentItem(oldItem);
    case "kungfu":
      return migrateKungfuItem(oldItem);
    case "spell":
      return migrateSpellItem(oldItem);
    case "supernatural":
      return migrateSupernaturalItem(oldItem);
    case "weapon":
      return migrateWeaponItem(oldItem);
    case "armor":
      return migrateArmorItem(oldItem);
    case "sanhei":
      return migrateSanheiItem(oldItem);
    case "ingredient":
      return migrateIngredientItem(oldItem);
    default:
      return migrateEquipmentItem({ ...oldItem, type: "item" });
  }
}
function migrateCharacter(old) {
  const s = old.system ?? {};
  const aspect = {};
  for (const [k, v] of Object.entries(s.aspect ?? {})) {
    aspect[k] = { chinese: v.chinese ?? "", label: v.label ?? "", value: Number(v.value ?? 0) };
  }
  const skills = {};
  for (const [k, v] of Object.entries(s.skills ?? {})) {
    skills[k] = { label: v.label ?? "", specialities: v.specialities ?? "", value: Number(v.value ?? 0) };
  }
  const resources = {};
  for (const [k, v] of Object.entries(s.resources ?? {})) {
    resources[k] = { label: v.label ?? "", specialities: v.specialities ?? "", value: Number(v.value ?? 0), debt: Boolean(v.debt) };
  }
  const component = {};
  for (const [k, v] of Object.entries(s.component ?? {})) {
    component[k] = { value: v.value ?? "" };
  }
  const MAGIC_SPECIALITIES = {
    internalcinnabar: ["essence", "mind", "purification", "manipulation", "aura"],
    alchemy: ["acupuncture", "elixirs", "poisons", "arsenal", "potions"],
    masteryoftheway: ["curse", "transfiguration", "necromancy", "climatecontrol", "goldenmagic"],
    exorcism: ["invocation", "tracking", "protection", "punishment", "domination"],
    geomancy: ["neutralization", "divination", "earthlyprayer", "heavenlyprayer", "fungseoi"]
  };
  const magics = {};
  for (const [school, specs] of Object.entries(MAGIC_SPECIALITIES)) {
    const om = s.magics?.[school] ?? {};
    const speciality = {};
    for (const spec of specs) {
      speciality[spec] = { check: Boolean(om.speciality?.[spec]?.check) };
    }
    magics[school] = { visible: Boolean(om.visible), value: Number(om.value ?? 0), speciality };
  }
  const tt = s.threetreasures ?? {};
  const threetreasures = {
    heiyang: { value: Number(tt.heiyang?.value ?? 0), max: Number(tt.heiyang?.max ?? 0) },
    heiyin: { value: Number(tt.heiyin?.value ?? 0), max: Number(tt.heiyin?.max ?? 0) },
    dicelevel: {
      level0d: {
        san: { value: Number(tt.dicelevel?.level0d?.san?.value ?? 0), max: Number(tt.dicelevel?.level0d?.san?.max ?? 0) },
        zing: { value: Number(tt.dicelevel?.level0d?.zing?.value ?? 0), max: Number(tt.dicelevel?.level0d?.zing?.max ?? 0) }
      },
      level1d: {
        san: { value: Number(tt.dicelevel?.level1d?.san?.value ?? 0), max: Number(tt.dicelevel?.level1d?.san?.max ?? 0) },
        zing: { value: Number(tt.dicelevel?.level1d?.zing?.value ?? 0), max: Number(tt.dicelevel?.level1d?.zing?.max ?? 0) }
      },
      level2d: {
        san: { value: Number(tt.dicelevel?.level2d?.san?.value ?? 0), max: Number(tt.dicelevel?.level2d?.san?.max ?? 0) },
        zing: { value: Number(tt.dicelevel?.level2d?.zing?.value ?? 0), max: Number(tt.dicelevel?.level2d?.zing?.max ?? 0) }
      }
    }
  };
  const description = s.description || s.biography || "";
  return {
    name: old.name,
    type: "character",
    img: old.img || DEFAULT_ACTOR_IMG,
    system: {
      concept: s.concept ?? "",
      guardian: parseInt(s.guardian ?? "0") || 0,
      initiative: Number(s.initiative ?? 1),
      anti_initiative: Number(s.anti_initiative ?? 24),
      description,
      aspect,
      skills,
      resources,
      component,
      magics,
      magicOrder: [],
      threetreasures,
      experience: {
        value: Number(s.experience?.value ?? 0),
        max: Number(s.experience?.max ?? 0),
        min: Number(s.experience?.min ?? 0)
      }
    },
    items: (old.items ?? []).map(migrateItem)
  };
}
function migrateNpc(old) {
  const s = old.system ?? {};
  const aptitudes = {};
  for (const [k, v] of Object.entries(s.aptitudes ?? {})) {
    aptitudes[k] = { value: Number(v.value ?? 0), speciality: v.speciality ?? "" };
  }
  return {
    name: old.name,
    type: "npc",
    img: old.img || DEFAULT_ACTOR_IMG,
    system: {
      type: s.type ?? "",
      // Old system had separate `levelofthreat`/`powerofnuisance` as numbers
      // and string copies `threat`/`nuisance` — use the numeric fields
      threat: Number(s.levelofthreat ?? s.threat ?? 0),
      nuisance: Number(s.powerofnuisance ?? s.nuisance ?? 0),
      initiative: Number(s.initiative ?? 1),
      anti_initiative: Number(s.anti_initiative ?? 24),
      aptitudes,
      vitality: {
        value: Number(s.vitality?.value ?? 0),
        calcul: Number(s.vitality?.calcul ?? 0),
        note: s.vitality?.note ?? ""
      },
      hei: {
        value: Number(s.hei?.value ?? 0),
        calcul: Number(s.hei?.calcul ?? 0),
        note: s.hei?.note ?? ""
      },
      description: s.description ?? ""
    },
    items: (old.items ?? []).map(migrateItem)
  };
}
function migrateActor(oldJson) {
  switch (oldJson.type) {
    case "character":
      return migrateCharacter(oldJson);
    case "npc":
      return migrateNpc(oldJson);
    default:
      throw new Error(`Unknown actor type "${oldJson.type}" in "${oldJson.name}"`);
  }
}
function parseLegacyJson(jsonText) {
  const parsed = JSON.parse(jsonText);
  if (typeof parsed !== "object" || parsed === null) {
    throw new Error("Le fichier JSON doit contenir un objet acteur ou un tableau d'acteurs");
  }
  const actors = Array.isArray(parsed) ? parsed : [parsed];
  return actors.map(migrateActor);
}

// src/ui/apps/migration-app.js
var MIGRATION_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-migration-app.html";
var CDEMigrationApp = class _CDEMigrationApp extends foundry.applications.api.HandlebarsApplicationMixin(
  foundry.applications.api.ApplicationV2
) {
  static DEFAULT_OPTIONS = {
    id: "cde-migration-app",
    classes: ["cde-migration-app"],
    tag: "div",
    window: {
      title: "CDE.MigrationTitle",
      icon: "fas fa-file-import",
      resizable: false
    },
    position: { width: 600, height: "auto" },
    actions: {
      clearFiles: _CDEMigrationApp.#clearFiles,
      doImport: _CDEMigrationApp.#doImport,
      confirmImport: _CDEMigrationApp.#confirmImport,
      cancelImport: _CDEMigrationApp.#cancelImport
    }
  };
  static PARTS = {
    form: { template: MIGRATION_TEMPLATE }
  };
  /** @type {Array<{name: string, type: string, img: string, system: object, items: object[], _srcFile: string}>} */
  #pending = [];
  /** @type {string[]} - error messages per file */
  #errors = [];
  /** @type {"idle"|"confirm"|"importing"} */
  #importState = "idle";
  /** @type {number} - actors created so far (during importing) */
  #progress = 0;
  async _prepareContext(options) {
    const enrichDuplicate = (a) => ({
      ...a,
      _duplicate: game.actors?.getName(a.name) !== null
    });
    const pending = this.#pending.map(enrichDuplicate);
    const duplicateCount = pending.filter((a) => a._duplicate).length;
    return {
      pending,
      errors: this.#errors,
      hasPending: this.#pending.length > 0,
      hasErrors: this.#errors.length > 0,
      hasDuplicates: duplicateCount > 0,
      duplicateCount,
      count: this.#pending.length,
      importState: this.#importState,
      progress: this.#progress,
      total: this.#pending.length
    };
  }
  /** After render, wire up the file input. */
  _onRender(context, options) {
    super._onRender(context, options);
    const input = this.element.querySelector(".cde-migration-file-input");
    input?.addEventListener("change", this.#onFileChange.bind(this));
    const dropZone = this.element.querySelector(".cde-migration-drop-zone");
    if (dropZone) {
      dropZone.addEventListener("dragover", (e) => {
        e.preventDefault();
        dropZone.classList.add("is-dragover");
      });
      dropZone.addEventListener("dragleave", () => dropZone.classList.remove("is-dragover"));
      dropZone.addEventListener("drop", (e) => {
        e.preventDefault();
        dropZone.classList.remove("is-dragover");
        this.#processFiles(Array.from(e.dataTransfer.files));
      });
    }
  }
  async #onFileChange(event) {
    const files = Array.from(event.target.files ?? []);
    event.target.value = "";
    await this.#processFiles(files);
  }
  async #processFiles(files) {
    for (const file of files) {
      if (!file.name.endsWith(".json")) {
        this.#errors.push(game.i18n.format("CDE.MigrationErrorNotJson", { file: file.name }));
        continue;
      }
      try {
        const text = await file.text();
        const actors = parseLegacyJson(text);
        for (const actor of actors) {
          actor._srcFile = file.name;
          if (this.#pending.some((p) => p.name === actor.name)) {
            this.#errors.push(`\xAB ${actor.name} \xBB ignor\xE9 (nom d\xE9j\xE0 dans la liste d'attente, fichier \xAB ${file.name} \xBB)`);
            continue;
          }
          this.#pending.push(actor);
        }
      } catch (err) {
        this.#errors.push(game.i18n.format("CDE.MigrationErrorParse", { file: file.name, error: err.message }));
      }
    }
    this.#importState = "idle";
    this.render();
  }
  static async #clearFiles() {
    this.#pending = [];
    this.#errors = [];
    this.#importState = "idle";
    this.#progress = 0;
    this.render();
  }
  /** First click: switch to confirmation state instead of importing immediately. */
  static async #doImport() {
    if (!this.#pending.length) return;
    this.#importState = "confirm";
    this.render();
  }
  /** Second click: actually perform the import. */
  static async #confirmImport() {
    if (!this.#pending.length) return;
    this.#importState = "importing";
    this.#progress = 0;
    this.render();
    const total = this.#pending.length;
    const created = [];
    const failed = [];
    for (let i = 0; i < total; i++) {
      const data = this.#pending[i];
      try {
        const { _srcFile, ...actorData } = data;
        const actor = await Actor.create(actorData);
        created.push(actor.name);
      } catch (err) {
        failed.push(`${data.name}: ${err.message}`);
        console.error(`CHRONIQUESDELETRANGE | Import failed for "${data.name}":`, err);
      }
      this.#progress = i + 1;
      const progEl = this.element?.querySelector(".cde-migration-progress-count");
      if (progEl) progEl.textContent = `${this.#progress}/${total}`;
    }
    this.#pending = [];
    this.#errors = failed;
    this.#importState = "idle";
    this.#progress = 0;
    this.render();
    if (created.length) {
      ui.notifications.info(
        game.i18n.format("CDE.MigrationSuccess", { count: created.length, names: created.join(", ") })
      );
    }
    if (failed.length) {
      ui.notifications.warn(
        game.i18n.format("CDE.MigrationPartialError", { count: failed.length })
      );
    }
  }
  static async #cancelImport() {
    this.#importState = "idle";
    this.render();
  }
};

// src/config/settings.js
function registerSettings() {
  game.settings.registerMenu(SYSTEM_ID, "migrationTool", {
    name: "CDE.MigrationTitle",
    label: "CDE.MigrationMenuLabel",
    hint: "CDE.MigrationMenuHint",
    icon: "fas fa-file-import",
    type: CDEMigrationApp,
    restricted: true
  });
  game.settings.register(SYSTEM_ID, "loksyuData", {
    scope: "world",
    config: false,
    type: Object,
    default: {
      wood: { yin: 0, yang: 0 },
      fire: { yin: 0, yang: 0 },
      earth: { yin: 0, yang: 0 },
      metal: { yin: 0, yang: 0 },
      water: { yin: 0, yang: 0 }
    }
  });
  game.settings.register(SYSTEM_ID, "loksyuConsumptionOrder", {
    name: "CDE.Settings.LoksyuConsumptionOrder",
    hint: "CDE.Settings.LoksyuConsumptionOrderHint",
    scope: "world",
    config: true,
    type: String,
    choices: {
      "yang-first": "CDE.Settings.LoksyuOrderYangFirst",
      "yin-first": "CDE.Settings.LoksyuOrderYinFirst",
      "balanced": "CDE.Settings.LoksyuOrderBalanced"
    },
    default: "yang-first",
    onChange: () => Hooks.callAll("cde:loksyuUpdated")
  });
  game.settings.register(SYSTEM_ID, "tinjiData", {
    scope: "world",
    config: false,
    type: Number,
    default: 0
  });
  game.settings.register(SYSTEM_ID, "welcomeSceneLoaded", {
    scope: "world",
    config: false,
    type: Boolean,
    default: false
  });
}
async function migrateIfNeeded() {
}
async function loadWelcomeSceneIfNeeded() {
  if (!game.user.isGM) return;
  if (game.settings.get(SYSTEM_ID, "welcomeSceneLoaded")) return;
  try {
    const pack = game.packs.get(`${SYSTEM_ID}.cde-scenes`);
    if (!pack) return;
    const index = await pack.getIndex();
    const entry = index.find((e) => e.name === "Accueil");
    if (!entry) return;
    const existing = game.scenes.find((s) => s.name === "Accueil");
    let scene = existing;
    if (!scene) {
      const doc = await pack.getDocument(entry._id);
      [scene] = await Scene.createDocuments([doc.toObject()]);
    }
    await game.settings.set(SYSTEM_ID, "welcomeSceneLoaded", true);
    await scene.activate();
  } catch (err) {
    console.error("CHRONIQUESDELETRANGE | loadWelcomeSceneIfNeeded failed:", err);
  }
}

// src/config/localize.js
function preLocalizeConfig() {
  const localizeConfigObject = (obj, keys) => {
    for (const o of Object.values(obj)) {
      for (const key of keys) {
        o[key] = game.i18n.localize(o[key]);
      }
    }
  };
  localizeConfigObject(SUBTYPES, ["label"]);
  Object.values(MAGICS).forEach((magic) => {
    magic.label = game.i18n.localize(magic.label);
    magic.aspectlabel = game.i18n.localize(magic.aspectlabel);
    Object.values(magic.speciality).forEach((spec) => {
      spec.label = game.i18n.localize(spec.label);
      spec.labelelementkey = spec.labelelement;
      spec.labelelement = game.i18n.localize(spec.labelelement);
    });
  });
}

// src/config/runtime.js
function configureRuntime() {
}

// src/data/actors/character.js
var CharacterDataModel = class extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const { fields } = foundry.data;
    const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra });
    const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
    const boolField = (initial = false) => new fields.BooleanField({ required: true, initial });
    const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
    const aspectField = (label, chinese) => new fields.SchemaField({
      chinese: stringField(chinese),
      label: stringField(label),
      value: numberField(15, { min: 0 })
    });
    const skillField = (label) => new fields.SchemaField({
      label: stringField(label),
      specialities: stringField(""),
      value: numberField(0, { min: 0 })
    });
    const resourceField = (label) => new fields.SchemaField({
      label: stringField(label),
      specialities: stringField(""),
      value: numberField(0, { min: 0 }),
      debt: boolField(false)
    });
    const componentField = () => new fields.SchemaField({
      value: stringField("")
    });
    const magicSpecialityField = () => new fields.SchemaField({
      check: boolField(false)
    });
    const magicField = () => new fields.SchemaField({
      visible: boolField(true),
      value: numberField(0, { min: 0 }),
      speciality: new fields.SchemaField({
        essence: magicSpecialityField(),
        mind: magicSpecialityField(),
        purification: magicSpecialityField(),
        manipulation: magicSpecialityField(),
        aura: magicSpecialityField(),
        acupuncture: magicSpecialityField(),
        elixirs: magicSpecialityField(),
        poisons: magicSpecialityField(),
        arsenal: magicSpecialityField(),
        potions: magicSpecialityField(),
        curse: magicSpecialityField(),
        transfiguration: magicSpecialityField(),
        necromancy: magicSpecialityField(),
        climatecontrol: magicSpecialityField(),
        goldenmagic: magicSpecialityField(),
        invocation: magicSpecialityField(),
        tracking: magicSpecialityField(),
        protection: magicSpecialityField(),
        punishment: magicSpecialityField(),
        domination: magicSpecialityField(),
        neutralization: magicSpecialityField(),
        divination: magicSpecialityField(),
        earthlyprayer: magicSpecialityField(),
        heavenlyprayer: magicSpecialityField(),
        fungseoi: magicSpecialityField()
      })
    });
    const treasureBranch = () => new fields.SchemaField({
      value: numberField(0, { min: 0 }),
      max: numberField(0, { min: 0 }),
      min: numberField(0, { min: 0 })
    });
    const treasureLevel = () => new fields.SchemaField({
      san: treasureBranch(),
      zing: treasureBranch()
    });
    const schema = {
      concept: stringField(""),
      guardian: numberField(0, { min: 0, max: 5 }),
      initiative: numberField(1, { min: 0 }),
      anti_initiative: numberField(24, { min: 0 }),
      description: htmlField(""),
      prefs: new fields.SchemaField({
        typeofthrow: new fields.SchemaField({
          check: boolField(true),
          choice: stringField("0")
        })
      }),
      prompt: new fields.SchemaField({
        typeofthrow: new fields.SchemaField({
          check: boolField(true),
          choice: stringField("0")
        }),
        configure: new fields.SchemaField({
          numberofdice: numberField(0),
          aspect: numberField(0),
          bonus: numberField(0),
          bonusauspiciousdice: numberField(0),
          typeofthrow: numberField(0),
          aspectskill: numberField(0),
          bonusmalusskill: numberField(0),
          rolldifficulty: numberField(0),
          freepowerlevels: numberField(0)
        })
      }),
      aspect: new fields.SchemaField({
        fire: aspectField("CDE.Fire", "\u328B"),
        earth: aspectField("CDE.Earth", "\u328F"),
        metal: aspectField("CDE.Metal", "\u328E"),
        water: aspectField("CDE.Water", "\u328C"),
        wood: aspectField("CDE.Wood", "\u328D")
      }),
      skills: new fields.SchemaField({
        art: skillField("CDE.Art"),
        investigation: skillField("CDE.Investigation"),
        erudition: skillField("CDE.Erudition"),
        knavery: skillField("CDE.Knavery"),
        wordliness: skillField("CDE.Wordliness"),
        prowess: skillField("CDE.Prowess"),
        sciences: skillField("CDE.Sciences"),
        technologies: skillField("CDE.Technologies"),
        kungfu: skillField("CDE.KungFu"),
        rangedcombat: skillField("CDE.RangedCombat")
      }),
      resources: new fields.SchemaField({
        supply: resourceField("CDE.Supply"),
        inquiry: resourceField("CDE.Inquiry"),
        influence: resourceField("CDE.Influence")
      }),
      component: new fields.SchemaField({
        one: componentField(),
        two: componentField(),
        three: componentField(),
        four: componentField(),
        five: componentField(),
        six: componentField(),
        seven: componentField(),
        eight: componentField(),
        nine: componentField(),
        zero: componentField()
      }),
      magicOrder: new fields.ArrayField(
        new fields.StringField({ required: true, nullable: false, initial: "" }),
        { required: true, initial: [] }
      ),
      magics: new fields.SchemaField({
        internalcinnabar: magicField(),
        alchemy: magicField(),
        masteryoftheway: magicField(),
        exorcism: magicField(),
        geomancy: magicField()
      }),
      threetreasures: new fields.SchemaField({
        heiyang: new fields.SchemaField({ value: numberField(0, { min: 0 }), max: numberField(0, { min: 0 }) }),
        heiyin: new fields.SchemaField({ value: numberField(0, { min: 0 }), max: numberField(0, { min: 0 }) }),
        dicelevel: new fields.SchemaField({
          level0d: treasureLevel(),
          level1d: treasureLevel(),
          level2d: treasureLevel()
        })
      }),
      experience: new fields.SchemaField({
        value: numberField(0, { min: 0 }),
        max: numberField(0, { min: 0 }),
        min: numberField(0, { min: 0 })
      })
    };
    return schema;
  }
  prepareDerivedData() {
    this.anti_initiative = 25 - (this.initiative ?? 0);
  }
};

// src/data/actors/npc.js
var NpcDataModel = class extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const { fields } = foundry.data;
    const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra });
    const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
    const boolField = (initial = false) => new fields.BooleanField({ required: true, initial });
    const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
    const aptitudeField = () => new fields.SchemaField({
      value: numberField(0, { min: 0 }),
      speciality: stringField("")
    });
    const trackedField = () => new fields.SchemaField({
      value: numberField(0, { min: 0 }),
      calcul: numberField(0, { min: 0 }),
      note: stringField("")
    });
    return {
      type: stringField(""),
      threat: numberField(0, { min: 0, max: 4 }),
      // profane(0) | apprentice(1) | initiate(2) | accomplished(3) | renowned(4)
      nuisance: numberField(0, { min: 0, max: 5 }),
      // figurant(0) | minion(1) | adversary(2) | ally(3) | boss(4) | divinity(5)
      initiative: numberField(1, { min: 0 }),
      anti_initiative: numberField(24, { min: 0 }),
      aptitudes: new fields.SchemaField({
        physical: aptitudeField(),
        martial: aptitudeField(),
        mental: aptitudeField(),
        social: aptitudeField(),
        spiritual: aptitudeField()
      }),
      vitality: trackedField(),
      hei: trackedField(),
      description: htmlField(""),
      prefs: new fields.SchemaField({
        typeofthrow: new fields.SchemaField({
          check: boolField(false),
          choice: stringField("0")
        })
      })
    };
  }
  prepareDerivedData() {
    this.anti_initiative = 25 - (this.initiative ?? 0);
    this.vitality.calcul = (this.aptitudes.physical.value ?? 0) * 4;
    this.hei.calcul = (this.aptitudes.spiritual.value ?? 0) * 4;
  }
};

// src/data/items/item.js
var EquipmentDataModel = class extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const { fields } = foundry.data;
    const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra });
    const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
    const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
    return {
      reference: stringField(""),
      description: htmlField(""),
      quantity: numberField(1, { min: 0 }),
      weight: numberField(0, { min: 0 }),
      notes: htmlField("")
    };
  }
};

// src/data/items/kungfu.js
var KungfuDataModel = class extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const { fields } = foundry.data;
    const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
    const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
    const boolField = (initial = false) => new fields.BooleanField({ required: true, initial });
    const techniqueField = () => new fields.SchemaField({
      check: boolField(false),
      name: stringField(""),
      activation: stringField("action-attack"),
      // action-attack | action-defense | action-aid | action-attack-defense | reaction | dice | damage-inflicted | damage-received
      technique: htmlField("")
    });
    return {
      reference: stringField(""),
      description: htmlField(""),
      orientation: stringField("yin"),
      // yin | yang | yinyang
      aspect: stringField("metal"),
      // metal | water | earth | fire | wood
      skill: stringField("kungfu"),
      // kungfu | rangedcombat
      speciality: stringField(""),
      style: stringField(""),
      techniques: new fields.SchemaField({
        technique1: techniqueField(),
        technique2: techniqueField(),
        technique3: techniqueField()
      }),
      notes: htmlField("")
    };
  }
};

// src/data/items/spell.js
var SpellDataModel = class extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const { fields } = foundry.data;
    const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
    const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
    return {
      reference: stringField(""),
      description: htmlField(""),
      specialityname: stringField(""),
      associatedelement: stringField("metal"),
      // metal | water | earth | fire | wood
      hei: stringField(""),
      realizationtimeritual: stringField(""),
      realizationtimeaccelerated: stringField(""),
      flashback: stringField(""),
      components: htmlField(""),
      effects: htmlField(""),
      examples: htmlField(""),
      notes: htmlField(""),
      discipline: stringField("internalcinnabar"),
      heiType: stringField("yin"),
      heiCost: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 1 }),
      difficulty: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 1 })
    };
  }
};

// src/data/items/supernatural.js
var SupernaturalDataModel = class extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const { fields } = foundry.data;
    const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
    const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
    return {
      reference: stringField(""),
      description: htmlField(""),
      notes: htmlField(""),
      heiType: stringField("yin"),
      heiCost: new fields.NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 0 }),
      trigger: stringField(""),
      effects: htmlField("")
    };
  }
};

// src/data/items/weapon.js
var WeaponDataModel = class extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const { fields } = foundry.data;
    const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
    const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
    const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts });
    const boolField = (initial = false) => new fields.BooleanField({ required: true, initial });
    return {
      reference: stringField(""),
      description: htmlField(""),
      hasSpeciality: boolField(false),
      weaponType: stringField("melee"),
      material: stringField(""),
      damageAspect: stringField("metal"),
      damageBase: intField(0),
      range: stringField("contact"),
      // contact | courte | mediane | longue | extreme
      obtainLevel: intField(0, { min: 0, max: 5 }),
      obtainDifficulty: intField(0, { min: 0, max: 3 }),
      quantity: intField(1, { min: 0 }),
      notes: htmlField("")
    };
  }
};

// src/data/items/armor.js
var ArmorDataModel = class extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const { fields } = foundry.data;
    const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
    const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
    const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts });
    return {
      reference: stringField(""),
      description: htmlField(""),
      protectionValue: intField(0),
      domain: stringField(""),
      obtainLevel: intField(0, { min: 0, max: 5 }),
      obtainDifficulty: intField(0, { min: 0, max: 3 }),
      quantity: intField(1, { min: 0 }),
      notes: htmlField("")
    };
  }
};

// src/data/items/sanhei.js
var SanheiDataModel = class extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const { fields } = foundry.data;
    const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
    const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
    const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts });
    const propertySchema = () => new fields.SchemaField({
      name: stringField(""),
      heiCost: intField(0),
      heiType: stringField("yin"),
      description: htmlField("")
    });
    return {
      reference: stringField(""),
      description: htmlField(""),
      heiType: stringField("yin"),
      properties: new fields.SchemaField({
        prop1: propertySchema(),
        prop2: propertySchema(),
        prop3: propertySchema()
      }),
      notes: htmlField("")
    };
  }
};

// src/data/items/ingredient.js
var IngredientDataModel = class extends foundry.abstract.TypeDataModel {
  static defineSchema() {
    const { fields } = foundry.data;
    const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial });
    const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true });
    const intField = (initial = 0, opts = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...opts });
    return {
      reference: stringField(""),
      description: htmlField(""),
      school: stringField("all"),
      obtainLevel: intField(0, { min: 0, max: 5 }),
      obtainDifficulty: intField(0, { min: 0, max: 3 }),
      quantity: intField(1, { min: 0 }),
      notes: htmlField("")
    };
  }
};

// src/documents/chat-message.js
var CDEMessage = class extends ChatMessage {
  async renderHTML({ canDelete, canClose = false, ...rest } = {}) {
    const html = await super.renderHTML({ canDelete, canClose, ...rest });
    this.#enrichChatCard(html);
    return html;
  }
  #enrichChatCard(html) {
    const tokenDoc = this.speaker.scene && this.speaker.token ? game.scenes.get(this.speaker.scene)?.tokens.get(this.speaker.token) : null;
    const actor = tokenDoc?.actor ?? game.actors.get(this.speaker.actor) ?? null;
    const [img, nameText] = this.isContentVisible ? [actor?.img ?? this.author.avatar, this.alias] : [this.author.avatar, this.author.name];
    const avatarImg = Object.assign(document.createElement("img"), { src: img, alt: nameText });
    const avatar = Object.assign(document.createElement("a"), { className: "avatar" });
    if (actor) avatar.dataset.uuid = actor.uuid;
    avatar.append(avatarImg);
    const title = Object.assign(document.createElement("span"), { className: "title", textContent: nameText });
    const name = Object.assign(document.createElement("span"), { className: "name-stacked" });
    name.append(title);
    html.querySelector(".message-sender")?.replaceChildren(avatar, name);
  }
};

// src/documents/actor.js
var CDEActor = class extends Actor {
  getRollData() {
    return this.toObject(false).system;
  }
};

// src/documents/item.js
var DEFAULT_ICONS = {
  kungfu: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-kungfu.svg",
  spell: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-spell.svg",
  supernatural: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-supernatural.svg",
  weapon: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-weapon.svg",
  armor: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-armor.svg",
  sanhei: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-sanhei.svg",
  ingredient: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-ingredient.svg",
  item: "systems/fvtt-chroniques-de-l-etrange/images/icons/icon-item.svg"
};
var CDEItem = class extends Item {
  /** @override */
  async _preCreate(data, options, userId) {
    await super._preCreate(data, options, userId);
    const defaultIcon = DEFAULT_ICONS[this.type];
    if (defaultIcon && (!data.img || data.img === Item.DEFAULT_ICON)) {
      this.updateSource({ img: defaultIcon });
    }
  }
};

// src/ui/helpers.js
function registerHandlebarsHelpers() {
  const { Handlebars } = globalThis;
  if (!Handlebars) return;
  Handlebars.registerHelper("getMagicLabel", function(magic) {
    return game.i18n.localize(MAGICS[magic]?.label ?? "");
  });
  Handlebars.registerHelper("getMagicAspectLabel", function(magic) {
    return game.i18n.localize(MAGICS[magic]?.aspectlabel ?? "");
  });
  Handlebars.registerHelper("getMagicSpecialityLabel", function(magic, speciality) {
    return game.i18n.localize(MAGICS[magic]?.speciality?.[speciality]?.label ?? "");
  });
  Handlebars.registerHelper("getMagicSpecialityClassIcon", function(magic, speciality) {
    return MAGICS[magic]?.speciality?.[speciality]?.classicon ?? "";
  });
  Handlebars.registerHelper("getMagicSpecialityElementIcon", function(magic, speciality) {
    return MAGICS[magic]?.speciality?.[speciality]?.elementicon ?? "";
  });
  Handlebars.registerHelper("getMagicSpecialityLabelIcon", function(magic, speciality) {
    return MAGICS[magic]?.speciality?.[speciality]?.labelicon ?? "";
  });
  Handlebars.registerHelper("getMagicSpecialityLabelElement", function(magic, speciality) {
    return game.i18n.localize(MAGICS[magic]?.speciality?.[speciality]?.labelelement ?? "");
  });
  Handlebars.registerHelper("getMagicAspectIcon", function(magic) {
    const icons = {
      internalcinnabar: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp",
      alchemy: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
      masteryoftheway: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
      exorcism: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
      geomancy: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp"
    };
    return icons[magic] ?? "";
  });
  Handlebars.registerHelper("getElementIcon", function(aspect) {
    const icons = {
      metal: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.webp",
      water: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
      earth: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
      fire: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
      wood: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp",
      // legacy French keys
      eau: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.webp",
      terre: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.webp",
      feu: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.webp",
      bois: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.webp"
    };
    return icons[aspect] ?? "";
  });
  Handlebars.registerHelper("getOrientationIcon", function(orientation) {
    const icons = {
      yin: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.webp",
      yang: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.webp",
      yinyang: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.webp"
    };
    return icons[orientation] ?? "";
  });
  Handlebars.registerHelper("getOrientationLabel", function(orientation) {
    const keys = {
      yin: "CDE.OrientationYin",
      yang: "CDE.OrientationYang",
      yinyang: "CDE.OrientationYinYang"
    };
    return game.i18n.localize(keys[orientation] ?? "CDE.Orientation");
  });
  Handlebars.registerHelper("getActivationLabel", function(activation) {
    const keys = {
      "action-attack": "CDE.ActivationAttack",
      "action-defense": "CDE.ActivationDefense",
      "action-aid": "CDE.ActivationAid",
      "action-attack-defense": "CDE.ActivationAttackOrDefense",
      reaction: "CDE.ActivationReaction",
      dice: "CDE.ActivationDice",
      "damage-inflicted": "CDE.ActivationDamageInflicted",
      "damage-received": "CDE.ActivationDamageReceived"
    };
    return game.i18n.localize(keys[activation] ?? "CDE.Activation");
  });
  Handlebars.registerHelper("cranPosition", function(cran, cx, cy, r) {
    const angleDeg = 90 + cran * 15;
    const angleRad = angleDeg * Math.PI / 180;
    const x = Math.round(cx + r * Math.cos(angleRad));
    const y = Math.round(cy - r * Math.sin(angleRad));
    return { x, y };
  });
  Handlebars.registerHelper("fighterX", function(cx, index, total) {
    const offset = total > 1 ? (index - (total - 1) / 2) * 34 : 0;
    return Math.round(cx - 15 + offset);
  });
  Handlebars.registerHelper("fighterY", function(cy, index, total) {
    return Math.round(cy - 50);
  });
}

// src/ui/templates.js
async function preloadPartials() {
  return foundry.applications.handlebars.loadTemplates(TEMPLATE_PARTIALS);
}

// src/ui/initiative.js
var PC_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-prompt.html";
var NPC_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-prompt-npc.html";
var RESULT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-initiative-result.html";
function buildPCOptions(sys) {
  const sk = sys.skills ?? {};
  const rs = sys.resources ?? {};
  const mg = sys.magics ?? {};
  return [
    { key: "art", label: game.i18n.localize("CDE.Art"), value: sk.art?.value ?? 0 },
    { key: "investigation", label: game.i18n.localize("CDE.Investigation"), value: sk.investigation?.value ?? 0 },
    { key: "erudition", label: game.i18n.localize("CDE.Erudition"), value: sk.erudition?.value ?? 0 },
    { key: "knavery", label: game.i18n.localize("CDE.Knavery"), value: sk.knavery?.value ?? 0 },
    { key: "wordliness", label: game.i18n.localize("CDE.Wordliness"), value: sk.wordliness?.value ?? 0 },
    { key: "prowess", label: game.i18n.localize("CDE.Prowess"), value: sk.prowess?.value ?? 0 },
    { key: "sciences", label: game.i18n.localize("CDE.Sciences"), value: sk.sciences?.value ?? 0 },
    { key: "technologies", label: game.i18n.localize("CDE.Technologies"), value: sk.technologies?.value ?? 0 },
    { key: "kungfu", label: game.i18n.localize("CDE.KungFu"), value: sk.kungfu?.value ?? 0 },
    { key: "rangedcombat", label: game.i18n.localize("CDE.RangedCombat"), value: sk.rangedcombat?.value ?? 0 },
    { key: "supply", label: game.i18n.localize("CDE.Supply"), value: rs.supply?.value ?? 0 },
    { key: "inquiry", label: game.i18n.localize("CDE.Inquiry"), value: rs.inquiry?.value ?? 0 },
    { key: "influence", label: game.i18n.localize("CDE.Influence"), value: rs.influence?.value ?? 0 },
    { key: "internalcinnabar", label: game.i18n.localize("CDE.InternalCinnabar"), value: mg.internalcinnabar?.value ?? 0 },
    { key: "alchemy", label: game.i18n.localize("CDE.Alchemy"), value: mg.alchemy?.value ?? 0 },
    { key: "masteryoftheway", label: game.i18n.localize("CDE.MasteryOfTheWay"), value: mg.masteryoftheway?.value ?? 0 },
    { key: "exorcism", label: game.i18n.localize("CDE.Exorcism"), value: mg.exorcism?.value ?? 0 },
    { key: "geomancy", label: game.i18n.localize("CDE.Geomancy"), value: mg.geomancy?.value ?? 0 }
  ];
}
function buildNPCOptions(sys) {
  const ap = sys.aptitudes ?? {};
  return [
    { key: "physical", label: game.i18n.localize("CDE.Physical"), value: ap.physical?.value ?? 0 },
    { key: "martial", label: game.i18n.localize("CDE.Martial"), value: ap.martial?.value ?? 0 },
    { key: "mental", label: game.i18n.localize("CDE.Mental"), value: ap.mental?.value ?? 0 },
    { key: "social", label: game.i18n.localize("CDE.Social"), value: ap.social?.value ?? 0 },
    { key: "spiritual", label: game.i18n.localize("CDE.Spiritual"), value: ap.spiritual?.value ?? 0 }
  ];
}
function readInitFields(dialog) {
  const root = dialog.element ?? dialog;
  const selectedKey = root.querySelector("select[name='firstaction']")?.value ?? "";
  const modifier = parseInt(root.querySelector("input[name='modifier']")?.value ?? "0", 10) || 0;
  return { selectedKey, modifier };
}
async function sendInitChatMessage({ actor, baseName, baseValue, actionName, actionValue, modifier, initiative, antiInitiative }) {
  const html = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE, {
    actorName: actor.name,
    actorImg: actor.img,
    baseName,
    baseValue,
    actionName,
    actionValue,
    modifier,
    hasModifier: modifier !== 0,
    initiative,
    antiInitiative
  });
  await ChatMessage.create({
    user: game.user.id,
    speaker: ChatMessage.getSpeaker({ actor }),
    content: html
  });
}
async function rollInitiativePC(actor) {
  const sys = actor.system;
  const prowess = sys.skills?.prowess?.value ?? 0;
  const options = buildPCOptions(sys);
  const baseName = game.i18n.localize("CDE.Prowess");
  const content = await foundry.applications.handlebars.renderTemplate(PC_PROMPT_TEMPLATE, {
    prowessValue: prowess,
    options,
    modifier: 0
  });
  const result = await foundry.applications.api.DialogV2.prompt({
    window: { title: game.i18n.localize("CDE.InitiativeRoll") },
    content,
    rejectClose: false,
    ok: {
      label: game.i18n.localize("CDE.Validate"),
      callback: (_ev, _btn, dialog) => readInitFields(dialog)
    }
  });
  if (!result) return;
  const { selectedKey, modifier } = result;
  const selected = options.find((o) => o.key === selectedKey) ?? options[0];
  const rawValue = prowess + selected.value + modifier;
  const initiative = Math.max(1, Math.min(24, rawValue));
  const antiInit = 25 - initiative;
  await actor.update({ "system.initiative": initiative });
  await sendInitChatMessage({
    actor,
    baseName,
    baseValue: prowess,
    actionName: selected.label,
    actionValue: selected.value,
    modifier,
    initiative,
    antiInitiative: antiInit
  });
}
async function rollInitiativeNPC(actor) {
  const sys = actor.system;
  const physical = sys.aptitudes?.physical?.value ?? 0;
  const options = buildNPCOptions(sys);
  const baseName = game.i18n.localize("CDE.Physical");
  const content = await foundry.applications.handlebars.renderTemplate(NPC_PROMPT_TEMPLATE, {
    physicalValue: physical,
    options,
    modifier: 0
  });
  const result = await foundry.applications.api.DialogV2.prompt({
    window: { title: game.i18n.localize("CDE.InitiativeRoll") },
    content,
    rejectClose: false,
    ok: {
      label: game.i18n.localize("CDE.Validate"),
      callback: (_ev, _btn, dialog) => readInitFields(dialog)
    }
  });
  if (!result) return;
  const { selectedKey, modifier } = result;
  const selected = options.find((o) => o.key === selectedKey) ?? options[0];
  const rawValue = physical + selected.value + modifier;
  const initiative = Math.max(1, Math.min(24, rawValue));
  const antiInit = 25 - initiative;
  await actor.update({ "system.initiative": initiative });
  await sendInitChatMessage({
    actor,
    baseName,
    baseValue: physical,
    actionName: selected.label,
    actionValue: selected.value,
    modifier,
    initiative,
    antiInitiative: antiInit
  });
}

// src/ui/apps/singletons.js
var SOCKET_CHANNEL = `system.${SYSTEM_ID}`;
function getLoksyuData() {
  return game.settings.get(SYSTEM_ID, "loksyuData") ?? {
    wood: { yin: 0, yang: 0 },
    fire: { yin: 0, yang: 0 },
    earth: { yin: 0, yang: 0 },
    metal: { yin: 0, yang: 0 },
    water: { yin: 0, yang: 0 }
  };
}
async function writeLoksyuData(data) {
  await game.settings.set(SYSTEM_ID, "loksyuData", data);
  Hooks.callAll("cde:loksyuUpdated", data);
}
async function writeTinjiValue(value) {
  value = Math.max(0, value);
  await game.settings.set(SYSTEM_ID, "tinjiData", value);
  Hooks.callAll("cde:tinjiUpdated", value);
}
async function setLoksyuData(data) {
  if (game.user.isGM) return writeLoksyuData(data);
  game.socket.emit(SOCKET_CHANNEL, { action: "setLoksyuData", data });
}
function getTinjiValue() {
  return game.settings.get(SYSTEM_ID, "tinjiData") ?? 0;
}
async function setTinjiValue(value) {
  if (game.user.isGM) return writeTinjiValue(value);
  game.socket.emit(SOCKET_CHANNEL, { action: "setTinjiValue", value });
}
function requestLoksyuDraw(aspect, order) {
  game.socket.emit(SOCKET_CHANNEL, { action: "loksyuDraw", aspect, order });
}
function requestTinjiSpend() {
  game.socket.emit(SOCKET_CHANNEL, { action: "tinjiSpend" });
}
function registerSingletonSocket() {
  game.socket.on(SOCKET_CHANNEL, async (payload) => {
    if (!game.user.isGM) return;
    switch (payload.action) {
      case "setLoksyuData":
        await writeLoksyuData(payload.data);
        break;
      case "setTinjiValue":
        await writeTinjiValue(payload.value);
        break;
      case "updateLoksyuFromRoll":
        await updateLoksyuFromRoll(payload.activeAspect, payload.faces);
        break;
      case "updateTinjiFromRoll":
        await updateTinjiFromRoll(payload.delta);
        break;
      case "loksyuDraw": {
        const data = getLoksyuData();
        const entry = data[payload.aspect] ?? { yin: 0, yang: 0 };
        const order = payload.order ?? "yang-first";
        if (order === "yin-first") {
          if (entry.yin > 0) entry.yin--;
          else entry.yang--;
        } else if (order === "balanced") {
          if (entry.yin > entry.yang) entry.yin--;
          else if (entry.yang > entry.yin) entry.yang--;
          else if (entry.yang > 0) entry.yang--;
          else entry.yin--;
        } else {
          if (entry.yang > 0) entry.yang--;
          else entry.yin--;
        }
        data[payload.aspect] = entry;
        await writeLoksyuData(data);
        break;
      }
      case "tinjiSpend": {
        const cur = getTinjiValue();
        if (cur > 0) await writeTinjiValue(cur - 1);
        break;
      }
    }
  });
}
async function updateLoksyuFromRoll(activeAspect, faces) {
  const cycle = WU_XING_CYCLE[activeAspect];
  if (!cycle) return;
  const lokAspect = cycle[3];
  const [yinFace, yangFace] = ASPECT_FACES[lokAspect] ?? [];
  if (yinFace === void 0) return;
  const yinCount = faces[yinFace] ?? 0;
  const yangCount = faces[yangFace] ?? 0;
  if (yinCount === 0 && yangCount === 0) return;
  if (game.user.isGM) {
    const data = getLoksyuData();
    const current = data[lokAspect] ?? { yin: 0, yang: 0 };
    data[lokAspect] = { yin: (current.yin ?? 0) + yinCount, yang: (current.yang ?? 0) + yangCount };
    await writeLoksyuData(data);
  } else {
    game.socket.emit(SOCKET_CHANNEL, { action: "updateLoksyuFromRoll", activeAspect, faces });
  }
}
async function updateTinjiFromRoll(count) {
  if (!count || count <= 0) return;
  if (game.user.isGM) {
    const current = getTinjiValue();
    await writeTinjiValue(current + count);
  } else {
    game.socket.emit(SOCKET_CHANNEL, { action: "updateTinjiFromRoll", delta: count });
  }
}

// src/ui/rolling.js
var RESULT_TEMPLATE2 = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html";
var SKILL_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-skill-dice-prompt.html";
var SKILL_SPECIAL_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-skill-special-dice-prompt.html";
var MAGIC_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-magic-dice-prompt.html";
var WEAPON_PROMPT_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-weapon-dice-prompt.html";
var LABELELEMENT_TO_ASPECT = {
  "CDE.Metal": "metal",
  "CDE.Water": "water",
  "CDE.Earth": "earth",
  "CDE.Fire": "fire",
  "CDE.Wood": "wood"
};
var RANGE_ORDER = ["contact", "courte", "mediane", "longue", "extreme"];
var RANGE_LABELS = {
  contact: "CDE.RangeContact",
  courte: "CDE.RangeCourte",
  mediane: "CDE.RangeMediane",
  longue: "CDE.RangeLongue",
  extreme: "CDE.RangeExtreme"
};
var WEAPON_TYPE_SKILL = {
  melee: "kungfu",
  thrown: "rangedcombat",
  ranged: "rangedcombat",
  firearm: "rangedcombat"
};
var WEAPON_ASPECT_INDEX = { metal: 0, eau: 1, water: 1, terre: 2, earth: 2, feu: 3, fire: 3, bois: 4, wood: 4 };
function countFaces(rollResults) {
  const counts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 0: 0 };
  for (const die of rollResults) {
    const face = die.result === 10 ? 0 : die.result;
    counts[face]++;
  }
  return counts;
}
function computeWuXingResults(faces, aspectName, bonusAuspicious = 0) {
  const cycle = WU_XING_CYCLE[aspectName];
  if (!cycle) return null;
  const [succAspect, ausAspect, noxAspect, lokAspect, tinAspect] = cycle;
  const [succYin, succYang] = ASPECT_FACES[succAspect];
  const [ausYin, ausYang] = ASPECT_FACES[ausAspect];
  const [noxYin, noxYang] = ASPECT_FACES[noxAspect];
  const [lokYin, lokYang] = ASPECT_FACES[lokAspect];
  const [tinYin, tinYang] = ASPECT_FACES[tinAspect];
  const yin = game.i18n.localize("CDE.Yin");
  const yang = game.i18n.localize("CDE.Yang");
  return {
    successesdice: faces[succYin] + faces[succYang],
    auspiciousdice: faces[ausYin] + faces[ausYang] + bonusAuspicious,
    noxiousdice: faces[noxYin] + faces[noxYang],
    loksyudice: faces[lokYin] + faces[lokYang],
    loksyurepartition: `[${yin}(${faces[lokYin]}) ${yang}(${faces[lokYang]})]`,
    tinjidice: faces[tinYin] + faces[tinYang]
  };
}
function readField(dlg, name) {
  const el = dlg.querySelector(`[name="${name}"]`);
  if (!el) return null;
  return el.type === "checkbox" ? el.checked : el.value;
}
async function showRollPrompt({ title, template, data, fields }) {
  const content = await foundry.applications.handlebars.renderTemplate(template, data);
  return foundry.applications.api.DialogV2.prompt({
    window: { title },
    content,
    rejectClose: false,
    ok: {
      label: game.i18n.localize("CDE.Validate"),
      callback: (event, button, dialog) => {
        const root = dialog.element ?? dialog;
        const result = {};
        for (const field of fields) {
          result[field] = readField(root, field);
        }
        return result;
      }
    }
  });
}
async function showSkillPrompt(params) {
  return showRollPrompt({
    title: params.title,
    template: params.isSpecial ? SKILL_SPECIAL_PROMPT_TEMPLATE : SKILL_PROMPT_TEMPLATE,
    data: {
      numberofdice: params.numberofdice,
      aspect: Number(params.aspect ?? 0),
      bonusmalus: params.bonusmalus ?? 0,
      woundmalus: params.woundmalus ?? 0,
      bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
      typeofthrow: Number(params.typeofthrow ?? 0)
    },
    fields: ["aspect", "bonusmalus", "woundmalus", "bonusauspiciousdice", "typeofthrow"]
  });
}
async function showMagicPrompt(params) {
  return showRollPrompt({
    title: params.title,
    template: MAGIC_PROMPT_TEMPLATE,
    data: {
      numberofdice: params.numberofdice ?? 0,
      aspectskill: Number(params.aspectskill ?? 0),
      bonusmalusskill: params.bonusmalusskill ?? 0,
      bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
      rolldifficulty: params.rolldifficulty ?? 1,
      freepowerlevels: params.freepowerlevels ?? 0,
      typeofthrow: Number(params.typeofthrow ?? 0)
    },
    fields: [
      "aspectskill",
      "bonusmalusskill",
      "bonusauspiciousdice",
      "rolldifficulty",
      "freepowerlevels",
      "typeofthrow"
    ]
  });
}
async function showWeaponPrompt(params) {
  const maxRange = params.effectiverange ?? "contact";
  const maxIdx = RANGE_ORDER.indexOf(maxRange);
  const validRanges = RANGE_ORDER.slice(0, maxIdx + 1).map((r) => ({
    value: r,
    label: RANGE_LABELS[r],
    selected: r === maxRange
  }));
  return showRollPrompt({
    title: params.title,
    template: WEAPON_PROMPT_TEMPLATE,
    data: {
      numberofdice: params.numberofdice ?? 0,
      weaponName: params.weaponName ?? "",
      weaponTypeLabel: params.weaponTypeLabel ?? "CDE.Weapon",
      weaponAspectIcon: params.weaponAspectIcon ?? "",
      weaponAspectLabel: params.weaponAspectLabel ?? "",
      damageBase: params.damageBase ?? 0,
      weaponskill: params.weaponskill ?? "kungfu",
      aspect: Number(params.aspect ?? 0),
      effectiverange: params.effectiverange ?? "contact",
      validRanges,
      bonusmalus: params.bonusmalus ?? 0,
      woundmalus: params.woundmalus ?? 0,
      bonusauspiciousdice: params.bonusauspiciousdice ?? 0,
      typeofthrow: Number(params.typeofthrow ?? 0)
    },
    fields: [
      "weaponskill",
      "aspect",
      "effectiverange",
      "bonusmalus",
      "woundmalus",
      "bonusauspiciousdice",
      "typeofthrow"
    ]
  });
}
async function sendResultMessage(actor, resultData, roll, rollMode) {
  const html = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE2, resultData);
  return ChatMessage.create({
    user: game.user.id,
    speaker: ChatMessage.getSpeaker({ actor }),
    content: html,
    rolls: [roll],
    rollMode,
    flags: {
      "fvtt-chroniques-de-l-etrange": { rollResult: { ...resultData } }
    }
  });
}
var ROLL_MODES = ["roll", "gmroll", "blindroll", "selfroll"];
async function rollForActor(actor, rollKey) {
  const parts = rollKey.split("-");
  const skillLibel = parts[0];
  const typeLibel = parts[1];
  const specialLibel = parts[2] ?? null;
  const sys = actor.system;
  const typeOfThrow = Number(sys.prefs?.typeofthrow?.choice ?? 0);
  let numberofdice = 0;
  let title = "";
  let isSpecial = false;
  let isMagic = false;
  let isMagicSpecial = false;
  let kfDefaultAspect = -1;
  const MAGIC_I18N_KEYS = {
    internalcinnabar: "CDE.InternalCinnabar",
    alchemy: "CDE.Alchemy",
    masteryoftheway: "CDE.MasteryOfTheWay",
    exorcism: "CDE.Exorcism",
    geomancy: "CDE.Geomancy"
  };
  switch (typeLibel) {
    case "aspect":
      numberofdice = sys.aspect[skillLibel]?.value ?? 0;
      title = game.i18n.localize(sys.aspect[skillLibel]?.label ?? "CDE.Roll");
      break;
    case "aptitude":
      numberofdice = sys.aptitudes?.[skillLibel]?.value ?? 0;
      title = game.i18n.localize(`CDE.${skillLibel.charAt(0).toUpperCase() + skillLibel.slice(1)}`);
      break;
    case "skill":
      numberofdice = sys.skills?.[skillLibel]?.value ?? 0;
      title = game.i18n.localize(sys.skills?.[skillLibel]?.label ?? "CDE.Roll");
      break;
    case "special":
      numberofdice = sys.skills?.[skillLibel]?.value ?? 0;
      title = game.i18n.localize(sys.skills?.[skillLibel]?.label ?? "CDE.Roll");
      title += ` [${game.i18n.localize("CDE.Speciality")}]`;
      isSpecial = true;
      if (!sys.skills?.[skillLibel]?.specialities) {
        ui.notifications.warn(game.i18n.localize("CDE.Error2"));
        return;
      }
      break;
    case "resource":
      numberofdice = sys.resources?.[skillLibel]?.value ?? 0;
      title = game.i18n.localize(sys.resources?.[skillLibel]?.label ?? "CDE.Roll");
      break;
    case "field":
      numberofdice = sys.resources?.[skillLibel]?.value ?? 0;
      title = game.i18n.localize(sys.resources?.[skillLibel]?.label ?? "CDE.Roll");
      title += ` [${game.i18n.localize("CDE.Field")}]`;
      isSpecial = true;
      if (!sys.resources?.[skillLibel]?.specialities) {
        ui.notifications.warn(game.i18n.localize("CDE.Error4"));
        return;
      }
      break;
    case "magic":
      numberofdice = sys.magics?.[skillLibel]?.value ?? 0;
      isMagic = true;
      title = game.i18n.localize(MAGIC_I18N_KEYS[skillLibel] ?? "CDE.Magics");
      break;
    case "magicspecial":
      numberofdice = sys.magics?.[skillLibel]?.value ?? 0;
      isMagicSpecial = true;
      isMagic = true;
      if (!sys.magics?.[skillLibel]?.speciality?.[specialLibel]?.check) {
        ui.notifications.warn(game.i18n.localize("CDE.Error6"));
        return;
      }
      title = `${game.i18n.localize(MAGIC_I18N_KEYS[skillLibel] ?? "CDE.Magics")} [${game.i18n.localize(MAGICS?.[skillLibel]?.speciality?.[specialLibel]?.label ?? "")}]`;
      break;
    case "itemkungfu": {
      const kfItem = actor.items.get(skillLibel);
      if (!kfItem) {
        ui.notifications.warn(game.i18n.localize("CDE.Error0"));
        return;
      }
      const kfSkill = kfItem.system.skill ?? "kungfu";
      numberofdice = sys.skills?.[kfSkill]?.value ?? 0;
      title = `${kfItem.name} [${game.i18n.localize(sys.skills?.[kfSkill]?.label ?? "CDE.KungFu")}]`;
      const kfAspect = kfItem.system.aspect?.toLowerCase() ?? "metal";
      const ASPECT_NORMALIZE = { eau: "water", terre: "earth", feu: "fire", bois: "wood" };
      kfDefaultAspect = ASPECT_NAMES.indexOf(ASPECT_NORMALIZE[kfAspect] ?? kfAspect);
      if (kfDefaultAspect < 0) kfDefaultAspect = 0;
      break;
    }
    case "itemweapon": {
      const wpItem = actor.items.get(skillLibel);
      if (!wpItem) {
        ui.notifications.warn(game.i18n.localize("CDE.Error0"));
        return;
      }
      const wpType = wpItem.system.weaponType ?? "melee";
      const wpSkill = WEAPON_TYPE_SKILL[wpType] ?? "kungfu";
      numberofdice = sys.skills?.[wpSkill]?.value ?? 0;
      const wpAspectRaw = wpItem.system.damageAspect ?? "metal";
      const wpAspectIdx = WEAPON_ASPECT_INDEX[wpAspectRaw] ?? 0;
      const wpRange = wpItem.system.range ?? "contact";
      const WEAPON_TYPE_LABELS = {
        melee: "CDE.WeaponMelee",
        thrown: "CDE.WeaponThrown",
        ranged: "CDE.WeaponRanged",
        firearm: "CDE.WeaponFirearm"
      };
      const wParams = await showWeaponPrompt({
        title: `${wpItem.name} [${game.i18n.localize(sys.skills?.[wpSkill]?.label ?? "CDE.WeaponRoll")}]`,
        numberofdice,
        weaponName: wpItem.name,
        weaponTypeLabel: WEAPON_TYPE_LABELS[wpType] ?? "CDE.Weapon",
        weaponAspectIcon: ASPECT_ICONS[ASPECT_NAMES[wpAspectIdx]] ?? "",
        weaponAspectLabel: game.i18n.localize(ASPECT_LABELS[ASPECT_NAMES[wpAspectIdx]] ?? ""),
        damageBase: wpItem.system.damageBase ?? 0,
        weaponskill: wpSkill,
        aspect: wpAspectIdx,
        effectiverange: wpRange,
        bonusmalus: 0,
        woundmalus: 0,
        bonusauspiciousdice: 0,
        typeofthrow: typeOfThrow
      });
      if (!wParams) return;
      const wpChosenSkill = wParams.weaponskill ?? wpSkill;
      const wpSkillDice = sys.skills?.[wpChosenSkill]?.value ?? 0;
      const wpAspFinal = Number(wParams.aspect ?? wpAspectIdx);
      const wpAspectDice = sys.aspect[ASPECT_NAMES[wpAspFinal]]?.value ?? 0;
      const wpBonusMalus = Number(wParams.bonusmalus ?? 0);
      const wpWoundMalus = Number(wParams.woundmalus ?? 0);
      const wpBonusAusp = Number(wParams.bonusauspiciousdice ?? 0);
      const wpThrowMode = Number(wParams.typeofthrow ?? 0);
      const wpDamageBase = wpItem.system.damageBase ?? 0;
      const wpSpecialtyBonus = wpItem.system.hasSpeciality ? 1 : 0;
      const wpTotalDice = wpSkillDice + wpAspectDice + wpBonusMalus - wpWoundMalus + wpSpecialtyBonus;
      if (wpTotalDice <= 0) {
        ui.notifications.warn(game.i18n.localize("CDE.Error0"));
        return;
      }
      const wpRoll = new Roll(`${wpTotalDice}d10`);
      await wpRoll.evaluate();
      const wpAspectName = ASPECT_NAMES[wpAspFinal] ?? "metal";
      const wpFaces = countFaces(wpRoll.dice[0]?.results ?? []);
      const wpResults = computeWuXingResults(wpFaces, wpAspectName, wpBonusAusp);
      if (!wpResults) return;
      const wpModParts = [];
      if (wpBonusMalus !== 0) wpModParts.push(`${wpBonusMalus > 0 ? "+" : ""}${wpBonusMalus} ${game.i18n.localize("CDE.BonusMalus")}`);
      if (wpWoundMalus !== 0) wpModParts.push(`-${wpWoundMalus} ${game.i18n.localize("CDE.WoundMalus")}`);
      if (wpBonusAusp !== 0) wpModParts.push(`+${wpBonusAusp} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`);
      const wpDamageAspectRaw = wpItem.system.damageAspect ?? "metal";
      const wpDamageAspectIdx = WEAPON_ASPECT_INDEX[wpDamageAspectRaw] ?? 0;
      const wpDamageAspectName = ASPECT_NAMES[wpDamageAspectIdx];
      const wpDamageAspectValue = sys.aspect?.[wpDamageAspectName]?.value ?? 0;
      const wpDamageAspectLabel = game.i18n.localize(ASPECT_LABELS[wpDamageAspectName] ?? "");
      const wpMsg = await sendResultMessage(actor, {
        rollLabel: `${wpItem.name}`,
        aspectName: wpAspectName,
        aspectLabel: game.i18n.localize(ASPECT_LABELS[wpAspectName] ?? ""),
        aspectIcon: ASPECT_ICONS[wpAspectName] ?? "",
        totalDice: wpTotalDice,
        modifiersText: wpModParts.length ? wpModParts.join(" \xB7 ") : "",
        spellPower: null,
        rollDifficulty: null,
        actorName: actor.name ?? "",
        actorImg: actor.img ?? "",
        // weapon-specific
        weaponName: wpItem.name,
        damageBase: wpDamageBase,
        damageAspectValue: wpDamageAspectValue,
        damageAspectLabel: wpDamageAspectLabel,
        totalDamage: wpDamageBase + wpDamageAspectValue,
        ...wpResults,
        aspect: wpAspectName,
        d1: wpFaces[1],
        d2: wpFaces[2],
        d3: wpFaces[3],
        d4: wpFaces[4],
        d5: wpFaces[5],
        d6: wpFaces[6],
        d7: wpFaces[7],
        d8: wpFaces[8],
        d9: wpFaces[9],
        d0: wpFaces[0]
      }, wpRoll, ROLL_MODES[wpThrowMode] ?? "roll");
      if (game.modules.get("dice-so-nice")?.active && wpMsg?.id) {
        try {
          await game.dice3d.waitFor3DAnimationByMessageID(wpMsg.id);
        } catch (_e) {
        }
      }
      if ((wpResults.loksyudice ?? 0) > 0) await updateLoksyuFromRoll(wpAspectName, wpFaces);
      if ((wpResults.tinjidice ?? 0) > 0) await updateTinjiFromRoll(wpResults.tinjidice);
      return;
    }
    default:
      ui.notifications.warn(`Unknown roll type: ${typeLibel}`);
      return;
  }
  if (numberofdice <= 0 && typeLibel !== "aspect" && typeLibel !== "itemkungfu" && !isMagic) {
    ui.notifications.warn(game.i18n.localize("CDE.Error0"));
    return;
  }
  const MAGIC_ASPECTS = {
    internalcinnabar: 0,
    // metal
    alchemy: 1,
    // water
    masteryoftheway: 2,
    // earth
    exorcism: 3,
    // fire
    geomancy: 4
    // wood
  };
  let defaultAspect = typeLibel === "aspect" ? ["metal", "water", "earth", "fire", "wood"].indexOf(skillLibel) : 0;
  if (isMagic && MAGIC_ASPECTS[skillLibel] !== void 0) {
    defaultAspect = MAGIC_ASPECTS[skillLibel];
  }
  if (kfDefaultAspect >= 0) {
    defaultAspect = kfDefaultAspect;
  }
  let params;
  if (isMagic) {
    params = await showMagicPrompt({
      title,
      numberofdice,
      aspectskill: defaultAspect,
      bonusmalusskill: 0,
      bonusauspiciousdice: 0,
      rolldifficulty: 1,
      freepowerlevels: 0,
      typeofthrow: typeOfThrow
    });
  } else {
    params = await showSkillPrompt({
      title,
      numberofdice,
      aspect: defaultAspect,
      bonusmalus: 0,
      woundmalus: 0,
      bonusauspiciousdice: 0,
      typeofthrow: typeOfThrow,
      isSpecial
    });
  }
  if (!params) return;
  let aspectIndex, bonusMalus, bonusAuspicious, throwMode;
  let rollDifficulty = 1;
  if (isMagic) {
    const skillAspectIndex = Number(params.aspectskill ?? 0);
    aspectIndex = skillAspectIndex;
    bonusMalus = Number(params.bonusmalusskill ?? 0);
    bonusAuspicious = Number(params.bonusauspiciousdice ?? 0);
    rollDifficulty = Math.max(1, Number(params.rolldifficulty ?? 1));
    throwMode = Number(params.typeofthrow ?? 0);
    const aspectDice = sys.aspect?.[ASPECT_NAMES[aspectIndex]]?.value ?? 0;
    numberofdice = numberofdice + aspectDice + bonusMalus + 1;
  } else {
    aspectIndex = Number(params.aspect ?? 0);
    bonusMalus = Number(params.bonusmalus ?? 0);
    const woundMalus = Number(params.woundmalus ?? 0);
    bonusAuspicious = Number(params.bonusauspiciousdice ?? 0);
    throwMode = Number(params.typeofthrow ?? 0);
    const aspectDice = typeLibel !== "aspect" ? sys.aspect?.[ASPECT_NAMES[aspectIndex]]?.value ?? 0 : 0;
    numberofdice = numberofdice + aspectDice + bonusMalus - woundMalus;
    if (isSpecial) numberofdice += 1;
  }
  if (numberofdice <= 0) {
    ui.notifications.warn(game.i18n.localize("CDE.Error0"));
    return;
  }
  const roll = new Roll(`${numberofdice}d10`);
  await roll.evaluate();
  const rollModeKey = ROLL_MODES[throwMode] ?? "roll";
  let spellPower = null;
  let spellPowerAspectName = null;
  let spellPowerAspectValue = null;
  if (isMagic) {
    if (isMagicSpecial && specialLibel) {
      const specialCfg = MAGICS?.[skillLibel]?.speciality?.[specialLibel];
      const elemName = LABELELEMENT_TO_ASPECT[specialCfg?.labelelementkey];
      if (elemName) spellPowerAspectName = elemName;
    }
    if (!spellPowerAspectName) spellPowerAspectName = ASPECT_NAMES[aspectIndex];
    spellPowerAspectValue = sys.aspect?.[spellPowerAspectName]?.value ?? 0;
    const freePowerLevels = Number(params.freepowerlevels ?? 0);
    spellPower = rollDifficulty * (spellPowerAspectValue + freePowerLevels);
  }
  const wuXingAspectName = ASPECT_NAMES[aspectIndex];
  const allResults = roll.dice[0]?.results ?? [];
  const faces = countFaces(allResults);
  const results = computeWuXingResults(faces, wuXingAspectName, bonusAuspicious);
  if (!results) return;
  const modParts = [];
  if (isMagic) {
    const bm = Number(params.bonusmalusskill ?? 0);
    const ba = Number(params.bonusauspiciousdice ?? 0);
    const fp = Number(params.freepowerlevels ?? 0);
    if (bm !== 0) modParts.push(`${bm > 0 ? "+" : ""}${bm} ${game.i18n.localize("CDE.BonusMalus")}`);
    if (ba !== 0) modParts.push(`+${ba} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`);
    if (fp !== 0) modParts.push(`+${fp} ${game.i18n.localize("CDE.FreePowerLevels")}`);
    if (rollDifficulty !== 1) modParts.push(`\xD7${rollDifficulty} ${game.i18n.localize("CDE.RollDifficulty")}`);
  } else {
    const bm = Number(params.bonusmalus ?? 0);
    const wm = Number(params.woundmalus ?? 0);
    const ba = Number(params.bonusauspiciousdice ?? 0);
    if (bm !== 0) modParts.push(`${bm > 0 ? "+" : ""}${bm} ${game.i18n.localize("CDE.BonusMalus")}`);
    if (wm !== 0) modParts.push(`-${wm} ${game.i18n.localize("CDE.WoundMalus")}`);
    if (ba !== 0) modParts.push(`+${ba} ${game.i18n.localize("CDE.BonusAuspiciousDice")}`);
  }
  const msg = await sendResultMessage(actor, {
    // Roll identity
    rollLabel: title,
    aspectName: wuXingAspectName,
    aspectLabel: game.i18n.localize(ASPECT_LABELS[wuXingAspectName] ?? ""),
    aspectIcon: ASPECT_ICONS[wuXingAspectName] ?? "",
    totalDice: numberofdice,
    modifiersText: modParts.length ? modParts.join(" \xB7 ") : "",
    // Spell power (magic only)
    spellPower,
    spellPowerAspectLabel: spellPowerAspectName ? game.i18n.localize(ASPECT_LABELS[spellPowerAspectName] ?? "") : "",
    spellPowerAspectValue,
    spellPowerFreeLevels: isMagic ? Number(params.freepowerlevels ?? 0) : 0,
    rollDifficulty: isMagic ? rollDifficulty : null,
    // Actor info
    actorName: actor.name ?? "",
    actorImg: actor.img ?? "",
    // Wu Xing results
    aspect: wuXingAspectName,
    ...results,
    // Die face counts
    d1: faces[1],
    d2: faces[2],
    d3: faces[3],
    d4: faces[4],
    d5: faces[5],
    d6: faces[6],
    d7: faces[7],
    d8: faces[8],
    d9: faces[9],
    d0: faces[0]
  }, roll, rollModeKey);
  if (game.modules.get("dice-so-nice")?.active && msg?.id) {
    try {
      await game.dice3d.waitFor3DAnimationByMessageID(msg.id);
    } catch (_e) {
    }
  }
  if ((results.loksyudice ?? 0) > 0) await updateLoksyuFromRoll(wuXingAspectName, faces);
  if ((results.tinjidice ?? 0) > 0) await updateTinjiFromRoll(results.tinjidice);
}

// src/ui/sheets/actors/base.js
var { HandlebarsApplicationMixin } = foundry.applications.api;
var CDEBaseActorSheet = class _CDEBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
  static DEFAULT_OPTIONS = {
    classes: ["fvtt-chroniques-de-l-etrange", "actor"],
    position: { width: 920, height: 800 },
    window: { resizable: true },
    form: { submitOnChange: true },
    dragDrop: [{ dragSelector: ".item, [data-drag='true']", dropSelector: null }],
    actions: {
      create: _CDEBaseActorSheet.#onItemCreate,
      edit: _CDEBaseActorSheet.#onItemEdit,
      delete: _CDEBaseActorSheet.#onItemDelete,
      editImage: _CDEBaseActorSheet.#onEditImage
    }
  };
  tabGroups = { primary: "description" };
  get title() {
    return this.document.name;
  }
  async _prepareContext() {
    const descriptionHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true });
    const cssClass = this.options.classes?.join(" ") ?? "";
    return {
      actor: this.document,
      system: this.document.system,
      systemData: this.document.system,
      systemFields: this.document.system.schema.fields,
      items: this.document.items.contents,
      descriptionHTML,
      editable: this.isEditable,
      cssClass
    };
  }
  // Restore the active tab after every render (including re-renders from submitOnChange).
  // AppV2 does NOT preserve tab state natively — we must re-apply it from this.tabGroups,
  // which is dynamically updated by changeTab() when the user clicks a tab.
  _onRender(context, options) {
    super._onRender?.(context, options);
    for (const [group, tab] of Object.entries(this.tabGroups)) {
      this.changeTab(tab, group, { force: true });
    }
  }
  static async #onItemCreate(event, target) {
    const type = target.dataset.type ?? "item";
    const cls = getDocumentClass("Item");
    const labels = {
      item: "CDE.ItemNew",
      weapon: "CDE.WeaponNew",
      armor: "CDE.ArmorNew",
      sanhei: "CDE.SanheiNew",
      ingredient: "CDE.IngredientNew",
      kungfu: "CDE.KFNew",
      spell: "CDE.SpellNew",
      supernatural: "CDE.SupernaturalNew"
    };
    const name = game.i18n.localize(labels[type] ?? "CDE.ItemNew");
    const systemData = {};
    if (type === "spell" && target.dataset.discipline) {
      systemData.discipline = target.dataset.discipline;
    }
    return cls.create({ name, type, system: systemData }, { parent: this.document });
  }
  static #onItemEdit(event, target) {
    const itemId = target.dataset.itemId ?? target.closest("[data-item-id]")?.dataset.itemId;
    const item = this.document.items.get(itemId);
    if (item) item.sheet.render(true);
  }
  static #onItemDelete(event, target) {
    const itemId = target.dataset.itemId ?? target.closest("[data-item-id]")?.dataset.itemId;
    const item = this.document.items.get(itemId);
    if (item) item.delete();
  }
  static async #onEditImage(event, target) {
    const attr = target.dataset.edit;
    const current = foundry.utils.getProperty(this.document, attr);
    const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {};
    const fp = new FilePicker({
      current,
      type: "image",
      redirectToRoot: img ? [img] : [],
      callback: (path) => this.document.update({ [attr]: path }),
      top: this.position.top + 40,
      left: this.position.left + 10
    });
    return fp.browse();
  }
};

// src/ui/sheets/actors/character.js
var CDECharacterSheet = 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 };
    const spellsByDiscipline = {};
    for (const spell of context.spells) {
      const disc = spell.system?.discipline ?? "internalcinnabar";
      if (!spellsByDiscipline[disc]) spellsByDiscipline[disc] = [];
      spellsByDiscipline[disc].push(spell);
    }
    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;
    }
    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: (event2, button, dialog) => {
          const root = dialog.element ?? dialog;
          return { bonusdice: Number(root.querySelector("[name=bonusdice]")?.value ?? 0) };
        }
      }
    });
    if (params === void 0) return;
    const totalDice = aspectValue + Math.max(0, Number(params.bonusdice ?? 0));
    const roll = new Roll(`${totalDice}d10`);
    await roll.evaluate();
    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;
    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")} \u2014 ${aspectLabel}`;
    const diceStr = results.join(" \xB7 ");
    const typeOfThrow = Number(sys.prefs?.typeofthrow?.choice ?? 0);
    const ROLL_MODES2 = ["roll", "gmroll", "blindroll", "selfroll"];
    const rollMode = ROLL_MODES2[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>\u2192 +${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>\u2192 +${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"
      });
    });
  }
};

// src/ui/sheets/actors/npc.js
var CDENpcSheet = class extends CDEBaseActorSheet {
  static DEFAULT_OPTIONS = {
    classes: ["npc"]
  };
  static PARTS = {
    main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-npc-sheet.html" }
  };
  tabGroups = { primary: "description" };
  async _prepareContext() {
    const context = await super._prepareContext();
    context.supernaturals = context.items.filter((item) => item.type === "supernatural");
    context.spells = context.items.filter((item) => item.type === "spell");
    context.kungfus = context.items.filter((item) => item.type === "kungfu");
    context.weapons = context.items.filter((item) => item.type === "weapon");
    context.armors = context.items.filter((item) => item.type === "armor");
    context.equipments = context.items.filter((item) => item.type === "item");
    return context;
  }
  _onRender(context, options) {
    super._onRender(context, options);
    this.#bindInitiativeControls();
    this.#bindRollButtons();
  }
  #bindRollButtons() {
    const cells = this.element?.querySelectorAll(".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));
      });
    });
  }
  #bindInitiativeControls() {
    const buttons = this.element?.querySelectorAll(".click-initiative-npc");
    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 rollInitiativeNPC(this.document);
        }
      });
    });
  }
};

// src/ui/sheets/items/base.js
var { HandlebarsApplicationMixin: HandlebarsApplicationMixin2 } = foundry.applications.api;
var CDEBaseItemSheet = class _CDEBaseItemSheet extends HandlebarsApplicationMixin2(foundry.applications.sheets.ItemSheetV2) {
  static DEFAULT_OPTIONS = {
    classes: ["fvtt-chroniques-de-l-etrange", "item"],
    position: { width: 520, height: "auto" },
    window: { resizable: true },
    form: { submitOnChange: true },
    actions: {
      editImage: _CDEBaseItemSheet.#onEditImage
    }
  };
  tabGroups = { primary: "details" };
  get title() {
    return this.document.name;
  }
  async _prepareContext() {
    const cssClass = this.options.classes?.join(" ") ?? "";
    const enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true });
    const enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes ?? "", { async: true });
    return {
      item: this.document,
      system: this.document.system,
      systemData: this.document.system,
      systemFields: this.document.system.schema.fields,
      editable: this.isEditable,
      cssClass,
      enrichedDescription,
      enrichedNotes,
      descriptionHTML: enrichedDescription,
      notesHTML: enrichedNotes
    };
  }
  // Restore the active tab after every render (including re-renders from submitOnChange).
  _onRender(context, options) {
    super._onRender?.(context, options);
    for (const [group, tab] of Object.entries(this.tabGroups)) {
      this.changeTab(tab, group, { force: true });
    }
  }
  static async #onEditImage(event, target) {
    const attr = target.dataset.edit;
    const current = foundry.utils.getProperty(this.document, attr);
    const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {};
    const fp = new FilePicker({
      current,
      type: "image",
      redirectToRoot: img ? [img] : [],
      callback: (path) => this.document.update({ [attr]: path }),
      top: this.position.top + 40,
      left: this.position.left + 10
    });
    return fp.browse();
  }
};

// src/ui/sheets/items/item.js
var CDEItemSheet = class extends CDEBaseItemSheet {
  static DEFAULT_OPTIONS = {
    classes: ["equipment"],
    position: { width: 560, height: 460 }
  };
  static PARTS = {
    main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-item-sheet.html" }
  };
};

// src/ui/sheets/items/kungfu.js
var CDEKungfuSheet = class extends CDEBaseItemSheet {
  static DEFAULT_OPTIONS = {
    classes: ["kungfu"],
    position: { width: 720, height: 680 }
  };
  static PARTS = {
    main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-kungfu-sheet.html" }
  };
  async _prepareContext() {
    const context = await super._prepareContext();
    const techniques = this.document.system.techniques ?? {};
    const enrich = (value) => foundry.applications.ux.TextEditor.implementation.enrichHTML(value ?? "", { async: true });
    context.descriptionTechnique1HTML = await enrich(techniques.technique1?.technique);
    context.descriptionTechnique2HTML = await enrich(techniques.technique2?.technique);
    context.descriptionTechnique3HTML = await enrich(techniques.technique3?.technique);
    return context;
  }
};

// src/ui/sheets/items/spell.js
var CDESpellSheet = class extends CDEBaseItemSheet {
  static DEFAULT_OPTIONS = {
    classes: ["spell"],
    position: { width: 660, height: 680 }
  };
  static PARTS = {
    main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-spell-sheet.html" }
  };
  async _prepareContext() {
    const context = await super._prepareContext();
    const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true });
    context.spellDescriptionHTML = await enrich(this.document.system.description);
    context.componentsDescriptionHTML = await enrich(this.document.system.components);
    context.effectsDescriptionHTML = await enrich(this.document.system.effects);
    context.examplesDescriptionHTML = await enrich(this.document.system.examples);
    return context;
  }
};

// src/ui/sheets/items/supernatural.js
var CDESupernaturalSheet = class extends CDEBaseItemSheet {
  static DEFAULT_OPTIONS = {
    classes: ["supernatural"],
    position: { width: 560, height: 520 }
  };
  static PARTS = {
    main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-supernatural-sheet.html" }
  };
  async _prepareContext() {
    const context = await super._prepareContext();
    const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true });
    context.effectsHTML = await enrich(this.document.system.effects);
    return context;
  }
};

// src/ui/sheets/items/weapon.js
var CDEWeaponSheet = class extends CDEBaseItemSheet {
  static DEFAULT_OPTIONS = {
    classes: ["weapon"],
    position: { width: 580, height: 520 }
  };
  static PARTS = {
    main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-weapon-sheet.html" }
  };
};

// src/ui/sheets/items/armor.js
var CDEArmorSheet = class extends CDEBaseItemSheet {
  static DEFAULT_OPTIONS = {
    classes: ["armor"],
    position: { width: 520, height: 460 }
  };
  static PARTS = {
    main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-armor-sheet.html" }
  };
};

// src/ui/sheets/items/sanhei.js
var CDESanheiSheet = class extends CDEBaseItemSheet {
  static DEFAULT_OPTIONS = {
    classes: ["sanhei"],
    position: { width: 580, height: 620 }
  };
  static PARTS = {
    main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-sanhei-sheet.html" }
  };
  async _prepareContext() {
    const context = await super._prepareContext();
    const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true });
    const props = this.document.system.properties ?? {};
    context.prop1DescriptionHTML = await enrich(props.prop1?.description);
    context.prop2DescriptionHTML = await enrich(props.prop2?.description);
    context.prop3DescriptionHTML = await enrich(props.prop3?.description);
    context.propFields = this.document.system.schema.fields.properties?.fields;
    return context;
  }
};

// src/ui/sheets/items/ingredient.js
var CDEIngredientSheet = class extends CDEBaseItemSheet {
  static DEFAULT_OPTIONS = {
    classes: ["ingredient"],
    position: { width: 520, height: 460 }
  };
  static PARTS = {
    main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-ingredient-sheet.html" }
  };
};

// src/ui/apps/loksyu-app.js
var CDELoksyuApp = class _CDELoksyuApp extends foundry.applications.api.HandlebarsApplicationMixin(
  foundry.applications.api.ApplicationV2
) {
  static DEFAULT_OPTIONS = {
    id: "cde-loksyu-app",
    tag: "div",
    window: {
      title: "CDE.Loksyu",
      icon: "fas fa-yin-yang",
      resizable: false
    },
    classes: ["cde-app", "cde-loksyu-standalone"],
    position: { width: 520, height: "auto" },
    actions: {
      resetElement: _CDELoksyuApp.#onResetElement,
      resetAll: _CDELoksyuApp.#onResetAll,
      zoomVisual: _CDELoksyuApp.#onZoomVisual
    }
  };
  static PARTS = {
    main: {
      template: `systems/${SYSTEM_ID}/templates/apps/cde-loksyu-app.html`
    }
  };
  /** @type {Function|null} bound hook handler */
  _updateHook = null;
  /** @type {Function|null} updateSetting hook handler (for socket-propagated writes) */
  _settingHook = null;
  /** Singleton accessor — open or bring to front */
  static open() {
    const existing = Array.from(foundry.applications.instances.values()).find(
      (app2) => app2 instanceof _CDELoksyuApp
    );
    if (existing) {
      existing.bringToFront();
      return existing;
    }
    const app = new _CDELoksyuApp();
    app.render(true);
    return app;
  }
  async _prepareContext() {
    const sys = getLoksyuData();
    const ELEMENTS = [
      { key: "wood", nameKey: "CDE.Wood", qualKey: "CDE.WoodQualities", img: `systems/${SYSTEM_ID}/images/cde_bois.webp` },
      { key: "fire", nameKey: "CDE.Fire", qualKey: "CDE.FireQualities", img: `systems/${SYSTEM_ID}/images/cde_feu.webp` },
      { key: "earth", nameKey: "CDE.Earth", qualKey: "CDE.EarthQualities", img: `systems/${SYSTEM_ID}/images/cde_terre.webp` },
      { key: "metal", nameKey: "CDE.Metal", qualKey: "CDE.MetalQualities", img: `systems/${SYSTEM_ID}/images/cde_metal.webp` },
      { key: "water", nameKey: "CDE.Water", qualKey: "CDE.WaterQualities", img: `systems/${SYSTEM_ID}/images/cde_eau.webp` }
    ];
    return {
      canEdit: game.user.isGM,
      elements: ELEMENTS.map((el) => ({
        ...el,
        yang: sys[el.key]?.yang ?? 0,
        yin: sys[el.key]?.yin ?? 0
      }))
    };
  }
  _onRender(context, options) {
    super._onRender(context, options);
    this.#bindInputs();
    if (!this._updateHook) {
      this._updateHook = Hooks.on("cde:loksyuUpdated", () => this.render());
    }
    if (!this._settingHook) {
      this._settingHook = Hooks.on("updateSetting", (setting) => {
        if (setting.key === `${SYSTEM_ID}.loksyuData`) this.render();
      });
    }
  }
  _onClose(options) {
    if (this._updateHook !== null) {
      Hooks.off("cde:loksyuUpdated", this._updateHook);
      this._updateHook = null;
    }
    if (this._settingHook !== null) {
      Hooks.off("updateSetting", this._settingHook);
      this._settingHook = null;
    }
    super._onClose(options);
  }
  #bindInputs() {
    const inputs = this.element?.querySelectorAll("input[data-field]");
    if (!inputs?.length) return;
    inputs.forEach((input) => {
      input.addEventListener("change", async (ev) => {
        const field = ev.currentTarget.dataset.field;
        const val = parseInt(ev.currentTarget.value, 10);
        if (!field || isNaN(val)) return;
        const [aspect, dim] = field.split(".");
        if (!aspect || !dim) return;
        const data = getLoksyuData();
        if (!data[aspect]) data[aspect] = { yin: 0, yang: 0 };
        data[aspect][dim] = Math.max(0, val);
        await setLoksyuData(data);
      });
    });
  }
  static async #onResetElement(event, target) {
    const key = target.dataset.element;
    if (!key) return;
    const data = getLoksyuData();
    data[key] = { yin: 0, yang: 0 };
    await setLoksyuData(data);
  }
  static async #onResetAll(_event, _target) {
    const KEYS = ["wood", "fire", "earth", "metal", "water"];
    const data = getLoksyuData();
    for (const k of KEYS) data[k] = { yin: 0, yang: 0 };
    await setLoksyuData(data);
  }
  static #onZoomVisual(_event, target) {
    new ImagePopout(target.src, {
      title: game.i18n.localize("CDE.LoksyuDiagramTitle"),
      shareable: false
    }).render(true);
  }
};

// src/ui/apps/tinji-app.js
var CDETinjiApp = class _CDETinjiApp extends foundry.applications.api.HandlebarsApplicationMixin(
  foundry.applications.api.ApplicationV2
) {
  static DEFAULT_OPTIONS = {
    id: "cde-tinji-app",
    tag: "div",
    window: {
      title: "CDE.TinJi2",
      icon: "fas fa-star",
      resizable: false
    },
    classes: ["cde-app", "cde-tinji-standalone"],
    position: { width: 380, height: "auto" },
    actions: {
      increment: _CDETinjiApp.#onIncrement,
      decrement: _CDETinjiApp.#onDecrement,
      reset: _CDETinjiApp.#onReset,
      spend: _CDETinjiApp.#onSpend
    }
  };
  static PARTS = {
    main: {
      template: `systems/${SYSTEM_ID}/templates/apps/cde-tinji-app.html`
    }
  };
  /** @type {Function|null} */
  _updateHook = null;
  /** @type {Function|null} */
  _settingHook = null;
  static open() {
    const existing = Array.from(foundry.applications.instances.values()).find(
      (app2) => app2 instanceof _CDETinjiApp
    );
    if (existing) {
      existing.bringToFront();
      return existing;
    }
    const app = new _CDETinjiApp();
    app.render(true);
    return app;
  }
  async _prepareContext() {
    return {
      canEdit: game.user.isGM,
      value: getTinjiValue()
    };
  }
  _onRender(context, options) {
    super._onRender(context, options);
    this.#bindDirectInput();
    if (!this._updateHook) {
      this._updateHook = Hooks.on("cde:tinjiUpdated", () => this.render());
    }
    if (!this._settingHook) {
      this._settingHook = Hooks.on("updateSetting", (setting) => {
        if (setting.key === `${SYSTEM_ID}.tinjiData`) this.render();
      });
    }
  }
  _onClose(options) {
    if (this._updateHook !== null) {
      Hooks.off("cde:tinjiUpdated", this._updateHook);
      this._updateHook = null;
    }
    if (this._settingHook !== null) {
      Hooks.off("updateSetting", this._settingHook);
      this._settingHook = null;
    }
    super._onClose(options);
  }
  #bindDirectInput() {
    const input = this.element?.querySelector("input.cde-tinji-direct");
    if (!input) return;
    input.addEventListener("change", async (ev) => {
      const val = parseInt(ev.currentTarget.value, 10);
      if (!isNaN(val)) await setTinjiValue(val);
    });
  }
  static async #onIncrement() {
    await setTinjiValue(getTinjiValue() + 1);
  }
  static async #onDecrement() {
    const current = getTinjiValue();
    if (current <= 0) return;
    await setTinjiValue(current - 1);
  }
  static async #onReset() {
    await setTinjiValue(0);
  }
  static async #onSpend() {
    const current = getTinjiValue();
    if (current <= 0) {
      ui.notifications.warn(game.i18n.localize("CDE.TinjiEmpty"));
      return;
    }
    await setTinjiValue(current - 1);
    await ChatMessage.create({
      user: game.user.id,
      content: `<div class="cde-tinji-spend-msg">
        <i class="fas fa-star"></i>
        <strong>${game.i18n.localize("CDE.TinJi2")}</strong>
        ${game.i18n.format("CDE.TinjiSpent", { name: game.user.name })}
        <span class="cde-tinji-remain">(${current - 1} ${game.i18n.localize("CDE.TinjiRemaining")})</span>
      </div>`
    });
  }
};

// src/documents/combat.js
var CDECombat = class extends Combat {
  /**
   * Override rollInitiative to open the PC or NPC initiative dialog
   * for each selected combatant, then sync the result to the Combatant document.
   */
  async rollInitiative(ids, options = {}) {
    const combatantIds = ids ? typeof ids === "string" ? [ids] : ids : this.combatants.map((c) => c.id);
    for (const id of combatantIds) {
      const combatant = this.combatants.get(id);
      if (!combatant) continue;
      const actor = combatant.actor;
      if (!actor) continue;
      if (actor.type === ACTOR_TYPES.character) {
        await rollInitiativePC(actor);
      } else {
        await rollInitiativeNPC(actor);
      }
    }
    return this;
  }
  /**
   * Sort combatants: highest initiative first (furthest counter-clockwise = acts first).
   * Ties: PCs before NPCs; among PCs, by name; among NPCs, by name.
   * Calls super.setupTurns() first to ensure this.current is properly initialized.
   */
  setupTurns() {
    super.setupTurns();
    this.turns = this.turns.slice().sort((a, b) => {
      const ia = a.initiative ?? 0;
      const ib = b.initiative ?? 0;
      if (ia !== ib) return ib - ia;
      const aIsPC = a.actor?.type === ACTOR_TYPES.character ? 1 : 0;
      const bIsPC = b.actor?.type === ACTOR_TYPES.character ? 1 : 0;
      if (aIsPC !== bIsPC) return bIsPC - aIsPC;
      return (a.name ?? "").localeCompare(b.name ?? "");
    });
    return this.turns;
  }
};
async function advanceCombatantPosition(combatant, cranCost) {
  const current = combatant.initiative ?? combatant.actor?.system?.initiative ?? 1;
  const newValue = (current - cranCost - 1 + 48) % 24 + 1;
  await combatant.update({ initiative: newValue });
}

// src/ui/apps/wheel-app.js
var WHEEL_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-wheel-app.html";
var ACTION_COSTS = [
  { key: "draw", label: "CDE.ActionCostDraw", cost: 1 },
  { key: "changestyle", label: "CDE.ActionCostChangeStyle", cost: 1 },
  { key: "defense", label: "CDE.ActionCostDefense", cost: 1 },
  { key: "aim", label: "CDE.ActionCostAim", cost: 2 },
  { key: "help", label: "CDE.ActionCostHelp", cost: 2 },
  { key: "defally", label: "CDE.ActionCostDefendAlly", cost: 2 },
  { key: "move", label: "CDE.ActionCostMove", cost: 2 },
  { key: "attack", label: "CDE.ActionCostAttack", cost: 3 },
  { key: "delay", label: "CDE.ActionCostDelay", cost: 6 }
];
var WHEEL_SEGMENTS = [
  { label: "M\xE9tal", color: "#b8c4cc", textColor: "#1a1a1a", crans: [1, 2, 3, 4] },
  { label: "Eau", color: "#3a7bd5", textColor: "#ffffff", crans: [5, 6, 7, 8] },
  { label: "Terre", color: "#c8a84b", textColor: "#1a1a1a", crans: [9, 10, 11, 12] },
  { label: "Feu", color: "#d94f3d", textColor: "#ffffff", crans: [13, 14, 15, 16] },
  { label: "Bois", color: "#4a9b5a", textColor: "#ffffff", crans: [17, 18, 19, 20] },
  { label: "Rep\xE8re", color: "#1a1a2e", textColor: "#aaaaaa", crans: [21, 22, 23, 24] }
];
function segmentForCran(cran) {
  return WHEEL_SEGMENTS.find((s) => s.crans.includes(cran)) ?? WHEEL_SEGMENTS[0];
}
var CDEWheelApp = class _CDEWheelApp extends foundry.applications.api.ApplicationV2 {
  static DEFAULT_OPTIONS = {
    id: "cde-wheel-app",
    classes: ["cde-wheel-app"],
    tag: "div",
    window: {
      title: "CDE.InitiativeWheel",
      icon: "fas fa-circle-notch",
      resizable: true
    },
    position: { width: 820, height: 620 },
    actions: {
      advanceCran: _CDEWheelApp.#advanceCran,
      setSurprised: _CDEWheelApp.#setSurprised,
      rollInitiative: _CDEWheelApp.#rollInitiative
    }
  };
  /** @type {CDEWheelApp|null} */
  static #instance = null;
  /** Open (or bring to front) the singleton instance. */
  static open() {
    if (!_CDEWheelApp.#instance || _CDEWheelApp.#instance.rendered === false) {
      _CDEWheelApp.#instance = new _CDEWheelApp();
      _CDEWheelApp.#instance.render(true);
    } else {
      _CDEWheelApp.#instance.bringToFront();
    }
    return _CDEWheelApp.#instance;
  }
  /** Currently selected combatant id (for action panel). */
  #selectedId = null;
  async _prepareContext(options) {
    const combat = game.combat;
    const combatants = combat ? [...combat.combatants.values()] : [];
    const sorted = [...combatants].sort((a, b) => (b.initiative ?? 0) - (a.initiative ?? 0));
    const cranData = this.#buildCranData(combatants);
    const selected = this.#selectedId ? combatants.find((c) => c.id === this.#selectedId) : null;
    const actionCosts = ACTION_COSTS.map((a) => ({
      ...a,
      label: game.i18n.localize(a.label)
    }));
    return {
      hasCombat: !!combat,
      combatants: sorted.map((c) => ({
        id: c.id,
        name: c.name,
        img: c.token?.texture?.src ?? c.actor?.img ?? "icons/svg/mystery-man.svg",
        initiative: c.initiative ?? "\u2014",
        segment: segmentForCran(c.initiative ?? 1),
        isActive: combat?.current?.combatantId === c.id,
        isSelected: c.id === this.#selectedId,
        hasInitiative: c.initiative != null
      })),
      cranData,
      selected,
      selectedName: selected?.name ?? null,
      actionCosts
    };
  }
  async _renderHTML(context, options) {
    return foundry.applications.handlebars.renderTemplate(WHEEL_TEMPLATE, context);
  }
  _replaceHTML(result, content, options) {
    content.innerHTML = result;
    this.#bindEvents(content);
  }
  /** Build per-cran data for the SVG wheel. */
  #buildCranData(combatants) {
    const data = [];
    for (let cran = 1; cran <= 24; cran++) {
      const segment = segmentForCran(cran);
      const fighters = combatants.filter((c) => Math.round(c.initiative) === cran);
      data.push({ cran, segment, fighters });
    }
    return data;
  }
  /** Bind click events for combatant selection. */
  #bindEvents(content) {
    content.querySelectorAll("[data-select-combatant]").forEach((el) => {
      el.addEventListener("click", () => {
        this.#selectedId = el.dataset.selectCombatant;
        this.render();
      });
    });
  }
  /** Action: advance selected combatant by given cran cost. */
  static async #advanceCran(event, element) {
    const app = _CDEWheelApp.#instance;
    if (!app?.#selectedId) return;
    const cost = parseInt(element.dataset.cost, 10);
    if (!cost || isNaN(cost)) return;
    const combatant = game.combat?.combatants.get(app.#selectedId);
    if (!combatant) return;
    await advanceCombatantPosition(combatant, cost);
  }
  /** Action: set selected combatant to surprised (position 1 = reference). */
  static async #setSurprised(event, element) {
    const app = _CDEWheelApp.#instance;
    if (!app?.#selectedId) return;
    const combatant = game.combat?.combatants.get(app.#selectedId);
    if (!combatant) return;
    await combatant.update({ initiative: 1 });
  }
  /** Action: open the initiative dialog for the selected combatant. */
  static async #rollInitiative(event, element) {
    const app = _CDEWheelApp.#instance;
    if (!app?.#selectedId) return;
    const combatant = game.combat?.combatants.get(app.#selectedId);
    if (!combatant) return;
    await game.combat.rollInitiative([app.#selectedId]);
  }
  /** Re-render when combat state changes. */
  static registerHooks() {
    const refresh = () => {
      if (_CDEWheelApp.#instance?.rendered) _CDEWheelApp.#instance.render();
    };
    Hooks.on("updateCombat", refresh);
    Hooks.on("updateCombatant", refresh);
    Hooks.on("createCombatant", refresh);
    Hooks.on("deleteCombatant", refresh);
    Hooks.on("updateActor", (_actor, diff) => {
      if (foundry.utils.hasProperty(diff, "system.initiative")) refresh();
    });
    Hooks.on("deleteCombat", () => {
      if (_CDEWheelApp.#instance?.rendered) _CDEWheelApp.#instance.render();
    });
  }
};

// src/ui/roll-actions.js
var RESULT_TEMPLATE3 = "systems/fvtt-chroniques-de-l-etrange/templates/form/cde-dice-result.html";
function injectRollActions(message, html) {
  const rollCard = html.querySelector(".cde-roll-result");
  if (!rollCard) return;
  const aspect = rollCard.dataset.aspect;
  if (!aspect || !WU_XING_CYCLE[aspect]) return;
  refreshRollActions(rollCard, aspect, message);
}
function refreshRollActions(rollCard, aspect, message) {
  rollCard.querySelector(".cde-roll-actions")?.remove();
  const cycle = WU_XING_CYCLE[aspect];
  const fasteAspect = cycle[1];
  const loksyu = getLoksyuData();
  const tinji = getTinjiValue();
  const successAvail = (loksyu[aspect]?.yin ?? 0) + (loksyu[aspect]?.yang ?? 0);
  const fasteAvail = (loksyu[fasteAspect]?.yin ?? 0) + (loksyu[fasteAspect]?.yang ?? 0);
  const hasSomething = successAvail > 0 || fasteAvail > 0 || tinji > 0;
  if (!hasSomething) return;
  const aspLabel = game.i18n.localize(ASPECT_LABELS[aspect]);
  const fasteLabel = game.i18n.localize(ASPECT_LABELS[fasteAspect]);
  let btns = "";
  if (successAvail > 0) {
    btns += `<button class="cde-roll-action-btn cde-roll-action--success" data-action="loksyu-success">
      <img src="${ASPECT_ICONS[aspect]}" class="cde-roll-action-icon" alt="${aspLabel}"/>
      <span class="cde-roll-action-label">+1 ${game.i18n.localize("CDE.Successes")}</span>
      <span class="cde-roll-action-count">${successAvail}</span>
    </button>`;
  }
  if (fasteAvail > 0) {
    btns += `<button class="cde-roll-action-btn cde-roll-action--faste" data-action="loksyu-faste">
      <img src="${ASPECT_ICONS[fasteAspect]}" class="cde-roll-action-icon" alt="${fasteLabel}"/>
      <span class="cde-roll-action-label">+1 ${game.i18n.localize("CDE.AuspiciousDie")}</span>
      <span class="cde-roll-action-count">${fasteAvail}</span>
    </button>`;
  }
  if (tinji > 0) {
    btns += `<button class="cde-roll-action-btn cde-roll-action--tinji" data-action="tinji">
      <span class="cde-roll-action-tinji-char">\u5929</span>
      <span class="cde-roll-action-label">${game.i18n.localize("CDE.TinJi2")}</span>
      <span class="cde-roll-action-count">${tinji}</span>
    </button>`;
  }
  const wrapper = document.createElement("div");
  wrapper.className = "cde-roll-actions";
  wrapper.innerHTML = `
    <div class="cde-roll-actions-title">
      <i class="fas fa-yin-yang"></i>
      ${game.i18n.localize("CDE.PostRollActions")}
    </div>
    <div class="cde-roll-actions-btns">${btns}</div>
  `;
  rollCard.appendChild(wrapper);
  wrapper.addEventListener("click", async (ev) => {
    const btn = ev.target.closest("[data-action]");
    if (!btn || btn.disabled) return;
    const action = btn.dataset.action;
    if (action === "loksyu-success") {
      await _drawFromLoksyu(message, aspect, "success", aspLabel);
    } else if (action === "loksyu-faste") {
      await _drawFromLoksyu(message, fasteAspect, "faste", fasteLabel);
    } else if (action === "tinji") {
      await _spendTinjiPostRoll();
    }
    if (action === "tinji") refreshRollActions(rollCard, aspect, message);
  });
}
async function _drawFromLoksyu(message, aspect, type, aspectLabel) {
  const data = getLoksyuData();
  const entry = data[aspect] ?? { yin: 0, yang: 0 };
  const total = entry.yin + entry.yang;
  if (total <= 0) {
    ui.notifications.warn(game.i18n.localize("CDE.LoksyuEmpty"));
    return;
  }
  const order = game.settings.get(SYSTEM_ID, "loksyuConsumptionOrder");
  if (order === "yin-first") {
    if (entry.yin > 0) entry.yin--;
    else entry.yang--;
  } else if (order === "balanced") {
    if (entry.yin > entry.yang) entry.yin--;
    else if (entry.yang > entry.yin) entry.yang--;
    else if (entry.yang > 0) entry.yang--;
    else entry.yin--;
  } else {
    if (entry.yang > 0) entry.yang--;
    else entry.yin--;
  }
  data[aspect] = entry;
  if (game.user.isGM) {
    await setLoksyuData(data);
  } else {
    requestLoksyuDraw(aspect, order);
  }
  const flags = message?.flags?.[SYSTEM_ID];
  if (flags?.rollResult && message.isOwner) {
    const updated = foundry.utils.deepClone(flags.rollResult);
    if (type === "success") {
      updated.successesdice = (updated.successesdice ?? 0) + 1;
      updated.loksyuBonusSuc = (updated.loksyuBonusSuc ?? 0) + 1;
      if (updated.damageBase != null) updated.totalDamage = updated.damageBase + (updated.damageAspectValue ?? 0);
    } else {
      updated.auspiciousdice = (updated.auspiciousdice ?? 0) + 1;
      updated.loksyuBonusFaste = (updated.loksyuBonusFaste ?? 0) + 1;
    }
    const newHtml = await foundry.applications.handlebars.renderTemplate(RESULT_TEMPLATE3, updated);
    await message.update({
      content: newHtml,
      [`flags.${SYSTEM_ID}.rollResult`]: updated
    });
  }
  const remain = entry.yin + entry.yang;
  const typeLabel = type === "success" ? game.i18n.localize("CDE.Successes") : game.i18n.localize("CDE.AuspiciousDie");
  await ChatMessage.create({
    user: game.user.id,
    content: `<div class="cde-loksyu-draw-msg">
      <div class="cde-loksyu-draw-header">
        <img src="${ASPECT_ICONS[aspect]}" class="cde-loksyu-draw-aspect-icon" alt="${aspectLabel}"/>
        <span class="cde-loksyu-draw-user">${game.user.name}</span>
        <span class="cde-loksyu-draw-action">${game.i18n.localize("CDE.LoksyuDrawsA")}</span>
        <span class="cde-loksyu-draw-type">${typeLabel}</span>
        <span class="cde-loksyu-draw-from">${game.i18n.localize("CDE.LoksyuFromAspect")} <em>${aspectLabel}</em></span>
      </div>
      <div class="cde-loksyu-draw-footer">
        <i class="fas fa-yin-yang"></i>
        <span>${game.i18n.localize("CDE.Loksyu")} ${aspectLabel} : </span>
        <strong class="cde-loksyu-remain">${remain} ${game.i18n.localize("CDE.LoksyuRemaining")}</strong>
      </div>
    </div>`
  });
}
async function _spendTinjiPostRoll() {
  const current = getTinjiValue();
  if (current <= 0) {
    ui.notifications.warn(game.i18n.localize("CDE.TinjiEmpty"));
    return;
  }
  if (game.user.isGM) {
    await setTinjiValue(current - 1);
  } else {
    requestTinjiSpend();
  }
  await ChatMessage.create({
    user: game.user.id,
    content: `<div class="cde-tinji-spend-msg">
      <span class="cde-tinji-icon">\u5929</span>
      <span class="cde-tinji-text">
        <strong>${game.user.name}</strong> ${game.i18n.localize("CDE.TinjiSpent").replace("{name}", game.user.name)}
      </span>
      <span class="cde-tinji-remain">(${current - 1} ${game.i18n.localize("CDE.TinjiRemaining")})</span>
    </div>`
  });
}
function refreshAllRollActions() {
  document.querySelectorAll(".chat-message .cde-roll-result[data-aspect]").forEach((card) => {
    const aspect = card.dataset.aspect;
    if (!aspect || !WU_XING_CYCLE[aspect]) return;
    const msgEl = card.closest("[data-message-id]");
    const msgId = msgEl?.dataset?.messageId;
    const message = msgId ? game.messages.get(msgId) : null;
    refreshRollActions(card, aspect, message);
  });
}

// src/ui/apps/welcome.js
var HELP_JOURNAL_UUID = `Compendium.${SYSTEM_ID}.cde-help.JournalEntry.CDEGuideMain0001`;
async function showWelcomeMessage() {
  const logo = `systems/${SYSTEM_ID}/images/logo_jeu.webp`;
  const content = `
<div class="cde-welcome-message">
  <img class="cde-welcome-logo" src="${logo}" alt="Les Chroniques de l'\xC9trange" />
  <h2 class="cde-welcome-title">Les Chroniques de l'\xC9trange</h2>
  <p class="cde-welcome-links">
    Un jeu de r\xF4le \xE9dit\xE9 par
    <a href="https://antre-monde.com/les-chroniques-de-letrengae/" target="_blank" rel="noopener">Antre-Monde \xC9ditions</a>
  </p>
  <p class="cde-welcome-links">
    Syst\xE8me FoundryVTT r\xE9alis\xE9 par
    <a href="https://www.uberwald.me" target="_blank" rel="noopener">LeRatierBretonnien</a>
  </p>
  <button type="button" class="cde-welcome-help-btn" data-action="open-cde-help">
    <i class="fas fa-book-open"></i>
    ${game.i18n.localize("CDE.WelcomeOpenHelp")}
  </button>
</div>`;
  await ChatMessage.create({
    content,
    speaker: { alias: "Les Chroniques de l'\xC9trange" },
    flags: { [SYSTEM_ID]: { welcome: true } }
  });
}
function injectWelcomeActions(_message, html) {
  const el = html instanceof HTMLElement ? html : html[0] ?? html;
  const btn = el?.querySelector?.("[data-action='open-cde-help']");
  if (!btn) return;
  btn.addEventListener("click", async () => {
    try {
      const doc = await fromUuid(HELP_JOURNAL_UUID);
      doc?.sheet?.render(true);
    } catch {
      game.packs.get(`${SYSTEM_ID}.cde-help`)?.render(true);
    }
  });
}

// src/system.js
Hooks.once("i18nInit", preLocalizeConfig);
Hooks.once("init", async () => {
  console.log(
    "%c\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n%c\u2551              Chroniques de l'\xC9trange \u2014 FoundryVTT        \u2551\n%c\u2551              Syst\xE8me de jeu par Antre-Monde \xC9ditions     \u2551\n%c\u2551          Made by Uberwald - https://www.ubwerwald.me      \u2551\n%c\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
    "color: #d4af37; font-weight: bold;",
    "color: #e2e8f4;",
    "color: #7d94b8;",
    "color: #5a7a9a;",
    "color: #d4af37; font-weight: bold;"
  );
  registerSettings();
  game.system.CONST = { MAGICS, SUBTYPES };
  game.cde = { CDELoksyuApp, CDETinjiApp, CDEWheelApp };
  CONFIG.Combat.documentClass = CDECombat;
  CONFIG.Actor.dataModels = {
    [ACTOR_TYPES.character]: CharacterDataModel,
    [ACTOR_TYPES.npc]: NpcDataModel
  };
  CONFIG.Item.dataModels = {
    [ITEM_TYPES.item]: EquipmentDataModel,
    [ITEM_TYPES.kungfu]: KungfuDataModel,
    [ITEM_TYPES.spell]: SpellDataModel,
    [ITEM_TYPES.supernatural]: SupernaturalDataModel,
    [ITEM_TYPES.weapon]: WeaponDataModel,
    [ITEM_TYPES.armor]: ArmorDataModel,
    [ITEM_TYPES.sanhei]: SanheiDataModel,
    [ITEM_TYPES.ingredient]: IngredientDataModel
  };
  CONFIG.Actor.documentClass = CDEActor;
  CONFIG.Item.documentClass = CDEItem;
  CONFIG.ChatMessage.documentClass = CDEMessage;
  configureRuntime();
  foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Actor, "core", foundry.appv1.sheets.ActorSheet);
  foundry.applications.apps.DocumentSheetConfig.unregisterSheet(Item, "core", foundry.appv1.sheets.ItemSheet);
  foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDECharacterSheet, {
    types: [ACTOR_TYPES.character],
    makeDefault: true,
    label: "CDE Character Sheet (V2)"
  });
  foundry.applications.apps.DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDENpcSheet, {
    types: [ACTOR_TYPES.npc],
    makeDefault: true,
    label: "CDE NPC Sheet (V2)"
  });
  foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEItemSheet, {
    types: [ITEM_TYPES.item],
    makeDefault: true,
    label: "CDE Item Sheet (V2)"
  });
  foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEKungfuSheet, {
    types: [ITEM_TYPES.kungfu],
    makeDefault: true,
    label: "CDE KungFu Sheet (V2)"
  });
  foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESpellSheet, {
    types: [ITEM_TYPES.spell],
    makeDefault: true,
    label: "CDE Spell Sheet (V2)"
  });
  foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESupernaturalSheet, {
    types: [ITEM_TYPES.supernatural],
    makeDefault: true,
    label: "CDE Supernatural Sheet (V2)"
  });
  foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEWeaponSheet, {
    types: [ITEM_TYPES.weapon],
    makeDefault: true,
    label: "CDE Weapon Sheet (V2)"
  });
  foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEArmorSheet, {
    types: [ITEM_TYPES.armor],
    makeDefault: true,
    label: "CDE Armor Sheet (V2)"
  });
  foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESanheiSheet, {
    types: [ITEM_TYPES.sanhei],
    makeDefault: true,
    label: "CDE Sanhei Sheet (V2)"
  });
  foundry.applications.apps.DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEIngredientSheet, {
    types: [ITEM_TYPES.ingredient],
    makeDefault: true,
    label: "CDE Ingredient Sheet (V2)"
  });
  await preloadPartials();
  registerHandlebarsHelpers();
  console.info(`CHRONIQUESDELETRANGE | Initialized`);
});
Hooks.once("ready", async () => {
  await migrateIfNeeded();
  await loadWelcomeSceneIfNeeded();
  CDEWheelApp.registerHooks();
  registerSingletonSocket();
  if (game.user.isGM) showWelcomeMessage();
});
Hooks.on("renderChatLog", (_app, html) => {
  const el = html instanceof HTMLElement ? html : html[0] ?? html;
  if (!el?.querySelector) return;
  if (el.querySelector(".cde-chat-app-buttons")) return;
  const wrapper = document.createElement("div");
  wrapper.classList.add("cde-chat-app-buttons");
  wrapper.innerHTML = `
    <button type="button" class="cde-chat-btn cde-chat-btn--loksyu">
      <i class="fas fa-yin-yang"></i> ${game.i18n.localize("CDE.Loksyu")}
    </button>
    <button type="button" class="cde-chat-btn cde-chat-btn--tinji">
      <i class="fas fa-star"></i> ${game.i18n.localize("CDE.TinJi2")}
    </button>
    <button type="button" class="cde-chat-btn cde-chat-btn--wheel">
      <i class="fas fa-circle-notch"></i> ${game.i18n.localize("CDE.InitiativeWheel")}
    </button>
  `;
  wrapper.addEventListener("click", (ev) => {
    if (ev.target.closest(".cde-chat-btn--loksyu")) CDELoksyuApp.open();
    if (ev.target.closest(".cde-chat-btn--tinji")) CDETinjiApp.open();
    if (ev.target.closest(".cde-chat-btn--wheel")) CDEWheelApp.open();
  });
  const anchor = el.querySelector(".chat-form") ?? el.querySelector(".chat-message-form") ?? el.querySelector("form");
  if (anchor) anchor.parentElement.insertBefore(wrapper, anchor);
  else el.appendChild(wrapper);
});
Hooks.on("renderChatMessageHTML", (message, html) => {
  injectRollActions(message, html);
  if (message.flags?.[SYSTEM_ID]?.welcome) injectWelcomeActions(message, html);
});
Hooks.on("updateSetting", (setting) => {
  if (!setting.key) return;
  if (setting.key.includes("loksyuData") || setting.key.includes("tinjiData")) {
    refreshAllRollActions();
  }
});
Hooks.on("updateActor", (actor, diff) => {
  if (!foundry.utils.hasProperty(diff, "system.initiative")) return;
  if (!game.combat) return;
  const initiative = actor.system.initiative;
  const combatant = game.combat.combatants.find((c) => c.actor?.id === actor.id);
  if (combatant && combatant.initiative !== initiative) {
    combatant.update({ initiative }).catch(() => {
    });
  }
});
Hooks.on("updateCombatant", (combatant, diff) => {
  if (!("initiative" in diff)) return;
  const initiative = combatant.initiative;
  if (initiative == null) return;
  setTimeout(() => {
    const actor = combatant.actor;
    if (actor && actor.system?.initiative !== initiative) {
      actor.update({ "system.initiative": initiative }).catch(() => {
      });
    }
  }, 0);
});
/**
 * 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/
 */
//# sourceMappingURL=system.js.map
