/* global React, Icon */
const { useState: useStateP, useEffect: useEffectP } = React;

// ==================== PROFILE ====================
function ProfileScreen({ t, lang, setLang }) {
  const [profile, setProfile] = useStateP(null);
  const [loading, setLoading] = useStateP(true);
  const [err, setErr]         = useStateP(null);

  // Form state
  const [fullName, setFullName] = useStateP("");
  const [locale,   setLocaleVal] = useStateP("fr");
  const [savingProfile, setSavingProfile] = useStateP(false);
  const [profileMsg, setProfileMsg]       = useStateP(null);

  // Password change
  const [pwd1, setPwd1] = useStateP("");
  const [pwd2, setPwd2] = useStateP("");
  const [savingPwd, setSavingPwd] = useStateP(false);
  const [pwdMsg,  setPwdMsg]      = useStateP(null);

  // Default display currency (via the same hook used by the topbar)
  const { currency, setCurrency, rates } = window.melr.useCurrency();

  useEffectP(() => {
    let cancelled = false;
    (async () => {
      try {
        const p = await window.melr.currentProfile();
        if (cancelled) return;
        if (!p) { setErr("not_logged_in"); setLoading(false); return; }
        setProfile(p);
        setFullName(p.full_name || "");
        setLocaleVal(p.locale || "fr");
      } catch (e) {
        if (!cancelled) setErr(e.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, []);

  if (loading) return <div className="page"><div className="page-body" style={{ padding: 40 }}>{window.L("Chargement du profil…", "Loading profile…", "Cargando el perfil…")}</div></div>;
  if (err === "not_logged_in") return <div className="page"><div className="page-body" style={{ padding: 40, color: "#b91c1c" }}>{window.L("Connectez-vous pour accéder à votre profil.", "Sign in to access your profile.", "Inicie sesión para acceder a su perfil.")}</div></div>;

  // Permet à la section avatar de demander un refresh du profil (pour
  // récupérer l'avatar_url à jour après upload/remove).
  const onAvatarChanged = async () => {
    try {
      const p = await window.melr.currentProfile();
      if (p) setProfile(p);
    } catch (_) {}
  };

  const saveProfile = async (e) => {
    e.preventDefault();
    setProfileMsg(null); setSavingProfile(true);
    try {
      const patch = { full_name: fullName.trim(), locale };
      const updated = await window.melr.updateProfile(patch);
      setProfile(updated);
      // Sync the global language toggle if it changed
      if (setLang && locale !== lang) setLang(locale);
      setProfileMsg({ tone: "success", text: window.L("Profil mis à jour.", "Profile updated.", "Perfil actualizado.") });
    } catch (e2) {
      setProfileMsg({ tone: "danger", text: e2.message });
    } finally {
      setSavingProfile(false);
    }
  };

  const savePwd = async (e) => {
    e.preventDefault();
    setPwdMsg(null);
    if (pwd1.length < 6) { setPwdMsg({ tone: "danger", text: window.L("Min. 6 caractères.", "Min 6 characters.", "Mín. 6 caracteres.") }); return; }
    if (pwd1 !== pwd2)   { setPwdMsg({ tone: "danger", text: window.L("Les deux mots de passe ne correspondent pas.", "Passwords don't match.", "Las dos contraseñas no coinciden.") }); return; }
    setSavingPwd(true);
    try {
      await window.melr.changePassword(pwd1);
      setPwd1(""); setPwd2("");
      setPwdMsg({ tone: "success", text: window.L("Mot de passe modifié.", "Password changed.", "Contraseña modificada.") });
    } catch (e2) {
      setPwdMsg({ tone: "danger", text: e2.message });
    } finally {
      setSavingPwd(false);
    }
  };

  const inp = { padding: "8px 10px", borderRadius: 6, border: "1px solid var(--line)", fontSize: 13, background: "var(--bg, white)", color: "var(--text, #111)" };
  const lbl = { fontSize: 11, opacity: 0.75 };

  return (
    <div className="page">
      <div className="page-header">
        <div className="page-eyebrow">{window.L("MON COMPTE", "MY ACCOUNT", "MI CUENTA")}</div>
        <div className="page-header-row">
          <div>
            <h1 className="page-title">{window.L("Mon profil", "My profile", "Mi perfil")}</h1>
            <div className="page-sub">{profile && profile.email}</div>
          </div>
        </div>
      </div>

      <div className="page-body" style={{ display: "grid", gap: 18, gridTemplateColumns: "1fr 1fr" }}>

        {/* === Photo de profil === */}
        <div style={{ gridColumn: "1 / -1" }}>
          <AvatarSection lang={lang} profile={profile} onChanged={onAvatarChanged} />
        </div>

        {/* === Informations === */}
        <form onSubmit={saveProfile} className="card" style={{ padding: 20 }}>
          <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 12 }}>
            {window.L("Informations", "Information", "Información")}
          </div>
          <div style={{ display: "grid", gap: 10 }}>
            <label style={{ display: "grid", gap: 4 }}>
              <span style={lbl}>{window.L("Nom complet", "Full name", "Nombre completo")}</span>
              <input value={fullName} onChange={(e) => setFullName(e.target.value)} required style={inp} />
            </label>
            <label style={{ display: "grid", gap: 4 }}>
              <span style={lbl}>{window.L("Email", "Email", "Correo electrónico")}</span>
              <input value={profile.email || ""} disabled style={{ ...inp, opacity: 0.6, cursor: "not-allowed" }} />
            </label>
            <label style={{ display: "grid", gap: 4 }}>
              <span style={lbl}>{window.L("Langue par défaut", "Default language", "Idioma por defecto")}</span>
              <select value={locale} onChange={(e) => setLocaleVal(e.target.value)} style={inp}>
                <option value="fr">Français</option>
                <option value="en">English</option>
              </select>
            </label>
            {profile.organization_id ? (
              <div className="text-faint" style={{ fontSize: 12 }}>
                <Icon.check className="xxs" /> {window.L("Membre de l'organisation REFT Africa", "Member of REFT Africa", "Miembro de la organización REFT Africa")}
              </div>
            ) : (
              <div style={{ color: "#92400e", fontSize: 12 }}>
                <Icon.info className="xxs" /> {window.L("Votre compte n'est pas encore rattaché à une organisation. Demandez à un administrateur de vous ajouter.", "Your account is not yet assigned to an organization. Ask an admin to add you.", "Su cuenta aún no está vinculada a una organización. Pida a un administrador que le añada.")}
              </div>
            )}
            {profileMsg && (
              <div style={{
                marginTop: 4, padding: "8px 10px", borderRadius: 6, fontSize: 12,
                background: profileMsg.tone === "success" ? "#dcfce7" : "#fee2e2",
                color:      profileMsg.tone === "success" ? "#166534" : "#991b1b",
              }}>{profileMsg.text}</div>
            )}
            <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 4 }}>
              <button type="submit" disabled={savingProfile}
                style={{ padding: "8px 14px", borderRadius: 6, border: 0, background: "#2563eb", color: "white", cursor: "pointer", fontWeight: 600 }}>
                {savingProfile ? "…" : (window.L("Enregistrer", "Save", "Guardar"))}
              </button>
            </div>
          </div>
        </form>

        {/* === Devise préférée === */}
        <div className="card" style={{ padding: 20 }}>
          <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 12 }}>
            {window.L("Devise d'affichage préférée", "Preferred display currency", "Moneda de visualización preferida")}
          </div>
          <div className="text-faint" style={{ fontSize: 12, marginBottom: 12 }}>
            {window.L("Choisissez la devise dans laquelle les montants sont affichés sur tous les écrans (Dashboard, Projets, Détail, etc.). Cette préférence est sauvegardée sur votre navigateur.", "Pick the currency for amounts shown across all screens. The choice is saved on your browser.", "Elija la moneda en la que se muestran los importes en todas las pantallas (Dashboard, Proyectos, Detalle, etc.). Esta preferencia se guarda en su navegador.")}
          </div>
          <div style={{ display: "grid", gap: 6 }}>
            {Object.entries(rates).map(([code, meta]) => (
              <button key={code} type="button"
                onClick={() => setCurrency(code)}
                className={"btn sm" + (code === currency ? " primary" : "")}
                style={{ justifyContent: "flex-start", textAlign: "left" }}>
                <span className="mono" style={{ width: 50, fontWeight: 600 }}>{code}</span>
                <span style={{ flex: 1 }}>{meta.name}</span>
                <span className="text-faint" style={{ fontSize: 11 }}>1 EUR = {meta.rate} {meta.symbol}</span>
                {code === currency && <Icon.check className="xxs" />}
              </button>
            ))}
          </div>
          <div className="text-faint" style={{ fontSize: 11, marginTop: 10 }}>
            {window.L("Vous pouvez ajouter d'autres devises depuis le sélecteur dans la barre du haut.", "You can add more currencies from the topbar selector.", "Puede añadir otras monedas desde el selector de la barra superior.")}
          </div>
        </div>

        {/* === Mot de passe === */}
        <form onSubmit={savePwd} className="card" style={{ padding: 20 }}>
          <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 12 }}>
            {window.L("Changer le mot de passe", "Change password", "Cambiar la contraseña")}
          </div>
          <div style={{ display: "grid", gap: 10 }}>
            <label style={{ display: "grid", gap: 4 }}>
              <span style={lbl}>{window.L("Nouveau mot de passe", "New password", "Nueva contraseña")}</span>
              <input type="password" value={pwd1} onChange={(e) => setPwd1(e.target.value)}
                autoComplete="new-password" required minLength={6} style={inp} />
            </label>
            <label style={{ display: "grid", gap: 4 }}>
              <span style={lbl}>{window.L("Confirmer", "Confirm", "Confirmar")}</span>
              <input type="password" value={pwd2} onChange={(e) => setPwd2(e.target.value)}
                autoComplete="new-password" required minLength={6} style={inp} />
            </label>
            {pwdMsg && (
              <div style={{
                padding: "8px 10px", borderRadius: 6, fontSize: 12,
                background: pwdMsg.tone === "success" ? "#dcfce7" : "#fee2e2",
                color:      pwdMsg.tone === "success" ? "#166534" : "#991b1b",
              }}>{pwdMsg.text}</div>
            )}
            <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 4 }}>
              <button type="submit" disabled={savingPwd}
                style={{ padding: "8px 14px", borderRadius: 6, border: 0, background: "#7c3aed", color: "white", cursor: "pointer", fontWeight: 600 }}>
                {savingPwd ? "…" : (window.L("Modifier", "Update", "Modificar"))}
              </button>
            </div>
          </div>
        </form>

        {/* === Sécurité · 2FA === */}
        <SecuritySection lang={lang} />

        {/* === Notifications par email (D8 · digest) === */}
        <NotificationsSection lang={lang} />

        {/* === K.2 · RGPD · Export portabilité === */}
        <GdprExportSection lang={lang} profile={profile} />

        {/* === K.3 · RGPD · Droit à l'oubli === */}
        <DeletionRequestSection lang={lang} profile={profile} />

        {/* === Session === */}
        <div className="card" style={{ padding: 20 }}>
          <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 12 }}>
            {window.L("Session", "Session", "Sesión")}
          </div>
          <div style={{ display: "grid", gap: 8 }}>
            <div className="text-faint" style={{ fontSize: 12 }}>
              {window.L("Vous êtes connecté en tant que", "Signed in as", "Ha iniciado sesión como")} <b>{profile.email}</b>
            </div>
            <button type="button"
              onClick={async () => { await window.melr.supabase.auth.signOut(); }}
              style={{ padding: "8px 14px", borderRadius: 6, border: "1px solid #b91c1c", background: "#dc2626", color: "white", cursor: "pointer", marginTop: 8, alignSelf: "flex-start", fontWeight: 500 }}>
              <Icon.logout className="xxs" /> {window.L("Se déconnecter", "Sign out", "Cerrar sesión")}
            </button>
          </div>
        </div>

      </div>
    </div>
  );
}

// ============================================================================
// AvatarSection · Photo de profil
// ----------------------------------------------------------------------------
// Bouton pour téléverser une image qui remplacera les initiales partout
// dans l'app (topbar en premier). Upload direct sur Storage (bucket
// 'avatars') via la helper window.melr.uploadAvatar. Max 2 Mo, formats
// image standards. Bouton "Supprimer" pour revenir aux initiales.
// ============================================================================
function AvatarSection({ lang, profile, onChanged }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const fileInputRef = React.useRef(null);
  const [busy, setBusy] = useStateP(false);
  const [err,  setErr]  = useStateP(null);
  const [msg,  setMsg]  = useStateP(null);

  const url = profile && profile.avatar_url;
  const initials = ((profile && (profile.full_name || profile.email)) || "??")
    .split(/[\s@.\-_]+/).filter(Boolean).slice(0, 2)
    .map((s) => s[0].toUpperCase()).join("");

  const pickFile = () => {
    setErr(null); setMsg(null);
    if (fileInputRef.current) fileInputRef.current.click();
  };

  const onFile = async (e) => {
    const file = e.target.files && e.target.files[0];
    e.target.value = "";  // permet de re-sélectionner le même fichier
    if (!file) return;
    setBusy(true); setErr(null); setMsg(null);
    try {
      await window.melr.uploadAvatar(file);
      setMsg(T("Photo de profil enregistrée.", "Profile photo saved."));
      if (onChanged) await onChanged();
    } catch (e) { setErr(e.message || String(e)); }
    finally { setBusy(false); }
  };

  const onRemove = async () => {
    if (!window.confirm(T(
      "Supprimer votre photo de profil ?",
      "Remove your profile photo?"
    ))) return;
    setBusy(true); setErr(null); setMsg(null);
    try {
      await window.melr.removeAvatar();
      setMsg(T("Photo supprimée.", "Photo removed."));
      if (onChanged) await onChanged();
    } catch (e) { setErr(e.message || String(e)); }
    finally { setBusy(false); }
  };

  // Cercle 96px : montre la photo si présente, sinon initiales sur fond bleu
  const circle = (
    <div style={{
      width: 96, height: 96, borderRadius: "50%",
      background: url ? "transparent" : "linear-gradient(135deg, #3b82f6, #1e40af)",
      color: "white", fontSize: 32, fontWeight: 700,
      display: "flex", alignItems: "center", justifyContent: "center",
      overflow: "hidden", border: "3px solid white",
      boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
      flexShrink: 0,
    }}>
      {url ? (
        <img src={url} alt=""
          referrerPolicy="no-referrer"
          onError={(e) => { e.currentTarget.style.display = "none"; }}
          style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
      ) : initials}
    </div>
  );

  return (
    <div className="card" style={{ padding: 20 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
        <span style={{ fontSize: 18 }}>🖼️</span>
        <div style={{ fontSize: 15, fontWeight: 600 }}>
          {T("Photo de profil", "Profile photo")}
        </div>
      </div>

      <div style={{ display: "flex", alignItems: "center", gap: 20, flexWrap: "wrap" }}>
        {circle}

        <div style={{ flex: 1, minWidth: 240 }}>
          <div className="text-faint" style={{ fontSize: 12.5, lineHeight: 1.6, marginBottom: 12 }}>
            {T(
              "Ajoutez une photo pour qu'elle apparaisse à la place de vos initiales dans l'interface (barre du haut, listes d'utilisateurs, etc.). Formats : JPEG, PNG, WebP ou GIF. Taille max : 2 Mo.",
              "Add a photo so it appears instead of your initials throughout the app (topbar, user lists, etc.). Formats: JPEG, PNG, WebP or GIF. Max size: 2 MB."
            )}
          </div>

          <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
            <button type="button" onClick={pickFile} disabled={busy}
              style={{
                padding: "8px 14px", borderRadius: 6,
                border: "1px solid #1d4ed8", background: busy ? "#cbd5e1" : "#2563eb",
                color: "white", cursor: busy ? "default" : "pointer",
                fontWeight: 500, fontSize: 13, display: "inline-flex", alignItems: "center", gap: 6,
              }}>
              {busy ? T("Envoi…", "Uploading…") : (url
                ? T("Changer la photo", "Change photo")
                : T("Téléverser une photo", "Upload a photo"))}
            </button>
            {url && (
              <button type="button" onClick={onRemove} disabled={busy}
                style={{
                  padding: "8px 14px", borderRadius: 6,
                  border: "1px solid #cbd5e1", background: "white", color: "#334155",
                  cursor: busy ? "default" : "pointer", fontSize: 13,
                }}>
                {T("Supprimer", "Remove")}
              </button>
            )}
            <input ref={fileInputRef} type="file" accept="image/jpeg,image/png,image/webp,image/gif"
              onChange={onFile} style={{ display: "none" }} />
          </div>

          {msg && (
            <div style={{
              marginTop: 12, padding: 8, borderRadius: 6,
              background: "#dcfce7", border: "1px solid #86efac", color: "#166534", fontSize: 12.5,
            }}>{msg}</div>
          )}
          {err && (
            <div style={{
              marginTop: 12, padding: 8, borderRadius: 6,
              background: "#fee2e2", border: "1px solid #fca5a5", color: "#7f1d1d", fontSize: 12.5,
            }}>{err}</div>
          )}
        </div>
      </div>
    </div>
  );
}

// ============================================================================
// SecuritySection · 2FA TOTP (C1)
// ----------------------------------------------------------------------------
// Liste les facteurs MFA actifs et permet d'en activer un nouveau via
// modal QR + vérification du code à 6 chiffres.
// ============================================================================
function SecuritySection({ lang }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const [factors, setFactors] = useStateP({ totp: [], all: [] });
  const [loading, setLoading] = useStateP(true);
  const [enrollOpen, setEnrollOpen] = useStateP(false);
  const [busy, setBusy] = useStateP(false);
  const [err, setErr] = useStateP(null);

  const refresh = async () => {
    setLoading(true); setErr(null);
    try {
      const f = await window.melr.mfaListFactors();
      setFactors(f);
    } catch (e) { setErr(e.message); }
    finally { setLoading(false); }
  };
  useEffectP(() => { refresh(); }, []);

  // Filtre les facteurs « verified » uniquement — les unverified sont
  // des enrollments abandonnés qu'on peut nettoyer.
  const verifiedTotp = (factors.totp || []).filter((f) => f.status === "verified");
  const unverifiedTotp = (factors.totp || []).filter((f) => f.status !== "verified");

  const onUnenroll = async (factorId) => {
    if (!window.confirm(T(
      "Désactiver la 2FA ? Vous pourrez vous reconnecter sans code, mais votre compte sera moins sécurisé.",
      "Disable 2FA? You'll be able to sign in without a code, but your account will be less secure."
    ))) return;
    setBusy(true); setErr(null);
    try { await window.melr.mfaUnenroll(factorId); await refresh(); }
    catch (e) { setErr(e.message); }
    finally { setBusy(false); }
  };

  const onCleanupUnverified = async (factorId) => {
    setBusy(true); setErr(null);
    try { await window.melr.mfaUnenroll(factorId); await refresh(); }
    catch (e) { setErr(e.message); }
    finally { setBusy(false); }
  };

  return (
    <div className="card" style={{ padding: 20 }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
        <div style={{ fontSize: 15, fontWeight: 600 }}>
          🔒 {T("Sécurité · Authentification à deux facteurs (2FA)", "Security · Two-factor authentication (2FA)")}
        </div>
        {verifiedTotp.length === 0 && !loading && (
          <button type="button" className="btn sm primary" onClick={() => setEnrollOpen(true)} disabled={busy}>
            <Icon.shieldCheck /> {T("Activer 2FA", "Enable 2FA")}
          </button>
        )}
      </div>

      <div className="text-faint" style={{ fontSize: 12, marginBottom: 12 }}>
        {T(
          "La 2FA ajoute un code à 6 chiffres généré par votre téléphone à chaque connexion. Recommandé pour les comptes ayant accès à des données sensibles (santé, finances).",
          "2FA adds a 6-digit code generated by your phone on each sign-in. Recommended for accounts with access to sensitive data (health, financial)."
        )}
      </div>

      {loading ? (
        <div className="text-faint" style={{ fontSize: 12 }}>{T("Chargement…", "Loading…")}</div>
      ) : verifiedTotp.length > 0 ? (
        <div>
          {verifiedTotp.map((f) => (
            <div key={f.id} style={{
              display: "flex", alignItems: "center", gap: 10,
              padding: "10px 12px", background: "#dcfce7",
              border: "1px solid #86efac", borderRadius: 6,
            }}>
              <span style={{ fontSize: 18 }}>✅</span>
              <div style={{ flex: 1 }}>
                <div style={{ fontWeight: 500, fontSize: 13 }}>
                  {T("2FA activée", "2FA enabled")}{f.friendly_name ? " · " + f.friendly_name : ""}
                </div>
                <div className="text-faint" style={{ fontSize: 11, marginTop: 2 }}>
                  {T("Activée le ", "Enabled on ")}{f.created_at ? new Date(f.created_at).toLocaleDateString() : "?"}
                </div>
              </div>
              <button className="btn xs ghost" onClick={() => onUnenroll(f.id)} disabled={busy}
                style={{ color: "#b91c1c" }}>
                {T("Désactiver", "Disable")}
              </button>
            </div>
          ))}
        </div>
      ) : (
        <div className="text-faint" style={{ fontSize: 12, padding: "10px 12px", background: "var(--bg-sunken)", borderRadius: 6 }}>
          {T(
            "Aucune authentification à deux facteurs configurée. Cliquez « Activer 2FA » pour démarrer.",
            "No two-factor authentication set up. Click 'Enable 2FA' to get started."
          )}
        </div>
      )}

      {/* Nettoyage des factors non-vérifiés (enrollment abandonné) */}
      {unverifiedTotp.length > 0 && (
        <div style={{ marginTop: 8, fontSize: 11, color: "var(--text-faint)" }}>
          {T("Enrollment(s) en attente : ", "Pending enrollment(s): ")}
          {unverifiedTotp.map((f) => (
            <button key={f.id} className="btn xs ghost" onClick={() => onCleanupUnverified(f.id)} disabled={busy}
              style={{ marginLeft: 4, fontSize: 10 }}>
              ✕ {T("nettoyer", "cleanup")}
            </button>
          ))}
        </div>
      )}

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

      {enrollOpen && (
        <Enable2FAModal lang={lang} onClose={() => setEnrollOpen(false)}
          onCompleted={async () => { setEnrollOpen(false); await refresh(); }} />
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// Enable2FAModal · 2-step enrollment (QR scan + verify code)
// ─────────────────────────────────────────────────────────────────────
function Enable2FAModal({ lang, onClose, onCompleted }) {
  const Modal = window.Modal;
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const [step, setStep] = useStateP("init"); // init → qr → success
  const [enrollment, setEnrollment] = useStateP(null); // { factorId, qrCode, secret, uri }
  const [code, setCode] = useStateP("");
  const [busy, setBusy] = useStateP(false);
  const [err, setErr] = useStateP(null);

  useEffectP(() => {
    // Au montage : démarre l'enrollment côté Supabase (génère QR + secret)
    let cancelled = false;
    (async () => {
      setBusy(true); setErr(null);
      try {
        const e = await window.melr.mfaEnroll();
        if (!cancelled) { setEnrollment(e); setStep("qr"); }
      } catch (ex) { if (!cancelled) setErr(ex.message); }
      finally { if (!cancelled) setBusy(false); }
    })();
    return () => { cancelled = true; };
  }, []);

  const verify = async () => {
    if (!code || code.length < 6) {
      setErr(T("Entrez le code à 6 chiffres affiché par votre app.", "Enter the 6-digit code shown by your app."));
      return;
    }
    setBusy(true); setErr(null);
    try {
      await window.melr.mfaVerify(enrollment.factorId, code);
      setStep("success");
      // Court délai pour montrer le succès, puis ferme.
      setTimeout(() => { onCompleted(); }, 1500);
    } catch (e) { setErr(e.message); setBusy(false); }
  };

  const copySecret = () => {
    if (enrollment && enrollment.secret) {
      try { navigator.clipboard.writeText(enrollment.secret); } catch (_) {}
    }
  };

  return (
    <Modal title={T("Activer la 2FA", "Enable 2FA")} onClose={busy && step !== "qr" ? null : onClose} size="md"
      footer={step === "qr" ? <>
        <button className="btn sm" onClick={onClose} disabled={busy}>{T("Annuler", "Cancel")}</button>
        <button className="btn sm primary" onClick={verify} disabled={busy || !code || code.length < 6}>
          {busy ? "…" : T("Vérifier", "Verify")}
        </button>
      </> : <button className="btn sm" onClick={onClose}>{T("Fermer", "Close")}</button>}>
      {step === "init" && (
        <div style={{ padding: 20, textAlign: "center", color: "var(--text-faint)" }}>
          {T("Génération du QR code…", "Generating QR code…")}
        </div>
      )}

      {step === "qr" && enrollment && (
        <div style={{ display: "grid", gap: 14, fontSize: 13 }}>
          <div>
            <strong>1. {T("Scannez ce QR code", "Scan this QR code")}</strong>
            <div className="text-faint" style={{ fontSize: 11.5, marginTop: 2 }}>
              {T(
                "Ouvrez une app d'authentification (Google Authenticator, Authy, 1Password, Microsoft Authenticator…) et scannez :",
                "Open an authenticator app (Google Authenticator, Authy, 1Password, Microsoft Authenticator…) and scan:"
              )}
            </div>
            <div style={{
              marginTop: 10, padding: 16, background: "white",
              borderRadius: 8, border: "1px solid var(--line)",
              display: "flex", justifyContent: "center",
            }}>
              {/* enrollment.qrCode est un data:image/svg+xml;... */}
              <img src={enrollment.qrCode} alt="QR code 2FA"
                style={{ width: 200, height: 200 }} />
            </div>
            <details style={{ marginTop: 8, fontSize: 11 }}>
              <summary style={{ cursor: "pointer", color: "var(--text-faint)" }}>
                {T("Vous ne pouvez pas scanner ? Entrer le code manuellement", "Can't scan? Enter the code manually")}
              </summary>
              <div style={{ marginTop: 6, padding: 8, background: "var(--bg-sunken)", borderRadius: 4, fontFamily: "monospace", fontSize: 12, wordBreak: "break-all" }}>
                {enrollment.secret}
                <button className="btn xs ghost" onClick={copySecret} style={{ marginLeft: 8 }}>
                  {T("Copier", "Copy")}
                </button>
              </div>
            </details>
          </div>

          <div>
            <strong>2. {T("Entrez le code à 6 chiffres", "Enter the 6-digit code")}</strong>
            <div className="text-faint" style={{ fontSize: 11.5, marginTop: 2, marginBottom: 6 }}>
              {T(
                "Une fois le QR scanné, votre app affiche un code qui change toutes les 30 s.",
                "Once the QR is scanned, your app shows a code that changes every 30s."
              )}
            </div>
            <input type="text" inputMode="numeric" pattern="\d{6}" maxLength={6} autoFocus
              value={code} onChange={(e) => setCode(e.target.value.replace(/\D/g, ""))}
              placeholder="123456"
              style={{
                fontSize: 24, fontFamily: "monospace", letterSpacing: "0.3em",
                textAlign: "center", padding: "10px 12px", width: "100%",
                borderRadius: 6, border: "1px solid var(--line)",
                background: "var(--bg, white)", color: "var(--text)",
                boxSizing: "border-box",
              }}
              onKeyDown={(e) => { if (e.key === "Enter" && code.length === 6) verify(); }} />
          </div>

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

          <div style={{ padding: 10, background: "#fef3c7", color: "#a16207", borderRadius: 6, fontSize: 11.5 }}>
            ⚠ <strong>{T("Important :", "Important:")}</strong> {T(
              "Conservez le code secret ou les codes de récupération de votre app. Sans accès à votre téléphone, vous ne pourrez plus vous connecter.",
              "Keep the secret or recovery codes from your app safe. Without access to your phone, you won't be able to sign in."
            )}
          </div>
        </div>
      )}

      {step === "success" && (
        <div style={{ padding: 30, textAlign: "center" }}>
          <div style={{ fontSize: 48 }}>✅</div>
          <div style={{ fontSize: 18, fontWeight: 600, marginTop: 8 }}>
            {T("2FA activée !", "2FA enabled!")}
          </div>
          <div className="text-faint" style={{ fontSize: 12, marginTop: 6 }}>
            {T(
              "À votre prochaine connexion, après le mot de passe, votre app vous demandera un code.",
              "On your next sign-in, after your password, your app will ask for a code."
            )}
          </div>
        </div>
      )}

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

// ============================================================================
// K.2 · GdprExportSection · Export de mes données personnelles (Article 20)
// ----------------------------------------------------------------------------
// Bouton qui appelle la RPC export_user_data(self) et propose le download
// du JSON brut (machine-readable) ou d'un XLSX multi-onglets (human-readable).
// La RPC trace elle-même l'export dans audit_log (GDPR_EXPORT event).
// ============================================================================
function GdprExportSection({ lang, profile }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const [busy, setBusy] = useStateP(false);
  const [err, setErr]   = useStateP(null);
  const [msg, setMsg]   = useStateP(null);
  const [lastSize, setLastSize] = useStateP(null);

  const runExport = async (format) => {
    setErr(null); setMsg(null); setBusy(true);
    try {
      const data = await window.melr.exportUserData();
      const date = new Date().toISOString().slice(0, 10);
      const slug = (profile && profile.email || "user").replace(/[^a-z0-9]+/gi, "_");
      if (format === "json") {
        window.melr.downloadJson(data, `melr_export_${slug}_${date}.json`);
      } else {
        window.melr.downloadGdprXlsx(data, `melr_export_${slug}_${date}.xlsx`);
      }
      // Estime la taille pour info
      const approxKb = Math.round(JSON.stringify(data).length / 1024);
      setLastSize(approxKb);
      setMsg(T(
        `Export ${format.toUpperCase()} téléchargé (~${approxKb} Ko). L'opération a été tracée dans le journal d'audit.`,
        `${format.toUpperCase()} export downloaded (~${approxKb} KB). The operation has been logged in the audit trail.`
      ));
    } catch (e) {
      console.warn("[MELR] GDPR export:", e);
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="card" style={{ padding: 20 }}>
      <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 10, gap: 16 }}>
        <div>
          <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 4 }}>
            📦 {T("Mes données personnelles (RGPD)", "My personal data (GDPR)")}
          </div>
          <div className="text-faint" style={{ fontSize: 12, maxWidth: 580, lineHeight: 1.5 }}>
            {T(
              "Article 20 du RGPD — Droit à la portabilité. Téléchargez l'ensemble des données personnelles que MELR conserve à votre sujet : profil, organisations, indicateurs saisis, activités, sites, documents, journal d'audit. Format JSON pour transfert vers un autre prestataire, ou XLSX multi-onglets pour consultation.",
              "GDPR Article 20 — Right to data portability. Download all the personal data MELR holds about you: profile, organizations, indicator values, activities, sites, documents, audit trail. JSON format to transfer to another provider, or XLSX multi-tab for human reading."
            )}
          </div>
        </div>
      </div>

      <div style={{ display: "flex", gap: 10, flexWrap: "wrap", alignItems: "center", marginTop: 6 }}>
        <button type="button" disabled={busy} onClick={() => runExport("json")}
          style={{
            padding: "8px 14px", borderRadius: 6, border: "1px solid #1d4ed8",
            background: "#2563eb", color: "white", cursor: busy ? "wait" : "pointer",
            fontWeight: 500, display: "inline-flex", alignItems: "center", gap: 6,
          }}>
          <Icon.download className="xxs" /> {busy ? "…" : T("Télécharger JSON", "Download JSON")}
        </button>
        <button type="button" disabled={busy} onClick={() => runExport("xlsx")}
          style={{
            padding: "8px 14px", borderRadius: 6, border: "1px solid #15803d",
            background: "#16a34a", color: "white", cursor: busy ? "wait" : "pointer",
            fontWeight: 500, display: "inline-flex", alignItems: "center", gap: 6,
          }}>
          <Icon.download className="xxs" /> {busy ? "…" : T("Télécharger XLSX", "Download XLSX")}
        </button>
        <div className="text-faint" style={{ fontSize: 11, marginLeft: "auto" }}>
          {T("L'export inclut un horodatage et est tracé dans le journal d'audit.",
             "Export includes a timestamp and is logged in the audit trail.")}
        </div>
      </div>

      {msg && (
        <div style={{
          marginTop: 12, padding: "8px 12px", borderRadius: 6, fontSize: 12,
          background: "#d1fae5", color: "#065f46", border: "1px solid #6ee7b7",
        }}>{msg}</div>
      )}
      {err && (
        <div style={{
          marginTop: 12, padding: "8px 12px", borderRadius: 6, fontSize: 12,
          background: "#fee2e2", color: "#991b1b", border: "1px solid #fca5a5",
        }}>{err}</div>
      )}
    </div>
  );
}

// ============================================================================
// DeletionRequestSection · K.3 · RGPD Article 17 (droit à l'effacement)
// ----------------------------------------------------------------------------
// Bouton « Demander la suppression de mon compte ». Crée une demande
// 'pending' qu'un super-admin doit ensuite approuver (anonymisation) ou
// refuser (motif requis). L'user voit le statut de sa demande en cours
// et peut l'annuler tant qu'elle est pending.
// ============================================================================
function DeletionRequestSection({ lang, profile }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const { data: myRequests, loading, refresh } = window.melr.useDeletionRequests
    ? window.melr.useDeletionRequests({ scope: "self" })
    : { data: [], loading: false, refresh: () => {} };

  const [confirmOpen, setConfirmOpen] = useStateP(false);
  const [reason, setReason]           = useStateP("");
  const [busy, setBusy]               = useStateP(false);
  const [err, setErr]                 = useStateP(null);

  // La demande la plus récente (par convention : on n'en a qu'une pending)
  const latest = (myRequests || [])[0] || null;
  const pending = latest && latest.status === "pending";

  const onSubmit = async () => {
    setBusy(true); setErr(null);
    try {
      await window.melr.requestAccountDeletion(reason.trim() || null);
      setConfirmOpen(false);
      setReason("");
      refresh();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const onCancel = async () => {
    if (!latest) return;
    if (!window.confirm(T(
      "Annuler la demande de suppression ?",
      "Cancel the deletion request?"
    ))) return;
    setBusy(true); setErr(null);
    try {
      await window.melr.cancelDeletionRequest(latest.id);
      refresh();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const fmtDate = (iso) => {
    if (!iso) return "—";
    try { return new Date(iso).toLocaleString(window.L("fr-FR", "en-GB", "es-ES")); }
    catch (_) { return iso; }
  };

  const statusBadge = (status) => {
    const m = {
      pending:   { fr: "En attente",  en: "Pending", es: "Pendiente",   bg: "#fef3c7", color: "#92400e" },
      approved:  { fr: "Approuvée",   en: "Approved", es: "Aprobada",  bg: "#dcfce7", color: "#166534" },
      rejected:  { fr: "Refusée",     en: "Rejected", es: "Rechazada",  bg: "#fee2e2", color: "#991b1b" },
      cancelled: { fr: "Annulée",     en: "Cancelled", es: "Cancelada", bg: "#f1f5f9", color: "#475569" },
    }[status] || { fr: status, en: status, bg: "#f1f5f9", color: "#475569" };
    return (
      <span style={{
        display: "inline-block", padding: "2px 8px", borderRadius: 10,
        background: m.bg, color: m.color, fontSize: 11, fontWeight: 600,
      }}>{T(m.fr, m.en)}</span>
    );
  };

  return (
    <div className="card" style={{ padding: 20, borderLeft: "4px solid #dc2626" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
        <span style={{ fontSize: 20 }}>🗑️</span>
        <div style={{ fontSize: 15, fontWeight: 600 }}>
          {T("Suppression de mon compte (RGPD · droit à l'oubli)",
             "Delete my account (GDPR · right to erasure)")}
        </div>
      </div>

      <div className="text-faint" style={{ fontSize: 12.5, marginBottom: 14, lineHeight: 1.5 }}>
        {T(
          "Article 17 du RGPD : vous avez le droit de demander la suppression de votre compte. La demande est soumise à un super-administrateur qui dispose de 30 jours pour la traiter. Une fois approuvée, votre identité (email, nom) est anonymisée et votre accès révoqué. Les données métier que vous avez saisies (indicateurs, activités, sites) sont conservées sans lien à votre identité.",
          "GDPR Article 17: you have the right to request the deletion of your account. The request is reviewed by a super-administrator within 30 days. Once approved, your identity (email, name) is anonymized and your access revoked. The business data you contributed (indicators, activities, sites) is kept without any link to your identity."
        )}
      </div>

      {/* État de la demande en cours, si applicable */}
      {!loading && latest && (
        <div style={{
          padding: 12, marginBottom: 14,
          background: pending ? "#fef3c7" : "#f8fafc",
          border: "1px solid " + (pending ? "#fde68a" : "#e2e8f0"),
          borderRadius: 6, fontSize: 12.5,
        }}>
          <div style={{ marginBottom: 6 }}>
            <strong>{T("Demande en cours :", "Current request:")}</strong> {statusBadge(latest.status)}
            {latest.overdue_30d && (
              <span style={{
                marginLeft: 8, padding: "2px 8px", borderRadius: 10,
                background: "#fef2f2", color: "#991b1b", fontSize: 10, fontWeight: 700,
              }}>
                {T("> 30 jours sans réponse", "> 30 days unresolved")}
              </span>
            )}
          </div>
          <div style={{ color: "var(--text-faint)", fontSize: 11.5 }}>
            {T("Soumise le", "Submitted")} {fmtDate(latest.requested_at)}
            {latest.processed_at && (
              <> · {T("Traitée le", "Processed")} {fmtDate(latest.processed_at)}</>
            )}
          </div>
          {latest.reason && (
            <div style={{ marginTop: 6, fontStyle: "italic", color: "#475569" }}>
              « {latest.reason} »
            </div>
          )}
          {latest.admin_notes && (
            <div style={{ marginTop: 6, fontSize: 11.5, color: "#475569" }}>
              <strong>{T("Note de l'admin :", "Admin note:")}</strong> {latest.admin_notes}
            </div>
          )}
          {pending && (
            <div style={{ marginTop: 10 }}>
              <button type="button" onClick={onCancel} disabled={busy}
                style={{
                  padding: "6px 12px", borderRadius: 6,
                  border: "1px solid #94a3b8", background: "white",
                  color: "#334155", cursor: busy ? "default" : "pointer", fontSize: 12,
                }}>
                {T("Annuler ma demande", "Cancel my request")}
              </button>
            </div>
          )}
        </div>
      )}

      {/* Bouton pour créer une nouvelle demande (si pas de pending en cours) */}
      {!pending && (
        <button type="button" onClick={() => setConfirmOpen(true)} disabled={busy}
          style={{
            padding: "10px 16px", borderRadius: 6,
            border: "1px solid #b91c1c", background: busy ? "#fecaca" : "#dc2626",
            color: "white", cursor: busy ? "default" : "pointer",
            fontWeight: 600, fontSize: 13,
          }}>
          {T("Demander la suppression de mon compte", "Request account deletion")}
        </button>
      )}

      {err && (
        <div style={{
          padding: 10, marginTop: 10,
          background: "#fee2e2", border: "1px solid #fca5a5",
          borderRadius: 6, fontSize: 12.5, color: "#7f1d1d",
        }}>
          {err}
        </div>
      )}

      {/* Modal de confirmation avec motif */}
      {confirmOpen && (
        <div style={{
          position: "fixed", inset: 0, background: "rgba(15,23,42,0.55)",
          display: "flex", alignItems: "center", justifyContent: "center",
          zIndex: 1000, padding: 20,
        }} onClick={() => !busy && setConfirmOpen(false)}>
          <div style={{
            background: "white", borderRadius: 10, maxWidth: 520, width: "100%",
            padding: 24, boxShadow: "0 20px 50px rgba(0,0,0,0.25)",
          }} onClick={(e) => e.stopPropagation()}>
            <h3 style={{ margin: 0, marginBottom: 12, fontSize: 17, color: "#7f1d1d" }}>
              ⚠️ {T("Confirmer la demande de suppression", "Confirm deletion request")}
            </h3>
            <p style={{ fontSize: 13, lineHeight: 1.6, color: "#475569", marginBottom: 14 }}>
              {T(
                "Une fois approuvée par un super-administrateur, l'opération est irréversible. Votre profil sera anonymisé et votre accès à la plateforme révoqué.",
                "Once approved by a super-administrator, the operation is irreversible. Your profile will be anonymized and your access to the platform revoked."
              )}
            </p>
            <label style={{ display: "block", fontSize: 12, fontWeight: 600, marginBottom: 4 }}>
              {T("Motif (facultatif)", "Reason (optional)")}
            </label>
            <textarea value={reason} onChange={(e) => setReason(e.target.value)}
              rows={3} maxLength={500}
              placeholder={T(
                "Ex. Je quitte mon organisation et ne souhaite plus que mes données soient associées à mon nom.",
                "E.g. I am leaving my organization and don't want my data associated with my name anymore."
              )}
              style={{
                width: "100%", boxSizing: "border-box",
                padding: 8, borderRadius: 6, border: "1px solid #cbd5e1",
                fontFamily: "inherit", fontSize: 12.5, resize: "vertical",
              }} />
            <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 16 }}>
              <button type="button" onClick={() => setConfirmOpen(false)} disabled={busy}
                style={{
                  padding: "8px 14px", borderRadius: 6,
                  border: "1px solid #94a3b8", background: "white", color: "#334155",
                  cursor: busy ? "default" : "pointer", fontSize: 13,
                }}>
                {T("Annuler", "Cancel")}
              </button>
              <button type="button" onClick={onSubmit} disabled={busy}
                style={{
                  padding: "8px 14px", borderRadius: 6, border: "1px solid #b91c1c",
                  background: busy ? "#fecaca" : "#dc2626", color: "white",
                  cursor: busy ? "default" : "pointer", fontWeight: 600, fontSize: 13,
                }}>
                {busy ? T("Envoi…", "Submitting…") : T("Confirmer la demande", "Confirm request")}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ============================================================================
// NotificationsSection · D8 · préférences digest email
// ----------------------------------------------------------------------------
// L'utilisateur choisit :
//   - fréquence (off / quotidien / hebdomadaire)
//   - catégories (cases à cocher)
//   - heure locale d'envoi (slider 0-23)
//   - timezone (auto-détectée, modifiable)
// Bouton "Tester maintenant" : appelle l'Edge Function send-digest pour
// l'utilisateur courant. Bouton "Aperçu" : montre le payload jsonb que
// la RPC build_user_digest générerait sur les 7 derniers jours.
// ============================================================================
const ALL_CATEGORIES = [
  { id: "new_projects",         fr: "Nouveaux projets",                en: "New projects", es: "Nuevos proyectos" },
  { id: "new_indicators",       fr: "Nouveaux indicateurs",            en: "New indicators", es: "Nuevos indicadores" },
  { id: "new_activities",       fr: "Nouvelles activités",             en: "New activities", es: "Nuevas actividades" },
  { id: "validations_pending",  fr: "Validations en attente",          en: "Pending validations", es: "Validaciones pendientes" },
  { id: "mentions",             fr: "Mentions (@vous)",                en: "Mentions (@you)", es: "Menciones (@tú)" },
];

function NotificationsSection({ lang }) {
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const { data: prefs, loading } = window.melr.useNotificationPrefs();
  const [freq,     setFreq]     = useStateP("off");
  const [cats,     setCats]     = useStateP([]);
  const [hour,     setHour]     = useStateP(8);
  const [tz,       setTz]       = useStateP("UTC");
  const [busy,     setBusy]     = useStateP(false);
  const [msg,      setMsg]      = useStateP(null);
  const [previewOpen, setPreviewOpen] = useStateP(false);
  const [preview,  setPreview]  = useStateP(null);

  // Hydrate depuis le hook quand les prefs arrivent
  useEffectP(() => {
    if (!prefs) return;
    setFreq(prefs.email_digest_frequency || "off");
    setCats(Array.isArray(prefs.email_digest_categories) ? prefs.email_digest_categories : []);
    setHour(typeof prefs.email_digest_hour === "number" ? prefs.email_digest_hour : 8);
    setTz(prefs.timezone || "UTC");
  }, [prefs]);

  const toggleCat = (id) => {
    setCats((cur) => cur.includes(id) ? cur.filter((c) => c !== id) : [...cur, id]);
  };

  const onSave = async () => {
    setMsg(null); setBusy(true);
    try {
      await window.melr.notificationPrefsCrud.save({
        email_digest_frequency: freq,
        email_digest_categories: cats,
        email_digest_hour: Number(hour) || 8,
        timezone: tz || "UTC",
      });
      setMsg({ tone: "success", text: T("Préférences enregistrées.", "Preferences saved.") });
    } catch (e) { setMsg({ tone: "danger", text: e.message }); }
    finally { setBusy(false); }
  };

  const onPreview = async () => {
    setMsg(null); setBusy(true);
    try {
      const p = await window.melr.notificationPrefsCrud.previewDigest();
      setPreview(p);
      setPreviewOpen(true);
    } catch (e) { setMsg({ tone: "danger", text: e.message }); }
    finally { setBusy(false); }
  };

  const onSendTest = async () => {
    if (!window.confirm(T(
      "Envoyer un digest de test à votre adresse email ?",
      "Send a test digest to your email address?"
    ))) return;
    setMsg(null); setBusy(true);
    try {
      await window.melr.notificationPrefsCrud.sendTestNow();
      setMsg({ tone: "success", text: T("Digest de test envoyé. Vérifiez votre boîte.", "Test digest sent. Check your inbox.") });
    } catch (e) { setMsg({ tone: "danger", text: e.message }); }
    finally { setBusy(false); }
  };

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

  return (
    <div className="card" style={{ padding: 20, gridColumn: "1 / -1" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
        <div style={{ fontSize: 15, fontWeight: 600 }}>
          📧 {T("Notifications par email · digest", "Email notifications · digest")}
        </div>
        {prefs && prefs.last_sent_at && (
          <div className="text-faint" style={{ fontSize: 11 }}>
            {T("Dernier envoi : ", "Last sent: ")}{new Date(prefs.last_sent_at).toLocaleString()}
          </div>
        )}
      </div>

      <div className="text-faint" style={{ fontSize: 12, marginBottom: 14 }}>
        {T(
          "Recevez un résumé périodique des nouveaux projets, indicateurs, activités et validations en attente. Aucun email instantané — uniquement un digest groupé pour éviter le spam.",
          "Get a periodic summary of new projects, indicators, activities, and pending validations. No instant emails — only a grouped digest to avoid spam."
        )}
      </div>

      {loading ? (
        <div className="text-faint" style={{ fontSize: 12 }}>{T("Chargement…", "Loading…")}</div>
      ) : (
        <div style={{ display: "grid", gap: 14 }}>
          {/* Fréquence */}
          <div style={{ display: "grid", gridTemplateColumns: "180px 1fr", gap: 12, alignItems: "center" }}>
            <label style={{ fontSize: 12, fontWeight: 500 }}>
              {T("Fréquence", "Frequency")}
            </label>
            <select value={freq} onChange={(e) => setFreq(e.target.value)} style={inp} disabled={busy}>
              <option value="off">{T("Désactivé (aucun email)", "Off (no emails)")}</option>
              <option value="daily">{T("Quotidien", "Daily")}</option>
              <option value="weekly">{T("Hebdomadaire (lundi matin)", "Weekly (Monday morning)")}</option>
            </select>
          </div>

          {freq !== "off" && (
            <>
              {/* Catégories */}
              <div style={{ display: "grid", gridTemplateColumns: "180px 1fr", gap: 12, alignItems: "start" }}>
                <label style={{ fontSize: 12, fontWeight: 500, paddingTop: 6 }}>
                  {T("Catégories incluses", "Included categories")}
                </label>
                <div style={{ display: "grid", gap: 6 }}>
                  {ALL_CATEGORIES.map((c) => (
                    <label key={c.id} style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13, cursor: "pointer" }}>
                      <input type="checkbox" checked={cats.includes(c.id)}
                             onChange={() => toggleCat(c.id)} disabled={busy} />
                      {(lang === "es" ? (c.es != null ? c.es : c.en) : lang === "fr" ? c.fr : c.en)}
                    </label>
                  ))}
                </div>
              </div>

              {/* Heure d'envoi */}
              <div style={{ display: "grid", gridTemplateColumns: "180px 1fr", gap: 12, alignItems: "center" }}>
                <label style={{ fontSize: 12, fontWeight: 500 }}>
                  {T("Heure d'envoi", "Send hour")}
                </label>
                <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
                  <input type="range" min="0" max="23" value={hour}
                         onChange={(e) => setHour(Number(e.target.value))}
                         disabled={busy} style={{ flex: 1 }} />
                  <span style={{ fontSize: 13, fontFamily: "monospace", minWidth: 60 }}>
                    {String(hour).padStart(2, "0")}:00
                  </span>
                </div>
              </div>

              {/* Timezone */}
              <div style={{ display: "grid", gridTemplateColumns: "180px 1fr", gap: 12, alignItems: "center" }}>
                <label style={{ fontSize: 12, fontWeight: 500 }}>
                  {T("Fuseau horaire", "Timezone")}
                </label>
                <input value={tz} onChange={(e) => setTz(e.target.value)} style={inp} disabled={busy}
                       placeholder="Africa/Dakar, Europe/Paris, UTC…" />
              </div>
            </>
          )}

          {/* Actions */}
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginTop: 4 }}>
            <button type="button" className="btn primary" onClick={onSave} disabled={busy}>
              {busy ? "…" : T("Enregistrer", "Save")}
            </button>
            <button type="button" className="btn ghost" onClick={onPreview} disabled={busy}>
              {T("Aperçu (7 derniers jours)", "Preview (last 7 days)")}
            </button>
            {freq !== "off" && (
              <button type="button" className="btn ghost" onClick={onSendTest} disabled={busy}>
                {T("Envoyer un test maintenant", "Send a test now")}
              </button>
            )}
          </div>

          {msg && (
            <div style={{
              padding: 8, fontSize: 12, borderRadius: 5,
              background: msg.tone === "success" ? "#dcfce7" : "#fee2e2",
              color: msg.tone === "success" ? "#166534" : "#991b1b",
            }}>{msg.text}</div>
          )}
        </div>
      )}

      {previewOpen && preview && (
        <PreviewDigestModal lang={lang} payload={preview} onClose={() => setPreviewOpen(false)} />
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// PreviewDigestModal · affiche le payload jsonb retourné par
// build_user_digest sous forme lisible. Utile pour vérifier que le
// digest contiendra bien ce que l'utilisateur attend avant de
// l'activer pour de vrai.
// ─────────────────────────────────────────────────────────────────────
function PreviewDigestModal({ lang, payload, onClose }) {
  const Modal = window.Modal;
  const T = (fr, en) => (lang === "fr" ? fr : en);
  const total = (payload && payload.summary && payload.summary.total) || 0;
  const section = (title, items, render) => {
    if (!Array.isArray(items) || items.length === 0) return null;
    return (
      <div style={{ marginTop: 14 }}>
        <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 6 }}>
          {title} <span className="text-faint" style={{ fontWeight: 400 }}>({items.length})</span>
        </div>
        <ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, display: "grid", gap: 4 }}>
          {items.map((it, i) => <li key={it.id || i}>{render(it)}</li>)}
        </ul>
      </div>
    );
  };
  return (
    <Modal isOpen={true} onClose={onClose}
           title={T("Aperçu du digest", "Digest preview")}
           size="md">
      <div style={{ padding: 16 }}>
        <div className="text-faint" style={{ fontSize: 11, marginBottom: 8 }}>
          {T("Période : ", "Window: ")}
          {payload.since ? new Date(payload.since).toLocaleString() : "?"}
          {" → "}
          {payload.until ? new Date(payload.until).toLocaleString() : "?"}
        </div>
        {total === 0 ? (
          <div style={{ padding: 16, background: "var(--bg-sunken)", borderRadius: 6, fontSize: 12 }}>
            {T(
              "Aucune nouveauté sur cette fenêtre. Le digest ne serait pas envoyé.",
              "No news in this window. The digest wouldn't be sent."
            )}
          </div>
        ) : (
          <>
            <div style={{ fontSize: 12, marginBottom: 4 }}>
              <b>{T("Total : ", "Total: ")}{total}</b> {T("entrée(s)", "item(s)")}
            </div>
            {section(T("Nouveaux projets", "New projects"), payload.new_projects,
              (p) => <span><b>{p.code || ""}</b> — {p.name_fr || p.name_en || "?"}</span>)}
            {section(T("Nouveaux indicateurs", "New indicators"), payload.new_indicators,
              (i) => <span><b>{i.code || ""}</b> — {i.name_fr || i.name_en || "?"}</span>)}
            {section(T("Nouvelles activités", "New activities"), payload.new_activities,
              (a) => <span><b>{a.code || ""}</b> — {a.title || "?"}</span>)}
            {section(T("Validations en attente", "Pending validations"), payload.validations_pending,
              (v) => <span>{v.title || v.object_type} · {v.priority || ""}</span>)}
          </>
        )}
        <div style={{ marginTop: 16, paddingTop: 12, borderTop: "1px solid var(--line)", textAlign: "right" }}>
          <button className="btn" onClick={onClose}>{T("Fermer", "Close")}</button>
        </div>
      </div>
    </Modal>
  );
}

window.ProfileScreen = ProfileScreen;
