/* 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 }) {
  // Defensive : un status inconnu (typo, ancien code) retombe sur
  // neutral plutot que d'exploser sur undefined.border.
  const c = STATUS_COLORS[status || "neutral"] || STATUS_COLORS.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.
// --------------------------------------------------------------------------
// ============================================================================
// REPORTING · ACTIVITES — agrégé multi-projets
// ----------------------------------------------------------------------------
// Vue analytique sur toutes les activités visibles (RLS multi-org), avec :
//   - filtres : période, projet(s), type(s), statut(s)
//   - KPIs : total, par statut, partenaires impliqués, indicateurs liés
//   - graphes : par mois, par statut, par type, par projet
//   - tableau filtré + export CSV
// ============================================================================

const ACT_STATUS_META = {
  not_started: { fr: "Non démarrée",  en: "Not started", color: "#94a3b8" },
  ongoing:     { fr: "En cours",       en: "Ongoing",     color: "#0ea5e9" },
  completed:   { fr: "Terminée",       en: "Completed",   color: "#16a34a" },
  postponed:   { fr: "Reportée",       en: "Postponed",   color: "#f59e0b" },
  cancelled:   { fr: "Annulée",        en: "Cancelled",   color: "#dc2626" },
};
const ACT_STATUS_KEYS = ["not_started", "ongoing", "completed", "postponed", "cancelled"];

// Petit bar-chart horizontal SVG-less (div pur). count + label + barre.
function ARBar({ label, value, max, color, sub }) {
  const pct = max > 0 ? Math.round((value / max) * 100) : 0;
  return (
    <div style={{ marginBottom: 6 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 2 }}>
        <span style={{ fontSize: 12, color: "var(--text)" }}>{label}{sub && <span className="text-faint" style={{ marginLeft: 6, fontSize: 11 }}>{sub}</span>}</span>
        <span className="mono" style={{ fontSize: 12, fontWeight: 600 }}>{value}</span>
      </div>
      <div style={{ height: 8, background: "var(--bg-sunken)", borderRadius: 4, overflow: "hidden" }}>
        <div style={{ height: "100%", width: pct + "%", background: color || "var(--accent)", borderRadius: 4, transition: "width 200ms ease" }}></div>
      </div>
    </div>
  );
}

// Bar-chart vertical : 12 colonnes (1 par mois). data = [count_jan, ..., count_dec].
function ARMonthlyChart({ data, lang, year }) {
  const max = Math.max(1, ...data);
  const months = lang === "fr"
    ? ["Jan", "Fév", "Mar", "Avr", "Mai", "Juin", "Juil", "Aoû", "Sep", "Oct", "Nov", "Déc"]
    : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  return (
    <div style={{ display: "flex", alignItems: "flex-end", gap: 6, height: 140, padding: "8px 4px" }}>
      {data.map((v, i) => {
        const h = max > 0 ? Math.round((v / max) * 100) : 0;
        return (
          <div key={i} style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 4 }}>
            <div style={{ fontSize: 10, color: "var(--text-faint)", height: 12 }}>{v > 0 ? v : ""}</div>
            <div style={{ width: "100%", height: h + "%", minHeight: v > 0 ? 4 : 0, background: "var(--accent)", borderRadius: "3px 3px 0 0", opacity: v > 0 ? 0.85 : 0.15 }}></div>
            <div style={{ fontSize: 10, color: "var(--text-faint)" }}>{months[i]}</div>
          </div>
        );
      })}
    </div>
  );
}

// ── Helpers d'export · Activités ────────────────────────────────────────────
// Reusable filename suffix : -YYYY-MM-DD
function _arDate() { return new Date().toISOString().slice(0, 10); }

// Capture le ref donné via le wrapper OKLCH-safe, retourne un PNG dataURL ou null.
async function _arCaptureRef(ref) {
  if (!ref || !ref.current) return null;
  const capture = (window.melr && window.melr.captureElement)
    || (window.html2canvas ? (el, o) => window.html2canvas(el, o) : null);
  if (!capture) return null;
  try {
    const canvas = await capture(ref.current, {
      backgroundColor: "#ffffff", scale: 2, useCORS: true,
    });
    return canvas.toDataURL("image/png", 1);
  } catch (e) { console.warn("[arCapture]", e); return null; }
}

function _arDataUrlToBytes(dataUrl) {
  const base64 = dataUrl.split(",")[1] || "";
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
  return bytes;
}

function _arImageDims(dataUrl, maxWidth) {
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => {
      const w = img.naturalWidth, h = img.naturalHeight;
      const scale = w > maxWidth ? maxWidth / w : 1;
      resolve({ width: Math.round(w * scale), height: Math.round(h * scale) });
    };
    img.onerror = () => resolve({ width: 400, height: 250 });
    img.src = dataUrl;
  });
}

// ── Export Excel (xlsx) ────────────────────────────────────────────────────
function exportActivitiesXlsx(filtered, stats, helpers, lang, meta) {
  if (!window.XLSX) { alert(lang === "fr" ? "Bibliothèque Excel indisponible." : "Excel library unavailable."); return; }
  const X = window.XLSX;
  const wb = X.utils.book_new();
  // Sheet 1 — Résumé (KPIs + agrégations)
  const sumRows = [
    [lang === "fr" ? "Rapport Activités" : "Activities Report"],
    [lang === "fr" ? "Période" : "Period", meta.periodLabel],
    [lang === "fr" ? "Généré le" : "Generated", new Date().toLocaleString()],
    [],
    [lang === "fr" ? "Indicateurs clés" : "Key indicators"],
    [lang === "fr" ? "Total activités" : "Total activities",       stats.total],
    [lang === "fr" ? "Terminées" : "Completed",                    stats.byStatus.completed],
    [lang === "fr" ? "En cours" : "Ongoing",                        stats.byStatus.ongoing],
    [lang === "fr" ? "Non démarrées" : "Not started",               stats.byStatus.not_started],
    [lang === "fr" ? "Reportées" : "Postponed",                     stats.byStatus.postponed],
    [lang === "fr" ? "Annulées" : "Cancelled",                      stats.byStatus.cancelled],
    [lang === "fr" ? "Partenaires impliqués" : "Partners involved", stats.partnersUsed],
    [lang === "fr" ? "Indicateurs liés" : "Indicators linked",      stats.indicatorsUsed],
    [lang === "fr" ? "Avec GPS" : "With GPS",                       stats.withGps],
    [],
    [lang === "fr" ? "Par statut" : "By status"],
    ...ACT_STATUS_KEYS.map((k) => [lang === "fr" ? ACT_STATUS_META[k].fr : ACT_STATUS_META[k].en, stats.byStatus[k]]),
    [],
    [lang === "fr" ? "Par mois" : "By month", meta.year],
    ...(lang === "fr"
      ? ["Jan","Fév","Mar","Avr","Mai","Juin","Juil","Août","Sep","Oct","Nov","Déc"]
      : ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]
    ).map((m, i) => [m, stats.byMonth[i]]),
  ];
  const ws1 = X.utils.aoa_to_sheet(sumRows);
  ws1["!cols"] = [{ wch: 32 }, { wch: 14 }];
  X.utils.book_append_sheet(wb, ws1, lang === "fr" ? "Résumé" : "Summary");

  // Sheet 2 — Détail des activités
  const detailHeader = [
    "Code", lang === "fr" ? "Titre" : "Title", lang === "fr" ? "Projet" : "Project",
    lang === "fr" ? "Type" : "Type", lang === "fr" ? "Statut" : "Status",
    lang === "fr" ? "Début" : "Start", lang === "fr" ? "Fin" : "End",
    lang === "fr" ? "Nb partenaires" : "Partners", lang === "fr" ? "Nb indicateurs" : "Indicators",
    "GPS lat", "GPS lng", lang === "fr" ? "Budget prévu" : "Planned budget",
    lang === "fr" ? "Coût réel" : "Actual cost", lang === "fr" ? "Devise" : "Currency",
    lang === "fr" ? "Lieu" : "Location", lang === "fr" ? "Description" : "Description",
  ];
  const detailRows = filtered.map((a) => [
    a.code || "",
    a.title || "",
    helpers.projectLabel(a.project_id),
    helpers.typeLabel(a.activity_type_id),
    (ACT_STATUS_META[a.status] || {})[lang === "fr" ? "fr" : "en"] || a.status,
    a.start_date || "",
    a.end_date || "",
    (helpers.partnerByActivity.get(a.id) || []).length,
    (helpers.indicatorByActivity.get(a.id) || []).length,
    a.latitude || "",
    a.longitude || "",
    a.budget_planned || "",
    a.cost_actual || "",
    a.currency || "",
    a.location_name || "",
    a.description || "",
  ]);
  const ws2 = X.utils.aoa_to_sheet([detailHeader, ...detailRows]);
  ws2["!cols"] = detailHeader.map(() => ({ wch: 16 }));
  X.utils.book_append_sheet(wb, ws2, lang === "fr" ? "Activités" : "Activities");

  // Sheet 3 — Agrégations top
  const aggRows = [];
  aggRows.push([lang === "fr" ? "Top types d'activité" : "Top activity types"]);
  aggRows.push([lang === "fr" ? "Type" : "Type", lang === "fr" ? "Nombre" : "Count"]);
  Array.from(stats.byType.entries()).sort((a, b) => b[1] - a[1]).forEach(([id, c]) => {
    aggRows.push([helpers.typeLabel(id), c]);
  });
  aggRows.push([], [lang === "fr" ? "Top projets" : "Top projects"]);
  aggRows.push([lang === "fr" ? "Projet" : "Project", lang === "fr" ? "Nombre" : "Count"]);
  Array.from(stats.byProject.entries()).sort((a, b) => b[1] - a[1]).forEach(([id, c]) => {
    aggRows.push([helpers.projectLabel(id), c]);
  });
  const ws3 = X.utils.aoa_to_sheet(aggRows);
  ws3["!cols"] = [{ wch: 40 }, { wch: 12 }];
  X.utils.book_append_sheet(wb, ws3, lang === "fr" ? "Agrégations" : "Aggregations");

  X.writeFile(wb, "activites-" + _arDate() + ".xlsx");
}

// ── Export PDF (jsPDF) ─────────────────────────────────────────────────────
async function exportActivitiesPdf(filtered, stats, helpers, lang, meta, chartsRef) {
  if (!window.jspdf) { alert(lang === "fr" ? "Bibliothèque PDF indisponible." : "PDF library unavailable."); return; }
  const { jsPDF } = window.jspdf;
  // Paysage A4 : 842 x 595. On a besoin de la largeur pour le tableau
  // detaille des activites (sinon les colonnes deboitent a droite).
  const pdf = new jsPDF({ orientation: "landscape", unit: "pt", format: "a4" });
  const W = 842, H = 595, M = 36, contentW = W - 2 * M; // 770pt utiles
  let y = 44;
  const lineH = 13;
  const ensureSpace = (need) => { if (y + need > H - 36) { pdf.addPage(); y = 44; } };

  // ── Helper : truncate intelligent base sur la mesure reelle du texte
  // jsPDF.getTextWidth() retourne la largeur dans l'unite courante (pt
  // ici). On coupe caractere par caractere jusqu'a tenir dans maxW, puis
  // on ajoute "...".
  const fitText = (s, maxW) => {
    if (!s) return "";
    s = String(s);
    if (pdf.getTextWidth(s) <= maxW) return s;
    let lo = 0, hi = s.length;
    while (lo < hi) {
      const mid = (lo + hi + 1) >> 1;
      if (pdf.getTextWidth(s.slice(0, mid) + "…") <= maxW) lo = mid;
      else hi = mid - 1;
    }
    return s.slice(0, lo) + "…";
  };

  // Header
  pdf.setFontSize(16); pdf.setFont(undefined, "bold");
  pdf.setTextColor("#4f46e5");
  pdf.text(lang === "fr" ? "Rapport Activités" : "Activities Report", M, y); y += 22;
  pdf.setFontSize(10); pdf.setFont(undefined, "normal");
  pdf.setTextColor("#666666");
  pdf.text((lang === "fr" ? "Période : " : "Period: ") + meta.periodLabel, M, y); y += lineH;
  pdf.text((lang === "fr" ? "Généré le : " : "Generated: ") + new Date().toLocaleString(), M, y); y += lineH + 6;
  pdf.setTextColor("#000000");

  // KPIs · en grille 2 colonnes pour profiter du paysage
  pdf.setFontSize(12); pdf.setFont(undefined, "bold");
  pdf.setTextColor("#4f46e5");
  pdf.text(lang === "fr" ? "Indicateurs clés" : "Key indicators", M, y); y += lineH + 4;
  pdf.setTextColor("#000000");
  pdf.setFontSize(10); pdf.setFont(undefined, "normal");
  const kpis = [
    [lang === "fr" ? "Total activités" : "Total activities",       stats.total],
    [lang === "fr" ? "Terminées" : "Completed",                    stats.byStatus.completed],
    [lang === "fr" ? "En cours" : "Ongoing",                        stats.byStatus.ongoing],
    [lang === "fr" ? "Non démarrées" : "Not started",               stats.byStatus.not_started],
    [lang === "fr" ? "Reportées" : "Postponed",                     stats.byStatus.postponed],
    [lang === "fr" ? "Annulées" : "Cancelled",                      stats.byStatus.cancelled],
    [lang === "fr" ? "Partenaires impliqués" : "Partners involved", stats.partnersUsed],
    [lang === "fr" ? "Indicateurs liés" : "Indicators linked",      stats.indicatorsUsed],
  ];
  const colGapX = (contentW - 20) / 2;
  for (let i = 0; i < kpis.length; i += 2) {
    ensureSpace(lineH);
    const [k1, v1] = kpis[i];
    pdf.text("• " + k1 + " : ", M, y);
    pdf.setFont(undefined, "bold"); pdf.text(String(v1), M + 230, y); pdf.setFont(undefined, "normal");
    if (kpis[i + 1]) {
      const [k2, v2] = kpis[i + 1];
      pdf.text("• " + k2 + " : ", M + colGapX + 20, y);
      pdf.setFont(undefined, "bold"); pdf.text(String(v2), M + colGapX + 250, y); pdf.setFont(undefined, "normal");
    }
    y += lineH;
  }
  y += 8;

  // Capture du tableau de bord (KPIs + charts) si dispo
  const chartImg = await _arCaptureRef(chartsRef);
  if (chartImg) {
    const dims = await _arImageDims(chartImg, contentW);
    ensureSpace(dims.height + 16);
    pdf.setFontSize(12); pdf.setFont(undefined, "bold"); pdf.setTextColor("#4f46e5");
    pdf.text(lang === "fr" ? "Vue d'ensemble" : "Overview", M, y); y += lineH + 4;
    pdf.setTextColor("#000000"); pdf.setFont(undefined, "normal");
    pdf.addImage(chartImg, "PNG", M, y, dims.width, dims.height);
    y += dims.height + 12;
  }

  // ── Tableau detaille ─────────────────────────────────────────────────
  // Page A4 paysage = 842pt, marges 36 → 770pt utiles. On reserve la
  // somme exacte pour eviter tout depassement, et chaque cellule est
  // tronquee avec fitText() qui mesure le texte reel.
  pdf.addPage(); y = 44;
  pdf.setFontSize(13); pdf.setFont(undefined, "bold"); pdf.setTextColor("#4f46e5");
  pdf.text(lang === "fr" ? "Détail des activités" : "Activity details", M, y); y += lineH + 6;
  pdf.setTextColor("#000000");
  pdf.setFontSize(8); pdf.setFont(undefined, "normal");

  const cols = [
    { key: "code",    label: "Code",                                      w: 50  },
    { key: "title",   label: lang === "fr" ? "Activité" : "Activity",      w: 200 },
    { key: "project", label: lang === "fr" ? "Projet" : "Project",         w: 140 },
    { key: "type",    label: lang === "fr" ? "Type" : "Type",              w: 110 },
    { key: "status",  label: lang === "fr" ? "Statut" : "Status",          w: 80  },
    { key: "start",   label: lang === "fr" ? "Début" : "Start",            w: 50  },
    { key: "end",     label: lang === "fr" ? "Fin" : "End",                w: 50  },
    { key: "part",    label: lang === "fr" ? "Part." : "Part.",            w: 40  },
    { key: "ind",     label: lang === "fr" ? "Ind." : "Ind.",              w: 50  },
  ]; // total : 770

  // Position X de chaque colonne (debut)
  let acc = M;
  cols.forEach((c) => { c.x = acc; acc += c.w; });

  // Padding cellule
  const pad = 4;

  // Largeur de texte utile dans chaque colonne (largeur - 2*padding)
  cols.forEach((c) => { c.textW = c.w - 2 * pad; });

  // Repaint header (utilisable apres chaque addPage)
  const paintHeader = () => {
    ensureSpace(lineH + 4);
    pdf.setFillColor("#eef2ff");
    pdf.rect(M, y - 9, contentW, lineH, "F");
    pdf.setFont(undefined, "bold"); pdf.setTextColor("#3730a3"); pdf.setFontSize(8);
    cols.forEach((c) => pdf.text(c.label, c.x + pad, y));
    pdf.setTextColor("#000000"); pdf.setFont(undefined, "normal");
    y += lineH;
  };
  paintHeader();

  // Lignes : alternance fond gris clair pour la lisibilite
  filtered.forEach((a, i) => {
    ensureSpace(lineH);
    if (i % 2 === 1) {
      pdf.setFillColor("#f9fafb");
      pdf.rect(M, y - 9, contentW, lineH, "F");
    }
    const values = {
      code:    a.code || "",
      title:   a.title || "",
      project: helpers.projectLabel(a.project_id),
      type:    helpers.typeLabel(a.activity_type_id),
      status:  (ACT_STATUS_META[a.status] || {})[lang === "fr" ? "fr" : "en"] || a.status || "",
      start:   a.start_date || "—",
      end:     a.end_date || "—",
      part:    String((helpers.partnerByActivity.get(a.id) || []).length),
      ind:     String((helpers.indicatorByActivity.get(a.id) || []).length),
    };
    cols.forEach((c) => {
      const txt = fitText(values[c.key], c.textW);
      pdf.text(txt, c.x + pad, y);
    });
    y += lineH;
    // Re-imprime le header si on saute une page au milieu du tableau
    if (y > H - 36) { pdf.addPage(); y = 44; paintHeader(); }
  });

  pdf.save("activites-" + _arDate() + ".pdf");
}

// ── Export Word (docx) ─────────────────────────────────────────────────────
async function exportActivitiesDocx(filtered, stats, helpers, lang, meta, chartsRef) {
  if (!window.docx) { alert(lang === "fr" ? "Bibliothèque Word indisponible." : "Word library unavailable."); return; }
  const D = window.docx;
  const p = (text, opts = {}) => new D.Paragraph({
    children: [new D.TextRun({ text, bold: opts.bold, italics: opts.italic, color: opts.color, size: opts.size ? opts.size * 2 : undefined })],
    alignment: opts.align,
    spacing: { after: opts.after || 80 },
  });
  const h1 = (t) => new D.Paragraph({
    children: [new D.TextRun({ text: t, bold: true, color: "4F46E5", size: 28 })],
    spacing: { before: 200, after: 120 },
  });
  const h2 = (t) => new D.Paragraph({
    children: [new D.TextRun({ text: t, bold: true, color: "111111", size: 22 })],
    spacing: { before: 120, after: 80 },
  });

  const children = [
    p(lang === "fr" ? "Rapport Activités" : "Activities Report", { bold: true, size: 18, align: D.AlignmentType.CENTER }),
    p((lang === "fr" ? "Période : " : "Period: ") + meta.periodLabel, { italic: true, color: "666666", align: D.AlignmentType.CENTER }),
    p((lang === "fr" ? "Généré le : " : "Generated: ") + new Date().toLocaleString(), { italic: true, color: "666666", align: D.AlignmentType.CENTER, after: 240 }),
    h1(lang === "fr" ? "1. Indicateurs clés" : "1. Key indicators"),
  ];
  const kpiRows = [
    [lang === "fr" ? "Total activités" : "Total activities",       String(stats.total)],
    [lang === "fr" ? "Terminées" : "Completed",                    String(stats.byStatus.completed)],
    [lang === "fr" ? "En cours" : "Ongoing",                        String(stats.byStatus.ongoing)],
    [lang === "fr" ? "Non démarrées" : "Not started",               String(stats.byStatus.not_started)],
    [lang === "fr" ? "Reportées" : "Postponed",                     String(stats.byStatus.postponed)],
    [lang === "fr" ? "Annulées" : "Cancelled",                      String(stats.byStatus.cancelled)],
    [lang === "fr" ? "Partenaires impliqués" : "Partners involved", String(stats.partnersUsed)],
    [lang === "fr" ? "Indicateurs liés" : "Indicators linked",      String(stats.indicatorsUsed)],
    [lang === "fr" ? "Avec GPS" : "With GPS",                       String(stats.withGps)],
  ];
  children.push(new D.Table({
    rows: kpiRows.map(([k, v]) => new D.TableRow({ children: [
      new D.TableCell({ children: [p(k, { bold: true })] }),
      new D.TableCell({ children: [p(v)] }),
    ]})),
    width: { size: 100, type: D.WidthType.PERCENTAGE },
  }));

  // Image capture du dashboard (KPI + charts) si dispo
  const chartImg = await _arCaptureRef(chartsRef);
  if (chartImg) {
    const dims = await _arImageDims(chartImg, 640);
    children.push(h1(lang === "fr" ? "2. Vue d'ensemble" : "2. Overview"));
    children.push(new D.Paragraph({
      children: [new D.ImageRun({ data: _arDataUrlToBytes(chartImg), transformation: dims })],
      alignment: D.AlignmentType.CENTER,
      spacing: { after: 160 },
    }));
  }

  // Tableau détaillé
  children.push(h1(lang === "fr" ? "3. Détail des activités" : "3. Activity details"));
  children.push(p((lang === "fr" ? "Total : " : "Total: ") + filtered.length, { italic: true, color: "666666" }));
  const header = [
    "Code", lang === "fr" ? "Activité" : "Activity",
    lang === "fr" ? "Projet" : "Project", lang === "fr" ? "Type" : "Type",
    lang === "fr" ? "Statut" : "Status", lang === "fr" ? "Début" : "Start", lang === "fr" ? "Fin" : "End",
  ];
  const detailTable = new D.Table({
    rows: [
      new D.TableRow({ tableHeader: true, children: header.map((h) =>
        new D.TableCell({ children: [p(h, { bold: true })] })
      )}),
      ...filtered.map((a) => new D.TableRow({ children: [
        new D.TableCell({ children: [p(a.code || "")] }),
        new D.TableCell({ children: [p(a.title || "")] }),
        new D.TableCell({ children: [p(helpers.projectLabel(a.project_id))] }),
        new D.TableCell({ children: [p(helpers.typeLabel(a.activity_type_id))] }),
        new D.TableCell({ children: [p((ACT_STATUS_META[a.status] || {})[lang === "fr" ? "fr" : "en"] || a.status || "")] }),
        new D.TableCell({ children: [p(a.start_date || "—")] }),
        new D.TableCell({ children: [p(a.end_date || "—")] }),
      ]})),
    ],
    width: { size: 100, type: D.WidthType.PERCENTAGE },
  });
  children.push(detailTable);

  const doc = new D.Document({
    creator: "MELR",
    title: "Rapport Activités",
    sections: [{ properties: {}, children }],
  });
  const blob = await D.Packer.toBlob(doc);
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = "activites-" + _arDate() + ".docx";
  document.body.appendChild(a); a.click(); document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

function ActivitiesReporting({ lang, projects, actingOrgId, myOrgId, isSuperAdmin }) {
  const { useState, useMemo, useRef } = React;
  // ── Données ─────────────────────────────────────────────────────────
  // Defensive : si l'utilisateur tombe sur une combinaison cache SW
  // periodique (vieux melr-data + nouvel ecran), les hooks n'existent
  // pas encore. On fournit un fallback inerte pour eviter une page
  // blanche, et on affiche un message de patience.
  const haveActivities = !!(window.melr && window.melr.useActivities);
  const haveTypes      = !!(window.melr && window.melr.useActivityTypes);
  const havePartners   = !!(window.melr && window.melr.usePartners);
  const haveRelations  = !!(window.melr && window.melr.useActivityRelationsBulk);
  const _useAct  = haveActivities ? window.melr.useActivities       : () => ({ data: [],          loading: false });
  const _useType = haveTypes      ? window.melr.useActivityTypes    : () => ({ data: [] });
  const _usePart = havePartners   ? window.melr.usePartners         : () => ({ data: [] });
  const _useRel  = haveRelations  ? window.melr.useActivityRelationsBulk
                                  : () => ({ partnerByActivity: new Map(), indicatorByActivity: new Map(), loading: false });
  const { data: activities, loading: actLoad } = _useAct();          // RLS-filtered
  const { data: activityTypes }                = _useType();
  const { data: partners }                     = _usePart();
  const { partnerByActivity, indicatorByActivity, loading: relLoad } = _useRel();
  // Si l'API n'est pas encore prete (cache obsolete), on demande un reload.
  if (!haveRelations) {
    return (
      <div className="card">
        <div className="card-body" style={{ padding: 32, textAlign: "center" }}>
          <div style={{ marginBottom: 10, color: "var(--text)" }}>
            {lang === "fr"
              ? "Le module Activités vient d'être mis à jour."
              : "The Activities module was just updated."}
          </div>
          <button className="btn primary" onClick={() => {
            if (navigator.serviceWorker) {
              navigator.serviceWorker.getRegistrations().then((regs) => Promise.all(regs.map((r) => r.unregister())))
                .finally(() => window.location.reload());
            } else { window.location.reload(); }
          }}>
            {lang === "fr" ? "Recharger pour activer" : "Reload to activate"}
          </button>
        </div>
      </div>
    );
  }

  // Acting-as-org : restreindre les activités aux projets de l'org cible.
  const acting = !!(isSuperAdmin && actingOrgId && actingOrgId !== myOrgId);
  const projectIdsInScope = useMemo(() => {
    if (!acting) return null;
    return new Set((projects || []).map((p) => p.uuid));
  }, [acting, projects]);

  // Ref qui enveloppe KPIs + 4 graphes pour la capture (PDF / Word).
  const chartsRef = useRef(null);

  // ── Filtres ─────────────────────────────────────────────────────────
  const now = new Date();
  const [year, setYear] = useState(now.getFullYear());
  const [preset, setPreset] = useState("Y");
  const [customStart, setCustomStart] = useState(year + "-01-01");
  const [customEnd, setCustomEnd]     = useState(year + "-12-31");
  const [projectFilter, setProjectFilter] = useState("");        // uuid or ""
  const [typeFilter, setTypeFilter]       = useState("");        // uuid or ""
  const [statusFilter, setStatusFilter]   = useState("");        // status or ""
  const [partnerFilter, setPartnerFilter] = useState("");        // uuid or ""

  // ── D2 · Filtres sauvegardés ────────────────────────────────────────
  // Sérialise l'état actuel des filtres en jsonb pour sauvegarde, et
  // applique un état sauvegardé en restaurant chaque setter.
  const currentFilters = {
    year, preset, customStart, customEnd,
    projectFilter, typeFilter, statusFilter, partnerFilter,
  };
  const applySavedFilters = (state) => {
    if (!state) return;
    if (state.year != null)        setYear(Number(state.year));
    if (state.preset != null)      setPreset(state.preset);
    if (state.customStart != null) setCustomStart(state.customStart);
    if (state.customEnd != null)   setCustomEnd(state.customEnd);
    if (state.projectFilter != null) setProjectFilter(state.projectFilter);
    if (state.typeFilter != null)    setTypeFilter(state.typeFilter);
    if (state.statusFilter != null)  setStatusFilter(state.statusFilter);
    if (state.partnerFilter != null) setPartnerFilter(state.partnerFilter);
  };

  const period = periodFromPreset(year, preset, customStart, customEnd);

  // ── Activités filtrées ──────────────────────────────────────────────
  const filtered = useMemo(() => {
    const start = new Date(period.start);
    const end   = new Date(period.end);
    return (activities || []).filter((a) => {
      if (projectIdsInScope && !projectIdsInScope.has(a.project_id)) return false;
      if (projectFilter && a.project_id !== projectFilter) return false;
      if (typeFilter && a.activity_type_id !== typeFilter) return false;
      if (statusFilter && a.status !== statusFilter) return false;
      if (partnerFilter) {
        const links = partnerByActivity.get(a.id) || [];
        if (!links.includes(partnerFilter)) return false;
      }
      // Période : on garde si [start_date, end_date] croise [period.start, period.end]
      const aStart = a.start_date ? new Date(a.start_date) : null;
      const aEnd   = a.end_date   ? new Date(a.end_date)   : aStart;
      if (aStart && aEnd) {
        if (aEnd < start || aStart > end) return false;
      } else if (aStart) {
        if (aStart < start || aStart > end) return false;
      }
      // Si pas de dates du tout, on garde (l'utilisateur pourrait vouloir voir
      // les activités non datées dans le bilan).
      return true;
    });
  }, [activities, period.start, period.end, projectFilter, typeFilter, statusFilter, partnerFilter, partnerByActivity, projectIdsInScope]);

  // ── Agrégats ───────────────────────────────────────────────────────
  const stats = useMemo(() => {
    const byStatus  = Object.fromEntries(ACT_STATUS_KEYS.map((k) => [k, 0]));
    const byType    = new Map();   // type_id → count
    const byProject = new Map();   // project_id → count
    const byMonth   = new Array(12).fill(0);
    const partnersUsed = new Set();
    const indicatorsUsed = new Set();
    let withGps = 0;
    filtered.forEach((a) => {
      byStatus[a.status] = (byStatus[a.status] || 0) + 1;
      if (a.activity_type_id) byType.set(a.activity_type_id, (byType.get(a.activity_type_id) || 0) + 1);
      if (a.project_id)       byProject.set(a.project_id,   (byProject.get(a.project_id)   || 0) + 1);
      // Mois de début (ou de fin si pas de début)
      const d = a.start_date || a.end_date;
      if (d) {
        const dt = new Date(d);
        // On compte uniquement les mois de l'année filtrée pour ne pas
        // mélanger les années.
        if (dt.getFullYear() === year) byMonth[dt.getMonth()] += 1;
      }
      (partnerByActivity.get(a.id) || []).forEach((pid) => partnersUsed.add(pid));
      (indicatorByActivity.get(a.id) || []).forEach((iid) => indicatorsUsed.add(iid));
      if (a.latitude && a.longitude) withGps += 1;
    });
    return {
      total: filtered.length,
      byStatus, byType, byProject, byMonth,
      partnersUsed: partnersUsed.size,
      indicatorsUsed: indicatorsUsed.size,
      withGps,
    };
  }, [filtered, partnerByActivity, indicatorByActivity, year]);

  // ── Helpers d'affichage ─────────────────────────────────────────────
  const typeLabel = (id) => {
    if (!id) return lang === "fr" ? "(Non typée)" : "(Untyped)";
    const t = (activityTypes || []).find((x) => x.id === id);
    return t ? (lang === "fr" ? t.name_fr : (t.name_en || t.name_fr)) : "—";
  };
  const projectLabel = (id) => {
    if (!id) return "—";
    const p = (projects || []).find((x) => x.uuid === id);
    return p ? (p.id + " — " + (lang === "fr" ? p.nameFr : (p.nameEn || p.nameFr))) : "—";
  };
  const partnerLabel = (id) => {
    const p = (partners || []).find((x) => x.id === id);
    return p ? p.name : "—";
  };
  const statusBadge = (s) => {
    const m = ACT_STATUS_META[s] || ACT_STATUS_META.not_started;
    return <span className="pill" style={{ background: m.color + "20", color: m.color, fontWeight: 600 }}>{lang === "fr" ? m.fr : m.en}</span>;
  };

  // Top N (pour les barres "par type" / "par projet")
  const topByType = useMemo(() => {
    return Array.from(stats.byType.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 8)
      .map(([id, count]) => ({ id, label: typeLabel(id), count }));
  }, [stats.byType, activityTypes, lang]);
  const topByProject = useMemo(() => {
    return Array.from(stats.byProject.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 8)
      .map(([id, count]) => ({ id, label: projectLabel(id), count }));
  }, [stats.byProject, projects, lang]);

  // ── Export CSV ──────────────────────────────────────────────────────
  const onExportCsv = () => {
    if (!window.melr || !window.melr.exportCSV) return;
    const date = new Date().toISOString().slice(0, 10);
    const rows = filtered.map((a) => ({
      code:        a.code || "",
      title:       a.title || "",
      project:     projectLabel(a.project_id),
      type:        typeLabel(a.activity_type_id),
      status:      (ACT_STATUS_META[a.status] || {})[lang === "fr" ? "fr" : "en"] || a.status,
      start:       a.start_date || "",
      end:         a.end_date || "",
      partners:    (partnerByActivity.get(a.id) || []).length,
      indicators:  (indicatorByActivity.get(a.id) || []).length,
      gps:         (a.latitude && a.longitude) ? (a.latitude + "," + a.longitude) : "",
      budget:      a.budget_planned || "",
      cost:        a.cost_actual || "",
      currency:    a.currency || "",
      location:    a.location_name || "",
    }));
    window.melr.exportCSV("activites-" + date + ".csv", rows, [
      { key: "code",       label: "Code" },
      { key: "title",      label: lang === "fr" ? "Titre" : "Title" },
      { key: "project",    label: lang === "fr" ? "Projet" : "Project" },
      { key: "type",       label: lang === "fr" ? "Type" : "Type" },
      { key: "status",     label: lang === "fr" ? "Statut" : "Status" },
      { key: "start",      label: lang === "fr" ? "Début" : "Start" },
      { key: "end",        label: lang === "fr" ? "Fin" : "End" },
      { key: "partners",   label: lang === "fr" ? "Nb partenaires" : "Partners" },
      { key: "indicators", label: lang === "fr" ? "Nb indicateurs" : "Indicators" },
      { key: "gps",        label: "GPS" },
      { key: "budget",     label: lang === "fr" ? "Budget" : "Budget" },
      { key: "cost",       label: lang === "fr" ? "Coût réel" : "Actual cost" },
      { key: "currency",   label: lang === "fr" ? "Devise" : "Currency" },
      { key: "location",   label: lang === "fr" ? "Lieu" : "Location" },
    ]);
  };

  // ── Rendu ──────────────────────────────────────────────────────────
  if (actLoad || relLoad) {
    return <div className="card"><div className="card-body" style={{ padding: 32, textAlign: "center", color: "var(--text-faint)" }}>
      {lang === "fr" ? "Chargement des activités…" : "Loading activities…"}
    </div></div>;
  }

  return (
    <div>
      {/* ── D2 · Filtres sauvegardés ─────────────────────────────────── */}
      {window.SavedViewsBar && (
        <div style={{ marginBottom: 12 }}>
          <window.SavedViewsBar
            screenKey="reporting.activities"
            currentFilters={currentFilters}
            onApply={applySavedFilters}
            lang={lang} />
        </div>
      )}

      {/* ── Barre de filtres ─────────────────────────────────────────── */}
      <div className="card">
        <div className="card-head"><div className="card-title">{lang === "fr" ? "Filtres" : "Filters"}</div></div>
        <div className="card-body">
          <div className="row gap-md" style={{ flexWrap: "wrap", alignItems: "flex-end" }}>
            {/* Année */}
            <div>
              <label className="lbl">{lang === "fr" ? "Année" : "Year"}</label>
              <select className="inp sm" value={year} onChange={(e) => setYear(Number(e.target.value))}>
                {[year + 1, year, year - 1, year - 2, year - 3].map((y) => <option key={y} value={y}>{y}</option>)}
              </select>
            </div>
            {/* Préréglage de période */}
            <div>
              <label className="lbl">{lang === "fr" ? "Période" : "Period"}</label>
              <select className="inp sm" 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>
            </div>
            {preset === "CUSTOM" && (<>
              <div>
                <label className="lbl">{lang === "fr" ? "Du" : "From"}</label>
                <input type="date" className="inp sm" value={customStart} onChange={(e) => setCustomStart(e.target.value)} />
              </div>
              <div>
                <label className="lbl">{lang === "fr" ? "Au" : "To"}</label>
                <input type="date" className="inp sm" value={customEnd} onChange={(e) => setCustomEnd(e.target.value)} />
              </div>
            </>)}
            {/* Projet */}
            <div>
              <label className="lbl">{lang === "fr" ? "Projet" : "Project"}</label>
              <select className="inp sm" value={projectFilter} onChange={(e) => setProjectFilter(e.target.value)}>
                <option value="">{lang === "fr" ? "Tous" : "All"}</option>
                {(projects || []).map((p) => <option key={p.uuid} value={p.uuid}>{p.id} — {lang === "fr" ? p.nameFr : (p.nameEn || p.nameFr)}</option>)}
              </select>
            </div>
            {/* Type */}
            <div>
              <label className="lbl">{lang === "fr" ? "Type" : "Type"}</label>
              <select className="inp sm" value={typeFilter} onChange={(e) => setTypeFilter(e.target.value)}>
                <option value="">{lang === "fr" ? "Tous" : "All"}</option>
                {(activityTypes || []).map((t) => <option key={t.id} value={t.id}>{lang === "fr" ? t.name_fr : (t.name_en || t.name_fr)}</option>)}
              </select>
            </div>
            {/* Statut */}
            <div>
              <label className="lbl">{lang === "fr" ? "Statut" : "Status"}</label>
              <select className="inp sm" value={statusFilter} onChange={(e) => setStatusFilter(e.target.value)}>
                <option value="">{lang === "fr" ? "Tous" : "All"}</option>
                {ACT_STATUS_KEYS.map((k) => <option key={k} value={k}>{lang === "fr" ? ACT_STATUS_META[k].fr : ACT_STATUS_META[k].en}</option>)}
              </select>
            </div>
            {/* Partenaire */}
            <div>
              <label className="lbl">{lang === "fr" ? "Partenaire" : "Partner"}</label>
              <select className="inp sm" value={partnerFilter} onChange={(e) => setPartnerFilter(e.target.value)}>
                <option value="">{lang === "fr" ? "Tous" : "All"}</option>
                {(partners || []).map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
              </select>
            </div>
            <div style={{ marginLeft: "auto", display: "flex", gap: 6 }}>
              {(() => {
                // Helpers passes aux exporters : on les construit une fois
                // pour partager projectLabel / typeLabel / pivots avec
                // les 4 fonctions d'export.
                const helpers = { projectLabel, typeLabel, partnerLabel, partnerByActivity, indicatorByActivity };
                // Periode lisible pour le rapport
                const periodLabel = (preset === "CUSTOM"
                  ? customStart + " → " + customEnd
                  : (PERIOD_PRESETS.find((p) => p.k === preset) || {})[lang === "fr" ? "lf" : "le"] + " " + year);
                const meta = { year, periodLabel };
                return (<>
                  <button className="btn sm ghost" onClick={onExportCsv} disabled={filtered.length === 0} title="CSV">
                    <Icon.download /> CSV
                  </button>
                  <button className="btn sm ghost" onClick={() => exportActivitiesXlsx(filtered, stats, helpers, lang, meta)} disabled={filtered.length === 0} title="Excel">
                    <Icon.download /> Excel
                  </button>
                  <button className="btn sm ghost" onClick={() => exportActivitiesPdf(filtered, stats, helpers, lang, meta, chartsRef)} disabled={filtered.length === 0} title="PDF">
                    <Icon.download /> PDF
                  </button>
                  <button className="btn sm" onClick={() => exportActivitiesDocx(filtered, stats, helpers, lang, meta, chartsRef)} disabled={filtered.length === 0} title="Word">
                    <Icon.download /> Word ({filtered.length})
                  </button>
                </>);
              })()}
            </div>
          </div>
        </div>
      </div>

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

      {/* Bloc capturable (KPIs + 4 graphes) pour les exports PDF / Word. */}
      <div ref={chartsRef} style={{ background: "var(--bg)", padding: 2 }}>

      {/* ── KPIs ─────────────────────────────────────────────────────── */}
      <div className="grid cols-6" style={{ display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 10 }}>
        <KpiTile label={lang === "fr" ? "Activités" : "Activities"} value={stats.total} status="accent" />
        <KpiTile label={lang === "fr" ? "Terminées" : "Completed"} value={stats.byStatus.completed} status="ok" />
        <KpiTile label={lang === "fr" ? "En cours" : "Ongoing"} value={stats.byStatus.ongoing} status="accent" />
        <KpiTile label={lang === "fr" ? "Non démarrées" : "Not started"} value={stats.byStatus.not_started} status="neutral" />
        <KpiTile label={lang === "fr" ? "Partenaires impliqués" : "Partners involved"} value={stats.partnersUsed} status="neutral" />
        <KpiTile label={lang === "fr" ? "Indicateurs liés" : "Indicators linked"} value={stats.indicatorsUsed} status="neutral" />
      </div>

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

      {/* ── Graphiques ───────────────────────────────────────────────── */}
      <div className="grid cols-2" style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 14 }}>
        <div className="card">
          <div className="card-head"><div className="card-title">
            {lang === "fr" ? "Activités par mois" : "Activities by month"}
            <span className="muted"> · {year}</span>
          </div></div>
          <div className="card-body">
            <ARMonthlyChart data={stats.byMonth} lang={lang} year={year} />
          </div>
        </div>
        <div className="card">
          <div className="card-head"><div className="card-title">{lang === "fr" ? "Par statut" : "By status"}</div></div>
          <div className="card-body">
            {ACT_STATUS_KEYS.map((k) => (
              <ARBar key={k}
                label={lang === "fr" ? ACT_STATUS_META[k].fr : ACT_STATUS_META[k].en}
                value={stats.byStatus[k]}
                max={Math.max(...Object.values(stats.byStatus), 1)}
                color={ACT_STATUS_META[k].color} />
            ))}
          </div>
        </div>
      </div>

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

      <div className="grid cols-2" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
        <div className="card">
          <div className="card-head"><div className="card-title">{lang === "fr" ? "Top types d'activité" : "Top activity types"}</div></div>
          <div className="card-body">
            {topByType.length === 0 && <div className="text-faint" style={{ fontSize: 12 }}>{lang === "fr" ? "Aucune donnée." : "No data."}</div>}
            {topByType.map((t) => (
              <ARBar key={t.id} label={t.label} value={t.count}
                max={topByType[0] ? topByType[0].count : 1}
                color="#7c3aed" />
            ))}
          </div>
        </div>
        <div className="card">
          <div className="card-head"><div className="card-title">{lang === "fr" ? "Top projets" : "Top projects"}</div></div>
          <div className="card-body">
            {topByProject.length === 0 && <div className="text-faint" style={{ fontSize: 12 }}>{lang === "fr" ? "Aucune donnée." : "No data."}</div>}
            {topByProject.map((p) => (
              <ARBar key={p.id} label={p.label} value={p.count}
                max={topByProject[0] ? topByProject[0].count : 1}
                color="#0d9488" />
            ))}
          </div>
        </div>
      </div>

      </div> {/* /chartsRef */}

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

      {/* ── Tableau détaillé ─────────────────────────────────────────── */}
      <div className="card">
        <div className="card-head">
          <div className="card-title">
            {lang === "fr" ? "Détail des activités" : "Activity details"}
            <span className="muted"> · {filtered.length}</span>
          </div>
        </div>
        <div className="card-body flush" style={{ maxHeight: 480, overflow: "auto" }}>
          {filtered.length === 0 ? (
            <div style={{ padding: 22, textAlign: "center", color: "var(--text-faint)", fontSize: 12.5 }}>
              {lang === "fr" ? "Aucune activité dans la sélection." : "No activity in selection."}
            </div>
          ) : (
            <table className="tbl">
              <thead><tr>
                <th>{lang === "fr" ? "Code" : "Code"}</th>
                <th>{lang === "fr" ? "Activité" : "Activity"}</th>
                <th>{lang === "fr" ? "Projet" : "Project"}</th>
                <th>{lang === "fr" ? "Type" : "Type"}</th>
                <th>{lang === "fr" ? "Statut" : "Status"}</th>
                <th>{lang === "fr" ? "Début" : "Start"}</th>
                <th>{lang === "fr" ? "Fin" : "End"}</th>
                <th className="num">{lang === "fr" ? "Part." : "Part."}</th>
                <th className="num">{lang === "fr" ? "Ind." : "Ind."}</th>
              </tr></thead>
              <tbody>
                {filtered.slice(0, 200).map((a) => (
                  <tr key={a.id}>
                    <td className="mono">{a.code || ""}</td>
                    <td className="strong">{a.title}</td>
                    <td className="muted">{projectLabel(a.project_id)}</td>
                    <td>{typeLabel(a.activity_type_id)}</td>
                    <td>{statusBadge(a.status)}</td>
                    <td className="mono num-sm">{a.start_date || "—"}</td>
                    <td className="mono num-sm">{a.end_date || "—"}</td>
                    <td className="num mono">{(partnerByActivity.get(a.id) || []).length}</td>
                    <td className="num mono">{(indicatorByActivity.get(a.id) || []).length}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
          {filtered.length > 200 && (
            <div style={{ padding: 10, textAlign: "center", color: "var(--text-faint)", fontSize: 11, borderTop: "1px solid var(--line-faint)" }}>
              {lang === "fr"
                ? "… affichage limité aux 200 premières activités. Exportez en CSV pour la liste complète."
                : "… display limited to first 200 activities. Export to CSV for the full list."}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

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);
  // Onglet courant : "indicators" (génération de rapports indicateurs)
  // ou "activities" (vue analytique des activités).
  const [tab, setTab] = useStateR("indicators");

  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">
        {/* Tabs · Indicateurs vs Activités */}
        <div className="seg" style={{ marginBottom: 16, display: "inline-flex" }}>
          <button className={"seg-btn" + (tab === "indicators" ? " active" : "")} onClick={() => setTab("indicators")}>
            <Icon.target /> {lang === "fr" ? "Indicateurs" : "Indicators"}
          </button>
          <button className={"seg-btn" + (tab === "activities" ? " active" : "")} onClick={() => setTab("activities")}>
            <Icon.calendar /> {lang === "fr" ? "Activités" : "Activities"}
          </button>
        </div>

        {tab === "indicators" && (<>
        {/* 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>
        </>)}

        {tab === "activities" && (
          <ActivitiesReporting
            lang={lang}
            projects={projects}
            actingOrgId={actingOrgId}
            myOrgId={myOrgId}
            isSuperAdmin={isSuperAdmin}
          />
        )}
      </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;
