// ═══════════════════════════════════════════════════════════════════════════
// ELEA · Traceur — Mode standalone (VPS)
// useFileFetch remplace useFile de Dust
// ═══════════════════════════════════════════════════════════════════════════
const { useState, useEffect, useRef, useMemo, useCallback } = React;

function useFileFetch(url: string): { content: string | null; isError: boolean } {
  const [content, setContent] = useState<string | null>(null);
  const [isError, setIsError] = useState(false);
  useEffect(() => {
    fetch(url)
      .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
      .then(setContent)
      .catch(e => { console.error('useFileFetch:', e); setIsError(true); });
  }, [url]);
  return { content, isError };
}

import { useFile, triggerUserFileDownload } from "@dust/react-hooks";
import { ChevronDown, ChevronRight, Download, Settings, Plus, X, MessageCircle, AlertCircle } from "lucide-react";
import { Button } from "shadcn";

// ─── Types ─────────────────────────────────────────────────────────────────────
type Them = { c: string; l: string; bg: string; fg: string };
type Obj  = { r: string; l: string; ch: number; t: string };
type Crit = { r: string; l: string; o: string; ch: number; t: string };
type Q    = { tc: string; it: string; q: string; src: string };
type EE   = { id: string; lt: string; nv: string; tx: string; cr: string; t: string; g: string; cvs: string; ed: string[]; eo: string[]; qs: Q[] };
type Ref  = { T: Them[]; O: Obj[]; C: Crit[]; E: EE[] };

type EESaisie = {
  cotation: string;
  constats: string;  // par EE
  justif_nc: string; // visible si NC
};
// Saisie par CRITÈRE — terrain + post-visite (comme Synaé)
type StatutCritere = "non_saisi"|"saisi"|"rédigé"|"cohérence_ok"|"validé";
type CritVersion   = { commentaire_n: string; justification_nc: string; date: string };

type CritSaisie = {
  edp_docs: string[];        // EDP documentation cochés (critère)
  edp_docs_custom: string[]; // EDP documentation terrain libres (critère)
  edp_obs: string[];         // EDP observations cochées (critère)
  edp_obs_custom: string[];  // EDP observations terrain libres (critère)
  edp_bib_ids: string[];     // IDs des items Cockpit (edpBib) cochés
  pts_forts: string;         // notes terrain : points forts
  axes_prog: string;         // notes terrain : axes de progrès
  edp_synae: string;         // post-visite → col H Synaé
  // Rédaction Synaé versionnée (3 versions)
  versions: [CritVersion, CritVersion, CritVersion]; // v1=terrain · v2=après A15 · v3=après cohérence
  version_courante: 0|1|2;   // version active pour l'export Synaé
  statut_critere: StatutCritere;
};
type Interlocuteur = { fonction: string; initiales: string };
type TabContexte   = {
  // AT / AT-RC
  initiales: string; profil: string; duree: string; notes: string;
  // TC / TC-RC / AS / AS-RC : liste des interlocuteurs
  interlocuteurs: Interlocuteur[];
};
type TabSaisie   = { contexte: TabContexte; ees: Record<string, EESaisie>; crits: Record<string, CritSaisie>; themes: string[] };
type Config      = { nbAT: number; nbTC: number; hasCVS: boolean };
type Session     = {
  id_cockpit: string;             // liaison avec CockpitESSMS (= n_synae, ex: 'EV-2026-018')
  essms: string; date: string; config: Config; grilles: Record<string, TabSaisie>;
  // Phase 1 — Promesse / Prospection
  typeMission?: string;           // "evaluation_has" | "accompagnement" | "expertise"
  oeCommanditaire?: string;       // "ETIKEVAL" | "AMPLEA" | "2VAN" | "GALATA"
  datePrevue?: string;            // date prévisionnelle de visite
  urlKDrive?: string;             // lien dossier kDrive
  phaseActuelle?: number;         // 1–8 selon ELEA_DEROULEMENT_8PHASES
  annule?: boolean;               // mission annulée (phase 1 uniquement)
  statut?: "preparation"|"visite"|"post_visite"|"livre"; // compat legacy
  // Phase 2 — Mission (BDC + FINESS + contacts)
  finess?: string;                // FINESS géographique (ET)
  finessJuridique?: string;       // FINESS entité juridique
  categorieFiness?: string;       // catégorie FINESS (code 3 chiffres)
  typeEssms?: string;             // libellé type ESSMS
  statutJuridique?: string;       // "associatif" | "public" | "prive_lucratif"
  capacite?: string;              // nb places autorisées
  adresse?: string;               // adresse postale
  departement?: string;           // département
  region?: string;                // région
  nomOG?: string;                 // organisme gestionnaire
  siretOG?: string;               // SIRET OG
  oe?: string;                    // OE (legacy)
  bdc?: string;                   // N° BDC / devis
  dateBdc?: string;               // date signature BDC
  montantHT?: string;             // montant HT BDC (€)
  numInterneOE?: string;          // N° interne OE (ex: EVE-XXXXXXXXX)
  referentOe?: string;            // référent OE
  dateRhapsody?: string;          // date envoi RHAPSODY
  urlSynae?: string;              // lien espace Synaé
  urlDriveOE?: string;            // lien drive OE
  contactNom?: string;            // contact direction ESSMS
  contactEmail?: string;
  contactTel?: string;
  contactQualite?: string;        // référent qualité ESSMS
  dpoCnil?: string;               // "trouve" | "non_trouve" | "a_verifier"
  dpoEmail?: string;
  dpoDateVerif?: string;
  roleEmmanuel?: string;          // "coordonnateur" | "binome"
  nomCoordonnateur?: string;      // si role = binome
  // Config grilles & équipe
  dateDebut?: string; dateFin?: string; nbJours?: string;
  evaluateurs?: Array<{nom: string; initiales: string; role: string}>;
  // Phase 3 — Cadrage (Coordonnateur uniquement)
  dateCadrage?: string;
  interlocuteurCadrage?: string;
  checklistCadrage?: { courrier: boolean; planning: boolean; accesSynae: boolean; accesDrive: boolean };
  notesGlobales?: string;
  documentsDemandesStr?: string;
  edpBib?: EDPBibItem[];              // bibliothèque EDP réutilisables
  edpBibLinks?: EDPBibLink[];         // liaisons EDP ↔ critères
  edpDrafts?: EDPDraft[];             // brouillons EDP en cours
  a11Result?: string;                 // résultats @A11 collés
};

// ─── Constants ─────────────────────────────────────────────────────────────────
const COTS = [
  { v: "1",  l: "1",  t: "Pas du tout satisfaisant",  bg: "#FEE2E2", bd: "#EF4444", tx: "#991B1B" },
  { v: "2",  l: "2",  t: "Plutôt pas satisfaisant",   bg: "#FFEDD5", bd: "#F97316", tx: "#9A3412" },
  { v: "3",  l: "3",  t: "Plutôt satisfaisant",        bg: "#FEF9C3", bd: "#EAB308", tx: "#713F12" },
  { v: "4",  l: "4",  t: "Tout à fait satisfaisant",   bg: "#DCFCE7", bd: "#22C55E", tx: "#14532D" },
  { v: "*",  l: "★",  t: "Optimisé",                   bg: "#D1FAE5", bd: "#10B981", tx: "#064E3B" },
  { v: "NC", l: "NC", t: "Non concerné → justifier",   bg: "#F3F4F6", bd: "#9CA3AF", tx: "#374151" },
  { v: "RI", l: "RI", t: "Réponse inadaptée (Ch. 1)",  bg: "#EDE9FE", bd: "#7C3AED", tx: "#4C1D95" },
];
const GRILLE_IT: Record<string, string> = {
  AT: "PA", "AT-RC": "PROF", TC: "PROF", "TC-RC": "GOUV", AS: "GOUV", "AS-RC": "PROF", CVS: ""
};
const GRILLE_G: Record<string, string> = {
  AT: "AT", "AT-RC": "AT-RC", TC: "TC", "TC-RC": "TC-RC", AS: "AS", "AS-RC": "AS-RC"
};
const ELEA_LOGO_B64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFN0lEQVR42u1XS2yUVRT+zr3/PzOd6dBSaHlWqhYIbUVNC0LTpIXwkjRGTdqFGzAhWBbuNFFjMp0F0aUbFxASwQSJ06hojIS4mKlpEcoAbUofFEhJaWtbauljnv/89x4XLQKKpS9g41n+/z3nfOc75557DvCMheasyUxT+gwifmqIA4GA9AXZePBbVSAgn4pznowaAFD8U9i9selG0frTp70AAJ9PzMWmmI1zIuLvu3rKP2i++VHEtJoxMdZqenPbC35pPAi/X8+FCTFD54KI+FjrTb/t8YY6bfUZmY61bCVBrFc7FmcdKTpzrqauulrNFsRji9DHLPxE+svW7g1Zbmd7dySqv+odUi4pJAMCYAVpEDQnSfG61r1beuHzCfj9ekEYqAhNnjGl3u52u3kgbikmmPS3LklO2Vqme9Mg9Ts+ZlFeUSEWLgUVU25YZBMRRfQjAiMCQcOy+SU/kV7f5aWFL0JwFAAUa/q3EZbxWEy/mJlR3tjd++rR90pSPmaxIABCoRAAQGt9U9k2DCJ6BAOkLAt5blduR0KdCfb1La0F2DeDqzmDFFRoAIjb8nI8FrU8hiHBeKjzaQArXU5qGR5OHR2NLvuk7fYXRMTthYW0IK34Xg841XnrwnVNm7++1avShJD8QBREgKUZICiZ5paciJW17S5trAoEZF11tZpXDYRCkAAgCHVLPB4YhCQDSUxlQwOwGTCIIJhYSpNJYx8ADGVn07yLMBSq1QDg1PSNjESSiwxDKeYE3a/QBAHMk0wInUwQgF1rgkFX/bZt9nRMzwiA3+/X+4JB15sb1vSnE06udJhpFnMKzHEwEgBiuF+cxHYKIMp1p1y5U91sHgCmHqDbyrmEmemuUj+scjqvMrNNBGZwBABICIAnSQARMzghBcYBALXguQMg4oJAwKEkmRQKyd9GJtpMSZeXO8w7ccUdhhD1AOIAAUQExoRwOIlADVd3bB30MYvp5oXH3oI1waArK+UuWmSiN0s4ajxCkCm5Z5nTuTk8Gi28nkhqt6RxQfSyILFaa91NhpmrE4lN7ZVlLWAmEOk5M5AHQOrEYBqMT4UhchyCmp7zuM+mp6XVbsn0XNyWmW5kSMMrNHqslLpCQIqt+NvtlWXNxUfCBoBpp6XHPp37jx/Xp9bmjRUeOBSTzGaWQUEmsVcq7clxGMMrXc6hdZmZw9kuM8MBvtAfSx3qrCwLg5ly7vTkeq/36ZH8VSnU1/PsUzBJH1eGw+6RRELmwXvALWjoWOnGk76WrtU5JLNg0PKhu+PNMnNx9uDE+MGowrfHreGmrUlvvi3VnYs7X/tzXp2w+Ex4hcuA2bij+DaIuOZ864de0xzI58h3ESuHhSe+KcZYOhCxWpJSrRi3+a1RpRoiGv0N20su3AtidgCYqaCuzeT0sUKDONb6etk1XzBodDiySrnvWmP+8xv3C9L24ZKCE8wsDl/qeGVUwTmhlXPYVi8kbHtVt40TprZyW3aXNk4Hgv6L9qIff1+mnext31N6ozgcNheP8U5p0JWz5SV/AMDH4Y7PTdPp0VpfsuzU1pjm+IRte8aVbYyzyE1a1sGGHZu6fbW15J9mOqKZNKLSXy+vIJ3KbtyzpaUqwLKumvT757u8aaauMZ2OwUg0tsRicW1cpRZN2HZGlAwRT9inz+0q7p9bCu79Y8ZDyveNTS4kgYB8d3n++piiWHRCDf38RknsyW1G/4yEmXwAdTe1b0jG41laiE4M3BgBqgDUoa6qSj/5jWnqnagMh93lwaDxbLZLZsL/Mg/5C5vEkLTYoPJSAAAAAElFTkSuQmCC";


const GRILLE_LABELS: Record<string, string> = {
  AT: "🧑 Accompagné Traceur", "AT-RC": "👥 Regard croisé Prof.",
  TC: "👔 Traceur Ciblé", "TC-RC": "🏛 Regard croisé Gouv.",
  AS: "🏛 Audit Système", "AS-RC": "👔 Regard croisé Prof.", CVS: "📋 CVS / CI"
};
const STORAGE   = "elea_grille_v2";
const REF_CACHE = "elea_ref_cache_v2";

// ── IndexedDB — remplace localStorage pour la persistence offline ─────────────
const IDB_NAME    = "elea_terrain_db";
const IDB_VERSION = 1;
const STORE_SESS  = "sessions";
const STORE_CACHE = "cache";

function openIDB(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open(IDB_NAME, IDB_VERSION);
    req.onupgradeneeded = (e) => {
      const db = (e.target as IDBOpenDBRequest).result;
      if (!db.objectStoreNames.contains(STORE_SESS))  db.createObjectStore(STORE_SESS);
      if (!db.objectStoreNames.contains(STORE_CACHE)) db.createObjectStore(STORE_CACHE);
    };
    req.onsuccess = (e) => resolve((e.target as IDBOpenDBRequest).result);
    req.onerror   = (e) => reject((e.target as IDBOpenDBRequest).error);
  });
}

async function loadSessIDB(id_cockpit?: string): Promise<Session> {
  try {
    const db  = await openIDB();
    const key = id_cockpit || STORAGE;
    return new Promise((resolve) => {
      const tx  = db.transaction(STORE_SESS, "readonly");
      const req = tx.objectStore(STORE_SESS).get(key);
      req.onsuccess = () => {
        if (req.result) {
          resolve(migrateSess(req.result));
        } else if (id_cockpit) {
          // Nouvelle session depuis URL params Cockpit
          resolve(initSess());
        } else {
          resolve(initSess());
        }
      };
      req.onerror = () => resolve(initSess());
    });
  } catch { return initSess(); }
}

// Lit les URL params envoyés par CockpitESSMS
function readCockpitParams(): Partial<Session> | null {
  try {
    const p = new URLSearchParams(window.location.search);
    const id = p.get("id_cockpit");
    if (!id) return null;
    return {
      id_cockpit: id,
      essms:        p.get("essms")       ?? "",
      finess:       p.get("finess")      ?? "",
      oeCommanditaire: p.get("oe")       ?? "",
      datePrevue:   p.get("date_visite") ?? "",
    };
  } catch { return null; }
}

async function saveSessIDB(s: Session): Promise<void> {
  try {
    const db  = await openIDB();
    const key = s.id_cockpit || STORAGE; // clé par mission si id_cockpit renseigné
    return new Promise((resolve, reject) => {
      const tx  = db.transaction(STORE_SESS, "readwrite");
      const req = tx.objectStore(STORE_SESS).put(s, key);
      req.onsuccess = () => resolve();
      req.onerror   = () => reject(req.error);
    });
  } catch (e) { console.warn("IndexedDB saveSess error:", e); }
}

async function saveRefIDB(data: string): Promise<void> {
  try {
    const db = await openIDB();
    const tx  = db.transaction(STORE_CACHE, "readwrite");
    tx.objectStore(STORE_CACHE).put(data, REF_CACHE);
  } catch { /* silently ignore */ }
}

async function loadRefIDB(): Promise<string | null> {
  try {
    const db = await openIDB();
    return new Promise((resolve) => {
      const tx  = db.transaction(STORE_CACHE, "readonly");
      const req = tx.objectStore(STORE_CACHE).get(REF_CACHE);
      req.onsuccess = () => resolve(req.result ?? null);
      req.onerror   = () => resolve(null);
    });
  } catch { return null; }
}

// Cotation calculée d'un critère (moyenne des EE numériques)
function critCot(ees: EE[], saisies: Record<string, EESaisie>): string {
  const nums = ees.map(e => saisies[e.id]?.cotation ?? "")
    .filter(v => ["1","2","3","4"].includes(v)).map(Number);
  if (!nums.length) return "";
  return (nums.reduce((a,b)=>a+b,0)/nums.length).toFixed(1);
}

function initTab(): TabSaisie {
  return { contexte: { initiales: "", profil: "", duree: "", notes: "", interlocuteurs: [] }, ees: {}, crits: {}, themes: [] };
}

// Migration : ancien format → nouveau (préserve cotations, constats, justif_nc)
function migrateSess(raw: any): Session {
  const grilles: Record<string, TabSaisie> = {};
  for (const [tabKey, tab] of Object.entries(raw.grilles ?? {})) {
    const t = tab as any;
    const ees: Record<string, EESaisie> = {};
    for (const [id, ee] of Object.entries(t.ees ?? {})) {
      const e = ee as any;
      ees[id] = { cotation: e.cotation ?? "", constats: e.constats ?? "", justif_nc: e.justif_nc ?? "" };
    }
    // Migrate old crit-level or EE-level pts_forts/axes_prog → CritSaisie
    const crits: Record<string, CritSaisie> = {};
    for (const [cr, cs] of Object.entries(t.crits ?? {})) {
      const c = cs as any;
      crits[cr] = {
        edp_docs: c.edp_docs ?? [], edp_docs_custom: c.edp_docs_custom ?? [],
        edp_obs: c.edp_obs ?? [],   edp_obs_custom: c.edp_obs_custom ?? [], edp_bib_ids: c.edp_bib_ids ?? [],
        pts_forts: c.pts_forts ?? "", axes_prog: c.axes_prog ?? "",
        edp_synae: c.edp_synae ?? "",
        // Migration : ancien commentaire_synae → v2, ancien justif_nc → v2.justification_nc
        versions: [
          { commentaire_n: "", justification_nc: "", date: "" },
          { commentaire_n: c.commentaire_synae ?? "", justification_nc: c.justif_nc ?? "", date: "" },
          { commentaire_n: "", justification_nc: "", date: "" },
        ],
        version_courante: (c.commentaire_synae ? 1 : 0) as 0|1|2,
        statut_critere: (c.validated ? "validé" : c.commentaire_synae ? "rédigé" : (c.pts_forts || c.axes_prog) ? "saisi" : "non_saisi") as StatutCritere,
      };
    }
    grilles[tabKey] = {
      contexte: {
        initiales: t.contexte?.initiales ?? "", profil: t.contexte?.profil ?? "",
        duree: t.contexte?.duree ?? "", notes: t.contexte?.notes ?? "",
        interlocuteurs: t.contexte?.interlocuteurs ?? [],
      },
      ees, crits, themes: t.themes ?? [],
    };
  }
  return {
    id_cockpit: raw.id_cockpit ?? "", essms: raw.essms ?? "", date: raw.date ?? "",
    config: raw.config ?? { nbAT: 1, nbTC: 1, hasCVS: false },
    grilles,
  };
}
function initSess(): Session {
  return { id_cockpit: "", essms: "", date: "", config: { nbAT: 1, nbTC: 1, hasCVS: false }, grilles: {} };
}
// loadSess() retourne initSess() synchrone — l'état réel est chargé en async dans useEffect
function loadSess(): Session { return initSess(); }
function saveSess(s: Session) { saveSessIDB(s).catch(e => console.warn("saveSess error:", e)); }

function buildTabs(cfg: Config): string[] {
  const t: string[] = [];
  for (let i = 1; i <= cfg.nbAT; i++) { t.push(`AT${i}`); t.push(`AT-RC${i}`); }
  for (let i = 1; i <= cfg.nbTC; i++) t.push(`TC${i}`);
  t.push("TC-RC", "AS", "AS-RC");
  if (cfg.hasCVS) t.push("CVS");
  return t;
}
function tabType(tab: string): string {
  if (tab.startsWith("AT-RC")) return "AT-RC";
  if (tab.startsWith("AT"))    return "AT";
  if (tab.startsWith("TC-RC")) return "TC-RC";
  if (tab.startsWith("TC"))    return "TC";
  if (tab === "AS-RC")         return "AS-RC";
  if (tab === "AS")            return "AS";
  return "CVS";
}
const emptyEE   = (): EESaisie => ({ cotation: "", constats: "", justif_nc: "" });
const emptyVer  = (): CritVersion  => ({ commentaire_n: "", justification_nc: "", date: "" });
const emptyCrit = (): CritSaisie => ({
  edp_docs: [], edp_docs_custom: [], edp_obs: [], edp_obs_custom: [], edp_bib_ids: [],
  pts_forts: "", axes_prog: "",
  edp_synae: "",
  versions: [emptyVer(), emptyVer(), emptyVer()],
  version_courante: 0,
  statut_critere: "non_saisi",
});

// ─── EDP checklist ─────────────────────────────────────────────────────────────
function EdpList({ items, checked, custom, onToggle, onAdd, onRemove, placeholder }: {
  items: string[]; checked: string[]; custom: string[];
  onToggle: (k: string) => void; onAdd: (v: string) => void;
  onRemove: (v: string) => void; placeholder: string;
}) {
  const [adding, setAdding] = useState(false);
  const [txt, setTxt] = useState("");
  const commit = () => { if (txt.trim()) { onAdd(txt.trim()); setTxt(""); } setAdding(false); };
  return (
    <div className="space-y-1">
      {items.map((item, i) => {
        const k = `h${i}`; const on = checked.includes(k);
        return (
          <label key={i} className="flex items-start gap-2 cursor-pointer">
            <input type="checkbox" className="mt-0.5 flex-shrink-0" checked={on} onChange={() => onToggle(k)}/>
            <span className={`text-xs leading-snug ${on ? "font-medium text-foreground" : "text-muted-foreground"}`}>{item}</span>
          </label>
        );
      })}
      {custom.map((item, i) => (
        <div key={i} className="flex items-start gap-1">
          <input type="checkbox" className="mt-0.5 flex-shrink-0" checked readOnly/>
          <span className="text-xs flex-1 font-medium text-teal-700">{item}</span>
          <button onClick={() => onRemove(item)}><X className="h-3 w-3 text-muted-foreground hover:text-destructive"/></button>
        </div>
      ))}
      {adding ? (
        <div className="flex gap-1 mt-1">
          <input autoFocus className="flex-1 text-xs border rounded px-2 py-1" placeholder={placeholder}
            value={txt} onChange={e => setTxt(e.target.value)}
            onKeyDown={e => { if (e.key === "Enter") commit(); if (e.key === "Escape") setAdding(false); }}/>
          <button onClick={commit} className="text-xs px-2 py-1 rounded border border-indigo-400 text-teal-700 hover:bg-teal-50">OK</button>
        </div>
      ) : (
        <button onClick={() => setAdding(true)} className="flex items-center gap-1 text-xs text-teal-600 hover:text-teal-800 mt-1">
          <Plus className="h-3 w-3"/>Ajouter sur terrain
        </button>
      )}
    </div>
  );
}

// ─── Question guide bubble ──────────────────────────────────────────────────────
function QGuide({ questions }: { questions: Q[] }) {
  const [open, setOpen] = useState(false);
  if (!questions.length) return null;
  return (
    <div className="relative inline-block"
      onMouseEnter={() => setOpen(true)}
      onMouseLeave={() => setOpen(false)}>
      <span className="flex items-center gap-1 text-xs text-amber-700 bg-amber-50 border border-amber-300 rounded-full px-2 py-0.5 cursor-default select-none">
        <MessageCircle className="h-3 w-3"/>Guide ({questions.length})
      </span>
      {open && (
        <div className="absolute left-0 top-6 z-50 w-80 rounded-xl border bg-amber-50 shadow-xl p-3 space-y-2">
          <span className="text-xs font-bold text-amber-800 block mb-1">Guide de questionnement</span>
          {questions.map((q, i) => (
            <div key={i} className="rounded bg-white border border-amber-200 px-2 py-1.5">
              <span className="text-xs font-semibold text-amber-700 block mb-0.5">{q.it}</span>
              <p className="text-xs text-foreground leading-snug">{q.q}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ─── EE card ───────────────────────────────────────────────────────────────────
function EECard({ ee, saisie, onUpdate, grille, postVisit }: {
  ee: EE; saisie: EESaisie; onUpdate: (s: EESaisie) => void; grille: string; postVisit: boolean;
}) {
  const it         = GRILLE_IT[grille] ?? "";
  const questions  = it ? ee.qs.filter(q => q.it === it) : ee.qs;
  const cot        = COTS.find(c => c.v === saisie.cotation);
  const v          = saisie.cotation;
  const showNC     = v === "NC";
  const isRI       = v === "RI";

  return (
    <div className="rounded-xl border-2 overflow-hidden mb-2.5"
      style={{ borderColor: cot ? cot.bd : "#E5E7EB" }}>

      {/* ── En-tête EE ── */}
      <div className="px-3 pt-2.5 pb-2 flex items-start gap-2"
        style={{ backgroundColor: cot ? `${cot.bg}CC` : "#F9FAFB" }}>
        <div className="h-6 w-6 rounded-full flex-shrink-0 flex items-center justify-center text-sm font-extrabold"
          style={{ backgroundColor: cot?.bd ?? "#9CA3AF", color: "white" }}>
          {ee.lt.toUpperCase()}
        </div>
        <div className="flex-1 min-w-0">
          <div className="flex flex-wrap items-center gap-1.5 mb-0.5">
            <span className="text-xs font-mono text-muted-foreground">{ee.id}</span>
            {ee.nv === "Impératif" && (
              <span className="px-1 py-0.5 rounded text-xs font-bold bg-red-200 text-red-800 border border-red-400">⚠ Impératif</span>
            )}
            {ee.cvs && (
              <span className="px-1 py-0.5 rounded text-xs font-bold bg-blue-100 text-blue-700 border border-blue-300">{ee.cvs}</span>
            )}
            <QGuide questions={questions}/>
          </div>
          <p className="text-sm font-semibold text-foreground leading-snug">{ee.tx}</p>
        </div>
      </div>

      {/* ── Cotation ── */}
      <div className="px-3 py-2 bg-background border-t border-muted">
        <p className="text-xs font-bold uppercase tracking-wide text-muted-foreground mb-1.5">Cotation</p>
        <div className="flex flex-wrap gap-1.5 items-center">
          {COTS.map(c => {
            const sel = v === c.v;
            return (
              <button key={c.v} title={c.t}
                onClick={() => onUpdate({ ...saisie, cotation: sel ? "" : c.v })}
                style={sel ? { backgroundColor: c.bg, borderColor: c.bd, color: c.tx, transform: "scale(1.15)", boxShadow: `0 0 0 2px ${c.bd}40` }
                           : { backgroundColor: "white", borderColor: "#D1D5DB", color: "#6B7280" }}
                className="px-3 py-1.5 rounded-lg border-2 text-sm font-extrabold transition-all">
                {c.l}
              </button>
            );
          })}
          {cot && <span className="text-xs italic ml-1" style={{ color: cot.tx }}>{cot.t}</span>}
        </div>
      </div>



      {/* ── Constats (toujours visible) ── */}
      <div className="px-3 py-2 border-t border-muted bg-background">
        <label className="block text-xs font-semibold text-slate-700 mb-1">
          📝 Constats
          {isRI && <span className="ml-1 text-purple-700 font-bold">(indiquez ici la justification du RI)</span>}
        </label>
        <textarea rows={3} className="w-full rounded border border-slate-200 px-2 py-1.5 text-xs resize-y focus:outline-none focus:ring-2 focus:ring-slate-400 bg-slate-50"
          placeholder={isRI ? "Justification de la réponse inadaptée…" : "Constats terrain, observations saillantes…"}
          value={saisie.constats} onChange={e => onUpdate({...saisie, constats: e.target.value})}/>
      </div>

      {/* ── Justification NC ── */}
      {showNC && (
        <div className="px-3 py-2 border-t border-gray-200 bg-gray-50">
          <label className="block text-xs font-bold text-gray-700 mb-1">⚠ Justification NC obligatoire</label>
          <textarea rows={2} className="w-full rounded border border-gray-300 px-2 py-1.5 text-xs resize-y focus:outline-none focus:ring-2 focus:ring-gray-400"
            placeholder="Pourquoi cet EE ne concerne pas l'ESSMS évalué ?"
            value={saisie.justif_nc} onChange={e => onUpdate({...saisie, justif_nc: e.target.value})}/>
        </div>
      )}




    </div>
  );
}

// ─── Critère block ──────────────────────────────────────────────────────────────
function CritBlock({ crit, ees, thMap, tabSaisie, onUpdate, isImp, hasAlert, anchor, isOpen, onToggle, grille, postVisit, edpBib }: {
  crit: Crit; ees: EE[]; thMap: Record<string, Them>;
  tabSaisie: TabSaisie; onUpdate: (t: TabSaisie) => void;
  isImp: boolean; hasAlert: boolean; anchor: string; isOpen: boolean; onToggle: () => void; grille: string; postVisit: boolean;
  edpBib?: EDPBibItem[];
}) {
  const [showPreview, setShowPreview] = useState(false);
  const th    = thMap[crit.t];
  const done  = ees.filter(ee => tabSaisie.ees[ee.id]?.cotation).length;
  const cs    = tabSaisie.crits?.[crit.r] ?? emptyCrit();
  const setCS = (c: CritSaisie) => onUpdate({ ...tabSaisie, crits: { ...(tabSaisie.crits ?? {}), [crit.r]: c } });
  const upEE  = (id: string, s: EESaisie) => onUpdate({ ...tabSaisie, ees: { ...tabSaisie.ees, [id]: s } });

  const autoGenComment = () => {
    // Constats bruts par EE (sans libellé EE), séparés par saut de ligne
    // puis pts_forts, puis axes_prog (règles de rédaction appliquées ensuite via prompt ELEA)
    const blocks: string[] = [];
    ees.forEach(ee => {
      const c = tabSaisie.ees[ee.id]?.constats?.trim();
      if (c) blocks.push(c);
    });
    if (cs.pts_forts?.trim()) blocks.push(cs.pts_forts.trim());
    if (cs.axes_prog?.trim()) blocks.push(cs.axes_prog.trim());
    if (blocks.length) {
      const vers = [...cs.versions] as typeof cs.versions;
      vers[0] = { ...vers[0], commentaire_n: blocks.join("\n\n"), date: new Date().toISOString() };
      setCS({ ...cs, versions: vers, statut_critere: "saisi" });
    }
  };
  const autoGenEDP = () => {
    // EDP cochés au niveau du critère (liste unifiée)
    const seenD = new Set<string>(); const docsAll: string[] = [];
    const seenO = new Set<string>(); const obsAll:  string[] = [];
    ees.forEach(ee => {
      ee.ed.forEach(d => { if (!seenD.has(d)) { seenD.add(d); docsAll.push(d); } });
      ee.eo.forEach(o => { if (!seenO.has(o)) { seenO.add(o); obsAll.push(o); } });
    });
    const docs = cs.edp_docs.map(k => { const i = parseInt(k.slice(1)); return docsAll[i] ?? k; });
    const obs  = cs.edp_obs.map(k  => { const i = parseInt(k.slice(1)); return obsAll[i] ?? k; });
    const parts = [
      ...docs.filter(Boolean),
      ...cs.edp_docs_custom,
      ...obs.filter(Boolean),
      ...cs.edp_obs_custom,
    ].filter(Boolean);
    if (parts.length) setCS({ ...cs, edp_synae: "- " + parts.join("\n- ") });
  };

  return (
    <div id={anchor} className="mb-3 rounded-xl border-2 overflow-hidden shadow-sm scroll-mt-4"
      style={{ borderColor: hasAlert ? "#DC2626" : isImp ? "#FCA5A5" : "#E5E7EB" }}>
      <button onClick={onToggle}
        className="w-full flex items-center gap-2 px-3 py-2.5 text-left transition-colors cursor-pointer hover:brightness-95"
        style={{ backgroundColor: hasAlert ? "#FEE2E2" : isImp ? "#FEE2E2" : th ? `#${th.bg}30` : "#F9FAFB" }}>
        {isOpen ? <ChevronDown className="h-4 w-4 flex-shrink-0 text-muted-foreground"/>
                : <ChevronRight className="h-4 w-4 flex-shrink-0 text-muted-foreground"/>}
        <span className="px-1.5 py-0.5 rounded text-xs font-bold flex-shrink-0"
          style={{ backgroundColor: `#${th?.bg}`, color: `#${th?.fg}` }}>{crit.t}</span>
        <span className="font-mono font-bold text-teal-700 text-sm flex-shrink-0">{crit.r}</span>
        {isImp && <span className="flex-shrink-0 text-xs font-bold text-red-700 bg-pink-100 px-1 rounded border border-red-300">CI</span>}
        <span className="flex-1 text-sm font-medium text-foreground truncate">{crit.l}</span>
        <span className="flex-shrink-0 text-xs font-bold"
          style={{ color: done===ees.length?"#059669":done>0?"#D97706":"#9CA3AF" }}>
          {done}/{ees.length}
        </span>
        {postVisit && (
          <span className={`flex-shrink-0 text-xs font-bold px-1.5 py-0.5 rounded ${
            cs.statut_critere === "validé" ? "bg-emerald-100 text-emerald-700" :
            cs.statut_critere === "cohérence_ok" ? "bg-orange-100 text-orange-700" :
            cs.statut_critere === "rédigé" ? "bg-amber-100 text-amber-700" :
            cs.statut_critere === "saisi"  ? "bg-blue-100 text-blue-600" :
            "bg-gray-100 text-gray-400"}`}>
            {cs.statut_critere === "validé" ? "✓ Synaé" :
             cs.statut_critere === "cohérence_ok" ? "🔍" :
             cs.statut_critere === "rédigé" ? "📝" :
             cs.statut_critere === "saisi"  ? "✏️" : "○"}
          </span>
        )}
      </button>
      {hasAlert && (
        <div className="flex items-center gap-2 px-3 py-1.5 bg-red-600 text-white text-xs font-bold">
          <span>⚠</span>
          <span>Un ou plusieurs EE impératifs de ce critère sont cotés 1, NC ou RI — vérification requise</span>
        </div>
      )}
      {isOpen && (
        <>
        {/* ── 1. EDP COCHABLES par critère — liste unique agrégée (terrain + post-visite) ── */}
        {(() => {
          // Dédupliquer les EDP de tous les EEs du critère
          const seenD = new Set<string>(); const docsAll: string[] = [];
          const seenO = new Set<string>(); const obsAll:  string[] = [];
          ees.forEach(ee => {
            ee.ed.forEach(d => { if (!seenD.has(d)) { seenD.add(d); docsAll.push(d); } });
            ee.eo.forEach(o => { if (!seenO.has(o)) { seenO.add(o); obsAll.push(o); } });
          });
          // Bib items split by type
          const bibDocs = (edpBib??[]).filter(x=>x.type==="document"||x.type==="procedure"||x.type==="autre");
          const bibObs  = (edpBib??[]).filter(x=>x.type==="observation");
          const hasAny = cs.edp_docs_custom.length > 0 || cs.edp_obs_custom.length > 0 || bibDocs.length > 0 || bibObs.length > 0;
          if (!hasAny) return null;
          return (
            <div className="px-3 py-2 border-t border-teal-100 bg-teal-50/40">
              <p className="text-xs font-bold text-teal-700 mb-2">📋 Éléments de preuve (critère)</p>
              <div className="grid gap-3" style={{ gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))" }}>
                {(bibDocs.length > 0 || cs.edp_docs_custom.length > 0) && (
                  <div>
                    <p className="text-xs font-semibold text-teal-600 mb-1">📄 Documentation</p>
                    {/* Cockpit préparés */}
                    {bibDocs.length > 0 && (
                      <div className="mb-1.5">
                        <span className="text-xs font-semibold text-teal-400 mb-0.5 block">📋 Préparés (Cockpit)</span>
                        {bibDocs.map(item => {
                          const on = cs.edp_bib_ids.includes(item.id);
                          return (
                            <label key={item.id} className="flex items-start gap-2 cursor-pointer mb-0.5">
                              <input type="checkbox" className="mt-0.5 flex-shrink-0" checked={on}
                                onChange={() => { const n = on ? cs.edp_bib_ids.filter(x=>x!==item.id) : [...cs.edp_bib_ids,item.id]; setCS({...cs,edp_bib_ids:n}); }}/>
                              <span className={`text-xs leading-snug ${on?"font-medium text-teal-700":"text-slate-500"}`}>{item.label}</span>
                            </label>
                          );
                        })}
                      </div>
                    )}
                    {/* Terrain — saisie libre */}
                    <EdpList items={[]} checked={[]} custom={cs.edp_docs_custom}
                      onToggle={()=>{}}
                      onAdd={v2 => setCS({...cs,edp_docs_custom:[...cs.edp_docs_custom,v2]})}
                      onRemove={v2 => setCS({...cs,edp_docs_custom:cs.edp_docs_custom.filter(x=>x!==v2)})}
                      placeholder="Document observé…"/>
                  </div>
                )}
                {(bibObs.length > 0 || cs.edp_obs_custom.length > 0) && (
                  <div>
                    <p className="text-xs font-semibold text-teal-600 mb-1">👁 Observations</p>
                    {bibObs.length > 0 && (
                      <div className="mb-1.5">
                        <span className="text-xs font-semibold text-teal-400 mb-0.5 block">📋 Préparées (Cockpit)</span>
                        {bibObs.map(item => {
                          const on = cs.edp_bib_ids.includes(item.id);
                          return (
                            <label key={item.id} className="flex items-start gap-2 cursor-pointer mb-0.5">
                              <input type="checkbox" className="mt-0.5 flex-shrink-0" checked={on}
                                onChange={() => { const n = on ? cs.edp_bib_ids.filter(x=>x!==item.id) : [...cs.edp_bib_ids,item.id]; setCS({...cs,edp_bib_ids:n}); }}/>
                              <span className={`text-xs leading-snug ${on?"font-medium text-teal-700":"text-slate-500"}`}>{item.label}</span>
                            </label>
                          );
                        })}
                      </div>
                    )}
                    <EdpList items={[]} checked={[]} custom={cs.edp_obs_custom}
                      onToggle={()=>{}}
                      onAdd={v2 => setCS({...cs,edp_obs_custom:[...cs.edp_obs_custom,v2]})}
                      onRemove={v2 => setCS({...cs,edp_obs_custom:cs.edp_obs_custom.filter(x=>x!==v2)})}
                      placeholder="Observation terrain…"/>
                  </div>
                )}
              </div>
            </div>
          );
        })()}

        {/* ── 2. EE cards — constats par EE ── */}
        <div className="px-3 py-3 bg-background">
          {ees.map(ee => (
            <EECard key={ee.id} ee={ee} grille={grille} postVisit={postVisit}
              saisie={tabSaisie.ees[ee.id] ?? emptyEE()}
              onUpdate={s => upEE(ee.id, s)}/>
          ))}
        </div>

        {/* ── 3. POINTS FORTS + AXES par critère ── */}
        {(() => {
          const avg   = parseFloat(critCot(ees, tabSaisie.ees));
          // Terrain : toujours les deux. Post-visite : règle sur la moyenne
          const showPF = !postVisit || (postVisit && !isNaN(avg) && avg >= 2);
          const showAP = !postVisit || (postVisit && !isNaN(avg) && avg <= 3);
          if (!showPF && !showAP) return null;
          return (
            <div className="px-3 py-2 border-t border-muted bg-background">
              <div style={{ display: "grid", gap: 8, gridTemplateColumns: showPF && showAP ? "1fr 1fr" : "1fr" }}>
                {showPF && (
                  <div>
                    <label className="block text-xs font-semibold text-emerald-700 mb-1">
                      ✅ Points forts {postVisit ? "rédigés" : "(critère)"}
                    </label>
                    <textarea rows={postVisit ? 3 : 2}
                      className="w-full rounded border border-emerald-200 px-2 py-1 text-xs resize-y focus:outline-none focus:ring-2 focus:ring-emerald-300 bg-emerald-50"
                      placeholder={postVisit ? "Phrases factuelles, présent, objectivées…" : "Mots-clés points forts…"}
                      value={cs.pts_forts} onChange={e => setCS({...cs, pts_forts: e.target.value})}/>
                  </div>
                )}
                {showAP && (
                  <div>
                    <label className="block text-xs font-semibold text-orange-700 mb-1">
                      🔧 Axes de progrès {postVisit ? "rédigés" : "(critère)"}
                    </label>
                    <textarea rows={postVisit ? 3 : 2}
                      className="w-full rounded border border-orange-200 px-2 py-1 text-xs resize-y focus:outline-none focus:ring-2 focus:ring-orange-300 bg-orange-50"
                      placeholder={postVisit ? "Objectif attendu (pas la solution)…" : "Mots-clés axes de progrès…"}
                      value={cs.axes_prog} onChange={e => setCS({...cs, axes_prog: e.target.value})}/>
                  </div>
                )}
              </div>
            </div>
          );
        })()}

        {/* ── POST-VISITE : commentaire et EDP au niveau du critère (format Synaé) ── */}
        {postVisit && (
          <div className="border-t-2 border-violet-400 bg-violet-50 px-4 py-3 space-y-3">
            <div className="flex items-center gap-2 flex-wrap">
              <span className="text-sm font-bold text-violet-800">✍️ Post-visite — {crit.r}</span>
              <span className="text-xs text-violet-400">(EDP + commentaire par critère)</span>
            </div>

            {/* 1. EDP rédigés */}
            <div>
              <div className="flex items-center justify-between mb-1">
                <label className="text-xs font-bold text-teal-700">📋 Éléments de preuve (col. H Synaé)</label>
                <button onClick={autoGenEDP}
                  className="text-xs text-teal-600 border border-teal-300 rounded px-2 py-0.5 hover:bg-teal-50">
                  ↻ Générer depuis terrain
                </button>
              </div>
              <textarea rows={3} className="w-full rounded-lg border border-teal-200 px-3 py-2 text-xs resize-y focus:outline-none focus:ring-2 focus:ring-teal-300 bg-white font-mono"
                placeholder="Éléments de preuve consultés (un par ligne)…"
                value={cs.edp_synae} onChange={e => setCS({ ...cs, edp_synae: e.target.value })}/>
            </div>

            {/* Justification NC (col I) */}
            {ees.some(ee => tabSaisie.ees[ee.id]?.cotation === "NC") && (
              <div>
                <label className="text-xs font-bold text-gray-600 mb-1 block">⚠ Éléments justificatifs NC (col. I Synaé)</label>
                <textarea rows={2} className="w-full rounded-lg border border-gray-300 px-3 py-2 text-xs resize-y focus:outline-none focus:ring-2 focus:ring-gray-400 bg-white"
                  placeholder="Justification du non-concerné…"
                  value={cs.justif_nc} onChange={e => setCS({ ...cs, justif_nc: e.target.value })}/>
              </div>
            )}

            {/* 2. Constats par EE en lecture seule */}
            {ees.some(ee => tabSaisie.ees[ee.id]?.constats) && (
              <div className="rounded-lg bg-slate-50 border border-slate-200 px-3 py-2 space-y-1">
                <p className="text-xs font-bold text-slate-500 mb-1">📝 Constats par EE (référence)</p>
                {ees.map(ee => {
                  const es = tabSaisie.ees[ee.id]; if (!es?.constats) return null;
                  return (
                    <p key={ee.id} className="text-xs text-slate-700 leading-snug">
                      <span className="font-bold text-teal-600">{ee.lt.toUpperCase()}</span>
                      {ee.nv==="Impératif" && <span className="text-pink-600 font-bold"> ⚠</span>}
                      {" · "}cot. <span className="font-semibold">{es.cotation || "—"}</span>
                      {" — "}{es.constats}
                    </p>
                  );
                })}
              </div>
            )}

            {/* 3. Rédaction Synaé — 3 versions + Agent A15 */}
            <div className="space-y-3">
              {/* Barre d'actions */}
              <div className="flex flex-wrap items-center gap-2 p-2.5 bg-violet-50 border border-violet-200 rounded-xl">
                <span className={`text-xs font-bold px-2 py-1 rounded-full border ${
                  cs.statut_critere === "validé"      ? "bg-emerald-100 text-emerald-700 border-emerald-300" :
                  cs.statut_critere === "cohérence_ok"? "bg-orange-100 text-orange-700 border-orange-300" :
                  cs.statut_critere === "rédigé"      ? "bg-amber-100 text-amber-700 border-amber-300" :
                  cs.statut_critere === "saisi"       ? "bg-blue-100 text-blue-700 border-blue-300" :
                  "bg-gray-100 text-gray-500 border-gray-300"}`}>
                  {cs.statut_critere === "validé" ? "✅ Validé" :
                   cs.statut_critere === "cohérence_ok" ? "🔍 Cohérence OK" :
                   cs.statut_critere === "rédigé" ? "📝 Rédigé" :
                   cs.statut_critere === "saisi" ? "✏️ Saisi" : "○ Non saisi"}
                </span>
                <div className="flex-1" />
                {/* Bouton A15 */}
                <button onClick={() => {
                  const ctx = ees.map(ee => {
                    const es = tabSaisie.ees[ee.id];
                    return { ref: ee.id.split("_").pop(), libelle: ee.tx, cotation: es?.cotation ?? "", notes_terrain: es?.constats ?? "", objectivation: (cs.edp_docs_custom ?? []).join(", ") };
                  });
                  const json = JSON.stringify({ critere_ref: crit.r, critere_libelle: crit.l, cotation_finale: critCot(ees, tabSaisie.ees) || "", ees: ctx }, null, 2);
                  navigator.clipboard?.writeText(json);
                  setCS({ ...cs, statut_critere: cs.statut_critere === "non_saisi" ? "saisi" : cs.statut_critere });
                }} className="flex items-center gap-1.5 px-3 py-1.5 bg-teal-600 hover:bg-teal-700 text-white text-xs font-semibold rounded-lg transition-colors">
                  ✍️ Copier JSON → A15
                </button>
                {/* Bouton Analyser cohérence */}
                <button
                  disabled={cs.statut_critere === "non_saisi"}
                  onClick={() => setCS({ ...cs, statut_critere: "cohérence_ok" })}
                  className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold rounded-lg transition-colors ${
                    cs.statut_critere === "non_saisi" ? "bg-gray-100 text-gray-400 cursor-not-allowed" :
                    "bg-orange-500 hover:bg-orange-600 text-white"}`}>
                  🔍 Cohérence
                </button>
                {/* Bouton Valider */}
                <button
                  disabled={cs.statut_critere === "non_saisi" || cs.statut_critere === "saisi"}
                  onClick={() => setCS({ ...cs, statut_critere: cs.statut_critere === "validé" ? "rédigé" : "validé" })}
                  className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold rounded-lg border-2 transition-colors ${
                    cs.statut_critere === "validé" ? "bg-emerald-500 text-white border-emerald-600" :
                    (cs.statut_critere === "cohérence_ok" || cs.statut_critere === "rédigé") ? "bg-white border-emerald-500 text-emerald-700 hover:bg-emerald-50" :
                    "bg-gray-100 text-gray-400 border-gray-200 cursor-not-allowed"}`}>
                  {cs.statut_critere === "validé" ? "✓ Validé Synaé" : "Valider →"}
                </button>
              </div>

              {/* 3 versions */}
              {([0, 1, 2] as const).map(idx => {
                const labels = ["v1 — Notes terrain", "v2 — Après A15", "v3 — Après cohérence"];
                const isCur = cs.version_courante === idx;
                const v = cs.versions[idx];
                return (
                  <div key={idx} className={`rounded-xl border-2 p-3 transition-all ${isCur ? "border-violet-400 bg-violet-50" : "border-gray-200 bg-white"}`}>
                    <div className="flex items-center justify-between mb-2">
                      <span className={`text-xs font-bold ${isCur ? "text-violet-700" : "text-gray-500"}`}>
                        {isCur ? "⭐ " : ""}{labels[idx]}
                      </span>
                      {v.date && <span className="text-xs text-gray-400">{new Date(v.date).toLocaleDateString("fr-FR")}</span>}
                    </div>
                    {/* Col J */}
                    <textarea rows={4} className={`w-full rounded-lg border px-3 py-2 text-sm resize-y focus:outline-none focus:ring-2 ${isCur ? "border-violet-300 focus:ring-violet-400" : "border-gray-200 focus:ring-gray-300"}`}
                      placeholder={idx === 0 ? "Agréger les notes terrain ici…" : idx === 1 ? "Coller la réponse de l'agent A15…" : "Ajuster après analyse de cohérence…"}
                      value={v.commentaire_n}
                      onChange={e => {
                        const vers = [...cs.versions] as typeof cs.versions;
                        vers[idx] = { ...vers[idx], commentaire_n: e.target.value, date: new Date().toISOString() };
                        setCS({ ...cs, versions: vers, statut_critere: idx >= 1 && e.target.value ? "rédigé" : cs.statut_critere });
                      }}/>
                    {v.commentaire_n && (
                      <div className="flex gap-3 mt-1 text-xs text-gray-400">
                        <span>{v.commentaire_n.length} car.</span>
                        {!v.commentaire_n.trim().endsWith(".") && <span className="text-amber-600">⚠ Terminer par un point</span>}
                      </div>
                    )}
                    {/* Justif NC col I */}
                    <input type="text" className="w-full mt-2 rounded-lg border border-gray-200 px-3 py-1.5 text-xs focus:outline-none focus:ring-1 focus:ring-gray-300"
                      placeholder="Col. I — Justification NC (laisser vide si cotation 1–4)"
                      value={v.justification_nc}
                      onChange={e => {
                        const vers = [...cs.versions] as typeof cs.versions;
                        vers[idx] = { ...vers[idx], justification_nc: e.target.value, date: new Date().toISOString() };
                        setCS({ ...cs, versions: vers });
                      }}/>
                    {/* Bouton définir courante */}
                    {!isCur && v.commentaire_n && (
                      <button onClick={() => setCS({ ...cs, version_courante: idx })}
                        className="mt-2 text-xs px-2 py-1 bg-violet-600 text-white rounded-lg hover:bg-violet-700 transition-colors">
                        Définir comme version active →
                      </button>
                    )}
                    {isCur && <div className="mt-1 text-xs text-violet-600 font-medium">← Version active pour l'export Synaé</div>}
                  </div>
                );
              })}
            </div>

            {/* ── Aperçu Synaé (version courante) ── */}
            {(cs.edp_synae || cs.versions[cs.version_courante].commentaire_n) && (
              <div className="border border-violet-200 rounded-lg overflow-hidden">
                <button onClick={() => setShowPreview(p => !p)}
                  className="w-full flex items-center justify-between px-3 py-1.5 bg-violet-100 hover:bg-violet-200 transition-colors text-left">
                  <span className="text-xs font-bold text-violet-800">👁 Aperçu Synaé — {crit.r} (v{cs.version_courante + 1})</span>
                  <span className="text-xs text-violet-500">{showPreview ? "▲ masquer" : "▼ afficher"}</span>
                </button>
                {showPreview && (() => {
                  const curV = cs.versions[cs.version_courante];
                  return (
                    <div className="bg-white px-4 py-3 space-y-3">
                      {cs.edp_synae && (
                        <div>
                          <p className="text-xs font-bold text-teal-700 mb-1">Col. H — Éléments de preuve consultés</p>
                          <div className="rounded border border-teal-100 bg-teal-50 px-3 py-2 text-xs text-slate-700 whitespace-pre-wrap leading-relaxed">{cs.edp_synae}</div>
                        </div>
                      )}
                      {curV.justification_nc && (
                        <div>
                          <p className="text-xs font-bold text-gray-600 mb-1">Col. I — Éléments justificatifs NC</p>
                          <div className="rounded border border-gray-200 bg-gray-50 px-3 py-2 text-xs text-slate-700 whitespace-pre-wrap leading-relaxed">{curV.justification_nc}</div>
                        </div>
                      )}
                      {curV.commentaire_n && (
                        <div>
                          <p className="text-xs font-bold text-violet-700 mb-1">Col. J — Commentaires</p>
                          <div className="rounded border border-violet-200 bg-violet-50 px-3 py-2 text-sm text-slate-800 leading-relaxed whitespace-pre-wrap">{curV.commentaire_n}</div>
                          <div className="flex gap-3 mt-1 text-xs text-slate-400">
                            <span>{curV.commentaire_n.length} caractères</span>
                            <span>{curV.commentaire_n.split(/[.!?]+/).filter(s => s.trim()).length} phrase(s)</span>
                          </div>
                        </div>
                      )}
                    </div>
                  );
                })()}
              </div>
            )}

            {/* Statut EEs */}
            <div className="flex items-center gap-3 pt-1 border-t border-violet-200 text-xs text-violet-600">
              {done === ees.length
                ? <span className="text-emerald-600 font-semibold">✓ Tous les EE cotés</span>
                : <span className="text-amber-600">⚠ {ees.length - done} EE(s) non coté(s)</span>}
              {cs.versions[cs.version_courante].commentaire_n
                ? <span className="text-emerald-600">✓ Commentaire v{cs.version_courante + 1} actif</span>
                : <span className="text-pink-500">✗ Commentaire manquant</span>}
            </div>
          </div>
        )}
        </>
      )}
    </div>
  );
}

// ─── Main ───────────────────────────────────────────────────────────────────────
// ── OBJECTIVATION VIEW v2 ─────────────────────────────────────────────────────
type EDPBibItem = {
  id: string;
  type: "formation"|"document"|"observation"|"procedure"|"autre";
  label: string; date?: string; organisme?: string; themes?: string; note?: string;
};
type EDPBibLink = {
  bibId: string; critRef: string; tabKey: string; absent?: boolean; note?: string;
};
// Draft EDP text per critère (for reformulation before push)
type EDPDraft = { critRef: string; tabKey: string; text: string; };

const BIB_TYPES: Record<EDPBibItem["type"],{icon:string;label:string;color:string;bg:string}> = {
  formation:   {icon:"🎓",label:"Formation / Sensibilisation",color:"#7c3aed",bg:"#f5f3ff"},
  document:    {icon:"📄",label:"Document",                   color:"#1d4ed8",bg:"#e0f7f9"},
  observation: {icon:"👁",label:"Observation",                color:"#166534",bg:"#f0fdf4"},
  procedure:   {icon:"📋",label:"Procédure",                  color:"#b45309",bg:"#fffbeb"},
  autre:       {icon:"📌",label:"Autre",                      color:"#64748b",bg:"#f1f5f9"},
};

function ObjectivationView({ sess, rd, onUpdate, onNavigate }: {
  sess: Session; rd: Ref;
  onUpdate: (patch: Partial<Session>) => void;
  onNavigate: (tabKey: string, critRef: string) => void;
}) {
  type SubTab = "global"|"bib"|"couverture";
  const [subTab, setSubTab] = useState<SubTab>("global");
  const [search, setSearch] = useState("");
  const [filterStatus, setFilterStatus] = useState<"all"|"ok"|"partial"|"none">("all");
  const [expandedCrit, setExpandedCrit] = useState<Set<string>>(new Set());
  const [showBibForm, setShowBibForm] = useState(false);
  const [editBibItem, setEditBibItem] = useState<EDPBibItem|null>(null);
  const [pushModal, setPushModal] = useState<{critRef:string;tabKey:string;oldText:string;newText:string}|null>(null);

  const bib: EDPBibItem[]   = sess.edpBib   ?? [];
  const links: EDPBibLink[] = sess.edpBibLinks ?? [];
  const drafts: EDPDraft[]  = sess.edpDrafts ?? [];

  const tabs = buildTabs(sess.config);

  // ── Helpers ────────────────────────────────────────────────────────────────
  const newId = () => `edp_${Date.now()}_${Math.random().toString(36).slice(2,6)}`;

  const terrainEDPFor = (critRef: string): { docs:string[]; obs:string[] } => {
    const docs: string[] = []; const obs: string[] = [];
    tabs.forEach(tab => {
      const cs = sess.grilles[tab]?.crits[critRef]; if (!cs) return;
      const ees = rd.E.filter(e=>e.cr===critRef);
      const seenD = new Set<string>(); const seenO = new Set<string>();
      ees.forEach(e=>{ e.ed.forEach(d=>{if(!seenD.has(d)){seenD.add(d);}}) ; e.eo.forEach(o=>{if(!seenO.has(o)){seenO.add(o);}}) });
      const docsAll = [...seenD]; const obsAll = [...seenO];
      cs.edp_docs.forEach(k=>{const d=docsAll[parseInt(k.slice(1))];if(d&&!docs.includes(d))docs.push(d);});
      cs.edp_docs_custom.forEach(d=>{if(!docs.includes(d))docs.push(d);});
      (cs.edp_bib_ids??[]).forEach(id=>{const item=(sess.edpBib??[]).find(x=>x.id===id);if(item&&!docs.includes(item.label))docs.push(item.label);});
      cs.edp_obs.forEach(k=>{const o=obsAll[parseInt(k.slice(1))];if(o&&!obs.includes(o))obs.push(o);});
      cs.edp_obs_custom.forEach(o=>{if(!obs.includes(o))obs.push(o);});
    });
    return { docs, obs };
  };

  const bibLinksFor  = (critRef: string) => links.filter(l=>l.critRef===critRef&&!l.absent);
  const bibItemsFor  = (critRef: string) => [...new Set(bibLinksFor(critRef).map(l=>l.bibId))].map(id=>bib.find(x=>x.id===id)).filter(Boolean) as EDPBibItem[];
  const isAbsent     = (critRef: string) => links.some(l=>l.critRef===critRef&&l.absent);
  const currentSynae = (critRef: string): string => {
    for (const tab of tabs) {
      const v = sess.grilles[tab]?.crits[critRef]?.edp_synae;
      if (v) return v;
    }
    return "";
  };
  const getDraft = (critRef:string,tabKey:string) => drafts.find(d=>d.critRef===critRef&&d.tabKey===tabKey)?.text ?? currentSynae(critRef);

  // Status of a critère for EDP coverage
  const critStatus = (critRef: string): "ok"|"partial"|"none"|"absent" => {
    const t = terrainEDPFor(critRef);
    const b = bibItemsFor(critRef);
    const total = t.docs.length + t.obs.length + b.length;
    if (isAbsent(critRef)) return "absent";
    if (total === 0) return "none";
    if (currentSynae(critRef)) return "ok";
    return "partial";
  };

  // Keyword-based suggestions from library
  const STOP_FR = new Set(["pour","dans","avec","cette","leurs","sont","plus","elle","les","des","que","qui","par","sur","bien","peut","tout","mais","être","comme","dont","même","sans","fait","lors","toute","elles","leur","aussi","donc","selon","entre","après","avant"]);
  const suggestionsFor = (critRef: string): EDPBibItem[] => {
    const crit = rd.C.find(c=>c.r===critRef);
    const obj = rd.O.find((o:any)=>o.r===crit?.o); const them = rd.T.find((t:any)=>t.c===obj?.t);
    const fullText = `${crit?.l??""} ${them?.l??""} ${critRef}`.toLowerCase();
    const words = fullText.split(/[\s\-\/,;.()'"]+/).filter(w=>w.length>=3&&!STOP_FR.has(w));
    return bib.filter(item => {
      if (bibLinksFor(critRef).some(l=>l.bibId===item.id)) return false;
      if (isAbsent(critRef)) return false;
      const hay = `${item.label} ${item.themes??""} ${item.note??""}`.toLowerCase();
      return words.filter(w=>hay.includes(w)).length >= 1;
    }).sort((a,b)=>{
      const hA=`${a.label} ${a.themes??""}`.toLowerCase(), hB=`${b.label} ${b.themes??""}`.toLowerCase();
      return words.filter(w=>hB.includes(w)).length - words.filter(w=>hA.includes(w)).length;
    });
  };

  // Stats
  const allCrits = rd.C.sort((a,b)=>a.r.localeCompare(b.r));
  const statOk      = allCrits.filter(c=>critStatus(c.r)==="ok").length;
  const statPartial = allCrits.filter(c=>critStatus(c.r)==="partial").length;
  const statAbsent  = allCrits.filter(c=>critStatus(c.r)==="absent").length;
  const statNone    = allCrits.filter(c=>critStatus(c.r)==="none").length;

  // ── Actions ────────────────────────────────────────────────────────────────
  const toggleCrit = (r:string) => setExpandedCrit(p=>{const n=new Set(p);n.has(r)?n.delete(r):n.add(r);return n;});

  const addBibLink = (bibId:string,critRef:string) => {
    const tabKey = tabs[0];
    if (links.some(l=>l.bibId===bibId&&l.critRef===critRef)) return;
    onUpdate({edpBibLinks:[...links,{bibId,critRef,tabKey}]});
  };
  const removeBibLink = (bibId:string,critRef:string) => onUpdate({edpBibLinks:links.filter(l=>!(l.bibId===bibId&&l.critRef===critRef))});
  const toggleAbsent  = (critRef:string) => {
    const exists = links.some(l=>l.absent&&l.critRef===critRef);
    onUpdate({edpBibLinks: exists ? links.filter(l=>!(l.absent&&l.critRef===critRef)) : [...links,{bibId:"__absent__",critRef,tabKey:tabs[0],absent:true}]});
  };

  const saveDraft = (critRef:string,tabKey:string,text:string) => {
    const others = drafts.filter(d=>!(d.critRef===critRef&&d.tabKey===tabKey));
    onUpdate({edpDrafts:[...others,{critRef,tabKey,text}]});
  };

  const confirmPush = () => {
    if (!pushModal) return;
    const {critRef,newText} = pushModal;
    const grilles = {...sess.grilles};
    tabs.forEach(tab => {
      const ts = grilles[tab]; if (!ts) return;
      grilles[tab] = {...ts, crits:{...ts.crits,[critRef]:{...ts.crits[critRef]??{edp_docs:[],edp_docs_custom:[],edp_obs:[],edp_obs_custom:[],edp_bib_ids:[],pts_forts:"",axes_prog:"",edp_synae:"",versions:[{commentaire_n:"",justification_nc:"",date:""},{commentaire_n:"",justification_nc:"",date:""},{commentaire_n:"",justification_nc:"",date:""}],version_courante:0,statut_critere:"saisi"},edp_synae:newText,statut_critere:"saisi"}}};
    });
    onUpdate({grilles, edpDrafts:drafts.filter(d=>d.critRef!==critRef)});
    setPushModal(null);
  };

  const upsertBib = (item:EDPBibItem) => {
    const idx=bib.findIndex(x=>x.id===item.id);
    onUpdate({edpBib:idx>=0?bib.map((x,i)=>i===idx?item:x):[...bib,item]});
    setShowBibForm(false);setEditBibItem(null);
  };
  const deleteBib = (id:string) => {
    if(!confirm("Supprimer cet EDP de la bibliothèque ?")) return;
    onUpdate({edpBib:bib.filter(x=>x.id!==id), edpBibLinks:links.filter(l=>l.bibId!==id)});
  };

  // Form state
  const emptyBibForm = ():EDPBibItem=>({id:newId(),type:"formation",label:"",date:"",organisme:"",themes:"",note:""});
  const [bibForm,setBibForm] = useState<EDPBibItem>(emptyBibForm());
  const openBibCreate=()=>{setBibForm(emptyBibForm());setEditBibItem(null);setShowBibForm(true);};
  const openBibEdit=(item:EDPBibItem)=>{setBibForm({...item});setEditBibItem(item);setShowBibForm(true);};

  const STATUS_CFG = {
    ok:     {bg:"#d1fae5",txt:"#065f46",icon:"✅",label:"EDP formalisé"},
    partial:{bg:"#e0f7f9",txt:"#0d5c66",icon:"📝",label:"EDP notés, à formaliser"},
    absent: {bg:"#fef3c7",txt:"#92400e",icon:"⚠️",label:"Aucun EDP pertinent"},
    none:   {bg:"#f1f5f9",txt:"#64748b",icon:"⬜",label:"Non traité"},
  };

  // Filtered crits
  const filtCrits = allCrits.filter(c=>{
    if(filterStatus!=="all" && critStatus(c.r)!==filterStatus) return false;
    if(search && !c.r.toLowerCase().includes(search.toLowerCase()) && !c.l?.toLowerCase().includes(search.toLowerCase())) return false;
    return true;
  });

  return (
    <div className="flex-1 overflow-auto bg-slate-50">
      {/* Header */}
      <div className="px-5 pt-5 pb-3">
        <div className="flex items-start justify-between flex-wrap gap-3 mb-3">
          <div>
            <h2 className="text-lg font-extrabold text-teal-900" style={{fontFamily:"'Raleway',sans-serif"}}>🔍 Objectivation</h2>
            <p className="text-xs text-slate-500 mt-0.5">Gestion des EDP sur tout le cycle · préparation → visite → post-visite · force de proposition validée</p>
          </div>
          {subTab==="bib" && <button onClick={openBibCreate} className="text-xs font-bold px-3 py-2 rounded-xl text-white hover:opacity-90 shadow" style={{background:"#2CB8C7"}}>＋ Ajouter EDP</button>}
        </div>

        {/* Coverage bar */}
        <div className="rounded-2xl border border-slate-200 bg-white shadow-sm px-4 py-3 mb-3">
          <div className="flex items-center justify-between mb-1.5">
            <span className="text-xs font-bold text-slate-600">Couverture — {rd.C.length} critères</span>
            <span className="text-xs text-slate-400">{statOk+statPartial} traités / {rd.C.length}</span>
          </div>
          <div className="flex h-3 rounded-full overflow-hidden">
            <div style={{width:`${statOk/rd.C.length*100}%`,background:"#10b981"}} title="Formalisé"/>
            <div style={{width:`${statPartial/rd.C.length*100}%`,background:"#2CB8C7"}} title="Notés"/>
            <div style={{width:`${statAbsent/rd.C.length*100}%`,background:"#f59e0b"}} title="Aucun pertinent"/>
            <div className="flex-1" style={{background:"#e2e8f0"}} title="Non traité"/>
          </div>
          <div className="flex gap-3 mt-1.5 text-xs flex-wrap">
            {[["#10b981","✅ Formalisé",statOk],["#2CB8C7","📝 À formaliser",statPartial],["#f59e0b","⚠️ Aucun pertinent",statAbsent],["#e2e8f0","⬜ Non traité",statNone]].map(([c,l,v])=>(
              <span key={l as string}><span className="inline-block w-2 h-2 rounded-full mr-1" style={{background:c as string}}/>{l as string} : <b>{v as number}</b></span>
            ))}
          </div>
        </div>

        {/* Sub-tabs */}
        <div className="flex rounded-xl border border-slate-200 overflow-hidden shadow-sm bg-white text-xs font-bold w-fit">
          {([["global","🗂 Vue par critère"],["bib","📚 Bibliothèque"],["couverture","📊 Couverture"]] as const).map(([v,l])=>(
            <button key={v} onClick={()=>setSubTab(v)}
              className="px-4 py-2 transition-all border-r last:border-0 border-slate-200"
              style={subTab===v?{background:"#0d5c66",color:"white"}:{color:"#64748b"}}>
              {l}
            </button>
          ))}
        </div>
      </div>

      {/* ══ VUE PAR CRITÈRE ════════════════════════════════════════════════ */}
      {subTab==="global" && (
        <div className="px-5 pb-5">
          {/* Filters */}
          <div className="flex items-center gap-2 flex-wrap mb-3">
            <input value={search} onChange={e=>setSearch(e.target.value)} placeholder="🔍 Critère…"
              className="text-xs border border-slate-200 rounded-xl px-3 py-1.5 outline-none focus:border-teal-300 bg-white shadow-sm w-44"/>
            <div className="flex rounded-xl border border-slate-200 overflow-hidden bg-white shadow-sm text-xs font-bold">
              {([["all","Tous"],["ok","✅"],["partial","📝"],["absent","⚠️"],["none","⬜"]] as const).map(([v,l])=>(
                <button key={v} onClick={()=>setFilterStatus(v as any)}
                  className="px-2.5 py-1.5 border-r last:border-0 border-slate-200"
                  style={filterStatus===v?{background:"#0d5c66",color:"white"}:{color:"#64748b"}}>
                  {l}
                </button>
              ))}
            </div>
          </div>

          <div className="space-y-1.5">
            {filtCrits.map(crit => {
              const st = critStatus(crit.r);
              const sc = STATUS_CFG[st];
              const isExp = expandedCrit.has(crit.r);
              const terrain = terrainEDPFor(crit.r);
              const bibItems = bibItemsFor(crit.r);
              const suggestions = suggestionsFor(crit.r);
              const synae = currentSynae(crit.r);
              const obj  = rd.O.find(o=>o.r===crit.o);
              const them = rd.T.find(t=>t.c===obj?.t);
              const firstTab = tabs[0];

              return (
                <div key={crit.r} className="rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden">
                  {/* Row header */}
                  <button onClick={()=>toggleCrit(crit.r)}
                    className="w-full px-3 py-2.5 flex items-center gap-2 hover:bg-slate-50 transition-colors text-left">
                    <span className="text-slate-400 text-xs w-3">{isExp?"▼":"▶"}</span>
                    <span className="w-5 h-5 rounded-full flex items-center justify-center text-xs flex-shrink-0" style={{background:sc.bg,color:sc.txt}}>{sc.icon}</span>
                    <span className="font-mono font-bold text-teal-600 text-xs flex-shrink-0">{crit.r}</span>
                    <span className="text-xs text-slate-700 flex-1 truncate">{crit.l?.slice(0,60)}{(crit.l?.length??0)>60?"…":""}</span>
                    <div className="flex items-center gap-1.5 flex-shrink-0">
                      {terrain.docs.length+terrain.obs.length>0 && <span className="text-xs px-1.5 py-0.5 rounded-full bg-green-100 text-green-700 font-bold">🗂 {terrain.docs.length+terrain.obs.length}</span>}
                      {bibItems.length>0 && <span className="text-xs px-1.5 py-0.5 rounded-full bg-teal-100 text-teal-700 font-bold">📚 {bibItems.length}</span>}
                      {suggestions.length>0 && <span className="text-xs px-1.5 py-0.5 rounded-full bg-amber-100 text-amber-700 font-bold">💡 {suggestions.length}</span>}
                      <span className="text-xs text-slate-300 font-mono">{them?.c}</span>
                    </div>
                  </button>

                  {/* Expanded content */}
                  {isExp && (
                    <div className="border-t border-slate-100 px-4 py-3 space-y-4">

                      {/* EDP TERRAIN (read-only, from grilles) */}
                      <div>
                        <div className="flex items-center justify-between mb-1.5">
                          <span className="text-xs font-bold text-green-700">🗂 EDP Terrain (saisis dans les grilles)</span>
                          <button onClick={()=>onNavigate(firstTab,crit.r)} className="text-xs text-teal-500 hover:text-teal-700 font-bold">→ Ouvrir dans la grille</button>
                        </div>
                        {terrain.docs.length===0&&terrain.obs.length===0
                          ? <div className="text-xs text-slate-400 italic px-2">Aucun EDP saisi dans les grilles pour ce critère</div>
                          : <div className="space-y-1">
                              {terrain.docs.map((d,i)=><div key={i} className="flex items-center gap-2 text-xs px-2 py-1 rounded-lg bg-blue-50"><span>📄</span><span className="text-slate-700">{d}</span></div>)}
                              {terrain.obs.map((o,i)=><div key={i} className="flex items-center gap-2 text-xs px-2 py-1 rounded-lg bg-green-50"><span>👁</span><span className="text-slate-700">{o}</span></div>)}
                            </div>
                        }
                      </div>

                      {/* EDP BIBLIOTHÈQUE */}
                      <div>
                        <div className="text-xs font-bold text-teal-700 mb-1.5">📚 EDP Bibliothèque (transversaux)</div>
                        <div className="space-y-1">
                          {bibItems.map(item=>{
                            const ts=BIB_TYPES[item.type];
                            return <div key={item.id} className="flex items-center gap-2 text-xs px-2 py-1.5 rounded-lg" style={{background:ts.bg}}>
                              <span>{ts.icon}</span><span className="flex-1 text-slate-700 font-medium">{item.label}</span>
                              {item.date&&<span className="text-slate-400">{item.date}</span>}
                              <button onClick={()=>removeBibLink(item.id,crit.r)} className="text-slate-300 hover:text-pink-400">✕</button>
                            </div>;
                          })}
                          {bib.filter(x=>!bibItems.some(b=>b.id===x.id)).map(item=>(
                            <button key={item.id} onClick={()=>addBibLink(item.id,crit.r)}
                              className="flex items-center gap-2 text-xs px-2 py-1.5 rounded-lg border border-dashed border-slate-200 w-full text-left hover:border-teal-300 hover:bg-teal-50 transition-all">
                              <span className="text-slate-400">＋</span>
                              <span className="text-slate-500">{BIB_TYPES[item.type].icon} {item.label}</span>
                            </button>
                          ))}
                          {bib.length===0 && <div className="text-xs text-slate-400 italic px-2">Bibliothèque vide — ajoutez des EDP dans l'onglet Bibliothèque</div>}
                          <label className={`flex items-center gap-2 text-xs px-2 py-1.5 rounded-lg cursor-pointer transition-all ${isAbsent(crit.r)?"bg-amber-50 border border-amber-200":"border border-dashed border-slate-200 hover:bg-amber-50"}`}>
                            <input type="checkbox" checked={isAbsent(crit.r)} onChange={()=>toggleAbsent(crit.r)} className="rounded"/>
                            <span className={isAbsent(crit.r)?"text-amber-700 font-bold":"text-slate-400"}>⚠️ Aucun EDP pertinent pour ce critère</span>
                          </label>
                        </div>
                      </div>

                      {/* SUGGESTIONS */}
                      {suggestions.length>0 && (
                        <div>
                          <div className="text-xs font-bold text-amber-700 mb-1.5">💡 Suggestions (non liées — à valider)</div>
                          <div className="space-y-1">
                            {suggestions.map(item=>{
                              const ts=BIB_TYPES[item.type];
                              return <div key={item.id} className="flex items-center gap-2 text-xs px-2 py-1.5 rounded-lg bg-amber-50 border border-amber-100">
                                <span>{ts.icon}</span>
                                <span className="flex-1 text-slate-700">{item.label}</span>
                                <button onClick={()=>addBibLink(item.id,crit.r)}
                                  className="text-xs font-bold px-2 py-0.5 rounded-lg bg-amber-600 text-white hover:bg-amber-700 transition-all">
                                  ✓ Valider
                                </button>
                              </div>;
                            })}
                          </div>
                        </div>
                      )}

                      {/* REFORMULATION → PUSH */}
                      <div>
                        <div className="flex items-center justify-between mb-1.5">
                          <span className="text-xs font-bold text-slate-600">✍️ Formulation EDP Synaé</span>
                          {synae && <span className="text-xs text-green-600 font-bold">✅ Actuellement dans la grille</span>}
                        </div>
                        <EdpDraftEditor
                          current={synae}
                          draft={getDraft(crit.r,firstTab)}
                          onSaveDraft={t=>saveDraft(crit.r,firstTab,t)}
                          onPush={newText=>setPushModal({critRef:crit.r,tabKey:firstTab,oldText:synae,newText})}
                        />
                      </div>
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        </div>
      )}

      {/* ══ BIBLIOTHÈQUE ═══════════════════════════════════════════════════ */}
      {subTab==="bib" && (
        <div className="px-5 pb-5 space-y-2">
          {bib.length===0 && (
            <div className="rounded-2xl border-2 border-dashed border-slate-200 bg-white p-10 text-center">
              <div className="text-4xl mb-2">📭</div>
              <div className="text-sm font-semibold text-slate-500">Bibliothèque vide</div>
              <div className="text-xs text-slate-400 mt-1">Ajoutez sensibilisations, formations, documents transversaux…</div>
            </div>
          )}
          {bib.map(item=>{
            const ts=BIB_TYPES[item.type];
            const nLinks=links.filter(l=>l.bibId===item.id&&!l.absent).length;
            return (
              <div key={item.id} className="rounded-2xl border border-slate-200 bg-white shadow-sm px-4 py-3 flex items-start gap-3">
                <div className="w-9 h-9 rounded-xl flex items-center justify-center text-lg flex-shrink-0" style={{background:ts.bg}}>{ts.icon}</div>
                <div className="flex-1 min-w-0">
                  <div className="flex items-center gap-2 flex-wrap">
                    <span className="font-bold text-slate-800 text-sm">{item.label}</span>
                    <span className="text-xs font-bold px-2 py-0.5 rounded-full" style={{background:ts.bg,color:ts.color}}>{ts.label}</span>
                    {nLinks>0 && <span className="text-xs font-bold px-2 py-0.5 rounded-full bg-teal-100 text-teal-700">🔗 {nLinks} critère{nLinks>1?"s":""}</span>}
                  </div>
                  <div className="flex gap-3 mt-0.5 text-xs text-slate-400 flex-wrap">
                    {item.date&&<span>📅 {item.date}</span>}
                    {item.organisme&&<span>🏢 {item.organisme}</span>}
                    {item.themes&&<span className="italic">"{item.themes}"</span>}
                  </div>
                  {item.note&&<div className="mt-0.5 text-xs text-slate-500 italic">{item.note}</div>}
                </div>
                <div className="flex gap-1 flex-shrink-0">
                  <button onClick={()=>openBibEdit(item)} className="text-xs px-2 py-1 rounded-lg bg-slate-100 text-slate-600 hover:bg-teal-50 hover:text-teal-600">✏️</button>
                  <button onClick={()=>deleteBib(item.id)} className="text-xs px-2 py-1 rounded-lg bg-slate-100 text-slate-400 hover:bg-pink-50 hover:text-pink-500">🗑</button>
                </div>
              </div>
            );
          })}
        </div>
      )}

      {/* ══ COUVERTURE ════════════════════════════════════════════════════ */}
      {subTab==="couverture" && (
        <div className="px-5 pb-5 space-y-3">
          {rd.T.map(them=>{
            const tc=allCrits.filter(c=>rd.O.find(o=>o.r===c.o)?.t===them.c);
            const byStatus = {ok:0,partial:0,absent:0,none:0};
            tc.forEach(c=>{byStatus[critStatus(c.r)]++;});
            const pct=tc.length?Math.round((byStatus.ok+byStatus.partial)/tc.length*100):0;
            return (
              <div key={them.c} className="rounded-2xl border border-slate-200 bg-white shadow-sm px-4 py-3">
                <div className="flex items-center justify-between mb-2">
                  <div className="flex items-center gap-2">
                    <div className="w-1.5 h-5 rounded-full flex-shrink-0" style={{background:them.bg||"#2CB8C7"}}/>
                    <span className="font-mono text-xs text-teal-600 font-bold">{them.c}</span>
                    <span className="font-bold text-sm text-slate-800">{them.l?.slice(0,50)}</span>
                  </div>
                  <span className="text-sm font-extrabold text-teal-700">{pct}%</span>
                </div>
                <div className="flex h-2 rounded-full overflow-hidden mb-1.5">
                  <div style={{width:`${byStatus.ok/tc.length*100}%`,background:"#10b981"}}/>
                  <div style={{width:`${byStatus.partial/tc.length*100}%`,background:"#2CB8C7"}}/>
                  <div style={{width:`${byStatus.absent/tc.length*100}%`,background:"#f59e0b"}}/>
                  <div className="flex-1" style={{background:"#e2e8f0"}}/>
                </div>
                <div className="flex gap-3 text-xs flex-wrap">
                  <span className="text-green-700 font-bold">✅ {byStatus.ok}</span>
                  <span className="text-teal-700 font-bold">📝 {byStatus.partial}</span>
                  <span className="text-amber-700 font-bold">⚠️ {byStatus.absent}</span>
                  <span className="text-slate-400">⬜ {byStatus.none}</span>
                </div>
              </div>
            );
          })}
        </div>
      )}

      {/* ══ PUSH MODAL ════════════════════════════════════════════════════ */}
      {pushModal && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
          <div className="bg-white rounded-2xl shadow-2xl w-full max-w-lg p-6">
            <h3 className="text-base font-extrabold text-teal-900 mb-1">📤 Pousser vers la grille</h3>
            <p className="text-xs text-slate-500 mb-4">Critère <span className="font-bold text-teal-600">{pushModal.critRef}</span> — Grille <span className="font-bold">{pushModal.tabKey}</span></p>
            {pushModal.oldText && (
              <div className="mb-3">
                <div className="text-xs font-bold text-slate-500 mb-1">Texte actuel dans la grille :</div>
                <div className="text-xs bg-pink-50 border border-pink-200 rounded-xl px-3 py-2 text-slate-600 whitespace-pre-wrap">{pushModal.oldText}</div>
              </div>
            )}
            <div className="mb-4">
              <div className="text-xs font-bold text-slate-500 mb-1">Nouveau texte à pousser :</div>
              <div className="text-xs bg-green-50 border border-green-200 rounded-xl px-3 py-2 text-slate-700 whitespace-pre-wrap">{pushModal.newText}</div>
            </div>
            <div className="flex gap-2">
              <button onClick={()=>setPushModal(null)} className="flex-1 py-2 rounded-xl text-xs font-bold border border-slate-200 text-slate-500 hover:bg-slate-50">Annuler</button>
              <button onClick={confirmPush} className="flex-1 py-2 rounded-xl text-xs font-bold bg-teal-600 text-white hover:bg-teal-700">✓ Confirmer le push</button>
            </div>
          </div>
        </div>
      )}

      {/* ══ BIB FORM MODAL ════════════════════════════════════════════════ */}
      {showBibForm && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
          <div className="bg-white rounded-2xl shadow-2xl w-full max-w-lg p-6 overflow-y-auto" style={{maxHeight:"90vh"}}>
            <h3 className="text-base font-extrabold text-teal-900 mb-4">{editBibItem?"✏️ Modifier":"＋ Ajouter"} un EDP</h3>
            <div className="space-y-3">
              <div>
                <label className="text-xs font-bold text-slate-600 mb-1 block">Type</label>
                <div className="grid grid-cols-3 gap-1.5">
                  {Object.entries(BIB_TYPES).map(([k,v])=>(
                    <button key={k} onClick={()=>setBibForm(f=>({...f,type:k as EDPBibItem["type"]}))}
                      className="py-2 rounded-xl text-xs font-bold border-2 transition-all"
                      style={bibForm.type===k?{background:v.bg,color:v.color,borderColor:v.color}:{borderColor:"#e2e8f0",color:"#94a3b8"}}>
                      {v.icon} {v.label.split(" ")[0]}
                    </button>
                  ))}
                </div>
              </div>
              <div>
                <label className="text-xs font-bold text-slate-600 mb-1 block">Intitulé *</label>
                <input value={bibForm.label} onChange={e=>setBibForm(f=>({...f,label:e.target.value}))}
                  placeholder="Ex: Sensibilisation bientraitance — Nov. 2024"
                  className="w-full text-xs border border-slate-200 rounded-xl px-3 py-2 outline-none focus:border-teal-300"/>
              </div>
              <div>
                <label className="text-xs font-bold text-slate-600 mb-1 block">Thématiques couvertes</label>
                <input value={bibForm.themes??""} onChange={e=>setBibForm(f=>({...f,themes:e.target.value}))}
                  placeholder="Ex: bientraitance, droits des usagers, maltraitance"
                  className="w-full text-xs border border-slate-200 rounded-xl px-3 py-2 outline-none focus:border-teal-300"/>
              </div>
              <div className="grid grid-cols-2 gap-2">
                <div>
                  <label className="text-xs font-bold text-slate-600 mb-1 block">Date</label>
                  <input type="date" value={bibForm.date??""} onChange={e=>setBibForm(f=>({...f,date:e.target.value}))}
                    className="w-full text-xs border border-slate-200 rounded-xl px-3 py-2 outline-none focus:border-teal-300"/>
                </div>
                <div>
                  <label className="text-xs font-bold text-slate-600 mb-1 block">Organisme / Source</label>
                  <input value={bibForm.organisme??""} onChange={e=>setBibForm(f=>({...f,organisme:e.target.value}))}
                    className="w-full text-xs border border-slate-200 rounded-xl px-3 py-2 outline-none focus:border-teal-300"/>
                </div>
              </div>
              <div>
                <label className="text-xs font-bold text-slate-600 mb-1 block">Note</label>
                <textarea value={bibForm.note??""} onChange={e=>setBibForm(f=>({...f,note:e.target.value}))} rows={2}
                  className="w-full text-xs border border-slate-200 rounded-xl px-3 py-2 outline-none focus:border-teal-300 resize-none"/>
              </div>
            </div>
            <div className="flex gap-2 mt-5">
              <button onClick={()=>{setShowBibForm(false);setEditBibItem(null);}} className="flex-1 py-2 rounded-xl text-xs font-bold border border-slate-200 text-slate-500">Annuler</button>
              <button onClick={()=>upsertBib(bibForm)} disabled={!bibForm.label.trim()}
                className="flex-1 py-2 rounded-xl text-xs font-bold bg-teal-600 text-white disabled:opacity-40">
                {editBibItem?"Modifier":"Ajouter"}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ── Sous-composant EdpDraftEditor ─────────────────────────────────────────────
function EdpDraftEditor({ current, draft, onSaveDraft, onPush }: {
  current: string; draft: string;
  onSaveDraft: (t:string)=>void; onPush: (t:string)=>void;
}) {
  const [text, setText] = useState(draft || current);
  useEffect(() => { setText(draft || current); }, [draft, current]);
  const dirty = text !== current;
  return (
    <div>
      <textarea value={text} onChange={e=>{setText(e.target.value);onSaveDraft(e.target.value);}} rows={3}
        placeholder="Saisir ou reformuler la synthèse EDP pour la colonne H Synaé…"
        className="w-full text-xs border border-slate-200 rounded-xl px-3 py-2 outline-none focus:border-teal-300 resize-y"/>
      <div className="flex items-center justify-between mt-1.5">
        <span className="text-xs text-slate-400">{dirty?"✏️ Brouillon non poussé":"Synchronisé avec la grille"}</span>
        <button onClick={()=>onPush(text)} disabled={!text.trim()||text===current}
          className="text-xs font-bold px-3 py-1.5 rounded-xl bg-teal-600 text-white hover:bg-teal-700 disabled:opacity-30 transition-all">
          📤 Pousser vers la grille
        </button>
      </div>
    </div>
  );
}
// ── FIN OBJECTIVATION VIEW ────────────────────────────────────────────────────




// ── A11 CONNECTEUR ────────────────────────────────────────────────────────────
function A11Connector({ sess, rd, onUpdate }: {
  sess: Session; rd: Ref | null;
  onUpdate: (patch: Partial<Session>) => void;
}) {
  const [phase, setPhase] = useState<"pre"|"post">("pre");
  const [copied, setCopied] = useState(false);
  const result: string = sess.a11Result ?? "";

  const bib = sess.edpBib ?? [];
  const docs = (sess as any).documentsDemandesStr ?? "";

  const generatePrompt = (): string => {
    const lines: string[] = [
      `## CONTEXTE MISSION — ${sess.essms || "ESSMS non renseigné"}`,
      `Phase : ${phase === "pre" ? "Pré-visite (analyse préparatoire)" : "Post-visite (consolidation des EDP)"}`,
      ``,
      `### Informations structure`,
      `- Type ESSMS : ${sess.typeEssms || "—"}`,
      `- Statut juridique : ${sess.statutJuridique || "—"}`,
      `- Capacité : ${sess.capacite || "—"} places`,
      `- Département : ${sess.departement || "—"}`,
      `- OE : ${sess.oe || "—"}`,
      `- Mission : ${sess.typeMission || "évaluation HAS"}`,
      ``,
    ];

    if (docs.trim()) {
      lines.push(`### Documents demandés à l'ESSMS`);
      lines.push(docs.trim());
      lines.push(``);
    }

    if (bib.length > 0) {
      lines.push(`### Bibliothèque EDP préparée`);
      const byType = bib.reduce((acc, x) => { (acc[x.type]=acc[x.type]??[]).push(x); return acc; }, {} as Record<string, EDPBibItem[]>);
      const typeLabels: Record<string, string> = {
        formation:"Formations / Sensibilisations", document:"Documents", observation:"Observations", procedure:"Procédures", autre:"Autres"
      };
      Object.entries(byType).forEach(([t, items]) => {
        lines.push(`**${typeLabels[t]||t}** :`);
        items.forEach(item => {
          let line = `- ${item.label}`;
          if (item.date) line += ` (${item.date})`;
          if (item.themes) line += ` — Thèmes : ${item.themes}`;
          if (item.organisme) line += ` — ${item.organisme}`;
          lines.push(line);
        });
      });
      lines.push(``);
    }

    if (rd && phase === "post") {
      const cotations: string[] = [];
      const edpTerrain: string[] = [];
      Object.entries(sess.grilles).forEach(([tab, ts]) => {
        Object.entries(ts.ees ?? {}).forEach(([eeId, ee]) => {
          const eeRef = rd.E.find((e:any)=>e.id===eeId);
          if ((ee as any).cotation) cotations.push(`  [${tab}] ${eeRef?.cr??""} : ${(ee as any).cotation}`);
        });
        Object.entries(ts.crits).forEach(([critRef, cs]) => {
          const crit = rd.C.find((c:any)=>c.r===critRef);
          const allEdp = [...cs.edp_docs_custom,...cs.edp_obs_custom,...(cs.edp_bib_ids??[]).map((id:string)=>bib.find(x=>x.id===id)?.label).filter(Boolean) as string[]];
          if (allEdp.length > 0 || cs.edp_synae) {
            edpTerrain.push(`**${critRef}** — ${crit?.l?.slice(0,50)??""}`);
            allEdp.forEach(e => edpTerrain.push(`  - ${e}`));
            if (cs.edp_synae) edpTerrain.push(`  → Synaé: ${cs.edp_synae}`);
          }
        });
      });
      if (cotations.length > 0) { lines.push(`### Cotations terrain`); lines.push(...cotations.slice(0,50)); lines.push(``); }
      if (edpTerrain.length > 0) { lines.push(`### EDP terrain collectés`); lines.push(...edpTerrain); lines.push(``); }
    }

    lines.push(`---`);
    lines.push(phase === "pre"
      ? `Analyse les documents transmis par l'ESSMS en croisant avec le référentiel HAS. Identifie les points forts, les manques et propose un pré-remplissage des EDP pour les critères couverts.`
      : `Analyse la cohérence des EDP collectés pendant la visite. Propose une reformulation harmonisée pour la colonne H Synaé critère par critère.`
    );

    return lines.join("\n");
  };

  const copyPrompt = () => {
    const txt = generatePrompt();
    const el = document.createElement("textarea");
    el.value=txt; el.style.position="fixed"; el.style.opacity="0";
    document.body.appendChild(el); el.select();
    document.execCommand("copy"); document.body.removeChild(el);
    setCopied(true); setTimeout(()=>setCopied(false),2500);
  };

  const statDocs = docs.trim().split("\n").filter(l=>l.trim().startsWith("-")).length;
  const statEdpTerrain = Object.values(sess.grilles).reduce((n, ts) =>
    n + Object.values(ts.crits).filter(cs => cs.edp_docs_custom.length+cs.edp_obs_custom.length+(cs.edp_bib_ids?.length??0)>0).length, 0);

  return (
    <div className="flex-1 overflow-auto bg-slate-50">
      <div className="px-5 pt-5 pb-4">
        <div className="flex items-start justify-between flex-wrap gap-3 mb-4">
          <div>
            <h2 className="text-lg font-extrabold text-teal-900" style={{fontFamily:"'Raleway',sans-serif"}}>🤖 Connecteur @A11</h2>
            <p className="text-xs text-slate-500 mt-0.5">Génère le prompt de contexte pour l'agent Analyse Documentaire · colle le résultat ici</p>
          </div>
        </div>

        {/* Context summary cards */}
        <div className="grid grid-cols-3 gap-3 mb-4">
          {[
            {icon:"🏢",label:"ESSMS",v:sess.typeEssms||"—",sub:sess.departement||""},
            {icon:"📋",label:"EDP bibliothèque",v:bib.length,sub:`${bib.filter(x=>x.type==="formation").length} formations`},
            {icon:"🗂",label:"EDP terrain saisis",v:statEdpTerrain,sub:"critères avec EDP"},
          ].map(({icon,label,v,sub})=>(
            <div key={label} className="rounded-2xl border border-slate-200 bg-white shadow-sm px-4 py-3 flex items-center gap-3">
              <span className="text-2xl">{icon}</span>
              <div>
                <div className="text-xs font-semibold text-slate-500">{label}</div>
                <div className="text-lg font-extrabold text-teal-800">{v}</div>
                <div className="text-xs text-slate-400">{sub}</div>
              </div>
            </div>
          ))}
        </div>

        {/* Phase selector */}
        <div className="rounded-2xl border border-slate-200 bg-white shadow-sm px-4 py-4 mb-4">
          <p className="text-xs font-bold text-slate-600 mb-3">Phase d'analyse</p>
          <div className="grid grid-cols-2 gap-2">
            {([
              ["pre","🔍 Pré-visite","Analyse des documents reçus · identification des manques · pré-remplissage EDP"],
              ["post","✍️ Post-visite","Cohérence des EDP terrain · reformulation Synaé · consolidation"],
            ] as const).map(([v,l,desc])=>(
              <button key={v} onClick={()=>setPhase(v)}
                className="text-left p-3 rounded-xl border-2 transition-all"
                style={phase===v?{borderColor:"#2CB8C7",background:"#e0f7f9"}:{borderColor:"#e2e8f0",background:"white"}}>
                <div className="text-sm font-bold text-slate-800 mb-0.5">{l}</div>
                <div className="text-xs text-slate-500">{desc}</div>
              </button>
            ))}
          </div>
        </div>

        {/* Generate button */}
        <div className="rounded-2xl border border-teal-200 bg-teal-50 px-4 py-4 mb-4">
          <div className="flex items-center justify-between flex-wrap gap-3">
            <div>
              <p className="text-sm font-bold text-teal-800">📋 Prompt contexte @A11</p>
              <p className="text-xs text-slate-500 mt-0.5">
                Inclut : mission · {bib.length} EDP bibliothèque · {statDocs} documents demandés
                {phase==="post" ? ` · ${statEdpTerrain} critères avec EDP terrain` : ""}
              </p>
            </div>
            <button onClick={copyPrompt}
              className="flex items-center gap-2 text-sm font-bold px-4 py-2.5 rounded-xl shadow transition-all"
              style={{background:copied?"#059669":"#2CB8C7",color:"white"}}>
              {copied ? "✅ Copié !" : "📋 Copier le prompt"}
            </button>
          </div>
          {/* Preview */}
          <details className="mt-3">
            <summary className="text-xs font-bold text-teal-600 cursor-pointer hover:text-teal-800">Aperçu du prompt →</summary>
            <pre className="mt-2 text-xs bg-white border border-teal-100 rounded-xl p-3 overflow-auto whitespace-pre-wrap text-slate-600"
              style={{maxHeight:200}}>{generatePrompt()}</pre>
          </details>
        </div>

        {/* Result zone */}
        <div className="rounded-2xl border border-slate-200 bg-white shadow-sm px-4 py-4">
          <div className="flex items-center justify-between mb-2">
            <p className="text-sm font-bold text-slate-700">📥 Résultats @A11</p>
            {result && <span className="text-xs font-bold text-green-600">✅ Résultats enregistrés</span>}
          </div>
          <p className="text-xs text-slate-400 mb-2">Colle ici la réponse de l'agent @A11 pour l'archiver avec la mission</p>
          <textarea rows={10}
            value={result}
            onChange={e => onUpdate({ a11Result: e.target.value })}
            placeholder={"Colle ici la réponse de @A11…\n\nElle sera conservée dans la session et exportée avec la mission."}
            className="w-full text-xs border border-slate-200 rounded-xl px-3 py-2.5 outline-none focus:border-teal-300 resize-y font-mono text-slate-700"/>
          {result && (
            <div className="flex gap-2 mt-2">
              <button onClick={()=>{ const el=document.createElement("textarea"); el.value=result; el.style.position="fixed"; el.style.opacity="0"; document.body.appendChild(el); el.select(); document.execCommand("copy"); document.body.removeChild(el); }}
                className="text-xs font-bold px-3 py-1.5 rounded-xl bg-slate-100 text-slate-600 hover:bg-slate-200">
                📋 Recopier
              </button>
              <button onClick={()=>{ if(confirm("Effacer les résultats @A11 ?")) onUpdate({a11Result:""}); }}
                className="text-xs font-bold px-3 py-1.5 rounded-xl bg-slate-100 text-pink-400 hover:bg-pink-50">
                🗑 Effacer
              </button>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}
// ── FIN A11 CONNECTEUR ────────────────────────────────────────────────────────

// ── QUALISCOPE VIEW v5 ────────────────────────────────────────────────────────
function RésultatsView({ sess, rd }: { sess: Session; rd: Ref }) {
  const myTabs = buildTabs(sess.config);
  const [collGrilles, setCollGrilles] = useState<Record<string, TabSaisie> | null>(null);
  const [importErr, setImportErr]     = useState("");
  const [colMode, setColMode]         = useState<"type"|"chap">("type"); // "type"=Standard/CI | "chap"=Ch1/Ch2/Ch3
  const [expanded, setExpanded]       = useState<Set<string>>(new Set());

  const tabsForChap = (ch: number) => {
    const types: Record<number, string[]> = { 1:["AT","AT-RC"], 2:["TC","TC-RC"], 3:["AS","AS-RC"] };
    return myTabs.filter(t => types[ch]?.includes(tabType(t)));
  };
  const [selCh, setSelCh] = useState<Record<number, string>>({
    1: tabsForChap(1)[0] ?? "moy",
    2: tabsForChap(2)[0] ?? "moy",
    3: tabsForChap(3)[0] ?? "moy"
  });

  const allGrilles: Record<string, TabSaisie> = { ...sess.grilles, ...(collGrilles ?? {}) };

  const toNum = (c: string): number | null => {
    if (c === "★" || c === "*") return 4;
    if (["1","2","3","4"].includes(c)) return +c;
    return null;
  };
  const avgOf = (vals: (number|null)[]): number | null => {
    const v = vals.filter((n): n is number => n !== null);
    return v.length ? v.reduce((a,b)=>a+b,0)/v.length : null;
  };

  const eesForTab = (tab: string): EE[] => {
    const tt = tabType(tab);
    const gc = GRILLE_G[tt] ?? "";
    if (tab === "CVS") return rd.E.filter(e => ["CVS","CVS/CI"].includes(e.cvs));
    return rd.E.filter(e => e.g === gc);
  };

  // Active tabs for chapter selection (used in chap mode and as base for type mode)
  const activeTabsForChap = (ch: number): string[] => {
    const s = selCh[ch];
    return s === "moy" ? tabsForChap(ch) : [s];
  };
  const allActiveTabs = (): string[] => {
    const tabs = [1,2,3].flatMap(ch => activeTabsForChap(ch));
    if (allGrilles["CVS"]) tabs.push("CVS");
    return [...new Set(tabs)];
  };

  // ── CRITÈRE SCORE: average of ALL its EEs from given tabs ──────────────────
  const critScore = (critRef: string, tabs: string[]): number | null => {
    const ees = rd.E.filter(e => e.cr === critRef);
    const nums = tabs.flatMap(tab => {
      const td = allGrilles[tab]; if (!td) return [];
      const tabEEs = eesForTab(tab);
      return ees.filter(e => tabEEs.some(x=>x.id===e.id)).map(e => toNum(td.ees[e.id]?.cotation??""));
    });
    return avgOf(nums);
  };

  // Is a critère "impératif" (CI) = has at least one EE with nv==="Impératif"
  const isCI = (critRef: string): boolean =>
    rd.E.some(e => e.cr === critRef && e.nv === "Impératif");

  // ── AGGREGATE SCORE for scope (list of critère refs), filtered by CI type ──
  // tabs: which grilles to use; ciFilter: undefined=all, true=CI only, false=Standard only
  const scopeScore = (critRefs: string[], tabs: string[], ciFilter?: boolean): number | null => {
    const filtered = ciFilter === undefined ? critRefs
      : ciFilter ? critRefs.filter(r => isCI(r))
      : critRefs.filter(r => !isCI(r));
    return avgOf(filtered.map(r => critScore(r, tabs)));
  };

  // Helpers
  const critsOfObj  = (obj: string)  => rd.C.filter(c=>c.o===obj).map(c=>c.r).sort();
  const critsOfThem = (them: string) => rd.O.filter(o=>o.t===them).flatMap(o=>critsOfObj(o.r));
  const critsAll    = ()             => rd.C.map(c=>c.r);

  // ── COLUMN DEFINITIONS ────────────────────────────────────────────────────
  type ColDef = { key: string; label: string; sub: string; color: string; light: string };
  const cols: ColDef[] = colMode === "type"
    ? [
        { key:"std",  label:"Standard",          sub:`${rd.C.filter(c=>!isCI(c.r)).length} critères`,        color:"#0891b2", light:"#ecfeff" },
        { key:"ci",   label:"🎯 Impératifs (CI)", sub:`${rd.C.filter(c=>isCI(c.r)).length} critères`,         color:"#C93F8B", light:"#fce7f3" },
      ]
    : [
        { key:"ch1",  label:"Ch.1 — AT",          sub:`${selCh[1]==="moy"?"Moy.":selCh[1]}`,                 color:"#2CB8C7", light:"#e0f7f9" },
        { key:"ch2",  label:"Ch.2 — TC",          sub:`${selCh[2]==="moy"?"Moy.":selCh[2]}`,                 color:"#0891b2", light:"#ecfeff" },
        { key:"ch3",  label:"Ch.3 — AS",          sub:`${selCh[3]==="moy"?"Moy.":selCh[3]}`,                 color:"#059669", light:"#ecfdf5" },
      ];

  // Get value for a column + scope
  const colVal = (col: ColDef, critRefs: string[]): number | null => {
    if (colMode === "type") {
      const ci = col.key === "ci";
      return scopeScore(critRefs, allActiveTabs(), ci);
    } else {
      const ch = parseInt(col.key.replace("ch",""));
      const tabs = [...activeTabsForChap(ch), ...(allGrilles["CVS"] ? ["CVS"] : [])];
      return scopeScore(critRefs, tabs);
    }
  };

  // Global score
  const globalScore = (critRefs: string[]): number | null =>
    avgOf(critRefs.map(r => critScore(r, allActiveTabs())));

  // ── SCORE BADGE ────────────────────────────────────────────────────────────
  const ScoreBadge = ({ v, size="sm" }: { v: number|null; size?: "sm"|"md"|"lg" }) => {
    if (v === null) return <span className="text-slate-300 text-xs">—</span>;
    const pct = ((v-1)/3)*100;
    const [bg,txt,bar] =
      v>=3.5 ? ["#d1fae5","#065f46","#10b981"] :
      v>=2.5 ? ["#ecfccb","#3f6212","#84cc16"] :
      v>=1.5 ? ["#fef3c7","#92400e","#f59e0b"] :
               ["#fce7f3","#7a1755","#C93F8B"];
    const ts = size==="lg"?"text-base font-extrabold":size==="md"?"text-sm font-extrabold":"text-xs font-bold";
    const w  = size==="lg"?52:size==="md"?44:36;
    return (
      <div className="inline-flex flex-col items-center gap-0.5">
        <span className={`${ts} px-2 py-0.5 rounded-full`} style={{background:bg,color:txt}}>{v.toFixed(2)}</span>
        <div className="rounded-full overflow-hidden bg-slate-200" style={{width:w,height:3}}>
          <div className="h-full rounded-full" style={{width:`${pct}%`,background:bar}}/>
        </div>
      </div>
    );
  };

  const toggle = (key: string) => setExpanded(p => { const n=new Set(p); n.has(key)?n.delete(key):n.add(key); return n; });

  const importColl = () => {
    const inp = document.createElement("input"); inp.type="file"; inp.accept=".json";
    inp.onchange = async (e) => {
      const f = (e.target as HTMLInputElement).files?.[0]; if (!f) return;
      try {
        const data: Session = JSON.parse(await f.text());
        if (data?.grilles) { setCollGrilles(data.grilles); setImportErr(""); }
        else setImportErr("❌ Fichier JSON invalide");
      } catch { setImportErr("❌ Erreur lecture fichier"); }
    }; inp.click();
  };

  const CHAP_COLORS: Record<number,{main:string;light:string}> = {
    1:{main:"#2CB8C7",light:"#e0f7f9"},
    2:{main:"#0891b2",light:"#ecfeff"},
    3:{main:"#059669",light:"#ecfdf5"}
  };
  const allCritRefs = critsAll();
  const gStd = scopeScore(allCritRefs, allActiveTabs(), false);
  const gCI  = scopeScore(allCritRefs, allActiveTabs(), true);
  const gAll = globalScore(allCritRefs);

  return (
    <div className="flex-1 overflow-auto bg-slate-50">
      <div className="px-5 pt-5 pb-4">
        {/* Header */}
        <div className="flex items-start justify-between gap-4 flex-wrap mb-4">
          <div>
            <h2 className="text-lg font-extrabold text-teal-900 tracking-tight">📊 Analyse des cotations terrain</h2>
            <p className="text-xs text-slate-500 mt-0.5">
              {rd.C.length} critères · <span className="text-pink-600 font-bold">{rd.C.filter(c=>isCI(c.r)).length} impératifs (CI)</span>
              {" · "}Score HAS A/B/C/D via <span className="font-bold text-teal-600">@QUALISCORE</span>
            </p>
          </div>
          <button onClick={importColl}
            className="text-xs font-bold px-3 py-2 rounded-xl border border-teal-200 bg-white text-teal-700 hover:bg-teal-50 shadow-sm">
            📥 Grilles collègue
          </button>
        </div>
        {importErr && <div className="mb-3 text-xs text-pink-600 bg-pink-50 border border-pink-200 rounded-xl px-3 py-2">{importErr}</div>}

        {/* Summary cards */}
        <div className="grid grid-cols-3 gap-3 mb-4">
          {[
            {label:"Standard",v:gStd,icon:"📋",sub:`${rd.C.filter(c=>!isCI(c.r)).length} critères`},
            {label:"Impératifs (CI)",v:gCI,icon:"🎯",sub:`${rd.C.filter(c=>isCI(c.r)).length} critères CI`},
            {label:"Global",v:gAll,icon:"⚖️",sub:"Tous critères"},
          ].map(({label,v,icon,sub})=>{
            const pct = v ? ((v-1)/3*100) : 0;
            const [bg,txt,bar] = !v?["#f8fafc","#94a3b8","#e2e8f0"]:
              v>=3.5?["#d1fae5","#065f46","#10b981"]:
              v>=2.5?["#ecfccb","#3f6212","#84cc16"]:
              v>=1.5?["#fef3c7","#92400e","#f59e0b"]:
              ["#fce7f3","#7a1755","#C93F8B"];
            return (
              <div key={label} className="bg-white rounded-2xl border border-slate-200 shadow-sm px-4 py-3 flex items-center gap-3">
                <span className="text-2xl">{icon}</span>
                <div className="flex-1 min-w-0">
                  <div className="text-xs font-semibold text-slate-600">{label}</div>
                  <div className="text-xs text-slate-400">{sub}</div>
                  <div className="mt-1.5 h-1.5 rounded-full bg-slate-100 overflow-hidden">
                    <div className="h-full rounded-full" style={{width:`${pct}%`,background:bar}}/>
                  </div>
                </div>
                <span className="text-xl font-extrabold" style={{color:txt}}>{v?v.toFixed(2):"—"}</span>
              </div>
            );
          })}
        </div>

        {/* Controls row: column mode + grille selectors */}
        <div className="flex items-center gap-3 flex-wrap">
          {/* Column mode toggle */}
          <div className="flex rounded-xl border border-slate-200 overflow-hidden shadow-sm bg-white text-xs font-bold">
            {([["type","📋 Standard / CI"],["chap","📚 Par chapitre"]] as const).map(([m,lbl])=>(
              <button key={m} onClick={()=>setColMode(m)}
                className="px-3 py-2 transition-all border-r last:border-0 border-slate-200"
                style={colMode===m?{background:"#0d5c66",color:"white"}:{color:"#64748b"}}>
                {lbl}
              </button>
            ))}
          </div>

          {/* Chapter grille selectors (always visible for context) */}
          {[1,2,3].map(ch => {
            const {main,light} = CHAP_COLORS[ch];
            const opts = [...tabsForChap(ch),"moy"];
            return (
              <div key={ch} className="flex items-center gap-1 rounded-xl border px-2.5 py-1.5 bg-white shadow-sm" style={{borderColor:main+"44"}}>
                <span className="text-xs font-extrabold mr-0.5" style={{color:main}}>Ch.{ch}</span>
                {opts.map(opt=>(
                  <button key={opt} onClick={()=>setSelCh(s=>({...s,[ch]:opt}))}
                    className="px-2 py-0.5 rounded-full text-xs font-bold transition-all"
                    style={selCh[ch]===opt?{background:main,color:"white"}:{background:light,color:main}}>
                    {opt==="moy"?"Moy.":opt}
                  </button>
                ))}
              </div>
            );
          })}
          {allGrilles["CVS"] && (
            <div className="flex items-center gap-1.5 rounded-xl border border-purple-200 bg-purple-50 px-2.5 py-1.5 text-xs font-bold text-purple-600">
              <span className="w-1.5 h-1.5 rounded-full bg-purple-400"/>CVS auto
            </div>
          )}
        </div>
      </div>

      {/* Table */}
      <div className="px-5 pb-5">
        <div className="rounded-2xl border border-slate-200 bg-white overflow-hidden shadow-sm">
          <div className="overflow-x-auto">
            <table className="w-full text-xs border-collapse">
              <thead>
                <tr style={{background:"#0d5c66"}}>
                  <th className="text-left px-4 py-3 font-bold text-white w-80">Thématique · Objectif · Critère</th>
                  {cols.map(col=>(
                    <th key={col.key} className="px-3 py-3 text-center" style={{minWidth:120,color:col.light}}>
                      <div className="font-bold">{col.label}</div>
                      <div className="text-xs font-normal opacity-70 mt-0.5">{col.sub}</div>
                    </th>
                  ))}
                  <th className="px-3 py-3 text-center font-bold text-teal-200 border-l border-teal-700" style={{minWidth:90}}>
                    <div>Global</div>
                    <div className="text-xs font-normal text-teal-400 mt-0.5">moy. critères</div>
                  </th>
                </tr>
              </thead>
              <tbody>
                {rd.T.map((them, ti) => {
                  const themKey = `th-${them.c}`;
                  const themExp = expanded.has(themKey);
                  const themCrits = critsOfThem(them.c);
                  const objs = rd.O.filter(o=>o.t===them.c).sort((a,b)=>a.r.localeCompare(b.r));
                  const rowBg = ti%2===0?"#fafafe":"#ffffff";

                  return (
                    <>
                      {/* Thématique row */}
                      <tr key={themKey} onClick={()=>toggle(themKey)}
                        className="cursor-pointer hover:bg-teal-50 border-b border-slate-100 transition-colors"
                        style={{background:themExp?"#e0f7f9":rowBg}}>
                        <td className="px-4 py-2.5">
                          <div className="flex items-center gap-2">
                            <div className="w-1 h-6 rounded-full flex-shrink-0" style={{background:them.bg||"#2CB8C7"}}/>
                            <span className="text-slate-400 w-3 flex-shrink-0">{themExp?"▼":"▶"}</span>
                            <span className="font-mono font-extrabold text-teal-600">{them.c}</span>
                            <span className="font-bold text-slate-800 leading-snug">{them.l?.slice(0,52)}{(them.l?.length??0)>52?"…":""}</span>
                          </div>
                        </td>
                        {cols.map(col=>(
                          <td key={col.key} className="px-3 py-2.5 text-center">
                            <ScoreBadge v={colVal(col,themCrits)} size="md"/>
                          </td>
                        ))}
                        <td className="px-3 py-2.5 text-center border-l border-slate-100">
                          <ScoreBadge v={globalScore(themCrits)} size="lg"/>
                        </td>
                      </tr>

                      {/* Objectifs */}
                      {themExp && objs.map(obj => {
                        const objKey = `ob-${obj.r}`;
                        const objExp = expanded.has(objKey);
                        const objCrits = critsOfObj(obj.r);
                        const crits = rd.C.filter(c=>c.o===obj.r).sort((a,b)=>a.r.localeCompare(b.r));

                        return (
                          <>
                            <tr key={objKey} onClick={e=>{e.stopPropagation();toggle(objKey);}}
                              className="cursor-pointer hover:bg-teal-50/50 border-b border-slate-50 transition-colors"
                              style={{background:"#f5f7fe"}}>
                              <td className="px-4 py-2 pl-10">
                                <div className="flex items-center gap-2">
                                  <div className="w-px h-4 bg-teal-200 flex-shrink-0"/>
                                  <span className="text-slate-400 w-3 flex-shrink-0">{objExp?"▼":"▶"}</span>
                                  <span className="font-mono font-bold text-teal-400">{obj.r}</span>
                                  <span className="text-slate-600 leading-snug">{obj.l?.slice(0,46)}{(obj.l?.length??0)>46?"…":""}</span>
                                </div>
                              </td>
                              {cols.map(col=>(
                                <td key={col.key} className="px-3 py-2 text-center">
                                  <ScoreBadge v={colVal(col,objCrits)}/>
                                </td>
                              ))}
                              <td className="px-3 py-2 text-center border-l border-slate-100">
                                <ScoreBadge v={globalScore(objCrits)} size="md"/>
                              </td>
                            </tr>

                            {/* Critères */}
                            {objExp && crits.map(crit => {
                              const ci = isCI(crit.r);
                              const vG = critScore(crit.r, allActiveTabs());
                              // In type mode: show score in correct column only
                              // In chap mode: show score per chapter column
                              return (
                                <tr key={`cr-${crit.r}`} className="border-b border-slate-50"
                                  style={{background:ci?"#fff5f5":"#f0f4ff"}}>
                                  <td className="px-4 py-1.5 pl-16">
                                    <div className="flex items-center gap-2">
                                      <div className="w-px h-3.5 bg-teal-100 flex-shrink-0"/>
                                      {ci && <span className="text-xs font-extrabold px-1.5 py-0.5 rounded-full text-white flex-shrink-0" style={{background:"#C93F8B",fontSize:"9px"}}>CI</span>}
                                      <span className="font-mono text-teal-300">{crit.r}</span>
                                      <span className="text-slate-500 leading-snug">{crit.l?.slice(0,40)}{(crit.l?.length??0)>40?"…":""}</span>
                                    </div>
                                  </td>
                                  {cols.map(col => {
                                    // type mode: show value only in matching column
                                    const show = colMode==="type"
                                      ? (col.key==="ci" ? ci : !ci)
                                      : true;
                                    const ch = colMode==="chap" ? parseInt(col.key.replace("ch","")) : 0;
                                    const tabs = colMode==="chap"
                                      ? [...activeTabsForChap(ch), ...(allGrilles["CVS"]?["CVS"]:[])]
                                      : allActiveTabs();
                                    const v = show ? critScore(crit.r, tabs) : null;
                                    return (
                                      <td key={col.key} className="px-3 py-1.5 text-center">
                                        <ScoreBadge v={v}/>
                                      </td>
                                    );
                                  })}
                                  <td className="px-3 py-1.5 text-center border-l border-slate-100">
                                    <ScoreBadge v={vG} size="md"/>
                                  </td>
                                </tr>
                              );
                            })}
                          </>
                        );
                      })}
                    </>
                  );
                })}

                {/* Footer */}
                <tr style={{background:"#0d5c66"}}>
                  <td className="px-4 py-3 text-white font-bold">Moyenne générale</td>
                  {cols.map(col=>(
                    <td key={col.key} className="px-3 py-3 text-center">
                      <ScoreBadge v={colVal(col,allCritRefs)} size="md"/>
                    </td>
                  ))}
                  <td className="px-3 py-3 text-center border-l border-teal-700">
                    <ScoreBadge v={gAll} size="lg"/>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>

          {/* Legend */}
          <div className="flex items-center gap-3 flex-wrap px-4 py-2.5 border-t border-slate-100 bg-slate-50 text-xs">
            <span className="font-bold text-slate-500">Légende :</span>
            {[["≥ 3.5 Exemplaire","#d1fae5","#065f46"],["2.5–3.5 Satisfaisant","#ecfccb","#3f6212"],["1.5–2.5 Partiel","#fef3c7","#92400e"],["< 1.5 Insuffisant","#fce7f3","#7a1755"]].map(([l,bg,c])=>(
              <span key={l} className="px-2 py-0.5 rounded-full font-bold" style={{background:bg,color:c}}>{l}</span>
            ))}
            <span className="ml-2 text-xs font-bold px-2 py-0.5 rounded-full text-white" style={{background:"#C93F8B",fontSize:"9px"}}>CI</span>
            <span className="text-xs text-slate-400">= Critère Impératif · ★=4 · NC=exclu · score critère = moy. de ses EE</span>
          </div>
        </div>
      </div>
    </div>
  );
}
// ── FIN QUALISCOPE VIEW ───────────────────────────────────────────────────────





export default function ElEaGrilleTerrain() {
  // ── ELEA brand fonts ──────────────────────────────────────────────────────
  useEffect(() => {
    const link = document.createElement("link");
    link.rel = "stylesheet";
    link.href = "https://fonts.googleapis.com/css2?family=Raleway:wght@600;700;800;900&family=Nunito:wght@400;500;600;700;800&display=swap";
    if (!document.head.querySelector("link[href*='Raleway']")) document.head.appendChild(link);
    const style = document.createElement("style");
    style.id = "elea-fonts";
    if (!document.getElementById("elea-fonts")) {
      style.textContent = `body,*{font-family:'Nunito',sans-serif!important}`;
      document.head.appendChild(style);
    }
  }, []);

  const file = useFileFetch("/data/ELEA_REF_V2.json");
  const [ref, setRef]       = useState<Ref | null>(null);
  const [err, setErr]       = useState<string | null>(null);
  const [sess, setSess]     = useState<Session>(loadSess);
  const [activeTab, setActiveTab] = useState("AT1");
  const [showCfg, setShowCfg]     = useState(false);
  const [flash, setFlash]         = useState(false);
  const [expTheme, setExpTheme]   = useState<Record<string, boolean>>({});
  const [expObj, setExpObj]       = useState<Record<string, boolean>>({});
  const [openCrits, setOpenCrits] = useState<Record<string, boolean>>({});
  const [sidebarSel, setSidebarSel] = useState<{ kind: "theme" | "obj"; val: string } | null>(null);
  const [offline, setOffline] = useState(false);
  const [sidebarOpen, setSidebarOpen] = useState(true);
  const [ctxOpen, setCtxOpen]     = useState<Record<string, boolean>>({});
  const [postVisit, setPostVisit]       = useState(false);
  const [showRestitution, setShowRestitution] = useState(false);
  const [mainTab, setMainTab]           = useState<"mission"|"objectivation"|"a11"|"terrain"|"coherence"|"resultats">("terrain");
  const [missionSection, setMissionSection] = useState<"promesse"|"mission"|"cadrage">("promesse");
  const contentRef  = useRef<HTMLDivElement>(null);
  const importRef   = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (!file) return;
    file.text()
      .then(t => {
        let parsed: Ref;
        try {
          parsed = JSON.parse(t);
        } catch {
          setErr("Erreur de lecture du référentiel (JSON invalide).");
          return;
        }
        setRef(parsed);
        setOffline(false);
        // Cache silencieux — on ignore les erreurs de quota
        saveRefIDB(t).catch(() => { /* silently ignore cache error */ });
      })
      .catch(() => {
        // Pas de réseau → essai depuis le cache IndexedDB
        loadRefIDB().then(cached => {
          if (cached) {
            try { setRef(JSON.parse(cached)); setOffline(true); }
            catch { setErr("Cache corrompu. Reconnectez-vous pour recharger le référentiel."); }
          } else {
            setErr("Connexion requise au premier démarrage pour charger le référentiel HAS.");
          }
        });
      });
  }, [file]);

  useEffect(() => {
    const params = readCockpitParams();
    const id = params?.id_cockpit;
    loadSessIDB(id).then(s => {
      // Si nouvelle session depuis Cockpit (id_cockpit présent mais essms vide) → pré-remplir
      if (params && id && !s.essms) {
        setSess({ ...s, ...params });
      } else {
        setSess(s);
      }
    });
  }, []); // IndexedDB async load + URL params Cockpit

  const update = useCallback((s: Session) => {
    setSess(s); saveSess(s);
    setFlash(true); setTimeout(() => setFlash(false), 900);
  }, []);

  const tabs    = useMemo(() => buildTabs(sess.config), [sess.config]);
  const thMap   = useMemo(() => Object.fromEntries((ref?.T ?? []).map(t => [t.c, t])), [ref]);
  const allCrit = useMemo(() => ref?.C ?? [], [ref]);
  const allEE   = useMemo(() => ref?.E ?? [], [ref]);

  // EEs for active tab
  const ttype = tabType(activeTab);
  const gCode = GRILLE_G[ttype] ?? "";
  const tabData = sess.grilles[activeTab] ?? initTab();
  const themeFilter = tabData.themes;

  const filtEEs = useMemo(() => {
    if (!ref) return [];
    if (ttype === "CVS") return ref.E.filter(e => ["CVS","CVS/CI"].includes(e.cvs));
    return ref.E.filter(e => e.g === gCode && (!themeFilter.length || themeFilter.includes(e.t)));
  }, [ref, ttype, gCode, themeFilter]);

  const critRefs = useMemo(() => {
    const seen = new Set<string>(); const out: string[] = [];
    filtEEs.forEach(e => { if (!seen.has(e.cr)) { seen.add(e.cr); out.push(e.cr); } });
    return out;
  }, [filtEEs]);

  // Tri numérique d'un ref critère ("1.1.1" < "1.2.1" < "2.1.1")
  const sortCritRef = (a: string, b: string) => {
    const pa = a.split(".").map(Number);
    const pb = b.split(".").map(Number);
    for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
      const d = (pa[i] ?? 0) - (pb[i] ?? 0);
      if (d !== 0) return d;
    }
    return 0;
  };

  // Critères visibles après filtre sidebar, triés par numéro
  const visibleCritRefs = useMemo(() => {
    const filtered = !sidebarSel ? critRefs : critRefs.filter(cr => {
      const c = allCrit.find(x => x.r === cr);
      if (!c) return false;
      if (sidebarSel.kind === "theme") return c.t === sidebarSel.val;
      if (sidebarSel.kind === "obj")   return c.o === sidebarSel.val;
      return true;
    });
    return [...filtered].sort(sortCritRef);
  }, [critRefs, sidebarSel, allCrit]);

  // Critères contenant au moins un EE impératif
  const impCrits = useMemo(() => {
    const s = new Set<string>();
    (ref?.E ?? []).filter(e => e.nv === "Impératif").forEach(e => s.add(e.cr));
    return s;
  }, [ref]);

  // Critères où un EE impératif est coté 1, NC ou RI → alerte rouge
  const impAlerts = useMemo(() => {
    const s = new Set<string>();
    if (!ref) return s;
    ref.E.filter(e => e.nv === "Impératif").forEach(e => {
      const cot = tabData.ees[e.id]?.cotation;
      if (cot === "1" || cot === "NC" || cot === "RI") s.add(e.cr);
    });
    return s;
  }, [ref, tabData]);

  // Sidebar tree: thématique → objectif → critère (for current tab)
  const sideTree = useMemo(() => {
    const themes: { code: string; objs: { ref: string; libelle: string; crits: Crit[] }[] }[] = [];
    for (const th of (ref?.T ?? [])) {
      const thCrits = critRefs.map(r => allCrit.find(c => c.r === r)!).filter(c => c?.t === th.c);
      if (!thCrits.length) continue;
      const objMap: Record<string, { ref: string; libelle: string; crits: Crit[] }> = {};
      for (const c of thCrits) {
        if (!objMap[c.o]) {
          const o = ref?.O.find(o => o.r === c.o);
          objMap[c.o] = { ref: c.o, libelle: o?.l ?? c.o, crits: [] };
        }
        objMap[c.o].crits.push(c);
      }
      themes.push({ code: th.c, objs: Object.values(objMap) });
    }
    return themes;
  }, [ref, critRefs, allCrit]);

  useEffect(() => {
    if (!tabs.includes(activeTab)) setActiveTab(tabs[0] ?? "AT1");
  }, [tabs]);

  const setTab = (tab: string, t: TabSaisie) => update({ ...sess, grilles: { ...sess.grilles, [tab]: t } });

  const scrollTo = (cr: string) => {
    setOpenCrits(s => ({ ...s, [cr]: true }));
    setTimeout(() => {
      const el = document.getElementById(`crit-${cr}`);
      if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
    }, 60);
  };

  const exportJSON = () => {
    const payload = { version: 2, exportedAt: new Date().toISOString(), session: sess };
    triggerUserFileDownload({
      content: JSON.stringify(payload, null, 2),
      filename: `${sess.essms || "grille"}_${sess.date || "session"}.elea.json`
    });
  };

  const importJSON = (e: React.ChangeEvent<HTMLInputElement>) => {
    const f = e.target.files?.[0];
    if (!f) return;
    const reader = new FileReader();
    reader.onload = ev => {
      try {
        const parsed = JSON.parse(ev.target?.result as string);
        const imported: Session = parsed.session ?? parsed;
        if (!imported.config || !imported.grilles) throw new Error("Format invalide");
        update(imported);
        alert(`✓ Import réussi — ${imported.essms || "session"}`);
      } catch { alert("❌ Fichier non reconnu. Utilisez un export .elea.json ELEA."); }
    };
    reader.readAsText(f);
    e.target.value = "";
  };

  // Paires de grilles fusionnées pour export Synaé
  const synaePair = (tab: string): string[] => {
    const tt = tabType(tab);
    if (tt === "AT") {
      const n = tab.replace("AT","");
      return [tab, `AT-RC${n}`].filter(t => tabs.includes(t));
    }
    if (tt === "TC") {
      return [tab, "TC-RC"].filter(t => tabs.includes(t));
    }
    if (tt === "AS") return ["AS","AS-RC"].filter(t => tabs.includes(t));
    return [];
  };

  const exportSynae = (tab: string) => {
    if (!ref) return;
    const pair = synaePair(tab);
    if (!pair.length) return;

    const rows = ["Chapitre\tThématique\tObjectif\tCritère\tIntitulé\tCotation\tNiveau\tÉléments de preuve consultés\tÉléments justificatifs\tCommentaires\tIs Question?\tID Chapitre\tID Thématique\tID Objectif\tID Critère\tID Question"];

    const addTabRows = (t: string) => {
      const tt = tabType(t);
      const gc = GRILLE_G[tt] ?? "";
      const td = sess.grilles[t];
      const tabEEs = ref.E.filter(e => e.g===gc&&!["CVS","CVS/CI"].includes(e.cvs));
      const seenCr = new Set<string>(); const crRefs: string[] = [];
      tabEEs.forEach(e => { if (!seenCr.has(e.cr)) { seenCr.add(e.cr); crRefs.push(e.cr); } });

      for (const cr of crRefs) {
        const crit = ref.C.find(c => c.r === cr)!;
        const critEEs = tabEEs.filter(e => e.cr === cr);
        const obj = ref.O.find(o => o.r === crit.o)!;
        const cs  = td?.crits?.[cr] ?? emptyCrit();
        const nums = critEEs.map(ee => td?.ees[ee.id]?.cotation ?? "")
          .filter(v => ["1","2","3","4"].includes(v)).map(Number);
        const cotCalc = nums.length ? (nums.reduce((a,b)=>a+b,0)/nums.length).toFixed(2) : "";

        rows.push([
          `Chapitre ${crit.ch}`, thMap[crit.t]?.l ?? crit.t,
          `Objectif ${crit.o} - ${obj?.l ?? ""}`, `Critère ${cr}`,
          crit.l, cotCalc, "Standard",
          cs.edp_synae.replace(/\n/g," | "), cs.versions[cs.version_courante].justification_nc, cs.versions[cs.version_courante].commentaire_n,
          "", `${crit.ch}`, crit.t, crit.o, cr, ""
        ].map(v => `"${String(v).replace(/"/g,'""')}"`).join("\t"));

        for (const ee of critEEs) {
          const es = td?.ees[ee.id] ?? emptyEE();
          rows.push([
            `Chapitre ${crit.ch}`, thMap[crit.t]?.l ?? crit.t,
            `Objectif ${crit.o} - ${obj?.l ?? ""}`, `Critère ${cr}`,
            `E.E. ${ee.tx}`, es.cotation, ee.nv === "Impératif" ? "Impératif" : "Standard",
            "","","","true",`${crit.ch}`,crit.t,crit.o,cr, ee.uuid ?? ""
          ].map(v => `"${String(v).replace(/"/g,'""')}"`).join("\t"));
        }
      }
    };

    pair.forEach(addTabRows);

    // Validation check
    const allTabEEs = pair.flatMap(t => {
      const tt = tabType(t); const gc = GRILLE_G[tt] ?? "";
      return ref.E.filter(e => e.g===gc&&!["CVS","CVS/CI"].includes(e.cvs));
    });
    const seenCr2 = new Set<string>(); const allCrRefs: string[] = [];
    allTabEEs.forEach(e => { if (!seenCr2.has(e.cr)) { seenCr2.add(e.cr); allCrRefs.push(e.cr); } });
    const nonValidated = allCrRefs.filter(cr => {
      return !pair.some(t => sess.grilles[t]?.crits?.[cr]?.statut_critere === 'validé');
    });
    if (nonValidated.length > 0) {
      const ok = window.confirm(`⚠ ${nonValidated.length} critère(s) non validés :\n${nonValidated.slice(0,8).join(", ")}${nonValidated.length>8?"…":""}\n\nExporter quand même ?`);
      if (!ok) return;
    }

    triggerUserFileDownload({
      content: rows.join("\n"),
      filename: `SYNAE_${tab}_${sess.essms || "evaluation"}_${sess.date || ""}.tsv`
    });
  };

  const exportCSV = () => {
    if (!ref) return;
    const rows = ["Grille;Critère;EE;Libellé EE;Niveau;Cotation;EDP docs;EDP obs;Constats (par EE);Justif NC (EE);Points forts (critère);Axes progrès (critère);Commentaire Synaé (critère)"];
    for (const tab of tabs) {
      const td = sess.grilles[tab]; if (!td) continue;
      const tt = tabType(tab); const gc = GRILLE_G[tt] ?? "";
      const ees = tt === "CVS" ? ref.E.filter(e => ["CVS","CVS/CI"].includes(e.cvs)) : ref.E.filter(e => e.g===gc&&!["CVS","CVS/CI"].includes(e.cvs));
      for (const ee of ees) {
        const es = td.ees[ee.id]; if (!es) continue;
        const docs = [...es.edp_docs.map(k => { const i = parseInt(k.slice(1)); return ee.ed[i]??k; }), ...es.edp_docs_custom].join(" | ");
        const obs  = [...es.edp_obs.map(k => { const i = parseInt(k.slice(1)); return ee.eo[i]??k; }), ...es.edp_obs_custom].join(" | ");
        const cs2 = td.crits?.[ee.cr] ?? emptyCrit();
        rows.push([tab,ee.cr,ee.id,`"${ee.tx}"`,ee.nv,es.cotation,`"${cs2.edp_synae}"`,`"${cs2.versions[cs2.version_courante].justification_nc}"`,`"${es.constats??""}"`,`"${es.justif_nc??""}"`,`"${cs2.pts_forts}"`,`"${cs2.axes_prog}"`,`"${cs2.versions[cs2.version_courante].commentaire_n}"`].join(";"));
      }
    }
    triggerUserFileDownload({ content: rows.join("\n"), filename: `${sess.essms||"grille"}_terrain.csv` });
  };

  // Progress
  const totalEE = allEE.length;
  const doneEE  = Object.values(sess.grilles).reduce((n, td) => n + Object.values(td.ees).filter(e => e.cotation).length, 0);

  if (err) return <div className="flex items-center gap-2 p-8 text-destructive"><AlertCircle className="h-5 w-5"/>{err}</div>;
  if (!ref) return (
    <div className="flex flex-col items-center justify-center min-h-screen gap-3 text-muted-foreground">
      <div className="h-8 w-8 rounded-full border-4 border-indigo-400 border-t-transparent animate-spin"/>
      <p className="text-sm">Chargement référentiel HAS…</p>
    </div>
  );

  const allThemes = ref.T.map(t => t.c);

  return (
    <>
    <div className="flex flex-col bg-background" style={{ height: "100vh" }}>
      {/* ── NAVIGATION PRINCIPALE ── */}
      <div className="flex items-center gap-0 px-3 pt-2 pb-0 bg-teal-900 border-b border-teal-700 flex-shrink-0">
        {/* TRACEUR branding */}
        <div className="flex items-center gap-2 mr-3 pr-3 border-r border-teal-700 pb-2 flex-shrink-0">
          <img src={ELEA_LOGO_B64} alt="ELEA" style={{height:26,width:"auto",filter:"brightness(0) invert(1)",opacity:0.92}}/>
          <div className="flex flex-col leading-none">
            <span className="font-black text-white" style={{fontFamily:"'Raleway', sans-serif",letterSpacing:"0.1em",fontSize:"11px"}}>ELEA · Traceur</span>
            <span className="text-teal-300" style={{fontSize:"8px",letterSpacing:"0.08em"}}>Outil d'évaluation</span>
          </div>
          <span className="text-xs font-bold px-1 py-0.5 rounded" style={{background:"#2CB8C7",color:"white",fontSize:"8px"}}>v2</span>
        </div>
        {(["mission","objectivation","a11","terrain","coherence","resultats"] as const).map(tab => {
          const labels: Record<string, string> = {
            mission: "📁 Mission", objectivation: "🔍 Objectivation", a11: "🤖 @A11",
            terrain: "🗂 Visite", coherence: "🔍 Cohérence", resultats: "📊 Résultats"
          };
          return (
            <button key={tab} onClick={() => setMainTab(tab)}
              className={`px-4 py-2 text-sm font-semibold rounded-t-lg transition-colors border-b-2 ${
                mainTab === tab
                  ? "bg-white text-teal-900 border-teal-900"
                  : "text-teal-300 hover:text-white border-transparent hover:bg-teal-800"
              }`}>
              {labels[tab]}
            </button>
          );
        })}
        <div className="flex-1"/>
        <span className="text-teal-400 text-xs pb-1">{sess.essms || "ESSMS"}</span>
      </div>

      {/* ── HEADER terrain uniquement ── */}
      {mainTab === "terrain" && <div className="flex-shrink-0 bg-teal-700 shadow-md z-30">
        {/* Title bar */}
        <div className="flex items-center justify-between px-3 py-2">
          <div className="flex items-center gap-2 min-w-0">
            <span className="text-white font-bold text-sm whitespace-nowrap">ELEA · Grille Terrain</span>
            <span className="text-teal-200 text-xs truncate">{sess.essms||"ESSMS"}{sess.date?` · ${sess.date}`:""}</span>
            {flash && <span className="text-emerald-300 text-xs font-semibold">✓</span>}
            {offline && <span className="text-amber-300 text-xs font-semibold">📶 hors-ligne</span>}
          </div>
          <div className="flex gap-1.5">
            {/* Restitution */}
            <button onClick={() => setShowRestitution(true)}
              className="h-7 text-xs px-2 rounded border border-amber-400 bg-amber-600 text-amber-100 hover:bg-amber-500 hover:text-white font-semibold transition-colors">
              📊 Restitution
            </button>
            {/* Post-visite toggle */}
            <button onClick={() => setPostVisit(o => !o)}
              className={`h-7 text-xs font-semibold px-2 rounded border transition-colors ${postVisit ? "bg-violet-500 text-white border-violet-400" : "bg-teal-600 text-teal-200 border-indigo-400 hover:text-white"}`}>
              {postVisit ? "✍️ Post-visite" : "🏕 Terrain"}
            </button>
            {/* Import JSON */}
            <input ref={importRef} type="file" accept=".json,.elea.json" className="hidden" onChange={importJSON}/>
            <button onClick={() => importRef.current?.click()}
              className="h-7 text-xs px-2 rounded border border-teal-300 bg-teal-600 text-indigo-100 hover:bg-teal-500 hover:text-white transition-colors">
              ↑ Import
            </button>
            {/* Export JSON */}
            <button onClick={exportJSON}
              className="h-7 text-xs px-2 rounded border border-teal-300 bg-teal-600 text-indigo-100 hover:bg-teal-500 hover:text-white transition-colors">
              ↓ JSON
            </button>
            {/* Export Synaé (onglet actif) */}
            {postVisit && synaePair(activeTab).length > 0 && (
              <button onClick={() => exportSynae(activeTab)}
                className="h-7 text-xs px-2 rounded border border-violet-400 bg-violet-700 text-violet-100 hover:bg-violet-600 hover:text-white font-bold transition-colors">
                ↓ Synaé ({synaePair(activeTab).join("+")})
              </button>
            )}
            <button onClick={exportCSV}
              className="h-7 text-xs px-2 rounded border border-teal-300 bg-teal-600 text-indigo-100 hover:bg-teal-500 hover:text-white flex items-center gap-1 transition-colors">
              <Download className="h-3 w-3"/>CSV
            </button>
            <button onClick={() => setShowCfg(o => !o)} className="text-teal-200 hover:text-white p-1 rounded">
              <Settings className="h-4 w-4"/>
            </button>
          </div>
        </div>
        {/* Progress */}
        <div className="px-3 pb-1.5">
          <div className="flex items-center gap-2">
            <div className="flex-1 h-1 rounded-full bg-teal-800 overflow-hidden">
              <div className="h-full rounded-full bg-emerald-400 transition-all" style={{ width: `${totalEE?Math.round(doneEE/totalEE*100):0}%`}}/>
            </div>
            <span className="text-xs text-teal-200">{doneEE}/{totalEE}</span>
          </div>
        </div>
        {/* Config */}
        {showCfg && (
          <div className="bg-teal-800 border-t border-teal-600 px-3 py-3 space-y-3">
            <div className="flex flex-wrap gap-3 items-end">
              <div>
                <label className="block text-xs text-teal-200 mb-1">Nom ESSMS</label>
                <input className="rounded border border-indigo-500 bg-teal-900 text-white text-xs px-2 py-1 w-44"
                  value={sess.essms} placeholder="ESSMS" onChange={e => update({...sess, essms: e.target.value})}/>
              </div>
              <div>
                <label className="block text-xs text-teal-200 mb-1">Date visite</label>
                <input type="date" className="rounded border border-indigo-500 bg-teal-900 text-white text-xs px-2 py-1"
                  value={sess.date} onChange={e => update({...sess, date: e.target.value})}/>
              </div>
            </div>
            <div className="flex flex-wrap gap-4 items-center">
              <div>
                <label className="block text-xs text-teal-200 mb-1">Traceurs AT</label>
                <div className="flex gap-1">{[1,2,3].map(n=>(
                  <button key={n} onClick={()=>update({...sess,config:{...sess.config,nbAT:n}})}
                    className={`w-8 h-7 rounded text-xs font-bold border ${sess.config.nbAT===n?"bg-white text-teal-700":"bg-teal-700 text-teal-200 border-indigo-500 hover:bg-teal-600"}`}>{n}</button>
                ))}</div>
              </div>
              <div>
                <label className="block text-xs text-teal-200 mb-1">Grilles TC</label>
                <div className="flex gap-1">{[1,2,3].map(n=>(
                  <button key={n} onClick={()=>update({...sess,config:{...sess.config,nbTC:n}})}
                    className={`w-8 h-7 rounded text-xs font-bold border ${sess.config.nbTC===n?"bg-white text-teal-700":"bg-teal-700 text-teal-200 border-indigo-500 hover:bg-teal-600"}`}>{n}</button>
                ))}</div>
              </div>
              <label className="flex items-center gap-2 cursor-pointer text-xs text-teal-200">
                <input type="checkbox" checked={sess.config.hasCVS} onChange={e=>update({...sess,config:{...sess.config,hasCVS:e.target.checked}})}/>CVS/CI
              </label>
              <button onClick={()=>setShowCfg(false)}
                className="h-7 text-xs px-3 rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 transition-colors">
                Fermer
              </button>
            </div>
          </div>
        )}
        {/* Tab bar */}
        <div className="flex border-t border-teal-600 overflow-x-auto">
          {tabs.map(tab => {
            const tt = tabType(tab); const td = sess.grilles[tab];
            const gc = GRILLE_G[tt] ?? "";
            const tabEEs = tt==="CVS" ? ref.E.filter(e=>["CVS","CVS/CI"].includes(e.cvs)) : ref.E.filter(e=>e.g===gc&&!["CVS","CVS/CI"].includes(e.cvs));
            const done = td ? tabEEs.filter(e=>td.ees[e.id]?.cotation).length : 0;
            const active = activeTab === tab;
            return (
              <button key={tab} onClick={()=>setActiveTab(tab)}
                className={`flex-shrink-0 px-3 py-2 text-xs font-bold border-b-2 whitespace-nowrap transition-colors ${active?"border-emerald-400 text-white bg-teal-600":"border-transparent text-teal-300 hover:text-white hover:bg-teal-600"}`}>
                {tab}{done>0&&<span className="ml-1 text-emerald-400">({done})</span>}
              </button>
            );
          })}
        </div>
      </div>}

      {/* ── BODY: sidebar + content ── */}
      {/* ══ ONGLET MISSION ══════════════════════════════════════════════ */}
      {mainTab === "mission" && (() => {
        const S = (key: "promesse"|"mission"|"cadrage") => (
          <button onClick={() => setMissionSection(key)}
            className={`px-4 py-2 text-sm font-semibold rounded-t-lg border-b-2 transition-colors ${
              missionSection===key ? "border-teal-600 text-teal-700 bg-teal-50" : "border-transparent text-slate-500 hover:text-slate-700"
            }`}>
            {{ promesse:"🤝 Promesse", mission:"📋 Mission", cadrage:"🔭 Cadrage" }[key]}
          </button>
        );
        const field = (label: string, key: string, placeholder: string, type="text") => (
          <div key={key}>
            <label className="block text-xs font-semibold text-slate-600 mb-1">{label}</label>
            <input type={type} className="w-full rounded border border-slate-200 px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-teal-300"
              placeholder={placeholder} value={(sess as any)[key] ?? ""}
              onChange={e => update({...sess, [key]: e.target.value} as any)}/>
          </div>
        );
        return (
          <div className="flex-1 overflow-auto bg-slate-50">
            <div className="flex gap-0 px-6 pt-4 border-b border-slate-200 bg-white">
              {S("promesse")}{S("mission")}{S("cadrage")}
            </div>

            <div className="max-w-3xl mx-auto p-6 space-y-5">

              {/* ── PROMESSE — identification & statut rapide ── */}
              {missionSection === "promesse" && (<>
                <div className="flex items-center justify-between">
                  <h3 className="text-base font-bold text-slate-800">🤝 Promesse — Identification</h3>
                  <a href="#" className="text-xs text-teal-500 hover:underline" target="_blank" rel="noopener noreferrer">
                    → Cockpit ELEA pour le pilotage complet
                  </a>
                </div>
                <div className="bg-white rounded-xl border border-slate-200 p-5 space-y-4">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide">ESSMS</p>
                  <div className="grid grid-cols-2 gap-4">
                    {field("Nom de l'ESSMS","essms","EHPAD Les Cerisiers…")}
                    {field("N° FINESS","finess","750xxxxxx")}
                    {field("Réf. mission (Cockpit)","id_cockpit","EV-2026-018")}
                    {field("Type d'ESSMS","typeEssms","EHPAD, IME, SSIAD…")}
                    {field("Département","departement","79 — Deux-Sèvres")}
                  </div>
                </div>
                <div className="bg-white rounded-xl border border-slate-200 p-5 space-y-4">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide">Commanditaire</p>
                  <div className="grid grid-cols-2 gap-4">
                    {field("Organisme Évaluateur (OE)","oe","ETIKEVAL, AMPLEA…")}
                    {field("Référent OE","referentOe","Prénom NOM")}
                    {field("N° BDC / Devis","bdc","DEV-2026-042")}
                    {field("Date BDC","dateBdc","","date")}
                  </div>
                </div>
                <div className="bg-white rounded-xl border border-slate-200 p-5 space-y-3">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide">Statut de la mission</p>
                  <div className="flex gap-2 flex-wrap">
                    {([["preparation","🟡 Préparation"],["visite","🔵 En visite"],["post_visite","🟠 Post-visite"],["livre","✅ Livré"]] as const).map(([val,label]) => (
                      <button key={val} onClick={() => update({...sess, statut: val})}
                        className={`px-3 py-1.5 rounded-lg text-xs font-semibold border transition-colors ${
                          (sess.statut??"preparation")===val ? "bg-teal-600 text-white border-teal-700" : "bg-white text-slate-600 border-slate-200 hover:bg-slate-50"
                        }`}>{label}</button>
                    ))}
                  </div>
                </div>
              </>)}

              {/* ── MISSION — config grilles & équipe ── */}
              {missionSection === "mission" && (<>
                <h3 className="text-base font-bold text-slate-800">📋 Mission — Configuration des grilles</h3>
                <div className="bg-white rounded-xl border border-slate-200 p-5 space-y-4">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide">Calendrier de la visite</p>
                  <div className="grid grid-cols-3 gap-4">
                    {field("Début visite","dateDebut","","date")}
                    {field("Fin visite","dateFin","","date")}
                    {field("Nb jours","nbJours","2")}
                  </div>
                </div>
                <div className="bg-white rounded-xl border border-slate-200 p-5 space-y-4">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide">Configuration des grilles</p>
                  <div className="flex gap-8 flex-wrap">
                    <div>
                      <label className="block text-xs font-semibold text-slate-600 mb-2">Traceurs AT</label>
                      <div className="flex gap-1">{[1,2,3].map(n=>(
                        <button key={n} onClick={()=>update({...sess,config:{...sess.config,nbAT:n}})}
                          className={`w-10 h-8 rounded text-sm font-bold border transition-colors ${sess.config.nbAT===n?"bg-teal-600 text-white border-teal-700":"bg-white text-slate-600 border-slate-200 hover:bg-slate-50"}`}>{n}</button>
                      ))}</div>
                    </div>
                    <div>
                      <label className="block text-xs font-semibold text-slate-600 mb-2">Grilles TC</label>
                      <div className="flex gap-1">{[1,2,3].map(n=>(
                        <button key={n} onClick={()=>update({...sess,config:{...sess.config,nbTC:n}})}
                          className={`w-10 h-8 rounded text-sm font-bold border transition-colors ${sess.config.nbTC===n?"bg-teal-600 text-white border-teal-700":"bg-white text-slate-600 border-slate-200 hover:bg-slate-50"}`}>{n}</button>
                      ))}</div>
                    </div>
                    <label className="flex items-center gap-2 cursor-pointer text-sm text-slate-600">
                      <input type="checkbox" className="w-4 h-4" checked={sess.config.hasCVS} onChange={e=>update({...sess,config:{...sess.config,hasCVS:e.target.checked}})}/>
                      <span className="font-semibold">CVS / CI</span>
                    </label>
                  </div>
                  <p className="text-xs text-slate-400">Onglets actifs : <span className="font-semibold text-teal-600">{tabs.join(" · ")}</span></p>
                </div>
                <div className="bg-white rounded-xl border border-slate-200 p-5 space-y-3">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide">Rôle sur cette mission</p>
                  <div className="flex gap-3">
                    {([["coordonnateur","🎯 Coordonnateur"],["binome","🤝 Évaluateur binôme"]] as const).map(([val,label]) => (
                      <button key={val} onClick={() => update({...sess, roleEmmanuel: val} as any)}
                        className={`px-4 py-2 rounded-lg text-sm font-bold border transition-colors ${
                          (sess as any).roleEmmanuel===val ? "bg-teal-600 text-white border-teal-700" : "bg-white text-slate-600 border-slate-200 hover:bg-slate-50"
                        }`}>{label}</button>
                    ))}
                  </div>
                  {(sess as any).roleEmmanuel === "binome" && field("Nom du Coordonnateur","nomCoordonnateur","Prénom NOM")}
                </div>
                <div className="bg-white rounded-xl border border-slate-200 p-5 space-y-3">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide">Équipe évaluatrice</p>
                  {(sess.evaluateurs ?? [{nom:"",initiales:"",role:"Évaluateur principal"}]).map((ev, i) => (
                    <div key={i} className="grid grid-cols-3 gap-3 items-center">
                      <input className="rounded border border-slate-200 px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-teal-300"
                        placeholder="Prénom NOM" value={ev.nom}
                        onChange={e => { const evs=[...(sess.evaluateurs??[{nom:"",initiales:"",role:""}])]; evs[i]={...ev,nom:e.target.value}; update({...sess,evaluateurs:evs}); }}/>
                      <input className="rounded border border-slate-200 px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-teal-300"
                        placeholder="Initiales" value={ev.initiales}
                        onChange={e => { const evs=[...(sess.evaluateurs??[{nom:"",initiales:"",role:""}])]; evs[i]={...ev,initiales:e.target.value}; update({...sess,evaluateurs:evs}); }}/>
                      <input className="rounded border border-slate-200 px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-teal-300"
                        placeholder="Rôle" value={ev.role}
                        onChange={e => { const evs=[...(sess.evaluateurs??[{nom:"",initiales:"",role:""}])]; evs[i]={...ev,role:e.target.value}; update({...sess,evaluateurs:evs}); }}/>
                    </div>
                  ))}
                  <button onClick={() => update({...sess,evaluateurs:[...(sess.evaluateurs??[{nom:"",initiales:"",role:""}]),{nom:"",initiales:"",role:"Co-évaluateur"}]})}
                    className="text-xs text-teal-600 hover:text-teal-800 font-semibold">+ Ajouter un évaluateur</button>
                </div>
              </>)}

              {/* ── CADRAGE — notes & docs demandés ── */}
              {missionSection === "cadrage" && (<>
                <h3 className="text-base font-bold text-slate-800">🔭 Cadrage — Préparation visite</h3>
                {(sess as any).roleEmmanuel === "binome" && (
                  <div className="rounded-xl bg-slate-50 border border-slate-200 p-4 text-sm text-slate-600">
                    🤝 Cadrage piloté par <strong>{(sess as any).nomCoordonnateur || "le Coordonnateur"}</strong> — tu peux noter ici tes propres repères.
                  </div>
                )}
                <div className="bg-white rounded-xl border border-slate-200 p-5 space-y-4">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide">Réunion de cadrage (J-30)</p>
                  <div className="grid grid-cols-2 gap-4">
                    {field("Date de cadrage","dateCadrage","","date")}
                    {field("Interlocuteur ESSMS","interlocuteurCadrage","Directeur·trice, Responsable qualité…")}
                  </div>
                </div>
                <div className="bg-white rounded-xl border border-slate-200 p-5 space-y-3">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide">Checklist cadrage</p>
                  {([
                    ["courrier","📬 Courrier officiel envoyé à l'ESSMS"],
                    ["planning","📅 Planning de visite proposé"],
                    ["accesSynae","🔑 Accès Synaé obtenu"],
                    ["accesDrive","📁 Accès Drive OE obtenu"],
                  ] as const).map(([key, label]) => {
                    const cl = (sess as any).checklistCadrage ?? {};
                    return (
                      <label key={key} className="flex items-center gap-3 cursor-pointer">
                        <input type="checkbox" className="w-4 h-4 accent-indigo-600"
                          checked={cl[key] ?? false}
                          onChange={e => update({...sess, checklistCadrage: {...cl, [key]: e.target.checked}} as any)}/>
                        <span className={`text-sm ${cl[key] ? "text-slate-400 line-through" : "text-slate-700"}`}>{label}</span>
                      </label>
                    );
                  })}
                  {(() => {
                    const cl = (sess as any).checklistCadrage ?? {};
                    const done = ["courrier","planning","accesSynae","accesDrive"].filter(k=>cl[k]).length;
                    return done === 4
                      ? <div className="rounded-lg bg-green-50 border border-green-200 p-2 text-xs text-green-700 font-semibold">✅ Cadrage complet — prêt pour la Préparation !</div>
                      : done > 0 ? <div className="text-xs text-slate-400">{done}/4 étapes complétées</div>
                      : null;
                  })()}
                </div>
                <div className="bg-white rounded-xl border border-slate-200 p-5">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide mb-3">Notes de cadrage</p>
                  <textarea rows={5} className="w-full rounded border border-slate-200 px-3 py-2 text-sm resize-y focus:outline-none focus:ring-2 focus:ring-teal-300"
                    placeholder="Contexte de l'ESSMS, points d'attention, particularités de l'offre…"
                    value={sess.notesGlobales ?? ""}
                    onChange={e => update({...sess, notesGlobales: e.target.value})}/>
                </div>
                <div className="bg-white rounded-xl border border-slate-200 p-5">
                  <p className="text-xs text-slate-400 font-semibold uppercase tracking-wide mb-3">Documents demandés à l'ESSMS</p>
                  <textarea rows={5} className="w-full rounded border border-slate-200 px-3 py-2 text-sm resize-y focus:outline-none focus:ring-2 focus:ring-teal-300"
                    placeholder={"- Projet d'établissement\n- Rapport d'activité\n- Procédures qualité…"}
                    value={(sess as any).documentsDemandesStr ?? ""}
                    onChange={e => update({...sess, documentsDemandesStr: e.target.value} as any)}/>
                </div>
                {/* Import bibliothèque EDP depuis Cockpit */}
                <div className="bg-teal-50 rounded-xl border border-teal-200 p-5">
                  <p className="text-xs font-bold text-teal-700 mb-1">📥 Bibliothèque EDP — Import Cockpit</p>
                  <p className="text-xs text-slate-500 mb-3">
                    Importer les EDP préparés dans le Cockpit (sensibilisations, formations, documents spécifiques à l'ESSMS).
                    Ces items apparaîtront dans les grilles terrain sous "📋 Préparés (Cockpit)".
                  </p>
                  <div className="flex items-center gap-2 flex-wrap">
                    <button onClick={() => {
                      const inp = document.createElement("input"); inp.type="file"; inp.accept=".json";
                      inp.onchange = async (e) => {
                        const f = (e.target as HTMLInputElement).files?.[0]; if (!f) return;
                        try {
                          const data = JSON.parse(await f.text());
                          const imported: EDPBibItem[] = data.edpBib ?? data.session?.edpBib ?? [];
                          const importedLinks: EDPBibLink[] = data.edpBibLinks ?? data.session?.edpBibLinks ?? [];
                          if (imported.length===0) { alert("❌ Aucun EDP trouvé dans ce fichier (clé edpBib manquante)"); return; }
                          const current = sess.edpBib ?? [];
                          const currentLinks = sess.edpBibLinks ?? [];
                          const newIds = new Set(current.map(x=>x.id));
                          const toAdd = imported.filter(x=>!newIds.has(x.id));
                          const toAddLinks = importedLinks.filter(l=>!currentLinks.some(x=>x.bibId===l.bibId&&x.critRef===l.critRef));
                          update({...sess, edpBib:[...current,...toAdd], edpBibLinks:[...currentLinks,...toAddLinks]});
                          alert(`✅ ${toAdd.length} EDP et ${toAddLinks.length} liaisons importés`);
                        } catch { alert("❌ Fichier JSON invalide"); }
                      }; inp.click();
                    }} className="text-xs font-bold px-3 py-2 rounded-xl text-white hover:opacity-90 shadow" style={{background:"#2CB8C7"}}>
                      📥 Importer JSON Cockpit
                    </button>
                    {(sess.edpBib??[]).length > 0 && (
                      <span className="text-xs font-bold text-teal-600">
                        ✅ {sess.edpBib!.length} EDP en bibliothèque
                      </span>
                    )}
                    <button onClick={() => {
                      const data = JSON.stringify({edpBib: sess.edpBib??[], edpBibLinks: sess.edpBibLinks??[]}, null, 2);
                      const a = document.createElement("a"); a.href="data:application/json,"+encodeURIComponent(data);
                      a.download=`EDP_${sess.essms||"mission"}_${new Date().toISOString().slice(0,10)}.json`; a.click();
                    }} className="text-xs font-bold px-3 py-2 rounded-xl border border-teal-300 bg-white text-teal-600 hover:bg-teal-50">
                      📤 Exporter vers Cockpit
                    </button>
                  </div>
                </div>
              </>)}

            </div>
          </div>
        );
      })()}

      {/* ══ ONGLET CLASSEMENT ══════════════════════════════════════════ */}
      {mainTab === "objectivation" && ref && (
        <ObjectivationView sess={sess} rd={ref}
          onUpdate={patch => { const s={...sess,...patch}; setSess(s); saveSessIDB(s); }}
          onNavigate={(tabKey, critRef) => {
            setActiveTab(tabKey);
            setMainTab("terrain");
            setTimeout(() => document.getElementById(`crit-${critRef}`)?.scrollIntoView({behavior:"smooth",block:"start"}), 100);
          }}
        />
      )}

      {/* ══ ONGLET DOCUMENTS ════════════════════════════════════════════ */}
      {mainTab === "a11" && (
        <A11Connector sess={sess} rd={ref}
          onUpdate={patch => { const s={...sess,...patch}; setSess(s); saveSessIDB(s); }}
        />
      )}

      {/* ══ ONGLET COHÉRENCE ════════════════════════════════════════════ */}
      {mainTab === "coherence" && ref && (
        <div className="flex-1 overflow-auto p-6 bg-slate-50">
          <div className="max-w-4xl mx-auto space-y-6">
            <h2 className="text-lg font-bold text-slate-800">🔍 Cohérence — Contrôle qualité de la saisie</h2>

            {/* Avancement par onglet */}
            <div className="bg-white rounded-xl border border-slate-200 p-5">
              <h3 className="text-sm font-bold text-slate-700 mb-4">📊 Avancement par onglet</h3>
              <div className="space-y-3">
                {tabs.map(tab => {
                  const tt = tabType(tab); const gc = GRILLE_G[tt] ?? "";
                  const tabEEs = tt === "CVS"
                    ? ref.E.filter((e: EE) => ["CVS","CVS/CI"].includes(e.cvs))
                    : ref.E.filter((e: EE) => e.g===gc&&!["CVS","CVS/CI"].includes(e.cvs));
                  const td = sess.grilles[tab];
                  const coté = tabEEs.filter(e => td?.ees[e.id]?.cotation).length;
                  const pct = tabEEs.length ? Math.round(coté/tabEEs.length*100) : 0;
                  const barColor = pct===100?"#059669":pct>=50?"#d97706":"#C93F8B";
                  return (
                    <div key={tab} className="flex items-center gap-3">
                      <span className="text-xs font-bold text-teal-600 w-12 flex-shrink-0">{tab}</span>
                      <div className="flex-1 h-4 rounded-full bg-slate-100 overflow-hidden border border-slate-200">
                        <div className="h-full rounded-full transition-all" style={{width:`${pct}%`,backgroundColor:barColor}}/>
                      </div>
                      <span className="text-xs font-bold w-24 flex-shrink-0" style={{color:barColor}}>
                        {coté}/{tabEEs.length} EEs ({pct}%)
                      </span>
                      {pct===100 && <span className="text-xs text-emerald-600 font-bold">✓</span>}
                    </div>
                  );
                })}
              </div>
            </div>

            {/* Critères impératifs non satisfaits */}
            {(() => {
              const impAlertsList = ref.E.filter((e: EE) => e.nv==="Impératif").flatMap(e => {
                return tabs.flatMap(tab => {
                  const tt = tabType(tab); const gc = GRILLE_G[tt]??"";
                  const belongs = tt==="CVS" ? ["CVS","CVS/CI"].includes(e.cvs) : e.g===gc&&!["CVS","CVS/CI"].includes(e.cvs);
                  if (!belongs) return [];
                  const cot = sess.grilles[tab]?.ees[e.id]?.cotation ?? "";
                  if (!["1","NC","RI"].includes(cot)) return [];
                  return [{tab, ee: e, cot}];
                });
              });
              if (!impAlertsList.length) return (
                <div className="bg-emerald-50 border border-emerald-200 rounded-xl p-4 text-sm text-emerald-700 font-semibold">
                  ✅ Aucun critère impératif non satisfait détecté.
                </div>
              );
              return (
                <div className="bg-white rounded-xl border border-pink-200 p-5">
                  <h3 className="text-sm font-bold text-red-700 mb-3">⚠ Critères impératifs non satisfaits ({impAlertsList.length})</h3>
                  <div className="space-y-1">
                    {impAlertsList.map(({tab,ee,cot},i) => {
                      const crit = ref.C.find((c: any)=>c.r===ee.cr);
                      return (
                        <div key={i} className="flex items-start gap-3 text-xs rounded-lg px-3 py-2 bg-pink-50 border border-red-100">
                          <span className="font-bold text-pink-600 flex-shrink-0 w-8">{cot}</span>
                          <span className="font-mono text-red-700 flex-shrink-0">{ee.cr}</span>
                          <span className="text-slate-600 flex-shrink-0 w-10">[{tab}]</span>
                          <span className="text-slate-700 flex-1">{ee.tx}</span>
                        </div>
                      );
                    })}
                  </div>
                </div>
              );
            })()}

            {/* EEs sans cotation */}
            {(() => {
              const missing = tabs.flatMap(tab => {
                const tt = tabType(tab); const gc = GRILLE_G[tt]??"";
                const tabEEs = tt==="CVS"
                  ? ref.E.filter((e: EE)=>["CVS","CVS/CI"].includes(e.cvs))
                  : ref.E.filter((e: EE)=>e.g===gc&&!["CVS","CVS/CI"].includes(e.cvs));
                return tabEEs.filter(e => !sess.grilles[tab]?.ees[e.id]?.cotation).map(e=>({tab,ee:e}));
              });
              if (!missing.length) return (
                <div className="bg-emerald-50 border border-emerald-200 rounded-xl p-4 text-sm text-emerald-700 font-semibold">
                  ✅ Tous les EEs sont cotés.
                </div>
              );
              // Group by tab
              const byTab: Record<string,typeof missing> = {};
              missing.forEach(m => { if (!byTab[m.tab]) byTab[m.tab]=[]; byTab[m.tab].push(m); });
              return (
                <div className="bg-white rounded-xl border border-amber-200 p-5">
                  <h3 className="text-sm font-bold text-amber-700 mb-3">⏳ EEs non cotés — {missing.length} manquant(s)</h3>
                  <div className="space-y-3">
                    {Object.entries(byTab).map(([tab, items]) => (
                      <div key={tab}>
                        <p className="text-xs font-bold text-teal-600 mb-1">{tab} — {items.length} manquant(s)</p>
                        <div className="space-y-0.5 pl-3">
                          {items.slice(0,10).map(({ee},i) => (
                            <p key={i} className="text-xs text-slate-600">
                              <span className="font-mono text-teal-500">{ee.cr}</span> — {ee.lt.toUpperCase()} — {ee.tx.slice(0,80)}…
                            </p>
                          ))}
                          {items.length > 10 && <p className="text-xs text-slate-400 italic">… et {items.length-10} autres</p>}
                        </div>
                      </div>
                    ))}
                  </div>
                </div>
              );
            })()}

            {/* Contradictions inter-onglets (même critère, écart > 1.5) */}
            {(() => {
              const contradictions: Array<{cr:string; tab1:string; avg1:number; tab2:string; avg2:number}> = [];
              const seenCr = new Set<string>();
              ref.E.forEach((e: EE) => {
                if (seenCr.has(e.cr)) return; seenCr.add(e.cr);
                const crEEs = ref.E.filter((x: EE)=>x.cr===e.cr);
                const tabAvgs = tabs.map(tab => {
                  const tt=tabType(tab); const gc=GRILLE_G[tt]??"";
                  const tEEs = crEEs.filter((x: EE)=>tt==="CVS"?["CVS","CVS/CI"].includes(x.cvs):x.g===gc&&!["CVS","CVS/CI"].includes(x.cvs));
                  const nums = tEEs.map((x: EE)=>sess.grilles[tab]?.ees[x.id]?.cotation??"").filter((v: string)=>["1","2","3","4"].includes(v)).map(Number);
                  return {tab, avg: nums.length?nums.reduce((a: number,b: number)=>a+b)/nums.length:null};
                }).filter(x=>x.avg!==null) as {tab:string;avg:number}[];
                for (let i=0;i<tabAvgs.length-1;i++) {
                  for (let j=i+1;j<tabAvgs.length;j++) {
                    if (Math.abs(tabAvgs[i].avg-tabAvgs[j].avg)>1.5) {
                      contradictions.push({cr:e.cr,tab1:tabAvgs[i].tab,avg1:tabAvgs[i].avg,tab2:tabAvgs[j].tab,avg2:tabAvgs[j].avg});
                    }
                  }
                }
              });
              if (!contradictions.length) return (
                <div className="bg-emerald-50 border border-emerald-200 rounded-xl p-4 text-sm text-emerald-700 font-semibold">
                  ✅ Aucune contradiction significative entre onglets détectée.
                </div>
              );
              return (
                <div className="bg-white rounded-xl border border-orange-200 p-5">
                  <h3 className="text-sm font-bold text-orange-700 mb-3">🔄 Contradictions inter-onglets — écart &gt; 1.5 ({contradictions.length})</h3>
                  <div className="space-y-1">
                    {contradictions.map(({cr,tab1,avg1,tab2,avg2},i) => {
                      const crit = ref.C.find((c: any)=>c.r===cr);
                      return (
                        <div key={i} className="flex items-center gap-3 text-xs rounded-lg px-3 py-2 bg-orange-50 border border-orange-100">
                          <span className="font-mono font-bold text-teal-600 flex-shrink-0">{cr}</span>
                          <span className="text-slate-600 flex-1 truncate">{crit?.l}</span>
                          <span className="font-bold text-slate-500">{tab1} : {avg1.toFixed(1)}</span>
                          <span className="text-slate-400">↔</span>
                          <span className="font-bold text-slate-500">{tab2} : {avg2.toFixed(1)}</span>
                          <span className="font-bold text-orange-600">Δ {Math.abs(avg1-avg2).toFixed(1)}</span>
                        </div>
                      );
                    })}
                  </div>
                </div>
              );
            })()}

            {/* Couverture EDP */}
            {(() => {
              const allC = ref.C;
              const withEDP = allC.filter((c: any) => {
                const terrain = tabs.some((tab:string) => { const cs = sess.grilles[tab]?.crits[c.r]; return cs && (cs.edp_docs_custom.length+cs.edp_obs_custom.length+(cs.edp_bib_ids?.length??0))>0; });
                return terrain || (sess.edpBibLinks??[]).some((l:any)=>l.critRef===c.r&&!l.absent);
              }).length;
              const withAbsent = allC.filter((c: any) => (sess.edpBibLinks??[]).some((l:any)=>l.critRef===c.r&&l.absent)).length;
              const none = allC.length - withEDP - withAbsent;
              return (
                <div className="bg-white rounded-xl border border-teal-200 p-5">
                  <h3 className="text-sm font-bold text-teal-700 mb-3">📋 Couverture EDP — {allC.length} critères</h3>
                  <div className="flex h-3 rounded-full overflow-hidden mb-2">
                    <div style={{width:`${withEDP/allC.length*100}%`,background:"#10b981"}}/><div style={{width:`${withAbsent/allC.length*100}%`,background:"#f59e0b"}}/><div className="flex-1 bg-slate-200"/>
                  </div>
                  <div className="flex gap-4 text-xs mb-2 flex-wrap">
                    <span className="text-green-700 font-bold">✅ Avec EDP : {withEDP}</span>
                    <span className="text-amber-700 font-bold">⚠️ Aucun pertinent : {withAbsent}</span>
                    <span className="text-slate-400">⬜ Non traités : {none}</span>
                  </div>
                  {none > 0 ? <p className="text-xs text-slate-500 italic">→ Compléter dans <span className="font-bold text-teal-600">🔍 Objectivation</span></p>
                  : <p className="text-xs text-green-600 font-bold">✅ Tous les critères sont traités</p>}
                </div>
              );
            })()}
          </div>
        </div>
      )}

      {/* ══ ONGLET QUALISCOPE ══════════════════════════════════════════ */}
      {mainTab === "resultats" && ref && <RésultatsView sess={sess} rd={ref}/>}

      {/* ══ ONGLET TERRAIN ══════════════════════════════════════════════ */}
      {mainTab === "terrain" && <div className="flex flex-1 overflow-hidden relative">

        {/* ── LEFT SIDEBAR ── */}
        {/* Toggle sidebar button */}
        <button onClick={() => setSidebarOpen(o => !o)}
          title={sidebarOpen ? "Réduire la barre" : "Afficher la barre"}
          className="absolute left-0 top-1/2 z-20 -translate-y-1/2 flex items-center justify-center bg-teal-100 hover:bg-teal-200 border border-teal-300 rounded-r-md transition-all"
          style={{ width: 14, height: 40, marginLeft: sidebarOpen ? 116 : 0 }}>
          {sidebarOpen
            ? <ChevronRight className="h-3 w-3 text-teal-600"/>
            : <ChevronRight className="h-3 w-3 text-teal-600 rotate-180"/>}
        </button>
        <div className="flex-shrink-0 border-r bg-card overflow-y-auto transition-all"
          style={{ width: sidebarOpen ? "116px" : "0px", overflow: sidebarOpen ? "auto" : "hidden", minWidth: 0 }}>
          {/* Thématique filter pills */}
          <div className="px-1.5 pt-1.5 pb-1 border-b border-muted">
            <div className="flex flex-wrap gap-1">
              <button onClick={()=>setTab(activeTab,{...tabData,themes:[]})}
                className={`text-xs px-1.5 py-0.5 rounded border font-bold ${!tabData.themes.length?"bg-teal-100 border-indigo-400 text-teal-700":"border-gray-200 text-muted-foreground hover:bg-muted"}`}>
                Ttes
              </button>
              {allThemes.map(code=>{
                const th=thMap[code]; const on=tabData.themes.includes(code);
                return(
                  <button key={code} onClick={()=>{
                    const n=on?tabData.themes.filter(t=>t!==code):[...tabData.themes,code];
                    setTab(activeTab,{...tabData,themes:n});
                  }}
                    style={on?{backgroundColor:`#${th?.bg}`,color:`#${th?.fg}`,borderColor:`#${th?.fg}60`}:{}}
                    className={`text-xs px-1.5 py-0.5 rounded border font-bold transition-colors ${on?"":"border-gray-200 text-muted-foreground hover:bg-muted"}`}>
                    {code}
                  </button>
                );
              })}
            </div>
          </div>

          {/* Tree: thématique → objectif → critère */}
          <div className="py-1">
            {sideTree.map(({ code, objs }) => {
              const th = thMap[code];
              const thOpen = expTheme[code] !== false;
              // Count done in this thématique for current tab
              const thCrits = objs.flatMap(o => o.crits);
              const thDone  = thCrits.filter(c => filtEEs.filter(e=>e.cr===c.r).some(e=>tabData.ees[e.id]?.cotation)).length;
              return (
                <div key={code}>
                  <button
                    onClick={() => {
                      setExpTheme(s => ({...s, [code]: !thOpen}));
                      setSidebarSel(s => s?.kind==="theme" && s.val===code ? null : { kind: "theme", val: code });
                    }}
                    title={th?.l}
                    className="w-full flex items-center gap-1 px-1.5 py-1.5 text-left hover:bg-muted transition-colors"
                    style={{ backgroundColor: sidebarSel?.kind==="theme" && sidebarSel.val===code ? `#${th?.bg}80` : th?`#${th.bg}30`:undefined }}>
                    {thOpen?<ChevronDown className="h-3 w-3 flex-shrink-0 text-muted-foreground"/>:<ChevronRight className="h-3 w-3 flex-shrink-0 text-muted-foreground"/>}
                    <span className="text-xs font-bold px-1 py-0.5 rounded flex-shrink-0" style={{ backgroundColor:`#${th?.bg}`, color:`#${th?.fg}` }}>{code}</span>
                    <span className="text-xs flex-shrink-0 ml-auto font-semibold"
                      style={{color:thDone===thCrits.length&&thCrits.length>0?"#059669":thDone>0?"#D97706":"#9CA3AF"}}>
                      {thDone}/{thCrits.length}
                    </span>
                  </button>
                  {thOpen && objs.map(obj => {
                    const objOpen = expObj[obj.ref] !== false;
                    const objDone = obj.crits.filter(c=>filtEEs.filter(e=>e.cr===c.r).some(e=>tabData.ees[e.id]?.cotation)).length;
                    return (
                      <div key={obj.ref}>
                        <button
                          onClick={() => {
                            setExpObj(s => ({...s, [obj.ref]: !objOpen}));
                            setSidebarSel(s => s?.kind==="obj" && s.val===obj.ref ? null : { kind: "obj", val: obj.ref });
                          }}
                          title={obj.libelle}
                          className="w-full flex items-center gap-1 pl-4 pr-1.5 py-1 text-left hover:bg-muted transition-colors"
                          style={{ backgroundColor: sidebarSel?.kind==="obj" && sidebarSel.val===obj.ref ? "#E0E7FF" : undefined }}>
                          {objOpen?<ChevronDown className="h-2.5 w-2.5 flex-shrink-0 text-muted-foreground"/>:<ChevronRight className="h-2.5 w-2.5 flex-shrink-0 text-muted-foreground"/>}
                          <span className="text-xs font-semibold text-muted-foreground">{obj.ref}</span>
                          <span className="text-xs flex-shrink-0 ml-auto"
                            style={{color:objDone===obj.crits.length&&obj.crits.length>0?"#059669":objDone>0?"#D97706":"#9CA3AF"}}>
                            {objDone}/{obj.crits.length}
                          </span>
                        </button>
                        {objOpen && obj.crits.map(c => {
                          const critEEs  = filtEEs.filter(e=>e.cr===c.r);
                          const critDone = critEEs.filter(e=>tabData.ees[e.id]?.cotation).length;
                          const critTotal = critEEs.length;
                          const isImp   = impCrits.has(c.r);
                          const hasAlert = impAlerts.has(c.r);
                          const cot = critCot(critEEs, tabData.ees);
                          // Couleur dot : alerte rouge > vert complet > orange en cours > rouge impératif non commencé > gris
                          const dotColor = hasAlert ? "#DC2626"
                            : critDone===critTotal && critTotal>0 ? "#059669"
                            : critDone>0 ? "#F59E0B"
                            : isImp ? "#FCA5A5" : "#D1D5DB";
                          const cotColor = cot === "" ? "" : Number(cot) >= 3 ? "#059669" : Number(cot) >= 2 ? "#D97706" : "#DC2626";
                          return (
                            <button key={c.r} onClick={()=>scrollTo(c.r)}
                              title={`${c.l}${hasAlert ? " ⚠ EE impératif coté 1/NC/RI !" : ""}`}
                              className="w-full flex items-center gap-1 pl-6 pr-1.5 py-0.5 text-left hover:bg-teal-50 transition-colors">
                              <span style={{ width: 6, height: 6, borderRadius: "50%", flexShrink: 0, backgroundColor: dotColor }} />
                              <span className={`text-xs font-mono font-bold flex-shrink-0 ${hasAlert?"text-red-700":isImp?"text-pink-500":"text-teal-600"}`}>{c.r}</span>
                              {hasAlert && <span className="text-pink-600 text-xs font-bold flex-shrink-0">⚠</span>}
                              {cot && <span className="text-xs font-bold ml-auto flex-shrink-0" style={{ color: cotColor }}>{cot}</span>}
                            </button>
                          );
                        })}
                      </div>
                    );
                  })}
                </div>
              );
            })}
          </div>
        </div>

        {/* ── MAIN CONTENT ── */}
        <div ref={contentRef} className="flex-1 overflow-y-auto px-4 py-3">
          {/* Barre de progression post-visite */}
          {postVisit && (() => {
            const pair = synaePair(activeTab);
            const allPairEEs = pair.flatMap(t => { const tt=tabType(t);const gc=GRILLE_G[tt]??""; return ref.E.filter(e=>e.g===gc&&!["CVS","CVS/CI"].includes(e.cvs)); });
            const seenV = new Set<string>(); allPairEEs.forEach(e=>seenV.add(e.cr));
            const crList = [...seenV];
            const validated = crList.filter(cr => pair.some(t => sess.grilles[t]?.crits?.[cr]?.statut_critere === 'validé')).length;
            return crList.length > 0 ? (
              <div className="mb-3 rounded-lg bg-violet-50 border border-violet-200 px-3 py-2 flex items-center gap-3">
                <span className="text-xs font-bold text-violet-700">Post-visite Synaé</span>
                <div className="flex-1 h-2 rounded-full bg-violet-100 overflow-hidden">
                  <div className="h-full rounded-full bg-violet-500 transition-all" style={{ width: `${Math.round(validated/crList.length*100)}%` }}/>
                </div>
                <span className="text-xs font-bold text-violet-700">{validated}/{crList.length} critères validés</span>
                {pair.length > 0 && validated === crList.length && (
                  <span className="text-xs font-bold text-emerald-600">→ Prêt pour export Synaé</span>
                )}
              </div>
            ) : null;
          })()}

          {/* Tab label + filtre actif */}
          <div className="flex items-center gap-2 mb-3 flex-wrap">
            <span className="text-base font-bold text-teal-700">{activeTab}</span>
            <span className="text-sm text-muted-foreground">{GRILLE_LABELS[ttype]}</span>
            {sidebarSel && (
              <button onClick={() => setSidebarSel(null)}
                className="flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-teal-100 text-teal-700 border border-teal-300 hover:bg-teal-200">
                <X className="h-3 w-3"/>
                {sidebarSel.kind === "theme" ? `Thématique ${sidebarSel.val}` : `Objectif ${sidebarSel.val}`}
              </button>
            )}
          </div>

          {/* Contexte AT */}
          {(ttype === "AT" || ttype === "AT-RC") && (() => {
            const ctxKey = `ctx-${activeTab}`;
            const hasData = !!(tabData.contexte.initiales || tabData.contexte.profil || tabData.contexte.duree);
            const isOpen = ctxOpen[ctxKey] !== false && (!hasData || ctxOpen[ctxKey] === true);
            return (
              <div className="mb-4 rounded-xl border border-amber-200 overflow-hidden">
                {/* Header always visible */}
                <button onClick={() => setCtxOpen(s => ({ ...s, [ctxKey]: !isOpen }))}
                  className="w-full flex items-center justify-between px-4 py-2 bg-amber-50 hover:bg-amber-100 transition-colors text-left">
                  <div className="flex items-center gap-2">
                    <span className="text-xs font-bold text-amber-800">📝 Contexte — {activeTab}</span>
                    {hasData && !isOpen && (
                      <span className="text-xs text-amber-700">
                        {[tabData.contexte.initiales, tabData.contexte.profil, tabData.contexte.duree].filter(Boolean).join(" · ")}
                      </span>
                    )}
                  </div>
                  <span className="text-xs text-amber-500">{isOpen ? "▲ masquer" : "▼ modifier"}</span>
                </button>
                {/* Fields collapsible */}
                {isOpen && (
                  <div className="px-4 py-3 bg-amber-50 border-t border-amber-200">
                    <div className="grid gap-2" style={{ gridTemplateColumns: "repeat(auto-fit, minmax(130px, 1fr))" }}>
                      {[["initiales","Initiales","M.D."],["profil","Profil","adulte, TSA"],["duree","Durée séjour","3 ans"],["notes","Notes","non verbal…"]].map(([f,l,ph])=>(
                        <div key={f}>
                          <label className="text-xs text-amber-700 font-semibold block mb-0.5">{l}</label>
                          <input className="w-full rounded border border-amber-300 bg-white text-sm px-2 py-1"
                            placeholder={ph} value={(tabData.contexte as Record<string,string>)[f]}
                            onChange={e=>setTab(activeTab,{...tabData,contexte:{...tabData.contexte,[f]:e.target.value}})}/>
                        </div>
                      ))}
                    </div>
                    {hasData && (
                      <button onClick={() => setCtxOpen(s => ({ ...s, [ctxKey]: false }))}
                        className="mt-2 text-xs text-amber-700 border border-amber-300 rounded px-2 py-0.5 hover:bg-amber-100">
                        ▲ Masquer
                      </button>
                    )}
                  </div>
                )}
              </div>
            );
          })()}

          {/* Interlocuteurs TC / TC-RC / AS / AS-RC */}
          {["TC","TC-RC","AS","AS-RC"].includes(ttype) && (() => {
            const interlocs = tabData.contexte.interlocuteurs ?? [];
            const addRow = () => {
              if (interlocs.length >= 10) return;
              setTab(activeTab, { ...tabData, contexte: { ...tabData.contexte,
                interlocuteurs: [...interlocs, { fonction: "", initiales: "" }] } });
            };
            const updRow = (i: number, field: keyof Interlocuteur, val: string) => {
              const next = interlocs.map((r, idx) => idx === i ? { ...r, [field]: val } : r);
              setTab(activeTab, { ...tabData, contexte: { ...tabData.contexte, interlocuteurs: next } });
            };
            const delRow = (i: number) => {
              const next = interlocs.filter((_, idx) => idx !== i);
              setTab(activeTab, { ...tabData, contexte: { ...tabData.contexte, interlocuteurs: next } });
            };
            const ctxKey = `ctx-${activeTab}`;
            const hasData = interlocs.length > 0;
            const isOpen = ctxOpen[ctxKey] !== false && (!hasData || ctxOpen[ctxKey] === true);
            return (
              <div className="mb-4 rounded-xl border border-blue-200 overflow-hidden">
                {/* Header always visible */}
                <button onClick={() => setCtxOpen(s => ({ ...s, [ctxKey]: !isOpen }))}
                  className="w-full flex items-center justify-between px-4 py-2 bg-blue-50 hover:bg-blue-100 transition-colors text-left">
                  <div className="flex items-center gap-2 flex-wrap">
                    <span className="text-xs font-bold text-blue-800">👥 Interlocuteurs — {activeTab}</span>
                    {hasData && !isOpen && (
                      <span className="text-xs text-blue-700">
                        {interlocs.map(r => r.fonction || r.initiales).filter(Boolean).join(" · ")}
                      </span>
                    )}
                    {!hasData && <span className="text-xs text-blue-400 italic">aucun enregistré</span>}
                  </div>
                  <span className="text-xs text-blue-500 flex-shrink-0">{isOpen ? "▲ masquer" : "▼ modifier"}</span>
                </button>
                {/* Fields collapsible */}
                {isOpen && (
                  <div className="px-4 py-3 bg-blue-50 border-t border-blue-200 space-y-2">
                    {interlocs.length === 0 && (
                      <p className="text-xs text-blue-400 italic">Aucun interlocuteur — cliquez Ajouter</p>
                    )}
                    {interlocs.map((row, i) => (
                      <div key={i} className="flex items-center gap-2">
                        <span className="text-xs text-blue-400 font-mono w-4 flex-shrink-0">{i+1}.</span>
                        <input className="flex-1 rounded border border-blue-200 bg-white text-xs px-2 py-1"
                          placeholder="Fonction / poste (ex : Directeur, IDE, AES…)"
                          value={row.fonction} onChange={e => updRow(i, "fonction", e.target.value)}/>
                        <input className="w-16 rounded border border-blue-200 bg-white text-xs px-2 py-1"
                          placeholder="Init." value={row.initiales}
                          onChange={e => updRow(i, "initiales", e.target.value)}/>
                        <button onClick={() => delRow(i)} className="text-blue-300 hover:text-pink-500">
                          <X className="h-3.5 w-3.5"/>
                        </button>
                      </div>
                    ))}
                    <div className="flex items-center gap-2 pt-1">
                      {interlocs.length < 10 && (
                        <button onClick={addRow}
                          className="flex items-center gap-1 text-xs text-blue-700 border border-blue-300 bg-white rounded px-2 py-0.5 hover:bg-blue-100">
                          <Plus className="h-3 w-3"/>Ajouter
                        </button>
                      )}
                      {hasData && (
                        <button onClick={() => setCtxOpen(s => ({ ...s, [ctxKey]: false }))}
                          className="text-xs text-blue-700 border border-blue-300 bg-white rounded px-2 py-0.5 hover:bg-blue-100">
                          ▲ Masquer
                        </button>
                      )}
                    </div>
                  </div>
                )}
              </div>
            );
          })()}

          {/* Critères */}
          {visibleCritRefs.length === 0 ? (
            <div className="flex flex-col items-center justify-center py-16 text-muted-foreground gap-2">
              <p className="text-sm">Aucun critère pour cette sélection.</p>
              {sidebarSel && <button onClick={() => setSidebarSel(null)} className="text-xs text-teal-600 underline">Effacer le filtre</button>}
            </div>
          ) : (
            visibleCritRefs.map(cr => {
              const crit = allCrit.find(c => c.r === cr)!;
              const ees  = filtEEs.filter(e => e.cr === cr);
              const isImp   = impCrits.has(cr);
              const hasAlert = impAlerts.has(cr);
              return (
                <CritBlock key={cr} crit={crit} ees={ees} thMap={thMap} edpBib={sess.edpBib??[]}
                  tabSaisie={tabData} onUpdate={t=>setTab(activeTab,t)}
                  isImp={isImp} hasAlert={hasAlert} anchor={`crit-${cr}`}
                  isOpen={openCrits[cr] ?? false}
                  onToggle={() => setOpenCrits(s => ({...s, [cr]: !(s[cr]??false)}))}
                  grille={ttype} postVisit={postVisit}/>
              );
            })
          )}
        </div>
      </div>} {/* end terrain tab */}
    </div>

    {/* ── MODAL RESTITUTION — par thématique ────────────────────────── */}
    {showRestitution && (() => {
      const cotColor = (v: string) =>
        ["4","*"].includes(v) ? "#059669" : v==="3" ? "#65a30d" : v==="2" ? "#d97706" :
        ["1","NC","RI"].includes(v) ? "#C93F8B" : "#9ca3af";
      const cotBg = (v: string) =>
        ["4","*"].includes(v) ? "#d1fae5" : v==="3" ? "#ecfccb" : v==="2" ? "#fef3c7" :
        ["1","NC","RI"].includes(v) ? "#fce7f3" : "#f9fafb";

      // All impératif EEs
      const impEEs = ref.E.filter(e => e.nv === "Impératif");

      // Group by thématique
      const thGroups: Record<string, string[]> = {}; // thCode -> crRefs[]
      impEEs.forEach(e => {
        const crit = ref.C.find(c => c.r === e.cr); if (!crit) return;
        if (!thGroups[crit.t]) thGroups[crit.t] = [];
        if (!thGroups[crit.t].includes(e.cr)) thGroups[crit.t].push(e.cr);
      });

      // Aggregate pts_forts and axes_prog across all tabs for a given set of crits
      const agg = (crRefs: string[], field: "pts_forts"|"axes_prog") =>
        crRefs.flatMap(cr => tabs.map(tab => sess.grilles[tab]?.crits?.[cr]?.[field] ?? ""))
          .filter(Boolean).join("\n");

      // Get tab results for a critère
      const getCrTabResults = (cr: string, crEEsImp: EE[]) =>
        tabs.map(tab => {
          const tt = tabType(tab); const gc = GRILLE_G[tt] ?? "";
          const tabEEs = crEEsImp.filter(e =>
            tt === "CVS" ? ["CVS","CVS/CI"].includes(e.cvs) : e.g===gc&&!["CVS","CVS/CI"].includes(e.cvs)
          );
          if (!tabEEs.length) return null;
          const td = sess.grilles[tab];
          const eeRows = tabEEs.map(ee => ({ ee, es: td?.ees[ee.id] ?? emptyEE() }));
          const nums = eeRows.map(r=>r.es.cotation).filter(v=>["1","2","3","4"].includes(v)).map(Number);
          return { tab, eeRows, avg: nums.length ? nums.reduce((a,b)=>a+b)/nums.length : null };
        }).filter(Boolean) as { tab: string; eeRows: {ee:EE; es:EESaisie}[]; avg: number|null }[];

      // All thématiques in order
      const allThCodes = ref.T.map((t: Them) => t.c);

      return (
        <div className="fixed inset-0 z-50 bg-black/70 flex items-start justify-center overflow-auto py-4 px-2">
          <div className="bg-white rounded-2xl shadow-2xl w-full max-w-5xl">
            {/* Modal header */}
            <div className="flex items-center justify-between px-6 py-4 border-b bg-amber-50 rounded-t-2xl sticky top-0 z-10">
              <div>
                <h2 className="text-lg font-bold text-amber-800">📊 Restitution de visite</h2>
                <p className="text-xs text-amber-600">{sess.essms || "ESSMS"} · {sess.date || ""} · {allThCodes.length} thématiques</p>
              </div>
              <button onClick={() => setShowRestitution(false)}
                className="text-sm border border-amber-300 rounded px-3 py-1 hover:bg-amber-100 text-amber-700 font-semibold">
                ✕ Fermer
              </button>
            </div>

            {/* Body — 1 carte par thématique */}
            <div className="px-6 py-5 space-y-6 overflow-auto" style={{ maxHeight: "82vh" }}>
              {allThCodes.map(thCode => {
                const th = thMap[thCode];
                const crRefs = [...(thGroups[thCode] ?? [])].sort(sortCritRef);

                // Aggregate pts_forts / axes across all critères of this thématique (including non-CI)
                const allCrOfTh = ref.C.filter(c => c.t === thCode).map(c => c.r);
                const aggPF  = agg(allCrOfTh, "pts_forts");
                const aggAP  = agg(allCrOfTh, "axes_prog");

                // Alert if any CI coté 1/NC/RI
                const isAlert = crRefs.some(cr => {
                  const crEEsImp = impEEs.filter(e => e.cr === cr);
                  return getCrTabResults(cr, crEEsImp).some(r =>
                    r.eeRows.some(({es}) => ["1","NC","RI"].includes(es.cotation))
                  );
                });

                return (
                  <div key={thCode} className="rounded-2xl border-2 overflow-hidden shadow-sm"
                    style={{ borderColor: th ? `#${th.bg}` : "#FCD34D" }}>
                    {/* Thématique header */}
                    <div className="px-5 py-3 flex items-center gap-3"
                      style={{ backgroundColor: th ? `#${th.bg}40` : "#FEF3C7" }}>
                      <span className="text-xl font-extrabold w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0"
                        style={{ backgroundColor: th ? `#${th.bg}` : "#FCD34D", color: th ? `#${th.fg}` : "#92400E" }}>
                        {thCode}
                      </span>
                      <div className="flex-1">
                        <p className="font-bold text-base" style={{ color: th ? `#${th.fg}` : "#92400E" }}>
                          {th?.l ?? `Thématique ${thCode}`}
                        </p>
                        <p className="text-xs text-muted-foreground">
                          {crRefs.length > 0 ? `${crRefs.length} critère(s) impératif(s)` : "Pas de critère impératif"}
                        </p>
                      </div>
                      {isAlert && (
                        <span className="text-sm font-bold text-pink-600 bg-pink-100 border border-red-300 rounded-full px-3 py-1">
                          ⚠ CI non satisfait
                        </span>
                      )}
                    </div>

                    <div className="p-4 space-y-4">
                      {/* ── Scores : thématique → objectifs → critères ── */}
                      {(() => {
                        // Compute critère averages across all tabs
                        const critAvg = (cr: string): number|null => {
                          const crEEs = ref.E.filter(e => e.cr === cr);
                          const nums = tabs.flatMap(tab => {
                            const tt = tabType(tab); const gc = GRILLE_G[tt] ?? "";
                            return crEEs
                              .filter(e => tt==="CVS" ? ["CVS","CVS/CI"].includes(e.cvs) : e.g===gc&&!["CVS","CVS/CI"].includes(e.cvs))
                              .map(e => sess.grilles[tab]?.ees?.[e.id]?.cotation ?? "")
                              .filter(v => ["1","2","3","4"].includes(v)).map(Number);
                          });
                          return nums.length ? nums.reduce((a,b)=>a+b)/nums.length : null;
                        };

                        const thCrits = ref.C.filter(c => c.t === thCode);
                        const thObjs  = [...new Set(thCrits.map(c => c.o))];

                        // Thématique avg
                        const allNums = thCrits.map(c => critAvg(c.r)).filter(v => v !== null) as number[];
                        const thAvg   = allNums.length ? allNums.reduce((a,b)=>a+b)/allNums.length : null;

                        const scoreColor = (v: number|null) =>
                          v === null ? "#9ca3af" : v>=3.5?"#059669":v>=2.5?"#65a30d":v>=1.5?"#d97706":"#C93F8B";
                        const scoreBg = (v: number|null) =>
                          v === null ? "#f3f4f6" : v>=3.5?"#d1fae5":v>=2.5?"#ecfccb":v>=1.5?"#fef3c7":"#fce7f3";

                        // ScoreBar component inline
                        const ScoreBar = ({ avg, imp, alert }: { avg: number|null; imp?: boolean; alert?: boolean }) => {
                          const pct = avg !== null ? (avg/4)*100 : 0;
                          const barColor = avg === null ? "#e5e7eb"
                            : avg>=3.5 ? "#059669" : avg>=2.5 ? "#84cc16"
                            : avg>=1.5 ? "#f59e0b" : "#C93F8B";
                          return (
                            <div className="flex items-center gap-2 flex-shrink-0" style={{ width: 120 }}>
                              <div className="flex-1 h-3 rounded-full bg-slate-100 overflow-hidden border border-slate-200">
                                <div className="h-full rounded-full transition-all"
                                  style={{ width: `${pct}%`, backgroundColor: barColor }}/>
                              </div>
                              <span className="text-xs font-extrabold w-8 text-right"
                                style={{ color: barColor }}>
                                {avg !== null ? avg.toFixed(1) : "—"}
                              </span>
                            </div>
                          );
                        };

                        // Sort objectifs by avg asc (lowest first)
                        const objsSorted = [...thObjs].sort((a, b) => {
                          const aC = thCrits.filter(c=>c.o===a).map(c=>critAvg(c.r)).filter(v=>v!==null) as number[];
                          const bC = thCrits.filter(c=>c.o===b).map(c=>critAvg(c.r)).filter(v=>v!==null) as number[];
                          const aA = aC.length ? aC.reduce((x,y)=>x+y)/aC.length : 99;
                          const bA = bC.length ? bC.reduce((x,y)=>x+y)/bC.length : 99;
                          return aA - bA;
                        });

                        return (
                          <div className="rounded-xl border border-slate-200 bg-white overflow-hidden">
                            {/* Thématique total */}
                            <div className="flex items-center gap-3 px-4 py-3 border-b border-slate-200 bg-slate-50">
                              <span className="text-xs font-bold text-slate-700 flex-1">
                                Thématique {thCode} — moyenne générale
                              </span>
                              <ScoreBar avg={thAvg}/>
                            </div>

                            {/* Per objectif — trié du plus faible au plus fort */}
                            {objsSorted.map((objCode, objIdx) => {
                              const obj = ref.O?.find((o: {r:string;l:string}) => o.r === objCode);
                              const objCrits = thCrits.filter(c => c.o === objCode);
                              const objNums  = objCrits.map(c => critAvg(c.r)).filter(v=>v!==null) as number[];
                              const objAvg   = objNums.length ? objNums.reduce((a,b)=>a+b)/objNums.length : null;

                              // Sort critères ascending by avg
                              const critsSorted = [...objCrits].sort((a, b) => {
                                const aA = critAvg(a.r) ?? 99;
                                const bA = critAvg(b.r) ?? 99;
                                return aA - bA;
                              });

                              return (
                                <div key={objCode} className="border-b border-slate-100 last:border-0">
                                  {/* Objectif row */}
                                  <div className="flex items-center gap-2 px-4 py-2 bg-slate-50/60">
                                    <span className="text-xs text-slate-400 font-bold w-4 flex-shrink-0">{objIdx+1}</span>
                                    <span className="text-xs font-semibold text-slate-500 font-mono flex-shrink-0">{objCode}</span>
                                    <span className="text-xs text-slate-700 font-semibold flex-1 truncate">{obj?.l ?? ""}</span>
                                    <ScoreBar avg={objAvg}/>
                                  </div>
                                  {/* Critères — triés du plus faible au plus fort */}
                                  <div className="px-3 pb-1.5 space-y-1 pt-1">
                                    {critsSorted.map((crit, critIdx) => {
                                      const avg = critAvg(crit.r);
                                      const isImpCrit  = impCrits.has(crit.r);
                                      const hasAlertCrit = impAlerts.has(crit.r);
                                      return (
                                        <div key={crit.r} className="flex items-center gap-2 rounded-lg px-3 py-1.5"
                                          style={{ backgroundColor: hasAlertCrit ? "#fff5f5" : avg !== null && avg < 2 ? "#fffbeb" : "#f9fafb" }}>
                                          <span className="text-xs text-slate-300 w-4 font-bold flex-shrink-0">{critIdx+1}</span>
                                          <span className={`text-xs font-mono font-bold flex-shrink-0 ${hasAlertCrit?"text-pink-600":isImpCrit?"text-pink-400":"text-teal-500"}`}>
                                            {crit.r}
                                          </span>
                                          {isImpCrit && !hasAlertCrit && <span className="text-xs text-red-300 flex-shrink-0 font-bold">CI</span>}
                                          {hasAlertCrit && <span className="text-xs text-pink-600 font-bold flex-shrink-0">⚠ CI</span>}
                                          <span className="text-xs text-slate-600 flex-1 leading-snug">{crit.l}</span>
                                          <ScoreBar avg={avg} imp={isImpCrit} alert={hasAlertCrit}/>
                                        </div>
                                      );
                                    })}
                                  </div>
                                </div>
                              );
                            })}
                          </div>
                        );
                      })()}

                      {/* Axes forts / Axes de progrès — vue thématique */}
                      <div className="grid gap-3" style={{ gridTemplateColumns: "1fr 1fr" }}>
                        <div className="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3">
                          <p className="text-sm font-bold text-emerald-700 mb-2">✅ Axe fort</p>
                          {aggPF
                            ? <p className="text-xs text-slate-700 leading-relaxed whitespace-pre-line">{aggPF}</p>
                            : <p className="text-xs text-slate-400 italic">Non encore saisi</p>}
                        </div>
                        <div className="rounded-xl border border-orange-200 bg-orange-50 px-4 py-3">
                          <p className="text-sm font-bold text-orange-700 mb-2">🔧 Axe de progrès</p>
                          {aggAP
                            ? <p className="text-xs text-slate-700 leading-relaxed whitespace-pre-line">{aggAP}</p>
                            : <p className="text-xs text-slate-400 italic">Non encore saisi</p>}
                        </div>
                      </div>

                      {/* Focale CI — uniquement si la thématique a des CI */}
                      {crRefs.length > 0 && <div className="rounded-xl border border-red-100 bg-pink-50/40 px-4 py-3">
                        <p className="text-sm font-bold text-red-700 mb-3">🎯 Focale Critères Impératifs</p>
                        <div className="space-y-3">
                          {crRefs.map(cr => {
                            const crit = ref.C.find(c => c.r === cr); if (!crit) return null;
                            const crEEsImp = impEEs.filter(e => e.cr === cr);
                            const tabResults = getCrTabResults(cr, crEEsImp);
                            const isCI_alert = tabResults.some(r =>
                              r.eeRows.some(({es}) => ["1","NC","RI"].includes(es.cotation))
                            );
                            return (
                              <div key={cr} className={`rounded-lg border overflow-hidden ${isCI_alert ? "border-red-400" : "border-slate-200"}`}
                                style={{ backgroundColor: isCI_alert ? "#fff5f5" : "#ffffff" }}>
                                {/* CI header */}
                                <div className="px-3 py-2 flex items-center gap-2 border-b border-slate-100">
                                  <span className="font-mono text-xs font-bold text-teal-600">{cr}</span>
                                  <span className="text-xs text-slate-700 flex-1">{crit.l}</span>
                                  {isCI_alert && <span className="text-xs text-pink-600 font-bold">⚠</span>}
                                </div>
                                {/* Per-tab EE details */}
                                {tabResults.map(({ tab, eeRows, avg }) => (
                                  <div key={tab} className="px-3 py-2 border-b border-slate-50 last:border-0">
                                    <div className="flex items-center gap-2 mb-1.5">
                                      <span className="text-xs font-bold text-teal-500 bg-teal-50 rounded px-1.5 py-0.5">{tab}</span>
                                      {avg !== null && (
                                        <span className="text-xs font-extrabold px-1.5 py-0.5 rounded"
                                          style={{ backgroundColor: cotBg(avg>=4?"4":avg>=3?"3":avg>=2?"2":"1"), color: cotColor(avg>=4?"4":avg>=3?"3":avg>=2?"2":"1") }}>
                                          {avg.toFixed(2)}
                                        </span>
                                      )}
                                    </div>
                                    <div className="space-y-1">
                                      {eeRows.map(({ ee, es }) => (
                                        <div key={ee.id} className="flex items-start gap-2 text-xs rounded px-2 py-1"
                                          style={{ backgroundColor: es.cotation ? cotBg(es.cotation) : "#f9fafb" }}>
                                          <span className="font-extrabold w-6 flex-shrink-0 text-center"
                                            style={{ color: cotColor(es.cotation) }}>
                                            {es.cotation || "—"}
                                          </span>
                                          <span className="text-slate-600 leading-snug flex-1">{ee.tx}</span>
                                          {es.constats && (
                                            <span className="text-slate-500 italic leading-snug text-right" style={{ maxWidth: 220 }}>
                                              {es.constats}
                                            </span>
                                          )}
                                        </div>
                                      ))}
                                    </div>
                                  </div>
                                ))}
                              </div>
                            );
                          })}
                        </div>
                      </div>}
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        </div>
      );
    })()}
    </>
  );
}



// Mount app
const container = document.getElementById('root');
if (container) {
  const root = ReactDOM.createRoot(container);
  root.render(React.createElement(ElEaGrilleTerrain));
}
