/**
 * Cyber-Assistant — Entity UI (multi-entités, mode serveur)
 * ---------------------------------------------------------------------------
 * Interface de la feature multi-entités : carte Réglages (admin — activation,
 * gestion des sous-entités, matrice de droits par profil) et adaptation de
 * l'espace d'une sous-entité (bandeau, nav masquée, lecture seule par section).
 *
 * Module épais : le gros de la logique UI vit ici ; bundle.js n'a que des
 * accroches fines (=== CSI-ME ===). Inerte en mode local (file://).
 * ES5 strict. DOM pur (createElement/textContent) — aucun innerHTML (anti-XSS).
 *
 * PRÉSENTATION : 100 % via le design system de l'app (classes .btn, .modal, .table,
 * .status-badge + classes .csi-... définies dans style.css qui utilisent les vrais
 * tokens de couleur/espacement/rayon du :root, d'où un thème clair/sombre automatique.
 * Aucune couleur en dur, aucun style de présentation inline.
 */
(function() {
  'use strict';

  var API = './data/users.php';
  var SYNC_API = './data/sync.php';   // group-activity / consolidated-read vivent sur sync.php
  var SECTION_LABELS = {
    'nis2-program': 'Budget / Programme', 'kpis': 'Indicateurs', 'documents': 'Documents',
    'stakeholders': 'Parties prenantes', 'managementReviews': 'Revues de direction',
    'docReview': 'Revue documentaire', 'nis2-socle': 'Socle NIS2', 'project-risks': 'Risques projets',
    'risk-analysis': 'Analyses de risques', 'app-config': 'Configuration (langue, référentiel)'
  };
  var CLUSTER = ['risks', 'threats-section', 'actions', 'soa', 'controls', 'audits', 'nonconformities', 'critical-assets'];
  var FREE_SECTIONS = ['kpis', 'documents', 'stakeholders', 'managementReviews', 'docReview',
    'nis2-program', 'nis2-socle', 'project-risks', 'risk-analysis', 'app-config'];

  // ── i18n : route toutes les chaînes visibles (FR/EN, fallback FR). ──────────
  function t(key, fr, params) {
    if (window.t) return window.t(key, params || fr);
    var s = fr || key;
    if (params && typeof params === 'object') s = s.replace(/\{\{(\w+)\}\}/g, function(m, p) { return params[p] != null ? params[p] : m; });
    return s;
  }

  // ── Micro-DOM ─────────────────────────────────────────────────────────────
  function el(tag, opts) {
    var e = document.createElement(tag); opts = opts || {};
    if (opts.cls) e.className = opts.cls;
    if (opts.css) e.style.cssText = opts.css;
    if (opts.text != null) e.textContent = opts.text;
    if (opts.type) e.type = opts.type;
    if (opts.placeholder) e.placeholder = opts.placeholder;
    if (opts.attrs) { for (var k in opts.attrs) { if (Object.prototype.hasOwnProperty.call(opts.attrs, k)) e.setAttribute(k, opts.attrs[k]); } }
    if (opts.children) { for (var i = 0; i < opts.children.length; i++) e.appendChild(opts.children[i]); }
    return e;
  }
  function icon(name) { var i = document.createElement('i'); i.className = 'fas ' + name; i.setAttribute('aria-hidden', 'true'); return i; }
  function _badge(name) { return el('span', { cls: 'csi-badge', text: name, attrs: { title: name } }); }

  function _auth() { return (window.CSI_AuthManager && window.CSI_AuthManager.getAuthHeader()) || ''; }
  function _headers() { return { 'Content-Type': 'application/json', 'Authorization': _auth() }; }
  function _getConfig() { return fetch(API + '?action=entities-config', { headers: { 'Authorization': _auth() } }).then(function(r) { return r.json(); }); }
  // FIX-ENT-PROFILE : le bouton « Ajouter » créait des sous-entités SANS clé `profile`,
  // alors que entResolveRights() (entities-core.php:380) n'applique le profil nommé que si
  // l'entité la porte. La matrice de droits de ce panneau était donc INERTE : les droits
  // étaient affichés et enregistrés dans profiles.standard, puis ignorés par le serveur
  // (résolution repliée sur defaults — vide — donc 'none' partout). Le contrat serveur est
  // correct et testé (entity-tests.sh:61-62 pose bien "profile":"standard").
  // On pose donc le profil à la création ET on répare les entités existantes à chaque
  // sauvegarde : le panneau s'auto-guérit dès la première interaction.
  function _ensureEntityProfiles(cfg) {
    ((cfg && cfg.entities) || []).forEach(function(e) {
      if (e && e.id && !e.profile) e.profile = 'standard';
    });
    return cfg;
  }
  function _saveConfig(cfg) {
    _ensureEntityProfiles(cfg);
    return fetch(API + '?action=entities-config', { method: 'POST', headers: _headers(), body: JSON.stringify(cfg) })
      .then(function(r) { return r.json().then(function(d) { return { status: r.status, data: d }; }); });
  }
  function _toast(msg, type) { if (window.showToast2) window.showToast2(msg, type || 'info', 3500); else if (window.showToast) window.showToast(msg, type || 'info'); }
  function _cloneCfg(cfg) { return JSON.parse(JSON.stringify(cfg || {})); }
  function _slug(s) {
    var out = (s.normalize ? s.normalize('NFD').replace(/[̀-ͯ]/g, '') : s).toLowerCase();
    out = out.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 32);
    if (!/^[a-z0-9]/.test(out)) out = 'e' + out;
    return out;
  }

  // ── Modale partagée (réutilise .modal-overlay / .modal du design system) ──
  // Retourne { overlay, body, close } ; body = .modal__body où empiler le contenu.
  function _openModal(opts) {
    opts = opts || {};
    var existing = document.getElementById(opts.id);
    if (existing && existing.parentNode) existing.parentNode.removeChild(existing);
    var overlay = el('div', { cls: 'modal-overlay modal-overlay--visible' });
    overlay.id = opts.id;
    // (audit a11y 2026-07-03) sémantique dialog + gestion de focus, alignées sur la modale
    // partagée de l'app (#modalOverlay) : focus initial, piège Tab, restauration du focus,
    // Échap au niveau document en CAPTURE (le raccourci global ne visait que #modalOverlay
    // et avalait l'événement ; l'ancien handler sur l'overlay n'était atteint que si le
    // focus était déjà DANS la modale — il ne l'était jamais, faute de focus initial).
    var prevFocus = document.activeElement;
    function close() {
      document.removeEventListener('keydown', onModalKey, true);
      if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
      try { if (prevFocus && prevFocus.focus) prevFocus.focus(); } catch (e) { /* détaché */ }
    }
    function onModalKey(ev) {
      if (!overlay.parentNode) { document.removeEventListener('keydown', onModalKey, true); return; }
      if (ev.key === 'Escape') { ev.preventDefault(); ev.stopPropagation(); close(); return; }
      if (ev.key === 'Tab') {
        var focusables = overlay.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
        if (!focusables.length) return;
        var first = focusables[0], last = focusables[focusables.length - 1];
        if (!overlay.contains(document.activeElement)) { ev.preventDefault(); first.focus(); }
        else if (ev.shiftKey && document.activeElement === first) { ev.preventDefault(); last.focus(); }
        else if (!ev.shiftKey && document.activeElement === last) { ev.preventDefault(); first.focus(); }
      }
    }
    overlay.addEventListener('click', function(ev) { if (ev.target === overlay) close(); });
    var closeBtn = el('button', { cls: 'modal__close', attrs: { 'aria-label': t('common.close', 'Fermer'), title: t('common.close', 'Fermer') }, children: [icon('fa-times')] });
    closeBtn.addEventListener('click', close);
    var titleId = (opts.id || 'csi-modal') + '-title';
    var h2 = el('h2', { cls: 'modal__title', children: [icon(opts.icon || 'fa-layer-group'), document.createTextNode(' ' + (opts.title || '')) ] });
    h2.id = titleId;
    var head = el('div', { cls: 'modal__header', children: [h2, closeBtn] });
    var body = el('div', { cls: 'modal__body' });
    var panel = el('div', { cls: 'modal csi-modal ' + (opts.size || 'modal--lg'), attrs: { role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': titleId }, children: [head, body] });
    overlay.appendChild(panel);
    document.body.appendChild(overlay);
    document.addEventListener('keydown', onModalKey, true);
    try { closeBtn.focus(); } catch (e) { /* environnement sans focus */ }
    return { overlay: overlay, body: body, close: close };
  }

  // ── Carte Réglages « Multi-entités » ──────────────────────────────────────
  function renderSettingsSection(container) {
    container.textContent = '';
    container.appendChild(el('p', { cls: 'settings-card__desc', text: t("entities.loading", "Chargement…") }));
    _getConfig().then(function(resp) {
      container.textContent = '';
      var cfg = (resp && resp.config) || { enabled: false, entities: [] };
      var available = !!(resp && resp.available);
      var configRev = (resp && typeof resp.configRev === 'number') ? resp.configRev : 0;
      var maxEntities = (resp && resp.maxEntities) || 0;
      if (!available && !cfg.enabled) {
        _card(container, t('entities.cardTitle', 'Multi-entités'), t('entities.cardDesc', 'Cloisonnez des sous-entités (directions, établissements, sites…) qui n\'accèdent qu\'à leurs propres encodages.'), function(body) {
          body.appendChild(el('p', { cls: 'settings-card__desc', text: t('entities.notInLicense', 'Cette fonction n\'est pas incluse dans votre licence actuelle.') }));
        });
        return;
      }
      _renderMainCard(container, cfg, configRev, maxEntities);
      if (cfg.enabled) { _renderOpsCard(container); _renderImpExpCard(container, cfg, configRev); }
    }).catch(function() {
      container.textContent = '';
      container.appendChild(el('p', { cls: 'settings-card__desc csi-error', text: t('entities.loadConfigFailed', 'Impossible de charger la configuration multi-entités.') }));
    });
  }

  function _card(container, title, desc, fill) {
    var iconWrap = el('div', { cls: 'settings-card__icon-wrap', children: [icon('fa-sitemap')] });
    var headerText = el('div', { cls: 'settings-card__header-text', children: [
      el('h3', { cls: 'settings-card__title', text: title }),
      el('p', { cls: 'settings-card__desc', text: desc })
    ] });
    var header = el('div', { cls: 'settings-card__header', children: [iconWrap, headerText] });
    var body = el('div', { cls: 'settings-card__body' });
    container.appendChild(el('div', { cls: 'settings-card', children: [header, body] }));
    if (fill) fill(body);
    return body;
  }

  // ── (Lot 3c) Carte « Opérations » : sauvegarde/restauration complètes ─────
  function _renderOpsCard(container) {
    _card(container, t('entities.opsTitle', 'Multi-entités — opérations'), t('entities.opsDesc', 'Sauvegarde et restauration de TOUTES les partitions (entité principale + sous-entités).'), function(body) {
      var dlBtn = el('button', { cls: 'btn btn--sm btn--secondary', children: [icon('fa-download'), document.createTextNode(' ' + t('entities.backupBtn', 'Télécharger la sauvegarde complète'))] });
      dlBtn.addEventListener('click', function() {
        dlBtn.disabled = true;
        fetch(SYNC_API + '?action=backup-all', { headers: { 'Authorization': _auth() } })
          .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.blob(); })
          .then(function(blob) {
            var a = document.createElement('a');
            a.href = URL.createObjectURL(blob);
            a.download = 'cyber-assistant-backup-complet.json';
            document.body.appendChild(a); a.click();
            setTimeout(function() { URL.revokeObjectURL(a.href); if (a.parentNode) a.parentNode.removeChild(a); }, 2000);
            dlBtn.disabled = false;
          })['catch'](function(e) { dlBtn.disabled = false; _toast(t('entities.backupFailedPrefix', 'Sauvegarde impossible : ') + e.message, 'error'); });
      });
      var fileInput = el('input', { type: 'file' });
      fileInput.accept = '.json,application/json';
      fileInput.style.display = 'none';
      var restBtn = el('button', { cls: 'btn btn--sm btn--outline', children: [icon('fa-upload'), document.createTextNode(' ' + t('entities.restoreBtn', 'Restaurer une sauvegarde complète…'))] });
      restBtn.addEventListener('click', function() { fileInput.click(); });
      fileInput.addEventListener('change', function() {
        var f = fileInput.files && fileInput.files[0];
        fileInput.value = '';
        if (!f) return;
        var reader = new FileReader();
        reader.onload = function() {
          var backup = null;
          try { backup = JSON.parse(String(reader.result)); } catch (e) {}
          if (!backup || backup._format !== 'csi-backup-all-v1') {
            _toast(t('entities.invalidBackup', 'Fichier invalide : attendu une sauvegarde complète (csi-backup-all-v1).'), 'error'); return;
          }
          var n = 0; for (var k in (backup.entities || {})) { if (Object.prototype.hasOwnProperty.call(backup.entities, k)) n++; }
          var ok = window.confirm(t('entities.restoreConfirm', 'RESTAURATION COMPLÈTE\n\nCeci va REMPLACER les données de l\'entité principale et de {{n}} sous-entité(s) par le contenu de la sauvegarde{{when}}.\n\nUn instantané de l\'état actuel est pris automatiquement avant écrasement. Continuer ?', { n: n, when: (backup._createdAt ? (' du ' + backup._createdAt) : '') }));
          if (!ok) return;
          _restoreAll(backup, false, restBtn);
        };
        reader.readAsText(f);
      });
      body.appendChild(el('div', { cls: 'csi-ops-row', children: [dlBtn, restBtn, fileInput] }));
      body.appendChild(el('p', { cls: 'settings-card__desc csi-mt-sm', text: t('entities.opsHint', 'La restauration remplace chaque partition (instantané automatique avant écrasement, rotation 20). Si une partition est en cours d\'édition, la restauration est refusée — vous pourrez alors reprendre les verrous.') }));
    });
  }
  function _restoreAll(backup, force, btn) {
    if (btn) btn.disabled = true;
    fetch(SYNC_API + '?action=restore-all' + (force ? '&force=1' : ''), {
      method: 'POST', headers: _headers(), body: JSON.stringify(backup)
    }).then(function(r) { return r.json().then(function(d) { return { status: r.status, d: d }; }); })
      .then(function(res) {
        if (btn) btn.disabled = false;
        if (res.status === 409 && res.d && res.d.error) {
          if (window.confirm(res.d.error + '\n\n' + t('entities.restoreTakeoverConfirm', 'Reprendre les verrous et restaurer quand même ?'))) {
            _restoreAll(backup, true, btn);
          }
          return;
        }
        if (res.d && res.d.ok) {
          var msg = 'Restauration terminée (' + (res.d.restored || []).length + ' partition(s))';
          if ((res.d.notInBackup || []).length) msg += ' — non couvertes par la sauvegarde : ' + res.d.notInBackup.join(', ');
          _toast(msg, 'success');
          setTimeout(function() { window.location.reload(); }, 1800);
        } else {
          _toast(t('entities.restoreErrorPrefix', 'Restauration : ') + ((res.d && res.d.error) || ('HTTP ' + res.status)), 'error');
        }
      })['catch'](function(e) { if (btn) btn.disabled = false; _toast('Restauration impossible : ' + e.message, 'error'); });
  }

  // ── (import/export par entité, 2026-07-09) ─────────────────────────────────
  // Dossier DOSSIER-ARCHITECTURE-IMPORT-EXPORT-ENTITES.md. Export d'une entité (fichier
  // csi-entity-v1, ré-importable en Corporate Local), export groupe (bundle / par fichier),
  // et assistant d'import (consolider = nouvelle entité / écraser une existante).
  function _entityOptions(cfg) {
    var opts = [{ id: 'group', name: t('entities.mainEntity', 'Entité principale'), archived: false, disabled: false }];
    var list = (cfg && cfg.entities) || [];
    for (var i = 0; i < list.length; i++) {
      var e = list[i];
      if (e && e.id) opts.push({ id: e.id, name: e.name || e.id, archived: !!e.archived, disabled: !!e.disabled });
    }
    return opts;
  }
  function _dlJson(obj, filename) {
    var blob = new Blob([JSON.stringify(obj, null, 2)], { type: 'application/json' });
    var a = document.createElement('a');
    a.href = URL.createObjectURL(blob); a.download = filename;
    document.body.appendChild(a); a.click();
    setTimeout(function() { URL.revokeObjectURL(a.href); if (a.parentNode) a.parentNode.removeChild(a); }, 2000);
  }
  function _datasetHasData(ds) { return !!(ds && typeof ds === 'object' && ds.controls && (ds.controls.length === undefined || ds.controls.length >= 0) && Array.isArray(ds.controls)); }
  function _stampDataset(ds, id, name) {
    var out = {}; for (var k in ds) { if (Object.prototype.hasOwnProperty.call(ds, k)) out[k] = ds[k]; }
    delete out._dataEpoch; delete out._serverSyncDate; delete out._restoredFrom; delete out._rev;
    if (id === 'group') { delete out._csiEntity; out._csiExportScope = 'group'; }
    else { delete out._csiExportScope; out._csiEntity = { id: String(id), name: String(name) }; }
    return out;
  }
  function _exportEntity(id, btn) {
    if (btn) btn.disabled = true;
    fetch(SYNC_API + '?action=entity-export&entity=' + encodeURIComponent(id), { headers: { 'Authorization': _auth() } })
      .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.blob(); })
      .then(function(blob) {
        var safe = String(id).replace(/[^a-z0-9-]/gi, '') || 'entite';
        var a = document.createElement('a'); a.href = URL.createObjectURL(blob);
        a.download = 'CyberAssistant_Entite_' + safe + '_' + new Date().toISOString().slice(0, 10) + '.json';
        document.body.appendChild(a); a.click();
        setTimeout(function() { URL.revokeObjectURL(a.href); if (a.parentNode) a.parentNode.removeChild(a); }, 2000);
        if (btn) btn.disabled = false;
      })['catch'](function(e) { if (btn) btn.disabled = false; _toast(t('entities.exportFailedPrefix', 'Export impossible : ') + e.message, 'error'); });
  }
  function _exportBundle(btn) {
    if (btn) btn.disabled = true;
    fetch(SYNC_API + '?action=backup-all', { headers: { 'Authorization': _auth() } })
      .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.blob(); })
      .then(function(blob) {
        var a = document.createElement('a'); a.href = URL.createObjectURL(blob);
        a.download = 'cyber-assistant-backup-complet.json';
        document.body.appendChild(a); a.click();
        setTimeout(function() { URL.revokeObjectURL(a.href); if (a.parentNode) a.parentNode.removeChild(a); }, 2000);
        if (btn) btn.disabled = false;
      })['catch'](function(e) { if (btn) btn.disabled = false; _toast(t('entities.exportFailedPrefix', 'Export impossible : ') + e.message, 'error'); });
  }
  function _exportPerFile(btn) {
    if (btn) btn.disabled = true;
    fetch(SYNC_API + '?action=backup-all', { headers: { 'Authorization': _auth() } })
      .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
      .then(function(bundle) {
        if (!bundle || bundle._format !== 'csi-backup-all-v1') throw new Error('format');
        var date = new Date().toISOString().slice(0, 10), n = 0;
        if (_datasetHasData(bundle.group)) { _dlJson(_stampDataset(bundle.group, 'group', ''), 'CyberAssistant_Entite_entite-principale_' + date + '.json'); n++; }
        var ents = bundle.entities || {}, ids = [];
        for (var k in ents) { if (Object.prototype.hasOwnProperty.call(ents, k) && _datasetHasData(ents[k])) ids.push(k); }
        var idx = 0;
        function next() {
          if (idx >= ids.length) { if (btn) btn.disabled = false; _toast(t('entities.exportPerFileDone', 'Export par fichier terminé ({{n}} fichier(s))', { n: n }), 'success'); return; }
          var id = ids[idx++]; _dlJson(_stampDataset(ents[id], id, id), 'CyberAssistant_Entite_' + id + '_' + date + '.json'); n++;
          setTimeout(next, 400);
        }
        next();
      })['catch'](function(e) { if (btn) btn.disabled = false; _toast(t('entities.exportFailedPrefix', 'Export impossible : ') + e.message, 'error'); });
  }

  // POST entity-import avec gestion du 409 verrou (proposition de reprise = force).
  function _entityImportPost(payload, statusEl, onSuccess) {
    if (statusEl) statusEl.textContent = t('entities.importInProgress', 'Import en cours…');
    fetch(SYNC_API + '?action=entity-import', { method: 'POST', headers: _headers(), body: JSON.stringify(payload) })
      .then(function(r) { return r.json().then(function(d) { return { status: r.status, d: d }; }); })
      .then(function(res) {
        if (res.status === 409 && res.d && res.d.lockedBy && !payload.force) {
          if (window.confirm(t('entities.importLockConfirm', 'La partition cible est en cours d\'édition par {{who}}. Reprendre le verrou et importer quand même ?', { who: res.d.lockedBy }))) {
            var p2 = {}; for (var k in payload) { if (Object.prototype.hasOwnProperty.call(payload, k)) p2[k] = payload[k]; } p2.force = true;
            _entityImportPost(p2, statusEl, onSuccess); return;
          }
          if (statusEl) statusEl.textContent = t('entities.importCancelled', 'Import annulé.');
          return;
        }
        if (res.d && res.d.ok) { if (onSuccess) onSuccess(res.d); }
        else { if (statusEl) statusEl.textContent = t('entities.importErrorPrefix', 'Import : ') + ((res.d && res.d.error) || ('HTTP ' + res.status)); }
      })['catch'](function(e) { if (statusEl) statusEl.textContent = t('entities.importErrorPrefix', 'Import : ') + e.message; });
  }

  // Assistant d'import : fichier → (bundle : choix de la source) → mode → cible → rapport.
  function _openImportAssistant(cfg, configRev, onDone) {
    var m = _openModal({ id: 'csi-entity-import', icon: 'fa-file-import', title: t('entities.importTitle', 'Importer une entité'), size: 'modal--lg' });
    var body = m.body;
    body.appendChild(el('p', { cls: 'settings-card__desc', text: t('entities.importIntro', 'Importez un export d\'entité (fichier individuel) ou une sauvegarde complète. Vous pourrez l\'ajouter comme NOUVELLE entité, ou ÉCRASER une entité existante.') }));
    var fileInput = el('input', { type: 'file' }); fileInput.accept = '.json,application/json'; fileInput.style.display = 'none';
    var pickBtn = el('button', { cls: 'btn btn--primary', children: [icon('fa-folder-open'), document.createTextNode(' ' + t('entities.chooseFile', 'Choisir un fichier…'))] });
    pickBtn.addEventListener('click', function() { fileInput.click(); });
    var stage = el('div', { cls: 'csi-mt-sm' });
    body.appendChild(el('div', { cls: 'csi-ops-row', children: [pickBtn, fileInput] }));
    body.appendChild(stage);
    fileInput.addEventListener('change', function() {
      var f = fileInput.files && fileInput.files[0]; fileInput.value = '';
      if (!f) return;
      var reader = new FileReader();
      reader.onerror = function() { _toast(t('entities.fileReadError', 'Lecture du fichier impossible.'), 'error'); };
      reader.onload = function() {
        var parsed = null;
        try { parsed = JSON.parse(String(reader.result)); } catch (e) {}
        if (!parsed || typeof parsed !== 'object') { stage.textContent = ''; stage.appendChild(el('p', { cls: 'settings-card__desc csi-error', text: t('entities.invalidFile', 'Fichier JSON invalide.') })); return; }
        _renderImportStage(stage, cfg, configRev, parsed, f.name, m, onDone);
      };
      reader.readAsText(f);
    });
  }

  // Détecte la forme (bundle vs entité unique) et construit la liste des sources.
  function _importSources(parsed) {
    if (parsed._format === 'csi-backup-all-v1') {
      var srcs = [];
      if (_datasetHasData(parsed.group)) srcs.push({ key: 'group', ds: parsed.group, name: t('entities.mainEntity', 'Entité principale') });
      var ents = parsed.entities || {};
      for (var k in ents) { if (Object.prototype.hasOwnProperty.call(ents, k)) srcs.push({ key: k, ds: ents[k], name: k, empty: !_datasetHasData(ents[k]) }); }
      return { bundle: true, sources: srcs };
    }
    var name = (parsed._csiEntity && parsed._csiEntity.name) || (parsed.title) || t('entities.importedEntity', 'Entité importée');
    return { bundle: false, sources: [{ key: 'single', ds: parsed, name: name, empty: !_datasetHasData(parsed) }] };
  }

  function _renderImportStage(stage, cfg, configRev, parsed, filename, modal, onDone) {
    stage.textContent = '';
    var info = _importSources(parsed);
    var report = el('div', { cls: 'csi-mt-sm' });

    // Sélection de la source (bundle uniquement).
    var chosenSource = null;
    var validSources = [];
    for (var s = 0; s < info.sources.length; s++) { if (!info.sources[s].empty) validSources.push(info.sources[s]); }
    if (!validSources.length) {
      stage.appendChild(el('p', { cls: 'settings-card__desc csi-error', text: t('entities.noDataInFile', 'Ce fichier ne contient aucune entité exploitable (pas de données).') }));
      return;
    }
    chosenSource = validSources[0];

    if (info.bundle) {
      stage.appendChild(el('p', { cls: 'settings-card__desc', text: t('entities.bundleDetected', 'Sauvegarde complète détectée ({{n}} entité(s) avec données). Choisissez la source à importer, ou ajoutez-les toutes comme nouvelles entités.', { n: validSources.length }) }));
      var srcSel = el('select', { cls: 'csi-select' });
      for (var v = 0; v < validSources.length; v++) { var o = el('option', { text: validSources[v].name + ' (' + validSources[v].key + ')' }); o.value = String(v); srcSel.appendChild(o); }
      srcSel.addEventListener('change', function() { chosenSource = validSources[parseInt(srcSel.value, 10) || 0]; });
      stage.appendChild(el('div', { cls: 'csi-ops-row', children: [srcSel] }));
      var allBtn = el('button', { cls: 'btn btn--sm btn--outline', children: [icon('fa-layer-group'), document.createTextNode(' ' + t('entities.importAllAsNew', 'Ajouter TOUTES comme nouvelles entités'))] });
      allBtn.addEventListener('click', function() { _bundleImportAll(validSources, report); });
      stage.appendChild(el('div', { cls: 'csi-ops-row csi-mt-sm', children: [allBtn] }));
      stage.appendChild(el('hr'));
    } else if (info.sources[0].empty) {
      stage.appendChild(el('p', { cls: 'settings-card__desc csi-error', text: t('entities.noDataInFile', 'Ce fichier ne contient aucune entité exploitable (pas de données).') }));
      return;
    }

    // Choix du mode.
    var modeWrap = el('div', { cls: 'csi-mt-sm' });
    var rName = 'csi-imp-mode';
    var radConsolidate = el('input', { type: 'radio' }); radConsolidate.name = rName; radConsolidate.value = 'create'; radConsolidate.checked = true;
    var radOverwrite = el('input', { type: 'radio' }); radOverwrite.name = rName; radOverwrite.value = 'overwrite';
    var lblC = el('label', { cls: 'csi-radio', children: [radConsolidate, el('span', { text: ' ' + t('entities.modeConsolidate', 'Consolider — ajouter comme NOUVELLE entité') })] });
    var lblO = el('label', { cls: 'csi-radio', children: [radOverwrite, el('span', { text: ' ' + t('entities.modeOverwrite', 'Écraser une entité EXISTANTE') })] });
    modeWrap.appendChild(lblC); modeWrap.appendChild(lblO);
    stage.appendChild(modeWrap);

    // Paramètres selon le mode.
    var paramWrap = el('div', { cls: 'csi-mt-sm' });
    var nameInput = el('input', { type: 'text', cls: 'csi-input', placeholder: t('entities.newEntityNamePh', 'Nom de la nouvelle entité') });
    nameInput.value = chosenSource ? chosenSource.name : '';
    var targetSel = el('select', { cls: 'csi-select' });
    var opts = _entityOptions(cfg);
    for (var i = 0; i < opts.length; i++) { var to = el('option', { text: opts[i].name + (opts[i].archived ? ' (archivée)' : '') }); to.value = opts[i].id; targetSel.appendChild(to); }
    function refreshParams() {
      paramWrap.textContent = '';
      if (radOverwrite.checked) {
        paramWrap.appendChild(el('label', { cls: 'settings-card__desc', text: t('entities.overwriteTargetLabel', 'Entité à écraser :') }));
        paramWrap.appendChild(el('div', { cls: 'csi-ops-row', children: [targetSel] }));
        paramWrap.appendChild(el('p', { cls: 'settings-card__desc csi-warn', text: t('entities.overwriteWarn', '⚠ Le contenu de l\'entité choisie sera REMPLACÉ. Un instantané est pris automatiquement avant écrasement.') }));
      } else {
        paramWrap.appendChild(el('label', { cls: 'settings-card__desc', text: t('entities.newEntityNameLabel', 'Nom de la nouvelle entité :') }));
        paramWrap.appendChild(el('div', { cls: 'csi-ops-row', children: [nameInput] }));
      }
    }
    radConsolidate.addEventListener('change', refreshParams);
    radOverwrite.addEventListener('change', refreshParams);
    refreshParams();
    stage.appendChild(paramWrap);

    var submit = el('button', { cls: 'btn btn--primary csi-mt-sm', children: [icon('fa-check'), document.createTextNode(' ' + t('entities.importConfirmBtn', 'Importer'))] });
    submit.addEventListener('click', function() {
      var ds = chosenSource && chosenSource.ds;
      if (!_datasetHasData(ds)) { _toast(t('entities.noDataInFile', 'Ce fichier ne contient aucune entité exploitable (pas de données).'), 'error'); return; }
      var payload;
      if (radOverwrite.checked) {
        var tgt = targetSel.value;
        var tgtLabel = opts.filter(function(o) { return o.id === tgt; })[0];
        tgtLabel = tgtLabel ? tgtLabel.name : tgt;
        var warn = (tgt === 'group')
          ? t('entities.overwriteGroupConfirm', 'ÉCRASER l\'ENTITÉ PRINCIPALE avec ces données ?\n\nLes échelles imposées du groupe sont préservées, mais toutes les autres données de la principale seront remplacées. Un instantané est pris avant écrasement.')
          : t('entities.overwriteConfirm', 'ÉCRASER l\'entité « {{name}} » avec ces données ?\n\nToutes ses données actuelles seront remplacées. Un instantané est pris avant écrasement.', { name: tgtLabel });
        if (!window.confirm(warn)) return;
        payload = { mode: 'overwrite', target: tgt, data: ds };
      } else {
        var nm = (nameInput.value || '').trim();
        if (!nm) { _toast(t('entities.nameRequired', 'Nom requis'), 'error'); return; }
        payload = { mode: 'create', name: nm, data: ds };
      }
      submit.disabled = true;
      report.textContent = '';
      var status = el('p', { cls: 'settings-card__desc' }); report.appendChild(status);
      _entityImportPost(payload, status, function(d) {
        submit.disabled = false;
        var msg = (d.mode === 'create')
          ? t('entities.createdOk', 'Entité créée : {{name}} (id {{id}}).', { name: payload.name, id: d.entityId })
          : t('entities.overwrittenOk', 'Entité écrasée : {{id}}.', { id: d.entityId });
        if (d.stolenLockFrom) msg += ' ' + t('entities.lockStolen', '(verrou repris à {{who}})', { who: d.stolenLockFrom });
        status.textContent = ''; status.appendChild(el('span', { cls: 'csi-ok', text: '✔ ' + msg }));
        _toast(msg, 'success');
        if (onDone) onDone();
      });
    });
    stage.appendChild(submit);
    stage.appendChild(report);
  }

  // Bundle : créer chaque source comme nouvelle entité (rapport de synthèse).
  function _bundleImportAll(validSources, report) {
    report.textContent = '';
    var list = el('ul', { cls: 'csi-report-list' }); report.appendChild(list);
    var idx = 0, okN = 0, failN = 0;
    function next() {
      if (idx >= validSources.length) {
        report.appendChild(el('p', { cls: 'settings-card__desc', text: t('entities.bundleImportDone', 'Terminé : {{ok}} créée(s), {{fail}} échec(s).', { ok: okN, fail: failN }) }));
        return;
      }
      var src = validSources[idx++];
      var li = el('li', { text: src.name + ' … ' }); list.appendChild(li);
      // Création séquentielle (une nouvelle entité par source ; partition fraîche = pas de
      // verrou, pas de 409 attendu). Chaque appel est atomique côté serveur.
      fetch(SYNC_API + '?action=entity-import', { method: 'POST', headers: _headers(), body: JSON.stringify({ mode: 'create', name: src.name, data: src.ds }) })
        .then(function(r) { return r.json().then(function(d) { return { status: r.status, d: d }; }); })
        .then(function(res) {
          if (res.d && res.d.ok) { okN++; li.appendChild(el('span', { cls: 'csi-ok', text: '✔ ' + (res.d.entityId || '') })); }
          else { failN++; li.appendChild(el('span', { cls: 'csi-error', text: '✗ ' + ((res.d && res.d.error) || res.status) })); }
          next();
        })['catch'](function(e) { failN++; li.appendChild(el('span', { cls: 'csi-error', text: '✗ ' + e.message })); next(); });
    }
    next();
  }

  function _renderImpExpCard(container, cfg, configRev) {
    _card(container, t('entities.impexpTitle', 'Multi-entités — export / import par entité'),
      t('entities.impexpDesc', 'Exportez une entité individuelle (ré-importable en version locale), tout le groupe, ou importez un fichier pour l\'ajouter comme nouvelle entité ou écraser une entité existante.'), function(body) {
        var opts = _entityOptions(cfg);
        var sel = el('select', { cls: 'csi-select' });
        for (var i = 0; i < opts.length; i++) { var o = el('option', { text: opts[i].name }); o.value = opts[i].id; sel.appendChild(o); }
        var expBtn = el('button', { cls: 'btn btn--sm btn--secondary', children: [icon('fa-download'), document.createTextNode(' ' + t('entities.exportEntityBtn', 'Exporter cette entité'))] });
        expBtn.addEventListener('click', function() { _exportEntity(sel.value, expBtn); });
        body.appendChild(el('label', { cls: 'settings-card__desc', text: t('entities.exportEntityLabel', 'Exporter une entité individuelle :') }));
        body.appendChild(el('div', { cls: 'csi-ops-row', children: [sel, expBtn] }));

        var bundleBtn = el('button', { cls: 'btn btn--sm btn--outline', children: [icon('fa-box-archive'), document.createTextNode(' ' + t('entities.exportBundleBtn', 'Bundle unique (toutes)'))] });
        bundleBtn.addEventListener('click', function() { _exportBundle(bundleBtn); });
        var perFileBtn = el('button', { cls: 'btn btn--sm btn--outline', children: [icon('fa-copy'), document.createTextNode(' ' + t('entities.exportPerFileBtn', 'Un fichier par entité'))] });
        perFileBtn.addEventListener('click', function() { _exportPerFile(perFileBtn); });
        body.appendChild(el('label', { cls: 'settings-card__desc csi-mt-sm', text: t('entities.exportGroupLabel', 'Exporter tout le groupe :') }));
        body.appendChild(el('div', { cls: 'csi-ops-row', children: [bundleBtn, perFileBtn] }));

        var impBtn = el('button', { cls: 'btn btn--sm btn--primary', children: [icon('fa-file-import'), document.createTextNode(' ' + t('entities.importEntityBtn', 'Importer une entité…'))] });
        impBtn.addEventListener('click', function() { _openImportAssistant(cfg, configRev, function() { renderSettingsSection(container); }); });
        body.appendChild(el('label', { cls: 'settings-card__desc csi-mt-sm', text: t('entities.importLabel', 'Importer un fichier d\'entité ou une sauvegarde :') }));
        body.appendChild(el('div', { cls: 'csi-ops-row', children: [impBtn] }));
      });
  }

  function _renderMainCard(container, cfg, configRev, maxEntities) {
    var activeCount = 0;
    (cfg.entities || []).forEach(function(e) { if (e && !e.archived && !e.disabled) activeCount++; });
    _card(container, 'Multi-entités', 'Activation et gestion des sous-entités et de leurs droits par section.', function(body) {
      var chk = el('input', { type: 'checkbox' }); chk.checked = !!cfg.enabled;
      var txt = el('span', { text: cfg.enabled ? t('entities.modeOn', 'Mode activé') : t('entities.modeActivate', 'Activer le mode multi-entités') });
      var lbl = el('label', { cls: 'csi-toggle', children: [chk, txt] });
      var toggleRow = el('div', { cls: 'csi-toggle-row', children: [lbl] });
      if (cfg.enabled && maxEntities) toggleRow.appendChild(el('span', { cls: 'csi-toggle-row__count', text: t('entities.countMax', '{{count}} / {{max}} sous-entités', { count: activeCount, max: maxEntities }) }));
      body.appendChild(toggleRow);

      chk.addEventListener('change', function() {
        if (!chk.checked) {
          var n = (cfg.entities || []).length;
          if (n > 0 && !window.confirm(t('entities.deactivateConfirm', 'Désactiver le mode multi-entités ?\n\nLes {{n}} sous-entité(s) et leurs données seront CONSERVÉES mais invisibles jusqu\'à réactivation. Les comptes assignés seront bloqués.', { n: n }))) { chk.checked = true; return; }
        }
        var nc = _cloneCfg(cfg); nc.enabled = chk.checked; nc.baseConfigRev = configRev;
        _saveConfig(nc).then(function(res) {
          if (res.status === 200) { _toast(chk.checked ? t('entities.activated', 'Mode multi-entités activé') : t('entities.deactivated', 'Mode désactivé'), 'success'); renderSettingsSection(container); }
          else if (res.status === 409) { _toast(t('entities.configChangedReload', 'Configuration modifiée ailleurs — rechargement'), 'warning'); renderSettingsSection(container); }
          else { _toast(res.data.error || 'Échec', 'error'); chk.checked = !chk.checked; }
        });
      });

      if (!cfg.enabled) {
        body.appendChild(el('p', { cls: 'settings-card__desc', text: t('entities.activateHint', 'Activez le mode pour créer des sous-entités. L\'activation n\'écrit aucune donnée : vos encodages actuels deviennent l\'entité principale.') }));
        return;
      }
      body.appendChild(_renderProfileEditor(cfg, configRev, container));
      body.appendChild(_renderEntitiesList(cfg, configRev, maxEntities, activeCount, container));
    });
  }

  function _rightSelector(current, onChange, ariaName) {
    var sel = el('select', { cls: 'form-control form-control--sm csi-right-select', attrs: ariaName ? { 'aria-label': ariaName } : {} });
    [['none', t('entities.rightNone', 'Rien')], ['read', t('entities.rightRead', 'Lecture')], ['write', t('entities.rightWrite', 'Écriture')]].forEach(function(pair) {
      var o = el('option', { text: pair[1] }); o.value = pair[0];
      if ((current || 'none') === pair[0]) o.selected = true; sel.appendChild(o);
    });
    sel.addEventListener('change', function() { onChange(sel.value); });
    return sel;
  }

  function _renderProfileEditor(cfg, configRev, container) {
    var wrap = el('div', { cls: 'csi-block' });
    wrap.appendChild(el('p', { cls: 'csi-subhead', text: t('entities.profileTitle', 'Profil « Sous-entité standard » — droits par section') }));
    var prof = (cfg.profiles && cfg.profiles.standard && cfg.profiles.standard.sections) || {};

    var clusterInfo = el('div', { cls: 'csi-rights__name', children: [
      el('div', { text: t('entities.clusterName', 'Bloc SMSI cœur') }),
      el('div', { cls: 'csi-hint', text: t('entities.clusterKeys', 'risques, menaces, actions, SoA, contrôles, audits, NC, actifs') })
    ] });
    var clusterSel = _rightSelector(prof[CLUSTER[0]] || 'none', function(v) { CLUSTER.forEach(function(s) { _ensureProfile(cfg).sections[s] = v; }); _persistProfile(cfg, configRev, container); }, t('entities.clusterName', 'Bloc SMSI cœur'));
    wrap.appendChild(el('div', { cls: 'csi-rights__section', children: [clusterInfo, clusterSel] }));

    FREE_SECTIONS.forEach(function(s) {
      var name = el('div', { cls: 'csi-rights__name', text: _secLabel(s) });
      var selr = _rightSelector(prof[s], function(v) { _ensureProfile(cfg).sections[s] = v; _persistProfile(cfg, configRev, container); }, _secLabel(s));
      wrap.appendChild(el('div', { cls: 'csi-rights__section', children: [name, selr] }));
    });

    var lockInfo = el('div', { cls: 'csi-rights__name', children: [
      el('div', { text: t('entities.scalesName', 'Échelles & référentiel') }),
      el('div', { cls: 'csi-hint', text: t('entities.scalesImposed', 'imposés par l\'entité principale') })
    ] });
    var lockSpan = el('span', { cls: 'csi-muted csi-locked', children: [icon('fa-lock'), document.createTextNode(' ' + t('entities.scalesLocked', 'Lecture (verrouillé)'))] });
    wrap.appendChild(el('div', { cls: 'csi-rights__section csi-rights__section--last', children: [lockInfo, lockSpan] }));
    return wrap;
  }

  function _ensureProfile(cfg) {
    if (!cfg.profiles) cfg.profiles = {};
    if (!cfg.profiles.standard) cfg.profiles.standard = { sections: {} };
    if (!cfg.profiles.standard.sections) cfg.profiles.standard.sections = {};
    return cfg.profiles.standard;
  }
  function _persistProfile(cfg, configRev, container) {
    var nc = _cloneCfg(cfg); nc.baseConfigRev = configRev;
    _saveConfig(nc).then(function(res) {
      if (res.status === 200) { _toast(t('entities.rightsUpdated', 'Droits mis à jour'), 'success'); renderSettingsSection(container); }
      else if (res.status === 400) { _toast(res.data.error || t('entities.configInvalid', 'Configuration invalide'), 'error'); }
      else if (res.status === 409) { _toast(t('entities.modifiedReload', 'Modifié ailleurs — rechargement'), 'warning'); renderSettingsSection(container); }
      else { _toast(res.data.error || t('entities.saveFailed', 'Échec'), 'error'); }
    });
  }

  function _renderEntitiesList(cfg, configRev, maxEntities, activeCount, container) {
    var wrap = el('div', { cls: 'csi-block' });
    wrap.appendChild(el('p', { cls: 'csi-subhead', text: t('entities.subEntitiesLabel', 'Sous-entités') }));

    (cfg.entities || []).forEach(function(e) {
      if (!e || !e.id) return;
      var nm = el('span', { cls: 'csi-entity-row__name', text: (e.name || e.id) + (e.archived ? ' ' + t('entities.archivedTag', '(archivée)') : '') });
      var seed = el('button', { cls: 'btn btn--sm btn--outline', text: t('entities.prefill', 'Pré-remplir') });
      seed.title = t('entities.prefillTitle', 'Copier les défauts sûrs de l\'entité principale (échelles, référentiel, cible) — jamais les données métier');
      seed.addEventListener('click', function() {
        if (!window.confirm(t('entities.prefillConfirm', 'Pré-remplir « {{name}} » avec les défauts de l\'entité principale (échelles d\'impact, référentiel, cible de maturité) ?\n\nAucune donnée métier (risques, actions…) n\'est copiée.', { name: (e.name || e.id) }))) return;
        seed.disabled = true;
        fetch(API + '?action=entity-seed', { method: 'POST', headers: _headers(), body: JSON.stringify({ entityId: e.id }) })
          .then(function(r) { return r.json().then(function(d) { return { status: r.status, d: d }; }); })
          .then(function(res) {
            seed.disabled = false;
            if (res.d && res.d.ok) _toast(t('entities.prefilled', 'Défauts copiés vers « {{name}} »', { name: (e.name || e.id) }), 'success');
            else _toast((res.d && res.d.error) || ('Échec HTTP ' + res.status), 'error');
          })['catch'](function() { seed.disabled = false; _toast(t('entities.networkError', 'Erreur réseau'), 'error'); });
      });
      // M11 (audit 2026-07-10) : archivage/désarchivage depuis l'UI. Le flag `archived` est
      // géré côté serveur (exclusion du quota, des sélecteurs) et recommandé comme alternative
      // RÉVERSIBLE à la suppression — mais aucun contrôle client ne le posait. Simple bascule
      // dans la config déjà envoyée par _saveConfig.
      var arch = el('button', { cls: 'btn btn--sm btn--outline', text: e.archived ? t('entities.unarchive', 'Désarchiver') : t('entities.archive', 'Archiver') });
      arch.title = t('entities.archiveTitle', 'Masquer et exclure du quota de licence — réversible, aucune donnée perdue');
      arch.addEventListener('click', function() {
        var nc = _cloneCfg(cfg); nc.baseConfigRev = configRev;
        for (var ai = 0; ai < (nc.entities || []).length; ai++) {
          if (nc.entities[ai].id === e.id) { nc.entities[ai].archived = !e.archived; break; }
        }
        _saveConfig(nc).then(function(res) {
          if (res.status === 200) {
            _toast(e.archived ? t('entities.unarchived', 'Sous-entité désarchivée') : t('entities.archived', 'Sous-entité archivée (réversible)'), 'success');
            renderSettingsSection(container);
          } else { _toast((res.data && res.data.error) || t('entities.saveFailed', 'Échec'), 'error'); }
        });
      });
      var del = el('button', { cls: 'btn btn--sm btn--outline btn--danger', text: t('common.delete', 'Supprimer') });
      del.addEventListener('click', function() {
        // M10 (audit 2026-07-10) : confirmation enrichie (sort des données + réservation du nom)
        // orientée vers l'archivage réversible, + export de secours automatique avant suppression.
        if (!window.confirm(t('entities.deleteConfirmStrong',
          'SUPPRIMER définitivement « {{name}} » ?\n\n• Ses données deviendront INACCESSIBLES (preuves d\'audit incluses).\n• Le nom « {{name}} » restera RÉSERVÉ et ne pourra pas être recréé sans intervention serveur.\n\nPréférez « Archiver » (réversible, aucune perte). Un export de secours va être téléchargé avant la suppression.\n\nConfirmer la suppression ?',
          { name: (e.name || e.id) }))) return;
        // Filet anti-perte : déclencher un export de l'entité avant de la supprimer.
        try { _exportEntity(e.id); } catch (ex) { /* best-effort */ }
        var nc = _cloneCfg(cfg); nc.baseConfigRev = configRev;
        nc.entities = nc.entities.filter(function(x) { return x.id !== e.id; });
        _saveConfig(nc).then(function(res) {
          if (res.status === 200) { _toast(t('entities.deleted', 'Sous-entité supprimée'), 'success'); renderSettingsSection(container); }
          else { _toast(res.data.error || t('entities.deleteFailed', 'Échec (comptes encore assignés ?)'), 'error'); }
        });
      });
      wrap.appendChild(el('div', { cls: 'csi-entity-row', children: [nm, seed, arch, del] }));
    });

    if (activeCount < maxEntities) {
      var inName = el('input', { type: 'text', placeholder: t('entities.addPlaceholder', 'Nom de la sous-entité'), cls: 'form-control form-control--sm csi-grow' });
      var add = el('button', { cls: 'btn btn--sm btn--primary', text: t('common.add', 'Ajouter') });
      add.addEventListener('click', function() {
        var name = (inName.value || '').trim();
        if (!name) { _toast(t('entities.nameRequired', 'Nom requis'), 'warning'); return; }
        var id = _slug(name);
        if (!id) { _toast(t('entities.nameInvalid', 'Nom invalide'), 'warning'); return; }
        var nc = _cloneCfg(cfg); nc.baseConfigRev = configRev;
        if (!nc.entities) nc.entities = [];
        // profile:'standard' OBLIGATOIRE — sans lui la matrice de droits est inerte (FIX-ENT-PROFILE).
        nc.entities.push({ id: id, name: name, createdAt: new Date().toISOString(), profile: 'standard', sections: {} });
        _saveConfig(nc).then(function(res) {
          if (res.status === 200) { _toast(t('entities.created', 'Sous-entité créée (droits fermés — ouvrez-les via le profil et assignez des comptes)'), 'success'); renderSettingsSection(container); }
          else { _toast(res.data.error || t('entities.saveFailed', 'Échec'), 'error'); }
        });
      });
      wrap.appendChild(el('div', { cls: 'csi-add-row', children: [inName, add] }));
      wrap.appendChild(el('p', { cls: 'settings-card__desc csi-hint csi-mt-sm', text: t('entities.newEntityHint', 'Une nouvelle sous-entité démarre VIDE et sans droits (fail-closed). Ouvrez ses sections via le profil, puis assignez des comptes dans « Utilisateurs ».') }));
    } else if (maxEntities) {
      wrap.appendChild(el('p', { cls: 'settings-card__desc', text: t('entities.capReached', 'Plafond de licence atteint ({{max}}).', { max: maxEntities }) }));
    }
    return wrap;
  }

  // ── Adaptation selon le compte (appelée après chaque (re)configuration nav) ──
  function applySubEntityView() {
    var EM = window.CSI_EntityManager;
    if (!EM || !EM.isMultiEntity()) {
      // Multi-entités désactivé (dont désactivation à chaud) : démonter le fil
      // d'Ariane résiduel et rendre son sous-titre au header. No-op sinon.
      var w1 = document.getElementById('csi-perimeter-wrap');
      if (w1 && w1.parentNode) w1.parentNode.removeChild(w1);
      var w2 = document.getElementById('csi-ownent-wrap');
      if (w2 && w2.parentNode) w2.parentNode.removeChild(w2);
      _cleanupScopeHost();
      _renderScopeBanner(null); // titre d'onglet + favicon débadgés
      return;
    }
    if (EM.isSubEntityUser()) {
      var navItems = document.querySelectorAll('.nav__item[data-tab]');
      var roTitle = t('entities.readonlyNav', 'Lecture seule (droits du périmètre)');
      for (var i = 0; i < navItems.length; i++) {
        var tab = navItems[i].getAttribute('data-tab');
        if (!tab || tab === 'dashboard' || tab === 'settings') continue;
        var right = EM.sectionRight(tab);
        navItems[i].style.display = (right === 'none') ? 'none' : '';
        // (audit 2026-07-03) droit 'read' : badge visuel + tooltip — la section était
        // présentée comme pleinement éditable alors que le serveur refuse l'écriture.
        if (right === 'read') {
          navItems[i].classList.add('csi-nav-readonly');
          navItems[i].setAttribute('title', roTitle);
        } else {
          navItems[i].classList.remove('csi-nav-readonly');
          if (navItems[i].getAttribute('title') === roTitle) navItems[i].removeAttribute('title');
        }
      }
      _renderEntityBanner(EM.info());
      _renderOwnEntitySelector();
    } else if (EM.isGroupUser()) {
      _renderPerimeterSelector();
    }
  }

  // (refonte header 2026-07) Le slot du sélecteur REMPLACE le sous-titre dans
  // .header__brand (fil d'Ariane sous le titre). Le <p.header__subtitle> est
  // masqué par classe — jamais supprimé — pour réapparaître si le multi-entités
  // se désactive (cf _cleanupScopeHost). Fallback vieux markup (pas de
  // .header__brand) : comportement historique, slot entre marque et actions.
  function _scopeHost() {
    var host = document.getElementById('csi-header-scope-slot');
    var brand = document.querySelector('.header__brand');
    if (brand) {
      if (!host) { host = el('div', {}); host.id = 'csi-header-scope-slot'; }
      host.className = 'csi-scope-breadcrumb';
      var subtitle = brand.querySelector('.header__subtitle');
      if (subtitle) {
        subtitle.classList.add('csi-subtitle--hidden');
        if (host.parentNode !== brand) brand.insertBefore(host, subtitle.nextSibling);
      } else if (host.parentNode !== brand) {
        brand.appendChild(host);
      }
      return host;
    }
    var headerContent = document.querySelector('.header__content');
    var fallback = document.querySelector('.header__actions');
    if (!headerContent) return fallback;
    if (!host) { host = el('div', {}); host.id = 'csi-header-scope-slot'; }
    host.className = 'csi-header-scope';
    if (host.parentNode !== headerContent) {
      var right = headerContent.querySelector('.header__right');
      if (right) headerContent.insertBefore(host, right);
      else headerContent.appendChild(host);
    }
    return host;
  }

  function _cleanupScopeHost() {
    var host = document.getElementById('csi-header-scope-slot');
    if (host && !host.querySelector('.csi-scope') && host.parentNode) host.parentNode.removeChild(host);
    // Slot démonté → rendre son sous-titre au header (masqué au montage du fil d'Ariane)
    if (!document.getElementById('csi-header-scope-slot')) {
      var subtitle = document.querySelector('.header__brand .header__subtitle');
      if (subtitle) subtitle.classList.remove('csi-subtitle--hidden');
    }
  }

  // ── Commutateur de périmètre (fil d'Ariane + menu déroulant) ──────────────
  // Un contrôle discret sous le titre (à la place du sous-titre) : icône +
  // libellé muted + « › » + nom du périmètre actif en semi-gras. Pattern
  // « workspace switcher » : le menu liste les périmètres (coche sur l'actif,
  // filtre au-delà de 8) et regroupe la vue consolidée. ES5, DOM pur, rôle menu
  // ARIA, clavier (flèches / Échap), fermeture au clic externe.
  function _buildScopeSwitcher(opts) {
    // opts : { id, label, activeName, isSub, items: [{id, name, active}],
    //          onPick(id), extra: {label, hint, title, onRun} | null }
    var wrap = el('span', { cls: 'csi-scope' + (opts.isSub ? ' csi-scope--sub' : '') });
    wrap.id = opts.id;
    var caret = icon('fa-chevron-down'); caret.className += ' csi-scope__caret';
    var btnTitle = opts.label + ' : ' + opts.activeName;
    var trigger = el('button', { type: 'button', cls: 'csi-scope__btn', attrs: { 'aria-haspopup': 'menu', 'aria-expanded': 'false', 'aria-label': btnTitle, title: btnTitle }, children: [
      icon('fa-layer-group'),
      el('span', { cls: 'csi-scope__label', text: opts.label }),
      el('span', { cls: 'csi-scope__chevron', text: '›', attrs: { 'aria-hidden': 'true' } }),
      el('span', { cls: 'csi-scope__name', text: opts.activeName }),
      caret
    ]});
    var menu = el('div', { cls: 'csi-scope__menu', attrs: { role: 'menu' } });
    menu.appendChild(el('div', { cls: 'csi-scope__head', text: opts.label }));
    function norm(s) { s = String(s || ''); return (s.normalize ? s.normalize('NFD').replace(/[̀-ͯ]/g, '') : s).toLowerCase(); }
    var itemEls = [];
    var filterIn = null;
    function applyFilter() {
      var q = filterIn ? norm(filterIn.value) : '';
      for (var i = 0; i < itemEls.length; i++) itemEls[i].el.style.display = (!q || itemEls[i].search.indexOf(q) >= 0) ? '' : 'none';
    }
    if (opts.items.length > 8) {
      filterIn = el('input', { type: 'text', cls: 'csi-scope__filter', placeholder: t('common.filter', 'Filtrer') + '…', attrs: { 'aria-label': t('common.filter', 'Filtrer'), autocomplete: 'off' } });
      filterIn.addEventListener('input', applyFilter);
      menu.appendChild(filterIn);
    }
    var list = el('div', { cls: 'csi-scope__list' });
    opts.items.forEach(function(item) {
      var check = icon('fa-check'); check.className += ' csi-scope__check' + (item.active ? '' : ' csi-scope__check--off');
      var b = el('button', { type: 'button', cls: 'csi-scope__item', attrs: { role: 'menuitemradio', 'aria-checked': item.active ? 'true' : 'false', title: item.name }, children: [
        check, el('span', { cls: 'csi-scope__item-name', text: item.name })
      ]});
      b.addEventListener('click', function() { close(true); if (!item.active) opts.onPick(item.id); });
      list.appendChild(b);
      itemEls.push({ el: b, search: norm(item.name) });
    });
    menu.appendChild(list);
    if (opts.extra) {
      menu.appendChild(el('div', { cls: 'csi-scope__sep', attrs: { role: 'separator' } }));
      var ex = el('button', { type: 'button', cls: 'csi-scope__item csi-scope__item--action', attrs: { role: 'menuitem', title: opts.extra.title || '' }, children: [
        icon('fa-table-list'),
        el('span', { cls: 'csi-scope__item-name', text: opts.extra.label }),
        el('span', { cls: 'csi-scope__hint', text: opts.extra.hint || '' })
      ]});
      ex.addEventListener('click', function() { close(true); opts.extra.onRun(); });
      menu.appendChild(ex);
    }
    var isOpen = false;
    function onDocDown(ev) {
      // le contrôle est re-rendu à chaque bascule : purger le listener orphelin
      if (!wrap.parentNode) { document.removeEventListener('mousedown', onDocDown, true); return; }
      if (!wrap.contains(ev.target)) close(false);
    }
    function close(refocus) {
      if (!isOpen) return;
      isOpen = false;
      wrap.classList.remove('csi-scope--open');
      trigger.setAttribute('aria-expanded', 'false');
      document.removeEventListener('mousedown', onDocDown, true);
      if (refocus) { try { trigger.focus(); } catch (e) { /* environnement sans focus */ } }
    }
    function openMenu() {
      if (isOpen) return;
      isOpen = true;
      wrap.classList.add('csi-scope--open');
      trigger.setAttribute('aria-expanded', 'true');
      document.addEventListener('mousedown', onDocDown, true);
      if (filterIn) { filterIn.value = ''; applyFilter(); }
    }
    function _navItems() {
      var all = menu.querySelectorAll('button.csi-scope__item');
      var out = [];
      for (var i = 0; i < all.length; i++) { if (all[i].style.display !== 'none') out.push(all[i]); }
      return out;
    }
    trigger.addEventListener('click', function() { if (isOpen) close(true); else openMenu(); });
    wrap.addEventListener('keydown', function(ev) {
      if (ev.key === 'Escape') {
        if (isOpen) { ev.preventDefault(); ev.stopPropagation(); close(true); }
        return;
      }
      if (ev.key !== 'ArrowDown' && ev.key !== 'ArrowUp') return;
      if (!isOpen) { if (ev.key === 'ArrowDown') { ev.preventDefault(); openMenu(); } return; }
      var items = _navItems();
      if (!items.length) return;
      ev.preventDefault();
      var idx = -1;
      for (var i = 0; i < items.length; i++) { if (items[i] === document.activeElement) { idx = i; break; } }
      var next = (ev.key === 'ArrowDown') ? (idx + 1) % items.length : (idx <= 0 ? items.length - 1 : idx - 1);
      try { items[next].focus(); } catch (e) { /* environnement sans focus */ }
    });
    wrap.appendChild(trigger);
    wrap.appendChild(menu);
    return wrap;
  }

  // ── Sélecteur de périmètre (compte principal) ─────────────────────────────
  function _renderPerimeterSelector(focusAfter) {
    var EM = window.CSI_EntityManager;
    var ents = EM.entities().filter(function(e) { return e && !e.archived && !e.disabled; });
    var host = _scopeHost();
    if (!host) return;
    var active = EM.activeScope(); // null = principal
    var existing = document.getElementById('csi-perimeter-wrap');
    if (existing) existing.parentNode.removeChild(existing);
    if (ents.length === 0) { _cleanupScopeHost(); _renderScopeBanner(null); return; }

    var activeName = t('entities.mainEntity', 'Entité principale');
    var items = [{ id: '', name: t('entities.mainEntity', 'Entité principale'), active: !active }];
    ents.forEach(function(e) {
      var nm = e.name || e.id;
      if (active === e.id) activeName = nm;
      items.push({ id: e.id, name: nm, active: active === e.id });
    });
    var wrap = _buildScopeSwitcher({
      id: 'csi-perimeter-wrap',
      label: t('entities.perimeter', 'Périmètre'),
      activeName: activeName,
      isSub: !!active,
      items: items,
      onPick: function(target) {
        var trig = wrap.querySelector('.csi-scope__btn');
        if (trig) trig.disabled = true;
        window._csiSwitchScope(target).then(function() {
          // re-rendu sur l'état RÉEL (EM.activeScope) : bascule le nom en cas de
          // succès, revient à l'ancien périmètre en cas d'échec — bannière incluse.
          _renderPerimeterSelector(true);
        });
      },
      extra: active ? null : {
        label: t('entities.allEntities', 'Toutes les entités'),
        hint: t('entities.consolidatedHint', 'lecture seule'),
        title: t('entities.allEntitiesTitle', 'Vue consolidée en lecture seule (toutes les entités confondues)'),
        onRun: function() { openConsolidatedConsole(); }
      }
    });
    host.appendChild(wrap);
    if (focusAfter) { var tb = wrap.querySelector('.csi-scope__btn'); if (tb) { try { tb.focus(); } catch (e) { /* environnement sans focus */ } } }
    _renderScopeBanner(active);
  }

  // ── Console consolidée « Toutes les entités » (lecture seule) ─────────────
  var SYNC_CONS_LABELS = {
    'risks': 'Risques', 'threats-section': 'Menaces', 'actions': 'Actions',
    'nonconformities': 'Non-conformités', 'critical-assets': 'Actifs critiques',
    'audits': 'Audits', 'documents': 'Documents', 'stakeholders': 'Parties prenantes',
    'kpis': 'Indicateurs', 'managementReviews': 'Revues de direction',
    'project-risks': 'Risques projets', 'risk-analysis': 'Analyses de risques'
  };
  var CONSOLIDATED_SECTIONS = ['risks', 'critical-assets', 'risk-analysis', 'project-risks',
    'actions', 'nonconformities', 'audits', 'threats-section', 'documents', 'stakeholders',
    'kpis', 'managementReviews'];
  var CONSOLIDATED_COLS = {
    'risks': [ { ks: ['title', 'name'], l: 'Intitulé' }, { ks: ['category', 'famille'], l: 'Catégorie' }, { ks: ['likelihood', 'vraisemblance'], l: 'V', n: 1 }, { ks: ['impact'], l: 'I', n: 1 }, { ks: ['status', 'statut'], l: 'Statut' } ],
    'threats-section': [ { ks: ['name', 'title', 'label'], l: 'Menace' }, { ks: ['category', 'type'], l: 'Type' }, { ks: ['status'], l: 'Statut' } ],
    'actions': [ { ks: ['title', 'name'], l: 'Intitulé' }, { ks: ['priority'], l: 'Priorité' }, { ks: ['status'], l: 'Statut' }, { ks: ['dueDate', 'echeance'], l: 'Échéance' }, { ks: ['assignedTo', 'responsable'], l: 'Responsable' } ],
    'nonconformities': [ { ks: ['title', 'name'], l: 'Intitulé' }, { ks: ['severity', 'gravite'], l: 'Sévérité' }, { ks: ['status'], l: 'Statut' } ],
    'critical-assets': [ { ks: ['name', 'title'], l: 'Nom' }, { ks: ['type'], l: 'Type' }, { ks: ['importance'], l: 'Importance' } ],
    'audits': [ { ks: ['title', 'name'], l: 'Intitulé' }, { ks: ['type'], l: 'Type' }, { ks: ['date'], l: 'Date' }, { ks: ['status'], l: 'Statut' } ],
    'documents': [ { ks: ['name', 'title'], l: 'Nom' }, { ks: ['type', 'category'], l: 'Type' }, { ks: ['version'], l: 'Version' }, { ks: ['status'], l: 'Statut' } ],
    'stakeholders': [ { ks: ['name', 'title'], l: 'Nom' }, { ks: ['role', 'category', 'type'], l: 'Rôle' } ],
    'kpis': [ { ks: ['name', 'title', 'label'], l: 'Indicateur' }, { ks: ['value', 'valeur'], l: 'Valeur' }, { ks: ['target', 'cible'], l: 'Cible' } ],
    'managementReviews': [ { ks: ['title', 'name'], l: 'Intitulé' }, { ks: ['date'], l: 'Date' }, { ks: ['status'], l: 'Statut' } ],
    'project-risks': [ { ks: ['title', 'name'], l: 'Intitulé' }, { ks: ['likelihood', 'vraisemblance'], l: 'V', n: 1 }, { ks: ['impact'], l: 'I', n: 1 }, { ks: ['status'], l: 'Statut' } ],
    'risk-analysis': [ { ks: ['name', 'title', 'label'], l: 'Nom' }, { ks: ['status'], l: 'Statut' }, { ks: ['date', 'createdAt'], l: 'Date' } ]
  };
  function _resolveField(item, ks) {
    for (var i = 0; i < ks.length; i++) { var v = item[ks[i]]; if (v != null && v !== '') return v; }
    return '';
  }
  // (audit i18n 2026-07-03) les maps SECTION_LABELS/SYNC_CONS_LABELS/CONSOLIDATED_COLS
  // sont les FALLBACKS français ; la traduction passe par t() AU RENDU (clés
  // entities.sections.* / entities.cols.* — présentes en FR et EN).
  function _secLabel(sid) {
    return t('entities.sections.' + sid, SYNC_CONS_LABELS[sid] || SECTION_LABELS[sid] || sid);
  }
  var COL_KEYS = {
    'Intitulé': 'title', 'Catégorie': 'category', 'V': 'likelihoodShort', 'I': 'impactShort',
    'Statut': 'status', 'Menace': 'threat', 'Type': 'type', 'Priorité': 'priority',
    'Échéance': 'dueDate', 'Responsable': 'assignee', 'Sévérité': 'severity', 'Nom': 'name',
    'Importance': 'importance', 'Date': 'date', 'Version': 'version', 'Rôle': 'role',
    'Indicateur': 'indicator', 'Valeur': 'value', 'Cible': 'target', 'Élément': 'item'
  };
  function _colLabel(l) { var k = COL_KEYS[l]; return k ? t('entities.cols.' + k, l) : l; }
  var _consState = { section: 'risks', items: [], sortKey: null, sortDir: 1, hidden: {}, selected: {} };
  function openConsolidatedConsole() {
    var EM = window.CSI_EntityManager;
    if (!EM || !EM.isMultiEntity() || !EM.isGroupUser() || (EM.activeScope && EM.activeScope())) return;
    var m = _openModal({ id: 'csi-cons-overlay', icon: 'fa-layer-group', title: t('entities.consoleTitle', 'Vue consolidée — toutes les entités'), size: 'modal--lg' });
    m.body.appendChild(el('p', { cls: 'csi-modal__hint', text: t('entities.consoleHint', 'Lecture seule. Chaque ligne porte l\'entité d\'origine. L\'édition se fait dans le périmètre concerné.') }));
    var secSel = el('select', { cls: 'form-control form-control--sm', attrs: { 'aria-label': t('entities.sectionLabel', 'Section') } });
    secSel.id = 'csi-cons-section';
    CONSOLIDATED_SECTIONS.forEach(function(s) {
      var o = el('option', { text: _secLabel(s) }); o.value = s;
      if (s === _consState.section) o.selected = true; secSel.appendChild(o);
    });
    secSel.addEventListener('change', function() { _consState.section = secSel.value; _consState.sortKey = null; _consState.hidden = {}; _consState.selected = {}; _loadCons(); });
    var refreshBtn = el('button', { cls: 'btn btn--sm btn--outline', children: [icon('fa-rotate'), document.createTextNode(" " + t("entities.refresh", "Actualiser"))] });
    refreshBtn.addEventListener('click', _loadCons);
    m.body.appendChild(el('div', { cls: 'csi-modal__controls', children: [
      el('span', { cls: 'csi-perimeter__label', text: t("entities.sectionLabel", "Section") }), secSel, refreshBtn
    ]}));
    var chips = el('div', { cls: 'csi-chips' }); chips.id = 'csi-cons-chips';
    m.body.appendChild(chips);
    var tblWrap = el('div', { cls: 'csi-modal-scroll table-container' }); tblWrap.id = 'csi-cons-tblwrap';
    m.body.appendChild(tblWrap);
    var moveBar = el('div', { cls: 'csi-movebar' }); moveBar.id = 'csi-cons-movebar'; moveBar.style.display = 'none';
    m.body.appendChild(moveBar);
    var foot = el('div', { cls: 'csi-modal__foot' }); foot.id = 'csi-cons-foot';
    m.body.appendChild(foot);
    _loadCons();
  }
  function _loadCons() {
    var wrap = document.getElementById('csi-cons-tblwrap');
    if (!wrap) return;
    while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
    wrap.appendChild(el('p', { cls: 'csi-muted csi-pad', text: t("entities.loading", "Chargement…") }));
    fetch(SYNC_API + '?action=consolidated-read&section=' + encodeURIComponent(_consState.section) + '&limit=1000',
      { headers: { 'Authorization': _auth() } })
      .then(function(r) { return r.json(); })
      .then(function(d) {
        if (!d || !d.ok) { _consError(d && d.error ? d.error : t("entities.loadFailed", "Chargement impossible")); return; }
        _consState.items = d.items || [];
        _consState.total = d.total || _consState.items.length;
        _consState.selected = {};
        _paintChips();
        _paintConsTable();
      })["catch"](function() { _consError(t("entities.networkError", "Erreur réseau")); });
  }
  function _consError(msg) {
    var wrap = document.getElementById('csi-cons-tblwrap');
    if (!wrap) return;
    while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
    wrap.appendChild(el('p', { cls: 'csi-error csi-pad', text: msg }));
  }
  function _paintChips() {
    var chips = document.getElementById('csi-cons-chips');
    if (!chips) return;
    while (chips.firstChild) chips.removeChild(chips.firstChild);
    var seen = {}, order = [];
    _consState.items.forEach(function(it) {
      var e = it._entity || {}; var id = e.id || 'group';
      if (!seen[id]) { seen[id] = e.name || id; order.push(id); }
    });
    if (order.length <= 1) return; // rien à filtrer
    order.forEach(function(id) {
      var on = !_consState.hidden[id];
      var chip = el('button', { cls: on ? 'csi-chip' : 'csi-chip csi-chip--off', text: seen[id], attrs: { type: 'button', 'aria-pressed': on ? 'true' : 'false' } });
      chip.addEventListener('click', function() { _consState.hidden[id] = on; _paintChips(); _paintConsTable(); });
      chips.appendChild(chip);
    });
  }
  function _paintConsTable() {
    var wrap = document.getElementById('csi-cons-tblwrap');
    var foot = document.getElementById('csi-cons-foot');
    if (!wrap) return;
    while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
    var cols = CONSOLIDATED_COLS[_consState.section] || [ { ks: ['title', 'name', 'label', 'id'], l: 'Élément' } ];
    var rows = _consState.items.filter(function(it) { var id = (it._entity || {}).id || 'group'; return !_consState.hidden[id]; });
    if (_consState.sortKey != null) {
      var col = _consState.sortKey === '_entity' ? null : cols[_consState.sortKey];
      rows = rows.slice().sort(function(a, b) {
        var va = col ? _resolveField(a, col.ks) : ((a._entity || {}).name || '');
        var vb = col ? _resolveField(b, col.ks) : ((b._entity || {}).name || '');
        if (col && col.n) { va = parseFloat(va) || 0; vb = parseFloat(vb) || 0; }
        else { va = String(va).toLowerCase(); vb = String(vb).toLowerCase(); }
        return (va < vb ? -1 : va > vb ? 1 : 0) * _consState.sortDir;
      });
    }
    var EM = window.CSI_EntityManager;
    var canMove = !!(EM && EM.canEditSubEntities());
    var tbl = el('table', { cls: 'csi-table' });
    var thr = el('tr', {});
    if (canMove) thr.appendChild(el('th', { cls: 'csi-check-col', attrs: { 'aria-label': t('entities.selectionCol', 'Sélection') } }));
    thr.appendChild(_consTh(t("entities.entityCol", "Entité"), "_entity"));
    cols.forEach(function(c, i) { thr.appendChild(_consTh(_colLabel(c.l), i, c.n)); });
    tbl.appendChild(el('thead', { children: [thr] }));
    var tbody = el('tbody', {});
    rows.forEach(function(it) {
      var tr = el('tr', {});
      var e = it._entity || {};
      var srcId = e.id || 'group';
      var itemId = it.id != null ? String(it.id) : '';
      if (canMove) {
        var cbCell = el('td', { cls: 'csi-num' });
        var _cbTitle = _resolveField(it, (cols[0] || {}).ks || ['title', 'name', 'id']) || itemId;
        var cb = el('input', { type: 'checkbox', attrs: { 'aria-label': t('entities.selectItemAria', 'Sélectionner : {{title}} ({{entity}})', { title: _cbTitle, entity: e.name || srcId }) } });
        var selKey = srcId + '|' + itemId;
        cb.checked = !!_consState.selected[selKey];
        cb.disabled = !itemId;
        cb.addEventListener('change', function() {
          if (cb.checked) _consState.selected[selKey] = { id: itemId, srcId: srcId };
          else delete _consState.selected[selKey];
          _renderMoveBar();
        });
        cbCell.appendChild(cb);
        tr.appendChild(cbCell);
      }
      var badgeCell = el('td', {});
      badgeCell.appendChild(_badge(e.name || e.id || 'Entité principale'));
      tr.appendChild(badgeCell);
      cols.forEach(function(c) {
        tr.appendChild(el('td', { cls: c.n ? 'csi-num' : '', text: String(_resolveField(it, c.ks)) }));
      });
      tbody.appendChild(tr);
    });
    tbl.appendChild(tbody);
    wrap.appendChild(tbl);
    if (rows.length === 0) wrap.appendChild(el('p', { cls: 'csi-muted csi-pad', text: t("entities.noItems", "Aucun élément.") }));
    if (foot) {
      while (foot.firstChild) foot.removeChild(foot.firstChild);
      var shown = rows.length, total = _consState.total || _consState.items.length;
      foot.appendChild(document.createTextNode(
        (shown > 1
          ? t('entities.shownCountPlural', '{{n}} éléments affichés', { n: shown })
          : t('entities.shownCountSingular', '{{n}} élément affiché', { n: shown })) +
        (total > _consState.items.length ? ' — ' + t('entities.shownTotal', '{{total}} au total (1000 max chargés)', { total: total }) : '')));
    }
    _renderMoveBar();
  }

  // ── Assistant de répartition (barre de déplacement dans la console) ───────
  function _selCount() { var n = 0; for (var k in _consState.selected) { if (Object.prototype.hasOwnProperty.call(_consState.selected, k)) n++; } return n; }
  function _renderMoveBar() {
    var EM = window.CSI_EntityManager;
    var bar = document.getElementById('csi-cons-movebar');
    if (!bar) return;
    while (bar.firstChild) bar.removeChild(bar.firstChild);
    if (!EM || !EM.canEditSubEntities()) return;
    var n = _selCount();
    if (n === 0) { bar.style.display = 'none'; return; }
    bar.style.display = 'flex';
    bar.appendChild(el('span', { cls: 'csi-movebar__count', text: t('entities.selectedN', '{{n}} sélectionné(s)', { n: n }) }));
    bar.appendChild(el('span', { cls: 'csi-movebar__arrow', text: t('entities.moveTo', '→ déplacer vers') }));
    var tgt = el('select', { cls: 'form-control form-control--sm', attrs: { 'aria-label': t('entities.moveTargetAria', 'Périmètre cible du déplacement') } }); tgt.id = 'csi-move-target';
    var oMain = el('option', { text: t('entities.mainEntity', 'Entité principale') }); oMain.value = 'group'; tgt.appendChild(oMain);
    EM.entities().filter(function(e) { return e && !e.archived; }).forEach(function(e) {
      var o = el('option', { text: e.name || e.id }); o.value = e.id; tgt.appendChild(o);
    });
    bar.appendChild(tgt);
    var btn = el('button', { cls: 'btn btn--sm btn--primary', text: t('entities.move', 'Déplacer') });
    btn.addEventListener('click', function() { _confirmMove(tgt.value, btn); });
    bar.appendChild(btn);
    var clear = el('button', { cls: 'btn btn--sm btn--secondary', text: t('common.cancel', 'Annuler') });
    clear.addEventListener('click', function() { _consState.selected = {}; _paintConsTable(); });
    bar.appendChild(clear);
  }
  function _confirmMove(targetId, btn) {
    var buckets = {}, total = 0;
    for (var k in _consState.selected) {
      if (!Object.prototype.hasOwnProperty.call(_consState.selected, k)) continue;
      var s = _consState.selected[k];
      if (s.srcId === targetId) continue; // même périmètre : ignoré
      (buckets[s.srcId] = buckets[s.srcId] || []).push(s.id);
      total++;
    }
    if (total === 0) { _toast(t('entities.moveTargetSame', 'Les éléments sélectionnés sont déjà dans le périmètre cible.'), 'info'); return; }
    var tgtName = targetId === 'group' ? 'Entité principale' : targetId;
    window.CSI_EntityManager.entities().forEach(function(e) { if (e && e.id === targetId) tgtName = e.name || e.id; });
    var ok = window.confirm(t('entities.moveConfirm', 'Déplacer {{n}} élément(s) vers « {{name}} » ?\n\nLes éléments sont copiés puis vérifiés avant suppression de la source (aucune perte). L\'édition reste possible ensuite dans le périmètre cible.\n\n⚠ Seuls les éléments de cette section sont déplacés — les objets LIÉS (actions, contrôles, actifs rattachés…) restent dans le périmètre source. Déplacez-les séparément si besoin, ou re-liez-les localement dans le périmètre cible.', { n: total, name: tgtName }));
    if (!ok) return;
    _doMoveBuckets(buckets, targetId, btn);
  }
  function _doMoveBuckets(buckets, targetId, btn) {
    if (btn) btn.disabled = true;
    var sources = [];
    for (var s in buckets) { if (Object.prototype.hasOwnProperty.call(buckets, s)) sources.push(s); }
    var section = _consState.section;
    var totMoved = 0, totSkipped = 0, errors = [];
    function step(i) {
      if (i >= sources.length) {
        var msg = t('entities.moveDone', '{{n}} élément(s) déplacé(s)', { n: totMoved }) + (totSkipped ? ', ' + t('entities.moveSkipped', '{{n}} ignoré(s) (déjà présents)', { n: totSkipped }) : '');
        _toast(errors.length ? (msg + ' — ' + t('entities.moveErrors', '{{n}} échec(s)', { n: errors.length }) + ' : ' + errors[0]) : msg, errors.length ? 'error' : 'success');
        _consState.selected = {};
        _loadCons();
        return;
      }
      var src = sources[i];
      fetch(SYNC_API + '?action=entity-transfer', {
        method: 'POST', headers: _headers(),
        body: JSON.stringify({ section: section, source: src, target: targetId, itemIds: buckets[src] })
      }).then(function(r) { return r.json().then(function(d) { return { status: r.status, d: d }; }); })
        .then(function(res) {
          if (res.d && res.d.ok) { totMoved += (res.d.moved || []).length; totSkipped += (res.d.skipped || []).length; }
          else { errors.push((res.d && res.d.error) ? res.d.error : ('HTTP ' + res.status)); }
          step(i + 1);
        })['catch'](function() { errors.push('Erreur réseau'); step(i + 1); });
    }
    step(0);
  }
  function _consTh(label, key, numeric) {
    // (audit a11y 2026-07-03) tri actionnable au clavier (vrai <button>, focus-visible
    // hérité) + état exposé via aria-sort — l'ancien th cliquable était invisible aux
    // technologies d'assistance et inutilisable sans souris (WCAG 2.1.1 / 4.1.2).
    var arrow = '';
    var th = el('th', { cls: 'csi-sortable' + (numeric ? ' csi-num' : '') });
    if (_consState.sortKey === key) {
      arrow = _consState.sortDir > 0 ? ' ▲' : ' ▼';
      th.setAttribute('aria-sort', _consState.sortDir > 0 ? 'ascending' : 'descending');
    }
    var btn = el('button', { cls: 'csi-th-btn', text: label + arrow, attrs: { type: 'button' } });
    btn.addEventListener('click', function() {
      if (_consState.sortKey === key) _consState.sortDir = -_consState.sortDir;
      else { _consState.sortKey = key; _consState.sortDir = 1; }
      _paintConsTable();
    });
    th.appendChild(btn);
    return th;
  }

  // (Lot 3c, anti-confusion §11.3) favicon badgé quand le périmètre ≠ principal :
  // pastille + initiale de l'entité, teinte alignée sur --color-warning. Restauré au retour.
  var _origFaviconHref;
  function _setScopeFavicon(name) {
    try {
      var link = document.querySelector('link[rel="icon"]');
      if (!link) {
        link = document.createElement('link'); link.rel = 'icon';
        document.head.appendChild(link);
        _origFaviconHref = '';
      } else if (_origFaviconHref === undefined) {
        _origFaviconHref = link.href || '';
      }
      if (!name) {
        if (_origFaviconHref) link.href = _origFaviconHref;
        else if (link.parentNode) link.parentNode.removeChild(link);
        return;
      }
      var c = document.createElement('canvas'); c.width = 32; c.height = 32;
      var g = c.getContext('2d');
      if (!g) return;
      g.fillStyle = '#db9f61'; g.beginPath(); g.arc(16, 16, 16, 0, Math.PI * 2); g.fill(); // --color-warning-dark
      g.fillStyle = '#ffffff'; g.font = 'bold 18px sans-serif';
      g.textAlign = 'center'; g.textBaseline = 'middle';
      g.fillText((String(name).charAt(0) || 'S').toUpperCase(), 16, 18);
      link.href = c.toDataURL('image/png');
    } catch (e) { /* favicon best-effort — jamais bloquant */ }
  }

  // Repères du périmètre actif (anti-confusion) quand ≠ principal : titre
  // d'onglet et favicon badgés. (refonte header 2026-07) L'ancien bandeau
  // « Périmètre : {name} — vous consultez/éditez une sous-entité » est supprimé :
  // le cas nominal est porté par le fil d'Ariane (point orange + nom) + titre/
  // favicon. Clé i18n entities.scopeBanner conservée dans les locales.
  function _renderScopeBanner(entityId) {
    var existing = document.getElementById('csi-scope-banner');
    if (existing) existing.parentNode.removeChild(existing); // purge un bandeau d'une version antérieure
    if (!entityId) {
      if (document.title.indexOf('[') === 0) document.title = document.title.replace(/^\[[^\]]*\]\s*/, '');
      _setScopeFavicon(null);
      return;
    }
    var EM = window.CSI_EntityManager;
    var name = entityId;
    EM.entities().forEach(function(e) { if (e && e.id === entityId) name = e.name || e.id; });
    document.title = '[' + String(name).replace(/[\[\]]/g, '') + '] Cyber-Assistant';
    _setScopeFavicon(name);
  }

  // ── Tuile « Vue groupe » (tableau de bord, compte principal) ──────────────
  var _groupTileAt = 0;
  function _relTime(ts) {
    if (!ts) return t('entities.noActivity', 'aucune activité');
    var now = Math.floor(Date.now() / 1000), d = now - ts;
    if (d < 60) return t('entities.justNow', 'à l\'instant');
    if (d < 3600) return t('entities.minsAgo', 'il y a {{n}} min', { n: Math.floor(d / 60) });
    if (d < 86400) return t('entities.hoursAgo', 'il y a {{n}} h', { n: Math.floor(d / 3600) });
    return t('entities.daysAgo', 'il y a {{n}} j', { n: Math.floor(d / 86400) });
  }
  function renderGroupTile() {
    var EM = window.CSI_EntityManager;
    if (!EM || !EM.isMultiEntity() || !EM.isGroupUser()) return;
    if (EM.activeScope()) {                            // masquée quand on consulte une sous-entité
      var t = document.getElementById('csi-group-tile');
      if (t && t.parentNode) t.parentNode.removeChild(t);
      _groupTileAt = 0;
      return;
    }
    var host = document.getElementById('dashboard');
    if (!host) return;
    var fresh = document.getElementById('csi-group-tile');
    if (fresh && (Date.now() - _groupTileAt) < 15000) return;
    _groupTileAt = Date.now();
    fetch(SYNC_API + '?action=group-activity', { headers: { 'Authorization': _auth() } })
      .then(function(r) { return r.json(); })
      .then(function(d) {
        if (!d || !d.ok || !d.enabled) return;
        _paintGroupTile(host, (d.entities || []));
      })['catch'](function() {});
  }
  function _paintGroupTile(host, ents) {
    var old = document.getElementById('csi-group-tile');
    if (old) old.parentNode.removeChild(old);
    var card = el('div', { cls: 'csi-tile' });
    card.id = 'csi-group-tile';
    var head = el('div', { cls: 'csi-tile__head', children: [
      icon('fa-layer-group'),
      el('h3', { cls: 'csi-tile__title', text: t('entities.tileTitle', 'Vue groupe — activité des sous-entités') }),
      el('span', { cls: 'csi-tile__count', text: t('entities.subEntitiesN', '{{n}} sous-entité(s)', { n: ents.length }) })
    ]});
    card.appendChild(head);
    if (ents.length === 0) {
      card.appendChild(el('p', { cls: 'csi-tile__empty', text: t('entities.tileEmpty', 'Aucune sous-entité configurée. Ajoutez-en depuis Réglages ▸ Multi-entités.') }));
      _mountGroupTile(host, card); return;
    }
    var tbl = el('table', { cls: 'csi-table' });
    var thr = el('tr', {});
    [['sub', t('entities.colSubEntity', 'Sous-entité')], ['act', t('entities.colLastActivity', 'Dernière activité')], ['by', t('entities.colBy', 'Par')], ['sec', t('entities.colSections', 'Sections')], ['state', t('entities.colState', 'État')], ['x', '']].forEach(function(pair) {
      var thOpts = { cls: (pair[0] === 'sec') ? 'csi-num' : '', text: pair[1] };
      if (pair[0] === 'x') thOpts.attrs = { 'aria-label': t('entities.actionsCol', 'Actions') };
      thr.appendChild(el('th', thOpts));
    });
    tbl.appendChild(el('thead', { children: [thr] }));
    var tbody = el('tbody', {});
    ents.forEach(function(e) {
      var tr = el('tr', {});
      var nameCell = el('td', { cls: 'csi-strong' });
      nameCell.appendChild(document.createTextNode(e.name || e.id));
      if (e.archived) nameCell.appendChild(el('span', { cls: 'csi-muted csi-suffix', text: t('entities.archivedTag', '(archivée)') }));
      else if (e.disabled) nameCell.appendChild(el('span', { cls: 'csi-muted csi-suffix', text: t('entities.disabledTag', '(désactivée)') }));
      tr.appendChild(nameCell);
      tr.appendChild(el('td', { cls: 'csi-muted', text: _relTime(e.lastActivity) }));
      tr.appendChild(el('td', { cls: 'csi-muted', text: e.lastActivityBy || '—' }));
      tr.appendChild(el('td', { cls: 'csi-num', text: String(e.sectionCount || 0) }));
      var stateCell = el('td', {});
      if (e.lockHolder) {
        stateCell.appendChild(el('span', { cls: 'csi-lock-pill', children: [icon('fa-lock'), document.createTextNode(' ' + t('entities.inEdition', 'En édition · {{name}}', { name: e.lockHolder }))] }));
      } else {
        stateCell.appendChild(el('span', { cls: 'csi-free', text: t('entities.free', 'Libre') }));
      }
      tr.appendChild(stateCell);
      var actCell = el('td', { cls: 'csi-right' });
      // (audit 2026-07-03, K11) désactivée : le serveur refuse la bascule (403) — aligné
      // sur le sélecteur de périmètre qui exclut archivées ET désactivées.
      if (!e.archived && !e.disabled) {
        var btn = el('button', { cls: 'btn btn--sm btn--outline', text: t('entities.consult', 'Consulter') });
        btn.addEventListener('click', function() {
          if (!window._csiSwitchScope) return;
          btn.disabled = true;
          window._csiSwitchScope(e.id).then(function(res) {
            btn.disabled = false;
            // re-rendu de la pastille de périmètre sur l'état réel (bannière incluse)
            if (res && res.ok) _renderPerimeterSelector();
          });
        });
        actCell.appendChild(btn);
      }
      tr.appendChild(actCell);
      tbody.appendChild(tr);
    });
    tbl.appendChild(tbody);
    card.appendChild(tbl);
    _mountGroupTile(host, card);
  }
  function _mountGroupTile(host, card) {
    var alerts = document.getElementById('dashboardAlerts');
    if (alerts && alerts.parentNode === host) host.insertBefore(card, alerts.nextSibling);
    else host.appendChild(card);
  }

  // (Lot 3c) Sélecteur de périmètre RESTREINT d'un compte multi-appartenance :
  // uniquement SES entités. Réutilise la bascule éprouvée window._csiSwitchScope.
  function _renderOwnEntitySelector(focusAfter) {
    var EM = window.CSI_EntityManager;
    var mine = EM.entities();
    var existing = document.getElementById('csi-ownent-wrap');
    if (existing) existing.parentNode.removeChild(existing);
    if (!mine || mine.length < 2) { _cleanupScopeHost(); return; }
    var host = _scopeHost();
    if (!host) return;
    var ids = (EM.info() && EM.info().entityIds) || [];
    var active = EM.activeScope() || ids[0];
    var activeName = '';
    var items = [];
    mine.forEach(function(e) {
      var nm = e.name || e.id;
      if (active === e.id) activeName = nm;
      items.push({ id: e.id, name: nm, active: active === e.id });
    });
    var wrap = _buildScopeSwitcher({
      id: 'csi-ownent-wrap',
      label: t('entities.myEntity', 'Mon entité'),
      activeName: activeName || (items[0] && items[0].name) || '',
      isSub: false,
      items: items,
      onPick: function(target) {
        var trig = wrap.querySelector('.csi-scope__btn');
        if (trig) trig.disabled = true;
        window._csiSwitchScope(target === ids[0] ? '' : target).then(function(res) {
          _renderOwnEntitySelector(true); // re-rendu sur l'état réel (succès ou échec)
          if (res && res.ok) _renderEntityBanner(EM.info());
        });
      },
      extra: null
    });
    host.appendChild(wrap);
    if (focusAfter) { var tb = wrap.querySelector('.csi-scope__btn'); if (tb) { try { tb.focus(); } catch (e) { /* environnement sans focus */ } } }
  }

  function _activeEntityLabel(info) {
    var EM = window.CSI_EntityManager;
    var ids = (info && info.entityIds) || [];
    var active = (EM && EM.activeScope()) || ids[0];
    var name = info && info.entityName;
    (EM ? EM.entities() : []).forEach(function(e) { if (e && e.id === active) name = e.name || e.id; });
    return name;
  }

  function _renderEntityBanner(info) {
    if (!info) return;
    var activeName = _activeEntityLabel(info);
    // (audit a11y 2026-07-03) titre d'onglet + favicon badgé aussi pour les comptes de
    // sous-entité — la bascule « Mon entité » (multi-appartenance) laissait le titre
    // générique, seul repère fiable quand plusieurs onglets sont ouverts.
    if (activeName) {
      document.title = '[' + String(activeName).replace(/[\[\]]/g, '') + '] Cyber-Assistant';
      _setScopeFavicon(activeName);
    }
    var label = activeName ? t('entities.spaceLabel', 'Espace {{name}}', { name: activeName }) : t('entities.spaceGeneric', 'Espace sous-entité');
    var existing = document.getElementById('csi-entity-banner');
    if (existing) { var sp = existing.querySelector('.csi-banner__label'); if (sp) sp.textContent = label; return; }
    var span = el('span', { cls: 'csi-banner__label', text: label });
    var b = el('div', { cls: 'csi-banner csi-banner--entity', attrs: { role: 'status' }, children: [icon('fa-building'), document.createTextNode(' '), span] });
    b.id = 'csi-entity-banner';
    var EM = window.CSI_EntityManager;
    if (EM && EM.groupViewSections && EM.groupViewSections().length > 0) {
      var gvBtn = el('button', { cls: 'csi-banner__action', text: t('entities.groupDataBtn', 'Données du groupe (lecture)') });
      gvBtn.addEventListener('click', openGroupViewConsole);
      b.appendChild(gvBtn);
    }
    var header = document.querySelector('.app-header') || document.querySelector('header');
    if (header && header.parentNode) header.parentNode.insertBefore(b, header.nextSibling);
  }

  // ── (Lot 3b) Console « Données du groupe » — lecture seule, sous-entités ──
  function openGroupViewConsole() {
    var EM = window.CSI_EntityManager;
    if (!EM || !EM.isSubEntityUser()) return;
    var m = _openModal({ id: 'csi-gv-overlay', icon: 'fa-building', title: t('entities.groupConsoleTitle', 'Données de l\'entité principale — lecture seule'), size: 'modal--md' });
    m.body.appendChild(el('p', { cls: 'csi-modal__hint', text: t('entities.groupConsoleHint', 'Sections partagées en lecture par l\'entité principale. Pour lier un de ces éléments à vos encodages, déclinez-le localement dans votre espace.') }));
    var sections = EM.groupViewSections();
    var secSel = el('select', { cls: 'form-control form-control--sm', attrs: { 'aria-label': t('entities.sectionLabel', 'Section') } }); secSel.id = 'csi-gv-section';
    sections.forEach(function(s) {
      var o = el('option', { text: _secLabel(s) }); o.value = s; secSel.appendChild(o);
    });
    secSel.addEventListener('change', function() { _paintGvTable(secSel.value); });
    var refreshBtn = el('button', { cls: 'btn btn--sm btn--outline', children: [icon('fa-rotate'), document.createTextNode(" " + t("entities.refresh", "Actualiser"))] });
    refreshBtn.addEventListener('click', function() {
      refreshBtn.disabled = true;
      _fetchGroupView(function() { refreshBtn.disabled = false; _paintGvTable(secSel.value); });
    });
    m.body.appendChild(el('div', { cls: 'csi-modal__controls', children: [
      el('span', { cls: 'csi-perimeter__label', text: t("entities.sectionLabel", "Section") }), secSel, refreshBtn
    ]}));
    var tblWrap = el('div', { cls: 'csi-modal-scroll table-container' }); tblWrap.id = 'csi-gv-tblwrap';
    m.body.appendChild(tblWrap);
    if (sections.length > 0) {
      _paintGvTable(sections[0]);
      _fetchGroupView(function() { _paintGvTable(secSel.value); });
    } else {
      tblWrap.appendChild(el('p', { cls: 'csi-muted csi-pad', text: t('entities.noSharedSection', 'Aucune section partagée.') }));
    }
  }
  // Récupération LÉGÈRE du sidecar : fetch direct de l'export, on n'en garde que
  // _csiGroupView (pas d'application à appData, pas d'updateLastPull — zéro effet de bord).
  function _fetchGroupView(done) {
    fetch(SYNC_API, { headers: { 'Authorization': _auth() } })
      .then(function(r) { return r.json(); })
      .then(function(d) {
        var EM = window.CSI_EntityManager;
        if (d && d._csiGroupView && EM && EM.ingestGroupView) EM.ingestGroupView(d._csiGroupView);
        if (done) done();
      })['catch'](function() { if (done) done(); });
  }
  function _paintGvTable(section) {
    var wrap = document.getElementById('csi-gv-tblwrap');
    if (!wrap) return;
    while (wrap.firstChild) wrap.removeChild(wrap.firstChild);
    var EM = window.CSI_EntityManager;
    var items = (EM.groupViewData() || {})[section] || [];
    var cols = CONSOLIDATED_COLS[section] || [ { ks: ['title', 'name', 'label', 'id'], l: 'Élément' } ];
    var tbl = el('table', { cls: 'csi-table' });
    var thr = el('tr', {});
    cols.forEach(function(c) { thr.appendChild(el('th', { cls: c.n ? 'csi-num' : '', text: _colLabel(c.l) })); });
    tbl.appendChild(el('thead', { children: [thr] }));
    var tbody = el('tbody', {});
    items.forEach(function(it) {
      if (!it || typeof it !== 'object') return;
      var tr = el('tr', {});
      cols.forEach(function(c) {
        tr.appendChild(el('td', { cls: c.n ? 'csi-num' : '', text: String(_resolveField(it, c.ks)) }));
      });
      tbody.appendChild(tr);
    });
    tbl.appendChild(tbody);
    wrap.appendChild(tbl);
    if (items.length === 0) wrap.appendChild(el('p', { cls: 'csi-muted csi-pad', text: t('entities.noSharedItems', 'Aucun élément partagé dans cette section (ou données pas encore synchronisées).') }));
  }

  // ── Stub inerte en mode local ─────────────────────────────────────────────
  if (window.CSI_LicenseManager && !window.CSI_LicenseManager.isServerMode() &&
      (window.CSI_LicenseManager.isDetected() || window.location.protocol === 'file:')) {
    window.CSI_EntityUI = { renderSettingsSection: function() {}, applySubEntityView: function() {}, renderGroupTile: function() {}, openConsolidatedConsole: function() {} };
    void 0;
    return;
  }
  window.CSI_EntityUI = { renderSettingsSection: renderSettingsSection, applySubEntityView: applySubEntityView, renderGroupTile: renderGroupTile, openConsolidatedConsole: openConsolidatedConsole, sectionLabel: _secLabel };
  void 0;
})();
