/* global React, Icon */
// ============================================================================
// REPORTING — live generator
// ----------------------------------------------------------------------------
// Picks a scope (all / programme / project) and a period (Q1..Q4 / S1..S2 /
// annual / custom), then renders a complete report with:
//   - executive KPI tiles
//   - per-project rollup
//   - indicator-by-indicator table with progress bars and status pills
// Exports: Print/PDF (window.print), Word (.doc HTML), CSV (Excel).
// Optional: save a row in `reports` so the audit trail is preserved.
// ============================================================================
const { useState: useStateR, useMemo: useMemoR, useEffect: useEffectR } = React;

// --------------------------------------------------------------------------
// Period helpers
// --------------------------------------------------------------------------
const PERIOD_PRESETS = [
  { k: "Q1", lf: "Q1 (Janv–Mars)", le: "Q1 (Jan–Mar)" },
  { k: "Q2", lf: "Q2 (Avr–Juin)",  le: "Q2 (Apr–Jun)" },
  { k: "Q3", lf: "Q3 (Juil–Sept)", le: "Q3 (Jul–Sep)" },
  { k: "Q4", lf: "Q4 (Oct–Déc)",   le: "Q4 (Oct–Dec)" },
  { k: "S1", lf: "S1 (Janv–Juin)", le: "H1 (Jan–Jun)" },
  { k: "S2", lf: "S2 (Juil–Déc)",  le: "H2 (Jul–Dec)" },
  { k: "Y",  lf: "Annuel",         le: "Annual" },
  { k: "CUSTOM", lf: "Personnalisé", le: "Custom" },
];

function periodFromPreset(year, preset, customStart, customEnd) {
  if (preset === "CUSTOM") return { start: customStart || (year + "-01-01"), end: customEnd || (year + "-12-31") };
  const ranges = {
    Q1: [year + "-01-01", year + "-03-31"],
    Q2: [year + "-04-01", year + "-06-30"],
    Q3: [year + "-07-01", year + "-09-30"],
    Q4: [year + "-10-01", year + "-12-31"],
    S1: [year + "-01-01", year + "-06-30"],
    S2: [year + "-07-01", year + "-12-31"],
    Y:  [year + "-01-01", year + "-12-31"],
  };
  const [s, e] = ranges[preset] || ranges.Y;
  return { start: s, end: e };
}
function periodLabel(year, preset, lang, customStart, customEnd) {
  if (preset === "CUSTOM") return (customStart || "?") + " → " + (customEnd || "?");
  const p = PERIOD_PRESETS.find((x) => x.k === preset);
  if (!p) return String(year);
  return (lang === "fr" ? p.lf : p.le) + " " + year;
}

// --------------------------------------------------------------------------
// Indicator helpers — pick the latest value within the period, compute
// progress = (value - baseline) / (target - baseline) clamped to [0, 1].
// --------------------------------------------------------------------------
function pickPeriodValue(ind, start, end) {
  const vals = ind.indicator_values || [];
  const inRange = vals.filter((v) => {
    const ps = v.period_start;
    return ps && ps >= start && ps <= end;
  });
  if (inRange.length === 0) return null;
  inRange.sort((a, b) => String(b.period_start).localeCompare(String(a.period_start)));
  return inRange[0];
}
function indicatorProgress(latest, baseline, target) {
  if (latest == null || baseline == null || target == null) return null;
  const b = Number(baseline), t = Number(target), v = Number(latest);
  if (!isFinite(b) || !isFinite(t) || !isFinite(v)) return null;
  if (t === b) return null;
  return Math.max(0, Math.min(1, (v - b) / (t - b)));
}
function indicatorStatus(progress) {
  if (progress == null) return "neutral";
  if (progress >= 0.9) return "ok";
  if (progress >= 0.6) return "amber";
  return "bad";
}
function statusLabel(s, lang) {
  return (lang === "fr"
    ? { ok: "Sur cible",    amber: "À surveiller", bad: "En retard", neutral: "—" }
    : { ok: "On target",    amber: "At risk",      bad: "Behind",    neutral: "—" })[s] || "—";
}
const STATUS_COLORS = {
  ok:      { fg: "#15803d", bg: "#dcfce7", border: "#86efac" },
  amber:   { fg: "#a16207", bg: "#fef3c7", border: "#fcd34d" },
  bad:     { fg: "#b91c1c", bg: "#fee2e2", border: "#fca5a5" },
  neutral: { fg: "#374151", bg: "#f3f4f6", border: "#d1d5db" },
  accent:  { fg: "#1e3a8a", bg: "#e0e7ff", border: "#a5b4fc" },
};

function fmtNum(v) {
  if (v == null || !isFinite(v)) return "—";
  const n = Number(v);
  return Math.abs(n) >= 1000 ? Math.round(n).toLocaleString() : Number(n.toFixed(2)).toString();
}
function fmtPct(v) {
  if (v == null || !isFinite(v)) return "—";
  return (Number(v) * 100).toFixed(0) + "%";
}

// --------------------------------------------------------------------------
// Compute the full report payload from raw projects + indicators arrays.
// --------------------------------------------------------------------------
function computeReportData({ scope, projects, indicators, period, lang }) {
  let scopedProjects;
  if (scope.mode === "all")            scopedProjects = projects;
  else if (scope.mode === "programme") scopedProjects = projects.filter((p) => p.programmeId === scope.id);
  else if (scope.mode === "project")   scopedProjects = projects.filter((p) => p.uuid === scope.id);
  else                                 scopedProjects = [];

  const projectUuids = new Set(scopedProjects.map((p) => p.uuid));
  const scopedIndicators = (indicators || []).filter((i) => projectUuids.has(i.project_id));

  const rows = scopedIndicators.map((ind) => {
    const v = pickPeriodValue(ind, period.start, period.end);
    const latest = v ? v.value : null;
    const progress = indicatorProgress(latest, ind.baseline, ind.target);
    const status = indicatorStatus(progress);
    return {
      id: ind.id,
      projectCode: (ind.projects && ind.projects.code) || "—",
      code: ind.code, level: ind.level,
      name: lang === "fr" ? (ind.name_fr || ind.name_en) : (ind.name_en || ind.name_fr),
      unit: ind.unit || "",
      baseline: ind.baseline, target: ind.target, latest,
      progress, status,
      measuredAt: v ? v.period_start : null,
      frequency: ind.frequency || "",
    };
  });

  const measured = rows.filter((r) => r.latest != null);
  const onTarget = rows.filter((r) => r.status === "ok").length;
  const atRisk   = rows.filter((r) => r.status === "amber").length;
  const behind   = rows.filter((r) => r.status === "bad").length;
  const avgProgress = measured.length > 0
    ? measured.reduce((s, r) => s + (r.progress || 0), 0) / measured.length
    : 0;

  const projectRows = scopedProjects.map((proj) => {
    const pr = rows.filter((r) => r.projectCode === proj.id);
    const measuredP = pr.filter((r) => r.latest != null);
    const onTargetP = pr.filter((r) => r.status === "ok").length;
    const avg = measuredP.length > 0 ? measuredP.reduce((s, r) => s + (r.progress || 0), 0) / measuredP.length : 0;
    return {
      uuid: proj.uuid, code: proj.id, name: lang === "fr" ? proj.nameFr : proj.nameEn,
      totalIndicators: pr.length, measured: measuredP.length, onTarget: onTargetP,
      avgProgress: avg,
      programmeCode: proj.programmeCode || "—", programmeName: proj.programmeName || "—",
      progress: proj.progress, status: proj.status,
      budget: proj.budget, disbursed: proj.disbursed, currency: proj.nativeCurrency,
    };
  });

  // Financial rollup (totals in EUR using convertToEur on raw native amounts)
  const conv = (window.melr && window.melr.convertToEur) ? window.melr.convertToEur : ((v) => v);
  let totalBudgetEur = 0, totalDisbursedEur = 0;
  scopedProjects.forEach((p) => {
    const ccy = p.nativeCurrency || "EUR";
    totalBudgetEur    += conv((p.budget    || 0) * 1_000_000, ccy);
    totalDisbursedEur += conv((p.disbursed || 0) * 1_000_000, ccy);
  });
  const financial = {
    totalBudgetEur, totalDisbursedEur,
    coverageRate: totalBudgetEur > 0 ? totalDisbursedEur / totalBudgetEur : 0,
  };

  // Sector rollup — group indicators by the parent project's sector
  const sectorMap = new Map();
  const projBySector = new Map(scopedProjects.map((p) => [p.uuid, p.sector || "other"]));
  rows.forEach((r) => {
    // Need to look up the project's sector via projectCode
    const proj = scopedProjects.find((p) => p.id === r.projectCode);
    const sid = proj ? (proj.sector || "other") : "other";
    if (!sectorMap.has(sid)) sectorMap.set(sid, { rows: [] });
    sectorMap.get(sid).rows.push(r);
  });
  const sectorRollup = Array.from(sectorMap.entries()).map(([sid, agg]) => {
    const measuredS = agg.rows.filter((r) => r.latest != null);
    const onTargetS = agg.rows.filter((r) => r.status === "ok").length;
    const avgS = measuredS.length > 0
      ? measuredS.reduce((s, r) => s + (r.progress || 0), 0) / measuredS.length
      : 0;
    const sec = (typeof sectorById === "function") ? sectorById(sid) : { id: sid, fr: sid, en: sid };
    return {
      sectorId: sid,
      sectorName: lang === "fr" ? sec.fr : sec.en,
      count: agg.rows.length, measured: measuredS.length, onTarget: onTargetS,
      avgProgress: avgS,
      status: avgS >= 0.9 ? "ok" : avgS >= 0.6 ? "amber" : "bad",
    };
  }).sort((a, b) => b.count - a.count);

  // Indicator level breakdown per project — for the stacked-bar chart
  const levelRollup = scopedProjects.map((p) => {
    const pr = rows.filter((r) => r.projectCode === p.id);
    const byLevel = { impact: 0, outcome: 0, mixed: 0, output: 0, other: 0 };
    pr.forEach((r) => {
      const lv = String(r.level || "").toLowerCase();
      if (lv === "impact" || lv === "outcome" || lv === "mixed" || lv === "output") byLevel[lv]++;
      else byLevel.other++;
    });
    return {
      code: p.id, name: lang === "fr" ? p.nameFr : p.nameEn,
      byLevel, total: pr.length,
    };
  }).filter((r) => r.total > 0);

  return {
    period, scopedProjects, scopedIndicators, rows, projectRows,
    financial, sectorRollup, levelRollup,
    summary: {
      totalProjects: scopedProjects.length,
      totalIndicators: rows.length,
      measured: measured.length,
      onTarget, atRisk, behind,
      avgProgress,
      coverage: rows.length > 0 ? measured.length / rows.length : 0,
    },
  };
}

// --------------------------------------------------------------------------
// Generator form — scope/period/year picker + "Generate" button.
// --------------------------------------------------------------------------
function ReportGeneratorForm({ programmes, projects, lang, onGenerate, busy }) {
  const [scopeMode, setScopeMode] = useStateR("all"); // all | programme | project
  const [scopeId, setScopeId]     = useStateR("");
  const thisYear = new Date().getFullYear();
  const [year, setYear]           = useStateR(thisYear);
  const [preset, setPreset]       = useStateR("Q" + Math.min(4, Math.ceil((new Date().getMonth() + 1) / 3)));
  const [customStart, setCustomStart] = useStateR(thisYear + "-01-01");
  const [customEnd, setCustomEnd]     = useStateR(thisYear + "-12-31");

  const programmeOptions = (programmes || []).filter((p) => p.id);
  const projectOptions   = (projects   || []).filter((p) => p.uuid);
  const inp = { padding: "8px 10px", borderRadius: 6, border: "1px solid var(--line)", fontSize: 13, width: "100%", boxSizing: "border-box", background: "var(--bg, white)", color: "var(--text)" };

  const handleSubmit = (e) => {
    e.preventDefault();
    const period = periodFromPreset(year, preset, customStart, customEnd);
    onGenerate({
      scope: { mode: scopeMode, id: scopeId || null },
      year, preset, period,
      label: periodLabel(year, preset, lang, customStart, customEnd),
    });
  };

  // When the scope mode changes, clear the id.
  useEffectR(() => { setScopeId(""); }, [scopeMode]);

  // Default scopeId to first available option when mode requires one.
  useEffectR(() => {
    if (scopeMode === "programme" && !scopeId && programmeOptions[0]) setScopeId(programmeOptions[0].id);
    if (scopeMode === "project"   && !scopeId && projectOptions[0])   setScopeId(projectOptions[0].uuid);
  }, [scopeMode, programmeOptions.length, projectOptions.length]);

  const canGenerate = scopeMode === "all" || !!scopeId;

  return (
    <form className="card" onSubmit={handleSubmit}>
      <div className="card-head">
        <div className="card-title">{lang === "fr" ? "Générateur de rapport" : "Report generator"}</div>
        <span className="pill accent dot">{lang === "fr" ? "Live data" : "Live data"}</span>
      </div>
      <div className="card-body">
        <div className="grid" style={{ gridTemplateColumns: "1.2fr 1fr 0.7fr 0.7fr", gap: 12, alignItems: "end" }}>
          {/* Scope */}
          <div>
            <label className="text-faint" style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.04em" }}>
              {lang === "fr" ? "Portée" : "Scope"}
            </label>
            <div className="row" style={{ gap: 6, marginTop: 4 }}>
              {[
                { v: "all",       lf: "Tous projets",  le: "All projects" },
                { v: "programme", lf: "Programme",     le: "Programme" },
                { v: "project",   lf: "Projet",        le: "Project" },
              ].map((s) => (
                <button type="button" key={s.v}
                  onClick={() => setScopeMode(s.v)}
                  className={"btn sm " + (scopeMode === s.v ? "primary" : "")}>
                  {lang === "fr" ? s.lf : s.le}
                </button>
              ))}
            </div>
            <div style={{ marginTop: 8 }}>
              {scopeMode === "programme" && (
                <select style={inp} value={scopeId} onChange={(e) => setScopeId(e.target.value)}>
                  <option value="">{lang === "fr" ? "— choisir un programme —" : "— pick a programme —"}</option>
                  {programmeOptions.map((p) => (
                    <option key={p.id} value={p.id}>{p.code} — {lang === "fr" ? p.name_fr : (p.name_en || p.name_fr)}</option>
                  ))}
                </select>
              )}
              {scopeMode === "project" && (
                <select style={inp} value={scopeId} onChange={(e) => setScopeId(e.target.value)}>
                  <option value="">{lang === "fr" ? "— choisir un projet —" : "— pick a project —"}</option>
                  {projectOptions.map((p) => (
                    <option key={p.uuid} value={p.uuid}>{p.id} — {lang === "fr" ? p.nameFr : p.nameEn}</option>
                  ))}
                </select>
              )}
              {scopeMode === "all" && (
                <div className="text-faint" style={{ fontSize: 11.5, padding: "8px 0" }}>
                  {lang === "fr"
                    ? "Rapport consolidé sur tout le portefeuille."
                    : "Consolidated portfolio-wide report."}
                </div>
              )}
            </div>
          </div>

          {/* Period preset */}
          <div>
            <label className="text-faint" style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.04em" }}>
              {lang === "fr" ? "Période" : "Period"}
            </label>
            <select style={{ ...inp, marginTop: 4 }} value={preset} onChange={(e) => setPreset(e.target.value)}>
              {PERIOD_PRESETS.map((p) => (
                <option key={p.k} value={p.k}>{lang === "fr" ? p.lf : p.le}</option>
              ))}
            </select>
            {preset === "CUSTOM" && (
              <div className="row" style={{ gap: 6, marginTop: 6 }}>
                <input type="date" style={inp} value={customStart} onChange={(e) => setCustomStart(e.target.value)} />
                <input type="date" style={inp} value={customEnd}   onChange={(e) => setCustomEnd(e.target.value)} />
              </div>
            )}
          </div>

          {/* Year (only when not custom) */}
          <div>
            <label className="text-faint" style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.04em" }}>
              {lang === "fr" ? "Année" : "Year"}
            </label>
            <input type="number" min="2000" max="2099"
              value={year} onChange={(e) => setYear(parseInt(e.target.value, 10) || thisYear)}
              disabled={preset === "CUSTOM"}
              style={{ ...inp, marginTop: 4 }} />
          </div>

          {/* Submit */}
          <div>
            <button type="submit" className="btn sm primary" disabled={!canGenerate || busy} style={{ width: "100%" }}>
              {busy
                ? (lang === "fr" ? "Génération…" : "Generating…")
                : <><Icon.fileText /> {lang === "fr" ? "Générer" : "Generate"}</>}
            </button>
          </div>
        </div>

        {!canGenerate && (
          <div className="text-faint" style={{ fontSize: 11, marginTop: 8 }}>
            {lang === "fr" ? "Sélectionnez un programme ou un projet pour générer." : "Pick a programme or a project to generate."}
          </div>
        )}
      </div>
    </form>
  );
}

// --------------------------------------------------------------------------
// KPI tile (visual match with ex-ante report).
// --------------------------------------------------------------------------
function KpiTile({ label, value, sub, status }) {
  const c = STATUS_COLORS[status || "neutral"];
  return (
    <div style={{
      border: "1px solid " + c.border, background: c.bg, color: c.fg,
      borderRadius: 6, padding: "10px 12px",
      display: "flex", flexDirection: "column", gap: 2, minHeight: 70,
    }}>
      <div style={{ fontSize: 9.5, fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.04em", opacity: 0.8 }}>{label}</div>
      <div style={{ fontSize: 18, fontWeight: 700, fontFamily: "Consolas, monospace", letterSpacing: "-0.01em" }}>{value}</div>
      {sub && <div style={{ fontSize: 10, opacity: 0.75 }}>{sub}</div>}
    </div>
  );
}

// --------------------------------------------------------------------------
// Chart-image hook — computes PNG dataURLs from the report data and
// re-runs when the data changes. Mirrors useExanteChartImages.
// --------------------------------------------------------------------------
function useReportingChartImages(data, lang) {
  const [charts, setCharts] = React.useState({});
  React.useEffect(() => {
    if (!window.reportingCharts || !data) return;
    let cancelled = false;
    const s = data.summary || {};
    const notMeasured = Math.max(0, (s.totalIndicators || 0) - (s.measured || 0));
    const opts = {
      indicatorStatus: {
        onTarget: s.onTarget || 0, atRisk: s.atRisk || 0,
        behind: s.behind || 0, notMeasured, lang,
      },
    };
    if (data.projectRows && data.projectRows.length > 1) {
      opts.progressByProject = {
        rows: data.projectRows.slice(0, 12).map((p) => ({
          code: p.code, name: p.name,
          progress: p.avgProgress || 0,
          status: p.avgProgress >= 0.9 ? "ok" : p.avgProgress >= 0.6 ? "amber" : "bad",
        })),
        lang,
      };
      const eurRows = data.projectRows
        .filter((p) => (p.budget || p.disbursed))
        .slice(0, 12)
        .map((p) => {
          const ccy = p.currency || "EUR";
          const conv = (window.melr && window.melr.convertToEur) ? window.melr.convertToEur : ((v) => v);
          return {
            code: p.code, name: p.name,
            budgetEur: conv((p.budget || 0) * 1_000_000, ccy) / 1_000_000,
            disbursedEur: conv((p.disbursed || 0) * 1_000_000, ccy) / 1_000_000,
          };
        });
      if (eurRows.length > 0) opts.budgetByProject = { rows: eurRows, lang };
    }
    if (data.levelRollup && data.levelRollup.length > 1) {
      opts.indicatorLevels = { rows: data.levelRollup.slice(0, 12), lang };
    }
    window.reportingCharts.dataURLs(opts).then((r) => {
      if (!cancelled) setCharts(r || {});
    }).catch((e) => console.error("[reporting-charts]", e));
    return () => { cancelled = true; };
  }, [data, lang]);
  return charts;
}

// --------------------------------------------------------------------------
// Printable report — used both for window.print() and the .doc download.
// --------------------------------------------------------------------------
function ReportPrintable({ data, meta, lang, charts }) {
  const t = (fr, en) => (lang === "fr" ? fr : en);
  const today = new Date().toLocaleDateString(lang === "fr" ? "fr-FR" : "en-US");
  const sum = data.summary;
  charts = charts || {};

  // Group rows by project for the detailed table.
  const rowsByProject = useMemoR(() => {
    const map = new Map();
    data.rows.forEach((r) => {
      if (!map.has(r.projectCode)) map.set(r.projectCode, []);
      map.get(r.projectCode).push(r);
    });
    return Array.from(map.entries());
  }, [data.rows]);

  // Top 3 risks (status = bad) and top 3 wins (status = ok, progress >= 0.95)
  const wins = data.rows.filter((r) => r.status === "ok" && (r.progress || 0) >= 0.95).slice(0, 5);
  const risks = data.rows.filter((r) => r.status === "bad").sort((a, b) => (a.progress || 0) - (b.progress || 0)).slice(0, 5);

  const styles = {
    body: { fontFamily: "Calibri, Arial, sans-serif", color: "#111", padding: 32, maxWidth: 900, margin: "0 auto", background: "white", fontSize: 12 },
    cover: { textAlign: "center", marginBottom: 40, paddingTop: 50 },
    coverTitle: { fontSize: 26, fontWeight: 700, marginBottom: 6 },
    coverSub: { fontSize: 14, color: "#555", marginBottom: 24 },
    coverMeta: { fontSize: 12, color: "#666", lineHeight: 1.7 },
    h1: { fontSize: 20, fontWeight: 700, color: "#1f2937", marginTop: 28, marginBottom: 10, borderBottom: "2px solid #1f2937", paddingBottom: 6 },
    h2: { fontSize: 14, fontWeight: 600, color: "#374151", marginTop: 16, marginBottom: 8 },
    table: { width: "100%", borderCollapse: "collapse", marginBottom: 12, fontSize: 11 },
    th: { borderBottom: "2px solid #1f2937", textAlign: "left", padding: "6px 8px", fontWeight: 700, background: "#f9fafb", fontSize: 10.5 },
    td: { borderBottom: "1px solid #e5e7eb", padding: "5px 8px", verticalAlign: "top" },
    numTd: { textAlign: "right", fontFamily: "Consolas, monospace" },
    pill: (c) => ({ display: "inline-block", padding: "2px 8px", borderRadius: 999, background: c.bg, color: c.fg, border: "1px solid " + c.border, fontSize: 10, fontWeight: 700 }),
    progressBar: (pct) => ({
      display: "inline-block", height: 6, width: 60, borderRadius: 3, background: "#e5e7eb",
      verticalAlign: "middle", marginRight: 6, overflow: "hidden", position: "relative",
    }),
    progressFill: (pct, status) => ({
      height: "100%", width: (pct * 100).toFixed(0) + "%",
      background: STATUS_COLORS[status].fg,
    }),
  };

  return (
    <div id="report-printable" style={styles.body}>
      {/* ===== COVER ===== */}
      <div style={styles.cover}>
        <div style={{ fontSize: 11, color: "#666", marginBottom: 12, textTransform: "uppercase", letterSpacing: "0.1em" }}>
          {t("RAPPORT DE SUIVI-ÉVALUATION", "MONITORING & EVALUATION REPORT")}
        </div>
        <div style={styles.coverTitle}>{meta.title}</div>
        <div style={styles.coverSub}>{meta.subtitle}</div>
        <div style={styles.coverMeta}>
          {t("Période", "Period")} : <strong>{meta.periodLabel}</strong> ({data.period.start} → {data.period.end})<br />
          {t("Portée", "Scope")} : <strong>{meta.scopeLabel}</strong> · {data.scopedProjects.length} {t("projet(s)", "project(s)")} · {data.scopedIndicators.length} {t("indicateur(s)", "indicator(s)")}<br />
          {t("Généré le", "Generated on")} : {today}{meta.author && (" · " + t("Auteur", "Author") + " : " + meta.author)}
        </div>
      </div>

      {/* ===== EXECUTIVE SUMMARY ===== */}
      <h1 style={styles.h1}>1. {t("Synthèse exécutive", "Executive summary")}</h1>
      <div className="grid" style={{ gridTemplateColumns: "repeat(4, 1fr)", gap: 10, marginBottom: 12, display: "grid" }}>
        <KpiTile label={t("Projets", "Projects")}    value={sum.totalProjects}    sub={t("dans la portée", "in scope")}    status="accent" />
        <KpiTile label={t("Indicateurs", "Indicators")} value={sum.totalIndicators}  sub={fmtPct(sum.coverage) + " " + t("mesurés", "measured")} status={sum.coverage >= 0.5 ? "ok" : "amber"} />
        <KpiTile label={t("Sur cible", "On target")} value={sum.onTarget} sub={sum.totalIndicators > 0 ? Math.round(sum.onTarget / sum.totalIndicators * 100) + "%" : "—"} status="ok" />
        <KpiTile label={t("En retard", "Behind")}    value={sum.behind}   sub={sum.totalIndicators > 0 ? Math.round(sum.behind / sum.totalIndicators * 100) + "%" : "—"} status={sum.behind > 0 ? "bad" : "ok"} />
      </div>
      <p style={{ fontSize: 11.5, lineHeight: 1.6, color: "#374151" }}>
        {meta.execSummary}
      </p>
      {charts.indicatorStatus && (
        <div style={{ textAlign: "center", margin: "12px 0" }}>
          <img src={charts.indicatorStatus} alt="Indicator status donut"
            style={{ maxWidth: "100%", height: "auto" }} />
        </div>
      )}

      {/* ===== FINANCIAL OVERVIEW ===== */}
      {data.financial && data.financial.totalBudgetEur > 0 && (
        <>
          <h2 style={{ ...styles.h2, marginTop: 18 }}>
            {t("Cadrage financier", "Financial overview")}
          </h2>
          <div className="grid" style={{ gridTemplateColumns: "repeat(3, 1fr)", gap: 10, marginBottom: 12, display: "grid" }}>
            <KpiTile
              label={t("Budget total (EUR)", "Total budget (EUR)")}
              value={(data.financial.totalBudgetEur / 1_000_000).toFixed(1) + " M€"}
              sub={data.scopedProjects.length + " " + t("projets", "projects")}
              status="accent" />
            <KpiTile
              label={t("Décaissé (EUR)", "Disbursed (EUR)")}
              value={(data.financial.totalDisbursedEur / 1_000_000).toFixed(1) + " M€"}
              sub={fmtPct(data.financial.coverageRate) + " " + t("du budget", "of budget")}
              status={data.financial.coverageRate >= 0.5 ? "ok" : "amber"} />
            <KpiTile
              label={t("Reste à engager", "Remaining")}
              value={((data.financial.totalBudgetEur - data.financial.totalDisbursedEur) / 1_000_000).toFixed(1) + " M€"}
              sub={fmtPct(1 - data.financial.coverageRate)}
              status="neutral" />
          </div>
        </>
      )}

      {/* ===== SECTOR BREAKDOWN ===== */}
      {data.sectorRollup && data.sectorRollup.length > 1 && (
        <>
          <h2 style={{ ...styles.h2, marginTop: 18 }}>
            {t("Performance par secteur", "Performance by sector")}
          </h2>
          <table style={styles.table}>
            <thead>
              <tr>
                <th style={styles.th}>{t("Secteur", "Sector")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Indicateurs", "Indicators")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Mesurés", "Measured")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Sur cible", "On target")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Avancement moy.", "Avg progress")}</th>
                <th style={styles.th}>{t("Statut", "Status")}</th>
              </tr>
            </thead>
            <tbody>
              {data.sectorRollup.map((s) => (
                <tr key={s.sectorId}>
                  <td style={{ ...styles.td, fontWeight: 500 }}>{s.sectorName}</td>
                  <td style={{ ...styles.td, ...styles.numTd }}>{s.count}</td>
                  <td style={{ ...styles.td, ...styles.numTd }}>{s.measured}</td>
                  <td style={{ ...styles.td, ...styles.numTd }}>{s.onTarget}</td>
                  <td style={{ ...styles.td, ...styles.numTd, fontWeight: 600 }}>{fmtPct(s.avgProgress)}</td>
                  <td style={styles.td}>
                    <span style={styles.pill(STATUS_COLORS[s.status])}>{statusLabel(s.status, lang)}</span>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </>
      )}

      {/* ===== PER-PROJECT ROLLUP ===== */}
      {data.projectRows.length > 1 && (
        <>
          <h1 style={styles.h1}>2. {t("Avancement par projet", "Project rollup")}</h1>
          <table style={styles.table}>
            <thead>
              <tr>
                <th style={styles.th}>{t("Code", "Code")}</th>
                <th style={styles.th}>{t("Projet", "Project")}</th>
                <th style={styles.th}>{t("Programme", "Programme")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Indic.", "Ind.")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Mesurés", "Measured")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Sur cible", "On target")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Avancement moy.", "Avg progress")}</th>
              </tr>
            </thead>
            <tbody>
              {data.projectRows.map((p) => (
                <tr key={p.uuid}>
                  <td style={{ ...styles.td, fontFamily: "Consolas, monospace", fontWeight: 600 }}>{p.code}</td>
                  <td style={styles.td}>{p.name || "—"}</td>
                  <td style={{ ...styles.td, color: "#6b7280", fontSize: 10.5 }}>{p.programmeCode}</td>
                  <td style={{ ...styles.td, ...styles.numTd }}>{p.totalIndicators}</td>
                  <td style={{ ...styles.td, ...styles.numTd }}>{p.measured}</td>
                  <td style={{ ...styles.td, ...styles.numTd }}>{p.onTarget}</td>
                  <td style={{ ...styles.td, ...styles.numTd, fontWeight: 600 }}>{fmtPct(p.avgProgress)}</td>
                </tr>
              ))}
            </tbody>
          </table>
          {charts.progressByProject && (
            <div style={{ textAlign: "center", margin: "12px 0" }}>
              <img src={charts.progressByProject} alt="Progress per project"
                style={{ maxWidth: "100%", height: "auto" }} />
            </div>
          )}
          {charts.budgetByProject && (
            <div style={{ textAlign: "center", margin: "12px 0" }}>
              <img src={charts.budgetByProject} alt="Budget vs disbursed"
                style={{ maxWidth: "100%", height: "auto" }} />
            </div>
          )}
          {charts.indicatorLevels && (
            <div style={{ textAlign: "center", margin: "12px 0" }}>
              <img src={charts.indicatorLevels} alt="Indicator levels"
                style={{ maxWidth: "100%", height: "auto" }} />
            </div>
          )}
        </>
      )}

      {/* ===== INDICATORS DETAIL ===== */}
      <h1 style={styles.h1}>{data.projectRows.length > 1 ? "3" : "2"}. {t("Détail des indicateurs", "Indicators detail")}</h1>
      {data.rows.length === 0 && (
        <p style={{ fontSize: 11.5, color: "#6b7280", fontStyle: "italic" }}>
          {t("Aucun indicateur dans la portée et la période sélectionnées.", "No indicators in the selected scope and period.")}
        </p>
      )}
      {rowsByProject.map(([projCode, projRows]) => (
        <div key={projCode} style={{ marginBottom: 18 }}>
          {data.projectRows.length > 1 && (
            <h2 style={styles.h2}>{projCode}</h2>
          )}
          <table style={styles.table}>
            <thead>
              <tr>
                <th style={styles.th}>{t("Code", "Code")}</th>
                <th style={styles.th}>{t("Indicateur", "Indicator")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Baseline", "Baseline")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Réalisé", "Actual")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Cible", "Target")}</th>
                <th style={{ ...styles.th, textAlign: "right" }}>{t("Avancement", "Progress")}</th>
                <th style={styles.th}>{t("Statut", "Status")}</th>
              </tr>
            </thead>
            <tbody>
              {projRows.map((r) => (
                <tr key={r.id}>
                  <td style={{ ...styles.td, fontFamily: "Consolas, monospace", fontWeight: 600 }}>{r.code}</td>
                  <td style={styles.td}>
                    {r.name || "—"}
                    {r.unit && <span style={{ color: "#9ca3af", fontSize: 10 }}> ({r.unit})</span>}
                  </td>
                  <td style={{ ...styles.td, ...styles.numTd }}>{fmtNum(r.baseline)}</td>
                  <td style={{ ...styles.td, ...styles.numTd, fontWeight: 600 }}>{fmtNum(r.latest)}</td>
                  <td style={{ ...styles.td, ...styles.numTd }}>{fmtNum(r.target)}</td>
                  <td style={styles.td}>
                    <span style={styles.progressBar(r.progress)}>
                      {r.progress != null && <span style={{ display: "block", ...styles.progressFill(r.progress, r.status) }} />}
                    </span>
                    <span style={{ fontFamily: "Consolas, monospace", fontSize: 10.5 }}>{r.progress == null ? "—" : fmtPct(r.progress)}</span>
                  </td>
                  <td style={styles.td}>
                    <span style={styles.pill(STATUS_COLORS[r.status])}>{statusLabel(r.status, lang)}</span>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      ))}

      {/* ===== HIGHLIGHTS ===== */}
      {(wins.length > 0 || risks.length > 0) && (
        <>
          <h1 style={styles.h1}>{data.projectRows.length > 1 ? "4" : "3"}. {t("Faits saillants", "Highlights")}</h1>
          {wins.length > 0 && (
            <>
              <h2 style={{ ...styles.h2, color: "#15803d" }}>{t("Réussites", "Wins")}</h2>
              <ul style={{ fontSize: 11.5, lineHeight: 1.6, paddingLeft: 20 }}>
                {wins.map((w) => (
                  <li key={w.id}>
                    <strong>{w.code}</strong> ({w.projectCode}) — {w.name || ""} : <strong>{fmtPct(w.progress)}</strong> {t("de la cible atteinte", "of target reached")}
                  </li>
                ))}
              </ul>
            </>
          )}
          {risks.length > 0 && (
            <>
              <h2 style={{ ...styles.h2, color: "#b91c1c" }}>{t("Risques / alertes", "Risks / alerts")}</h2>
              <ul style={{ fontSize: 11.5, lineHeight: 1.6, paddingLeft: 20 }}>
                {risks.map((r) => (
                  <li key={r.id}>
                    <strong>{r.code}</strong> ({r.projectCode}) — {r.name || ""} : <strong>{r.progress == null ? "—" : fmtPct(r.progress)}</strong> {t("de la cible", "of target")}
                    {r.latest != null && <> ({t("réalisé", "actual")} {fmtNum(r.latest)} / {t("cible", "target")} {fmtNum(r.target)})</>}
                  </li>
                ))}
              </ul>
            </>
          )}
        </>
      )}

      {/* ===== FOOTER ===== */}
      <div style={{ marginTop: 40, paddingTop: 16, borderTop: "1px solid #e5e7eb", fontSize: 10, color: "#6b7280", textAlign: "center" }}>
        {t("Document généré automatiquement par MELR — ", "Generated automatically by MELR — ")}{today}
      </div>
    </div>
  );
}

// --------------------------------------------------------------------------
// Modal wrapper with toolbar.
// --------------------------------------------------------------------------
function ReportPreviewModal({ data, meta, lang, onClose, onSave, saved, submitted, onSubmitForValidation }) {
  const charts = useReportingChartImages(data, lang);
  const [scheduleOpen, setScheduleOpen] = useStateR(false);
  const onPrint = () => window.print();

  const onExportDocx = () => {
    if (!window.exportReportingDocx) return;
    window.exportReportingDocx({ data, meta, lang });
  };

  // Compose a mailto: URL with the executive summary as body. Browsers
  // can't programmatically attach files via mailto:, so we tell the user
  // to download then drag-attach manually.
  const onEmail = () => {
    const subject = encodeURIComponent("[MELR] " + meta.title);
    const body = encodeURIComponent(
      meta.title + "\n" +
      meta.subtitle + "\n" +
      (lang === "fr" ? "Période : " : "Period: ") + meta.periodLabel + " (" + data.period.start + " → " + data.period.end + ")\n\n" +
      meta.execSummary + "\n\n" +
      (lang === "fr"
        ? "— Téléchargez le rapport au format Word/PDF/Excel et joignez-le manuellement à cet email (les pièces jointes ne peuvent pas être ajoutées automatiquement)."
        : "— Download the report as Word/PDF/Excel and attach it manually to this email (browsers cannot auto-attach files).")
    );
    window.location.href = "mailto:?subject=" + subject + "&body=" + body;
  };

  const onExportDoc = () => {
    const node = document.getElementById("report-printable");
    if (!node) return;
    const html = `<!DOCTYPE html>
<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40">
<head>
<meta charset="utf-8">
<title>${meta.title}</title>
<style>
  body { font-family: Calibri, sans-serif; font-size: 11pt; }
  h1 { font-size: 18pt; color: #1f2937; border-bottom: 2px solid #1f2937; padding-bottom: 4pt; }
  h2 { font-size: 13pt; color: #374151; }
  table { border-collapse: collapse; width: 100%; margin-bottom: 12pt; }
  th { background: #f9fafb; border-bottom: 2px solid #1f2937; padding: 4pt 6pt; text-align: left; font-weight: bold; }
  td { border-bottom: 1px solid #e5e7eb; padding: 4pt 6pt; vertical-align: top; }
</style>
</head>
<body>
${node.innerHTML}
</body></html>`;
    const blob = new Blob(["﻿", html], { type: "application/msword" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url; a.download = meta.filename + ".doc";
    document.body.appendChild(a); a.click(); document.body.removeChild(a);
    URL.revokeObjectURL(url);
  };

  const onExportCsv = () => {
    const cols = [
      { key: "projectCode", label: "Projet" },
      { key: "code",        label: "Code" },
      { key: "name",        label: lang === "fr" ? "Indicateur" : "Indicator" },
      { key: "unit",        label: lang === "fr" ? "Unité" : "Unit" },
      { key: "baseline",    label: "Baseline" },
      { key: "target",      label: lang === "fr" ? "Cible" : "Target" },
      { key: "latest",      label: lang === "fr" ? "Réalisé" : "Actual" },
      { key: "progress",    label: lang === "fr" ? "Avancement" : "Progress", value: (r) => r.progress == null ? "" : (r.progress * 100).toFixed(1) + "%" },
      { key: "status",      label: lang === "fr" ? "Statut" : "Status", value: (r) => statusLabel(r.status, lang) },
      { key: "measuredAt",  label: lang === "fr" ? "Mesuré le" : "Measured at" },
    ];
    window.melr.exportCSV(meta.filename + ".csv", data.rows, cols);
  };

  return (
    <div className="exante-report-overlay" style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.6)", zIndex: 9999,
      display: "flex", flexDirection: "column",
    }}>
      {/* Toolbar — hidden on print */}
      <div className="exante-report-toolbar" style={{
        padding: "10px 16px", background: "var(--bg, white)", color: "var(--text, #111)",
        borderBottom: "1px solid var(--line)", display: "flex", gap: 10, alignItems: "center",
      }}>
        <div style={{ fontWeight: 600, fontSize: 14 }}>
          {lang === "fr" ? "Aperçu du rapport" : "Report preview"}
        </div>
        <span className="pill" style={{ marginLeft: 8 }}>{meta.scopeLabel} · {meta.periodLabel}</span>
        <div style={{ flex: 1 }} />
        <button onClick={onExportCsv}
          style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid var(--line)", background: "var(--bg-elev)", color: "var(--text)", cursor: "pointer", fontWeight: 500 }}>
          ↓ CSV / Excel
        </button>
        {window.exportReportingDocxAvailable && (
          <button onClick={onExportDocx}
            style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid var(--line)", background: "var(--bg-elev)", color: "var(--text)", cursor: "pointer", fontWeight: 500 }}
            title={lang === "fr" ? "Document Word natif (avec graphiques)" : "Native Word document (with charts)"}>
            ↓ Word (.docx)
          </button>
        )}
        <button onClick={onExportDoc}
          style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid var(--line)", background: "var(--bg-elev)", color: "var(--text)", cursor: "pointer", fontWeight: 500 }}
          title={lang === "fr" ? "Format Word HTML (compatibilité large)" : "HTML Word format (broad compatibility)"}>
          ↓ Word (.doc)
        </button>
        <button onClick={onPrint}
          style={{ padding: "8px 14px", borderRadius: 6, border: 0, background: "#2563eb", color: "white", cursor: "pointer", fontWeight: 600 }}>
          ↓ {lang === "fr" ? "Imprimer / PDF" : "Print / PDF"}
        </button>
        <button onClick={onEmail}
          style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid var(--line)", background: "var(--bg-elev)", color: "var(--text)", cursor: "pointer", fontWeight: 500 }}
          title={lang === "fr" ? "Pré-rédige un email avec le résumé" : "Compose an email with the summary"}>
          ✉ {lang === "fr" ? "Email" : "Email"}
        </button>
        <button onClick={() => setScheduleOpen(true)}
          style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid var(--line)", background: "var(--bg-elev)", color: "var(--text)", cursor: "pointer", fontWeight: 500 }}
          title={lang === "fr" ? "Planifier la génération récurrente" : "Schedule recurring generation"}>
          ⏰ {lang === "fr" ? "Programmer" : "Schedule"}
        </button>
        {onSave && (
          <button onClick={onSave} disabled={saved}
            style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid var(--line)", background: saved ? "#dcfce7" : "var(--bg-elev)", color: saved ? "#15803d" : "var(--text)", cursor: saved ? "default" : "pointer", fontWeight: 600 }}>
            {saved ? (lang === "fr" ? "✓ Enregistré" : "✓ Saved") : (lang === "fr" ? "Enregistrer" : "Save draft")}
          </button>
        )}
        {saved && onSubmitForValidation && (
          <button onClick={onSubmitForValidation} disabled={submitted}
            title={lang === "fr"
              ? "Soumettre ce rapport pour validation"
              : "Submit this report for approval"}
            style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid var(--line)", background: submitted ? "#dcfce7" : "var(--bg-elev)", color: submitted ? "#15803d" : "var(--text)", cursor: submitted ? "default" : "pointer", fontWeight: 600 }}>
            {submitted
              ? (lang === "fr" ? "✓ Soumis" : "✓ Submitted")
              : <>↗ {lang === "fr" ? "Soumettre" : "Submit"}</>}
          </button>
        )}
        <button onClick={onClose}
          style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid var(--line)", background: "transparent", color: "var(--text)", cursor: "pointer" }}>
          {lang === "fr" ? "Fermer" : "Close"}
        </button>
      </div>
      {/* Scrollable doc area */}
      <div className="exante-report-scroll" style={{ flex: 1, overflow: "auto", background: "#f3f4f6", padding: "24px 0" }}>
        <ReportPrintable data={data} meta={meta} lang={lang} charts={charts} />
      </div>
      {scheduleOpen && (
        <ReportScheduleModal
          lang={lang} meta={meta}
          onClose={() => setScheduleOpen(false)} />
      )}
    </div>
  );
}

// --------------------------------------------------------------------------
// Schedule modal — inserts a row into report_schedules so the platform
// can re-generate this report on a cadence (weekly / monthly / quarterly).
// --------------------------------------------------------------------------
function ReportScheduleModal({ lang, meta, onClose }) {
  const [cadence, setCadence] = useStateR("monthly");
  const [recipients, setRecipients] = useStateR("");
  const [active, setActive] = useStateR(true);
  const [busy, setBusy] = useStateR(false);
  const [err, setErr] = useStateR(null);
  const [done, setDone] = useStateR(false);

  const inp = { padding: "8px 10px", borderRadius: 6, border: "1px solid var(--line)", fontSize: 13, width: "100%", boxSizing: "border-box", background: "var(--bg, white)", color: "var(--text)" };

  // Compute next_run_at based on cadence
  const computeNextRun = () => {
    const d = new Date();
    if (cadence === "weekly")    d.setDate(d.getDate() + 7);
    if (cadence === "monthly")   d.setMonth(d.getMonth() + 1);
    if (cadence === "quarterly") d.setMonth(d.getMonth() + 3);
    return d.toISOString();
  };

  const onSubmit = async (e) => {
    e.preventDefault();
    setBusy(true); setErr(null);
    try {
      const sb = window.melr && window.melr.supabase;
      if (!sb) throw new Error("Base de données indisponible");
      const recArr = recipients.split(/[,;\s]+/).map((r) => r.trim()).filter(Boolean);
      const row = {
        cadence,
        next_run_at: computeNextRun(),
        recipients: recArr,
        active,
        scope_project_id:   meta.scope.mode === "project"   ? meta.scope.id : null,
        scope_programme_id: meta.scope.mode === "programme" ? meta.scope.id : null,
      };
      const r = await sb.from("report_schedules").insert(row).select().single();
      if (r.error) throw new Error(r.error.message);
      setDone(true);
    } catch (e) {
      console.error("[schedule]", e);
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div onClick={() => !busy && onClose()} style={{
      position: "fixed", inset: 0, background: "rgba(0,0,0,.5)", zIndex: 10000,
      display: "flex", alignItems: "center", justifyContent: "center",
    }}>
      <form onClick={(e) => e.stopPropagation()} onSubmit={onSubmit}
        style={{
          background: "var(--bg, white)", color: "var(--text, #111)",
          padding: 24, borderRadius: 10, width: 480, maxWidth: "92vw",
          boxShadow: "0 10px 30px rgba(0,0,0,.25)",
        }}>
        <div style={{ fontSize: 18, fontWeight: 600, marginBottom: 6 }}>
          {lang === "fr" ? "Planifier ce rapport" : "Schedule this report"}
        </div>
        <div className="text-faint" style={{ fontSize: 12, marginBottom: 16 }}>
          {lang === "fr"
            ? "Le rapport sera régénéré automatiquement à la cadence choisie pour la portée et la période actuelles."
            : "The report will be regenerated automatically at the chosen cadence for the current scope and period."}
        </div>

        <label style={{ display: "block", fontSize: 11, color: "var(--text-faint)", marginBottom: 4, textTransform: "uppercase", letterSpacing: "0.04em" }}>
          {lang === "fr" ? "Cadence" : "Cadence"}
        </label>
        <select style={{ ...inp, marginBottom: 12 }} value={cadence} onChange={(e) => setCadence(e.target.value)}>
          <option value="weekly">{lang === "fr" ? "Hebdomadaire" : "Weekly"}</option>
          <option value="monthly">{lang === "fr" ? "Mensuelle" : "Monthly"}</option>
          <option value="quarterly">{lang === "fr" ? "Trimestrielle" : "Quarterly"}</option>
        </select>

        <label style={{ display: "block", fontSize: 11, color: "var(--text-faint)", marginBottom: 4, textTransform: "uppercase", letterSpacing: "0.04em" }}>
          {lang === "fr" ? "Destinataires (emails séparés par virgules)" : "Recipients (comma-separated emails)"}
        </label>
        <textarea style={{ ...inp, marginBottom: 12, minHeight: 60, resize: "vertical", fontFamily: "inherit" }}
          value={recipients} onChange={(e) => setRecipients(e.target.value)}
          placeholder="alice@example.org, bob@example.org" />

        <label style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13, marginBottom: 14 }}>
          <input type="checkbox" checked={active} onChange={(e) => setActive(e.target.checked)} />
          {lang === "fr" ? "Activer la planification dès maintenant" : "Activate the schedule immediately"}
        </label>

        <div style={{ fontSize: 11.5, color: "var(--text-faint)", padding: "8px 10px", background: "var(--bg-sunken, #f9fafb)", borderRadius: 6, marginBottom: 14 }}>
          {lang === "fr"
            ? "Prochaine exécution : " + new Date(computeNextRun()).toLocaleDateString("fr-FR", { year: "numeric", month: "long", day: "numeric" })
            : "Next run: " + new Date(computeNextRun()).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })}
        </div>

        {err && <div style={{ color: "#b91c1c", fontSize: 12, marginBottom: 10 }}>{err}</div>}
        {done && <div style={{ color: "#15803d", fontSize: 12.5, marginBottom: 10 }}>
          {lang === "fr" ? "✓ Planification enregistrée." : "✓ Schedule saved."}
        </div>}

        <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
          <button type="button" onClick={onClose} disabled={busy}
            style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid var(--line)", background: "transparent", cursor: "pointer" }}>
            {done ? (lang === "fr" ? "Fermer" : "Close") : (lang === "fr" ? "Annuler" : "Cancel")}
          </button>
          {!done && (
            <button type="submit" disabled={busy}
              style={{ padding: "8px 14px", borderRadius: 6, border: 0, background: "#2563eb", color: "white", cursor: "pointer", fontWeight: 600 }}>
              {busy ? "…" : (lang === "fr" ? "Enregistrer" : "Save")}
            </button>
          )}
        </div>
      </form>
    </div>
  );
}

// --------------------------------------------------------------------------
// Build the executive summary paragraph from numbers.
// --------------------------------------------------------------------------
function buildExecSummary(data, lang) {
  const s = data.summary;
  if (s.totalIndicators === 0) {
    return lang === "fr"
      ? "Aucun indicateur ne couvre la période et la portée sélectionnées. Vérifiez les fréquences de collecte et les périodes des mesures."
      : "No indicators cover the selected scope and period. Check collection frequencies and measurement periods.";
  }
  const cov = Math.round(s.coverage * 100);
  const ok  = Math.round(s.onTarget / s.totalIndicators * 100);
  const bad = Math.round(s.behind   / s.totalIndicators * 100);
  const avg = Math.round(s.avgProgress * 100);
  if (lang === "fr") {
    return `Sur la période évaluée, ${s.measured} mesure(s) ont été collectées couvrant ${cov}% du portefeuille d'indicateurs (${s.totalIndicators} au total). L'avancement moyen pondéré atteint ${avg}% de la cible. ${ok}% des indicateurs sont sur cible (${s.onTarget}), ${bad}% sont en retard (${s.behind}) et ${s.atRisk} sont à surveiller. ` +
      (s.behind > 0
        ? "Les indicateurs en retard nécessitent une instruction immédiate — voir la section Faits saillants."
        : "Aucun indicateur critique n'est en retard sur cette période.");
  }
  return `Over the reporting period, ${s.measured} measurement(s) were collected, covering ${cov}% of the indicator portfolio (${s.totalIndicators} total). Weighted average progress stands at ${avg}% of target. ${ok}% of indicators are on target (${s.onTarget}), ${bad}% are behind (${s.behind}), and ${s.atRisk} are at risk. ` +
    (s.behind > 0
      ? "Behind-schedule indicators require immediate attention — see the Highlights section."
      : "No critical indicators are behind for this period.");
}

// --------------------------------------------------------------------------
// Reporting screen — top-level entry point.
// --------------------------------------------------------------------------
function Reporting({ t, lang, isSuperAdmin, actingOrgId, myOrgId }) {
  // Super-admin acting-as-org: narrow the scope picker, the project list,
  // and the indicators set to the target org. Non-super-admins are RLS-
  // filtered already; this is a no-op for them.
  const acting = !!(isSuperAdmin && actingOrgId && actingOrgId !== myOrgId);
  const { data: liveReports } = window.melr.useReports();
  // usePrograms now accepts a target org; pass actingOrgId to fetch the
  // right set when acting (super-admin SELECT policy permits this).
  const { programmes: allProgrammes } = (window.melr.usePrograms
    ? window.melr.usePrograms(acting ? actingOrgId : undefined)
    : { programmes: [] });
  const { projects: allProjects }   = window.melr.useProjects();
  const { data: allIndicators } = window.melr.useIndicators();  // all indicators visible to the user
  const projects   = acting ? (allProjects   || []).filter((p) => p.organizationId === actingOrgId) : (allProjects || []);
  const indicators = acting
    ? (allIndicators || []).filter((i) => i.projects && i.projects.organization_id === actingOrgId)
    : (allIndicators || []);
  const programmes = allProgrammes || [];

  const [generated, setGenerated] = useStateR(null);   // { data, meta }
  const [busy, setBusy]           = useStateR(false);
  const [saved, setSaved]         = useStateR(false);
  const [savedReportId, setSavedReportId] = useStateR(null);
  const [freshSaves, setFreshSaves] = useStateR([]);  // locally-prepended saves
  const [submitted, setSubmitted] = useStateR(false);
  // Surface a save error from handleSave inline (instead of just alert()).
  // Also referenced (and reset) by handleGenerate — that's what the
  // previous code was trying to do before the state was declared.
  const [saveError, setSaveError] = useStateR(null);

  const handleGenerate = ({ scope, year, preset, period, label }) => {
    setBusy(true);
    setSaved(false);
    // Reset transient flags from the previous generation pass. Note:
    // setSaveError was previously called here but never declared via
    // useState — the resulting ReferenceError fired BEFORE the try
    // block, so finally{} never ran and busy stayed stuck on true
    // (the "Générer tourne sans résultat" bug). saveError is now
    // declared below; this comment stays as a tripwire if anyone
    // re-introduces a phantom setter outside the try.
    setSaveError(null);
    setSubmitted(false);
    try {
      const data = computeReportData({ scope, projects, indicators, period, lang });

      // Build meta
      let scopeLabel = lang === "fr" ? "Tous projets" : "All projects";
      if (scope.mode === "programme") {
        const prog = (programmes || []).find((p) => p.id === scope.id);
        scopeLabel = prog ? (prog.code + " — " + (lang === "fr" ? prog.name_fr : (prog.name_en || prog.name_fr))) : "Programme";
      } else if (scope.mode === "project") {
        const proj = (projects || []).find((p) => p.uuid === scope.id);
        scopeLabel = proj ? (proj.id + " — " + (lang === "fr" ? proj.nameFr : proj.nameEn)) : "Project";
      }
      const title = (lang === "fr" ? "Rapport " : "Report ") + label;
      const subtitle = scopeLabel;
      const safeScope = (scope.mode === "all" ? "portfolio" : (scope.id ? scope.id.slice(0, 8) : "scope"));
      const filename = "rapport-" + safeScope + "-" + year + "-" + preset.toLowerCase();
      const meta = {
        title, subtitle, scopeLabel, periodLabel: label,
        filename,
        scope, year, preset,
        execSummary: buildExecSummary(data, lang),
      };

      setGenerated({ data, meta });
    } catch (e) {
      console.error("[reporting] generate:", e);
      alert((lang === "fr" ? "Erreur de génération : " : "Generation error: ") + e.message);
    } finally {
      setBusy(false);
    }
  };

  const handleSave = async () => {
    if (!generated) return;
    setSaveError(null);
    try {
      const sb = window.melr && window.melr.supabase;
      if (!sb) throw new Error("Base de données indisponible");
      const { meta, data } = generated;
      // For a "project" scope, attach to that project; otherwise null.
      const project_id = meta.scope.mode === "project" ? meta.scope.id : null;
      const row = {
        title: meta.title,
        project_id,
        period_start: data.period.start,
        period_end:   data.period.end,
        state: "draft",
      };
      const r = await sb.from("reports").insert(row).select(
        "id, title, project_id, period_start, period_end, state, created_at, projects(code)"
      ).single();
      if (r.error) throw new Error(r.error.message);
      setSaved(true);
      setSavedReportId(r.data.id);
      setFreshSaves((prev) => [r.data, ...prev]);
    } catch (e) {
      console.error("[reporting] save:", e);
      setSaveError(e.message);
      alert((lang === "fr" ? "Erreur d'enregistrement : " : "Save error: ") + e.message);
    }
  };

  // Build the list of saved reports (live + fixtures fallback)
  const FIXTURE_REPORTS = [
    { id: "R-2026-Q1-241", title: lang === "fr" ? "Rapport Q1 2026 — P-241 Sahel" : "Q1 2026 Report — P-241 Sahel", format: "AFD-RP", state: "draft", who: "K. Diabaté", when: lang === "fr" ? "il y a 2h" : "2h ago", thumb: "AFD" },
    { id: "R-2026-Q1-238", title: lang === "fr" ? "Rapport Q1 2026 — P-238 TB" : "Q1 2026 Report — P-238 TB", format: "Global Fund", state: "review", who: "M. Sané", when: lang === "fr" ? "hier" : "yesterday", thumb: "GF" },
    { id: "R-2025-A-235", title: lang === "fr" ? "Rapport annuel 2025 — Atlas" : "2025 Annual report — Atlas", format: "MSP / UE", state: "approved", who: "L. Benali", when: "2025-12-18", thumb: "MSP" },
  ];
  const mergedLive = [...freshSaves, ...(liveReports || [])]
    .filter((r, idx, arr) => arr.findIndex((x) => x.id === r.id) === idx);
  const reports = mergedLive.length > 0
    ? mergedLive.map((r) => ({
        id: r.id,
        title: r.title,
        format: "—",
        state: r.state === "sent" ? "approved" : r.state,
        who: (r.author && r.author.full_name) || "—",
        when: new Date(r.created_at).toLocaleDateString(lang === "fr" ? "fr-FR" : "en-US"),
        thumb: (r.projects && r.projects.code) || "—",
      }))
    : FIXTURE_REPORTS;
  const stateBadge = (s) => ({
    draft: <span className="pill amber dot">{lang === "fr" ? "Brouillon" : "Draft"}</span>,
    review: <span className="pill accent dot">{lang === "fr" ? "En revue" : "In review"}</span>,
    approved: <span className="pill green dot">{lang === "fr" ? "Validé" : "Approved"}</span>,
  })[s];

  return (
    <div className="page">
      <div className="page-header">
        <div className="page-eyebrow">{lang === "fr" ? "MODULE / REPORTING" : "MODULE / REPORTING"}</div>
        <div className="page-header-row">
          <div>
            <h1 className="page-title">{t("nav.reporting")}</h1>
            <div className="page-sub">{lang === "fr"
              ? "Générateur de rapports périodiques par portée (portefeuille, programme, projet) · sortie PDF, Word et Excel · alimenté par les indicateurs live"
              : "Periodic report generator by scope (portfolio, programme, project) · PDF, Word and Excel output · powered by live indicators"}</div>
          </div>
        </div>
      </div>

      <div className="page-body">
        {/* Generator */}
        <ReportGeneratorForm
          programmes={programmes}
          projects={projects}
          lang={lang}
          onGenerate={handleGenerate}
          busy={busy}
        />

        <div style={{ height: 16 }} />

        {/* Saved reports list */}
        <div className="card">
          <div className="card-head">
            <div className="card-title">{lang === "fr" ? "Rapports enregistrés" : "Saved reports"}</div>
            <span className="pill">{reports.length}</span>
          </div>
          <div className="card-body flush">
            {reports.length === 0 && (
              <div className="text-faint" style={{ padding: 18, fontSize: 12 }}>
                {lang === "fr" ? "Aucun rapport enregistré pour l'instant." : "No saved reports yet."}
              </div>
            )}
            {reports.map((r) => (
              <div key={r.id} className="report-card">
                <div className="report-thumb">{r.thumb}</div>
                <div>
                  <div style={{ fontWeight: 500 }}>{r.title}</div>
                  <div className="text-faint" style={{ fontSize: 11.5, marginTop: 2 }}>
                    <span className="tag-mono">{r.id}</span> · {r.who} · {r.when}
                  </div>
                </div>
                <div className="row" style={{ gap: 6 }}>
                  {stateBadge(r.state)}
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Preview modal */}
      {generated && (
        <ReportPreviewModal
          data={generated.data}
          meta={generated.meta}
          lang={lang}
          onClose={() => setGenerated(null)}
          onSave={handleSave}
          saved={saved}
          submitted={submitted}
          onSubmitForValidation={savedReportId ? async () => {
            try {
              const result = await window.melr.submitForValidation({
                object_type: "report",
                object_id: savedReportId,
                project_id: generated.meta.scope.mode === "project" ? generated.meta.scope.id : null,
                title: generated.meta.title,
                total_steps: 4,
                sla_days: 7,
                priority: "normal",
              });
              setSubmitted(true);
              alert(lang === "fr"
                ? (result.reused ? "Validation déjà en cours (réutilisée)." : "Rapport soumis pour validation.")
                : (result.reused ? "Validation already in flight." : "Report submitted for validation."));
            } catch (e) {
              alert((lang === "fr" ? "Erreur : " : "Error: ") + e.message);
            }
          } : null}
        />
      )}
    </div>
  );
}
window.Reporting = Reporting;

// ==================== LEARNING ====================
function Learning({ t, lang }) {
  const { data: liveLQ } = window.melr.useLearningQuestions();
  const FIXTURE_QUESTIONS = [
    { id: "LQ-12", t: lang === "fr" ? "Pourquoi l'adoption des moustiquaires chute-t-elle de 18% en saison sèche ?" : "Why does ITN adoption drop 18% in dry season?", proj: "P-220", state: "active", own: "P. Nguema", date: "10/04/2026", evidence: 8, comments: 14, tags: [lang === "fr" ? "Comportement" : "Behavior", "ITN"] },
    { id: "LQ-11", t: lang === "fr" ? "Quel est l'effet réel de l'incentive ASC sur la rétention à 12 mois ?" : "What is the real effect of CHW incentive on 12-month retention?", proj: "P-227", state: "instructed", own: "N. Mukamana", date: "22/03/2026", evidence: 12, comments: 21, tags: ["RH", lang === "fr" ? "Rétention" : "Retention"] },
    { id: "LQ-10", t: lang === "fr" ? "L'externalisation transport vaccins a-t-elle réduit les ruptures ?" : "Did vaccine transport outsourcing reduce stockouts?", proj: "P-229", state: "active", own: "A. Tesfaye", date: "18/03/2026", evidence: 5, comments: 9, tags: ["Logistique", "DTC3"] },
    { id: "LQ-09", t: lang === "fr" ? "Pourquoi le succès thérapeutique TB plafonne à 74% ?" : "Why is TB treatment success plateauing at 74%?", proj: "P-238", state: "active", own: "M. Sané", date: "05/03/2026", evidence: 11, comments: 18, tags: ["TB", lang === "fr" ? "Adhérence" : "Adherence"] },
    { id: "LQ-08", t: lang === "fr" ? "Quels facteurs expliquent la sur-performance d'Atlas sur la santé maternelle ?" : "What drives the over-performance of Atlas on maternal health?", proj: "P-235", state: "closed", own: "L. Benali", date: "12/02/2026", evidence: 17, comments: 28, tags: [lang === "fr" ? "Santé maternelle" : "Maternal health"] },
  ];
  const questions = (liveLQ && liveLQ.length > 0)
    ? liveLQ.map((q) => ({
        id: q.code || q.id,
        t: q.question,
        proj: (q.projects && q.projects.code) || "—",
        state: q.state || "active",
        own: (q.owner && q.owner.full_name) || "—",
        date: new Date(q.created_at).toLocaleDateString(lang === "fr" ? "fr-FR" : "en-US"),
        evidence: 0,
        comments: 0,
        tags: q.tags || [],
      }))
    : FIXTURE_QUESTIONS;
  const insights = [
    { t: lang === "fr" ? "L'effet ASC dépend du paquet d'accompagnement, pas de l'incentive seul" : "CHW effect depends on support package, not incentive alone", from: "LQ-11", strength: "high" },
    { t: lang === "fr" ? "La perception « saison sèche = peu de moustiques » sous-estime la transmission résiduelle" : "Perception 'dry season = few mosquitos' underestimates residual transmission", from: "LQ-12", strength: "med" },
    { t: lang === "fr" ? "L'externalisation logistique est efficace ssi la maintenance reste interne" : "Logistics outsourcing works only if maintenance stays in-house", from: "LQ-10", strength: "med" },
    { t: lang === "fr" ? "Maillage CPN dense (≥1 pt./3 km) explique 62% de la variance Atlas" : "Dense ANC mesh (≥1 pt./3km) explains 62% of Atlas variance", from: "LQ-08", strength: "high" },
  ];

  return (
    <div className="page">
      <div className="page-header">
        <div className="page-eyebrow">{lang === "fr" ? "MODULE / APPRENTISSAGE" : "MODULE / LEARNING"}</div>
        <div className="page-header-row">
          <div>
            <h1 className="page-title">{t("nav.learning")}</h1>
            <div className="page-sub">{lang === "fr"
              ? "Questions d'apprentissage instruites par projet · evidence-base structurée · synthèse trans-projets"
              : "Learning questions instructed per project · structured evidence base · cross-project synthesis"}</div>
          </div>
          <div className="page-header-actions">
            <button className="btn sm"><Icon.brain /> {lang === "fr" ? "Synthèse trans-projets" : "Cross-project synthesis"}</button>
            <button className="btn sm primary"><Icon.plus /> {lang === "fr" ? "Nouvelle question" : "New question"}</button>
          </div>
        </div>
      </div>
      <div className="page-body">
        <div className="grid" style={{ gridTemplateColumns: "1.5fr 1fr", gap: 16 }}>
          <div className="card">
            <div className="card-head">
              <div className="card-title">{lang === "fr" ? "Questions d'apprentissage" : "Learning questions"}</div>
              <span className="pill">{questions.length}</span>
            </div>
            <div className="card-body flush">
              {questions.map((q) => (
                <div key={q.id} style={{ padding: "14px 16px", borderBottom: "1px solid var(--line-faint)" }}>
                  <div className="row" style={{ marginBottom: 6 }}>
                    <span className="tag-mono">{q.id}</span>
                    <span className="tag-mono">{q.proj}</span>
                    {q.state === "active" && <span className="pill accent dot">{lang === "fr" ? "En instruction" : "In progress"}</span>}
                    {q.state === "instructed" && <span className="pill green dot">{lang === "fr" ? "Instruite" : "Instructed"}</span>}
                    {q.state === "closed" && <span className="pill">{lang === "fr" ? "Clôturée" : "Closed"}</span>}
                    <div style={{ flex: 1 }}></div>
                    <span className="text-faint" style={{ fontSize: 11 }}>{q.date}</span>
                  </div>
                  <div style={{ fontWeight: 500, fontSize: 13.5, marginBottom: 6, textWrap: "pretty" }}>{q.t}</div>
                  <div className="row" style={{ fontSize: 11.5, color: "var(--text-faint)", gap: 12 }}>
                    <span><Icon.user className="sm" /> {q.own}</span>
                    <span><Icon.fileText className="sm" /> {q.evidence} {lang === "fr" ? "preuves" : "evidence"}</span>
                    <span><Icon.message className="sm" /> {q.comments}</span>
                    <div style={{ flex: 1 }}></div>
                    {q.tags.map((tag) => <span key={tag} className="pill" style={{ fontSize: 10.5 }}>{tag}</span>)}
                  </div>
                </div>
              ))}
            </div>
          </div>

          <div className="card">
            <div className="card-head">
              <div className="card-title">{lang === "fr" ? "Insights consolidés" : "Consolidated insights"}</div>
              <span className="pill accent">14</span>
            </div>
            <div className="card-body flush">
              {insights.map((i, idx) => (
                <div key={idx} style={{ padding: "14px 16px", borderBottom: "1px solid var(--line-faint)" }}>
                  <div className="row" style={{ marginBottom: 6 }}>
                    {i.strength === "high" && <span className="pill green">{lang === "fr" ? "Évidence haute" : "High evidence"}</span>}
                    {i.strength === "med" && <span className="pill amber">{lang === "fr" ? "Évidence moyenne" : "Medium evidence"}</span>}
                    <span className="tag-mono" style={{ marginLeft: "auto" }}>← {i.from}</span>
                  </div>
                  <div style={{ fontSize: 12.5, textWrap: "pretty" }}>{i.t}</div>
                </div>
              ))}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
window.Learning = Learning;
