/* global React, Icon, Modal, window */
// ============================================================================
// MELR · PEFA — évaluation de la gestion des finances publiques (phase 1)
// ----------------------------------------------------------------------------
// Cycle officiel PEFA (Manuel Vol. I & IV) : Planification (note
// conceptuelle) → Travail de terrain (notation) → Rapport (PFM-PR, PEFA
// Check) → Dialogue sur les réformes (plan d'action).
//
// Phase 1 (ce fichier) :
//   - Vue d'ensemble : liste des évaluations PEFA + création
//   - Note conceptuelle : les 9 sections du modèle officiel (CN Template),
//     workflow de statut (brouillon → revue interne → revue Secrétariat →
//     approuvée), export Word
//   - Notation / Preuves / Rapport / Plan d'action : placeholders phase 2-3.
//     Décision actée : la notation n'est PAS bloquée si la CN n'est pas
//     approuvée — simple avertissement.
//
// Design : PEFA/DESIGN-MODULE-EVALUATIONS.md.
// ============================================================================

(function () {
  const { useState, useEffect, useMemo } = React;

  // ── Onglets (les ancres "tab:<clé>" arrivent de la sidebar) ─────────────
  // Teinte OKLCH distincte par onglet — même système coloré que les onglets
  // de « Projets » / Ex-ante (.pd-tabs / .pd-tab + --tab-hue).
  const TABS = [
    { key: "tab:overview", fr: "Vue d'ensemble",    en: "Overview", es: "Visión general",                hue: 230 },
    { key: "tab:cn",       fr: "Note conceptuelle", en: "Concept note", es: "Nota conceptual",            hue: 50 },
    { key: "tab:notation", fr: "Notation",          en: "Scoring", es: "Puntuación",                 hue: 150 },
    { key: "tab:preuves",  fr: "Preuves & calculs", en: "Evidence & calculations", es: "Pruebas y cálculos", hue: 25 },
    { key: "tab:rapport",  fr: "Rapport & qualité", en: "Report & quality", es: "Informe y calidad",        hue: 285 },
    { key: "tab:plan",     fr: "Plan d'action",     en: "Action plan", es: "Plan de acción",             hue: 350 },
  ];

  // ── Les 9 sections du modèle officiel de note conceptuelle ──────────────
  // (CN Template National Gov, versions FR/EN — pefa.org/resources)
  const CN_SECTIONS = [
    { key: "context",       fr: "1. Contexte et justification",        en: "1. Background and rationale", es: "1. Contexto y justificación",
      hint_fr: "Pays, réformes GFP en cours, évaluations PEFA antérieures et suites données.",
      hint_en: "Country, ongoing PFM reforms, previous PEFA assessments and follow-up." },
    { key: "purpose",       fr: "2. Objet et objectifs",               en: "2. Purpose and objectives", es: "2. Objeto y objetivos",
      hint_fr: "Pourquoi cette évaluation, pour quels utilisateurs, quelles décisions éclairera-t-elle ?",
      hint_en: "Why this assessment, for which users, which decisions will it inform?" },
    { key: "scope",         fr: "3. Champ et couverture",              en: "3. Scope and coverage", es: "3. Ámbito y cobertura",
      hint_fr: "Administration centrale / infranationale, unités extrabudgétaires, entreprises publiques, exercices couverts.",
      hint_en: "Central / subnational government, extrabudgetary units, SOEs, fiscal years covered." },
    { key: "indicators",    fr: "4. Ensemble d'indicateurs",           en: "4. Indicator set", es: "4. Conjunto de indicadores",
      hint_fr: "PEFA 2016 (31 PI) ± modules Genre (GRPFM) et Climat (CRPFM) — voir les cases ci-dessus.",
      hint_en: "PEFA 2016 (31 PIs) ± Gender (GRPFM) and Climate (CRPFM) modules — see checkboxes above." },
    { key: "management",    fr: "5. Gestion et supervision",           en: "5. Management and oversight", es: "5. Gestión y supervisión",
      hint_fr: "Équipe d'évaluation, comité de pilotage, responsabilités, gouvernement / partenaires.",
      hint_en: "Assessment team, oversight team, responsibilities, government / partners." },
    { key: "calendar",      fr: "6. Calendrier",                       en: "6. Timeline", es: "6. Calendario",
      hint_fr: "Jalons par phase : préparation, terrain, projets de rapport, revues, rapport final.",
      hint_en: "Milestones per phase: preparation, fieldwork, draft reports, reviews, final report." },
    { key: "budget",        fr: "7. Budget et financement",            en: "7. Budget and funding", es: "7. Presupuesto y financiación",
      hint_fr: "Coûts estimés et sources de financement.",
      hint_en: "Estimated costs and funding sources." },
    { key: "quality",       fr: "8. Assurance qualité (PEFA Check)",   en: "8. Quality assurance (PEFA Check)", es: "8. Aseguramiento de la calidad (PEFA Check)",
      hint_fr: "Réviseurs désignés (gouvernement, Secrétariat PEFA, partenaires), engagement PEFA Check.",
      hint_en: "Designated reviewers (government, PEFA Secretariat, partners), PEFA Check commitment." },
    { key: "dissemination", fr: "9. Diffusion et utilisation",         en: "9. Dissemination and use", es: "9. Difusión y utilización",
      hint_fr: "Publication du rapport, ateliers de restitution, lien avec le dialogue sur les réformes.",
      hint_en: "Report publication, dissemination workshops, link to the PFM reform dialogue." },
  ];

  // Workflow CN — ordre des statuts (décision actée : non bloquant)
  const CN_STATUSES = ["draft", "internal_review", "secretariat_review", "approved"];
  const CN_STATUS_META = {
    draft:              { fr: "Brouillon",              en: "Draft", es: "Borrador",               color: "#92400e", fill: "#fef3c7" },
    internal_review:    { fr: "Revue interne",          en: "Internal review", es: "Revisión interna",     color: "#1d4ed8", fill: "#dbeafe" },
    secretariat_review: { fr: "Revue Secrétariat PEFA", en: "PEFA Secretariat review", es: "Revisión del Secretariado PEFA", color: "#6b21a8", fill: "#ede9fe" },
    approved:           { fr: "Approuvée",              en: "Approved", es: "Aprobada",            color: "#15803d", fill: "#dcfce7" },
  };

  function CnStatusBadge({ status, lang }) {
    const m = CN_STATUS_META[status] || CN_STATUS_META.draft;
    return (
      <span style={{
        display: "inline-block", padding: "3px 10px", borderRadius: 10,
        background: m.fill, color: m.color, fontSize: 11.5, fontWeight: 700,
      }}>{(lang === "es" ? (m.es != null ? m.es : m.en) : lang === "fr" ? m.fr : m.en)}</span>
    );
  }

  function ModuleChips({ modules, lang }) {
    const m = modules || {};
    return (
      <span style={{ display: "inline-flex", gap: 4 }}>
        {m.gender && <span style={{ fontSize: 10.5, fontWeight: 700, background: "#fce7f3", color: "#9d174d", padding: "1px 7px", borderRadius: 999 }}>
          {window.L("+ Genre", "+ Gender", "+ Género")}</span>}
        {m.climate && <span style={{ fontSize: 10.5, fontWeight: 700, background: "#dcfce7", color: "#166534", padding: "1px 7px", borderRadius: 999 }}>
          {window.L("+ Climat", "+ Climate", "+ Clima")}</span>}
      </span>
    );
  }

  // ── Export Word de la note conceptuelle ─────────────────────────────────
  // Structure du modèle officiel : titre, métadonnées, une rubrique par
  // section. window.docx est déjà chargé pour les autres exports MELR.
  function exportConceptNoteWord(lang, assessment, note) {
    const D = window.docx;
    if (!D) { window.alert(window.L("Bibliothèque Word indisponible.", "Word library unavailable.", "Biblioteca Word no disponible.")); return; }
    const ev = assessment.evaluation || {};
    const title = lang === "fr" ? (ev.title_fr || "Évaluation PEFA") : (ev.title_en || ev.title_fr || "PEFA assessment");
    const sections = (note && note.sections) || {};
    const meta = CN_STATUS_META[(note && note.status) || "draft"];
    const P = (text, opts) => new D.Paragraph({
      children: [new D.TextRun({ text, size: (opts && opts.size) || 22, bold: !!(opts && opts.bold), color: (opts && opts.color) || "111827", font: "Arial" })],
      spacing: { after: (opts && opts.after) != null ? opts.after : 120 },
    });
    const children = [
      P(window.L("NOTE CONCEPTUELLE — ÉVALUATION PEFA", "CONCEPT NOTE — PEFA ASSESSMENT", "NOTA CONCEPTUAL — EVALUACIÓN PEFA"), { size: 30, bold: true, color: "0F2740", after: 60 }),
      P(title, { size: 26, bold: true, color: "1D4ED8", after: 60 }),
      P((window.L("Cadre PEFA 2016 · Niveau : ", "PEFA 2016 framework · Level: ", "Marco PEFA 2016 · Nivel: "))
        + (assessment.government_level === "subnational" ? (window.L("infranational", "subnational", "subnacional")) : "national")
        + ((assessment.modules || {}).gender  ? (window.L(" · + module Genre (GRPFM)", " · + Gender module (GRPFM)", " · + módulo Género (GRPFM)")) : "")
        + ((assessment.modules || {}).climate ? (window.L(" · + module Climat (CRPFM)", " · + Climate module (CRPFM)", " · + módulo Clima (CRPFM)")) : ""),
        { size: 18, color: "64748B", after: 60 }),
      P((window.L("Statut : ", "Status: ", "Estado: ")) + ((lang === "es" ? (meta.es != null ? meta.es : meta.en) : lang === "fr" ? meta.fr : meta.en))
        + (note && note.version ? " · v" + note.version : "")
        + (note && note.approved_at ? (window.L(" · approuvée le ", " · approved on ", " · aprobada el ")) + String(note.approved_at).slice(0, 10) : ""),
        { size: 18, color: "64748B", after: 240 }),
    ];
    CN_SECTIONS.forEach((s) => {
      children.push(P((lang === "es" ? (s.es != null ? s.es : s.en) : lang === "fr" ? s.fr : s.en), { size: 24, bold: true, color: "0F2740", after: 80 }));
      const body = (sections[s.key] || "").trim() || (window.L("(à compléter)", "(to be completed)", "(por completar)"));
      body.split(/\n+/).forEach((line) => children.push(P(line, { size: 21, after: 80 })));
      children.push(P("", { after: 120 }));
    });
    const doc = new D.Document({ sections: [{ children }] });
    const fname = "PEFA_CN_" + (ev.title_fr || "evaluation").replace(/[^\wÀ-ſ-]+/g, "_") + ".docx";
    D.Packer.toBlob(doc).then((blob) => {
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url; a.download = fname; a.click();
      setTimeout(() => URL.revokeObjectURL(url), 4000);
    });
  }

  // ── Bandeau d'avertissement CN non approuvée (non bloquant) ─────────────
  function CnWarningBanner({ lang, note }) {
    if (note && note.status === "approved") return null;
    return (
      <div style={{
        background: "#fffbeb", border: "1px solid #f59e0b", borderRadius: 8,
        padding: "10px 14px", marginBottom: 14, fontSize: 12.5, color: "#92400e",
        display: "flex", alignItems: "center", gap: 8,
      }}>
        <Icon.alert />
        {window.L("La note conceptuelle n'est pas encore approuvée. La procédure officielle PEFA recommande de la faire approuver (y compris par le Secrétariat PEFA) avant de commencer la notation.", "The concept note is not approved yet. The official PEFA process recommends getting it approved (including by the PEFA Secretariat) before starting the scoring.", "La nota conceptual aún no está aprobada. El procedimiento oficial PEFA recomienda hacerla aprobar (incluso por el Secretariado PEFA) antes de comenzar la calificación.")}
      </div>
    );
  }

  // ── Notation (phase 2) ───────────────────────────────────────────────────
  // Grille construite depuis window.PEFA_CATALOG (structure officielle
  // statique, bilingue) ; les notes vivent dans pefa_scores. L'agrégat
  // M1/M2 est recalculé en direct (window.pefaAggregate, table officielle)
  // et peut être FORCÉ manuellement (ligne dimension_code NULL).
  const ROMAN = ["I", "II", "III", "IV", "V", "VI", "VII"];
  const DIM_SCORES = ["", "A", "B", "C", "D", "D*", "NA"];
  const OVERRIDE_SCORES = ["A", "B+", "B", "C+", "C", "D+", "D", "NR"];

  // Conventions visuelles partagées du groupe ÉVALUATIONS (définies dans
  // screens-evaluations.jsx → window.MelrSection) : sections teintées
  // amber/blue/green/purple + boutons d'export pleins Word/PDF/Excel.
  const sec = (c, extra) => (window.MelrSection ? window.MelrSection.style(c, extra) : { marginBottom: 14, ...(extra || {}) });
  const secTitle = (c) => (window.MelrSection ? window.MelrSection.title(c) : { fontSize: 13.5, fontWeight: 800, margin: "0 0 8px" });
  const xbtn = (bg) => (window.MelrSection ? window.MelrSection.btn(bg) : {});

  function scorePillStyle(score) {
    const l = (score || "").charAt(0);
    if (l === "A") return { background: "#dcfce7", color: "#15803d" };
    if (l === "B") return { background: "#dbeafe", color: "#1d4ed8" };
    if (l === "C") return { background: "#fef3c7", color: "#b45309" };
    if (l === "D") return { background: "#fee2e2", color: "#b91c1c" };
    return { background: "#f3f4f6", color: "#6b7280" };
  }
  function ScorePill({ score, label }) {
    if (!score) return <span style={{ fontSize: 11, color: "var(--text-faint)" }}>—</span>;
    return (
      <span style={{
        display: "inline-block", minWidth: 26, textAlign: "center",
        padding: "2px 8px", borderRadius: 8, fontSize: 12, fontWeight: 800,
        ...scorePillStyle(score),
      }} title={label || undefined}>{score}</span>
    );
  }

  // Ligne de dimension : note (select) + justification (sauvegardée au blur).
  function PefaDimRow({ lang, dim, row, canManage, onScore, onJustify }) {
    const justKey = window.L("justification_fr", "justification_en", "justification_en");
    return (
      <div style={{ display: "grid", gridTemplateColumns: "minmax(220px, 1.6fr) 92px minmax(180px, 2fr)", gap: 10, alignItems: "start", padding: "7px 0", borderTop: "1px dashed var(--line)" }}>
        <div style={{ fontSize: 12 }}>
          <span style={{ fontWeight: 700, marginRight: 6 }}>{dim.code}</span>
          <span style={{ color: "var(--text-muted)" }}>{(lang === "es" ? (dim.es != null ? dim.es : dim.en) : lang === "fr" ? dim.fr : dim.en)}</span>
        </div>
        <select className="input" disabled={!canManage}
          value={(row && row.score) || ""}
          onChange={(e) => onScore(dim, e.target.value)}
          style={{ fontWeight: 700, ...(row && row.score ? scorePillStyle(row.score) : {}) }}>
          {DIM_SCORES.map((s) => <option key={s || "none"} value={s}>{s || (window.L("— non notée —", "— not scored —", "— no calificada —"))}</option>)}
        </select>
        <textarea className="input" rows={1} disabled={!canManage}
          placeholder={window.L("Justification / éléments probants…", "Justification / evidence…", "Justificación / elementos probatorios…")}
          style={{ width: "100%", resize: "vertical", fontSize: 11.5 }}
          defaultValue={(row && row[justKey]) || ""}
          onBlur={(e) => {
            const v = e.target.value;
            if (v !== ((row && row[justKey]) || "")) onJustify(dim, v);
          }} />
      </div>
    );
  }

  function PefaIndicatorCard({ lang, ind, byKey, canManage, onScore, onJustify, onOverride }) {
    const L = (fr, en) => (lang === "fr" ? fr : en);
    const dimRows = ind.dims.map((dd) => byKey[ind.code + "|" + dd.code] || null);
    const scoredCount = dimRows.filter((r) => r && r.score).length;
    const overrideRow = byKey[ind.code + "|"];
    const auto = window.pefaAggregate
      ? window.pefaAggregate(ind.method || "M1", dimRows.map((r) => (r && r.score) || null))
      : null;
    const finalScore = overrideRow && overrideRow.score ? overrideRow.score : auto;
    const [open, setOpen] = useState(false);
    return (
      <div style={{ border: "1px solid var(--line)", borderRadius: 8, marginBottom: 8, background: "var(--bg-elev)" }}>
        <div onClick={() => setOpen(!open)}
          style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 12px", cursor: "pointer" }}>
          <span style={{ fontWeight: 800, fontSize: 12.5, minWidth: 64 }}>{ind.code}</span>
          <span style={{ flex: 1, fontSize: 12.5 }}>{(lang === "es" ? (ind.es != null ? ind.es : ind.en) : lang === "fr" ? ind.fr : ind.en)}</span>
          {ind.method && (
            <span style={{ fontSize: 10, fontWeight: 700, color: "#6b21a8", background: "#ede9fe", padding: "1px 7px", borderRadius: 999 }}
              title={ind.method === "M1" ? L("Maillon le plus faible", "Weakest link") : L("Table de conversion (moyenne)", "Conversion table (averaging)")}>
              {ind.method}
            </span>
          )}
          <span className="text-faint" style={{ fontSize: 10.5 }}>{scoredCount}/{ind.dims.length}</span>
          <ScorePill score={finalScore}
            label={overrideRow && overrideRow.score
              ? L("Note forcée manuellement", "Manually overridden score")
              : L("Agrégat automatique " + (ind.method || ""), "Automatic aggregate " + (ind.method || ""))} />
          {overrideRow && overrideRow.score && (
            <span style={{ fontSize: 9.5, fontWeight: 700, color: "#b45309" }} title={L("Note forcée", "Overridden")}>✎</span>
          )}
        </div>
        {open && (
          <div style={{ padding: "2px 12px 10px" }}>
            {ind.dims.map((dd) => (
              <PefaDimRow key={dd.code} lang={lang} dim={dd}
                row={byKey[ind.code + "|" + dd.code]}
                canManage={canManage} onScore={onScore} onJustify={onJustify} />
            ))}
            {ind.dims.length > 1 && canManage && (
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 8, fontSize: 11.5 }}>
                <span className="text-faint">
                  {L("Note de l'indicateur :", "Indicator score:")}{" "}
                  {auto ? L("proposée ", "suggested ") + auto + " (" + (ind.method || "M1") + ")" : L("incomplète", "incomplete")}
                </span>
                <select className="input" style={{ fontSize: 11.5 }}
                  value={(overrideRow && overrideRow.score) || ""}
                  onChange={(e) => onOverride(ind, e.target.value)}>
                  <option value="">{L("— automatique —", "— automatic —")}</option>
                  {OVERRIDE_SCORES.map((s) => <option key={s} value={s}>{L("Forcer ", "Force ") + s}</option>)}
                </select>
              </div>
            )}
          </div>
        )}
      </div>
    );
  }

  function PefaScoringTab({ lang, assessment, canManage }) {
    const L = (fr, en) => (lang === "fr" ? fr : en);
    const { data: scores, loading } = window.melr.usePefaScores
      ? window.melr.usePefaScores(assessment.id)
      : { data: [], loading: false };
    const [err, setErr] = useState(null);

    const byKey = useMemo(() => {
      const m = {};
      (scores || []).forEach((r) => { m[r.indicator_code + "|" + (r.dimension_code || "")] = r; });
      return m;
    }, [scores]);

    const cat = window.PEFA_CATALOG;
    const mods = assessment.modules || {};
    const groups = useMemo(() => {
      if (!cat) return [];
      const g = cat.pillars.map((p) => ({
        key: "p" + p.num,
        title: L("Pilier ", "Pillar ") + ROMAN[p.num - 1] + " · " + ((lang === "es" ? (p.es != null ? p.es : p.en) : lang === "fr" ? p.fr : p.en)),
        inds: cat.indicators.filter((i) => i.pillar === p.num),
      }));
      if (mods.gender)  g.push({ key: "gender",  title: L("Module Genre — GRPFM 2020", "Gender module — GRPFM 2020"), inds: cat.gender });
      if (mods.climate) g.push({ key: "climate", title: L("Module Climat — CRPFM", "Climate module — CRPFM"), inds: cat.climate });
      return g;
    }, [cat, lang, mods.gender, mods.climate]);

    const allDims = useMemo(() => groups.reduce((n, g) => n + g.inds.reduce((m, i) => m + i.dims.length, 0), 0), [groups]);
    const scoredDims = useMemo(() => {
      let n = 0;
      groups.forEach((g) => g.inds.forEach((i) => i.dims.forEach((dd) => {
        const r = byKey[i.code + "|" + dd.code];
        if (r && r.score) n++;
      })));
      return n;
    }, [groups, byKey]);

    const [openGroups, setOpenGroups] = useState({ p1: true });

    const mkScore = (ind) => async (dim, value) => {
      try { await window.melr.upsertPefaScore(assessment.id, ind.code, dim.code, { score: value || null }); setErr(null); }
      catch (e) { setErr(e.message); }
    };
    const mkJustify = (ind) => async (dim, text) => {
      const field = window.L("justification_fr", "justification_en", "justification_en");
      try { await window.melr.upsertPefaScore(assessment.id, ind.code, dim.code, { [field]: text }); setErr(null); }
      catch (e) { setErr(e.message); }
    };
    const onOverride = async (ind, value) => {
      try {
        if (!value) await window.melr.clearPefaOverride(assessment.id, ind.code);
        else await window.melr.upsertPefaScore(assessment.id, ind.code, null, { score: value, score_override: true });
        setErr(null);
      } catch (e) { setErr(e.message); }
    };

    if (!cat) {
      return <div className="card"><div className="card-body" style={{ padding: 20, fontSize: 12.5, color: "#b91c1c" }}>
        {L("pefa-catalog.jsx n'a pas pu se charger (window.PEFA_CATALOG absent). Vérifier la console (F12).",
           "pefa-catalog.jsx failed to load (window.PEFA_CATALOG missing). Check the console (F12).")}
      </div></div>;
    }

    return (
      <div>
        {/* Progression (blue = KPIs) */}
        <div style={sec("blue")}>
          <div>
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <div style={{ flex: 1, height: 8, background: "var(--bg-sunken)", borderRadius: 4 }}>
                <div style={{ width: (allDims ? Math.round(100 * scoredDims / allDims) : 0) + "%", height: 8, background: "#15803d", borderRadius: 4, transition: "width .3s" }} />
              </div>
              <span style={{ fontSize: 12.5, fontWeight: 700 }}>
                {scoredDims} / {allDims} {L("dimensions notées", "dimensions scored")}
              </span>
            </div>
            <div className="text-faint" style={{ fontSize: 11, marginTop: 6 }}>
              {L("Notes de dimension : A, B, C, D (D* = note D faute d'informations suffisantes, NA = non applicable). La note d'indicateur est agrégée automatiquement — M1 : maillon le plus faible ; M2 : table de conversion officielle — et peut être forcée.",
                 "Dimension scores: A, B, C, D (D* = D for lack of sufficient information, NA = not applicable). The indicator score is aggregated automatically — M1: weakest link; M2: official conversion table — and can be overridden.")}
            </div>
            {loading && <div className="text-faint" style={{ fontSize: 11, marginTop: 4 }}>{L("Chargement des notes…", "Loading scores…")}</div>}
            {err && <div style={{ fontSize: 11.5, color: "#b91c1c", marginTop: 4 }}>{err}</div>}
          </div>
        </div>

        {/* Groupes : 7 piliers + modules activés */}
        {groups.map((g) => {
          const total = g.inds.reduce((m, i) => m + i.dims.length, 0);
          const done = g.inds.reduce((m, i) => m + i.dims.filter((dd) => {
            const r = byKey[i.code + "|" + dd.code]; return r && r.score;
          }).length, 0);
          const open = !!openGroups[g.key];
          return (
            <div key={g.key} style={{ marginBottom: 10 }}>
              <div onClick={() => setOpenGroups({ ...openGroups, [g.key]: !open })}
                style={{
                  display: "flex", alignItems: "center", gap: 10, cursor: "pointer",
                  padding: "9px 12px", borderRadius: 8,
                  background: "var(--bg-sunken)", border: "1px solid var(--line)",
                  marginBottom: open ? 8 : 0,
                }}>
                <span style={{ fontSize: 12.5, fontWeight: 800, flex: 1 }}>{g.title}</span>
                <span className="text-faint" style={{ fontSize: 11 }}>{done}/{total}</span>
                <Icon.chevronDown style={{ transform: open ? "rotate(0deg)" : "rotate(-90deg)", transition: "transform .15s", opacity: 0.6 }} />
              </div>
              {open && g.inds.map((ind) => (
                <PefaIndicatorCard key={ind.code} lang={lang} ind={ind} byKey={byKey}
                  canManage={canManage}
                  onScore={mkScore(ind)} onJustify={mkJustify(ind)} onOverride={onOverride} />
              ))}
            </div>
          );
        })}
      </div>
    );
  }

  // ── Preuves & calculs (phase 2b) ─────────────────────────────────────────
  // Section 1 : bibliothèque de preuves (bucket pefa-evidence, table
  // pefa_evidence). Section 2 : feuilles de calcul officielles PI-1/PI-2
  // (dépenses + imprévus) et PI-3 (recettes) — calculs window.pefaCalc,
  // données dans pefa_calc_data, report des notes proposées vers la notation.
  const EMPTY_CALC = () => ({
    years: ["", "", ""],
    exp: { rows: [{ name: "" }], contingency: ["", "", ""] },
    eco: { rows: [{ name: "" }] },
    rev: { rows: [{ name: "" }] },
  });

  function CalcGrid({ lang, rows, years, onRows, canManage, nameLabel }) {
    const L = (fr, en) => (lang === "fr" ? fr : en);
    const setCell = (i, k, v) => {
      const next = rows.map((r, j) => (j === i ? { ...r, [k]: v } : r));
      onRows(next);
    };
    const cellStyle = { width: "100%", fontSize: 11.5, padding: "3px 6px", textAlign: "right" };
    return (
      <div style={{ overflowX: "auto" }}>
        <table className="tbl" style={{ minWidth: 680 }}>
          <thead>
            <tr>
              <th style={{ minWidth: 180 }}>{nameLabel}</th>
              {[0, 1, 2].map((y) => (
                <th key={y} colSpan={2} style={{ textAlign: "center" }}>
                  {years[y] || (L("Exercice ", "FY ") + (y + 1))}
                </th>
              ))}
              <th></th>
            </tr>
            <tr>
              <th></th>
              {[0, 1, 2].map((y) => (
                <React.Fragment key={y}>
                  <th style={{ fontSize: 10.5, textAlign: "right" }}>{L("Budget", "Budget")}</th>
                  <th style={{ fontSize: 10.5, textAlign: "right" }}>{L("Réalisé", "Actual")}</th>
                </React.Fragment>
              ))}
              <th></th>
            </tr>
          </thead>
          <tbody>
            {rows.map((r, i) => (
              <tr key={i}>
                <td>
                  <input className="input" value={r.name || ""} disabled={!canManage}
                    onChange={(e) => setCell(i, "name", e.target.value)}
                    style={{ width: "100%", fontSize: 11.5, padding: "3px 6px" }} />
                </td>
                {[1, 2, 3].map((y) => (
                  <React.Fragment key={y}>
                    <td><input type="number" className="input" value={r["b" + y] == null ? "" : r["b" + y]} disabled={!canManage}
                      onChange={(e) => setCell(i, "b" + y, e.target.value === "" ? null : Number(e.target.value))} style={cellStyle} /></td>
                    <td><input type="number" className="input" value={r["a" + y] == null ? "" : r["a" + y]} disabled={!canManage}
                      onChange={(e) => setCell(i, "a" + y, e.target.value === "" ? null : Number(e.target.value))} style={cellStyle} /></td>
                  </React.Fragment>
                ))}
                <td style={{ textAlign: "center" }}>
                  {canManage && (
                    <button className="btn xs ghost" title={L("Supprimer la ligne", "Remove row")}
                      onClick={() => onRows(rows.filter((_, j) => j !== i))}>
                      <Icon.x />
                    </button>
                  )}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
        {canManage && (
          <button className="btn xs ghost" style={{ marginTop: 6 }}
            onClick={() => onRows([...rows, { name: "" }])}>
            <Icon.plus /> {L("Ajouter une ligne", "Add row")}
          </button>
        )}
      </div>
    );
  }

  function CalcResult({ lang, code, label, value, score, canManage, onApply }) {
    const L = (fr, en) => (lang === "fr" ? fr : en);
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "6px 0", borderTop: "1px dashed var(--line)", fontSize: 12 }}>
        <span style={{ fontWeight: 700, minWidth: 64 }}>{code}</span>
        <span style={{ flex: 1, color: "var(--text-muted)" }}>{label}</span>
        <span className="text-faint" style={{ fontSize: 11.5, minWidth: 170, textAlign: "right" }}>{value}</span>
        <ScorePill score={score} />
        {canManage && score && (
          <button className="btn xs ghost" onClick={() => onApply(code, score)}
            title={L("Inscrire cette note dans l'onglet Notation", "Write this score into the Scoring tab")}>
            {L("Reporter →", "Apply →")}
          </button>
        )}
      </div>
    );
  }

  function PefaEvidenceCalcTab({ lang, assessment, canManage, effectiveOrgId }) {
    const L = (fr, en) => (lang === "fr" ? fr : en);
    const cat = window.PEFA_CATALOG;
    const mods = assessment.modules || {};
    const indicatorOptions = useMemo(() => {
      if (!cat) return [];
      let list = cat.indicators.slice();
      if (mods.gender)  list = list.concat(cat.gender);
      if (mods.climate) list = list.concat(cat.climate);
      return list;
    }, [cat, mods.gender, mods.climate]);

    // ── Preuves ──
    const { data: evidence, refresh: refreshEvidence } = window.melr.usePefaEvidence
      ? window.melr.usePefaEvidence(assessment.id)
      : { data: [], refresh: () => {} };
    const [evInd, setEvInd] = useState("");
    const [evTitle, setEvTitle] = useState("");
    const [evSource, setEvSource] = useState("");
    const [evYear, setEvYear] = useState("");
    const [evFile, setEvFile] = useState(null);
    const [evBusy, setEvBusy] = useState(false);
    const [evMsg, setEvMsg] = useState(null);
    const [evFilter, setEvFilter] = useState("");

    const uploadEvidence = async () => {
      setEvBusy(true); setEvMsg(null);
      try {
        await window.melr.uploadPefaEvidence(assessment.id, effectiveOrgId, evFile, {
          indicator_code: evInd, title: evTitle, source: evSource, fiscal_year: evYear,
        });
        setEvTitle(""); setEvSource(""); setEvYear(""); setEvFile(null);
        refreshEvidence && refreshEvidence();
        setEvMsg(L("Preuve ajoutée.", "Evidence added."));
      } catch (e) { setEvMsg(L("Erreur : ", "Error: ") + e.message); }
      finally { setEvBusy(false); }
    };
    const openEvidence = async (row) => {
      const url = await window.melr.getPefaEvidenceUrl(row.file_path);
      if (url) window.open(url, "_blank", "noopener");
    };
    const deleteEvidence = async (row) => {
      if (!window.confirm(L("Supprimer cette preuve ?", "Delete this evidence?"))) return;
      try { await window.melr.removePefaEvidence(row.id); refreshEvidence && refreshEvidence(); }
      catch (e) { setEvMsg(L("Erreur : ", "Error: ") + e.message); }
    };
    const visibleEvidence = (evidence || []).filter((r) => !evFilter || r.indicator_code === evFilter);

    // ── Feuilles de calcul ──
    const [calc, setCalc] = useState(EMPTY_CALC());
    const [calcDirty, setCalcDirty] = useState(false);
    const [calcBusy, setCalcBusy] = useState(false);
    const [calcMsg, setCalcMsg] = useState(null);
    useEffect(() => {
      let cancelled = false;
      setCalc(EMPTY_CALC()); setCalcDirty(false); setCalcMsg(null);
      window.melr.fetchPefaCalcData(assessment.id, "calc")
        .then((row) => {
          if (cancelled || !row || !row.data) return;
          const d = row.data;
          setCalc({
            years: d.years || ["", "", ""],
            exp: { rows: (d.exp && d.exp.rows) || [{ name: "" }], contingency: (d.exp && d.exp.contingency) || ["", "", ""] },
            eco: { rows: (d.eco && d.eco.rows) || [{ name: "" }] },
            rev: { rows: (d.rev && d.rev.rows) || [{ name: "" }] },
          });
        })
        .catch((e) => { if (!cancelled) setCalcMsg(e.message); });
      return () => { cancelled = true; };
    }, [assessment.id]);
    const patchCalc = (patch) => { setCalc({ ...calc, ...patch }); setCalcDirty(true); };
    const saveCalc = async () => {
      setCalcBusy(true); setCalcMsg(null);
      try {
        await window.melr.savePefaCalcData(assessment.id, "calc", calc);
        setCalcDirty(false);
        setCalcMsg(L("Feuilles de calcul enregistrées.", "Calculation sheets saved."));
      } catch (e) { setCalcMsg(L("Erreur : ", "Error: ") + e.message); }
      finally { setCalcBusy(false); }
    };

    const PC = window.pefaCalc;
    const pct = (v) => (v == null ? "—" : v.toFixed(1) + " %");
    const expOut  = PC ? [1, 2, 3].map((y) => PC.outturnPct(calc.exp.rows, y)) : [];
    const expVar  = PC ? [1, 2, 3].map((y) => PC.compositionVariancePct(calc.exp.rows, y)) : [];
    const ecoVar  = PC ? [1, 2, 3].map((y) => PC.compositionVariancePct(calc.eco.rows, y)) : [];
    const revOut  = PC ? [1, 2, 3].map((y) => PC.outturnPct(calc.rev.rows, y)) : [];
    const revVar  = PC ? [1, 2, 3].map((y) => PC.compositionVariancePct(calc.rev.rows, y)) : [];
    const contAvg = PC ? PC.contingencyAvgPct(calc.exp.rows, calc.exp.contingency) : null;
    const results = PC ? [
      { code: "PI-1.1", ind: "PI-1", label: L("Dépenses exécutées globales", "Aggregate expenditure outturn"),
        value: expOut.map(pct).join(" · "), score: PC.scorePI11(expOut) },
      { code: "PI-2.1", ind: "PI-2", label: L("Variance de composition (adm./fonctionnelle)", "Composition variance (admin/functional)"),
        value: expVar.map(pct).join(" · "), score: PC.scoreComposition(expVar) },
      { code: "PI-2.2", ind: "PI-2", label: L("Variance de composition (économique)", "Composition variance (economic)"),
        value: ecoVar.map(pct).join(" · "), score: PC.scoreComposition(ecoVar) },
      { code: "PI-2.3", ind: "PI-2", label: L("Dépenses sur réserves pour imprévus (moyenne)", "Expenditure from contingency reserves (average)"),
        value: contAvg == null ? "—" : contAvg.toFixed(1) + " %", score: PC.scoreContingency(contAvg) },
      { code: "PI-3.1", ind: "PI-3", label: L("Recettes exécutées globales", "Aggregate revenue outturn"),
        value: revOut.map(pct).join(" · "), score: PC.scorePI31(revOut) },
      { code: "PI-3.2", ind: "PI-3", label: L("Variance de composition des recettes", "Revenue composition variance"),
        value: revVar.map(pct).join(" · "), score: PC.scoreComposition(revVar) },
    ] : [];
    const applyScore = async (dimCode, score) => {
      const res = results.find((r) => r.code === dimCode);
      if (!res) return;
      try {
        await window.melr.upsertPefaScore(assessment.id, res.ind, dimCode, { score });
        setCalcMsg(L("Note " + score + " reportée sur " + dimCode + " (onglet Notation).",
                     "Score " + score + " written to " + dimCode + " (Scoring tab)."));
      } catch (e) { setCalcMsg(L("Erreur : ", "Error: ") + e.message); }
    };

    const lbl = { fontSize: 11, fontWeight: 600, display: "block", marginBottom: 2 };

    return (
      <div>
        {/* ═ Preuves (green = contenu principal) ═ */}
        <div style={sec("green")}>
          <div>
            <h3 style={secTitle("green")}>📎 {L("Bibliothèque de preuves", "Evidence library")}</h3>
            <div className="text-faint" style={{ fontSize: 11.5, marginBottom: 10 }}>
              {L("Pièces justificatives par indicateur (Manuel PEFA Vol. II — « evidence and sources »). Fichiers privés, visibles par les membres de l'organisation.",
                 "Supporting documents per indicator (PEFA Handbook Vol. II — “evidence and sources”). Private files, visible to organization members.")}
            </div>
            {canManage && (
              <div style={{ display: "grid", gridTemplateColumns: "150px 1fr 150px 90px auto auto", gap: 8, alignItems: "end", marginBottom: 12 }}>
                <div>
                  <label style={lbl}>{L("Indicateur *", "Indicator *")}</label>
                  <select className="input" value={evInd} onChange={(e) => setEvInd(e.target.value)} style={{ width: "100%", fontSize: 11.5 }}>
                    <option value="">—</option>
                    {indicatorOptions.map((i) => <option key={i.code} value={i.code}>{i.code}</option>)}
                  </select>
                </div>
                <div>
                  <label style={lbl}>{L("Intitulé", "Title")}</label>
                  <input className="input" value={evTitle} onChange={(e) => setEvTitle(e.target.value)} style={{ width: "100%", fontSize: 11.5 }} />
                </div>
                <div>
                  <label style={lbl}>{L("Source", "Source")}</label>
                  <input className="input" value={evSource} onChange={(e) => setEvSource(e.target.value)} style={{ width: "100%", fontSize: 11.5 }} />
                </div>
                <div>
                  <label style={lbl}>{L("Exercice", "FY")}</label>
                  <input className="input" value={evYear} onChange={(e) => setEvYear(e.target.value)} placeholder="2025" style={{ width: "100%", fontSize: 11.5 }} />
                </div>
                <div>
                  <label style={lbl}>{L("Fichier *", "File *")}</label>
                  <input type="file" onChange={(e) => setEvFile(e.target.files && e.target.files[0])} style={{ fontSize: 11 }} />
                </div>
                <button className="btn sm primary" disabled={evBusy || !evInd || !evFile} onClick={uploadEvidence}>
                  {evBusy ? "…" : L("Ajouter", "Add")}
                </button>
              </div>
            )}
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
              <select className="input" value={evFilter} onChange={(e) => setEvFilter(e.target.value)} style={{ fontSize: 11.5 }}>
                <option value="">{L("Tous les indicateurs", "All indicators")}</option>
                {indicatorOptions.map((i) => <option key={i.code} value={i.code}>{i.code}</option>)}
              </select>
              <span className="text-faint" style={{ fontSize: 11 }}>{visibleEvidence.length} {L("preuve(s)", "item(s)")}</span>
              {evMsg && <span className="text-faint" style={{ fontSize: 11 }}>{evMsg}</span>}
            </div>
            <table className="tbl" style={{ width: "100%" }}>
              <thead><tr>
                <th>{L("Indicateur", "Indicator")}</th><th>{L("Intitulé / fichier", "Title / file")}</th>
                <th>{L("Source", "Source")}</th><th>{L("Exercice", "FY")}</th><th></th>
              </tr></thead>
              <tbody>
                {visibleEvidence.length === 0 && (
                  <tr><td colSpan={5} className="text-faint" style={{ padding: 12, fontSize: 11.5 }}>
                    {L("Aucune preuve pour le moment.", "No evidence yet.")}
                  </td></tr>
                )}
                {visibleEvidence.map((r) => (
                  <tr key={r.id}>
                    <td style={{ fontWeight: 700, fontSize: 11.5 }}>{r.indicator_code}</td>
                    <td style={{ fontSize: 11.5 }}>
                      <a href="#" onClick={(e) => { e.preventDefault(); openEvidence(r); }}>
                        {r.title || r.file_name}
                      </a>
                      {r.title && <span className="text-faint" style={{ fontSize: 10.5 }}> · {r.file_name}</span>}
                    </td>
                    <td style={{ fontSize: 11.5 }}>{r.source || "—"}</td>
                    <td style={{ fontSize: 11.5 }}>{r.fiscal_year || "—"}</td>
                    <td style={{ textAlign: "right" }}>
                      {canManage && (
                        <button className="btn xs ghost" onClick={() => deleteEvidence(r)} title={L("Supprimer", "Delete")}>
                          <Icon.x />
                        </button>
                      )}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>

        {/* ═ Feuilles de calcul (amber = outils / paramètres) ═ */}
        <div style={sec("amber")}>
          <div>
            <h3 style={secTitle("amber")}>🧮 {L("Feuilles de calcul officielles PI-1 / PI-2 / PI-3", "Official PI-1 / PI-2 / PI-3 calculation sheets")}</h3>
            <div className="text-faint" style={{ fontSize: 11.5, marginBottom: 10 }}>
              {L("Méthodologie des classeurs du Secrétariat PEFA : exécution globale, variance de composition (budget ajusté), réserves pour imprévus — sur les 3 derniers exercices clos. Les notes proposées suivent les barèmes du cadre 2016 et peuvent être reportées vers l'onglet Notation.",
                 "Methodology of the PEFA Secretariat workbooks: aggregate outturn, composition variance (adjusted budget), contingency reserves — over the last three completed fiscal years. Suggested scores follow the 2016 framework calibrations and can be applied to the Scoring tab.")}
            </div>
            <div style={{ display: "flex", gap: 8, alignItems: "end", marginBottom: 12 }}>
              {[0, 1, 2].map((y) => (
                <div key={y}>
                  <label style={lbl}>{L("Exercice ", "FY ") + (y + 1)}</label>
                  <input className="input" value={calc.years[y] || ""} disabled={!canManage}
                    placeholder={"20" + (23 + y)}
                    onChange={(e) => {
                      const years = calc.years.slice(); years[y] = e.target.value;
                      patchCalc({ years });
                    }}
                    style={{ width: 90, fontSize: 11.5 }} />
                </div>
              ))}
              {canManage && (
                <button className="btn sm primary" disabled={calcBusy || !calcDirty} onClick={saveCalc}>
                  {calcBusy ? "…" : L("Enregistrer", "Save")}
                </button>
              )}
              {calcDirty && <span style={{ fontSize: 11, color: "#b45309" }}>{L("Non enregistré", "Unsaved")}</span>}
              {calcMsg && <span className="text-faint" style={{ fontSize: 11 }}>{calcMsg}</span>}
            </div>

            <div style={{ fontSize: 12.5, fontWeight: 700, margin: "10px 0 4px" }}>
              {L("1 · Dépenses par unité administrative / fonction (PI-1, PI-2.1)", "1 · Expenditure by administrative unit / function (PI-1, PI-2.1)")}
            </div>
            <CalcGrid lang={lang} rows={calc.exp.rows} years={calc.years} canManage={canManage}
              nameLabel={L("Unité administrative / fonction", "Administrative unit / function")}
              onRows={(rows) => patchCalc({ exp: { ...calc.exp, rows } })} />
            <div style={{ display: "flex", gap: 8, alignItems: "end", margin: "8px 0 4px" }}>
              <span style={{ fontSize: 11.5, fontWeight: 600 }}>
                {L("Dépenses imputées aux réserves pour imprévus :", "Expenditure charged to contingency reserves:")}
              </span>
              {[0, 1, 2].map((y) => (
                <input key={y} type="number" className="input" disabled={!canManage}
                  value={calc.exp.contingency[y] == null ? "" : calc.exp.contingency[y]}
                  placeholder={calc.years[y] || (L("Ex. ", "FY ") + (y + 1))}
                  onChange={(e) => {
                    const contingency = calc.exp.contingency.slice();
                    contingency[y] = e.target.value === "" ? null : Number(e.target.value);
                    patchCalc({ exp: { ...calc.exp, contingency } });
                  }}
                  style={{ width: 110, fontSize: 11.5, textAlign: "right" }} />
              ))}
            </div>

            <div style={{ fontSize: 12.5, fontWeight: 700, margin: "14px 0 4px" }}>
              {L("2 · Dépenses par classification économique (PI-2.2)", "2 · Expenditure by economic classification (PI-2.2)")}
            </div>
            <CalcGrid lang={lang} rows={calc.eco.rows} years={calc.years} canManage={canManage}
              nameLabel={L("Catégorie économique", "Economic category")}
              onRows={(rows) => patchCalc({ eco: { ...calc.eco, rows } })} />

            <div style={{ fontSize: 12.5, fontWeight: 700, margin: "14px 0 4px" }}>
              {L("3 · Recettes par catégorie (PI-3)", "3 · Revenue by category (PI-3)")}
            </div>
            <CalcGrid lang={lang} rows={calc.rev.rows} years={calc.years} canManage={canManage}
              nameLabel={L("Catégorie de recettes", "Revenue category")}
              onRows={(rows) => patchCalc({ rev: { ...calc.rev, rows } })} />

            <div style={sec("purple", { marginTop: 16, marginBottom: 0 })}>
              <div style={secTitle("purple")}>
                📊 {L("Résultats et notes proposées", "Results and suggested scores")}
                <span className="text-faint" style={{ fontWeight: 400, fontSize: 11 }}>
                  {L("(par exercice : ", "(per fiscal year: ")}{(calc.years.filter(Boolean).join(" · ") || "—")})
                </span>
              </div>
              {results.map((r) => (
                <CalcResult key={r.code} lang={lang} code={r.code} label={r.label}
                  value={r.value} score={r.score} canManage={canManage} onApply={applyScore} />
              ))}
            </div>
          </div>
        </div>
      </div>
    );
  }

  // ── Rapport & qualité (phase 3) ──────────────────────────────────────────
  // Sections narratives du PFM-PR (sheet 'report') + charte d'assurance
  // qualité PEFA Check (sheet 'check' — les 8 engagements officiels de la
  // charte, extraits des Instructions PEFA Check 02/03/2020). L'export Word
  // assemble : page de titre, résumé, tableau des notes, sections 1-5 (la
  // section 3 « Évaluation de la performance » est générée depuis les notes
  // et justifications de l'onglet Notation).
  const REPORT_SECTIONS = [
    { key: "summary",     fr: "Résumé analytique",                              en: "Executive summary", es: "Resumen analítico",
      hint_fr: "Objet, couverture, principales constatations par pilier, évolution depuis la dernière évaluation.",
      hint_en: "Purpose, coverage, main findings per pillar, change since the previous assessment." },
    { key: "intro",       fr: "1. Introduction",                                en: "1. Introduction", es: "1. Introducción",
      hint_fr: "Justification et objet, gestion de l'évaluation et assurance qualité, méthodologie.",
      hint_en: "Rationale and purpose, assessment management and quality assurance, methodology." },
    { key: "country",     fr: "2. Informations générales sur le pays",          en: "2. Country background information", es: "2. Información general sobre el país",
      hint_fr: "Situation économique, tendances budgétaires, cadre juridique et institutionnel de la GFP.",
      hint_en: "Economic situation, fiscal trends, legal and institutional framework for PFM." },
    { key: "conclusions", fr: "4. Conclusions de l'analyse des systèmes de GFP", en: "4. Conclusions of the analysis of PFM systems", es: "4. Conclusiones del análisis de los sistemas de GFP",
      hint_fr: "Analyse transversale : discipline budgétaire globale, allocation stratégique, efficacité des services.",
      hint_en: "Integrated analysis: aggregate fiscal discipline, strategic allocation, efficient service delivery." },
    { key: "reform",      fr: "5. Processus de réforme de la GFP",              en: "5. Government PFM reform process", es: "5. Proceso de reforma de la GFP",
      hint_fr: "Démarche générale, réformes récentes et en cours, considérations institutionnelles.",
      hint_en: "Overall approach, recent and ongoing reforms, institutional considerations." },
  ];

  // Les 8 engagements de la charte d'assurance qualité (PEFA Check).
  const CHECK_ITEMS = [
    { key: "cn_reviewed",   fr: "Projet de note conceptuelle établi et revu par quatre réviseurs indépendants, y compris le Secrétariat PEFA",
                            en: "Draft concept note prepared and reviewed by four independent reviewers, including the PEFA Secretariat", es: "Proyecto de nota conceptual elaborado y revisado por cuatro revisores independientes, incluido el Secretariado PEFA" },
    { key: "cn_final",      fr: "Note conceptuelle finalisée avant le début de la collecte des données sur le terrain",
                            en: "Concept note finalized before data collection starts in the field", es: "Nota conceptual finalizada antes del inicio de la recolección de datos sobre el terreno" },
    { key: "draft_sent",    fr: "Projet de rapport PEFA complet transmis à tous les réviseurs pour examen",
                            en: "Full draft PEFA report sent to all reviewers for review", es: "Proyecto de informe PEFA completo transmitido a todos los revisores para su examen" },
    { key: "comments_matrix", fr: "Projet de rapport révisé, annexé d'une matrice des commentaires des réviseurs et des réponses de l'équipe",
                            en: "Revised draft report with an annexed matrix of reviewers' comments and the team's responses", es: "Proyecto de informe revisado, con anexo de una matriz de los comentarios de los revisores y las respuestas del equipo" },
    { key: "secretariat_final", fr: "Rapport final examiné par le Secrétariat PEFA (trois seuils d'indice qualité atteints)",
                            en: "Final report reviewed by the PEFA Secretariat (three quality index thresholds met)", es: "Informe final examinado por el Secretariado PEFA (tres umbrales del índice de calidad alcanzados)" },
    { key: "qa_in_report",  fr: "Rapport final incluant les informations sur la procédure d'assurance qualité",
                            en: "Final report includes the quality assurance arrangements", es: "Informe final que incluye la información sobre el procedimiento de aseguramiento de la calidad" },
    { key: "check_requested", fr: "Attribution du PEFA Check sollicitée auprès du Secrétariat PEFA",
                            en: "PEFA Check award requested from the PEFA Secretariat", es: "Atribución del PEFA Check solicitada al Secretariado PEFA" },
    { key: "gov_dissemination", fr: "Rapport final transmis au gouvernement pour dissémination et exploitation",
                            en: "Final report sent to the government for dissemination and use", es: "Informe final transmitido al gobierno para su difusión y utilización" },
  ];

  // Note finale affichée d'un indicateur : override si présent, sinon
  // agrégat M1/M2 calculé depuis les dimensions.
  function finalIndicatorScore(ind, byKey) {
    const ov = byKey[ind.code + "|"];
    if (ov && ov.score) return ov.score;
    return window.pefaAggregate
      ? window.pefaAggregate(ind.method || "M1", ind.dims.map((dd) => {
          const r = byKey[ind.code + "|" + dd.code];
          return (r && r.score) || null;
        }))
      : null;
  }
  function catalogIndicators(assessment) {
    const cat = window.PEFA_CATALOG;
    if (!cat) return [];
    const mods = (assessment && assessment.modules) || {};
    let list = cat.indicators.slice();
    if (mods.gender)  list = list.concat(cat.gender);
    if (mods.climate) list = list.concat(cat.climate);
    return list;
  }

  function exportPfmPrWord(lang, assessment, report, check, byKey, actions) {
    const D = window.docx;
    if (!D) { window.alert(window.L("Bibliothèque Word indisponible.", "Word library unavailable.", "Biblioteca Word no disponible.")); return; }
    const L = (fr, en) => (lang === "fr" ? fr : en);
    const ev = assessment.evaluation || {};
    const title = lang === "fr" ? (ev.title_fr || "Évaluation PEFA") : (ev.title_en || ev.title_fr || "PEFA assessment");
    const cat = window.PEFA_CATALOG;
    const P = (text, o) => new D.Paragraph({
      children: [new D.TextRun({ text, size: (o && o.size) || 21, bold: !!(o && o.bold), color: (o && o.color) || "111827", font: "Arial", italics: !!(o && o.italics) })],
      spacing: { after: (o && o.after) != null ? o.after : 100 },
    });
    const H1 = (t) => P(t, { size: 26, bold: true, color: "0F2740", after: 140 });
    const H2 = (t) => P(t, { size: 23, bold: true, color: "1D4ED8", after: 100 });
    const narrative = (key) => {
      const body = ((report || {})[key] || "").trim() || L("(à compléter)", "(to be completed)");
      return body.split(/\n+/).map((line) => P(line));
    };
    // Bordures POINTILLÉES (présentation demandée) pour tous les tableaux.
    const border = { style: D.BorderStyle.DOTTED, size: 2, color: "94A3B8" };
    const borders = { top: border, bottom: border, left: border, right: border };
    const cell = (t, opts) => new D.TableCell({
      borders, margins: { top: 30, bottom: 30, left: 80, right: 80 },
      shading: opts && opts.head ? { type: D.ShadingType.CLEAR, fill: "1D4ED8" } : undefined,
      children: [new D.Paragraph({
        alignment: opts && opts.center ? D.AlignmentType.CENTER : undefined,
        children: [new D.TextRun({
          text: String(t), size: 17, font: "Arial",
          bold: !!(opts && (opts.head || opts.bold)), color: opts && opts.head ? "FFFFFF" : "111827",
        })] })],
    });
    const inds = catalogIndicators(assessment);
    const dimScoreOf = (ind, i) => {
      const dd = ind.dims[i];
      if (!dd) return "";
      const r = byKey[ind.code + "|" + dd.code];
      return (r && r.score) || "—";
    };
    // Tableau récapitulatif : notes des dimensions PUIS note globale.
    const scoreTable = new D.Table({
      width: { size: 100, type: D.WidthType.PERCENTAGE },
      rows: [
        new D.TableRow({ tableHeader: true, children: [
          cell(L("Indicateur", "Indicator"), { head: true }),
          cell(L("Intitulé", "Name"), { head: true }),
          cell(L("Dim. 1", "Dim. 1"), { head: true, center: true }),
          cell(L("Dim. 2", "Dim. 2"), { head: true, center: true }),
          cell(L("Dim. 3", "Dim. 3"), { head: true, center: true }),
          cell(L("Dim. 4", "Dim. 4"), { head: true, center: true }),
          cell(L("Note globale", "Overall score"), { head: true, center: true }),
        ]}),
        ...inds.map((ind) => new D.TableRow({ children: [
          cell(ind.code, { bold: true }),
          cell((lang === "es" ? (ind.es != null ? ind.es : ind.en) : lang === "fr" ? ind.fr : ind.en)),
          cell(dimScoreOf(ind, 0), { center: true }),
          cell(dimScoreOf(ind, 1), { center: true }),
          cell(dimScoreOf(ind, 2), { center: true }),
          cell(dimScoreOf(ind, 3), { center: true }),
          cell(finalIndicatorScore(ind, byKey) || "—", { center: true, bold: true }),
        ]})),
      ],
    });
    const children = [
      P(L("RAPPORT SUR LA PERFORMANCE DE LA GESTION DES FINANCES PUBLIQUES (PFM-PR)",
          "PUBLIC FINANCIAL MANAGEMENT PERFORMANCE REPORT (PFM-PR)"), { size: 28, bold: true, color: "0F2740", after: 80 }),
      P(title, { size: 25, bold: true, color: "1D4ED8", after: 60 }),
      P(L("Cadre PEFA 2016 · ", "PEFA 2016 framework · ")
        + (assessment.government_level === "subnational" ? L("Infranational", "Subnational") : "National")
        + L(" · Exercices : ", " · Fiscal years: ") + (((assessment.scope || {}).fiscal_years || []).join(", ") || "—"),
        { size: 18, color: "64748B", after: 60 }),
    ];
    const checkData = check || {};
    const doneCount = CHECK_ITEMS.filter((it) => checkData.items && checkData.items[it.key] && checkData.items[it.key].done).length;
    children.push(P(L("Assurance qualité (charte PEFA Check) : " + doneCount + "/8 engagements remplis",
                      "Quality assurance (PEFA Check charter): " + doneCount + "/8 commitments met")
      + (checkData.funded_by ? L(" · Financée par : ", " · Funded by: ") + checkData.funded_by : "")
      + (checkData.led_by    ? L(" · Dirigée par : ", " · Led by: ") + checkData.led_by : "")
      + (checkData.manager   ? L(" · Responsable : ", " · Manager: ") + checkData.manager : ""),
      { size: 17, color: "64748B", after: 240 }));
    children.push(H1(L("Résumé analytique", "Executive summary")));
    children.push(...narrative("summary"));
    children.push(H2(L("Tableau récapitulatif des notes", "Summary of scores")));
    children.push(scoreTable);
    children.push(P("", { after: 200 }));
    children.push(H1(L("1. Introduction", "1. Introduction")));
    children.push(...narrative("intro"));
    children.push(H1(L("2. Informations générales sur le pays", "2. Country background information")));
    children.push(...narrative("country"));
    children.push(H1(L("3. Évaluation de la performance de la GFP", "3. Assessment of PFM performance")));
    const groups = [];
    (cat ? cat.pillars : []).forEach((p) => {
      groups.push({ key: "p" + p.num,
        title: L("Pilier ", "Pillar ") + ROMAN[p.num - 1] + " · " + ((lang === "es" ? (p.es != null ? p.es : p.en) : lang === "fr" ? p.fr : p.en)),
        inds: (cat ? cat.indicators : []).filter((i) => i.pillar === p.num) });
    });
    const mods = assessment.modules || {};
    if (mods.gender && cat)  groups.push({ key: "gender",  title: L("Module Genre — GRPFM", "Gender module — GRPFM"), inds: cat.gender });
    if (mods.climate && cat) groups.push({ key: "climate", title: L("Module Climat — CRPFM", "Climate module — CRPFM"), inds: cat.climate });
    const justKey = window.L("justification_fr", "justification_en", "justification_en");
    const pillarNotes    = (report || {}).pillar_notes    || {};
    const indicatorNotes = (report || {}).indicator_notes || {};
    groups.forEach((g) => {
      children.push(H2(g.title));
      // Narratif analytique du pilier (saisi dans l'onglet Rapport & qualité)
      if (pillarNotes[g.key]) {
        pillarNotes[g.key].split(/\n+/).forEach((line) => children.push(P(line, { size: 19, after: 60 })));
      }
      g.inds.forEach((ind) => {
        const sc = finalIndicatorScore(ind, byKey);
        children.push(P(ind.code + " — " + ((lang === "es" ? (ind.es != null ? ind.es : ind.en) : lang === "fr" ? ind.fr : ind.en)) + L(" · Note : ", " · Score: ") + (sc || "—"),
          { size: 20, bold: true, after: 60 }));
        // Narratif analytique de l'indicateur
        if (indicatorNotes[ind.code]) {
          indicatorNotes[ind.code].split(/\n+/).forEach((line) => children.push(P(line, { size: 18, after: 50 })));
        }
        ind.dims.forEach((dd) => {
          const row = byKey[ind.code + "|" + dd.code];
          const dimScore = (row && row.score) || "—";
          children.push(P(dd.code + " (" + dimScore + ") — " + ((lang === "es" ? (dd.es != null ? dd.es : dd.en) : lang === "fr" ? dd.fr : dd.en)), { size: 18, after: 30 }));
          const just = row && (row[justKey] || row.justification_fr);
          if (just) just.split(/\n+/).forEach((line) => children.push(P(line, { size: 18, italics: true, after: 50 })));
        });
        children.push(P("", { after: 60 }));
      });
    });
    children.push(H1(L("4. Conclusions de l'analyse des systèmes de GFP", "4. Conclusions of the analysis of PFM systems")));
    children.push(...narrative("conclusions"));
    children.push(H1(L("5. Processus de réforme de la GFP", "5. Government PFM reform process")));
    children.push(...narrative("reform"));
    // 6. Plan d'action — recommandations issues de l'évaluation
    const items = actions || [];
    children.push(H1(L("6. Plan d'action — dialogue sur les réformes", "6. Action plan — PFM reform dialogue")));
    if (items.length === 0) {
      children.push(P(L("(aucune recommandation saisie)", "(no recommendation entered)"), { size: 18, italics: true }));
    } else {
      const PR = { high: L("Haute", "High"), medium: L("Moyenne", "Medium"), low: L("Basse", "Low") };
      const ST = { todo: L("À faire", "To do"), in_progress: L("En cours", "In progress"), done: L("Réalisée", "Done") };
      children.push(new D.Table({
        width: { size: 100, type: D.WidthType.PERCENTAGE },
        rows: [
          new D.TableRow({ tableHeader: true, children: [
            cell(L("Pilier", "Pillar"), { head: true, center: true }),
            cell(L("Indicateur", "Indicator"), { head: true, center: true }),
            cell(L("Recommandation", "Recommendation"), { head: true }),
            cell(L("Priorité", "Priority"), { head: true, center: true }),
            cell(L("Responsable", "Responsible"), { head: true }),
            cell(L("Échéance", "Due"), { head: true, center: true }),
            cell(L("Statut", "Status"), { head: true, center: true }),
          ]}),
          ...items.map((it) => new D.TableRow({ children: [
            cell(it.pillar ? ROMAN[it.pillar - 1] : L("Transv.", "Cross"), { center: true }),
            cell(it.indicator_code || "—", { center: true }),
            cell((lang === "es" ? (it.recommendation_es != null ? it.recommendation_es : it.recommendation_en || it.recommendation_fr) : lang === "fr" ? it.recommendation_fr : it.recommendation_en || it.recommendation_fr)),
            cell(PR[it.priority] || it.priority, { center: true }),
            cell(it.responsible || "—"),
            cell(it.due_date || "—", { center: true }),
            cell(ST[it.status] || it.status, { center: true }),
          ]})),
        ],
      }));
    }
    const doc = new D.Document({ sections: [{ children }] });
    const fname = "PEFA_PFM-PR_" + (ev.title_fr || "evaluation").replace(/[^\wÀ-ſ-]+/g, "_") + ".docx";
    D.Packer.toBlob(doc).then((blob) => {
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url; a.download = fname; a.click();
      setTimeout(() => URL.revokeObjectURL(url), 4000);
    });
  }

  // Export PDF du PFM-PR — flux texte jsPDF (A4 portrait, multi-pages),
  // même contenu que l'export Word.
  function exportPfmPrPdf(lang, assessment, report, check, byKey, actions) {
    if (!window.jspdf || !window.jspdf.jsPDF) {
      window.alert(window.L("Bibliothèque PDF indisponible.", "PDF library unavailable.", "Biblioteca PDF no disponible."));
      return;
    }
    const L = (fr, en) => (lang === "fr" ? fr : en);
    const { jsPDF } = window.jspdf;
    const doc = new jsPDF({ unit: "pt", format: "a4" });
    const W = 595, M = 48, BOTTOM = 800;
    let y = 56;
    const ensure = (h) => { if (y + h > BOTTOM) { doc.addPage(); y = 56; } };
    const text = (str, size, opts) => {
      const o = opts || {};
      doc.setFont("helvetica", o.bold ? "bold" : (o.italics ? "italic" : "normal"));
      doc.setFontSize(size);
      const c = o.color || [17, 24, 39];
      doc.setTextColor(c[0], c[1], c[2]);
      const lines = doc.splitTextToSize(String(str || ""), W - 2 * M);
      lines.forEach((line) => { ensure(size * 1.5); doc.text(line, M, y); y += size * 1.35; });
      y += o.after != null ? o.after : 5;
    };
    const H1 = (t) => { ensure(40); text(t, 14, { bold: true, color: [15, 39, 64], after: 8 }); };
    const H2 = (t) => { ensure(30); text(t, 12, { bold: true, color: [29, 78, 216], after: 6 }); };
    const narrative = (key) => text(((report || {})[key] || "").trim() || L("(à compléter)", "(to be completed)"), 10, { after: 10 });

    const ev = assessment.evaluation || {};
    const title = lang === "fr" ? (ev.title_fr || "Évaluation PEFA") : (ev.title_en || ev.title_fr || "PEFA assessment");
    const cat = window.PEFA_CATALOG;
    const inds = catalogIndicators(assessment);
    const checkData = check || {};
    const doneCount = CHECK_ITEMS.filter((it) => checkData.items && checkData.items[it.key] && checkData.items[it.key].done).length;

    text(L("RAPPORT SUR LA PERFORMANCE DE LA GESTION DES FINANCES PUBLIQUES (PFM-PR)",
           "PUBLIC FINANCIAL MANAGEMENT PERFORMANCE REPORT (PFM-PR)"), 15, { bold: true, color: [15, 39, 64], after: 4 });
    text(title, 13, { bold: true, color: [29, 78, 216], after: 4 });
    text(L("Cadre PEFA 2016 · ", "PEFA 2016 framework · ")
      + (assessment.government_level === "subnational" ? L("Infranational", "Subnational") : "National")
      + L(" · Exercices : ", " · Fiscal years: ") + (((assessment.scope || {}).fiscal_years || []).join(", ") || "—")
      + L(" · PEFA Check : ", " · PEFA Check: ") + doneCount + "/8", 9, { color: [100, 116, 139], after: 14 });

    H1(L("Résumé analytique", "Executive summary"));
    narrative("summary");
    H2(L("Tableau récapitulatif des notes", "Summary of scores"));
    // En-tête : dimensions puis note globale ; séparateurs pointillés.
    doc.setLineDashPattern([1.5, 1.5], 0);
    doc.setDrawColor(148, 163, 184);
    const COLS = [M, M + 52, W - M - 150, W - M - 118, W - M - 86, W - M - 54, W - M - 4];
    ensure(16);
    doc.setFont("helvetica", "bold"); doc.setFontSize(8); doc.setTextColor(29, 78, 216);
    doc.text(L("Code", "Code"), COLS[0], y);
    doc.text(L("Intitulé", "Name"), COLS[1], y);
    ["D1", "D2", "D3", "D4"].forEach((h, i) => doc.text(h, COLS[2 + i], y, { align: "center" }));
    doc.text(L("Note", "Score"), COLS[6], y, { align: "right" });
    y += 4; doc.line(M, y, W - M, y); y += 10;
    inds.forEach((ind) => {
      const sc = finalIndicatorScore(ind, byKey) || "—";
      ensure(14);
      doc.setFont("helvetica", "bold"); doc.setFontSize(8.5); doc.setTextColor(17, 24, 39);
      doc.text(ind.code, COLS[0], y);
      doc.setFont("helvetica", "normal");
      doc.text(doc.splitTextToSize((lang === "es" ? (ind.es != null ? ind.es : ind.en) : lang === "fr" ? ind.fr : ind.en), COLS[2] - COLS[1] - 14)[0], COLS[1], y);
      for (let i = 0; i < 4; i++) {
        const dd = ind.dims[i];
        const r = dd && byKey[ind.code + "|" + dd.code];
        doc.text(dd ? ((r && r.score) || "—") : "", COLS[2 + i], y, { align: "center" });
      }
      doc.setFont("helvetica", "bold");
      doc.text(sc, COLS[6], y, { align: "right" });
      y += 3; doc.line(M, y, W - M, y); y += 10;
    });
    doc.setLineDashPattern([], 0);
    y += 10;

    H1(L("1. Introduction", "1. Introduction"));
    narrative("intro");
    H1(L("2. Informations générales sur le pays", "2. Country background information"));
    narrative("country");
    H1(L("3. Évaluation de la performance de la GFP", "3. Assessment of PFM performance"));
    const groups = [];
    (cat ? cat.pillars : []).forEach((p) => {
      groups.push({ key: "p" + p.num,
        title: L("Pilier ", "Pillar ") + ROMAN[p.num - 1] + " · " + ((lang === "es" ? (p.es != null ? p.es : p.en) : lang === "fr" ? p.fr : p.en)),
        inds: (cat ? cat.indicators : []).filter((i) => i.pillar === p.num) });
    });
    const mods = assessment.modules || {};
    if (mods.gender && cat)  groups.push({ key: "gender",  title: L("Module Genre — GRPFM", "Gender module — GRPFM"), inds: cat.gender });
    if (mods.climate && cat) groups.push({ key: "climate", title: L("Module Climat — CRPFM", "Climate module — CRPFM"), inds: cat.climate });
    const justKey = window.L("justification_fr", "justification_en", "justification_en");
    const pillarNotes    = (report || {}).pillar_notes    || {};
    const indicatorNotes = (report || {}).indicator_notes || {};
    groups.forEach((g) => {
      H2(g.title);
      if (pillarNotes[g.key]) text(pillarNotes[g.key], 10, { after: 6 });
      g.inds.forEach((ind) => {
        const sc = finalIndicatorScore(ind, byKey);
        text(ind.code + " — " + ((lang === "es" ? (ind.es != null ? ind.es : ind.en) : lang === "fr" ? ind.fr : ind.en)) + L(" · Note : ", " · Score: ") + (sc || "—"), 10, { bold: true, after: 2 });
        if (indicatorNotes[ind.code]) text(indicatorNotes[ind.code], 9, { after: 3 });
        ind.dims.forEach((dd) => {
          const row = byKey[ind.code + "|" + dd.code];
          text(dd.code + " (" + ((row && row.score) || "—") + ") — " + ((lang === "es" ? (dd.es != null ? dd.es : dd.en) : lang === "fr" ? dd.fr : dd.en)), 9, { after: 1 });
          const just = row && (row[justKey] || row.justification_fr);
          if (just) text(just, 9, { italics: true, color: [75, 85, 99], after: 3 });
        });
        y += 4;
      });
    });
    H1(L("4. Conclusions de l'analyse des systèmes de GFP", "4. Conclusions of the analysis of PFM systems"));
    narrative("conclusions");
    H1(L("5. Processus de réforme de la GFP", "5. Government PFM reform process"));
    narrative("reform");
    // 6. Plan d'action
    H1(L("6. Plan d'action — dialogue sur les réformes", "6. Action plan — PFM reform dialogue"));
    const items = actions || [];
    if (items.length === 0) {
      text(L("(aucune recommandation saisie)", "(no recommendation entered)"), 9, { italics: true });
    } else {
      const PR = { high: L("haute", "high"), medium: L("moyenne", "medium"), low: L("basse", "low") };
      const ST = { todo: L("à faire", "to do"), in_progress: L("en cours", "in progress"), done: L("réalisée", "done") };
      items.forEach((it, i) => {
        text((i + 1) + ". [" + (it.pillar ? ROMAN[it.pillar - 1] : L("Transv.", "Cross"))
          + (it.indicator_code ? " · " + it.indicator_code : "") + "] "
          + ((lang === "es" ? (it.recommendation_es != null ? it.recommendation_es : it.recommendation_en || it.recommendation_fr) : lang === "fr" ? it.recommendation_fr : it.recommendation_en || it.recommendation_fr)), 10, { bold: true, after: 1 });
        text(L("Priorité ", "Priority ") + (PR[it.priority] || it.priority)
          + (it.responsible ? L(" · Responsable : ", " · Responsible: ") + it.responsible : "")
          + (it.due_date ? L(" · Échéance : ", " · Due: ") + it.due_date : "")
          + L(" · Statut : ", " · Status: ") + (ST[it.status] || it.status), 9, { color: [75, 85, 99], after: 6 });
      });
    }
    const fname = "PEFA_PFM-PR_" + (ev.title_fr || "evaluation").replace(/[^\wÀ-ſ-]+/g, "_") + ".pdf";
    doc.save(fname);
  }

  function PefaReportTab({ lang, assessment, canManage }) {
    const L = (fr, en) => (lang === "fr" ? fr : en);
    const { data: scores } = window.melr.usePefaScores
      ? window.melr.usePefaScores(assessment.id)
      : { data: [] };
    // Plan d'action : inclus dans la section 6 des exports Word/PDF.
    const { data: actionItems } = window.melr.usePefaActionItems
      ? window.melr.usePefaActionItems(assessment.id)
      : { data: [] };
    const byKey = useMemo(() => {
      const m = {};
      (scores || []).forEach((r) => { m[r.indicator_code + "|" + (r.dimension_code || "")] = r; });
      return m;
    }, [scores]);

    const [report, setReport] = useState({});
    const [check, setCheck]   = useState({ funded_by: "", led_by: "", manager: "", items: {} });
    const [dirty, setDirty]   = useState(false);
    const [busy, setBusy]     = useState(false);
    const [msg, setMsg]       = useState(null);
    const [openNarr, setOpenNarr] = useState({});
    useEffect(() => {
      let cancelled = false;
      setReport({}); setCheck({ funded_by: "", led_by: "", manager: "", items: {} }); setDirty(false); setMsg(null);
      Promise.all([
        window.melr.fetchPefaCalcData(assessment.id, "report"),
        window.melr.fetchPefaCalcData(assessment.id, "check"),
      ]).then(([r, c]) => {
        if (cancelled) return;
        if (r && r.data) setReport(r.data);
        if (c && c.data) setCheck({ funded_by: "", led_by: "", manager: "", items: {}, ...c.data });
      }).catch((e) => { if (!cancelled) setMsg(e.message); });
      return () => { cancelled = true; };
    }, [assessment.id]);

    const save = async () => {
      setBusy(true); setMsg(null);
      try {
        await window.melr.savePefaCalcData(assessment.id, "report", report);
        await window.melr.savePefaCalcData(assessment.id, "check", check);
        setDirty(false);
        setMsg(L("Rapport et charte qualité enregistrés.", "Report and quality charter saved."));
      } catch (e) { setMsg(L("Erreur : ", "Error: ") + e.message); }
      finally { setBusy(false); }
    };
    const setCheckItem = (key, patch) => {
      setCheck({ ...check, items: { ...check.items, [key]: { ...(check.items[key] || {}), ...patch } } });
      setDirty(true);
    };
    const doneCount = CHECK_ITEMS.filter((it) => check.items[it.key] && check.items[it.key].done).length;
    const lbl = { fontSize: 11, fontWeight: 600, display: "block", marginBottom: 2 };

    return (
      <div>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 14, flexWrap: "wrap" }}>
          {canManage && (
            <button className="btn sm primary" disabled={busy || !dirty} onClick={save}>
              {busy ? "…" : L("Enregistrer", "Save")}
            </button>
          )}
          <button style={xbtn("#2563eb")} onClick={() => exportPfmPrWord(lang, assessment, report, check, byKey, actionItems)}>
            📄 {L("Rapport PFM-PR (Word)", "PFM-PR report (Word)")}
          </button>
          <button style={xbtn("#dc2626")} onClick={() => exportPfmPrPdf(lang, assessment, report, check, byKey, actionItems)}>
            📕 {L("Rapport PFM-PR (PDF)", "PFM-PR report (PDF)")}
          </button>
          {dirty && <span style={{ fontSize: 11, color: "#b45309" }}>{L("Modifications non enregistrées", "Unsaved changes")}</span>}
          {msg && <span className="text-faint" style={{ fontSize: 11 }}>{msg}</span>}
        </div>

        {/* Sections narratives (green = saisie principale) */}
        {REPORT_SECTIONS.map((s) => (
          <div key={s.key} style={sec("green", { marginBottom: 12 })}>
            <div>
              <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 2 }}>{(lang === "es" ? (s.es != null ? s.es : s.en) : lang === "fr" ? s.fr : s.en)}</div>
              <div className="text-faint" style={{ fontSize: 11.5, marginBottom: 8 }}>{(lang === "es" ? (s.hint_es != null ? s.hint_es : s.hint_en) : lang === "fr" ? s.hint_fr : s.hint_en)}</div>
              <textarea className="input" rows={4} disabled={!canManage}
                style={{ width: "100%", resize: "vertical", fontSize: 12.5 }}
                value={report[s.key] || ""}
                onChange={(e) => { setReport({ ...report, [s.key]: e.target.value }); setDirty(true); }} />
            </div>
          </div>
        ))}
        {/* Narratifs analytiques par pilier / indicateur (section 3 du rapport) */}
        <div style={sec("green")}>
          <div>
            <div style={secTitle("green")}>
              🧭 {L("3. Évaluation de la performance — narratifs analytiques", "3. Assessment of performance — analytical narratives")}
            </div>
            <div className="text-faint" style={{ fontSize: 11.5, marginBottom: 10 }}>
              {L("Un narratif par pilier et, si besoin, par indicateur. Dans l'export, ils précèdent les notes et justifications des dimensions (générées depuis l'onglet Notation). Le plan d'action est inclus en section 6.",
                 "One narrative per pillar and, if needed, per indicator. In the export they precede the dimension scores and justifications (generated from the Scoring tab). The action plan is included as section 6.")}
            </div>
            {(() => {
              const cat = window.PEFA_CATALOG;
              if (!cat) return null;
              const mods = assessment.modules || {};
              const groups = cat.pillars.map((p) => ({
                key: "p" + p.num,
                title: L("Pilier ", "Pillar ") + ROMAN[p.num - 1] + " · " + ((lang === "es" ? (p.es != null ? p.es : p.en) : lang === "fr" ? p.fr : p.en)),
                inds: cat.indicators.filter((i) => i.pillar === p.num),
              }));
              if (mods.gender)  groups.push({ key: "gender",  title: L("Module Genre — GRPFM", "Gender module — GRPFM"), inds: cat.gender });
              if (mods.climate) groups.push({ key: "climate", title: L("Module Climat — CRPFM", "Climate module — CRPFM"), inds: cat.climate });
              const pn = report.pillar_notes || {};
              const inN = report.indicator_notes || {};
              return groups.map((g) => {
                const open = !!openNarr[g.key];
                const filled = (pn[g.key] ? 1 : 0) + g.inds.filter((i) => inN[i.code]).length;
                return (
                  <div key={g.key} style={{ borderTop: "1px dashed var(--line)", padding: "6px 0" }}>
                    <div onClick={() => setOpenNarr({ ...openNarr, [g.key]: !open })}
                      style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }}>
                      <Icon.chevronDown style={{ transform: open ? "rotate(0deg)" : "rotate(-90deg)", transition: "transform .15s", opacity: 0.6 }} />
                      <span style={{ fontSize: 12.5, fontWeight: 700, flex: 1 }}>{g.title}</span>
                      <span className="text-faint" style={{ fontSize: 10.5 }}>
                        {filled} {L("narratif(s)", "narrative(s)")}
                      </span>
                    </div>
                    {open && (
                      <div style={{ paddingLeft: 22, marginTop: 6 }}>
                        <label style={{ fontSize: 11, fontWeight: 600, display: "block", marginBottom: 2 }}>
                          {L("Narratif du pilier", "Pillar narrative")}
                        </label>
                        <textarea className="input" rows={3} disabled={!canManage}
                          style={{ width: "100%", resize: "vertical", fontSize: 12, marginBottom: 8 }}
                          value={pn[g.key] || ""}
                          onChange={(e) => {
                            setReport({ ...report, pillar_notes: { ...pn, [g.key]: e.target.value } });
                            setDirty(true);
                          }} />
                        {g.inds.map((ind) => (
                          <div key={ind.code} style={{ marginBottom: 6 }}>
                            <label style={{ fontSize: 11, fontWeight: 600, display: "block", marginBottom: 2 }}>
                              {ind.code} — {(lang === "es" ? (ind.es != null ? ind.es : ind.en) : lang === "fr" ? ind.fr : ind.en)}
                            </label>
                            <textarea className="input" rows={2} disabled={!canManage}
                              placeholder={L("Narratif analytique (optionnel)…", "Analytical narrative (optional)…")}
                              style={{ width: "100%", resize: "vertical", fontSize: 12 }}
                              value={inN[ind.code] || ""}
                              onChange={(e) => {
                                setReport({ ...report, indicator_notes: { ...inN, [ind.code]: e.target.value } });
                                setDirty(true);
                              }} />
                          </div>
                        ))}
                      </div>
                    )}
                  </div>
                );
              });
            })()}
          </div>
        </div>

        {/* Charte PEFA Check (purple = qualité / synthèse) */}
        <div style={sec("purple")}>
          <div>
            <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
              <div style={secTitle("purple")}>✅ {L("Charte d'assurance qualité — PEFA Check", "Quality assurance charter — PEFA Check")}</div>
              <span style={{
                fontSize: 11, fontWeight: 700, padding: "2px 10px", borderRadius: 10,
                background: doneCount === 8 ? "#dcfce7" : "#fef3c7",
                color: doneCount === 8 ? "#15803d" : "#92400e",
              }}>{doneCount}/8</span>
            </div>
            <div className="text-faint" style={{ fontSize: 11.5, marginBottom: 10 }}>
              {L("Les 8 engagements du responsable de l'évaluation (modèle officiel de charte, instructions PEFA Check).",
                 "The 8 commitments of the assessment manager (official charter template, PEFA Check instructions).")}
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10, marginBottom: 12 }}>
              <div>
                <label style={lbl}>{L("Financée par", "Funded by")}</label>
                <input className="input" disabled={!canManage} value={check.funded_by || ""}
                  onChange={(e) => { setCheck({ ...check, funded_by: e.target.value }); setDirty(true); }}
                  style={{ width: "100%", fontSize: 11.5 }} />
              </div>
              <div>
                <label style={lbl}>{L("Dirigée par", "Led by")}</label>
                <input className="input" disabled={!canManage} value={check.led_by || ""}
                  onChange={(e) => { setCheck({ ...check, led_by: e.target.value }); setDirty(true); }}
                  style={{ width: "100%", fontSize: 11.5 }} />
              </div>
              <div>
                <label style={lbl}>{L("Responsable de l'évaluation", "Assessment manager")}</label>
                <input className="input" disabled={!canManage} value={check.manager || ""}
                  onChange={(e) => { setCheck({ ...check, manager: e.target.value }); setDirty(true); }}
                  style={{ width: "100%", fontSize: 11.5 }} />
              </div>
            </div>
            {CHECK_ITEMS.map((it, idx) => {
              const st = check.items[it.key] || {};
              return (
                <div key={it.key} style={{ display: "grid", gridTemplateColumns: "24px 1fr 120px minmax(140px, 1fr)", gap: 8, alignItems: "center", padding: "6px 0", borderTop: "1px dashed var(--line)" }}>
                  <input type="checkbox" disabled={!canManage} checked={!!st.done}
                    onChange={(e) => setCheckItem(it.key, { done: e.target.checked })} />
                  <span style={{ fontSize: 12, textDecoration: st.done ? "none" : undefined, color: st.done ? "var(--text)" : "var(--text-muted)" }}>
                    {idx + 1}. {(lang === "es" ? (it.es != null ? it.es : it.en) : lang === "fr" ? it.fr : it.en)}
                  </span>
                  <input type="date" className="input" disabled={!canManage} value={st.date || ""}
                    onChange={(e) => setCheckItem(it.key, { date: e.target.value })}
                    style={{ fontSize: 11 }} />
                  <input className="input" disabled={!canManage} value={st.note || ""}
                    placeholder={L("Commentaire / référence", "Comment / reference")}
                    onChange={(e) => setCheckItem(it.key, { note: e.target.value })}
                    style={{ width: "100%", fontSize: 11 }} />
                </div>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  // ── Plan d'action (phase 3) ──────────────────────────────────────────────
  const ACTION_PRIORITIES = {
    high:   { fr: "Haute",   en: "High", es: "Alta",   color: "#b91c1c", fill: "#fee2e2" },
    medium: { fr: "Moyenne", en: "Medium", es: "Media", color: "#b45309", fill: "#fef3c7" },
    low:    { fr: "Basse",   en: "Low", es: "Baja",    color: "#15803d", fill: "#dcfce7" },
  };
  const ACTION_STATUSES = {
    todo:        { fr: "À faire",  en: "To do", es: "Por hacer",       color: "#6b7280", fill: "#f3f4f6" },
    in_progress: { fr: "En cours", en: "In progress", es: "En curso", color: "#1d4ed8", fill: "#dbeafe" },
    done:        { fr: "Réalisée", en: "Done", es: "Realizada",        color: "#15803d", fill: "#dcfce7" },
  };

  function PefaActionPlanTab({ lang, assessment, canManage }) {
    const L = (fr, en) => (lang === "fr" ? fr : en);
    const { data: items, refresh } = window.melr.usePefaActionItems
      ? window.melr.usePefaActionItems(assessment.id)
      : { data: [], refresh: () => {} };
    const { projects } = window.melr.useProjects ? window.melr.useProjects() : { projects: [] };
    const cat = window.PEFA_CATALOG;
    const inds = catalogIndicators(assessment);

    const [pillar, setPillar] = useState("");
    const [indCode, setIndCode] = useState("");
    const [reco, setReco] = useState("");
    const [priority, setPriority] = useState("medium");
    const [responsible, setResponsible] = useState("");
    const [dueDate, setDueDate] = useState("");
    const [projectId, setProjectId] = useState("");
    const [busy, setBusy] = useState(false);
    const [msg, setMsg] = useState(null);

    const addItem = async () => {
      if (!reco.trim()) { setMsg(L("La recommandation est requise.", "Recommendation is required.")); return; }
      setBusy(true); setMsg(null);
      try {
        await window.melr.pefaActionItemsCrud.create({
          assessment_id: assessment.id,
          pillar: pillar ? Number(pillar) : null,
          indicator_code: indCode || null,
          recommendation_fr: reco.trim(),
          priority, responsible: responsible || null,
          due_date: dueDate || null,
          linked_project_id: projectId || null,
        });
        setReco(""); setResponsible(""); setDueDate(""); setProjectId("");
        refresh && refresh();
      } catch (e) { setMsg(L("Erreur : ", "Error: ") + e.message); }
      finally { setBusy(false); }
    };
    const setStatus = async (item, status) => {
      try { await window.melr.pefaActionItemsCrud.update(item.id, { status }); refresh && refresh(); }
      catch (e) { setMsg(L("Erreur : ", "Error: ") + e.message); }
    };
    const removeItem = async (item) => {
      if (!window.confirm(L("Supprimer cette recommandation ?", "Delete this recommendation?"))) return;
      try { await window.melr.pefaActionItemsCrud.remove(item.id); refresh && refresh(); }
      catch (e) { setMsg(L("Erreur : ", "Error: ") + e.message); }
    };
    const lbl = { fontSize: 11, fontWeight: 600, display: "block", marginBottom: 2 };

    // Exports tabulaires du plan d'action (helpers MELR existants).
    const exportCols = [
      { label: L("Pilier", "Pillar"), value: (r) => (r.pillar ? ROMAN[r.pillar - 1] : L("Transversal", "Cross-cutting")) },
      { label: L("Indicateur", "Indicator"), value: (r) => r.indicator_code || "" },
      { label: L("Recommandation", "Recommendation"), value: (r) => ((lang === "es" ? (r.recommendation_es != null ? r.recommendation_es : r.recommendation_en || r.recommendation_fr) : lang === "fr" ? r.recommendation_fr : r.recommendation_en || r.recommendation_fr)) },
      { label: L("Priorité", "Priority"), value: (r) => { const m = ACTION_PRIORITIES[r.priority]; return m ? ((lang === "es" ? (m.es != null ? m.es : m.en) : lang === "fr" ? m.fr : m.en)) : (r.priority || ""); } },
      { label: L("Responsable", "Responsible"), value: (r) => r.responsible || "" },
      { label: L("Échéance", "Due date"), value: (r) => r.due_date || "" },
      { label: L("Projet", "Project"), value: (r) => (r.projects ? r.projects.code : "") },
      { label: L("Statut", "Status"), value: (r) => { const m = ACTION_STATUSES[r.status]; return m ? ((lang === "es" ? (m.es != null ? m.es : m.en) : lang === "fr" ? m.fr : m.en)) : (r.status || ""); } },
    ];
    const exportTitle = L("Plan d'action PEFA — ", "PEFA action plan — ")
      + ((lang === "es" ? (assessment.evaluation.title_es != null ? assessment.evaluation.title_es : assessment.evaluation.title_en || assessment.evaluation.title_fr) : lang === "fr" ? assessment.evaluation.title_fr : assessment.evaluation.title_en || assessment.evaluation.title_fr));
    const xb = (bg) => (window.MelrSection ? window.MelrSection.btn(bg) : {});

    return (
      <div>
        {/* Barre d'export (convention : Excel vert, Word bleu, PDF rouge) */}
        <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginBottom: 10 }}>
          <button style={xb("#15803d")} onClick={() => window.melr.exportRowsExcel("PEFA_plan_action.xlsx", items || [], exportCols, "Plan d'action")}>
            📈 Excel
          </button>
          <button style={xb("#2563eb")} onClick={() => window.melr.exportRowsWord("PEFA_plan_action.docx", exportTitle, items || [], exportCols)}>
            📄 Word
          </button>
          <button style={xb("#dc2626")} onClick={() => window.melr.exportRowsPdf("PEFA_plan_action.pdf", exportTitle, items || [], exportCols)}>
            📕 PDF
          </button>
        </div>
        {canManage && (
          <div style={sec("amber")}>
            <div>
              <div style={secTitle("amber")}>➕ {L("Nouvelle recommandation", "New recommendation")}</div>
              <div style={{ display: "grid", gridTemplateColumns: "130px 130px 1fr", gap: 8, marginBottom: 8 }}>
                <div>
                  <label style={lbl}>{L("Pilier", "Pillar")}</label>
                  <select className="input" value={pillar} onChange={(e) => setPillar(e.target.value)} style={{ width: "100%", fontSize: 11.5 }}>
                    <option value="">{L("— Transversal —", "— Cross-cutting —")}</option>
                    {(cat ? cat.pillars : []).map((p) => (
                      <option key={p.num} value={p.num}>{ROMAN[p.num - 1]} · {(lang === "es" ? (p.es != null ? p.es : p.en) : lang === "fr" ? p.fr : p.en)}</option>
                    ))}
                  </select>
                </div>
                <div>
                  <label style={lbl}>{L("Indicateur", "Indicator")}</label>
                  <select className="input" value={indCode} onChange={(e) => setIndCode(e.target.value)} style={{ width: "100%", fontSize: 11.5 }}>
                    <option value="">—</option>
                    {inds.map((i) => <option key={i.code} value={i.code}>{i.code}</option>)}
                  </select>
                </div>
                <div>
                  <label style={lbl}>{L("Recommandation *", "Recommendation *")}</label>
                  <input className="input" value={reco} onChange={(e) => setReco(e.target.value)} style={{ width: "100%", fontSize: 11.5 }} />
                </div>
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "120px 1fr 140px 1fr auto", gap: 8, alignItems: "end" }}>
                <div>
                  <label style={lbl}>{L("Priorité", "Priority")}</label>
                  <select className="input" value={priority} onChange={(e) => setPriority(e.target.value)} style={{ width: "100%", fontSize: 11.5 }}>
                    {Object.keys(ACTION_PRIORITIES).map((k) => (
                      <option key={k} value={k}>{(lang === "es" ? (ACTION_PRIORITIES[k].es != null ? ACTION_PRIORITIES[k].es : ACTION_PRIORITIES[k].en) : lang === "fr" ? ACTION_PRIORITIES[k].fr : ACTION_PRIORITIES[k].en)}</option>
                    ))}
                  </select>
                </div>
                <div>
                  <label style={lbl}>{L("Responsable", "Responsible")}</label>
                  <input className="input" value={responsible} onChange={(e) => setResponsible(e.target.value)} style={{ width: "100%", fontSize: 11.5 }} />
                </div>
                <div>
                  <label style={lbl}>{L("Échéance", "Due date")}</label>
                  <input type="date" className="input" value={dueDate} onChange={(e) => setDueDate(e.target.value)} style={{ width: "100%", fontSize: 11.5 }} />
                </div>
                <div>
                  <label style={lbl}>{L("Projet MELR lié", "Linked MELR project")}</label>
                  {/* useProjects() remappe : uuid = UUID (FK), id = CODE, nameFr/nameEn. */}
                  <select className="input" value={projectId} onChange={(e) => setProjectId(e.target.value)} style={{ width: "100%", fontSize: 11.5 }}>
                    <option value="">—</option>
                    {(projects || []).map((p) => (
                      <option key={p.uuid} value={p.uuid}>{p.id} · {(lang === "es" ? (p.nameEs != null ? p.nameEs : p.nameEn || p.nameFr) : lang === "fr" ? p.nameFr : p.nameEn || p.nameFr)}</option>
                    ))}
                  </select>
                </div>
                <button className="btn sm primary" disabled={busy} onClick={addItem}>
                  {busy ? "…" : L("Ajouter", "Add")}
                </button>
              </div>
              {msg && <div style={{ fontSize: 11.5, color: "#b91c1c", marginTop: 6 }}>{msg}</div>}
            </div>
          </div>
        )}

        <div style={sec("green")}>
          <div>
            <table className="tbl" style={{ width: "100%" }}>
              <thead><tr>
                <th>{L("Pilier", "Pillar")}</th><th>{L("Indicateur", "Indicator")}</th>
                <th>{L("Recommandation", "Recommendation")}</th><th>{L("Priorité", "Priority")}</th>
                <th>{L("Responsable", "Responsible")}</th><th>{L("Échéance", "Due")}</th>
                <th>{L("Projet", "Project")}</th><th>{L("Statut", "Status")}</th><th></th>
              </tr></thead>
              <tbody>
                {(items || []).length === 0 && (
                  <tr><td colSpan={9} className="text-faint" style={{ padding: 14, fontSize: 11.5 }}>
                    {L("Aucune recommandation pour le moment. Le plan d'action alimente le dialogue sur les réformes (phase 4 du cycle PEFA).",
                       "No recommendation yet. The action plan feeds the PFM reform dialogue (phase 4 of the PEFA cycle).")}
                  </td></tr>
                )}
                {(items || []).map((it) => {
                  const pm = ACTION_PRIORITIES[it.priority] || ACTION_PRIORITIES.medium;
                  const sm = ACTION_STATUSES[it.status] || ACTION_STATUSES.todo;
                  return (
                    <tr key={it.id}>
                      <td style={{ fontSize: 11.5 }}>{it.pillar ? ROMAN[it.pillar - 1] : L("Transv.", "Cross")}</td>
                      <td style={{ fontSize: 11.5, fontWeight: 700 }}>{it.indicator_code || "—"}</td>
                      <td style={{ fontSize: 12 }}>{(lang === "es" ? (it.recommendation_es != null ? it.recommendation_es : it.recommendation_en || it.recommendation_fr) : lang === "fr" ? it.recommendation_fr : it.recommendation_en || it.recommendation_fr)}</td>
                      <td><span style={{ fontSize: 10.5, fontWeight: 700, padding: "1px 8px", borderRadius: 999, background: pm.fill, color: pm.color }}>
                        {(lang === "es" ? (pm.es != null ? pm.es : pm.en) : lang === "fr" ? pm.fr : pm.en)}</span></td>
                      <td style={{ fontSize: 11.5 }}>{it.responsible || "—"}</td>
                      <td style={{ fontSize: 11.5 }}>{it.due_date || "—"}</td>
                      <td style={{ fontSize: 11.5 }}>{it.projects ? it.projects.code : "—"}</td>
                      <td>
                        {canManage
                          ? <select className="input" value={it.status} onChange={(e) => setStatus(it, e.target.value)}
                              style={{ fontSize: 11, fontWeight: 700, background: sm.fill, color: sm.color }}>
                              {Object.keys(ACTION_STATUSES).map((k) => (
                                <option key={k} value={k}>{(lang === "es" ? (ACTION_STATUSES[k].es != null ? ACTION_STATUSES[k].es : ACTION_STATUSES[k].en) : lang === "fr" ? ACTION_STATUSES[k].fr : ACTION_STATUSES[k].en)}</option>
                              ))}
                            </select>
                          : <span style={{ fontSize: 10.5, fontWeight: 700, padding: "1px 8px", borderRadius: 999, background: sm.fill, color: sm.color }}>
                              {(lang === "es" ? (sm.es != null ? sm.es : sm.en) : lang === "fr" ? sm.fr : sm.en)}</span>}
                      </td>
                      <td style={{ textAlign: "right" }}>
                        {canManage && (
                          <button className="btn xs ghost" onClick={() => removeItem(it)} title={L("Supprimer", "Delete")}>
                            <Icon.x />
                          </button>
                        )}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
      </div>
    );
  }

  // ── Comparaison temporelle (phase 3) ─────────────────────────────────────
  // Deux évaluations du MÊME cadre 2016, indicateur par indicateur, avec
  // sens d'évolution. (PEFA n'admet pas la comparaison entre cadres.)
  const SCORE_RANK = { "A": 7, "B+": 6, "B": 5, "C+": 4, "C": 3, "D+": 2, "D": 1, "D*": 1 };

  function PefaComparison({ lang, assessments }) {
    const L = (fr, en) => (lang === "fr" ? fr : en);
    const eligible = (assessments || []).filter((a) => (a.framework_version || "2016") === "2016");
    const [idA, setIdA] = useState("");
    const [idB, setIdB] = useState("");
    const a = eligible.find((x) => x.id === idA) || eligible[1] || null;
    const b = eligible.find((x) => x.id === idB) || eligible[0] || null;
    const { data: sA } = window.melr.usePefaScores ? window.melr.usePefaScores(a ? a.id : null) : { data: [] };
    const { data: sB } = window.melr.usePefaScores ? window.melr.usePefaScores(b ? b.id : null) : { data: [] };
    const mkKey = (rows) => {
      const m = {};
      (rows || []).forEach((r) => { m[r.indicator_code + "|" + (r.dimension_code || "")] = r; });
      return m;
    };
    const kA = useMemo(() => mkKey(sA), [sA]);
    const kB = useMemo(() => mkKey(sB), [sB]);
    if (eligible.length < 2) return null;
    const cat = window.PEFA_CATALOG;
    if (!cat) return null;
    const titleOf = (x) => x ? ((lang === "es" ? (x.evaluation.title_es != null ? x.evaluation.title_es : x.evaluation.title_en || x.evaluation.title_fr) : lang === "fr" ? x.evaluation.title_fr : x.evaluation.title_en || x.evaluation.title_fr)) : "—";
    return (
      <div style={sec("purple", { marginTop: 14 })}>
        <div>
          <div style={secTitle("purple")}>
            ⇄ {L("Comparaison temporelle (cadre 2016)", "Time comparison (2016 framework)")}
          </div>
          <div style={{ display: "flex", gap: 8, alignItems: "center", marginBottom: 10, flexWrap: "wrap" }}>
            <select className="input" value={a ? a.id : ""} onChange={(e) => setIdA(e.target.value)} style={{ fontSize: 11.5 }}>
              {eligible.map((x) => <option key={x.id} value={x.id}>{titleOf(x)}</option>)}
            </select>
            <span style={{ fontSize: 12 }}>→</span>
            <select className="input" value={b ? b.id : ""} onChange={(e) => setIdB(e.target.value)} style={{ fontSize: 11.5 }}>
              {eligible.map((x) => <option key={x.id} value={x.id}>{titleOf(x)}</option>)}
            </select>
          </div>
          <table className="tbl" style={{ width: "100%" }}>
            <thead><tr>
              <th>{L("Indicateur", "Indicator")}</th>
              <th style={{ textAlign: "center" }}>{titleOf(a)}</th>
              <th style={{ textAlign: "center" }}>{titleOf(b)}</th>
              <th style={{ textAlign: "center" }}>Δ</th>
            </tr></thead>
            <tbody>
              {cat.indicators.map((ind) => {
                const va = finalIndicatorScore(ind, kA);
                const vb = finalIndicatorScore(ind, kB);
                let delta = null;
                if (va && vb && SCORE_RANK[va] != null && SCORE_RANK[vb] != null) {
                  delta = SCORE_RANK[vb] - SCORE_RANK[va];
                }
                return (
                  <tr key={ind.code}>
                    <td style={{ fontSize: 11.5 }}>
                      <span style={{ fontWeight: 700, marginRight: 6 }}>{ind.code}</span>
                      <span className="text-faint">{(lang === "es" ? (ind.es != null ? ind.es : ind.en) : lang === "fr" ? ind.fr : ind.en)}</span>
                    </td>
                    <td style={{ textAlign: "center" }}><ScorePill score={va} /></td>
                    <td style={{ textAlign: "center" }}><ScorePill score={vb} /></td>
                    <td style={{ textAlign: "center", fontWeight: 800, fontSize: 12.5,
                      color: delta == null ? "var(--text-faint)" : delta > 0 ? "#15803d" : delta < 0 ? "#b91c1c" : "var(--text-muted)" }}>
                      {delta == null ? "—" : delta > 0 ? "↑ +" + delta : delta < 0 ? "↓ " + delta : "="}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
          <div className="text-faint" style={{ fontSize: 10.5, marginTop: 8 }}>
            {L("Δ exprimé en crans de note (D=1 … A=7). Seules les évaluations menées sous le cadre 2016 sont comparables.",
               "Δ expressed in score steps (D=1 … A=7). Only assessments under the 2016 framework are comparable.")}
          </div>
        </div>
      </div>
    );
  }

  // ── Écran principal ──────────────────────────────────────────────────────
  function PefaScreen({ lang, setRoute, effectiveOrgId, isSuperAdmin, hasPerm, activeSection }) {
    const { data: assessments, loading, refresh } = window.melr.usePefaAssessments
      ? window.melr.usePefaAssessments(effectiveOrgId)
      : { data: [], loading: false, refresh: () => {} };

    const [tab, setTab] = useState(activeSection || "tab:overview");
    useEffect(() => { if (activeSection) setTab(activeSection); }, [activeSection]);

    const [selectedId, setSelectedId] = useState(() => {
      try { return localStorage.getItem("melr.pefa.selected") || null; } catch (_) { return null; }
    });
    const selected = useMemo(
      () => (assessments || []).find((a) => a.id === selectedId) || (assessments || [])[0] || null,
      [assessments, selectedId]
    );
    useEffect(() => {
      if (selected && selected.id !== selectedId) {
        setSelectedId(selected.id);
        try { localStorage.setItem("melr.pefa.selected", selected.id); } catch (_) {}
      }
    }, [selected, selectedId]);

    // Note conceptuelle de l'évaluation sélectionnée
    const [note, setNote] = useState(null);
    const [noteLoading, setNoteLoading] = useState(false);
    const [sections, setSections] = useState({});
    const [dirty, setDirty] = useState(false);
    const [busy, setBusy] = useState(false);
    const [msg, setMsg] = useState(null);
    useEffect(() => {
      let cancelled = false;
      setNote(null); setSections({}); setDirty(false); setMsg(null);
      if (!selected) return;
      setNoteLoading(true);
      window.melr.fetchPefaConceptNote(selected.id)
        .then((n) => { if (!cancelled) { setNote(n); setSections((n && n.sections) || {}); } })
        .catch((e) => { if (!cancelled) setMsg(e.message); })
        .finally(() => { if (!cancelled) setNoteLoading(false); });
      return () => { cancelled = true; };
    }, [selected && selected.id]);

    const [newOpen, setNewOpen] = useState(false);
    const canManage = isSuperAdmin
      || (typeof hasPerm === "function" && (hasPerm("users.manage") || hasPerm("evaluations.manage")));
    const L = (fr, en) => (lang === "fr" ? fr : en);

    const saveSections = async () => {
      if (!selected) return;
      setBusy(true); setMsg(null);
      try {
        const n = await window.melr.savePefaConceptNote(selected.id, sections);
        setNote(n); setDirty(false);
        setMsg(L("Note conceptuelle enregistrée.", "Concept note saved."));
      } catch (e) { setMsg(L("Erreur : ", "Error: ") + e.message); }
      finally { setBusy(false); }
    };

    const changeStatus = async (status) => {
      if (!note) {
        // Pas encore de ligne : on enregistre d'abord le contenu courant.
        try {
          const n = await window.melr.savePefaConceptNote(selected.id, sections);
          setNote(n);
          const n2 = await window.melr.setPefaConceptNoteStatus(n.id, status);
          setNote(n2);
        } catch (e) { setMsg(L("Erreur : ", "Error: ") + e.message); }
        return;
      }
      setBusy(true); setMsg(null);
      try {
        const n = await window.melr.setPefaConceptNoteStatus(note.id, status);
        setNote(n);
      } catch (e) { setMsg(L("Erreur : ", "Error: ") + e.message); }
      finally { setBusy(false); }
    };

    const toggleModule = async (key, value) => {
      if (!selected || !canManage) return;
      try {
        const modules = { ...(selected.modules || {}), [key]: value };
        await window.melr.updatePefaAssessment(selected.id, { modules });
        refresh && refresh();
      } catch (e) { setMsg(L("Erreur : ", "Error: ") + e.message); }
    };

    const statusIdx = note ? CN_STATUSES.indexOf(note.status) : 0;

    return (
      <div className="page" data-screen-label="PEFA">
        <div className="page-header">
          <div className="page-eyebrow">{L("ÉVALUATIONS · PEFA", "EVALUATIONS · PEFA")}</div>
          <div className="page-header-row">
            <div>
              <h1 className="page-title">
                {L("Évaluation PEFA", "PEFA assessment")}
                {selected && <span style={{ fontWeight: 500, color: "var(--text-faint)" }}>
                  {" — "}{(lang === "es" ? (selected.evaluation.title_es != null ? selected.evaluation.title_es : selected.evaluation.title_en || selected.evaluation.title_fr) : lang === "fr" ? selected.evaluation.title_fr : selected.evaluation.title_en || selected.evaluation.title_fr)}
                </span>}
              </h1>
              <div className="page-sub">
                {L("Dépense publique et responsabilité financière — cadre 2016, modules Genre (GRPFM 2020) et Climat (CRPFM 2024).",
                   "Public Expenditure and Financial Accountability — 2016 framework, Gender (GRPFM 2020) and Climate (CRPFM 2024) modules.")}
              </div>
            </div>
            <div className="page-header-actions">
              {canManage && (
                <button className="btn sm primary" onClick={() => setNewOpen(true)}>
                  <Icon.plus /> {L("Nouvelle évaluation PEFA", "New PEFA assessment")}
                </button>
              )}
            </div>
          </div>
        </div>

        {/* Sélecteur d'évaluation + onglets */}
        {(assessments || []).length > 1 && (
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }}>
            <span className="text-faint" style={{ fontSize: 11.5 }}>{L("Évaluation :", "Assessment:")}</span>
            <select className="input" value={selected ? selected.id : ""}
              onChange={(e) => {
                setSelectedId(e.target.value);
                try { localStorage.setItem("melr.pefa.selected", e.target.value); } catch (_) {}
              }}>
              {(assessments || []).map((a) => (
                <option key={a.id} value={a.id}>
                  {(lang === "es" ? (a.evaluation.title_es != null ? a.evaluation.title_es : a.evaluation.title_en || a.evaluation.title_fr) : lang === "fr" ? a.evaluation.title_fr : a.evaluation.title_en || a.evaluation.title_fr)}
                </option>
              ))}
            </select>
          </div>
        )}
        <div className="pd-tabs" style={{ marginBottom: 16 }}>
          {TABS.map((tb) => (
            <button key={tb.key}
              className={"pd-tab" + (tab === tb.key ? " active" : "")}
              onClick={() => setTab(tb.key)}
              data-hue={tb.hue}
              style={{ "--tab-hue": tb.hue }}>
              {(lang === "es" ? (tb.es != null ? tb.es : tb.en) : lang === "fr" ? tb.fr : tb.en)}
            </button>
          ))}
        </div>

        {/* ═══ Vue d'ensemble ═══ */}
        {tab === "tab:overview" && (
          <>
          <div style={sec("green")}>
            <div>
              <table className="tbl" style={{ width: "100%" }}>
                <thead>
                  <tr>
                    <th>{L("Titre", "Title")}</th>
                    <th>{L("Niveau", "Level")}</th>
                    <th>{L("Cadre", "Framework")}</th>
                    <th>{L("Exercices", "Fiscal years")}</th>
                    <th>{L("Modules", "Modules")}</th>
                    <th>{L("Statut", "Status")}</th>
                    <th></th>
                  </tr>
                </thead>
                <tbody>
                  {loading && <tr><td colSpan={7} style={{ padding: 18, color: "var(--text-faint)" }}>{L("Chargement…", "Loading…")}</td></tr>}
                  {!loading && (assessments || []).length === 0 && (
                    <tr><td colSpan={7} style={{ padding: 18, color: "var(--text-faint)" }}>
                      {L("Aucune évaluation PEFA. La première étape de la procédure est la note conceptuelle — créez une évaluation pour commencer.",
                         "No PEFA assessment yet. The first step of the process is the concept note — create an assessment to get started.")}
                    </td></tr>
                  )}
                  {(assessments || []).map((a) => {
                    const ev = a.evaluation || {};
                    const sm = (window.EVALUATION_STATUS_META || {})[ev.status] || { fr: ev.status, en: ev.status, color: "#6b7280", fill: "#f3f4f6" };
                    return (
                      <tr key={a.id}
                        style={{ background: selected && selected.id === a.id ? "var(--bg-sunken)" : undefined }}>
                        <td style={{ fontWeight: 600, fontSize: 12.5 }}>
                          {(lang === "es" ? (ev.title_es != null ? ev.title_es : ev.title_en || ev.title_fr) : lang === "fr" ? ev.title_fr : ev.title_en || ev.title_fr)}
                        </td>
                        <td style={{ fontSize: 12 }}>
                          {a.government_level === "subnational" ? L("Infranational", "Subnational") : "National"}
                        </td>
                        <td style={{ fontSize: 12 }}>
                          <span className="pill" style={{ fontSize: 10.5 }}>{a.framework_version || "2016"}</span>
                        </td>
                        <td style={{ fontSize: 12 }}>{((a.scope || {}).fiscal_years || []).join(", ") || "—"}</td>
                        <td><ModuleChips modules={a.modules} lang={lang} /></td>
                        <td>
                          <span style={{ display: "inline-block", padding: "2px 8px", borderRadius: 10, background: sm.fill, color: sm.color, fontSize: 11, fontWeight: 600 }}>
                            {(lang === "es" ? (sm.es != null ? sm.es : sm.en) : lang === "fr" ? sm.fr : sm.en)}
                          </span>
                        </td>
                        <td style={{ textAlign: "right", paddingRight: 12 }}>
                          <button className="btn xs ghost" onClick={() => {
                            setSelectedId(a.id);
                            try { localStorage.setItem("melr.pefa.selected", a.id); } catch (_) {}
                            setTab("tab:cn");
                          }}>
                            {L("Ouvrir", "Open")} →
                          </button>
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          </div>
          {/* Comparaison entre deux évaluations 2016 (masquée s'il n'y en a qu'une) */}
          <PefaComparison lang={lang} assessments={assessments} />
          </>
        )}

        {/* ═══ Note conceptuelle ═══ */}
        {tab === "tab:cn" && !selected && (
          <div className="card"><div className="card-body" style={{ padding: 22, color: "var(--text-faint)", fontSize: 12.5 }}>
            {L("Créez d'abord une évaluation PEFA (bouton en haut à droite).", "Create a PEFA assessment first (top-right button).")}
          </div></div>
        )}
        {tab === "tab:cn" && selected && (
          <div>
            {/* Workflow de statut (amber = workflow / outils) */}
            <div style={sec("amber")}>
              <div>
                <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 10 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
                    <span style={{ fontSize: 12.5, fontWeight: 700 }}>
                      {L("Statut de la note conceptuelle :", "Concept note status:")}
                    </span>
                    <CnStatusBadge status={note ? note.status : "draft"} lang={lang} />
                    {note && <span className="text-faint" style={{ fontSize: 11 }}>v{note.version}</span>}
                    {noteLoading && <span className="text-faint" style={{ fontSize: 11 }}>{L("Chargement…", "Loading…")}</span>}
                  </div>
                  <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
                    {canManage && statusIdx > 0 && (note ? note.status : "draft") !== "draft" && (
                      <button className="btn xs ghost" disabled={busy}
                        onClick={() => changeStatus(CN_STATUSES[statusIdx - 1])}>
                        ← {(lang === "es" ? (CN_STATUS_META[CN_STATUSES[statusIdx - 1]].es != null ? CN_STATUS_META[CN_STATUSES[statusIdx - 1]].es : CN_STATUS_META[CN_STATUSES[statusIdx - 1]].en) : lang === "fr" ? CN_STATUS_META[CN_STATUSES[statusIdx - 1]].fr : CN_STATUS_META[CN_STATUSES[statusIdx - 1]].en)}
                      </button>
                    )}
                    {canManage && statusIdx < CN_STATUSES.length - 1 && (
                      <button className="btn xs primary" disabled={busy}
                        onClick={() => changeStatus(CN_STATUSES[statusIdx + 1])}>
                        {(lang === "es" ? (CN_STATUS_META[CN_STATUSES[statusIdx + 1]].es != null ? CN_STATUS_META[CN_STATUSES[statusIdx + 1]].es : CN_STATUS_META[CN_STATUSES[statusIdx + 1]].en) : lang === "fr" ? CN_STATUS_META[CN_STATUSES[statusIdx + 1]].fr : CN_STATUS_META[CN_STATUSES[statusIdx + 1]].en)} →
                      </button>
                    )}
                    <button style={xbtn("#2563eb")} onClick={() => exportConceptNoteWord(lang, selected, { ...(note || {}), sections })}>
                      📄 Word
                    </button>
                  </div>
                </div>
                {note && note.status === "approved" && (
                  <div className="text-faint" style={{ fontSize: 11.5, marginTop: 8 }}>
                    {L("Version approuvée et figée. Toute modification du contenu ouvrira automatiquement une nouvelle version en brouillon.",
                       "Approved and frozen version. Any content change will automatically open a new draft version.")}
                  </div>
                )}
              </div>
            </div>

            <CnWarningBanner lang={lang} note={note} />

            {/* Modules complémentaires (section 4 du modèle) */}
            <div style={sec("amber")}>
              <div style={{ display: "flex", gap: 18, alignItems: "center", flexWrap: "wrap" }}>
                <span style={{ fontSize: 12.5, fontWeight: 700 }}>{L("Ensemble d'indicateurs :", "Indicator set:")}</span>
                <span style={{ fontSize: 12 }}>{L("PEFA 2016 — 31 indicateurs (toujours inclus)", "PEFA 2016 — 31 indicators (always included)")}</span>
                <label style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 12 }}>
                  <input type="checkbox" disabled={!canManage}
                    checked={!!(selected.modules || {}).gender}
                    onChange={(e) => toggleModule("gender", e.target.checked)} />
                  {L("Genre — GRPFM (9 ind.)", "Gender — GRPFM (9 ind.)")}
                </label>
                <label style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 12 }}>
                  <input type="checkbox" disabled={!canManage}
                    checked={!!(selected.modules || {}).climate}
                    onChange={(e) => toggleModule("climate", e.target.checked)} />
                  {L("Climat — CRPFM (14 ind.)", "Climate — CRPFM (14 ind.)")}
                </label>
              </div>
            </div>

            {/* Les 9 sections (green = saisie principale) */}
            {CN_SECTIONS.map((s) => (
              <div key={s.key} style={sec("green", { marginBottom: 12 })}>
                <div>
                  <div style={{ fontSize: 13, fontWeight: 700, marginBottom: 2 }}>
                    {(lang === "es" ? (s.es != null ? s.es : s.en) : lang === "fr" ? s.fr : s.en)}
                  </div>
                  <div className="text-faint" style={{ fontSize: 11.5, marginBottom: 8 }}>
                    {(lang === "es" ? (s.hint_es != null ? s.hint_es : s.hint_en) : lang === "fr" ? s.hint_fr : s.hint_en)}
                  </div>
                  <textarea className="input" rows={4} style={{ width: "100%", resize: "vertical", fontSize: 12.5 }}
                    value={sections[s.key] || ""}
                    disabled={!canManage}
                    onChange={(e) => { setSections({ ...sections, [s.key]: e.target.value }); setDirty(true); }} />
                </div>
              </div>
            ))}

            <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 24 }}>
              {canManage && (
                <button className="btn sm primary" disabled={busy || !dirty} onClick={saveSections}>
                  {busy ? "…" : L("Enregistrer la note conceptuelle", "Save concept note")}
                </button>
              )}
              {dirty && <span style={{ fontSize: 11.5, color: "#b45309" }}>{L("Modifications non enregistrées", "Unsaved changes")}</span>}
              {msg && <span className="text-faint" style={{ fontSize: 11.5 }}>{msg}</span>}
            </div>
          </div>
        )}

        {/* ═══ Notation (phase 2) ═══ */}
        {tab === "tab:notation" && (
          <div>
            {selected && selected.framework_version && selected.framework_version !== "2016" && (
              <div style={{
                background: "#fef2f2", border: "1px solid #f87171", borderRadius: 8,
                padding: "10px 14px", marginBottom: 14, fontSize: 12.5, color: "#991b1b",
              }}>
                {L("Cette évaluation a été menée sous le cadre " + selected.framework_version + ". La grille ci-dessous suit le cadre 2016 : les notes de l'ancien cadre ne sont pas transposables (comparaison entre cadres non admise par PEFA).",
                   "This assessment was conducted under the " + selected.framework_version + " framework. The grid below follows the 2016 framework: scores from the old framework are not transposable (cross-framework comparison is not allowed by PEFA).")}
              </div>
            )}
            <CnWarningBanner lang={lang} note={note} />
            {!selected
              ? <div className="card"><div className="card-body" style={{ padding: 22, color: "var(--text-faint)", fontSize: 12.5 }}>
                  {L("Créez d'abord une évaluation PEFA (bouton en haut à droite).", "Create a PEFA assessment first (top-right button).")}
                </div></div>
              : <PefaScoringTab lang={lang} assessment={selected} canManage={canManage} />}
          </div>
        )}
        {/* ═══ Preuves & calculs (phase 2b) ═══ */}
        {tab === "tab:preuves" && (
          !selected
            ? <div className="card"><div className="card-body" style={{ padding: 22, color: "var(--text-faint)", fontSize: 12.5 }}>
                {L("Créez d'abord une évaluation PEFA (bouton en haut à droite).", "Create a PEFA assessment first (top-right button).")}
              </div></div>
            : <PefaEvidenceCalcTab lang={lang} assessment={selected} canManage={canManage} effectiveOrgId={effectiveOrgId} />
        )}
        {/* ═══ Rapport & qualité (phase 3) ═══ */}
        {tab === "tab:rapport" && (
          !selected
            ? <div className="card"><div className="card-body" style={{ padding: 22, color: "var(--text-faint)", fontSize: 12.5 }}>
                {L("Créez d'abord une évaluation PEFA (bouton en haut à droite).", "Create a PEFA assessment first (top-right button).")}
              </div></div>
            : <PefaReportTab lang={lang} assessment={selected} canManage={canManage} />
        )}
        {/* ═══ Plan d'action (phase 3) ═══ */}
        {tab === "tab:plan" && (
          !selected
            ? <div className="card"><div className="card-body" style={{ padding: 22, color: "var(--text-faint)", fontSize: 12.5 }}>
                {L("Créez d'abord une évaluation PEFA (bouton en haut à droite).", "Create a PEFA assessment first (top-right button).")}
              </div></div>
            : <PefaActionPlanTab lang={lang} assessment={selected} canManage={canManage} />
        )}

        {newOpen && window.NewPefaModal && (
          <window.NewPefaModal lang={lang} effectiveOrgId={effectiveOrgId}
            onClose={() => setNewOpen(false)}
            onCreated={(res) => {
              setNewOpen(false);
              setSelectedId(res.assessment.id);
              try { localStorage.setItem("melr.pefa.selected", res.assessment.id); } catch (_) {}
              refresh && refresh();
              setTab("tab:cn");
            }} />
        )}
      </div>
    );
  }

  window.PefaScreen = PefaScreen;
})();
