/**
 * Cyber-Assistant - Module Matrices d'Impacts
 * Gestion de grilles d'impact personnalisables (niveaux × dimensions)
 * Réutilisables dans les analyses de risques
 */
(function() {
  'use strict';

  // ============================================================
  // HELPERS
  // ============================================================
  function _t(key, fallback) {
    return typeof window.t === 'function' ? window.t(key) : (fallback || key);
  }
  var _esc = window.escapeHtml;
  function _id() {
    return 'imx-' + Date.now() + '-' + Math.random().toString(36).substr(2, 6);
  }
  function _now() { return new Date().toISOString(); }
  function _deepClone(o) { try { return JSON.parse(JSON.stringify(o)); } catch(e) { void 0; return o; } }

  // ============================================================
  // SEED DATA — Matrice par défaut (5 niveaux, 10 dimensions)
  // ============================================================
  function createDefaultMatrix() {
    return {
      id: 'imx-default-v1',
      name: "Matrice d'impact g\u00e9n\u00e9rique (PESTEL/5M/POTI)",
      description: "Matrice d'impact 5 niveaux, 10 dimensions. Contenu g\u00e9n\u00e9rique bas\u00e9 sur une matrice \u00e9v\u00e9nement/crise.",
      createdAt: '2026-02-08T00:00:00.000Z',
      updatedAt: '2026-02-08T00:00:00.000Z',
      levels: [
        { level: 1, label: 'Insignifiant', band: 'low', responsePriority: 5, responseCapability: "capacit\u00e9\nde gestion courante de l\u2019organisation/\nchef de projet / programme" },
        { level: 2, label: 'Mineur', band: 'low', responsePriority: 4, responseCapability: "capacit\u00e9\nde gestion courante de l\u2019organisation/\nchef de projet / programme" },
        { level: 3, label: 'Significatif', band: 'medium', responsePriority: 3, responseCapability: "capacit\u00e9\nde gestion courante de l\u2019organisation/ le comit\u00e9 de pilotage projet/programme" },
        { level: 4, label: 'Grave', band: 'medium', responsePriority: 2, responseCapability: "Capacit\u00e9 de gestion courante de l\u2019organisation/\ngestion des incidents IT majeurs/gestion des probl\u00e8mes IT majeurs/ le comit\u00e9 op\u00e9rationnel et la direction -> constitution d\u2019une task force ad hoc qui rapporte \u00e0 la direction et aux b\u00e9n\u00e9ficiaires impact\u00e9s" },
        { level: 5, label: 'Critique', band: 'high', responsePriority: 1, responseCapability: "Les capacit\u00e9s gestion courante de l\u2019organisation sont d\u00e9pass\u00e9es -> d\u00e9clenchement gestion de crise (constitution cellule de crise en plus de task forces qui lui rapportent)" }
      ],
      dimensions: [
        { id: 'POL_REPUTATION', label: 'Politique / R\u00e9putation (PESTEL)', sourceLabel: 'Politique (PESTEL) / R\u00e9putation', order: 1 },
        { id: 'ECO_FINANCE', label: '\u00c9conomique / Moyens (PESTEL, 5M)', sourceLabel: 'Economique (PESTEL) / Moyens (5M)', order: 2 },
        { id: 'SOC_PERSONNES', label: 'Sociologique / Personnes (PESTEL, 5M, RGPD)', sourceLabel: "Sociologique (PESTEL) / Main d\u2019\u0153uvre (5M) / Personnes (RGPD)", order: 3 },
        { id: 'TECH_SERVICES', label: 'Technologique / Services & Mat\u00e9riel (PESTEL, POTI, 5M)', sourceLabel: 'Technologique / services (PESTEL & POTI) / mat\u00e9riel (5M)', order: 4 },
        { id: 'ENV_MILIEU', label: 'Environnemental / Milieu (PESTEL, 5M)', sourceLabel: 'Environnemental (PESTEL) / Milieu (5M)', order: 5 },
        { id: 'LEGAL_REG', label: 'L\u00e9gal & R\u00e9glementaire / M\u00e9thodes (PESTEL, 5M)', sourceLabel: 'L\u00e9gal & r\u00e9glementaire (PESTEL) / M\u00e9thodes (5M)', order: 6 },
        { id: 'ORG_PROCESS', label: 'Organisation / Processus (POTI)', sourceLabel: 'Organisation / processus (POTI)', order: 7 },
        { id: 'INFO_CONF', label: 'Information: Confidentialit\u00e9 (POTI)', sourceLabel: 'Information (POTI): Confidentialit\u00e9', order: 8 },
        { id: 'INFO_INTEG', label: 'Information: Int\u00e9grit\u00e9 (POTI)', sourceLabel: 'Information (POTI): Int\u00e9grit\u00e9', order: 9 },
        { id: 'INFO_AVAIL', label: 'Information: Disponibilit\u00e9 (POTI)', sourceLabel: 'Information (POTI): Disponibilit\u00e9', order: 10 }
      ],
      cells: {
        POL_REPUTATION: {
          '1': "La r\u00e9putation ou l\u2019image de l\u2019organisation ou de ses b\u00e9n\u00e9ficiaires n\u2019est pas engag\u00e9e.",
          '2': "Diff\u00e9rentes critiques non r\u00e9currentes sont remont\u00e9es. La situation est le r\u00e9sultat d\u2019un fait mineur ou d\u2019un fait divers.\nImpact sur l\u2019image/visibilit\u00e9: interne",
          '3': "L\u2019organisation est concern\u00e9e de mani\u00e8re temporaire par une atteinte \u00e0 sa r\u00e9putation. L\u2019image de l\u2019organisme ou de ses b\u00e9n\u00e9ficiaires n\u2019est pas impact\u00e9e sur le long terme.\nImpact sur l\u2019image/visibilit\u00e9: local",
          '4': "Critiques s\u00e9rieuses et r\u00e9p\u00e9t\u00e9es dans les m\u00e9dias. La situation n\u2019est pas temporaire et un impact \u00e0 plus long terme est possible pour l\u2019image de l\u2019organisation et/ou de ses b\u00e9n\u00e9ficiaires. la direction et les instances de gouvernance sont impact\u00e9s.\nImpact sur l\u2019image/visibilit\u00e9: m\u00e9dia",
          '5': "L\u2019alt\u00e9ration de l\u2019image de l\u2019organisation et/ou de ses b\u00e9n\u00e9ficiaires est d\u00e9finitive. L\u2019organisation restera longuement touch\u00e9e. Une crise est engag\u00e9e. les autorit\u00e9s / instances de tutelle sont impact\u00e9s.\nImpact sur l\u2019image/visibilit\u00e9: m\u00e9dia"
        },
        ECO_FINANCE: {
          '1': "pas d\u2019impact financier\n\nD\u00e9passement insignifiant du budget du projet/programme",
          '2': "impact financier\nfaible\n\nperte financiere li\u00e9e \u00e0 des tarifs pr\u00e9f\u00e9rentiels dont on ne peut b\u00e9n\u00e9ficier compte-tenu des d\u00e9lais courts\ndouble paiement de factures\n\nD\u00e9passement budg\u00e9taire de 0,5% du budget du projet/programme",
          '3': "impact financier\nmoyen\n\namende et retard de paiement\n\nperte financiere inf\u00e9rieure \u00e0 20 000 euros\n\nPlus de 5% de d\u00e9passement budg\u00e9taire du budget du projet/programme",
          '4': "impact financier\nimportant\n\nd\u00e9passement de la consommation budg\u00e9taire avec impact sur la continuit\u00e9 business\n\nperte financiere sup\u00e9rieure \u00e0 20 000 euros\n\nPlus de 10% de d\u00e9passement budg\u00e9taire du budget du projet/programme",
          '5': "impact financier\ntr\u00e8s important\n\nla position de tr\u00e9sorerie ne permet pas de couvrir au-del\u00e0 d\u2019un mois de salaires du personnel, incapacit\u00e9 de payer les fournisseurs\nd\u00e9tournement de fonds de l\u2019organisation"
        },
        SOC_PERSONNES: {
          '1': "Les personnes concern\u00e9es ne sont pas impact\u00e9es.\nNear-miss/incident/presque-accident: pas de dommage humain",
          '2': "Les personnes concern\u00e9es pourraient connaitre quelques d\u00e9sagr\u00e9ments, qu\u2019elles surmonteront sans difficult\u00e9. Exemples : perte de temps, simple contrariet\u00e9, agacement, \u00e9nervement.\nincapacit\u00e9 temporaire de travail inf\u00e9rieure \u00e0 3 jours",
          '3': "Les personnes concern\u00e9es pourraient connaitre des d\u00e9sagr\u00e9ments significatifs, qu\u2019elles pourront surmonter malgr\u00e9 quelques difficult\u00e9s. Exemples : frais suppl\u00e9mentaires, refus d\u2019acc\u00e8s \u00e0 des services/prestations, peur, incompr\u00e9hension, stress, sentiment d\u2019atteinte \u00e0 la vie priv\u00e9e.\nincapacit\u00e9 temporaire de travail sup\u00e9rieure \u00e0 3 jours",
          '4': "Les personnes concern\u00e9es pourraient connaitre des cons\u00e9quences significatives, qu\u2019elles devraient pouvoir surmonter, mais avec de s\u00e9rieuses difficult\u00e9s. Exemples : difficult\u00e9s financi\u00e8res non temporaires, d\u00e9gradation de biens, perte d\u2019emploi, \u00e9tat de sant\u00e9, assignation en justice, harc\u00e8lement ou chantage.\n\nD\u00e9c\u00e8s d\u2019un membre du personnel dans le cadre priv\u00e9; accident du travail entrainant une incapacit\u00e9 permanente du membre du personnel",
          '5': "Les personnes concern\u00e9es pourraient connaitre des cons\u00e9quences significatives, voire irr\u00e9m\u00e9diables, qu\u2019elles pourraient ne pas surmonter. Exemples : p\u00e9ril financier, affection physique ou psychologique de longue dur\u00e9e ou permanente, d\u00e9c\u00e8s, perte d\u2019acc\u00e8s \u00e0 une infrastructure vitale, sanctions p\u00e9nales.\n\nAccident de travail: d\u00e9c\u00e8s du membre du personnel sur les lieux du travail/sur le chemin du travail"
        },
        TECH_SERVICES: {
          '1': "incident IT sans impact sur le business\n\nmat\u00e9riel: vol/perte d\u2019asset sans impact sur la continuit\u00e9 de service et facilement rempla\u00e7able (ex: perte d\u2019un pc rempla\u00e7able rapidement avec stock disponible)\n\nQualit\u00e9 produit : anomalie mineure permettant de travailler sur le logiciel, sans impact majeur.Ex : faute d\u2019orthographe, d\u00e9faut de couleur.",
          '2': "incident IT 1 utilisateur\nde 1 \u00e0 10 utilisateurs sont impact\u00e9s avec workaround\n\nmat\u00e9riel: vol/perte d\u2019asset de faible valeur, facilement rempla\u00e7able et avec peu d\u2019impact sur la continuit\u00e9 de service (en stock)\n\nQualit\u00e9 produit : anomalie moyenne permettant de travailler sur le logiciel, qui d\u00e9grade la fonctionnalit\u00e9 sans impact majeur. Ex : un contr\u00f4le erron\u00e9 d\u2019un champ courriel.",
          '3': "incident IT Une application est impact\u00e9e avec workaround\nplus de 10 utilisateurs sont impact\u00e9s avec workaround\nde 1 \u00e0 10 utilisateurs sont impact\u00e9s sans workaround\nun VIP est impact\u00e9\n\nmat\u00e9riel: vol/perte d\u2019asset impliquant un d\u00e9lais de remplacement et impactant la continuit\u00e9 de service\n\nQualit\u00e9 produit : anomalie majeure qui d\u00e9grade l\u2019utilisation d\u2019une fonction de l\u2019application et pour laquelle il existe une solution de contournement technique ou organsationnelle \u2013 anomalie ne permettant l\u2019exploitation du logiciel qu\u2019en mode d\u00e9grad\u00e9, non viable sur le long terme. Ex : un bouton \u00ab annuler \u00bb effectue un envoi.",
          '4': "incident IT de s\u00e9curit\u00e9\nUn site complet est impact\u00e9\nplus de 10 utilisateurs sont impact\u00e9s sans workaround\n\nincident IT Une application est impact\u00e9e sans workaround\nmat\u00e9riel: vol/perte/vandalisme/destruction totale d\u2019 asset critique rempla\u00e7able avec impact sur la continuit\u00e9 de service ( en stock et/ou et supplychain d\u00e9lais raisonnables,...)\n\nQualit\u00e9 produit : anomalie bloquante qui conduit \u00e0 rendre inop\u00e9rante toute une application, ou qui bloque l\u2019utilisation ou provoque un r\u00e9sultat erron\u00e9 pour une fonction de l\u2019application et pour laquelle il ne peut exister une solution de contournement technique ou organisationnelle. Ex :l\u2019envoi de donn\u00e9es donne un message d\u2019erreur.",
          '5': "incident de s\u00e9curit\u00e9\nmat\u00e9riel: vol/perte/vandalisme/destruction totale d\u2019 asset critique difficilement rempla\u00e7able (ex:serveur) avec fort impact sur la continuit\u00e9 de service ( pas en stock et supplychain d\u00e9lais importants,...)"
        },
        ENV_MILIEU: {
          '1': "Near miss/incident/presque-accident:\npas de dommage pour l\u2019environnement de travail",
          '2': "environnement mat\u00e9riel facilement rempla\u00e7able",
          '3': "environnement mat\u00e9riel difficilement rempla\u00e7able",
          '4': "Destruction partielle de\nl\u2019environnement de travail dont certaines zones sont encore accessibles",
          '5': "Accident de travail:\ndestruction totale de l\u2019environnement de travail ; environnement de travail rendu totalement inaccessible"
        },
        LEGAL_REG: {
          '1': "pas d\u2019impact r\u00e9glementaire\naux l\u00e9gislations sp\u00e9cifiques \u00e0 la protection des donn\u00e9es\nPas ou peu d\u2019impact juridique",
          '2': "pas d\u2019impact r\u00e9glementaire\naux l\u00e9gislations sp\u00e9cifiques \u00e0 la protection des donn\u00e9es\nimpact juridique: Revendication d\u2019un tiers",
          '3': "blame possible\nformul\u00e9 par l\u2019autorit\u00e9 de protection des donn\u00e9es (publication de la d\u00e9cision)\nimpact juridique:Autorit\u00e9 impliqu\u00e9e",
          '4': "Mesures correctrices :\navertissement, r\u00e9primande, rappel \u00e0 l\u2019ordre, injonction, interdiction temporaire du traitement, astreintes. - Poursuites civiles : suspension du traitement (action en cessation), dommages et int\u00e9r\u00eats (action en r\u00e9paration). - Sanctions p\u00e9nales : amendes de 2.000 EUR \u00e0 120.000 EUR (pour certains types d\u2019infractions seulement, en ce compris les transferts de donn\u00e9es hors de l\u2019EEE non-conformes \u00e0 l\u2019arr\u00eat Schrems II).\nImpact juridique: Annulation ou nullit\u00e9 d\u2019un acte",
          '5': "Mesures correctrices :\navertissement, r\u00e9primande, rappel \u00e0 l\u2019ordre, injonction, interdiction d\u00e9finitive du traitement, astreintes. - Poursuites civiles : suspension du traitement (action en cessation), dommages et int\u00e9r\u00eats (action en r\u00e9paration). - Sanctions p\u00e9nales : amendes de 2.000 EUR \u00e0 120.000 EUR (pour certains types d\u2019infractions seulement, en ce compris les transferts de donn\u00e9es hors de l\u2019EEE non-conformes \u00e0 l\u2019arr\u00eat Schrems II).\nImpact juridique:sanction p\u00e9nale"
        },
        ORG_PROCESS: {
          '1': "L\u2019organisation et ses b\u00e9n\u00e9ficiaires ne sont pas perturb\u00e9s dans leurs activit\u00e9s. La production n\u2019est pas touch\u00e9e.\nProjet/programme: D\u00e9calage d\u2019activit\u00e9s hors chemin critique, Nice-to-have non r\u00e9alis\u00e9",
          '2': "L\u2019organisation est l\u00e9g\u00e8rement perturb\u00e9e mais il n\u2019y a pas d\u2019impact sur les activit\u00e9s ou la production de l\u2019organisme pour ses b\u00e9n\u00e9ficiaires.\n\nObjectifs du processus de l\u2019organisation partiellement atteints; pas d\u2019impact sur d\u2019autres processus de l\u2019organisation; impact op\u00e9rationnel porte \u00e0 cons\u00e9quence mais reste tol\u00e9rable\n\nprojet/programme: D\u00e9calage d\u2019activit\u00e9s sur chemin critique,Must-Have non r\u00e9alis\u00e9",
          '3': "Probl\u00e8mes affectant le fonctionnement interne et engendrant du retard et un impact limit\u00e9 sur la production ou les activit\u00e9s de l\u2019organisation et/ou de ses b\u00e9n\u00e9ficiaires.\n\nobjectifs du processus de l\u2019organisation moyennement atteints; impact sur un autre processus de l\u2019organisation; impact op\u00e9rationnel: ne peut etre tol\u00e9r\u00e9 qu\u2019\u00e0 titre provisoire mais doit \u00eatre trait\u00e9 \u00e0 terme\n\nprojet/programme: Non respect d\u2019un jalon interm\u00e9diaire,Pan complet non r\u00e9alis\u00e9",
          '4': "Paralysie de certains processus de l\u2019organisation affectant les employ\u00e9s internes et les b\u00e9n\u00e9ficiaires de l\u2019organisation. La production et les activit\u00e9s sont fortement perturb\u00e9es pendant plusieurs jours.\n\nObjectifs du processus de l\u2019organisation faiblement atteints; impact sur plusieurs autres processus de l\u2019organisation ou sur le processus de management; impact op\u00e9rationnel: ne met pas l\u2019entit\u00e9 en p\u00e9rile mais reste tr\u00e8s grave et doit imp\u00e9rativement \u00eatre trait\u00e9\n\nprojet/programme:Non respect de l\u2019engagement contractuel/l\u00e9gal,R\u00e9vision int\u00e9grale du scope",
          '5': "La production de l\u2019organisation et/ou de ses b\u00e9n\u00e9ficiaires est \u00e0 l\u2019arr\u00eat et la reprise de l\u2019activit\u00e9 n\u2019est pas garantie dans plus d\u2019une ou plusieurs semaines.\n\nObjectifs du processus non atteints; impact sur l\u2019ensemble des processus de l\u2019organisation; impact op\u00e9rationnel: mets la survie de l\u2019entit\u00e9 en cause"
        },
        INFO_CONF: {
          '1': "La confidentialit\u00e9 n\u2019est pas directement impact\u00e9e et ne constitue pas un \u00e9l\u00e9ment important.",
          '2': "Impact faible ou n\u00e9gligeable mais la perte de confidentialit\u00e9 est d\u00e9favorable pour l\u2019organisation et/ou pour ses b\u00e9n\u00e9ficiaires.",
          '3': "Impact significatif mais limit\u00e9 \u00e0 un certain p\u00e9rim\u00e8tre sur la confidentialit\u00e9 d\u2019une certaine quantit\u00e9 de donn\u00e9es appartenant \u00e0 l\u2019organisation et/ou \u00e0 ses b\u00e9n\u00e9ficiaires.",
          '4': "Impact grave sur la confidentialit\u00e9 conduisant \u00e0 une perturbation cons\u00e9quente des activit\u00e9s de l\u2019organisation et/ou de ses b\u00e9n\u00e9ficiaires.",
          '5': "Impact critique sur la confidentialit\u00e9 conduisant \u00e0 une perturbation d\u00e9finitive des activit\u00e9s de l\u2019organisation et/ou de ses b\u00e9n\u00e9ficiaires."
        },
        INFO_INTEG: {
          '1': "L\u2019int\u00e9grit\u00e9 n\u2019est pas directement impact\u00e9e et ne constitue pas un \u00e9l\u00e9ment important.",
          '2': "Impact faible ou n\u00e9gligeable mais la perte d\u2019int\u00e9grit\u00e9 est d\u00e9favorable pour l\u2019organisation et/ou pour ses b\u00e9n\u00e9ficiaires.",
          '3': "Impact significatif mais limit\u00e9 \u00e0 un certain p\u00e9rim\u00e8tre sur l\u2019int\u00e9grit\u00e9 d\u2019une certaine quantit\u00e9 de donn\u00e9es appartenant \u00e0 l\u2019organisation ou \u00e0 ses b\u00e9n\u00e9ficiaires.",
          '4': "Impact grave sur l\u2019int\u00e9grit\u00e9 conduisant \u00e0 une perturbation cons\u00e9quente des activit\u00e9s de l\u2019organisation et/ou de ses b\u00e9n\u00e9ficiaires.",
          '5': "Impact critique sur l\u2019int\u00e9grit\u00e9 conduisant \u00e0 une perturbation d\u00e9finitive des activit\u00e9s de l\u2019organisation et/ou de ses b\u00e9n\u00e9ficiaires."
        },
        INFO_AVAIL: {
          '1': "La disponibilit\u00e9 n\u2019est pas directement impact\u00e9e et ne constitue pas un \u00e9l\u00e9ment important.",
          '2': "Impact faible ou n\u00e9gligeable mais la perte de disponibilit\u00e9 est d\u00e9favorable pour l\u2019organisation et/ou pour ses b\u00e9n\u00e9ficiaires.",
          '3': "Impact significatif mais limit\u00e9 \u00e0 un certain p\u00e9rim\u00e8tre sur la disponibilit\u00e9 d\u2019une certaine quantit\u00e9 de donn\u00e9es appartenant \u00e0 l\u2019organisation ou \u00e0 ses b\u00e9n\u00e9ficiaires.",
          '4': "Impact grave sur la disponibilit\u00e9 conduisant \u00e0 une perturbation cons\u00e9quente des activit\u00e9s de l\u2019organisation et/ou de ses b\u00e9n\u00e9ficiaires.",
          '5': "Impact critique sur la disponibilit\u00e9 conduisant \u00e0 une perturbation d\u00e9finitive des activit\u00e9s de l\u2019organisation et/ou de ses b\u00e9n\u00e9ficiaires."
        }
      },
      meta: {
        calculation: {
          defaultAggregation: 'max',
          notes: "Pour calculer un impact global \u00e0 partir des dimensions, la r\u00e8gle par d\u00e9faut est le max des niveaux s\u00e9lectionn\u00e9s."
        }
      }
    };
  }

  // ============================================================
  // SEED DATA — NIST SP 800-30 (5 niveaux, 6 dimensions)
  // ============================================================
  function createNistMatrix() {
    return {
      id: 'imx-nist-800-30-v1',
      name: "NIST SP 800-30 \u2014 Risk Impact Assessment",
      description: "Matrice d'impact bas\u00e9e sur le NIST Special Publication 800-30 Rev. 1 (Guide for Conducting Risk Assessments). R\u00e9f\u00e9rence internationale majeure utilis\u00e9e par les agences f\u00e9d\u00e9rales am\u00e9ricaines et adopt\u00e9e mondialement. 5 niveaux de s\u00e9v\u00e9rit\u00e9, 6 dimensions d'\u00e9valuation.",
      createdAt: '2026-03-06T00:00:00.000Z',
      updatedAt: '2026-03-06T00:00:00.000Z',
      levels: [
        { level: 1, label: 'Very Low', band: 'low', responsePriority: 5, responseCapability: "Gestion courante, aucune escalade requise." },
        { level: 2, label: 'Low', band: 'low', responsePriority: 4, responseCapability: "Gestion au niveau op\u00e9rationnel, notification au management de proximit\u00e9." },
        { level: 3, label: 'Moderate', band: 'medium', responsePriority: 3, responseCapability: "Escalade au management interm\u00e9diaire, activation des proc\u00e9dures de r\u00e9ponse." },
        { level: 4, label: 'High', band: 'medium', responsePriority: 2, responseCapability: "Escalade \u00e0 la direction, mobilisation de ressources d\u00e9di\u00e9es, task force si n\u00e9cessaire." },
        { level: 5, label: 'Very High', band: 'high', responsePriority: 1, responseCapability: "Escalade imm\u00e9diate \u00e0 la direction g\u00e9n\u00e9rale, activation du plan de gestion de crise." }
      ],
      dimensions: [
        { id: 'MISSION_BUSINESS', label: 'Mission / Business Impact', sourceLabel: "Impact sur les missions et objectifs m\u00e9tier de l'organisation", order: 1 },
        { id: 'DATA_CONFIDENTIALITY', label: 'Data Confidentiality', sourceLabel: "Perte de confidentialit\u00e9 des donn\u00e9es et informations", order: 2 },
        { id: 'DATA_INTEGRITY', label: 'Data Integrity', sourceLabel: "Alt\u00e9ration ou corruption des donn\u00e9es et informations", order: 3 },
        { id: 'SYSTEM_AVAILABILITY', label: 'System Availability', sourceLabel: "Interruption des syst\u00e8mes et services critiques", order: 4 },
        { id: 'FINANCIAL_LOSS', label: 'Financial Loss', sourceLabel: "Pertes financi\u00e8res directes et indirectes", order: 5 },
        { id: 'REGULATORY_LEGAL', label: 'Regulatory / Legal Compliance', sourceLabel: "Non-conformit\u00e9 r\u00e9glementaire et sanctions juridiques", order: 6 }
      ],
      cells: {
        MISSION_BUSINESS: {
          '1': "Impact n\u00e9gligeable sur les missions. Les objectifs m\u00e9tier restent pleinement atteignables sans d\u00e9gradation mesurable.",
          '2': "Impact limit\u00e9 et temporaire sur certaines missions secondaires. Les objectifs principaux restent atteignables avec des ajustements mineurs.",
          '3': "Impact significatif sur une ou plusieurs missions principales. D\u00e9gradation notable de la capacit\u00e9 \u00e0 atteindre les objectifs dans les d\u00e9lais pr\u00e9vus.",
          '4': "Impact majeur sur les missions critiques. Incapacit\u00e9 temporaire \u00e0 remplir les missions essentielles, n\u00e9cessitant une r\u00e9organisation significative.",
          '5': "Impact catastrophique rendant l'organisation incapable de remplir ses missions fondamentales. Remise en cause de la viabilit\u00e9 de l'organisation."
        },
        DATA_CONFIDENTIALITY: {
          '1': "Divulgation de donn\u00e9es publiques ou non sensibles. Aucun impact sur la vie priv\u00e9e ou les int\u00e9r\u00eats de l'organisation.",
          '2': "Divulgation limit\u00e9e de donn\u00e9es internes non critiques. Impact mineur sur la vie priv\u00e9e d'un petit nombre de personnes.",
          '3': "Divulgation de donn\u00e9es confidentielles affectant un p\u00e9rim\u00e8tre limit\u00e9. Risque de pr\u00e9judice pour les personnes concern\u00e9es (donn\u00e9es personnelles, secrets commerciaux).",
          '4': "Divulgation massive de donn\u00e9es hautement confidentielles (donn\u00e9es sensibles, secrets industriels, donn\u00e9es de sant\u00e9). Pr\u00e9judice grave pour de nombreuses personnes.",
          '5': "Compromission totale de donn\u00e9es classifi\u00e9es ou critiques pour la s\u00e9curit\u00e9 nationale. Cons\u00e9quences irr\u00e9versibles pour les personnes et l'organisation."
        },
        DATA_INTEGRITY: {
          '1': "Alt\u00e9ration n\u00e9gligeable de donn\u00e9es non critiques, facilement d\u00e9tectable et corrigeable. Aucune d\u00e9cision m\u00e9tier impact\u00e9e.",
          '2': "Alt\u00e9ration limit\u00e9e de donn\u00e9es op\u00e9rationnelles. Correction possible sans impact majeur sur les processus m\u00e9tier.",
          '3': "Alt\u00e9ration significative de donn\u00e9es m\u00e9tier importantes. Risque de d\u00e9cisions erron\u00e9es et de perturbation des processus pendant plusieurs jours.",
          '4': "Corruption massive de donn\u00e9es critiques (financi\u00e8res, m\u00e9dicales, contractuelles). Impact grave sur la fiabilit\u00e9 des syst\u00e8mes et la confiance des parties prenantes.",
          '5': "Destruction ou corruption irr\u00e9versible de donn\u00e9es vitales. Perte totale de confiance dans les syst\u00e8mes d'information. Impossibilit\u00e9 de reconstituer les donn\u00e9es."
        },
        SYSTEM_AVAILABILITY: {
          '1': "Interruption br\u00e8ve (< 1h) de syst\u00e8mes non critiques. Aucun impact sur les utilisateurs ou les services m\u00e9tier.",
          '2': "Interruption de quelques heures de syst\u00e8mes secondaires. Impact limit\u00e9, contournement possible via des proc\u00e9dures alternatives.",
          '3': "Interruption prolong\u00e9e (1-3 jours) de syst\u00e8mes importants. D\u00e9gradation significative des services, impact sur les utilisateurs internes et externes.",
          '4': "Interruption majeure (> 3 jours) de syst\u00e8mes critiques. Paralysie de processus m\u00e9tier essentiels, impact financier et r\u00e9putationnel important.",
          '5': "Indisponibilit\u00e9 totale et prolong\u00e9e de l'infrastructure critique. Arr\u00eat complet des activit\u00e9s, risque de non-reprise."
        },
        FINANCIAL_LOSS: {
          '1': "Pertes financi\u00e8res n\u00e9gligeables (< 10 000 \u20ac). Aucun impact sur le budget op\u00e9rationnel.",
          '2': "Pertes financi\u00e8res limit\u00e9es (10 000 - 100 000 \u20ac). Impact absorbable dans le budget courant sans r\u00e9allocation.",
          '3': "Pertes financi\u00e8res significatives (100 000 - 1 M\u20ac). R\u00e9allocation budg\u00e9taire n\u00e9cessaire, impact sur les investissements pr\u00e9vus.",
          '4': "Pertes financi\u00e8res majeures (1 - 10 M\u20ac). Impact sur la rentabilit\u00e9 annuelle, n\u00e9cessit\u00e9 de financement externe ou de restructuration.",
          '5': "Pertes financi\u00e8res catastrophiques (> 10 M\u20ac). Menace sur la viabilit\u00e9 financi\u00e8re de l'organisation, risque de cessation de paiement."
        },
        REGULATORY_LEGAL: {
          '1': "Aucun impact r\u00e9glementaire. Conformit\u00e9 maintenue \u00e0 toutes les obligations l\u00e9gales.",
          '2': "Non-conformit\u00e9 mineure d\u00e9tect\u00e9e en interne. Correction possible avant notification aux autorit\u00e9s. Risque limit\u00e9 de sanction.",
          '3': "Non-conformit\u00e9 significative n\u00e9cessitant une notification aux autorit\u00e9s (ex: notification RGPD dans les 72h). Risque d'avertissement ou de mise en demeure.",
          '4': "Violation grave des obligations r\u00e9glementaires (NIS2, RGPD, sectorielles). Sanctions financi\u00e8res importantes (jusqu'\u00e0 2% du CA), injonctions de mise en conformit\u00e9.",
          '5': "Violation majeure avec sanctions maximales (jusqu'\u00e0 4% du CA mondial ou 20 M\u20ac RGPD / 10 M\u20ac NIS2). Poursuites p\u00e9nales possibles, interdiction temporaire d'activit\u00e9."
        }
      },
      meta: {
        calculation: {
          defaultAggregation: 'max',
          notes: "Agr\u00e9gation par maximum conform\u00e9ment aux recommandations NIST SP 800-30. L'impact global correspond au niveau le plus \u00e9lev\u00e9 parmi toutes les dimensions \u00e9valu\u00e9es."
        }
      }
    };
  }

  // ============================================================
  // SEED DATA — EBIOS RM / ISO 27005 (4 niveaux, 5 dimensions)
  // ============================================================
  function createEbiosMatrix() {
    return {
      id: 'imx-ebios-rm-v1',
      name: "EBIOS RM / ISO 27005 \u2014 Matrice d'impacts",
      description: "Matrice d'impact bas\u00e9e sur la m\u00e9thode EBIOS Risk Manager de l'ANSSI (Agence nationale de la s\u00e9curit\u00e9 des syst\u00e8mes d'information, France), align\u00e9e sur la norme ISO/CEI 27005:2022. R\u00e9f\u00e9rence europ\u00e9enne officielle pour l'analyse de risques cyber. 4 niveaux de gravit\u00e9, 5 dimensions d'\u00e9valuation.",
      createdAt: '2026-03-06T00:00:00.000Z',
      updatedAt: '2026-03-06T00:00:00.000Z',
      levels: [
        { level: 1, label: 'N\u00e9gligeable', band: 'low', responsePriority: 4, responseCapability: "L'organisation peut g\u00e9rer cet impact dans le cadre de ses activit\u00e9s courantes." },
        { level: 2, label: 'Limit\u00e9', band: 'low', responsePriority: 3, responseCapability: "L'organisation subit un pr\u00e9judice limit\u00e9 mais surmont\u00e9 sans difficult\u00e9 majeure." },
        { level: 3, label: 'Important', band: 'medium', responsePriority: 2, responseCapability: "L'organisation subit un pr\u00e9judice grave n\u00e9cessitant une mobilisation importante." },
        { level: 4, label: 'Critique', band: 'high', responsePriority: 1, responseCapability: "L'organisation ne peut pas surmonter ce pr\u00e9judice. Sa survie est menac\u00e9e." }
      ],
      dimensions: [
        { id: 'IMPACT_MISSIONS', label: 'Impacts sur les missions', sourceLabel: "Perturbation des activit\u00e9s op\u00e9rationnelles et des missions de l'organisation", order: 1 },
        { id: 'IMPACT_HUMAINS', label: 'Impacts humains', sourceLabel: "Cons\u00e9quences sur la s\u00e9curit\u00e9, la sant\u00e9 et les conditions des personnes", order: 2 },
        { id: 'IMPACT_FINANCIERS', label: 'Impacts financiers', sourceLabel: "Co\u00fbts directs, amendes, pertes de revenus et co\u00fbts de rem\u00e9diation", order: 3 },
        { id: 'IMPACT_JURIDIQUES', label: 'Impacts juridiques', sourceLabel: "Non-conformit\u00e9 l\u00e9gale, sanctions r\u00e9glementaires et litiges", order: 4 },
        { id: 'IMPACT_IMAGE', label: "Impacts sur l'image", sourceLabel: "Atteinte \u00e0 la r\u00e9putation et \u00e0 la confiance des parties prenantes", order: 5 }
      ],
      cells: {
        IMPACT_MISSIONS: {
          '1': "Perturbation mineure n'affectant pas les missions principales. Retards n\u00e9gligeables, facilement rattrapables.",
          '2': "D\u00e9gradation temporaire de certaines missions secondaires. Les missions principales sont maintenues mais avec une efficacit\u00e9 r\u00e9duite pendant quelques jours.",
          '3': "Perturbation grave des missions principales pendant une dur\u00e9e prolong\u00e9e. Incapacit\u00e9 partielle \u00e0 d\u00e9livrer les services essentiels aux b\u00e9n\u00e9ficiaires.",
          '4': "Arr\u00eat total ou quasi-total des missions de l'organisation. Incapacit\u00e9 durable \u00e0 assurer la continuit\u00e9 des services. Mise en p\u00e9ril de la p\u00e9rennit\u00e9 de l'organisation."
        },
        IMPACT_HUMAINS: {
          '1': "Aucun impact sur les personnes. D\u00e9sagr\u00e9ments mineurs facilement surmontables (perte de temps, contrari\u00e9t\u00e9).",
          '2': "Impacts limit\u00e9s sur les personnes : stress, inconfort, difficult\u00e9s administratives temporaires. Pas d'atteinte \u00e0 l'int\u00e9grit\u00e9 physique.",
          '3': "Impacts graves sur les personnes : atteinte \u00e0 la vie priv\u00e9e, pr\u00e9judice financier personnel, d\u00e9gradation des conditions de travail, risques psychosociaux significatifs.",
          '4': "Atteintes irr\u00e9versibles aux personnes : mise en danger de la vie, handicap permanent, cons\u00e9quences psychologiques durables, perte d'autonomie financi\u00e8re."
        },
        IMPACT_FINANCIERS: {
          '1': "Co\u00fbts n\u00e9gligeables absorb\u00e9s dans le budget de fonctionnement courant. Aucune r\u00e9allocation budg\u00e9taire n\u00e9cessaire.",
          '2': "Co\u00fbts limit\u00e9s n\u00e9cessitant une r\u00e9allocation budg\u00e9taire interne. Impact sur les marges mais pas sur la viabilit\u00e9. Amendes mineures possibles.",
          '3': "Co\u00fbts importants : pertes de revenus significatives, amendes substantielles, co\u00fbts de rem\u00e9diation \u00e9lev\u00e9s. Impact sur les r\u00e9sultats annuels et les investissements.",
          '4': "Co\u00fbts catastrophiques mena\u00e7ant la viabilit\u00e9 financi\u00e8re : amendes maximales (% du CA), pertes de contrats majeurs, co\u00fbts de reconstruction, cessation de paiement possible."
        },
        IMPACT_JURIDIQUES: {
          '1': "Aucune cons\u00e9quence juridique. L'organisation reste conforme \u00e0 l'ensemble de ses obligations l\u00e9gales et r\u00e9glementaires.",
          '2': "Non-conformit\u00e9 mineure identifi\u00e9e. Risque de rappel \u00e0 l'ordre ou d'avertissement par les autorit\u00e9s comp\u00e9tentes. Mise en conformit\u00e9 rapide possible.",
          '3': "Non-conformit\u00e9 av\u00e9r\u00e9e entra\u00eenant des sanctions : amendes administratives, injonctions de mise en conformit\u00e9, actions civiles en r\u00e9paration. Atteinte \u00e0 la r\u00e9putation juridique.",
          '4': "Violations graves entra\u00eenant des sanctions maximales : amendes p\u00e9nales, poursuites judiciaires, interdiction d'activit\u00e9, mise sous tutelle. Responsabilit\u00e9 personnelle des dirigeants engag\u00e9e."
        },
        IMPACT_IMAGE: {
          '1': "Aucun impact sur l'image. L'incident reste interne et n'affecte pas la perception des parties prenantes.",
          '2': "Atteinte limit\u00e9e et temporaire \u00e0 la r\u00e9putation. Quelques critiques internes ou de partenaires proches, sans couverture m\u00e9diatique.",
          '3': "Atteinte significative \u00e0 la r\u00e9putation avec couverture m\u00e9diatique locale ou sectorielle. Perte de confiance de certains partenaires et clients. N\u00e9cessit\u00e9 d'une communication de crise.",
          '4': "Atteinte majeure et durable \u00e0 la r\u00e9putation avec couverture m\u00e9diatique nationale/internationale. Perte de confiance g\u00e9n\u00e9ralis\u00e9e des parties prenantes. Remise en cause du leadership. Impact irr\u00e9versible sur l'image de marque."
        }
      },
      meta: {
        calculation: {
          defaultAggregation: 'max',
          notes: "Conform\u00e9ment \u00e0 la m\u00e9thode EBIOS RM, la gravit\u00e9 globale correspond au niveau maximal observ\u00e9 parmi les 5 dimensions d'impact."
        }
      }
    };
  }

  // ============================================================
  // NORMALISATION
  // ============================================================
  function normalizeImpactMatrices(raw) {
    if (!raw || typeof raw !== 'object') {
      raw = { schemaVersion: 1, activeMatrixId: null, matrices: [] };
    }
    if (!raw.schemaVersion) raw.schemaVersion = 1;
    if (!Array.isArray(raw.matrices)) raw.matrices = [];
    // Seed si aucune matrice
    if (raw.matrices.length === 0) {
      raw.matrices.push(createDefaultMatrix());
      raw.matrices.push(createNistMatrix());
      raw.matrices.push(createEbiosMatrix());
    }
    // Vérifier activeMatrixId
    if (!raw.activeMatrixId || !raw.matrices.find(function(m) { return m.id === raw.activeMatrixId; })) {
      raw.activeMatrixId = raw.matrices[0].id;
    }
    // Normaliser chaque matrice
    raw.matrices.forEach(function(m) {
      if (!m.id) m.id = _id();
      if (!m.name) m.name = 'Sans nom';
      if (!m.description) m.description = '';
      if (!m.createdAt) m.createdAt = _now();
      if (!m.updatedAt) m.updatedAt = _now();
      if (!Array.isArray(m.levels)) m.levels = [{ level: 1, label: 'Niveau 1', band: 'low', responsePriority: 1, responseCapability: '' }];
      if (!Array.isArray(m.dimensions)) m.dimensions = [];
      if (!m.cells || typeof m.cells !== 'object') m.cells = {};
      if (!m.meta) m.meta = { calculation: { defaultAggregation: 'max', notes: '' } };
      if (!m.meta.calculation) m.meta.calculation = { defaultAggregation: 'max', notes: '' };
      // Compléter cells manquantes
      m.dimensions.forEach(function(dim) {
        if (!m.cells[dim.id]) m.cells[dim.id] = {};
        m.levels.forEach(function(lv) {
          if (m.cells[dim.id][String(lv.level)] === undefined) {
            m.cells[dim.id][String(lv.level)] = '';
          }
        });
      });
    });
    return raw;
  }
  window.normalizeImpactMatrices = normalizeImpactMatrices;

  // ============================================================
  // STATE
  // ============================================================
  var _selectedId = null;
  var _boundContainer = null;

  // ============================================================
  // DATA ACCESS
  // ============================================================
  function ensureInitialized() {
    if (!window.appData) return;
    if (!window.appData.impactMatrices) {
      window.appData.impactMatrices = normalizeImpactMatrices(null);
      _save();
    } else {
      window.appData.impactMatrices = normalizeImpactMatrices(window.appData.impactMatrices);
    }
  }

  function getStore() {
    return window.appData && window.appData.impactMatrices ? window.appData.impactMatrices : null;
  }

  function getActiveMatrix() {
    var store = getStore();
    if (!store) return null;
    return store.matrices.find(function(m) { return m.id === store.activeMatrixId; }) || store.matrices[0] || null;
  }

  function findMatrix(id) {
    var store = getStore();
    if (!store) return null;
    return store.matrices.find(function(m) { return m.id === id; }) || null;
  }

  // ============================================================
  // SAVE (debounced)
  // ============================================================
  var _saveTimer = null;
  function _save() {
    if (typeof window.saveData === 'function') window.saveData();
  }
  function debouncedSave() {
    if (_saveTimer) clearTimeout(_saveTimer);
    _saveTimer = setTimeout(function() {
      _save();
    }, 400);
  }

  // ============================================================
  // CRUD
  // ============================================================
  function createMatrix() {
    if (typeof window.ensureEditMode === 'function' && !window.ensureEditMode()) return;
    var store = getStore();
    if (!store) return;
    var m = {
      id: _id(),
      name: _t('impactMatrices.newMatrixName', 'Nouvelle matrice'),
      description: '',
      createdAt: _now(),
      updatedAt: _now(),
      levels: [
        { level: 1, label: 'Faible', band: 'low', responsePriority: 3, responseCapability: '' },
        { level: 2, label: 'Moyen', band: 'medium', responsePriority: 2, responseCapability: '' },
        { level: 3, label: 'Fort', band: 'high', responsePriority: 1, responseCapability: '' }
      ],
      dimensions: [
        { id: 'DIM_1', label: 'Dimension 1', sourceLabel: '', order: 1 },
        { id: 'DIM_2', label: 'Dimension 2', sourceLabel: '', order: 2 }
      ],
      cells: {
        DIM_1: { '1': '', '2': '', '3': '' },
        DIM_2: { '1': '', '2': '', '3': '' }
      },
      meta: { calculation: { defaultAggregation: 'max', notes: '' } }
    };
    store.matrices.push(m);
    _selectedId = m.id;
    store.matrices[store.matrices.length - 1].updatedAt = _now();
    _save();
    renderModule();
    if (typeof window.showToast2 === 'function') {
      window.showToast2(_t('impactMatrices.toastCreated', 'Matrice cr\u00e9\u00e9e'), 'success');
    }
  }

  function duplicateMatrix(id) {
    if (typeof window.ensureEditMode === 'function' && !window.ensureEditMode()) return;
    var store = getStore();
    if (!store) return;
    var src = findMatrix(id);
    if (!src) return;
    var copy = _deepClone(src);
    copy.id = _id();
    copy.name = src.name + ' (' + _t('impactMatrices.copy', 'copie') + ')';
    copy.createdAt = _now();
    copy.updatedAt = _now();
    store.matrices.push(copy);
    _selectedId = copy.id;
    _save();
    renderModule();
    if (typeof window.showToast2 === 'function') {
      window.showToast2(_t('impactMatrices.toastDuplicated', 'Matrice dupliqu\u00e9e'), 'success');
    }
  }

  function deleteMatrix(id) {
    if (typeof window.ensureEditMode === 'function' && !window.ensureEditMode()) return;
    var store = getStore();
    if (!store) return;
    if (store.matrices.length <= 1) {
      if (typeof window.showToast2 === 'function') {
        window.showToast2(_t('impactMatrices.toastCannotDeleteLast', 'Impossible de supprimer la derni\u00e8re matrice'), 'warning');
      }
      return;
    }
    var m = findMatrix(id);
    if (!m) return;
    if (!confirm(_t('impactMatrices.confirmDelete', 'Supprimer cette matrice ?') + '\n' + m.name)) return;
    store.matrices = store.matrices.filter(function(mx) { return mx.id !== id; });
    if (store.activeMatrixId === id) {
      store.activeMatrixId = store.matrices[0].id;
    }
    if (_selectedId === id) {
      _selectedId = store.matrices[0].id;
    }
    // FIX-CASCADE-PERSIST (2026-06-29) : \u00e9viter les r\u00e9f\u00e9rences orphelines \u2014 les analyses de
    // risque qui pointaient cette matrice retombent sur la matrice active (impactMatrixId=null),
    // et l'onglet risk-analysis doit \u00eatre persist\u00e9 en mode serveur (sinon l'impactMatrixId
    // orphelin r\u00e9appara\u00eet au prochain pull).
    var _raCleaned = false;
    if (window.appData && Array.isArray(window.appData.riskAnalyses)) {
      window.appData.riskAnalyses.forEach(function(a) {
        if (a && a.impactMatrixId === id) { a.impactMatrixId = null; _raCleaned = true; }
      });
    }
    _save();
    // Persiste impactMatrices (au cas où l'onglet courant ne serait pas 'impact-matrices' :
    // saveData ne pousserait alors pas la suppression) + risk-analysis si des analyses ont été
    // nettoyées. Le helper saute l'onglet courant (déjà poussé par saveData) → pas de double push.
    if (typeof window._csiPersistAffectedTabs === 'function') {
      var _mxKeys = ['impactMatrices'];
      if (_raCleaned) _mxKeys.push('riskAnalyses');
      window._csiPersistAffectedTabs(_mxKeys);
    }
    renderModule();
    if (typeof window.showToast2 === 'function') {
      window.showToast2(_t('impactMatrices.toastDeleted', 'Matrice supprim\u00e9e'), 'success');
    }
  }

  function activateMatrix(id) {
    if (typeof window.ensureEditMode === 'function' && !window.ensureEditMode()) return;
    var store = getStore();
    if (!store) return;
    store.activeMatrixId = id;
    _save();
    renderModule();
    if (typeof window.showToast2 === 'function') {
      window.showToast2(_t('impactMatrices.toastActivated', 'Matrice activ\u00e9e'), 'info');
    }
  }

  // ============================================================
  // ENTRY POINT
  // ============================================================
  function renderModule() {
    ensureInitialized();
    var container = document.getElementById('impactMatricesContent');
    if (!container) return;
    var store = getStore();
    if (!store) return;
    // Default selection
    if (!_selectedId || !findMatrix(_selectedId)) {
      _selectedId = store.activeMatrixId || (store.matrices[0] && store.matrices[0].id);
    }
    renderLayout(container, store);
    if (typeof window.refreshReadOnlyLocks === 'function') window.refreshReadOnlyLocks();
  }
  window.renderImpactMatricesModule = renderModule;

  // ============================================================
  // RENDER LAYOUT
  // ============================================================
  function renderLayout(container, store) {
    var html = '<div class="imx-layout">';
    html += '<div class="imx-list-panel">';
    html += renderListPanel(store);
    html += '</div>';
    html += '<div class="imx-editor-panel">';
    html += renderEditorPanel(store);
    html += '</div>';
    html += '</div>';
    container.innerHTML = html;
    // Bind events only once per container to avoid listener accumulation
    if (_boundContainer !== container) {
      _boundContainer = container;
      bindEvents(container);
    }
  }

  // ============================================================
  // RENDER LIST PANEL
  // ============================================================
  function renderListPanel(store) {
    var html = '';
    html += '<div class="imx-toolbar">';
    html += '<span class="imx-toolbar__title">' + _esc(_t('impactMatrices.listTitle', 'Matrices')) + '</span>';
    html += '<button class="btn btn--primary btn--sm" data-imx-action="create" data-readonly-lock="true">';
    html += '<i class="fas fa-plus"></i> ' + _esc(_t('impactMatrices.newMatrix', 'Nouvelle'));
    html += '</button>';
    html += '</div>';

    store.matrices.forEach(function(m) {
      var isSelected = m.id === _selectedId;
      var isActive = m.id === store.activeMatrixId;
      var dateStr = m.updatedAt ? new Date(m.updatedAt).toLocaleDateString() : '';
      html += '<div class="imx-card' + (isSelected ? ' imx-card--selected' : '') + '" data-imx-select="' + _esc(m.id) + '">';
      if (isActive) {
        html += '<span class="imx-badge-active">' + _esc(_t('impactMatrices.active', 'Active')) + '</span>';
      }
      html += '<div class="imx-card__name">' + _esc(m.name) + '</div>';
      html += '<div class="imx-card__date">' + _esc(dateStr) + '</div>';
      html += '<div class="imx-card__stats">' + m.levels.length + ' ' + _esc(_t('impactMatrices.cardLevels', 'niveaux')) + ' &middot; ' + m.dimensions.length + ' ' + _esc(_t('impactMatrices.cardDimensions', 'dimensions')) + '</div>';
      html += '<div class="imx-card__colorbar">';
      m.levels.forEach(function(lv) {
        html += '<div class="imx-card__colorbar-seg" data-severity="' + lv.level + '"></div>';
      });
      html += '</div>';
      html += '<div class="imx-card__actions">';
      if (!isActive) {
        html += '<button class="btn btn--sm" data-imx-action="activate" data-imx-id="' + _esc(m.id) + '" data-readonly-lock="true" title="' + _esc(_t('impactMatrices.activate', 'Activer')) + '">';
        html += '<i class="fas fa-check-circle"></i>';
        html += '</button>';
      }
      html += '<button class="btn btn--sm" data-imx-action="duplicate" data-imx-id="' + _esc(m.id) + '" data-readonly-lock="true" title="' + _esc(_t('impactMatrices.duplicate', 'Dupliquer')) + '">';
      html += '<i class="fas fa-copy"></i>';
      html += '</button>';
      html += '<button class="btn btn--sm btn--danger" data-imx-action="delete" data-imx-id="' + _esc(m.id) + '" data-readonly-lock="true" title="' + _esc(_t('impactMatrices.delete', 'Supprimer')) + '">';
      html += '<i class="fas fa-trash"></i>';
      html += '</button>';
      html += '</div>';
      html += '</div>';
    });

    return html;
  }

  // ============================================================
  // RENDER EDITOR PANEL
  // ============================================================
  function renderEditorPanel(store) {
    var m = findMatrix(_selectedId);
    if (!m) {
      return '<div class="imx-empty"><i class="fas fa-table-cells"></i><p>' +
        _esc(_t('impactMatrices.selectOrCreate', 'S\u00e9lectionnez ou cr\u00e9ez une matrice')) + '</p></div>';
    }

    var html = '';

    // --- Name & Description ---
    html += '<div class="imx-editor-section imx-editor-section--collapsed" data-imx-section="general">';
    html += '<div class="imx-editor-section__title" data-imx-action="toggleSection" data-imx-section-target="general">';
    html += '<span><i class="fas fa-chevron-down imx-chevron"></i>' + _esc(_t('impactMatrices.generalInfo', 'Informations g\u00e9n\u00e9rales')) + '</span>';
    html += '</div>';
    html += '<div class="imx-editor-section__body">';
    html += '<div class="imx-field">';
    html += '<label>' + _esc(_t('impactMatrices.matrixName', 'Nom')) + '</label>';
    html += '<input type="text" data-imx-field="name" value="' + _esc(m.name) + '" data-readonly-lock="true">';
    html += '</div>';
    html += '<div class="imx-field">';
    html += '<label>' + _esc(_t('impactMatrices.matrixDescription', 'Description')) + '</label>';
    html += '<textarea data-imx-field="description" data-readonly-lock="true" rows="2">' + _esc(m.description) + '</textarea>';
    html += '</div>';
    html += '</div>';
    html += '</div>';

    // --- Levels ---
    html += '<div class="imx-editor-section imx-editor-section--collapsed" data-imx-section="levels">';
    html += '<div class="imx-editor-section__title" data-imx-action="toggleSection" data-imx-section-target="levels">';
    html += '<span><i class="fas fa-chevron-down imx-chevron"></i>' + _esc(_t('impactMatrices.levelsTitle', 'Niveaux de gravit\u00e9')) + '</span>';
    html += '<button class="btn btn--primary btn--sm" data-imx-action="addLevel" data-readonly-lock="true">';
    html += '<i class="fas fa-plus"></i> ' + _esc(_t('impactMatrices.addLevel', 'Ajouter'));
    html += '</button>';
    html += '</div>';
    html += '<div class="imx-editor-section__body">';
    html += '<table class="imx-levels-table">';
    html += '<thead><tr>';
    html += '<th>#</th>';
    html += '<th>' + _esc(_t('impactMatrices.levelLabel', 'Libell\u00e9')) + '</th>';
    html += '<th>' + _esc(_t('impactMatrices.levelBand', 'Bande')) + '</th>';
    html += '<th>' + _esc(_t('impactMatrices.levelPriority', 'Priorit\u00e9')) + '</th>';
    html += '<th>' + _esc(_t('impactMatrices.levelCapability', 'Capacit\u00e9 de r\u00e9ponse')) + '</th>';
    html += '<th class="imx-col-actions"></th>';
    html += '</tr></thead><tbody>';
    m.levels.forEach(function(lv, idx) {
      html += '<tr data-severity="' + lv.level + '">';
      html += '<td class="imx-level-num"><span class="imx-level-dot" data-severity="' + lv.level + '"></span>' + lv.level + '</td>';
      html += '<td><input type="text" data-imx-level="' + idx + '" data-imx-lfield="label" value="' + _esc(lv.label) + '" data-readonly-lock="true"></td>';
      html += '<td><select data-imx-level="' + idx + '" data-imx-lfield="band" data-readonly-lock="true">';
      ['low', 'medium', 'high'].forEach(function(b) {
        html += '<option value="' + b + '"' + (lv.band === b ? ' selected' : '') + '>' + b + '</option>';
      });
      html += '</select></td>';
      html += '<td><input type="number" data-imx-level="' + idx + '" data-imx-lfield="responsePriority" value="' + (lv.responsePriority || 1) + '" min="1" data-readonly-lock="true" style="width:60px"></td>';
      html += '<td><textarea data-imx-level="' + idx + '" data-imx-lfield="responseCapability" data-readonly-lock="true" rows="2">' + _esc(lv.responseCapability || '') + '</textarea></td>';
      html += '<td class="imx-col-actions">';
      if (idx > 0) {
        html += '<button class="btn btn--sm" data-imx-action="moveLevel" data-imx-dir="up" data-imx-idx="' + idx + '" data-readonly-lock="true" title="' + _esc(_t('common.moveUp', 'Monter')) + '"><i class="fas fa-arrow-up"></i></button>';
      }
      if (idx < m.levels.length - 1) {
        html += '<button class="btn btn--sm" data-imx-action="moveLevel" data-imx-dir="down" data-imx-idx="' + idx + '" data-readonly-lock="true" title="' + _esc(_t('common.moveDown', 'Descendre')) + '"><i class="fas fa-arrow-down"></i></button>';
      }
      if (m.levels.length > 1) {
        html += '<button class="btn btn--sm btn--danger" data-imx-action="removeLevel" data-imx-idx="' + idx + '" data-readonly-lock="true" title="' + _esc(_t('common.delete', 'Supprimer')) + '"><i class="fas fa-times"></i></button>';
      }
      html += '</td>';
      html += '</tr>';
    });
    html += '</tbody></table>';
    html += '</div>';
    html += '</div>';

    // --- Dimensions ---
    html += '<div class="imx-editor-section imx-editor-section--collapsed" data-imx-section="dimensions">';
    html += '<div class="imx-editor-section__title" data-imx-action="toggleSection" data-imx-section-target="dimensions">';
    html += '<span><i class="fas fa-chevron-down imx-chevron"></i>' + _esc(_t('impactMatrices.dimensionsTitle', 'Dimensions')) + '</span>';
    html += '<button class="btn btn--primary btn--sm" data-imx-action="addDimension" data-readonly-lock="true">';
    html += '<i class="fas fa-plus"></i> ' + _esc(_t('impactMatrices.addDimension', 'Ajouter'));
    html += '</button>';
    html += '</div>';
    html += '<div class="imx-editor-section__body">';
    html += '<div class="imx-dim-list">';
    m.dimensions.forEach(function(dim, idx) {
      html += '<div class="imx-dim-chip">';
      html += '<input type="text" data-imx-dim="' + idx + '" data-imx-dfield="label" value="' + _esc(dim.label) + '" data-readonly-lock="true">';
      if (m.dimensions.length > 1) {
        html += '<span class="imx-dim-remove" data-imx-action="removeDimension" data-imx-idx="' + idx + '" data-readonly-lock="true" title="' + _esc(_t('common.delete', 'Supprimer')) + '"><i class="fas fa-times"></i></span>';
      }
      html += '</div>';
    });
    html += '</div>';
    html += '</div>';
    html += '</div>';

    // --- Impact Grid ---
    html += '<div class="imx-editor-section" data-imx-section="grid">';
    html += '<div class="imx-editor-section__title" data-imx-action="toggleSection" data-imx-section-target="grid">';
    html += '<span><i class="fas fa-chevron-down imx-chevron"></i>' + _esc(_t('impactMatrices.gridTitle', 'Grille d\'impacts')) + '</span>';
    html += '</div>';
    html += '<div class="imx-editor-section__body">';
    html += '<div class="imx-grid-wrapper">';
    html += '<table class="imx-grid">';
    html += '<thead><tr>';
    html += '<th class="imx-grid__dim-header">' + _esc(_t('impactMatrices.dimension', 'Dimension')) + '</th>';
    m.levels.forEach(function(lv) {
      html += '<th data-severity="' + lv.level + '">' + _esc(lv.label) + ' (' + lv.level + ')</th>';
    });
    html += '</tr></thead><tbody>';
    m.dimensions.forEach(function(dim) {
      html += '<tr>';
      html += '<td class="imx-grid__dim-label">' + _esc(dim.label) + '</td>';
      m.levels.forEach(function(lv) {
        var cellVal = (m.cells[dim.id] && m.cells[dim.id][String(lv.level)]) || '';
        html += '<td data-severity="' + lv.level + '"><textarea data-imx-cell-dim="' + _esc(dim.id) + '" data-imx-cell-level="' + lv.level + '" data-readonly-lock="true">' + _esc(cellVal) + '</textarea></td>';
      });
      html += '</tr>';
    });
    html += '</tbody></table>';
    html += '</div>';
    html += '</div>';
    html += '</div>';

    // --- Aggregation ---
    html += '<div class="imx-editor-section imx-editor-section--collapsed" data-imx-section="aggregation">';
    html += '<div class="imx-editor-section__title" data-imx-action="toggleSection" data-imx-section-target="aggregation">';
    html += '<span><i class="fas fa-chevron-down imx-chevron"></i>' + _esc(_t('impactMatrices.aggregationTitle', 'M\u00e9thode d\'agr\u00e9gation')) + '</span>';
    html += '</div>';
    html += '<div class="imx-editor-section__body">';
    html += '<div class="imx-aggregation">';
    html += '<label>' + _esc(_t('impactMatrices.aggregationMethod', 'Calcul de l\'impact global')) + '</label>';
    html += '<select data-imx-field="aggregation" data-readonly-lock="true">';
    var agg = m.meta.calculation.defaultAggregation || 'max';
    ['max', 'max+override', 'weighted'].forEach(function(opt) {
      html += '<option value="' + opt + '"' + (agg === opt ? ' selected' : '') + '>' + opt + '</option>';
    });
    html += '</select>';
    html += '</div>';
    html += '<div class="imx-field" style="margin-top:var(--spacing-sm,0.5rem)">';
    html += '<label>' + _esc(_t('impactMatrices.aggregationNotes', 'Notes')) + '</label>';
    html += '<textarea data-imx-field="aggregationNotes" data-readonly-lock="true" rows="2">' + _esc(m.meta.calculation.notes || '') + '</textarea>';
    html += '</div>';
    html += '</div>';
    html += '</div>';

    return html;
  }

  // ============================================================
  // EVENT BINDING
  // ============================================================
  function bindEvents(container) {
    // Delegation
    container.addEventListener('click', function(e) {
      var target = e.target.closest('[data-imx-action]') || e.target.closest('[data-imx-select]');
      if (!target) return;

      // Card selection
      if (target.dataset.imxSelect) {
        _selectedId = target.dataset.imxSelect;
        renderModule();
        return;
      }

      var action = target.dataset.imxAction;
      var id = target.dataset.imxId;
      var idx = target.dataset.imxIdx !== undefined ? parseInt(target.dataset.imxIdx, 10) : -1;
      var dir = target.dataset.imxDir;

      switch (action) {
        case 'create': createMatrix(); break;
        case 'duplicate': duplicateMatrix(id); break;
        case 'delete': deleteMatrix(id); break;
        case 'activate': activateMatrix(id); break;
        case 'addLevel': addLevel(); break;
        case 'removeLevel': removeLevel(idx); break;
        case 'moveLevel': moveLevel(idx, dir); break;
        case 'addDimension': addDimension(); break;
        case 'removeDimension': removeDimension(idx); break;
        case 'toggleSection':
          var sectionTarget = target.dataset.imxSectionTarget;
          var section = container.querySelector('[data-imx-section="' + sectionTarget + '"]');
          if (section) section.classList.toggle('imx-editor-section--collapsed');
          break;
      }
    });

    // Input changes (debounced)
    container.addEventListener('input', function(e) {
      var el = e.target;
      var m = findMatrix(_selectedId);
      if (!m) return;

      // Top-level fields
      if (el.dataset.imxField) {
        handleFieldChange(m, el);
        return;
      }

      // Level fields
      if (el.dataset.imxLevel !== undefined && el.dataset.imxLfield) {
        handleLevelChange(m, el);
        return;
      }

      // Dimension fields
      if (el.dataset.imxDim !== undefined && el.dataset.imxDfield) {
        handleDimensionChange(m, el);
        return;
      }

      // Cell fields
      if (el.dataset.imxCellDim && el.dataset.imxCellLevel) {
        handleCellChange(m, el);
        return;
      }
    });

    // Select changes
    container.addEventListener('change', function(e) {
      var el = e.target;
      var m = findMatrix(_selectedId);
      if (!m) return;

      if (el.dataset.imxField) {
        handleFieldChange(m, el);
        return;
      }
      if (el.dataset.imxLevel !== undefined && el.dataset.imxLfield) {
        handleLevelChange(m, el);
        return;
      }
    });
  }

  // ============================================================
  // FIELD HANDLERS
  // ============================================================
  function handleFieldChange(m, el) {
    var field = el.dataset.imxField;
    if (field === 'name') {
      m.name = el.value;
    } else if (field === 'description') {
      m.description = el.value;
    } else if (field === 'aggregation') {
      m.meta.calculation.defaultAggregation = el.value;
    } else if (field === 'aggregationNotes') {
      m.meta.calculation.notes = el.value;
    }
    m.updatedAt = _now();
    debouncedSave();
  }

  function handleLevelChange(m, el) {
    var idx = parseInt(el.dataset.imxLevel, 10);
    var field = el.dataset.imxLfield;
    if (!m.levels[idx]) return;
    if (field === 'responsePriority') {
      m.levels[idx][field] = parseInt(el.value, 10) || 1;
    } else {
      m.levels[idx][field] = el.value;
    }
    m.updatedAt = _now();
    debouncedSave();
  }

  function handleDimensionChange(m, el) {
    var idx = parseInt(el.dataset.imxDim, 10);
    var field = el.dataset.imxDfield;
    if (!m.dimensions[idx]) return;
    m.dimensions[idx][field] = el.value;
    m.updatedAt = _now();
    debouncedSave();
  }

  function handleCellChange(m, el) {
    var dimId = el.dataset.imxCellDim;
    var level = el.dataset.imxCellLevel;
    if (!m.cells[dimId]) m.cells[dimId] = {};
    m.cells[dimId][level] = el.value;
    m.updatedAt = _now();
    debouncedSave();
  }

  // ============================================================
  // LEVEL / DIMENSION OPERATIONS
  // ============================================================
  function addLevel() {
    if (typeof window.ensureEditMode === 'function' && !window.ensureEditMode()) return;
    var m = findMatrix(_selectedId);
    if (!m) return;
    // (audit 2026-07-03, BLOQUANT) le moteur de cotation des risques est une grille FIXE 5x5 :
    // au-delà de 5 niveaux, l'impact (max des niveaux sélectionnés) sort de l'échelle 1-5 et
    // la cotation retombe en vert (faux-négatif). On plafonne à 5 niveaux, aligné sur l'échelle.
    if (m.levels.length >= 5) {
      if (typeof window.showToast2 === 'function') {
        window.showToast2(_t('impactMatrices.maxLevels', 'Maximum 5 niveaux (aligné sur l’échelle de cotation des risques 1-5).'), 'warning');
      }
      return;
    }
    var newNum = m.levels.length + 1;
    m.levels.push({
      level: newNum,
      label: _t('impactMatrices.newLevelLabel', 'Niveau') + ' ' + newNum,
      band: 'medium',
      responsePriority: 1,
      responseCapability: ''
    });
    // Add empty cells for new level in each dimension
    m.dimensions.forEach(function(dim) {
      if (!m.cells[dim.id]) m.cells[dim.id] = {};
      m.cells[dim.id][String(newNum)] = '';
    });
    m.updatedAt = _now();
    _save();
    renderModule();
  }

  function removeLevel(idx) {
    if (typeof window.ensureEditMode === 'function' && !window.ensureEditMode()) return;
    var m = findMatrix(_selectedId);
    if (!m || m.levels.length <= 1) return;
    var removedLevel = m.levels[idx].level;
    m.levels.splice(idx, 1);
    // Renumber levels
    m.levels.forEach(function(lv, i) { lv.level = i + 1; });
    // Rebuild cells: remove old level key, renumber
    m.dimensions.forEach(function(dim) {
      var oldCells = m.cells[dim.id] || {};
      var origKeys = Object.keys(oldCells).sort(function(a, b) { return window.SortUtils.numeric(parseInt(a), parseInt(b)); });
      origKeys = origKeys.filter(function(k) { return k !== String(removedLevel); });
      var newCells = {};
      m.levels.forEach(function(lv, i) {
        newCells[String(lv.level)] = origKeys[i] !== undefined ? (oldCells[origKeys[i]] || '') : '';
      });
      m.cells[dim.id] = newCells;
    });
    m.updatedAt = _now();
    _save();
    renderModule();
  }

  function moveLevel(idx, dir) {
    if (typeof window.ensureEditMode === 'function' && !window.ensureEditMode()) return;
    var m = findMatrix(_selectedId);
    if (!m) return;
    var targetIdx = dir === 'up' ? idx - 1 : idx + 1;
    if (targetIdx < 0 || targetIdx >= m.levels.length) return;
    // Collect cell values in current level order before swap
    var cellsByPos = {};
    m.dimensions.forEach(function(dim) {
      cellsByPos[dim.id] = m.levels.map(function(lv) {
        return (m.cells[dim.id] && m.cells[dim.id][String(lv.level)]) || '';
      });
    });
    // Swap level objects in the array
    var tmp = m.levels[idx];
    m.levels[idx] = m.levels[targetIdx];
    m.levels[targetIdx] = tmp;
    // Swap corresponding cell values
    m.dimensions.forEach(function(dim) {
      var arr = cellsByPos[dim.id];
      var tmpVal = arr[idx];
      arr[idx] = arr[targetIdx];
      arr[targetIdx] = tmpVal;
    });
    // Renumber levels sequentially and rebuild cell keys
    m.levels.forEach(function(lv, i) { lv.level = i + 1; });
    m.dimensions.forEach(function(dim) {
      var newCells = {};
      m.levels.forEach(function(lv, i) {
        newCells[String(lv.level)] = cellsByPos[dim.id][i];
      });
      m.cells[dim.id] = newCells;
    });
    m.updatedAt = _now();
    _save();
    renderModule();
  }

  function addDimension() {
    if (typeof window.ensureEditMode === 'function' && !window.ensureEditMode()) return;
    var m = findMatrix(_selectedId);
    if (!m) return;
    var n = m.dimensions.length + 1;
    var dimId = 'DIM_' + Date.now();
    m.dimensions.push({
      id: dimId,
      label: _t('impactMatrices.newDimensionLabel', 'Dimension') + ' ' + n,
      sourceLabel: '',
      order: n
    });
    m.cells[dimId] = {};
    m.levels.forEach(function(lv) {
      m.cells[dimId][String(lv.level)] = '';
    });
    m.updatedAt = _now();
    _save();
    renderModule();
  }

  function removeDimension(idx) {
    if (typeof window.ensureEditMode === 'function' && !window.ensureEditMode()) return;
    var m = findMatrix(_selectedId);
    if (!m || m.dimensions.length <= 1) return;
    var dimId = m.dimensions[idx].id;
    m.dimensions.splice(idx, 1);
    delete m.cells[dimId];
    // Reorder
    m.dimensions.forEach(function(d, i) { d.order = i + 1; });
    m.updatedAt = _now();
    _save();
    renderModule();
  }

  // ============================================================
  // PUBLIC API
  // ============================================================
  window.CSI_ImpactMatrices = {
    getActiveMatrix: getActiveMatrix,
    getMatrixById: findMatrix,
    listMatrices: function() {
      var store = getStore();
      return store ? store.matrices : [];
    },
    ensureInitialized: ensureInitialized,
    normalize: normalizeImpactMatrices,
    createNistMatrix: createNistMatrix,
    createEbiosMatrix: createEbiosMatrix
  };

})();
