/**
 * Cyber-Assistant — Registre des packs de référentiels (menaces & scénarios)
 * ============================================================================
 * Socle L1 du chantier « référentiels → menaces/risques » (étude
 * ETUDE-REFERENTIELS-ANALYSE-RISQUES-2026-08-04.md, §4).
 *
 * Un PACK est un fichier de données en LECTURE SEULE (data/frameworks/
 * catalog-<id>.js — jamais data/catalogs/ : refusé par l'allowlist de
 * update.php), IIFE auto-enregistrante :
 *
 *   window._csiThreatCatalogs = window._csiThreatCatalogs || {};
 *   window._csiThreatCatalogs['owasp-top10-2025'] = { meta: {...},
 *     threats: [...], scenarios: [...] };
 *
 * Schéma minimal :
 *   meta      { id, version, publishedAt, name/nameEn/nameNl, kind, licence,
 *               disclaimer/disclaimerEn }               — version OBLIGATOIRE
 *   threats[] { id, ref, label, labelEn, labelNl, adversarial (true|false),
 *               domainHints[], defaults {likelihood, impact} }
 *   scenarios[] (optionnel) { id, threatId, label, labelEn, labelNl,
 *               domainHints[] (surcharge, sinon hérite de la menace),
 *               defaults, applicableWhen }
 *
 * Ce module ne stocke RIEN dans appData : les objets utilisateur portent une
 * référence (packId/threatId/scenarioId) + un DÉNORMALISÉ figé à la sélection
 * (_threat/_scenario/_domainHints/_packVersion) — garde-fou n°1 de l'étude.
 * Il est chargé AVANT risk-analysis.js et fonctionne sans lui (file:// inclus).
 */
(function () {
  'use strict';

  window._csiThreatCatalogs = window._csiThreatCatalogs || {};

  function _lang() {
    try {
      if (window.CSI_i18n && typeof window.CSI_i18n.getLanguage === 'function') {
        return window.CSI_i18n.getLanguage();
      }
    } catch (e) { /* i18n pas encore chargé : défaut FR */ }
    return 'fr';
  }

  // Libellé localisé d'un item de pack : base = 'label' → label / labelEn / labelNl.
  // NL retombe sur FR si un pack tiers omet labelNl (les 6 packs livrés sont
  // trilingues complets, décision PO 04/08) — jamais de chaîne vide.
  function _pick(item, base) {
    if (!item) return '';
    var lang = _lang();
    if (lang === 'en') return item[base + 'En'] || item[base] || '';
    if (lang === 'nl') return item[base + 'Nl'] || item[base] || '';
    return item[base] || item[base + 'En'] || '';
  }

  function _get(packId) {
    // FIX-AUDIT-PROTO (audit v2, 2026-08-25) : `_csiThreatCatalogs['constructor']` renvoyait
    // la fonction Object, et l'appelant faisait `pack.meta.name` → TypeError non rattrapée
    // (aperçu étape 8 cassé, bouton PDF sans effet). sourceFramework n'est validé nulle part.
    // hasOwnProperty ferme la classe pour TOUS les consommateurs du registre.
    if (!packId || !Object.prototype.hasOwnProperty.call(window._csiThreatCatalogs, packId)) return null;
    return window._csiThreatCatalogs[packId] || null;
  }

  function _findIn(list, id) {
    if (!Array.isArray(list) || !id) return null;
    for (var i = 0; i < list.length; i++) { if (list[i] && list[i].id === id) return list[i]; }
    return null;
  }

  // Entrée NORMALISÉE au format du catalogue d'analyse (RA_RISK_CATALOG) :
  // { threat, threatEn, threatNl, scenario, scenarioEn, scenarioNl,
  //   domainHints, adversarial, ref, defaults, packId, packVersion }.
  // scenarioId prime (grain le plus fin) ; sinon la menace seule.
  function _normalize(pack, threat, scenario) {
    if (!pack || !threat) return null;
    var hints = (scenario && scenario.domainHints) || threat.domainHints || [];
    return {
      id: (scenario ? scenario.id : threat.id),
      threatId: threat.id,
      scenarioId: scenario ? scenario.id : null,
      ref: (scenario && scenario.ref) || threat.ref || '',
      threat: threat.label || '',
      threatEn: threat.labelEn || threat.label || '',
      threatNl: threat.labelNl || threat.label || '',
      scenario: (scenario ? scenario.label : '') || '',
      scenarioEn: (scenario ? (scenario.labelEn || scenario.label) : '') || '',
      scenarioNl: (scenario ? (scenario.labelNl || scenario.label) : '') || '',
      domainHints: hints.slice(),
      adversarial: threat.adversarial !== false,
      defaults: (scenario && scenario.defaults) || threat.defaults || null,
      packId: pack.meta.id,
      packVersion: pack.meta.version || '?'
    };
  }

  var api = {
    /** Enregistre un pack (utilisé par les fichiers de pack eux-mêmes). */
    register: function (pack) {
      if (!pack || !pack.meta || !pack.meta.id) return false;
      window._csiThreatCatalogs[pack.meta.id] = pack;
      return true;
    },

    /** Liste des packs chargés (meta uniquement). */
    list: function () {
      return Object.keys(window._csiThreatCatalogs).map(function (k) {
        return window._csiThreatCatalogs[k].meta;
      });
    },

    get: _get,

    /**
     * Résout une référence (packId, threatId, scenarioId?) en entrée normalisée.
     * Retourne null si le pack ou la menace est absent — l'appelant retombe
     * alors sur le dénormalisé de l'objet utilisateur.
     */
    resolveEntry: function (packId, threatId, scenarioId) {
      var pack = _get(packId);
      if (!pack) return null;
      var scenario = scenarioId ? _findIn(pack.scenarios, scenarioId) : null;
      var tid = threatId || (scenario && scenario.threatId);
      var threat = _findIn(pack.threats, tid);
      if (!threat && scenario) {
        // Scénario sans menace résolue : dégrade sur le scénario seul.
        threat = { id: scenario.id, label: scenario.label, labelEn: scenario.labelEn,
                   labelNl: scenario.labelNl, domainHints: scenario.domainHints,
                   adversarial: true, defaults: scenario.defaults };
        scenario = null;
      }
      if (!threat) return null;
      return _normalize(pack, threat, scenario);
    },

    /**
     * Entrées « sélectionnables » d'un pack pour la grille de l'étape Risques
     * (L2) : une entrée par scénario ; une menace sans scénario donne une
     * entrée seule.
     */
    entriesFor: function (packId) {
      var pack = _get(packId);
      if (!pack) return [];
      var out = [];
      var covered = {};
      (pack.scenarios || []).forEach(function (sc) {
        var threat = _findIn(pack.threats, sc.threatId);
        if (!threat) return;
        covered[threat.id] = true;
        var e = _normalize(pack, threat, sc);
        if (e) out.push(e);
      });
      (pack.threats || []).forEach(function (t) {
        if (covered[t.id]) return;
        var e = _normalize(pack, t, null);
        if (e) out.push(e);
      });
      return out;
    },

    /** Libellé localisé (FR/EN/NL avec repli FR) — exposé pour l'UI. */
    pickLabel: _pick
  };

  window.CSI_ThreatCatalog = api;
})();
