// Rôle : centralise les règles de validation des JSON éditables du projet.
import assert from "node:assert/strict";

const SNAKE_CASE_RE = /^[a-z0-9]+(?:_[a-z0-9]+)*$/;
const PHYSICAL_HITZONE_KEYS = ["cut", "blunt", "ammo"];
const ELEMENTAL_HITZONE_KEYS = ["fire", "water", "thunder", "ice", "dragon"];

function valueAt(object, path) {
  return path.split(".").reduce((value, key) => value?.[key], object);
}

function assertNonEmptyString(object, path) {
  const value = valueAt(object, path);
  assert.equal(typeof value, "string", `${path} must be a string`);
  assert.ok(value.trim(), `${path} must not be empty`);
}

export function assertStringArray(object, path, minLength = 1) {
  const value = valueAt(object, path);
  assert.ok(Array.isArray(value), `${path} must be an array`);
  assert.ok(value.length >= minLength, `${path} must contain at least ${minLength} item(s)`);
  value.forEach((item, index) => {
    assert.equal(typeof item, "string", `${path}[${index}] must be a string`);
    assert.ok(item.trim(), `${path}[${index}] must not be empty`);
  });
}

export function validateSiteContent(site) {
  [
    "brand.name",
    "brand.homeAriaLabel",
    "navigation.home",
    "navigation.toolboxes",
    "navigation.games",
    "navigation.library",
    "navigation.about",
    "navigation.mobileLibrary",
    "navigation.mobileGames",
    "sidebar.badge",
    "sidebar.note",
    "topbar.dashboard",
    "topbar.toolbox",
    "topbar.library",
    "topbar.games",
    "gamesPage.eyebrow",
    "gamesPage.title",
    "gamesPage.description",
    "gamesPage.emptyTitle",
    "gamesPage.emptyText",
    "library.eyebrow",
    "library.title",
    "library.description",
    "library.summaryLabel",
    "library.tocLabel",
    "library.toolsLabel",
    "home.hero.eyebrow",
    "home.hero.title",
    "home.hero.description",
    "home.hero.primaryAction",
    "home.hero.secondaryAction",
    "home.stats.toolboxSingular",
    "home.stats.toolboxPlural",
    "home.stats.toolSingular",
    "home.stats.toolPlural",
    "home.origin.eyebrow",
    "home.origin.title",
    "home.origin.visualAlt",
    "home.origin.dialogueAriaLabel",
    "home.origin.caption",
    "home.aboutLink",
    "home.legal.copyright",
    "home.legal.disclaimer",
    "about.eyebrow",
    "about.title",
    "about.description",
    "about.toolsTitle",
    "about.toolsLibraryLink",
    "about.limitsTitle",
    "about.contribute.eyebrow",
    "about.contribute.title",
    "about.contribute.description",
    "about.contribute.example",
    "about.contribute.promptTitle",
    "about.contribute.promptIntro",
    "about.contribute.promptText",
    "toolboxes.eyebrow",
    "toolboxes.title",
    "toolboxes.newButton",
    "toolboxes.libraryLink",
    "toolboxes.importAll",
    "toolboxes.exportAll",
    "toolboxes.importOne",
    "toolboxes.preferencesLabel",
    "toolboxes.toastPositionLabel",
    "toolboxes.toastPositionLeft",
    "toolboxes.toastPositionRight",
    "toolboxes.summary",
    "toolboxes.storageHelp.title",
    "toolboxes.storageHelp.text",
    "toolboxes.modules.notepad.placeholder",
    "toolboxes.modules.notepad.toolbarLabel",
    "toolboxes.modules.notepad.textFormatLabel",
    "toolboxes.modules.notepad.boldTitle",
    "toolboxes.modules.notepad.italicTitle",
    "toolboxes.modules.notepad.underlineTitle",
    "toolboxes.modules.notepad.strikeTitle",
    "toolboxes.modules.notepad.headingTitle",
    "toolboxes.modules.notepad.listGroupLabel",
    "toolboxes.modules.notepad.bulletListTitle",
    "toolboxes.modules.notepad.numberListTitle",
    "toolboxes.modules.notepad.colorGroupLabel",
    "toolboxes.modules.notepad.textColorLabel",
    "toolboxes.modules.notepad.highlightColorLabel",
    "toolboxes.modules.notepad.drawingLabel",
    "toolboxes.modules.notepad.drawTitle",
    "toolboxes.modules.notepad.eraseTitle",
    "toolboxes.modules.notepad.temporaryTitle",
    "toolboxes.modules.notepad.permanentTitle",
    "toolboxes.modules.notepad.undoDrawingTitle",
    "toolboxes.modules.notepad.clearDrawingTitle",
    "toolboxes.modules.notepad.drawingColorLabel",
    "toolboxes.modules.notepad.drawingWidthLabel",
    "toolboxes.modules.notepad.temporaryStatus",
    "toolboxes.modules.notepad.permanentStatus",
    "toolboxes.modules.notepad.updatedAtPrefix",
    "toolboxes.modules.notepad.sessionStorageError",
    "toolboxes.modules.checklist.itemPlaceholder",
    "toolboxes.modules.checklist.sectionPlaceholder",
    "toolboxes.modules.checklist.quantityLabel",
    "toolboxes.modules.checklist.addButton",
    "toolboxes.modules.checklist.importPlaceholder",
    "toolboxes.modules.checklist.importOpenButton",
    "toolboxes.modules.checklist.importModalTitle",
    "toolboxes.modules.checklist.importButton",
    "toolboxes.modules.checklist.hideCompletedSectionsTitle",
    "toolboxes.modules.checklist.hideCompletedSectionsFullyTitle",
    "toolboxes.modules.checklist.completedSectionsModeTitle",
    "toolboxes.modules.checklist.completedSectionsVisibleLabel",
    "toolboxes.modules.checklist.completedSectionsReducedLabel",
    "toolboxes.modules.checklist.completedSectionsHiddenLabel",
    "toolboxes.modules.checklist.showCompletedSectionTitle",
    "toolboxes.modules.checklist.hideCompletedSectionTitle",
    "toolboxes.modules.checklist.decrementLabel",
    "toolboxes.modules.checklist.incrementLabel",
    "toolboxes.modules.checklist.currentQuantityLabel",
    "toolboxes.modules.checklist.reorderTitle",
    "toolboxes.modules.checklist.reorderSectionTitle",
    "toolboxes.modules.checklist.sectionEditTitle",
    "toolboxes.modules.checklist.itemEditLabel",
    "toolboxes.modules.checklist.categoryEditLabel",
    "toolboxes.modules.checklist.editTitle",
    "toolboxes.modules.checklist.deleteTitle",
    "toolboxes.modules.images.addImages",
    "toolboxes.modules.images.pastePlaceholder",
    "toolboxes.modules.images.pasteAriaLabel",
    "toolboxes.modules.images.imageAlt",
    "toolboxes.modules.images.labelPlaceholder",
    "toolboxes.modules.images.labelAriaLabel",
    "toolboxes.modules.images.previewAriaLabel",
    "toolboxes.modules.images.annotateAriaLabel",
    "toolboxes.modules.images.annotateTitle",
    "toolboxes.modules.images.deleteAriaLabel",
    "toolboxes.modules.images.deleteTitle",
    "toolboxes.modules.links.titlePlaceholder",
    "toolboxes.modules.links.urlPlaceholder",
    "toolboxes.modules.links.addButton",
    "toolboxes.modules.links.importPlaceholder",
    "toolboxes.modules.links.importOpenButton",
    "toolboxes.modules.links.importModalTitle",
    "toolboxes.modules.links.importButton",
    "toolboxes.modules.links.copyTitle",
    "toolboxes.modules.links.copiedTitle",
    "toolboxes.modules.links.deleteTitle",
    "toolboxes.modules.counters.labelPlaceholder",
    "toolboxes.modules.counters.addButton",
    "toolboxes.modules.counters.decrementLabel",
    "toolboxes.modules.counters.incrementLabel",
    "toolboxes.modules.counters.resetTitle",
    "toolboxes.modules.counters.deleteTitle",
    "toolboxes.modules.combos.deviceLabel",
    "toolboxes.modules.combos.keyboardLayoutLabel",
    "toolboxes.modules.combos.categoryPlaceholder",
    "toolboxes.modules.combos.targetCategoryLabel",
    "toolboxes.modules.combos.noCategoryLabel",
    "toolboxes.modules.combos.comboNameLabel",
    "toolboxes.modules.combos.comboNamePlaceholder",
    "toolboxes.modules.combos.defaultComboName",
    "toolboxes.modules.combos.emptyDraft",
    "toolboxes.modules.combos.simultaneousModeTitle",
    "toolboxes.modules.combos.successiveModeLabel",
    "toolboxes.modules.combos.simultaneousModeLabel",
    "toolboxes.modules.combos.holdLabel",
    "toolboxes.modules.combos.holdToggleLabel",
    "toolboxes.modules.combos.removeStepTitle",
    "toolboxes.modules.combos.removeLastInputTitle",
    "toolboxes.modules.combos.addComboButton",
    "toolboxes.modules.combos.saveComboButton",
    "toolboxes.modules.combos.cancelEditTitle",
    "toolboxes.modules.combos.cancelEditButton",
    "toolboxes.modules.combos.paletteLabel",
    "toolboxes.modules.combos.customKeyPlaceholder",
    "toolboxes.modules.combos.addCustomKeyButton",
    "toolboxes.modules.combos.emptyCombos",
    "toolboxes.modules.combos.emptySequence",
    "toolboxes.modules.combos.showCategoryTitle",
    "toolboxes.modules.combos.hideCategoryTitle",
    "toolboxes.modules.combos.reorderCategoryTitle",
    "toolboxes.modules.combos.reorderComboTitle",
    "toolboxes.modules.combos.editComboTitle",
    "toolboxes.modules.combos.deleteComboTitle",
    "toolboxes.modules.calculator.expressionLabel",
    "toolboxes.modules.calculator.expressionPlaceholder",
    "toolboxes.modules.calculator.resultLabel",
    "toolboxes.modules.calculator.labelLabel",
    "toolboxes.modules.calculator.labelPlaceholder",
    "toolboxes.modules.calculator.saveButton",
    "toolboxes.modules.calculator.rootButton",
    "toolboxes.modules.calculator.resetTitle",
    "toolboxes.modules.calculator.copyTitle",
    "toolboxes.modules.calculator.copiedTitle",
    "toolboxes.modules.calculator.scrollableTitle",
    "toolboxes.modules.calculator.emptyResults",
    "toolboxes.modules.calculator.readonlyValueTitle",
    "toolboxes.modules.calculator.useEntryTitle",
    "toolboxes.modules.calculator.renameTitle",
    "toolboxes.modules.calculator.deleteTitle",
    "toolboxes.modules.table.toolbarLabel",
    "toolboxes.modules.table.gridLabel",
    "toolboxes.modules.table.cellLabel",
    "toolboxes.modules.table.rowActionsLabel",
    "toolboxes.modules.table.columnActionsLabel",
    "toolboxes.modules.table.sizeLabel",
    "toolboxes.modules.table.addRowTitle",
    "toolboxes.modules.table.removeRowTitle",
    "toolboxes.modules.table.addColumnTitle",
    "toolboxes.modules.table.removeColumnTitle",
    "toolboxes.modules.table.copyTitle",
    "toolboxes.modules.table.copiedTitle",
    "toolboxes.modules.table.errorTitle",
    "toolboxes.modules.table.columnLabel",
    "toolboxes.modules.table.rowLabel",
    "toolboxes.modules.table.columnFormulaTitle",
    "toolboxes.modules.table.rowFormulaTitle",
    "toolboxes.modules.timer.stopwatchTab",
    "toolboxes.modules.timer.countdownTab",
    "toolboxes.modules.timer.stopwatchTitle",
    "toolboxes.modules.timer.countdownTitle",
    "toolboxes.modules.timer.startButton",
    "toolboxes.modules.timer.pauseButton",
    "toolboxes.modules.timer.lapButton",
    "toolboxes.modules.timer.resetTitle",
    "toolboxes.modules.timer.lapLabelAriaLabel",
    "toolboxes.modules.timer.countdownLabel",
    "toolboxes.modules.timer.countdownLabelPlaceholder",
    "toolboxes.modules.timer.countdownTypeLabel",
    "toolboxes.modules.timer.durationType",
    "toolboxes.modules.timer.dailyTimeType",
    "toolboxes.modules.timer.timePatternType",
    "toolboxes.modules.timer.intervalType",
    "toolboxes.modules.timer.durationLabel",
    "toolboxes.modules.timer.dailyTimeLabel",
    "toolboxes.modules.timer.timePatternLabel",
    "toolboxes.modules.timer.intervalLabel",
    "toolboxes.modules.timer.intervalStartNowOption",
    "toolboxes.modules.timer.intervalStartTimeOption",
    "toolboxes.modules.timer.intervalStartTimeLabel",
    "toolboxes.modules.timer.addCountdownButton",
    "toolboxes.modules.timer.requiredError",
    "toolboxes.modules.timer.invalidError",
    "toolboxes.modules.timer.defaultCountdownLabel",
    "toolboxes.modules.timer.finishedLabel",
    "toolboxes.modules.timer.targetLabel",
    "toolboxes.modules.timer.stepLabel",
    "toolboxes.modules.timer.stepsTitle",
    "toolboxes.modules.timer.countdownsTitle",
    "toolboxes.modules.timer.scrollableTitle",
    "toolboxes.modules.timer.sortTitle",
    "toolboxes.modules.timer.autoRefreshTitle",
    "toolboxes.modules.timer.alertModeTitle",
    "toolboxes.modules.timer.alertOffTitle",
    "toolboxes.modules.timer.alertVisibleTitle",
    "toolboxes.modules.timer.alertSiteTitle",
    "toolboxes.modules.timer.alertTooSoonTitle",
    "toolboxes.modules.timer.alertMessage",
    "toolboxes.modules.timer.emptySteps",
    "toolboxes.modules.timer.emptyCountdowns",
    "toolboxes.modules.timer.renameTitle",
    "toolboxes.modules.timer.deleteTitle",
    "toolboxes.modules.taskPlanner.settingsTitle",
    "toolboxes.modules.taskPlanner.settingsButtonLabel",
    "toolboxes.modules.taskPlanner.hideCompletedTitle",
    "toolboxes.modules.taskPlanner.globalWeeklyResetDayLabel",
    "toolboxes.modules.taskPlanner.resetTimeLabel",
    "toolboxes.modules.taskPlanner.clearCompletedUniqueTitle",
    "toolboxes.modules.taskPlanner.clearCompletedUniqueButton",
    "toolboxes.modules.taskPlanner.titlePlaceholder",
    "toolboxes.modules.taskPlanner.titleLabel",
    "toolboxes.modules.taskPlanner.descriptionPlaceholder",
    "toolboxes.modules.taskPlanner.descriptionLabel",
    "toolboxes.modules.taskPlanner.typeLabel",
    "toolboxes.modules.taskPlanner.uniqueType",
    "toolboxes.modules.taskPlanner.dailyType",
    "toolboxes.modules.taskPlanner.weeklyType",
    "toolboxes.modules.taskPlanner.addButton",
    "toolboxes.modules.taskPlanner.reorderTitle",
    "toolboxes.modules.taskPlanner.checkTitle",
    "toolboxes.modules.taskPlanner.uncheckTitle",
    "toolboxes.modules.taskPlanner.deleteTitle",
    "toolboxes.modules.taskPlanner.showDescriptionTitle",
    "toolboxes.modules.taskPlanner.hideDescriptionTitle",
    "toolboxes.modules.taskPlanner.taskSettingsTitle",
    "toolboxes.modules.taskPlanner.missingPrerequisiteTitle",
    "toolboxes.modules.taskPlanner.missingPrerequisiteBadge",
    "toolboxes.modules.taskPlanner.missingPrerequisiteNotice",
    "toolboxes.modules.taskPlanner.categoryLabel",
    "toolboxes.modules.taskPlanner.categoryPlaceholder",
    "toolboxes.modules.taskPlanner.categoryReorderTitle",
    "toolboxes.modules.taskPlanner.uncategorizedDropTitle",
    "toolboxes.modules.taskPlanner.showCategoryTitle",
    "toolboxes.modules.taskPlanner.hideCategoryTitle",
    "toolboxes.modules.taskPlanner.taskWeeklyResetDayLabel",
    "toolboxes.modules.taskPlanner.inheritWeeklyResetDay",
    "toolboxes.modules.taskPlanner.linksLabel",
    "toolboxes.modules.taskPlanner.emptyLinks",
    "toolboxes.modules.taskPlanner.addLinkLabel",
    "toolboxes.modules.taskPlanner.prerequisiteCheckboxLabel",
    "toolboxes.modules.taskPlanner.removeRelationTitle",
    "toolboxes.modules.taskPlanner.emptyTasks",
    "toolboxes.modules.imageAnnotation.addImage",
    "toolboxes.modules.imageAnnotation.replaceImage",
    "toolboxes.modules.imageAnnotation.pastePlaceholder",
    "toolboxes.modules.imageAnnotation.pasteAriaLabel",
    "toolboxes.modules.imageAnnotation.stageAriaLabel",
    "toolboxes.modules.imageAnnotation.addMarkerAriaLabel",
    "toolboxes.modules.imageAnnotation.imageAlt",
    "toolboxes.modules.imageAnnotation.previewTitle",
    "toolboxes.modules.imageAnnotation.previewAriaLabel",
    "toolboxes.modules.imageAnnotation.markerPrefix",
    "toolboxes.modules.imageAnnotation.markersSingular",
    "toolboxes.modules.imageAnnotation.markersPlural",
    "toolboxes.modules.imageAnnotation.markerPlaceholder",
    "toolboxes.modules.imageAnnotation.emptyImage",
    "toolboxes.modules.imageAnnotation.emptyMarkers",
    "toolboxes.modules.imageAnnotation.deleteImageTitle",
    "toolboxes.modules.imageAnnotation.deleteMarkerTitle",
    "toolboxes.modules.imageAnnotation.drawingLabel",
    "toolboxes.modules.imageAnnotation.drawTitle",
    "toolboxes.modules.imageAnnotation.eraseTitle",
    "toolboxes.modules.imageAnnotation.undoDrawingTitle",
    "toolboxes.modules.imageAnnotation.clearDrawingTitle",
    "toolboxes.modules.imageAnnotation.drawingColorLabel",
    "toolboxes.modules.imageAnnotation.drawingWidthLabel",
    "toolboxes.modules.imageAnnotation.temporaryTitle",
    "toolboxes.modules.imageAnnotation.permanentTitle",
    "toolboxes.modules.imageAnnotation.sessionStorageError",
    "toolboxes.emptyTitle",
    "toolboxes.emptyText"
  ].forEach((path) => assertNonEmptyString(site, path));

  assertStringArray(site, "toolboxes.storageHelp.items", 3);

  const librarySummaryItems = valueAt(site, "library.summaryItems");
  assert.ok(Array.isArray(librarySummaryItems), "library.summaryItems must be an array");
  assert.ok(librarySummaryItems.length >= 3, "library.summaryItems must contain at least 3 items");
  librarySummaryItems.forEach((item, index) => {
    assertNonEmptyString({ item }, `item.icon`);
    assertNonEmptyString({ item }, `item.title`);
    assertNonEmptyString({ item }, `item.text`);
    assert.equal(typeof item, "object", `library.summaryItems[${index}] must be an object`);
  });

  const aboutLimits = valueAt(site, "about.limits");
  assert.ok(Array.isArray(aboutLimits), "about.limits must be an array");
  assert.ok(aboutLimits.length >= 3, "about.limits must contain at least 3 items");
  aboutLimits.forEach((limit, index) => {
    if (typeof limit === "string") {
      assert.ok(limit.trim(), `about.limits[${index}] must not be empty`);
      return;
    }
    assert.equal(typeof limit, "object", `about.limits[${index}] must be a string or object`);
    assertNonEmptyString({ limit }, "limit.icon");
    assertNonEmptyString({ limit }, "limit.title");
    assertNonEmptyString({ limit }, "limit.text");
  });

  const aboutSections = valueAt(site, "about.sections");
  assert.ok(Array.isArray(aboutSections), "about.sections must be an array");
  assert.ok(aboutSections.length >= 2, "about.sections must contain at least 2 items");
  aboutSections.forEach((section) => {
    assertNonEmptyString({ section }, "section.title");
    assertNonEmptyString({ section }, "section.text");
  });

  const aboutTools = valueAt(site, "about.tools");
  assert.ok(Array.isArray(aboutTools), "about.tools must be an array");
  assert.ok(aboutTools.length >= 1, "about.tools must contain at least 1 item");
  aboutTools.forEach((tool) => {
    assertNonEmptyString({ tool }, "tool.icon");
    assertNonEmptyString({ tool }, "tool.name");
    assertNonEmptyString({ tool }, "tool.description");
  });

  const lines = valueAt(site, "home.origin.lines");
  assert.ok(Array.isArray(lines), "home.origin.lines must be an array");
  assert.ok(lines.length >= 2, "home.origin.lines must contain at least 2 items");
  lines.forEach((line, index) => {
    assert.ok(["user", "app"].includes(line?.speaker), `home.origin.lines[${index}].speaker must be "user" or "app"`);
    assert.equal(typeof line?.text, "string", `home.origin.lines[${index}].text must be a string`);
    assert.ok(line.text.trim(), `home.origin.lines[${index}].text must not be empty`);
  });
}

export function validateLibraryData(payload) {
  assert.equal(typeof payload?.toolbox, "object", "library.toolbox must be an object");
  assert.equal(typeof payload?.modules, "object", "library.modules must be an object");
  assertNonEmptyString(payload, "toolbox.name");
  assert.ok(Array.isArray(payload.toolbox.modules), "library.toolbox.modules must be an array");
  assert.ok(payload.toolbox.modules.length >= 1, "library.toolbox.modules must not be empty");

  const moduleIds = new Set();
  payload.toolbox.modules.forEach((module, index) => {
    assertNonEmptyString({ module }, "module.id");
    assertNonEmptyString({ module }, "module.type");
    assert.ok(!moduleIds.has(module.id), `library.toolbox.modules[${index}].id must be unique`);
    moduleIds.add(module.id);
    assert.equal(typeof payload.modules[module.id], "object", `library.modules.${module.id} must be an object`);
  });

  ["notepad", "checklist", "images", "links", "counters", "combos", "calculator", "table", "timer", "taskPlanner", "imageAnnotation"].forEach((type) => {
    assert.ok(payload.toolbox.modules.some((module) => module.type === type), `library must include a ${type} example`);
  });

  const imagesModule = payload.toolbox.modules.find((module) => module.type === "images");
  const imagesData = payload.modules[imagesModule.id];
  assert.ok(Array.isArray(imagesData.images), "library images example must contain images");
  assert.ok(imagesData.images.length >= 1, "library images example must not be empty");
  imagesData.images.forEach((image, index) => {
    assertNonEmptyString({ image }, "image.id");
    assertNonEmptyString({ image }, "image.dataUrl");
    assert.match(image.dataUrl, /^data:image\//, `library images[${index}].dataUrl must be an inline image`);
  });

  const taskModule = payload.toolbox.modules.find((module) => module.type === "taskPlanner");
  const taskData = payload.modules[taskModule.id];
  assert.ok(Array.isArray(taskData.tasks), "library task planner example must contain tasks");
  assert.ok(Array.isArray(taskData.relations), "library task planner example must contain relations");
  assert.ok(taskData.relations.length >= 1, "library task planner example must include a parent/child relation");
}

function assertSnakeCase(value, path) {
  assert.equal(typeof value, "string", `${path} must be a string`);
  assert.match(value, SNAKE_CASE_RE, `${path} must be snake_case`);
}

function validateConditionValues(items, path) {
  assert.ok(Array.isArray(items), `${path} must be an array`);
  items.forEach((item, index) => {
    assertSnakeCase(item?.condition, `${path}[${index}].condition`);
    assert.ok(Array.isArray(item?.values), `${path}[${index}].values must be an array`);
    item.values.forEach((value, valueIndex) => assertSnakeCase(value, `${path}[${index}].values[${valueIndex}]`));
  });
}

export function validateMonsterData(monsters) {
  assert.ok(Array.isArray(monsters), "monsters must be an array");
  const ids = new Set();
  const names = new Set();

  monsters.forEach((monster, monsterIndex) => {
    const path = `monsters[${monsterIndex}]`;
    assert.equal(typeof monster.id, "number", `${path}.id must be a number`);
    assert.ok(Number.isInteger(monster.id), `${path}.id must be an integer`);
    assert.ok(!ids.has(monster.id), `${path}.id must be unique`);
    ids.add(monster.id);

    assertSnakeCase(monster.name, `${path}.name`);
    assert.ok(!names.has(monster.name), `${path}.name must be unique`);
    names.add(monster.name);
    assertSnakeCase(monster.type, `${path}.type`);
    validateConditionValues(monster.ailments, `${path}.ailments`);
    validateConditionValues(monster.weaknesses, `${path}.weaknesses`);
    validateConditionValues(monster.weakpoints, `${path}.weakpoints`);

    assert.equal(monster.damage, undefined, `${path}.damage must not be used`);
    assert.ok(Array.isArray(monster.hitzones), `${path}.hitzones must be an array`);
    assert.ok(monster.hitzones.length > 0, `${path}.hitzones must not be empty`);
    monster.hitzones.forEach((hitzone, hitzoneIndex) => {
      const hitzonePath = `${path}.hitzones[${hitzoneIndex}]`;
      assertSnakeCase(hitzone.name, `${hitzonePath}.name`);
      assert.equal(typeof hitzone.physical, "object", `${hitzonePath}.physical must be an object`);
      assert.equal(typeof hitzone.elemental, "object", `${hitzonePath}.elemental must be an object`);
      PHYSICAL_HITZONE_KEYS.forEach((key) => assert.equal(typeof hitzone.physical[key], "number", `${hitzonePath}.physical.${key} must be a number`));
      ELEMENTAL_HITZONE_KEYS.forEach((key) => assert.equal(typeof hitzone.elemental[key], "number", `${hitzonePath}.elemental.${key} must be a number`));
    });
  });
}

export function validateEndemicData(endemicPayload) {
  ["endemicLife", "aquaticLife"].forEach((collection) => {
    assert.ok(Array.isArray(endemicPayload[collection]), `${collection} must be an array`);
    endemicPayload[collection].forEach((item, itemIndex) => {
      const path = `${collection}[${itemIndex}]`;
      assertSnakeCase(item.name, `${path}.name`);
      if (item.description) assertSnakeCase(item.description, `${path}.description`);
      validateConditionValues(item.locations, `${path}.locations`);
    });
  });
}

export function validateTranslationKeys(translations, path) {
  Object.keys(translations).forEach((key) => {
    assert.ok(!/\s/.test(key), `${path}.${key} must not contain spaces`);
  });
}

export function validateDiablo4Affixes(payload) {
  assert.ok(Array.isArray(payload.categories), "diablo4.categories must be an array");
  assert.ok(payload.categories.length > 0, "diablo4.categories must not be empty");
  assert.ok(Array.isArray(payload.affixes), "diablo4.affixes must be an array");
  assert.ok(payload.affixes.length > 0, "diablo4.affixes must not be empty");

  const ids = new Set();
  const categoryIds = payload.categories.map((category, index) => {
    assertSnakeCase(category.id, `diablo4.categories[${index}].id`);
    assertNonEmptyString({ category }, "category.label");
    assertSnakeCase(category.icon, `diablo4.categories[${index}].icon`);
    assertSnakeCase(category.tone, `diablo4.categories[${index}].tone`);
    return category.id;
  });

  payload.affixes.forEach((affix, index) => {
    assertSnakeCase(affix.id, `diablo4.affixes[${index}].id`);
    assert.ok(!ids.has(affix.id), `diablo4.affixes[${index}].id must be unique`);
    ids.add(affix.id);
    assertNonEmptyString({ affix }, "affix.label");
    assert.ok(Array.isArray(affix.categories), `diablo4.affixes[${index}].categories must be an array`);
    affix.categories.forEach((category, categoryIndex) => {
      assertSnakeCase(category, `diablo4.affixes[${index}].categories[${categoryIndex}]`);
      assert.ok(categoryIds.includes(category), `diablo4.affixes[${index}].categories[${categoryIndex}] must exist in diablo4.categories`);
    });
  });
}
