// Admin.jsx — page « Administration » (réservée au rôle admin)
//
// Section extensible : pour l'instant un seul panneau, « Utilisateurs »,
// qui gère la whitelist allowed_users via /api/admin-users (GET/POST/DELETE).
// Structure prête à accueillir d'autres onglets admin plus tard.

const ROLE_LABELS = {
  contributor: "Contributeur",
  moderator: "Modérateur",
  admin: "Administrateur",
};
const ROLE_ORDER = ["contributor", "moderator", "admin"];

function Admin({ setRoute, setPlantId, setEditId }) {
  const auth = window.useAuth ? window.useAuth() : { user: null, loading: false };
  const isMobile = useMobile();
  const isAdmin = auth.user?.role === "admin";

  // ── Guard d'accès ────────────────────────────────────────────────────
  if (!auth.loading && !isAdmin) {
    return (
      <main className="page-enter">
        <section style={{ padding: "120px 0", textAlign: "center" }}>
          <div style={{ maxWidth: 560, margin: "0 auto", padding: "0 20px" }}>
            <div className="kicker" style={{ marginBottom: 18, color: "var(--moss-deep)" }}>Accès restreint</div>
            <h1 className="h-display" style={{ fontSize: "clamp(40px, 7vw, 64px)", lineHeight: 1, margin: "0 0 24px" }}>
              Réservé à l'administration.
            </h1>
            <p style={{ fontFamily: "var(--serif)", fontSize: 18, lineHeight: 1.55, color: "var(--ink-soft)", margin: "0 0 32px" }}>
              Cette page n'est accessible qu'à l'administrateur du recueil.
            </p>
            <button className="btn" onClick={() => setRoute("home")}>Retour à l'accueil</button>
          </div>
        </section>
      </main>
    );
  }

  const [tab, setTab] = React.useState("dashboard");

  return (
    <main className="page-enter">
      <section style={{ padding: isMobile ? "30px 0 16px" : "60px 0 28px" }}>
        <div style={{ maxWidth: 1100, margin: "0 auto", padding: isMobile ? "0 20px" : "0 40px" }}>
          <div className="kicker" style={{ marginBottom: isMobile ? 12 : 18, color: "var(--moss-deep)" }}>
            Administration
          </div>
          <h1 className="h-display" style={{ fontSize: isMobile ? "clamp(38px, 9vw, 56px)" : "clamp(56px, 7vw, 88px)", lineHeight: 0.95, margin: "0 0 18px", letterSpacing: "-0.02em" }}>
            Gestion du <span className="h-italic">recueil</span>.
          </h1>

          {/* Barre d'onglets internes (extensible) */}
          <div style={{ display: "flex", gap: 8, borderBottom: "1px solid var(--line)", marginTop: 8 }}>
            <AdminTab label="Tableau de bord" active={tab === "dashboard"} onClick={() => setTab("dashboard")} />
            <AdminTab label="Utilisateurs" active={tab === "users"} onClick={() => setTab("users")} />
            <AdminTab label="Activité" active={tab === "activity"} onClick={() => setTab("activity")} />
          </div>
        </div>
      </section>

      <section style={{ padding: isMobile ? "8px 0 64px" : "16px 0 100px" }}>
        <div style={{ maxWidth: 1100, margin: "0 auto", padding: isMobile ? "0 20px" : "0 40px" }}>
          {tab === "dashboard" && (
            <DashboardPanel isMobile={isMobile} setRoute={setRoute} setPlantId={setPlantId} setEditId={setEditId} />
          )}
          {tab === "users" && <UsersPanel currentEmail={auth.user?.email} isMobile={isMobile} />}
          {tab === "activity" && (
            <ActivityPanel isMobile={isMobile} setRoute={setRoute} setPlantId={setPlantId} />
          )}
        </div>
      </section>
    </main>
  );
}

function AdminTab({ label, active, onClick }) {
  return (
    <button
      type="button"
      className="kicker"
      onClick={onClick}
      style={{
        padding: "10px 14px",
        marginBottom: -1,
        border: "none",
        borderBottom: active ? "2px solid var(--moss-deep)" : "2px solid transparent",
        background: "transparent",
        color: active ? "var(--ink)" : "var(--ink-mute)",
        cursor: "pointer",
        font: "inherit",
        letterSpacing: ".1em",
      }}
    >
      {label}
    </button>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// Panneau Tableau de bord (stats + santé éditoriale)
// ─────────────────────────────────────────────────────────────────────────

// Libellés FR des champs remontés par /api/admin-stats (missing / gaps).
const FIELD_LABELS = {
  incipit: "incipit", photo: "photo", famille: "famille", usage: "usage",
  morphologie: "morphologie", habitat: "habitat", "arbre.description": "description jardin",
  histoire: "histoire", symbolique: "symbolique", medicinal: "médicinal",
  comestibilite: "comestibilité", composes: "composés",
  recettes: "recettes", bioindication: "bio-indication",
  precautions: "précautions", photoLocale: "photo locale", feuille: "feuille",
};

// Libellés FR des champs remontés en anomalie (valeurs hors-liste).
const ANOMALY_LABELS = {
  usage: "usage", floraison: "floraison", origine: "origine", statut: "statut",
};

function DashboardPanel({ isMobile, setRoute, setPlantId, setEditId }) {
  const [stats, setStats] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  const [healthFilter, setHealthFilter] = React.useState(null); // clé de champ ou null = tous

  React.useEffect(() => {
    setLoading(true);
    (window.authedFetch || fetch)("/api/admin-users?view=stats", { cache: "no-store" })
      .then((r) => (r.ok ? r.json() : Promise.reject(r)))
      .then((d) => { setStats(d); setError(null); setLoading(false); })
      .catch(async (err) => {
        const msg = err.json ? (await err.json().catch(() => ({}))).error : "Erreur de chargement";
        setError(msg || "Erreur de chargement"); setLoading(false);
      });
  }, []);

  // Ouvre l'éditeur pré-rempli sur une fiche (même mécanisme que Fiche.jsx).
  const editFiche = (id) => {
    if (setEditId) setEditId(id);
    setRoute("edit");
  };

  if (loading) return <p style={{ fontFamily: "var(--serif)", color: "var(--ink-soft)" }}>Chargement du tableau de bord…</p>;
  if (error) return <p style={{ fontFamily: "var(--serif)", color: "#b42318" }}>{error}</p>;
  if (!stats) return null;

  // Compteurs par champ manquant, dérivés de health (missing = critique).
  const fieldCounts = {};
  const criticalFields = new Set();
  for (const h of stats.health) {
    h.missing.forEach((f) => { fieldCounts[f] = (fieldCounts[f] || 0) + 1; criticalFields.add(f); });
    h.gaps.forEach((f) => { fieldCounts[f] = (fieldCounts[f] || 0) + 1; });
  }
  const fieldChips = Object.entries(fieldCounts)
    .map(([field, n]) => ({ field, n, critical: criticalFields.has(field) }))
    .sort((a, b) => (b.critical - a.critical) || (b.n - a.n) || a.field.localeCompare(b.field));
  const shownHealth = healthFilter
    ? stats.health.filter((h) => h.missing.includes(healthFilter) || h.gaps.includes(healthFilter))
    : stats.health;

  return (
    <div>
      {/* Chiffres clés */}
      <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr 1fr" : "repeat(4, 1fr)", gap: 12, marginBottom: 28 }}>
        <StatCard value={stats.total} label="fiches au recueil" />
        <StatCard value={stats.parfaites} label="fiches complètes" accent />
        <StatCard value={stats.health.length} label="à enrichir" hint={stats.aCompleter > 0 ? `dont ${stats.aCompleter} critique${stats.aCompleter > 1 ? "s" : ""}` : "rien de critique"} />
        <StatCard value={stats.byFamille.length} label="familles botaniques" />
      </div>

      {/* Répartitions */}
      <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr", gap: isMobile ? 20 : 28, marginBottom: 32 }}>
        <Distribution title="Par usage" data={stats.byUsage} total={stats.total} />
        <Distribution title="Par statut" data={stats.byStatut} total={stats.total} />
        <Distribution title="Par origine" data={stats.byOrigine} total={stats.total} />
        <Distribution title="Par famille" data={stats.byFamille} total={stats.total} limit={8} />
      </div>

      {/* Santé éditoriale */}
      <div className="kicker" style={{ marginBottom: 12, color: "var(--ink-mute)" }}>
        Santé éditoriale
      </div>
      {stats.health.length === 0 ? (
        <p style={{ fontFamily: "var(--serif)", fontSize: 16, color: "var(--moss-deep)" }}>
          Toutes les fiches sont complètes. Rien à signaler.
        </p>
      ) : (
        <div style={{ display: "grid", gap: 8 }}>
          <p style={{ fontFamily: "var(--serif)", fontSize: 15, color: "var(--ink-soft)", margin: "0 0 8px" }}>
            {stats.health.length} fiche{stats.health.length > 1 ? "s" : ""} à enrichir.
            Filtre par manque, clique sur une fiche pour ouvrir l'éditeur.
          </p>

          {/* Chips-filtres : un compteur par champ manquant */}
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 10 }}>
            <FilterChip
              label={`tous (${stats.health.length})`}
              active={healthFilter === null}
              onClick={() => setHealthFilter(null)}
            />
            {fieldChips.map((c) => (
              <FilterChip
                key={c.field}
                label={`${FIELD_LABELS[c.field] || c.field} (${c.n})`}
                active={healthFilter === c.field}
                critical={c.critical}
                onClick={() => setHealthFilter(healthFilter === c.field ? null : c.field)}
              />
            ))}
          </div>

          {shownHealth.map((h) => (
            <HealthRow key={h.id} h={h} isMobile={isMobile} onEdit={() => editFiche(h.id)} />
          ))}
        </div>
      )}

      {/* À corriger — valeurs hors-liste (distinct des manques : c'est une erreur de saisie) */}
      {stats.anomalies && stats.anomalies.length > 0 && (
        <div style={{ marginTop: 32 }}>
          <div className="kicker" style={{ marginBottom: 12, color: "#b42318" }}>
            À corriger — valeurs hors-liste
          </div>
          <p style={{ fontFamily: "var(--serif)", fontSize: 15, color: "var(--ink-soft)", margin: "0 0 10px" }}>
            {stats.anomalies.length} fiche{stats.anomalies.length > 1 ? "s" : ""} avec une valeur non reconnue
            (ignorée par les filtres du catalogue). Clique pour ouvrir l'éditeur.
          </p>
          <div style={{ display: "grid", gap: 8 }}>
            {stats.anomalies.map((a) => (
              <button key={a.id} onClick={() => editFiche(a.id)} style={{
                display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12,
                textAlign: "left", cursor: "pointer", width: "100%",
                padding: "10px 14px", borderRadius: 10,
                border: "1px solid rgba(180,35,24,0.35)", background: "rgba(180,35,24,0.05)",
              }}>
                <span>
                  <span style={{ fontFamily: "var(--serif)", fontSize: 16, color: "var(--ink)" }}>{a.nom}</span>
                  <span className="h-italic" style={{ fontSize: 13, color: "var(--moss-deep)", marginLeft: 8 }}>{a.latin}</span>
                </span>
                <span style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
                  {a.champs.map((c) => (
                    <span key={c} style={{
                      fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".08em", textTransform: "uppercase",
                      color: "#b42318", border: "1px solid rgba(180,35,24,0.4)", borderRadius: 100, padding: "3px 9px",
                    }}>{ANOMALY_LABELS[c] || c}</span>
                  ))}
                </span>
              </button>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function StatCard({ value, label, hint, accent }) {
  return (
    <div style={{
      padding: "16px 18px", borderRadius: 12,
      background: accent ? "rgba(45,67,41,0.08)" : "var(--paper-2)",
      border: `1px solid ${accent ? "var(--moss-deep)" : "var(--line)"}`,
    }}>
      <div className="h-display" style={{ fontSize: 38, lineHeight: 1, color: accent ? "var(--moss-deep)" : "var(--ink)" }}>
        {value}
      </div>
      <div style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: ".08em", textTransform: "uppercase", color: "var(--ink-mute)", marginTop: 6 }}>
        {label}
      </div>
      {hint && <div style={{ fontFamily: "var(--serif)", fontSize: 12, color: "var(--ink-soft)", marginTop: 2 }}>{hint}</div>}
    </div>
  );
}

function Distribution({ title, data, total, limit }) {
  const rows = limit ? data.slice(0, limit) : data;
  const max = rows.reduce((m, r) => Math.max(m, r.n), 1);
  const extra = limit && data.length > limit ? data.length - limit : 0;
  return (
    <div>
      <div className="kicker" style={{ marginBottom: 12, color: "var(--ink-mute)" }}>{title}</div>
      <div style={{ display: "grid", gap: 7 }}>
        {rows.map((r) => (
          <div key={r.key} style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <div style={{ flex: "0 0 38%", fontFamily: "var(--serif)", fontSize: 14, color: "var(--ink)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }} title={r.key}>
              {r.key}
            </div>
            <div style={{ flex: 1, height: 8, background: "var(--paper)", borderRadius: 999, overflow: "hidden", border: "1px solid var(--line)" }}>
              <div style={{ width: `${Math.round((r.n / max) * 100)}%`, height: "100%", background: "var(--moss-deep)", opacity: 0.75 }} />
            </div>
            <div style={{ flex: "0 0 auto", fontFamily: "var(--mono)", fontSize: 12, color: "var(--ink-mute)", minWidth: 28, textAlign: "right" }}>
              {r.n}
            </div>
          </div>
        ))}
        {extra > 0 && (
          <div style={{ fontFamily: "var(--mono)", fontSize: 11, color: "var(--ink-mute)", marginTop: 2 }}>
            + {extra} autre{extra > 1 ? "s" : ""}…
          </div>
        )}
      </div>
    </div>
  );
}

// Chip-filtre de la santé éditoriale : compteur cliquable par champ manquant.
function FilterChip({ label, active, critical, onClick }) {
  const border = active ? "var(--moss-deep)" : critical ? "#d92d20" : "var(--line)";
  const color = active ? "var(--paper)" : critical ? "#b42318" : "var(--ink-mute)";
  return (
    <button
      type="button"
      onClick={onClick}
      style={{
        fontFamily: "var(--mono)", fontSize: 11, padding: "4px 10px", borderRadius: 999,
        border: `1px solid ${border}`,
        color,
        background: active ? "var(--moss-deep)" : critical ? "#fdecea" : "var(--paper)",
        cursor: "pointer",
      }}
    >
      {label}
    </button>
  );
}

function HealthRow({ h, isMobile, onEdit }) {
  const chip = (txt, critical) => (
    <span key={txt} style={{
      fontFamily: "var(--mono)", fontSize: 11, padding: "2px 8px", borderRadius: 999,
      border: `1px solid ${critical ? "#d92d20" : "var(--line)"}`,
      color: critical ? "#b42318" : "var(--ink-mute)",
      background: critical ? "#fdecea" : "var(--paper)",
    }}>{FIELD_LABELS[txt] || txt}</span>
  );
  return (
    <button
      type="button"
      onClick={onEdit}
      style={{
        display: "flex", flexDirection: isMobile ? "column" : "row",
        alignItems: isMobile ? "flex-start" : "center", gap: isMobile ? 8 : 14,
        padding: "12px 16px", background: "var(--paper-2)",
        border: "1px solid var(--line)", borderRadius: 10, cursor: "pointer", textAlign: "left", width: "100%",
      }}
    >
      <div style={{ flex: 1, minWidth: 0 }}>
        <span style={{ fontFamily: "var(--serif)", fontSize: 16, color: "var(--ink)", fontWeight: 500 }}>
          {h.nom}
        </span>
        {h.isArbre && <span style={{ marginLeft: 8, fontFamily: "var(--mono)", fontSize: 10, color: "var(--ink-mute)" }}>ARBRE</span>}
        <span style={{ marginLeft: 8, fontFamily: "var(--serif)", fontStyle: "italic", fontSize: 13, color: "var(--ink-mute)" }}>{h.latin}</span>
      </div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
        {h.missing.map((f) => chip(f, true))}
        {h.gaps.map((f) => chip(f, false))}
      </div>
    </button>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// Panneau Activité (journal par compte)
// ─────────────────────────────────────────────────────────────────────────

const ACTION_LABELS = { create: "création", update: "édition", delete_fiche: "suppression" };
const REV_STATUS = {
  approved: { label: "approuvée", color: "var(--moss-deep)", bg: "rgba(45,67,41,0.08)" },
  rejected: { label: "rejetée", color: "#b42318", bg: "#fdecea" },
  pending: { label: "en attente", color: "#946a00", bg: "#fffaeb" },
  cancelled: { label: "annulée", color: "var(--ink-mute)", bg: "var(--paper)" },
};

function ActivityPanel({ isMobile, setRoute, setPlantId }) {
  const [users, setUsers] = React.useState([]);
  const [email, setEmail] = React.useState("");
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);

  // Charge la liste des comptes pour le sélecteur.
  React.useEffect(() => {
    (window.authedFetch || fetch)("/api/admin-users", { cache: "no-store" })
      .then((r) => (r.ok ? r.json() : Promise.reject(r)))
      .then((d) => setUsers(d.users || []))
      .catch(() => setUsers([]));
  }, []);

  // Charge l'activité du compte sélectionné.
  React.useEffect(() => {
    if (!email) { setData(null); return; }
    setLoading(true); setError(null);
    (window.authedFetch || fetch)(`/api/admin-users?view=activity&email=${encodeURIComponent(email)}`, { cache: "no-store" })
      .then((r) => (r.ok ? r.json() : Promise.reject(r)))
      .then((d) => { setData(d); setLoading(false); })
      .catch(async (err) => {
        const msg = err.json ? (await err.json().catch(() => ({}))).error : "Erreur de chargement";
        setError(msg || "Erreur de chargement"); setLoading(false);
      });
  }, [email]);

  const openFiche = (id) => { if (setPlantId) setPlantId(id); setRoute("fiche"); };

  return (
    <div>
      <p style={{ fontFamily: "var(--serif)", fontSize: 15, color: "var(--ink-soft)", margin: "0 0 14px" }}>
        Consultez l'activité d'un compte : fiches éditées directement, propositions modérées, contributions soumises.
      </p>

      {/* Sélecteur de compte */}
      <select
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        style={{
          fontFamily: "var(--serif)", fontSize: 15, padding: "10px 12px", borderRadius: 8,
          border: "1px solid var(--line)", background: "var(--paper)", color: "var(--ink)",
          minWidth: isMobile ? "100%" : 320, marginBottom: 24,
        }}
      >
        <option value="">— Choisir un compte —</option>
        {users.map((u) => (
          <option key={u.email} value={u.email}>
            {(u.display_name || u.email)} · {ROLE_LABELS[u.role] || u.role}
          </option>
        ))}
      </select>

      {loading && <p style={{ fontFamily: "var(--serif)", color: "var(--ink-soft)" }}>Chargement de l'activité…</p>}
      {error && <p style={{ fontFamily: "var(--serif)", color: "#b42318" }}>{error}</p>}

      {data && !loading && (
        <div>
          {/* Compteurs */}
          <div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr 1fr 1fr" : "repeat(3, 160px)", gap: 12, marginBottom: 28 }}>
            <StatCard value={data.counts.edited} label="fiches éditées" />
            <StatCard value={data.counts.moderated} label="modérations" accent />
            <StatCard value={data.counts.submitted} label="soumissions" />
          </div>

          <ActivitySection title="Modérations effectuées" empty="Aucune révision modérée par ce compte.">
            {data.moderated.map((m, i) => (
              <ActivityRow key={"m" + i} onClick={() => openFiche(m.plant_id)}
                nom={m.nom} action={ACTION_LABELS[m.action] || m.action}
                status={m.status} when={fmtDate(m.reviewed_at)} note={m.review_note} />
            ))}
          </ActivitySection>

          <ActivitySection title="Fiches éditées directement" empty="Aucune édition directe attribuée à ce compte.">
            {data.editedFiches.map((f) => (
              <ActivityRow key={f.id} onClick={() => openFiche(f.id)}
                nom={f.nom} latin={f.latin} action={f.role || "édition"} when={f.date} />
            ))}
          </ActivitySection>

          <ActivitySection title="Contributions soumises" empty="Aucune proposition soumise par ce compte.">
            {data.submitted.map((s, i) => (
              <ActivityRow key={"s" + i} onClick={() => openFiche(s.plant_id)}
                nom={s.nom} action={ACTION_LABELS[s.action] || s.action}
                status={s.status} when={fmtDate(s.created_at)} note={s.summary} />
            ))}
          </ActivitySection>
        </div>
      )}
    </div>
  );
}

// Formate une date ISO en court FR ; renvoie tel quel si déjà du texte.
function fmtDate(v) {
  if (!v) return "";
  const d = new Date(v);
  if (isNaN(d.getTime())) return String(v);
  return d.toLocaleDateString("fr-FR", { day: "2-digit", month: "short", year: "numeric" });
}

function ActivitySection({ title, empty, children }) {
  const items = React.Children.toArray(children).filter(Boolean);
  return (
    <div style={{ marginBottom: 28 }}>
      <div className="kicker" style={{ marginBottom: 12, color: "var(--ink-mute)" }}>{title}</div>
      {items.length === 0 ? (
        <p style={{ fontFamily: "var(--serif)", fontSize: 14, color: "var(--ink-soft)" }}>{empty}</p>
      ) : (
        <div style={{ display: "grid", gap: 8 }}>{items}</div>
      )}
    </div>
  );
}

function ActivityRow({ nom, latin, action, status, when, note, onClick }) {
  const st = status ? REV_STATUS[status] : null;
  return (
    <button
      type="button"
      onClick={onClick}
      style={{
        display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap",
        padding: "12px 16px", background: "var(--paper-2)", border: "1px solid var(--line)",
        borderRadius: 10, cursor: "pointer", textAlign: "left", width: "100%",
      }}
    >
      <div style={{ flex: 1, minWidth: 0 }}>
        <span style={{ fontFamily: "var(--serif)", fontSize: 16, color: "var(--ink)", fontWeight: 500 }}>{nom}</span>
        {latin && <span style={{ marginLeft: 8, fontFamily: "var(--serif)", fontStyle: "italic", fontSize: 13, color: "var(--ink-mute)" }}>{latin}</span>}
        {note && <div style={{ fontFamily: "var(--serif)", fontSize: 13, color: "var(--ink-soft)", marginTop: 3 }}>{note}</div>}
      </div>
      <span style={{ fontFamily: "var(--mono)", fontSize: 11, color: "var(--ink-mute)", textTransform: "uppercase", letterSpacing: ".06em" }}>{action}</span>
      {st && (
        <span style={{
          fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".08em", textTransform: "uppercase",
          padding: "2px 8px", borderRadius: 999, border: `1px solid ${st.color}`, color: st.color, background: st.bg,
        }}>{st.label}</span>
      )}
      {when && <span style={{ fontFamily: "var(--mono)", fontSize: 11, color: "var(--ink-mute)", whiteSpace: "nowrap" }}>{when}</span>}
    </button>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// Panneau Utilisateurs
// ─────────────────────────────────────────────────────────────────────────
function UsersPanel({ currentEmail, isMobile }) {
  const [users, setUsers] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  const [notice, setNotice] = React.useState(null); // { type: "ok"|"err", msg }
  const [busyEmail, setBusyEmail] = React.useState(null);
  const [refreshKey, setRefreshKey] = React.useState(0);
  const [query, setQuery] = React.useState("");

  const refresh = () => setRefreshKey((k) => k + 1);
  const adminCount = users.filter((u) => u.role === "admin").length;

  // Filtre de recherche (email ou nom), insensible à la casse.
  const q = query.trim().toLowerCase();
  const shown = q
    ? users.filter((u) =>
        (u.email || "").toLowerCase().includes(q) ||
        (u.display_name || "").toLowerCase().includes(q))
    : users;

  React.useEffect(() => {
    setLoading(true);
    (window.authedFetch || fetch)("/api/admin-users", { cache: "no-store" })
      .then((r) => (r.ok ? r.json() : Promise.reject(r)))
      .then((d) => { setUsers(d.users || []); setError(null); setLoading(false); })
      .catch(async (err) => {
        const msg = err.json ? (await err.json().catch(() => ({}))).error : "Erreur de chargement";
        setError(msg || "Erreur de chargement"); setLoading(false);
      });
  }, [refreshKey]);

  // Action générique POST/DELETE avec gestion d'erreur + refresh.
  async function call(method, body, onOk) {
    setNotice(null);
    setBusyEmail(body.email);
    try {
      const res = await (window.authedFetch || fetch)("/api/admin-users", {
        method,
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) {
        setNotice({ type: "err", msg: data.error || `Échec (HTTP ${res.status})` });
      } else {
        if (onOk) onOk(data);
        refresh();
      }
    } catch (e) {
      setNotice({ type: "err", msg: "Impossible de joindre le serveur." });
    } finally {
      setBusyEmail(null);
    }
  }

  const changeRole = (email, role) => call("POST", { email, role }, () =>
    setNotice({ type: "ok", msg: `Rôle de ${email} mis à jour.` }));

  const removeUser = (email) => {
    if (!window.confirm(`Retirer définitivement ${email} de la liste des accès ?`)) return;
    call("DELETE", { email }, () => setNotice({ type: "ok", msg: `${email} retiré.` }));
  };

  const resendInvite = (email) => call("POST", { email, resend: true }, (d) => {
    setNotice({ type: "ok", msg: d.skipped
      ? `Mail non envoyé (mode test : ${d.skipped}).`
      : `Invitation renvoyée à ${email}.` });
  });

  return (
    <div>
      {/* Notices */}
      {notice && (
        <div style={{
          marginBottom: 16, padding: "12px 16px", borderRadius: 8,
          fontFamily: "var(--serif)", fontSize: 15,
          background: notice.type === "ok" ? "rgba(45,67,41,0.08)" : "#fdecea",
          border: `1px solid ${notice.type === "ok" ? "var(--moss-deep)" : "#d92d20"}`,
          color: notice.type === "ok" ? "var(--moss-deep)" : "#b42318",
        }}>
          {notice.msg}
        </div>
      )}

      {/* Formulaire d'invitation */}
      <InviteForm isMobile={isMobile} onInvite={(body, cb) => call("POST", body, (d) => {
        setNotice({ type: "ok", msg: d.created
          ? `${body.email} invité — un mail d'invitation lui a été envoyé.`
          : `${body.email} mis à jour.` });
        cb && cb();
      })} busy={busyEmail} />

      {/* Liste */}
      {loading ? (
        <p style={{ fontFamily: "var(--serif)", color: "var(--ink-soft)" }}>Chargement des comptes…</p>
      ) : error ? (
        <p style={{ fontFamily: "var(--serif)", color: "#b42318" }}>{error}</p>
      ) : (
        <div style={{ marginTop: 28 }}>
          <div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", justifyContent: "space-between", gap: 10, marginBottom: 12 }}>
            <div className="kicker" style={{ color: "var(--ink-mute)" }}>
              {shown.length}{q ? ` / ${users.length}` : ""} compte{users.length > 1 ? "s" : ""}
            </div>
            {users.length > 5 && (
              <input
                type="search"
                placeholder="Rechercher (email, nom)…"
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                style={{
                  fontFamily: "var(--serif)", fontSize: 14, padding: "8px 12px",
                  borderRadius: 8, border: "1px solid var(--line)", background: "var(--paper)",
                  color: "var(--ink)", minWidth: 220,
                }}
              />
            )}
          </div>
          {shown.length === 0 ? (
            <p style={{ fontFamily: "var(--serif)", color: "var(--ink-soft)" }}>Aucun compte ne correspond à « {query} ».</p>
          ) : (
            <div style={{ display: "grid", gap: 10 }}>
              {shown.map((u) => (
                <UserRow
                  key={u.email}
                  u={u}
                  isMobile={isMobile}
                  isSelf={u.email === currentEmail}
                  isLastAdmin={u.role === "admin" && adminCount <= 1}
                  busy={busyEmail === u.email}
                  onChangeRole={changeRole}
                  onRemove={removeUser}
                  onResend={resendInvite}
                />
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function UserRow({ u, isMobile, isSelf, isLastAdmin, busy, onChangeRole, onRemove, onResend }) {
  const pending = !u.last_seen_at; // jamais connecté = invitation en attente
  const lastSeen = u.last_seen_at
    ? "vu le " + new Date(u.last_seen_at).toLocaleDateString("fr-FR", { day: "2-digit", month: "short", year: "numeric" })
    : null;
  // Rétrograder le dernier admin est interdit (serveur + UI).
  const roleLocked = isLastAdmin;
  // Suppression interdite : dernier admin, ou soi-même (évite l'auto-exclusion).
  const removeDisabled = isLastAdmin || isSelf || busy;

  // Méta : email · invité par X · dernière connexion (ce qui est disponible).
  const meta = [
    u.display_name ? u.email : null,
    u.invited_by ? "invité par " + u.invited_by : null,
    lastSeen,
  ].filter(Boolean).join(" · ");

  return (
    <div style={{
      display: "flex", flexDirection: isMobile ? "column" : "row",
      alignItems: isMobile ? "stretch" : "center", gap: isMobile ? 10 : 16,
      padding: "14px 16px", background: "var(--paper-2)",
      border: "1px solid var(--line)", borderRadius: 10,
      opacity: busy ? 0.6 : 1,
    }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontFamily: "var(--serif)", fontSize: 17, color: "var(--ink)", fontWeight: 500, wordBreak: "break-all" }}>
          {u.display_name || u.email}
          {isSelf && <span style={{ marginLeft: 8, fontSize: 12, color: "var(--moss-deep)", fontFamily: "var(--mono)" }}>(vous)</span>}
          <span style={{
            marginLeft: 8, fontFamily: "var(--mono)", fontSize: 10, letterSpacing: ".08em",
            textTransform: "uppercase", padding: "2px 7px", borderRadius: 999,
            border: `1px solid ${pending ? "#b88207" : "var(--moss-deep)"}`,
            color: pending ? "#946a00" : "var(--moss-deep)",
            background: pending ? "#fffaeb" : "rgba(45,67,41,0.08)",
          }}>
            {pending ? "en attente" : "actif"}
          </span>
        </div>
        <div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--ink-mute)", marginTop: 2 }}>
          {meta}
        </div>
      </div>

      {pending && (
        <button
          onClick={() => onResend(u.email)}
          disabled={busy}
          title="Renvoyer le mail d'invitation"
          style={{
            fontFamily: "var(--mono)", fontSize: 11, letterSpacing: ".08em", textTransform: "uppercase",
            padding: "8px 14px", borderRadius: 999, border: "1px solid var(--line)",
            background: "transparent", color: "var(--ink-soft)",
            cursor: busy ? "not-allowed" : "pointer", whiteSpace: "nowrap",
          }}
        >
          Renvoyer l'invitation
        </button>
      )}

      <select
        value={u.role}
        disabled={roleLocked || busy}
        onChange={(e) => onChangeRole(u.email, e.target.value)}
        title={roleLocked ? "Dernier administrateur — désignez d'abord un autre admin" : "Changer le rôle"}
        style={{
          fontFamily: "var(--mono)", fontSize: 12, letterSpacing: ".06em",
          padding: "8px 10px", borderRadius: 8, border: "1px solid var(--line)",
          background: "var(--paper)", color: "var(--ink)",
          cursor: roleLocked ? "not-allowed" : "pointer", minWidth: 150,
        }}
      >
        {ROLE_ORDER.map((r) => (
          <option key={r} value={r}>{ROLE_LABELS[r]}</option>
        ))}
      </select>

      <button
        onClick={() => onRemove(u.email)}
        disabled={removeDisabled}
        title={isLastAdmin ? "Dernier administrateur" : isSelf ? "Vous ne pouvez pas vous retirer vous-même" : "Retirer ce compte"}
        style={{
          fontFamily: "var(--mono)", fontSize: 11, letterSpacing: ".1em", textTransform: "uppercase",
          padding: "8px 14px", borderRadius: 999, border: "1px solid",
          borderColor: removeDisabled ? "var(--line)" : "#d92d20",
          background: "transparent",
          color: removeDisabled ? "var(--ink-mute)" : "#b42318",
          cursor: removeDisabled ? "not-allowed" : "pointer",
          opacity: removeDisabled ? 0.5 : 1,
        }}
      >
        Retirer
      </button>
    </div>
  );
}

function InviteForm({ onInvite, busy, isMobile }) {
  const [email, setEmail] = React.useState("");
  const [name, setName] = React.useState("");
  const [role, setRole] = React.useState("contributor");

  const submit = (e) => {
    e.preventDefault();
    const trimmed = email.trim().toLowerCase();
    if (!trimmed.includes("@")) return;
    onInvite({ email: trimmed, role, displayName: name.trim() || undefined }, () => {
      setEmail(""); setName("");
    });
  };

  const inputStyle = {
    fontFamily: "var(--serif)", fontSize: 15, padding: "10px 12px",
    borderRadius: 8, border: "1px solid var(--line)", background: "var(--paper)",
    color: "var(--ink)", minWidth: 0,
  };

  return (
    <form onSubmit={submit} style={{
      display: "flex", flexWrap: "wrap", gap: 10, alignItems: "center",
      padding: "16px", background: "var(--paper-2)", border: "1px dashed var(--line)", borderRadius: 10,
    }}>
      <div style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: ".1em", textTransform: "uppercase", color: "var(--ink-mute)", width: isMobile ? "100%" : "auto", marginRight: 4 }}>
        Inviter
      </div>
      <input style={{ ...inputStyle, flex: isMobile ? "1 1 100%" : "2 1 220px" }} type="email" required
        placeholder="email@exemple.fr" value={email} onChange={(e) => setEmail(e.target.value)} />
      <input style={{ ...inputStyle, flex: isMobile ? "1 1 100%" : "1 1 160px" }} type="text"
        placeholder="Nom (optionnel)" value={name} onChange={(e) => setName(e.target.value)} />
      <select style={{ ...inputStyle, fontFamily: "var(--mono)", fontSize: 12, cursor: "pointer" }}
        value={role} onChange={(e) => setRole(e.target.value)}>
        {ROLE_ORDER.map((r) => <option key={r} value={r}>{ROLE_LABELS[r]}</option>)}
      </select>
      <button className="btn" type="submit" disabled={!!busy} style={{ whiteSpace: "nowrap" }}>
        Ajouter
      </button>
    </form>
  );
}

window.Admin = Admin;
