/* global React, Icon */
// ============================================================================
// F1.1 — DHIS2 Integration admin screen
// ----------------------------------------------------------------------------
// Configure a single DHIS2 connection per organization. Admin-only.
// V1 shows :
//   - Form to create / edit the connection (URL + auth type + creds)
//   - "Test the connection" button → calls dhis2-proxy action="ping"
//   - Last test status (success / failure + DHIS2 version / latency)
//   - Recent sync log (last 20 calls)
//
// Future phases (F1.2+) will plug import / push / pull onto this same
// screen as additional cards.
// ============================================================================
const { useState: useStateD, useEffect: useEffectD } = React;

function Dhis2Admin({ t, lang, effectiveOrgId }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const orgId = effectiveOrgId; // already resolved by app.jsx (acting-as-org aware)
  const { data: connections, loading } = window.melr.useDhis2Connections(orgId);
  const conn = (connections && connections[0]) || null;
  const [editing, setEditing] = useStateD(false);
  const [msg, setMsg] = useStateD(null);

  if (!orgId) {
    return (
      <div className="page">
        <div className="page-body" style={{ padding: 40 }}>
          {T("Sélectionnez une organisation pour configurer DHIS2.",
             "Pick an organization to configure DHIS2.")}
        </div>
      </div>
    );
  }

  return (
    <div className="page">
      <div className="page-header">
        <div className="page-eyebrow">{T("INTÉGRATIONS", "INTEGRATIONS")}</div>
        <div className="page-header-row">
          <div>
            <h1 className="page-title">DHIS2</h1>
            <div className="page-sub">
              {T("Connecter MELR à votre instance DHIS2 (ministère, district, projet)",
                 "Connect MELR to your DHIS2 instance (ministry, district, project)")}
            </div>
          </div>
        </div>
      </div>

      <div className="page-body" style={{ display: "grid", gap: 18 }}>
        {/* === Connection panel === */}
        {loading ? (
          <div className="card" style={{ padding: 20 }}>{T("Chargement…", "Loading…")}</div>
        ) : (!conn || editing) ? (
          <Dhis2ConnectionForm
            lang={lang} orgId={orgId} existing={conn}
            onSaved={() => { setEditing(false); setMsg({ tone: "success", text: T("Connexion enregistrée.", "Connection saved.") }); }}
            onCancel={conn ? () => setEditing(false) : null}
          />
        ) : (
          <Dhis2ConnectionCard lang={lang} conn={conn} onEdit={() => setEditing(true)} onMessage={setMsg} />
        )}

        {msg && (
          <div className="card" style={{
            padding: "10px 14px",
            background: msg.tone === "success" ? "#dcfce7" : "#fee2e2",
            color: msg.tone === "success" ? "#166534" : "#991b1b",
            border: "1px solid " + (msg.tone === "success" ? "#86efac" : "#fca5a5"),
            fontSize: 13,
          }}>{msg.text}</div>
        )}

        {/* === F1.2 Import catalogue === */}
        {conn && conn.enabled && conn.last_test_ok && (
          <Dhis2CatalogueImportCard lang={lang} conn={conn} onMessage={setMsg} />
        )}

        {/* === F1.3 Push values === */}
        {conn && conn.enabled && conn.last_test_ok && (
          <Dhis2PushValuesCard lang={lang} conn={conn} onMessage={setMsg} />
        )}

        {/* === F1.4 Pull aggregates === */}
        {conn && conn.enabled && conn.last_test_ok && (
          <Dhis2PullAnalyticsCard lang={lang} conn={conn} onMessage={setMsg} />
        )}

        {/* === Sync log === */}
        {conn && <Dhis2SyncLogCard lang={lang} connectionId={conn.id} />}

        {/* === Sprint F1 complete === */}
        {conn && conn.last_test_ok && (
          <div className="card" style={{ padding: 20 }}>
            <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 10 }}>
              {T("Intégration DHIS2 — Sprint F1 complet ✓", "DHIS2 integration — Sprint F1 complete ✓")}
            </div>
            <div className="text-faint" style={{ fontSize: 13, lineHeight: 1.55 }}>
              {T(
                "Toutes les phases F1 sont livrées : connexion (F1.1), import du catalogue (F1.2), push des valeurs (F1.3), lecture des agrégats (F1.4). Prochaines évolutions possibles : sélecteur d'orgUnit par site, dataSet par défaut, sync programmée via cron.",
                "All F1 phases shipped: connection (F1.1), catalogue import (F1.2), value push (F1.3), aggregate pull (F1.4). Possible next: per-site orgUnit picker, default dataSet, scheduled sync via cron."
              )}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// Connection card · read-only view + Test + Edit
// ─────────────────────────────────────────────────────────────────────
function Dhis2ConnectionCard({ lang, conn, onEdit, onMessage }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const [testing, setTesting] = useStateD(false);

  const onTest = async () => {
    onMessage && onMessage(null);
    setTesting(true);
    try {
      const info = await window.melr.dhis2ConnectionsCrud.ping(conn.id);
      onMessage && onMessage({
        tone: "success",
        text: T(
          "✓ Connexion OK · DHIS2 ",
          "✓ Connection OK · DHIS2 "
        ) + (info && info.version ? info.version : "?")
          + (info && info.revision ? " (rev " + info.revision + ")" : ""),
      });
    } catch (e) {
      onMessage && onMessage({ tone: "danger", text: T("Échec du test : ", "Test failed: ") + e.message });
    } finally {
      setTesting(false);
    }
  };

  const onToggleEnabled = async () => {
    try {
      await window.melr.dhis2ConnectionsCrud.update(conn.id, { enabled: !conn.enabled });
    } catch (e) {
      onMessage && onMessage({ tone: "danger", text: e.message });
    }
  };

  const onDelete = async () => {
    if (!window.confirm(T(
      "Supprimer cette connexion DHIS2 ? Les mappings d'indicateurs existants seront aussi perdus.",
      "Delete this DHIS2 connection? Any existing indicator mappings will also be lost."
    ))) return;
    try {
      await window.melr.dhis2ConnectionsCrud.remove(conn.id);
      onMessage && onMessage({ tone: "success", text: T("Connexion supprimée.", "Connection deleted.") });
    } catch (e) {
      onMessage && onMessage({ tone: "danger", text: e.message });
    }
  };

  const statusBadge = conn.last_test_at ? (
    <span style={{
      padding: "3px 8px", borderRadius: 4, fontSize: 11,
      background: conn.last_test_ok ? "#dcfce7" : "#fee2e2",
      color:      conn.last_test_ok ? "#166534" : "#991b1b",
      border: "1px solid " + (conn.last_test_ok ? "#86efac" : "#fca5a5"),
    }}>
      {conn.last_test_ok ? "✓ " + T("OK", "OK") : "✗ " + T("Échec", "Failed")}
    </span>
  ) : (
    <span style={{ padding: "3px 8px", borderRadius: 4, fontSize: 11, background: "var(--bg-sunken)", color: "var(--text-faint)" }}>
      {T("Jamais testée", "Never tested")}
    </span>
  );

  return (
    <div className="card" style={{ padding: 20 }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 16, gap: 10, flexWrap: "wrap" }}>
        <div>
          <div style={{ fontSize: 17, fontWeight: 600, marginBottom: 4 }}>
            {conn.name || "DHIS2"} {statusBadge}
            {!conn.enabled && <span style={{ marginLeft: 8, padding: "3px 8px", borderRadius: 4, fontSize: 11, background: "#fef3c7", color: "#92400e" }}>{T("Désactivée", "Disabled")}</span>}
          </div>
          <div className="text-faint" style={{ fontSize: 12, fontFamily: "monospace", wordBreak: "break-all" }}>
            {conn.base_url}
          </div>
        </div>
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          <button className="btn primary" onClick={onTest} disabled={testing}>
            {testing ? "…" : T("Tester la connexion", "Test connection")}
          </button>
          <button className="btn" onClick={onEdit}>{T("Modifier", "Edit")}</button>
          <button className="btn ghost" onClick={onToggleEnabled}>
            {conn.enabled ? T("Désactiver", "Disable") : T("Activer", "Enable")}
          </button>
          <button className="btn ghost" onClick={onDelete} style={{ color: "#b91c1c" }}>
            {T("Supprimer", "Delete")}
          </button>
        </div>
      </div>

      <div style={{ display: "grid", gap: 8, fontSize: 12 }}>
        <div>
          <b style={{ color: "var(--text-faint)" }}>{T("Authentification", "Authentication")} :</b>{" "}
          {conn.auth_type === "pat"
            ? T("Personal Access Token (PAT)", "Personal Access Token (PAT)")
            : T("Basic (utilisateur + mot de passe)", "Basic (username + password)")}
          {conn.auth_type === "basic" && conn.username && <> · <code>{conn.username}</code></>}
        </div>
        {conn.last_test_at && (
          <div>
            <b style={{ color: "var(--text-faint)" }}>{T("Dernier test", "Last test")} :</b>{" "}
            {new Date(conn.last_test_at).toLocaleString()}
            {conn.last_test_message && (
              <span className="text-faint"> — {conn.last_test_message}</span>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// Connection form · create + edit. Secret fields stay empty on edit
// (we never re-render the stored value) — leave blank to keep, fill
// to overwrite.
// ─────────────────────────────────────────────────────────────────────
function Dhis2ConnectionForm({ lang, orgId, existing, onSaved, onCancel }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const [name,     setName]     = useStateD(existing?.name || "DHIS2");
  const [baseUrl,  setBaseUrl]  = useStateD(existing?.base_url || "https://play.im.dhis2.org/stable-2-41-3");
  const [authType, setAuthType] = useStateD(existing?.auth_type || "pat");
  const [username, setUsername] = useStateD(existing?.username || "");
  const [password, setPassword] = useStateD("");
  const [patToken, setPatToken] = useStateD("");
  const [defaultOrgUnit, setDefaultOrgUnit] = useStateD(existing?.default_orgunit_uid || "");
  const [orgUnitPickerOpen, setOrgUnitPickerOpen] = useStateD(false);
  const [busy,     setBusy]     = useStateD(false);
  const [err,      setErr]      = useStateD(null);

  const inp = { padding: "8px 10px", borderRadius: 6, border: "1px solid var(--line)", fontSize: 13, background: "var(--bg, white)", color: "var(--text, #111)", width: "100%", boxSizing: "border-box" };
  const lbl = { fontSize: 11, fontWeight: 500, marginBottom: 4, display: "block", color: "var(--text-faint)" };

  const onSubmit = async (e) => {
    e.preventDefault();
    setErr(null); setBusy(true);
    try {
      if (existing) {
        const patch = { name, base_url: baseUrl, auth_type: authType, username, default_orgunit_uid: defaultOrgUnit || null };
        if (password) patch.password = password;
        if (patToken) patch.pat_token = patToken;
        await window.melr.dhis2ConnectionsCrud.update(existing.id, patch);
      } else {
        await window.melr.dhis2ConnectionsCrud.create({
          organization_id: orgId, name, base_url: baseUrl,
          auth_type: authType, username, password, pat_token: patToken,
          default_orgunit_uid: defaultOrgUnit || null,
        });
      }
      onSaved && onSaved();
    } catch (e2) {
      setErr(e2.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <form onSubmit={onSubmit} className="card" style={{ padding: 20 }}>
      <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 16 }}>
        {existing ? T("Modifier la connexion DHIS2", "Edit DHIS2 connection") : T("Nouvelle connexion DHIS2", "New DHIS2 connection")}
      </div>

      <div style={{ display: "grid", gap: 12, gridTemplateColumns: "1fr 1fr" }}>
        <div>
          <label style={lbl}>{T("Nom de la connexion", "Connection name")}</label>
          <input style={inp} value={name} onChange={(e) => setName(e.target.value)} placeholder="DHIS2 Ministère Santé" />
        </div>
        <div>
          <label style={lbl}>{T("URL de base", "Base URL")} *</label>
          <input style={inp} value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} required placeholder="https://dhis2.minsante.sn" />
        </div>

        <div style={{ gridColumn: "1 / -1" }}>
          <label style={lbl}>{T("Type d'authentification", "Authentication type")}</label>
          <div style={{ display: "flex", gap: 16, marginTop: 4 }}>
            <label style={{ fontSize: 13, cursor: "pointer" }}>
              <input type="radio" checked={authType === "pat"} onChange={() => setAuthType("pat")} style={{ marginRight: 6 }} />
              {T("Personal Access Token (recommandé, DHIS2 ≥ 2.39)", "Personal Access Token (recommended, DHIS2 ≥ 2.39)")}
            </label>
            <label style={{ fontSize: 13, cursor: "pointer" }}>
              <input type="radio" checked={authType === "basic"} onChange={() => setAuthType("basic")} style={{ marginRight: 6 }} />
              {T("Basic (utilisateur + mot de passe)", "Basic (username + password)")}
            </label>
          </div>
        </div>

        {authType === "basic" && (
          <>
            <div>
              <label style={lbl}>{T("Utilisateur DHIS2", "DHIS2 username")} *</label>
              <input style={inp} value={username} onChange={(e) => setUsername(e.target.value)} required={!existing} autoComplete="off" />
            </div>
            <div>
              <label style={lbl}>{T("Mot de passe DHIS2", "DHIS2 password")} {existing ? T("(laisser vide pour conserver)", "(leave blank to keep)") : "*"}</label>
              <input style={inp} type="password" value={password} onChange={(e) => setPassword(e.target.value)} required={!existing} autoComplete="new-password" />
            </div>
          </>
        )}

        {authType === "pat" && (
          <div style={{ gridColumn: "1 / -1" }}>
            <label style={lbl}>{T("Personal Access Token", "Personal Access Token")} {existing ? T("(laisser vide pour conserver)", "(leave blank to keep)") : "*"}</label>
            <input style={inp} type="password" value={patToken} onChange={(e) => setPatToken(e.target.value)} required={!existing}
                   autoComplete="off" placeholder="d2pat_..." />
            <div className="text-faint" style={{ fontSize: 11, marginTop: 4 }}>
              {T(
                "Dans DHIS2 : Profil → Edit Profile → onglet Personal Access Token → Create new token.",
                "In DHIS2: Profile → Edit Profile → Personal Access Token tab → Create new token."
              )}
            </div>
          </div>
        )}

        {/* F1.3 · default orgUnit UID — utilise pour le push de valeurs */}
        <div style={{ gridColumn: "1 / -1" }}>
          <label style={lbl}>
            {T("orgUnit DHIS2 par défaut (UID)", "Default DHIS2 orgUnit (UID)")}{" "}
            <span style={{ fontWeight: 400, color: "var(--text-faint)" }}>
              {T("— optionnel, utilisé en fallback lors du push de valeurs", "— optional, used as fallback when pushing values")}
            </span>
          </label>
          <div style={{ display: "flex", gap: 6 }}>
            <input style={{ ...inp, flex: 1 }} value={defaultOrgUnit} onChange={(e) => setDefaultOrgUnit(e.target.value)}
                   placeholder="ImspTQPwCqd" />
            <button type="button" className="btn"
              onClick={() => existing && setOrgUnitPickerOpen(true)}
              disabled={!existing}
              title={existing ? "" : T("Enregistrez d'abord la connexion, puis revenez modifier pour ouvrir le sélecteur.", "Save the connection first, then come back to edit it to open the picker.")}>
              {T("Choisir un orgUnit…", "Pick orgUnit…")}
            </button>
          </div>
          <div className="text-faint" style={{ fontSize: 11, marginTop: 4 }}>
            {existing ? T(
              "Quand un site MELR n'a pas son propre mapping DHIS2, ses valeurs seront poussées vers cet orgUnit. Laissez vide si chaque site doit absolument avoir son mapping (le push échouera pour les sites non mappés).",
              "When a MELR site has no DHIS2 mapping of its own, its values will be pushed to this orgUnit. Leave blank if every site must have its own mapping (push will fail for unmapped sites)."
            ) : T(
              "💡 Pour utiliser le sélecteur d'orgUnit, enregistrez d'abord la connexion (juste l'URL + auth), testez le ping, puis revenez en mode Modifier.",
              "💡 To use the orgUnit picker, save the connection first (just URL + auth), test the ping, then come back via Edit."
            )}
          </div>
        </div>
      </div>

      {/* Modal du sélecteur d'orgUnit */}
      {orgUnitPickerOpen && existing && (
        <Dhis2OrgUnitPickerModal
          lang={lang} connectionId={existing.id}
          currentValue={defaultOrgUnit}
          onSelect={(uid) => { setDefaultOrgUnit(uid); setOrgUnitPickerOpen(false); }}
          onClose={() => setOrgUnitPickerOpen(false)} />
      )}

      {err && (
        <div style={{ marginTop: 12, padding: 8, background: "#fee2e2", color: "#991b1b", borderRadius: 5, fontSize: 12 }}>
          ⚠ {err}
        </div>
      )}

      <div style={{ display: "flex", gap: 8, marginTop: 16 }}>
        <button type="submit" className="btn primary" disabled={busy}>
          {busy ? "…" : T("Enregistrer", "Save")}
        </button>
        {onCancel && <button type="button" className="btn ghost" onClick={onCancel}>{T("Annuler", "Cancel")}</button>}
      </div>

      <div className="text-faint" style={{ fontSize: 11, marginTop: 14, padding: 10, background: "var(--bg-sunken)", borderRadius: 6, lineHeight: 1.5 }}>
        🔒 {T(
          "Les identifiants sont stockés côté serveur et ne sont jamais renvoyés au navigateur. Toutes les requêtes vers DHIS2 passent par notre proxy authentifié — votre token n'apparaît jamais dans le code de la page.",
          "Credentials are stored server-side and never returned to the browser. All DHIS2 requests go through our authenticated proxy — your token never appears in page source."
        )}
      </div>
    </form>
  );
}

// ─────────────────────────────────────────────────────────────────────
// Sync log card · last N proxy calls for this connection
// ─────────────────────────────────────────────────────────────────────
function Dhis2SyncLogCard({ lang, connectionId }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const { data: log, loading } = window.melr.useDhis2SyncLog(connectionId, 20);

  return (
    <div className="card" style={{ padding: 20 }}>
      <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 12 }}>
        {T("Journal des derniers appels DHIS2", "Recent DHIS2 calls log")}
      </div>
      {loading ? (
        <div className="text-faint" style={{ fontSize: 12 }}>{T("Chargement…", "Loading…")}</div>
      ) : (!log || log.length === 0) ? (
        <div className="text-faint" style={{ fontSize: 12, padding: 10, background: "var(--bg-sunken)", borderRadius: 6 }}>
          {T("Aucun appel encore enregistré. Cliquez « Tester la connexion » pour voir la première entrée.",
             "No calls logged yet. Click 'Test connection' to see the first entry.")}
        </div>
      ) : (
        <div style={{ overflowX: "auto" }}>
          <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
            <thead>
              <tr style={{ textAlign: "left", color: "var(--text-faint)", borderBottom: "1px solid var(--line)" }}>
                <th style={{ padding: "6px 8px" }}>{T("Quand", "When")}</th>
                <th style={{ padding: "6px 8px" }}>{T("Action", "Action")}</th>
                <th style={{ padding: "6px 8px", textAlign: "center" }}>HTTP</th>
                <th style={{ padding: "6px 8px" }}>{T("Détails", "Details")}</th>
              </tr>
            </thead>
            <tbody>
              {log.map((r) => (
                <tr key={r.id} style={{ borderBottom: "1px solid var(--line)" }}>
                  <td style={{ padding: "6px 8px", whiteSpace: "nowrap" }}>{new Date(r.created_at).toLocaleString()}</td>
                  <td style={{ padding: "6px 8px" }}><code>{r.action}</code></td>
                  <td style={{ padding: "6px 8px", textAlign: "center" }}>
                    <span style={{
                      padding: "2px 6px", borderRadius: 4, fontSize: 11,
                      background: r.ok ? "#dcfce7" : "#fee2e2",
                      color:      r.ok ? "#166534" : "#991b1b",
                    }}>{r.http_status || "—"}</span>
                  </td>
                  <td style={{ padding: "6px 8px", color: "var(--text-faint)", maxWidth: 360, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                    {r.message || (r.response_summary && JSON.stringify(r.response_summary)) || (r.request_summary && r.request_summary.path) || ""}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// F1.2 · Catalogue import card
// ----------------------------------------------------------------------------
// Fetch DHIS2 data elements + indicators, let the user filter + select,
// import the picks into indicator_definitions. Imported items get
// dhis2_uid set so the same import is a no-op on second run (idempotent
// via the unique partial index org_id+dhis2_uid).
// ─────────────────────────────────────────────────────────────────────
function Dhis2CatalogueImportCard({ lang, conn, onMessage }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const [loading, setLoading] = useStateD(false);
  const [catalogue, setCatalogue] = useStateD(null); // { data_elements: [], indicators: [] }
  const [tab, setTab] = useStateD("data_element");   // 'data_element' | 'indicator'
  const [search, setSearch] = useStateD("");
  const [selected, setSelected] = useStateD(new Set());
  const [importing, setImporting] = useStateD(false);

  // Definitions deja importees pour cette org : on les desactive dans
  // la liste pour ne pas faire perdre du temps a l'utilisateur a
  // re-cocher des choses qui passeront en no-op.
  const { data: definitions } = window.melr.useIndicatorDefinitions();
  const importedUids = (() => {
    const s = new Set();
    (definitions || []).forEach((d) => { if (d.dhis2_uid) s.add(d.dhis2_uid); });
    return s;
  })();

  const onFetch = async () => {
    setLoading(true);
    onMessage && onMessage(null);
    try {
      const cat = await window.melr.dhis2ConnectionsCrud.fetchCatalogue(conn.id, { cap: 500 });
      setCatalogue(cat);
      setSelected(new Set());
    } catch (e) {
      onMessage && onMessage({ tone: "danger", text: T("Échec récupération du catalogue : ", "Catalogue fetch failed: ") + e.message });
    } finally {
      setLoading(false);
    }
  };

  const items = (catalogue && catalogue[tab === "data_element" ? "data_elements" : "indicators"]) || [];
  const filtered = (() => {
    if (!search.trim()) return items;
    const s = search.toLowerCase();
    return items.filter((d) => {
      return (d.displayName && d.displayName.toLowerCase().includes(s))
          || (d.code && d.code.toLowerCase().includes(s))
          || (d.id && d.id.toLowerCase().includes(s));
    });
  })();

  const toggleOne = (uid) => {
    setSelected((prev) => {
      const n = new Set(prev);
      if (n.has(uid)) n.delete(uid); else n.add(uid);
      return n;
    });
  };
  const toggleAllVisible = () => {
    setSelected((prev) => {
      const n = new Set(prev);
      const allChecked = filtered.every((d) => importedUids.has(d.id) || n.has(d.id));
      filtered.forEach((d) => {
        if (importedUids.has(d.id)) return;
        if (allChecked) n.delete(d.id);
        else n.add(d.id);
      });
      return n;
    });
  };

  const [lastImport, setLastImport] = useStateD(null);
  const onImport = async () => {
    if (selected.size === 0) return;
    setImporting(true);
    onMessage && onMessage(null);
    setLastImport(null);
    try {
      const picked = items.filter((d) => selected.has(d.id));
      const r = await window.melr.dhis2ConnectionsCrud.importFromCatalogue({
        organization_id: conn.organization_id,
        items: picked,
        source: tab,
      });
      setLastImport(r);
      const tone = r.created > 0 ? "success" : "warn";
      onMessage && onMessage({
        tone: r.created > 0 ? "success" : "danger",
        text: T(
          "Import terminé : ", "Import done: "
        ) + r.created + T(" créé(s)", " created")
          + (r.skipped > 0 ? (", " + r.skipped + T(" ignoré(s) — voir détails ci-dessous", " skipped — see details below")) : "") + ".",
      });
      setSelected(new Set());
    } catch (e) {
      onMessage && onMessage({ tone: "danger", text: T("Échec import : ", "Import failed: ") + e.message });
    } finally {
      setImporting(false);
    }
  };

  return (
    <div className="card" style={{ padding: 20 }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12, gap: 10, flexWrap: "wrap" }}>
        <div style={{ fontSize: 15, fontWeight: 600 }}>
          📥 {T("Importer le catalogue DHIS2", "Import DHIS2 catalogue")}
        </div>
        <button className="btn primary" onClick={onFetch} disabled={loading}>
          {loading ? T("Récupération…", "Fetching…")
            : (catalogue ? T("Actualiser", "Refresh") : T("Récupérer le catalogue", "Fetch catalogue"))}
        </button>
      </div>

      <div className="text-faint" style={{ fontSize: 12, marginBottom: 14, lineHeight: 1.5 }}>
        {T(
          "MELR récupère les data elements et indicators de votre instance DHIS2 (max 500 de chaque). Cochez ceux qui vous intéressent et cliquez « Importer » : ils apparaîtront dans votre catalogue MELR (Sidebar → Indicateurs → Définition des indicateurs) avec le code et l'UID DHIS2 conservés. Les imports sont idempotents — relancer ne crée pas de doublons.",
          "MELR fetches data elements and indicators from your DHIS2 instance (up to 500 of each). Tick the ones you want and click Import: they appear in your MELR catalogue (Sidebar → Indicators → Definitions) with the DHIS2 code and UID preserved. Imports are idempotent — re-running won't create duplicates."
        )}
      </div>

      {!catalogue ? (
        <div className="text-faint" style={{ fontSize: 12, padding: 14, background: "var(--bg-sunken)", borderRadius: 6, textAlign: "center" }}>
          {T("Cliquez « Récupérer le catalogue » pour démarrer.",
             "Click 'Fetch catalogue' to start.")}
        </div>
      ) : (
        <>
          {/* Tab bar (data_elements vs indicators) + search + import button */}
          <div style={{ display: "flex", gap: 8, alignItems: "center", marginBottom: 10, flexWrap: "wrap" }}>
            <div style={{ display: "flex", border: "1px solid var(--line)", borderRadius: 6, overflow: "hidden" }}>
              {[
                { k: "data_element", label: T("Data elements", "Data elements"), count: (catalogue.data_elements || []).length },
                { k: "indicator",    label: T("Indicators DHIS2", "DHIS2 Indicators"),   count: (catalogue.indicators    || []).length },
              ].map((c) => (
                <button key={c.k} onClick={() => { setTab(c.k); setSearch(""); }}
                  style={{
                    padding: "6px 12px", fontSize: 12,
                    background: tab === c.k ? "var(--accent)" : "transparent",
                    color: tab === c.k ? "white" : "var(--text)",
                    border: 0, cursor: "pointer", fontWeight: tab === c.k ? 600 : 400,
                  }}>
                  {c.label} <span style={{ opacity: 0.65 }}>({c.count})</span>
                </button>
              ))}
            </div>
            <input value={search} onChange={(e) => setSearch(e.target.value)}
              placeholder={T("Rechercher (nom, code, UID)…", "Search (name, code, UID)…")}
              style={{ flex: 1, minWidth: 200, padding: "6px 10px", borderRadius: 6, border: "1px solid var(--line)", fontSize: 13, background: "var(--bg)", color: "var(--text)" }} />
            <button className="btn primary" onClick={onImport} disabled={importing || selected.size === 0}>
              {importing ? "…" : (T("Importer ", "Import ") + selected.size + (selected.size > 1 ? T(" éléments", " items") : T(" élément", " item")))}
            </button>
          </div>

          {/* Table */}
          {filtered.length === 0 ? (
            <div className="text-faint" style={{ fontSize: 12, padding: 14, background: "var(--bg-sunken)", borderRadius: 6, textAlign: "center" }}>
              {T("Aucun résultat pour cette recherche.", "No results for this search.")}
            </div>
          ) : (
            <div style={{ overflowX: "auto", maxHeight: 480, overflowY: "auto", border: "1px solid var(--line)", borderRadius: 6 }}>
              <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
                <thead style={{ position: "sticky", top: 0, background: "var(--bg-sunken)", zIndex: 1 }}>
                  <tr style={{ textAlign: "left", borderBottom: "1px solid var(--line)" }}>
                    <th style={{ padding: "6px 10px", width: 36 }}>
                      <input type="checkbox" onChange={toggleAllVisible}
                        checked={filtered.length > 0 && filtered.every((d) => importedUids.has(d.id) || selected.has(d.id))} />
                    </th>
                    <th style={{ padding: "6px 10px" }}>{T("Nom", "Name")}</th>
                    <th style={{ padding: "6px 10px" }}>{T("Code", "Code")}</th>
                    <th style={{ padding: "6px 10px" }}>UID</th>
                    {tab === "data_element" && <th style={{ padding: "6px 10px" }}>Type</th>}
                  </tr>
                </thead>
                <tbody>
                  {filtered.map((d) => {
                    const isImported = importedUids.has(d.id);
                    const isChecked  = selected.has(d.id);
                    return (
                      <tr key={d.id} style={{
                        borderBottom: "1px solid var(--line)",
                        opacity: isImported ? 0.5 : 1,
                        background: isChecked ? "color-mix(in oklch, var(--accent) 8%, transparent)" : undefined,
                      }}>
                        <td style={{ padding: "6px 10px", textAlign: "center" }}>
                          <input type="checkbox" disabled={isImported}
                            checked={isChecked} onChange={() => toggleOne(d.id)} />
                        </td>
                        <td style={{ padding: "6px 10px" }}>
                          {d.displayName || "—"}
                          {isImported && (
                            <span style={{ marginLeft: 6, fontSize: 10, color: "#16a34a", fontWeight: 600 }}>
                              ✓ {T("déjà importé", "imported")}
                            </span>
                          )}
                        </td>
                        <td style={{ padding: "6px 10px", fontFamily: "monospace", fontSize: 11 }}>{d.code || "—"}</td>
                        <td style={{ padding: "6px 10px", fontFamily: "monospace", fontSize: 11, color: "var(--text-faint)" }}>{d.id}</td>
                        {tab === "data_element" && <td style={{ padding: "6px 10px", fontSize: 11, color: "var(--text-faint)" }}>{d.valueType || "—"}</td>}
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          )}

          <div className="text-faint" style={{ fontSize: 11, marginTop: 8 }}>
            {filtered.length} {T(" élément(s) affichés", " items shown")}
            {selected.size > 0 && <> · {selected.size} {T(" coché(s)", " checked")}</>}
            {importedUids.size > 0 && <> · {importedUids.size} {T(" déjà importé(s) au total", " already imported total")}</>}
          </div>

          {/* F1.2 fix · Skip details : critique pour diagnostiquer un import vide */}
          {lastImport && lastImport.skipped_reasons && lastImport.skipped_reasons.length > 0 && (
            <details open style={{ marginTop: 10, padding: 10, background: "#fef3c7", border: "1px solid #fbbf24", borderRadius: 6, fontSize: 12 }}>
              <summary style={{ cursor: "pointer", fontWeight: 600 }}>
                ⚠ {lastImport.skipped_reasons.length}{" "}
                {T("élément(s) ignoré(s) — pourquoi ?", "item(s) skipped — why?")}
              </summary>
              <ul style={{ marginTop: 8, paddingLeft: 18, maxHeight: 200, overflowY: "auto", lineHeight: 1.6 }}>
                {lastImport.skipped_reasons.map((s, i) => (
                  <li key={i}>
                    <code style={{ fontSize: 11 }}>{s.code || s.dhis2_id}</code> — {s.reason}
                  </li>
                ))}
              </ul>
              <div className="text-faint" style={{ fontSize: 11, marginTop: 8 }}>
                {T(
                  "Causes habituelles : (1) l'élément a déjà été importé (re-import ignoré, normal) ; (2) un indicateur MELR existant utilise déjà ce code — renommez l'un des deux pour éviter la collision.",
                  "Common causes: (1) item already imported (re-import skipped, expected); (2) an existing MELR indicator uses the same code — rename one to avoid the collision."
                )}
              </div>
            </details>
          )}
        </>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// F1.3 · Push values card
// ----------------------------------------------------------------------------
// Lists indicator_values that can be pushed (their definition has a
// dhis2_uid) within a date range. Lets admin pick a DHIS2 period type
// (daily/monthly/quarterly/yearly), preview the payload, dry-run, then
// push for real. Skipped rows (missing orgUnit mapping, null value)
// are listed inline so the admin can fix them.
// ─────────────────────────────────────────────────────────────────────
function Dhis2PushValuesCard({ lang, conn, onMessage }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  // Default date range : last 90 days
  const today = new Date().toISOString().slice(0, 10);
  const ninetyAgo = new Date(Date.now() - 90 * 24 * 3600 * 1000).toISOString().slice(0, 10);
  const [from, setFrom]     = useStateD(ninetyAgo);
  const [to, setTo]         = useStateD(today);
  const [periodType, setPeriodType] = useStateD("monthly");
  const [onlyUnpushed, setOnlyUnpushed] = useStateD(true);
  const [loading, setLoading] = useStateD(false);
  const [rows, setRows]       = useStateD(null);
  const [pushing, setPushing] = useStateD(false);
  const [lastResult, setLastResult] = useStateD(null);

  const onLoad = async () => {
    setLoading(true);
    onMessage && onMessage(null);
    try {
      const r = await window.melr.dhis2ConnectionsCrud.fetchPushableValues({
        organization_id: conn.organization_id,
        from, to, only_unpushed: onlyUnpushed,
      });
      setRows(r);
      setLastResult(null);
    } catch (e) {
      onMessage && onMessage({ tone: "danger", text: T("Erreur chargement : ", "Load error: ") + e.message });
    } finally {
      setLoading(false);
    }
  };

  const doPush = async (dry) => {
    if (!rows || rows.length === 0) return;
    if (!dry && !window.confirm(T(
      "Pousser " + rows.length + " valeur(s) vers DHIS2 ? Les valeurs marquées last_pushed_at seront stampées.",
      "Push " + rows.length + " value(s) to DHIS2? Pushed rows will be stamped with last_pushed_at."
    ))) return;
    setPushing(true);
    onMessage && onMessage(null);
    try {
      const result = await window.melr.dhis2ConnectionsCrud.pushValues({
        connection_id: conn.id,
        items: rows,
        period_type: periodType,
        default_orgunit_uid: conn.default_orgunit_uid,
        dry_run: !!dry,
      });
      setLastResult(result);
      const ic = result.dhis2_response && result.dhis2_response.importCount;
      const summary = ic
        ? T("Importés: ", "Imported: ") + (ic.imported || 0)
          + " · " + T("MAJ: ", "Updated: ") + (ic.updated || 0)
          + " · " + T("Ignorés: ", "Ignored: ") + (ic.ignored || 0)
          + " · " + T("Erreurs: ", "Errors: ") + (ic.deleted || 0)
        : "";
      onMessage && onMessage({
        tone: "success",
        text: (dry ? T("✓ Dry-run OK · ", "✓ Dry-run OK · ") : T("✓ Push effectué · ", "✓ Push done · "))
          + T("Envoyés: ", "Sent: ") + result.pushed
          + (result.skipped && result.skipped.length > 0
              ? (" · " + T("Skip: ", "Skip: ") + result.skipped.length)
              : "")
          + (summary ? (" · " + summary) : ""),
      });
      // Reload to refresh last_pushed_at
      if (!dry) {
        setTimeout(() => onLoad(), 800);
      }
    } catch (e) {
      onMessage && onMessage({ tone: "danger", text: T("Push échoué : ", "Push failed: ") + e.message });
    } finally {
      setPushing(false);
    }
  };

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

  return (
    <div className="card" style={{ padding: 20 }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12, gap: 10, flexWrap: "wrap" }}>
        <div style={{ fontSize: 15, fontWeight: 600 }}>
          📤 {T("Pousser les valeurs vers DHIS2", "Push values to DHIS2")}
        </div>
      </div>

      <div className="text-faint" style={{ fontSize: 12, marginBottom: 14, lineHeight: 1.5 }}>
        {T(
          "Seules les valeurs dont l'indicateur est lié à un data element DHIS2 (catalogue importé via F1.2) sont éligibles. Choisissez la fenêtre temporelle, le type de période DHIS2, prévisualisez, puis cliquez Pousser. Le dry-run valide auprès de DHIS2 sans persister.",
          "Only values whose indicator is linked to a DHIS2 data element (catalogue imported via F1.2) are eligible. Pick the date window, DHIS2 period type, preview, then click Push. Dry-run validates with DHIS2 without persisting."
        )}
      </div>

      {/* Filters */}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr auto", gap: 8, alignItems: "end", marginBottom: 12 }}>
        <div>
          <label style={{ fontSize: 11, color: "var(--text-faint)", display: "block", marginBottom: 4 }}>{T("Du", "From")}</label>
          <input type="date" value={from} onChange={(e) => setFrom(e.target.value)} style={{ ...inp, width: "100%" }} />
        </div>
        <div>
          <label style={{ fontSize: 11, color: "var(--text-faint)", display: "block", marginBottom: 4 }}>{T("Au", "To")}</label>
          <input type="date" value={to} onChange={(e) => setTo(e.target.value)} style={{ ...inp, width: "100%" }} />
        </div>
        <div>
          <label style={{ fontSize: 11, color: "var(--text-faint)", display: "block", marginBottom: 4 }}>{T("Période DHIS2", "DHIS2 period")}</label>
          <select value={periodType} onChange={(e) => setPeriodType(e.target.value)} style={{ ...inp, width: "100%" }}>
            <option value="daily">{T("Quotidien (YYYYMMDD)", "Daily (YYYYMMDD)")}</option>
            <option value="monthly">{T("Mensuel (YYYYMM)", "Monthly (YYYYMM)")}</option>
            <option value="quarterly">{T("Trimestriel (YYYYQq)", "Quarterly (YYYYQq)")}</option>
            <option value="yearly">{T("Annuel (YYYY)", "Yearly (YYYY)")}</option>
          </select>
        </div>
        <div>
          <label style={{ fontSize: 11, display: "flex", alignItems: "center", gap: 6, cursor: "pointer", marginTop: 18 }}>
            <input type="checkbox" checked={onlyUnpushed} onChange={(e) => setOnlyUnpushed(e.target.checked)} />
            {T("Non poussées", "Unpushed only")}
          </label>
        </div>
        <button className="btn primary" onClick={onLoad} disabled={loading}>
          {loading ? "…" : T("Charger", "Load")}
        </button>
      </div>

      {rows === null ? (
        <div className="text-faint" style={{ fontSize: 12, padding: 14, background: "var(--bg-sunken)", borderRadius: 6, textAlign: "center" }}>
          {T("Cliquez Charger pour prévisualiser les valeurs pushables.", "Click Load to preview pushable values.")}
        </div>
      ) : rows.length === 0 ? (
        <div className="text-faint" style={{ fontSize: 12, padding: 14, background: "var(--bg-sunken)", borderRadius: 6, textAlign: "center" }}>
          {T("Aucune valeur pushable pour ces filtres.", "No pushable values for these filters.")}
        </div>
      ) : (
        <>
          <div style={{ overflowX: "auto", maxHeight: 360, overflowY: "auto", border: "1px solid var(--line)", borderRadius: 6, marginBottom: 12 }}>
            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
              <thead style={{ position: "sticky", top: 0, background: "var(--bg-sunken)", zIndex: 1 }}>
                <tr style={{ textAlign: "left", borderBottom: "1px solid var(--line)" }}>
                  <th style={{ padding: "6px 10px" }}>{T("Indicateur", "Indicator")}</th>
                  <th style={{ padding: "6px 10px" }}>{T("Site", "Site")}</th>
                  <th style={{ padding: "6px 10px" }}>{T("Période", "Period")}</th>
                  <th style={{ padding: "6px 10px", textAlign: "right" }}>{T("Valeur", "Value")}</th>
                  <th style={{ padding: "6px 10px" }}>{T("orgUnit DHIS2", "DHIS2 orgUnit")}</th>
                  <th style={{ padding: "6px 10px" }}>{T("Déjà poussé", "Last push")}</th>
                </tr>
              </thead>
              <tbody>
                {rows.map((r) => {
                  const orgUnit = r.site_dhis2_uid || conn.default_orgunit_uid;
                  const willSkip = !orgUnit || (r.value_numeric == null && !r.value_text);
                  return (
                    <tr key={r.value_id} style={{ borderBottom: "1px solid var(--line)", opacity: willSkip ? 0.5 : 1 }}>
                      <td style={{ padding: "6px 10px" }}>
                        <code style={{ fontSize: 11 }}>{r.definition_code}</code>
                        <div className="text-faint" style={{ fontSize: 11 }}>{r.definition_name_fr}</div>
                      </td>
                      <td style={{ padding: "6px 10px" }}>{r.site_name || "—"}</td>
                      <td style={{ padding: "6px 10px", fontFamily: "monospace", fontSize: 11 }}>{r.period_start || "—"}</td>
                      <td style={{ padding: "6px 10px", textAlign: "right", fontFamily: "monospace" }}>
                        {r.value_numeric != null ? r.value_numeric : (r.value_text || "—")}
                      </td>
                      <td style={{ padding: "6px 10px", fontFamily: "monospace", fontSize: 11, color: orgUnit ? "var(--text)" : "#dc2626" }}>
                        {orgUnit || T("⚠ aucun", "⚠ none")}
                        {!r.site_dhis2_uid && orgUnit && (
                          <span style={{ marginLeft: 4, fontSize: 9, color: "var(--text-faint)" }}>({T("défaut", "default")})</span>
                        )}
                      </td>
                      <td style={{ padding: "6px 10px", fontSize: 11, color: r.last_pushed_at ? "#16a34a" : "var(--text-faint)" }}>
                        {r.last_pushed_at ? new Date(r.last_pushed_at).toLocaleDateString() : "—"}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>

          <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
            <button className="btn" onClick={() => doPush(true)} disabled={pushing}>
              {T("Tester (dry-run)", "Test (dry-run)")}
            </button>
            <button className="btn primary" onClick={() => doPush(false)} disabled={pushing}>
              {pushing ? "…" : T("Pousser ", "Push ") + rows.length + T(" valeur(s) vers DHIS2", " value(s) to DHIS2")}
            </button>
            <span className="text-faint" style={{ fontSize: 11, marginLeft: 8 }}>
              {!conn.default_orgunit_uid && T(
                "💡 Aucun orgUnit par défaut configuré sur la connexion — les sites sans mapping seront skippés.",
                "💡 No default orgUnit on the connection — unmapped sites will be skipped."
              )}
            </span>
          </div>

          {lastResult && lastResult.skipped && lastResult.skipped.length > 0 && (
            <details style={{ marginTop: 10, padding: 10, background: "#fef3c7", border: "1px solid #fbbf24", borderRadius: 6, fontSize: 11 }}>
              <summary style={{ cursor: "pointer", fontWeight: 600 }}>
                {lastResult.skipped.length} {T(" ligne(s) skippée(s) — voir détails", " row(s) skipped — see details")}
              </summary>
              <ul style={{ marginTop: 6, paddingLeft: 18, maxHeight: 200, overflowY: "auto" }}>
                {lastResult.skipped.map((s, i) => (
                  <li key={i}><code style={{ fontSize: 10 }}>{(s.value_id || "?").slice(0, 8)}…</code> — {s.reason}</li>
                ))}
              </ul>
            </details>
          )}
        </>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// F1.4 · Pull aggregates card
// ----------------------------------------------------------------------------
// Lit les valeurs agregees DHIS2 (/api/analytics) pour un dataElement +
// un orgUnit + un range de periodes. Affiche dans un tableau, calcule
// total + moyenne. Utile pour comparer "valeurs MELR" vs "agregat
// national DHIS2" lors d'une evaluation.
// ─────────────────────────────────────────────────────────────────────
function Dhis2PullAnalyticsCard({ lang, conn, onMessage }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const { data: definitions } = window.melr.useIndicatorDefinitions();
  // Seuls les indicateurs avec dhis2_uid sont eligibles pour le pull
  const eligible = (definitions || []).filter((d) =>
    d.dhis2_uid && d.organization_id === conn.organization_id
  );

  const today = new Date();
  const oneYearAgo = new Date(today); oneYearAgo.setUTCFullYear(oneYearAgo.getUTCFullYear() - 1);
  const [defId, setDefId]       = useStateD("");
  const [orgUnit, setOrgUnit]   = useStateD(conn.default_orgunit_uid || "");
  const [from, setFrom]         = useStateD(oneYearAgo.toISOString().slice(0, 10));
  const [to, setTo]             = useStateD(today.toISOString().slice(0, 10));
  const [periodType, setPeriodType] = useStateD("monthly");
  const [loading, setLoading]   = useStateD(false);
  const [result, setResult]     = useStateD(null);

  const selectedDef = eligible.find((d) => d.id === defId);

  const onPull = async () => {
    if (!selectedDef) { onMessage && onMessage({ tone: "danger", text: T("Choisir un indicateur.", "Pick an indicator.") }); return; }
    if (!orgUnit)     { onMessage && onMessage({ tone: "danger", text: T("Renseigner un orgUnit.", "Enter an orgUnit.") }); return; }
    setLoading(true);
    onMessage && onMessage(null);
    try {
      const periods = window.melr.dhis2ConnectionsCrud._generatePeriods(from, to, periodType);
      if (periods.length === 0) throw new Error(T("Range de dates invalide.", "Invalid date range."));
      const r = await window.melr.dhis2ConnectionsCrud.pullAnalytics({
        connection_id: conn.id,
        dataElement: selectedDef.dhis2_uid,
        orgUnit,
        periods,
      });
      setResult(r);
      onMessage && onMessage({
        tone: "success",
        text: T("✓ ", "✓ ") + r.rows.length + T(" valeur(s) récupérée(s)", " value(s) retrieved")
              + " · " + T("Périodes interrogées : ", "Periods queried: ") + periods.length,
      });
    } catch (e) {
      onMessage && onMessage({ tone: "danger", text: T("Échec lecture : ", "Pull failed: ") + e.message });
    } finally {
      setLoading(false);
    }
  };

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

  // Stats simples sur le résultat
  const stats = (() => {
    if (!result || result.rows.length === 0) return null;
    const nums = result.rows.map((r) => Number(r.value)).filter((n) => !isNaN(n));
    if (nums.length === 0) return null;
    const sum = nums.reduce((a, b) => a + b, 0);
    const avg = sum / nums.length;
    const min = Math.min(...nums);
    const max = Math.max(...nums);
    return { count: nums.length, sum, avg, min, max };
  })();

  return (
    <div className="card" style={{ padding: 20 }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12, gap: 10, flexWrap: "wrap" }}>
        <div style={{ fontSize: 15, fontWeight: 600 }}>
          📥 {T("Lire les agrégats DHIS2", "Pull DHIS2 aggregates")}
        </div>
      </div>

      <div className="text-faint" style={{ fontSize: 12, marginBottom: 14, lineHeight: 1.5 }}>
        {T(
          "Récupère les valeurs agrégées DHIS2 via /api/analytics pour un de vos indicateurs importés (F1.2). Utile pour comparer la performance d'un projet MELR vs les chiffres nationaux ou de district. Read-only — ne modifie rien dans DHIS2.",
          "Fetches aggregated DHIS2 values via /api/analytics for one of your imported indicators (F1.2). Useful to benchmark MELR project results vs national or district figures. Read-only — never writes to DHIS2."
        )}
      </div>

      {eligible.length === 0 ? (
        <div className="text-faint" style={{ fontSize: 12, padding: 14, background: "var(--bg-sunken)", borderRadius: 6, textAlign: "center" }}>
          {T(
            "Aucun indicateur lié à DHIS2 dans votre catalogue. Importez d'abord via la carte « Importer le catalogue DHIS2 ».",
            "No DHIS2-linked indicator in your catalogue. Import some first via the 'Import DHIS2 catalogue' card."
          )}
        </div>
      ) : (
        <>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginBottom: 10 }}>
            <div>
              <label style={{ fontSize: 11, color: "var(--text-faint)", display: "block", marginBottom: 4 }}>{T("Indicateur (avec UID DHIS2)", "Indicator (with DHIS2 UID)")}</label>
              <select value={defId} onChange={(e) => setDefId(e.target.value)} style={{ ...inp, width: "100%" }}>
                <option value="">— {T("Choisir", "Pick")} —</option>
                {eligible.map((d) => (
                  <option key={d.id} value={d.id}>
                    {d.code} · {(d.name_fr || "").slice(0, 60)} {d.dhis2_uid ? "[" + d.dhis2_uid + "]" : ""}
                  </option>
                ))}
              </select>
            </div>
            <div>
              <label style={{ fontSize: 11, color: "var(--text-faint)", display: "block", marginBottom: 4 }}>{T("orgUnit DHIS2 (UID)", "DHIS2 orgUnit (UID)")}</label>
              <input value={orgUnit} onChange={(e) => setOrgUnit(e.target.value)} style={{ ...inp, width: "100%", boxSizing: "border-box" }}
                placeholder={conn.default_orgunit_uid || "ImspTQPwCqd"} />
            </div>
            <div>
              <label style={{ fontSize: 11, color: "var(--text-faint)", display: "block", marginBottom: 4 }}>{T("Du", "From")}</label>
              <input type="date" value={from} onChange={(e) => setFrom(e.target.value)} style={{ ...inp, width: "100%", boxSizing: "border-box" }} />
            </div>
            <div>
              <label style={{ fontSize: 11, color: "var(--text-faint)", display: "block", marginBottom: 4 }}>{T("Au", "To")}</label>
              <input type="date" value={to} onChange={(e) => setTo(e.target.value)} style={{ ...inp, width: "100%", boxSizing: "border-box" }} />
            </div>
            <div>
              <label style={{ fontSize: 11, color: "var(--text-faint)", display: "block", marginBottom: 4 }}>{T("Période DHIS2", "DHIS2 period")}</label>
              <select value={periodType} onChange={(e) => setPeriodType(e.target.value)} style={{ ...inp, width: "100%" }}>
                <option value="monthly">{T("Mensuel", "Monthly")}</option>
                <option value="quarterly">{T("Trimestriel", "Quarterly")}</option>
                <option value="yearly">{T("Annuel", "Yearly")}</option>
              </select>
            </div>
            <div style={{ display: "flex", alignItems: "end" }}>
              <button className="btn primary" onClick={onPull} disabled={loading || !selectedDef || !orgUnit}>
                {loading ? "…" : T("Charger les agrégats", "Pull aggregates")}
              </button>
            </div>
          </div>

          {result && (
            result.rows.length === 0 ? (
              <div className="text-faint" style={{ fontSize: 12, padding: 14, background: "var(--bg-sunken)", borderRadius: 6, textAlign: "center" }}>
                {T("DHIS2 a répondu mais aucune valeur sur ce range. Vérifiez orgUnit + périodes.", "DHIS2 returned but no value in this range. Check orgUnit + periods.")}
              </div>
            ) : (
              <>
                {stats && (
                  <div style={{ display: "flex", gap: 12, marginBottom: 10, flexWrap: "wrap", fontSize: 12 }}>
                    <div className="text-faint">{T("N: ", "N: ")}<b>{stats.count}</b></div>
                    <div className="text-faint">{T("Somme: ", "Sum: ")}<b>{stats.sum.toLocaleString()}</b></div>
                    <div className="text-faint">{T("Moyenne: ", "Avg: ")}<b>{stats.avg.toFixed(2)}</b></div>
                    <div className="text-faint">{T("Min: ", "Min: ")}<b>{stats.min}</b></div>
                    <div className="text-faint">{T("Max: ", "Max: ")}<b>{stats.max}</b></div>
                  </div>
                )}
                <div style={{ overflowX: "auto", maxHeight: 360, overflowY: "auto", border: "1px solid var(--line)", borderRadius: 6 }}>
                  <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
                    <thead style={{ position: "sticky", top: 0, background: "var(--bg-sunken)", zIndex: 1 }}>
                      <tr style={{ textAlign: "left", borderBottom: "1px solid var(--line)" }}>
                        <th style={{ padding: "6px 10px" }}>{T("Période DHIS2", "DHIS2 Period")}</th>
                        <th style={{ padding: "6px 10px", textAlign: "right" }}>{T("Valeur agrégée", "Aggregated value")}</th>
                      </tr>
                    </thead>
                    <tbody>
                      {result.rows.map((r, i) => (
                        <tr key={i} style={{ borderBottom: "1px solid var(--line)" }}>
                          <td style={{ padding: "6px 10px", fontFamily: "monospace" }}>{r.period}</td>
                          <td style={{ padding: "6px 10px", textAlign: "right", fontFamily: "monospace" }}>{r.value}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              </>
            )
          )}
        </>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// F1.3+ · OrgUnit picker modal
// ----------------------------------------------------------------------------
// Fetches /api/organisationUnits via the existing dhis2-proxy and shows a
// flat searchable list. Click to select → returns the UID via onSelect.
// Uses pagination to handle DHIS2 instances with thousands of orgUnits :
// load 500 first, "Load more" button for additional pages.
// ─────────────────────────────────────────────────────────────────────
function Dhis2OrgUnitPickerModal({ lang, connectionId, currentValue, onSelect, onClose }) {
  const Modal = window.Modal;
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const [loading, setLoading]   = useStateD(false);
  const [items, setItems]       = useStateD([]);     // [{ id, displayName, level, path }]
  const [page, setPage]         = useStateD(0);      // 0 = not loaded yet
  const [pageCount, setPageCount] = useStateD(null);
  const [err, setErr]           = useStateD(null);
  const [search, setSearch]     = useStateD("");

  const loadPage = async (p) => {
    setLoading(true); setErr(null);
    try {
      const data = await window.melr.dhis2ConnectionsCrud.raw(
        connectionId, "api/organisationUnits.json",
        { query: {
          fields: "id,displayName,level,path",
          pageSize: 500, page: p,
          order: "path:asc",
        } },
      );
      const arr  = (data && data.organisationUnits) || [];
      const pgr  = data && data.pager;
      setItems((prev) => p === 1 ? arr : [...prev, ...arr]);
      setPage(p);
      setPageCount(pgr ? pgr.pageCount : null);
    } catch (e) {
      setErr(e.message);
    } finally {
      setLoading(false);
    }
  };

  // Auto-load first page on open
  React.useEffect(() => { loadPage(1); /* eslint-disable-line */ }, []);

  // Filter items by search (client-side)
  const filtered = (() => {
    if (!search.trim()) return items;
    const s = search.toLowerCase();
    return items.filter((u) =>
      (u.displayName && u.displayName.toLowerCase().includes(s))
      || (u.id && u.id.toLowerCase().includes(s))
      || (u.path && u.path.toLowerCase().includes(s))
    );
  })();

  // Indentation visuelle selon le level (1 = pays, 2 = region, etc.)
  const indent = (lvl) => Math.max(0, (lvl || 1) - 1) * 14;

  return (
    <Modal isOpen={true} onClose={onClose}
      title={T("Choisir un orgUnit DHIS2", "Pick a DHIS2 orgUnit")}
      size="md">
      <div style={{ padding: 16 }}>
        <div className="text-faint" style={{ fontSize: 12, marginBottom: 10, lineHeight: 1.5 }}>
          {T(
            "Liste de votre instance DHIS2 (max 500 par page). L'UID s'affiche en monospace ; cliquez sur une ligne pour la sélectionner.",
            "List from your DHIS2 instance (max 500 per page). UIDs in monospace ; click a row to select it."
          )}
        </div>

        <input value={search} onChange={(e) => setSearch(e.target.value)}
          placeholder={T("Rechercher (nom, UID, chemin)…", "Search (name, UID, path)…")}
          autoFocus
          style={{ width: "100%", padding: "8px 10px", borderRadius: 6, border: "1px solid var(--line)", fontSize: 13, background: "var(--bg)", color: "var(--text)", marginBottom: 10, boxSizing: "border-box" }} />

        {err && (
          <div style={{ padding: 8, background: "#fee2e2", color: "#991b1b", borderRadius: 5, fontSize: 12, marginBottom: 10 }}>
            ⚠ {err}
          </div>
        )}

        {loading && items.length === 0 ? (
          <div className="text-faint" style={{ padding: 30, textAlign: "center", fontSize: 13 }}>
            {T("Chargement du catalogue d'orgUnits…", "Loading orgUnits catalogue…")}
          </div>
        ) : filtered.length === 0 ? (
          <div className="text-faint" style={{ padding: 20, textAlign: "center", fontSize: 13, background: "var(--bg-sunken)", borderRadius: 6 }}>
            {T("Aucun résultat pour cette recherche.", "No results for this search.")}
          </div>
        ) : (
          <div style={{ maxHeight: 360, overflowY: "auto", border: "1px solid var(--line)", borderRadius: 6 }}>
            {filtered.map((u) => {
              const isCurrent = u.id === currentValue;
              return (
                <button key={u.id} type="button"
                  onClick={() => onSelect(u.id)}
                  style={{
                    display: "block", width: "100%", textAlign: "left",
                    padding: "8px 12px",
                    background: isCurrent ? "color-mix(in oklch, var(--accent) 12%, transparent)" : "transparent",
                    border: 0, borderBottom: "1px solid var(--line)",
                    cursor: "pointer", fontSize: 12, color: "var(--text)",
                  }}
                  onMouseEnter={(e) => { e.currentTarget.style.background = "color-mix(in oklch, var(--accent) 6%, transparent)"; }}
                  onMouseLeave={(e) => { e.currentTarget.style.background = isCurrent ? "color-mix(in oklch, var(--accent) 12%, transparent)" : "transparent"; }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <span style={{ marginLeft: indent(u.level), color: "var(--text-faint)", fontSize: 10, width: 28 }}>
                      L{u.level || "?"}
                    </span>
                    <span style={{ flex: 1, fontWeight: isCurrent ? 600 : 400 }}>
                      {u.displayName || "—"}
                      {isCurrent && <span style={{ marginLeft: 6, color: "var(--accent)", fontSize: 10 }}>● {T("actuel", "current")}</span>}
                    </span>
                    <code style={{ fontSize: 10, color: "var(--text-faint)" }}>{u.id}</code>
                  </div>
                  {u.path && (
                    <div style={{ marginLeft: 36 + indent(u.level), fontSize: 10, color: "var(--text-faint)", marginTop: 2 }}>
                      {u.path}
                    </div>
                  )}
                </button>
              );
            })}
          </div>
        )}

        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 12, gap: 8 }}>
          <div className="text-faint" style={{ fontSize: 11 }}>
            {filtered.length} / {items.length} {T(" affichés", " shown")}
            {pageCount && pageCount > 1 && <> · {T("Page ", "Page ")}{page} / {pageCount}</>}
          </div>
          <div style={{ display: "flex", gap: 6 }}>
            {pageCount && page < pageCount && (
              <button type="button" className="btn ghost" onClick={() => loadPage(page + 1)} disabled={loading}>
                {loading ? "…" : T("Charger plus", "Load more")}
              </button>
            )}
            <button type="button" className="btn" onClick={onClose}>{T("Fermer", "Close")}</button>
          </div>
        </div>
      </div>
    </Modal>
  );
}

// Expose to app.jsx as a sidebar-routable screen
window.Dhis2Admin = Dhis2Admin;
