/* =========================================================
   Rende+ — Painel de administração (Root/Admin)
   ---------------------------------------------------------
   Quem tem role==="admin" entra aqui automaticamente ao carregar
   a app (ver useEffect "entrouModoAdmin" em app.jsx) — deixou de
   ser uma rota escondida. Tem a sua própria navegação (AdminSidebar,
   mais abaixo), mas com o MESMO tema claro e os mesmos componentes
   (.sidebar, .nav-item, .nav-label) do menu normal — a diferença
   está só nos itens (Utilizadores/Métricas/Auditoria em vez de
   Painel/Transações/...), na etiqueta "Modo Administrador" e no
   mini-perfil do admin, nunca numa paleta escura à parte. A proteção
   REAL continua sempre no backend (exigirLogin + exigirAdmin em
   todas as rotas /api/admin/*) — esta verificação aqui é só para
   nunca mostrar nada do painel a quem não é admin.
   ========================================================= */

// Agrupado por área (mesmo padrão visual dos grupos "Geral"/"Outros" da
// sidebar normal — ver NAV_GROUPS em components.jsx): Gestão (utilizadores e
// os seus números), Negócio (o que rende dinheiro/uso), Operações (o que
// precisa de atenção operacional: avisos aos utilizadores e erros técnicos).
const ADMIN_GRUPOS = [
  {
    label: "Gestão",
    secoes: [
      { id: "utilizadores", label: "Utilizadores", ic: "users", desc: "Consulta, pesquisa e gere as contas registadas na plataforma." },
      { id: "metricas", label: "Métricas", ic: "chart", desc: "Números e evolução da plataforma em tempo real." },
    ],
  },
  {
    label: "Negócio",
    secoes: [
      { id: "receita", label: "Receita", ic: "wallet", desc: "Subscrições ativas, MRR estimado e renovações." },
      { id: "assistente", label: "Assistente IA", ic: "bot", desc: "Uso do assistente financeiro por utilizador, no mês atual." },
    ],
  },
  {
    label: "Operações",
    secoes: [
      { id: "anuncios", label: "Anúncios", ic: "bell", desc: "Avisos mostrados aos utilizadores dentro da app." },
      { id: "envio-email", label: "Envio de email", ic: "send", desc: "Campanhas de reativação para utilizadores registados que ainda não usaram o Rende+." },
      { id: "erros", label: "Erros", ic: "flag", desc: "Últimos erros capturados pelo backend." },
      { id: "auditoria", label: "Auditoria", ic: "history", desc: "Histórico de todas as ações administrativas." },
    ],
  },
];
// Lista plana (todas as secções, sem grupo) — usada onde só interessa procurar
// por id (título/descrição da página, abas mobile, rota por omissão, etc.).
const ADMIN_SECOES = ADMIN_GRUPOS.flatMap((g) => g.secoes);

/* Navegação própria do Modo Administrador — substitui a Sidebar normal por
   inteiro (nunca as duas ao mesmo tempo, ver app.jsx). Reaproveita a .sidebar
   e os .nav-item tal e qual como a sidebar normal (mesmo tema claro), incluindo
   o próprio botão de recolher/expandir (collapsed/onToggle vêm do Shell — é o
   mesmo estado partilhado com a sidebar normal). */
function AdminSidebar({ route, go, account, collapsed, onToggle }) {
  const tr = useT();
  return (
    <aside className="sidebar admin-sidebar">
      <div className="sidebar-brand">
        <Brand />
      </div>
      <div className="admin-badge-row">
        <span className="admin-badge"><Icon name="shield" size={12} color="#fff" /> Modo Administrador</span>
      </div>
      <nav>
        {ADMIN_GRUPOS.map((g) => (
          <React.Fragment key={g.label}>
            <div className="nav-label">{g.label}</div>
            {g.secoes.map((s) => (
              <button key={s.id} type="button" className={"nav-item" + (route === "admin-" + s.id ? " active" : "")} onClick={() => go("admin-" + s.id)}>
                <Icon name={s.ic} size={19} />
                <span>{s.label}</span>
              </button>
            ))}
          </React.Fragment>
        ))}
      </nav>
      <div className="sidebar-foot">
        <div className="admin-mini-profile">
          <Avatar account={account} size={32} />
          <div className="admin-mini-profile-txt">
            <b>{(account && account.nome) || "Administrador"}</b>
            <span>{account && account.email}</span>
          </div>
        </div>
        <button type="button" className="sb-admin-card" onClick={() => go("dashboard")} title="Vê a app tal como um utilizador normal a vê">
          <span className="sb-admin-card-ico"><Icon name="user" size={15} color="var(--accent)" /></span>
          <span className="sb-admin-card-txt">Ver como utilizador normal</span>
          <span className="sb-admin-card-chev"><Icon name="chevR" size={14} color="var(--ink-3)" /></span>
        </button>
        <button type="button" className="nav-item sb-toggle" onClick={onToggle} title={collapsed ? tr("sb_expand") : tr("sb_collapse")}>
          <span style={{ display: "grid", transform: collapsed ? "none" : "rotate(180deg)" }}><Icon name="chevR" size={18} /></span>
          <span>{collapsed ? tr("sb_expand") : tr("sb_collapse")}</span>
        </button>
      </div>
    </aside>
  );
}

function AdminPanel({ go, route }) {
  const fin = useFinance();
  const ehAdmin = fin.account && fin.account.role === "admin";

  React.useEffect(() => {
    if (!ehAdmin) go("dashboard");
  }, [ehAdmin]);

  if (!ehAdmin) return null;

  const seccaoId = route.replace(/^admin-/, "") || "utilizadores";
  const seccao = ADMIN_SECOES.find((s) => s.id === seccaoId) || ADMIN_SECOES[0];

  return (
    <div className="content">
      <div className="card pg-head admin-pg-head">
        <span className="pg-head-ic admin-pg-head-ic"><Icon name={seccao.ic} size={20} color="#fff" /></span>
        <div className="pg-head-txt">
          <b className="pg-head-name">{seccao.label}</b>
          <div className="tiny muted">{seccao.desc}</div>
        </div>
        <button type="button" className="btn btn-ghost admin-ver-normal-btn" onClick={() => go("dashboard")}>
          <Icon name="user" size={15} /> Ver como utilizador normal
        </button>
      </div>

      {/* Em desktop a navegação já vive na AdminSidebar; estas abas só aparecem em
          mobile (a sidebar esconde-se abaixo dos 860px — ver styles.css). */}
      <div className="pg-tabs admin-mobile-tabs">
        {ADMIN_SECOES.map((s) => (
          <button key={s.id} type="button" className={"pg-tab" + (seccaoId === s.id ? " on" : "")} onClick={() => go("admin-" + s.id)}>
            <Icon name={s.ic} size={16} /> {s.label}
          </button>
        ))}
      </div>

      {seccaoId === "utilizadores" && <AdminUtilizadores />}
      {seccaoId === "metricas" && <AdminMetricas />}
      {seccaoId === "receita" && <AdminReceita />}
      {seccaoId === "assistente" && <AdminAssistenteUso />}
      {seccaoId === "anuncios" && <AdminAnuncios />}
      {seccaoId === "envio-email" && <AdminEnvioEmail />}
      {seccaoId === "erros" && <AdminErros />}
      {seccaoId === "auditoria" && <AdminAuditoria />}
    </div>
  );
}

/* Paginação simples, reutilizada pelas 2 tabelas (utilizadores/auditoria). */
function AdminPaginacao({ pagina, totalPaginas, onChange }) {
  if (totalPaginas <= 1) return null;
  return (
    <div className="row" style={{ justifyContent: "center", gap: 14, padding: "14px 0 4px" }}>
      <button type="button" className="btn btn-ghost" disabled={pagina <= 1} onClick={() => onChange(pagina - 1)}>Anterior</button>
      <span className="tiny muted" style={{ fontWeight: 600 }}>Página {pagina} de {totalPaginas}</span>
      <button type="button" className="btn btn-ghost" disabled={pagina >= totalPaginas} onClick={() => onChange(pagina + 1)}>Seguinte</button>
    </div>
  );
}

/* Cabeçalho de coluna clicável para ordenar — mostra a seta na direção atual
   só na coluna que está a ordenar. */
function AdminThSort({ campo, sort, dir, onSort, children, align }) {
  const ativo = sort === campo;
  return (
    <th style={align ? { textAlign: align } : undefined}>
      <button type="button" className="admin-th-sort" onClick={() => onSort(campo)}>
        {children}
        {ativo && <Icon name={dir === "asc" ? "arrowUp" : "arrowDown"} size={11} />}
      </button>
    </th>
  );
}

/* ---------- Separador: Utilizadores ---------- */
function AdminUtilizadores() {
  const [linhas, setLinhas] = React.useState([]);
  const [pagina, setPagina] = React.useState(1);
  const [totalPaginas, setTotalPaginas] = React.useState(1);
  const [total, setTotal] = React.useState(0);
  const [q, setQ] = React.useState("");
  const [filtroPlano, setFiltroPlano] = React.useState("");
  const [filtroRole, setFiltroRole] = React.useState("");
  const [inativoDias, setInativoDias] = React.useState("");
  const [sort, setSort] = React.useState("createdAt");
  const [dir, setDir] = React.useState("desc");
  const [busy, setBusy] = React.useState(true);
  const [erro, setErro] = React.useState("");
  const [editUser, setEditUser] = React.useState(null);
  const [verUser, setVerUser] = React.useState(null);
  const [exportando, setExportando] = React.useState(false);

  const exportarCSV = async () => {
    setExportando(true);
    try {
      const { blob, nome } = await API.baixarFicheiro("/api/admin/utilizadores/exportar");
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url; a.download = nome;
      document.body.appendChild(a); a.click(); a.remove();
      URL.revokeObjectURL(url);
    } catch (e) {
      setErro(e.message || "Não foi possível exportar os utilizadores.");
    } finally {
      setExportando(false);
    }
  };

  const carregar = React.useCallback(async (p) => {
    setBusy(true); setErro("");
    try {
      const resp = await API.adminListarUtilizadores({ page: p, pesquisa: q, plano: filtroPlano, role: filtroRole, inativoDias, sort, dir });
      setLinhas(resp.utilizadores || []);
      setPagina(resp.pagina || 1);
      setTotalPaginas(resp.totalPaginas || 1);
      setTotal(resp.total || 0);
    } catch (e) {
      setErro(e.message || "Não foi possível carregar os utilizadores.");
    } finally {
      setBusy(false);
    }
  }, [q, filtroPlano, filtroRole, inativoDias, sort, dir]);

  // Pesquisa com um pequeno atraso, para não disparar um pedido a cada letra;
  // filtros e ordenação recarregam de imediato, sempre a partir da página 1.
  React.useEffect(() => {
    const t = setTimeout(() => carregar(1), q ? 350 : 0);
    return () => clearTimeout(t);
  }, [carregar]);

  const toggleSort = (campo) => {
    if (sort === campo) setDir((d) => (d === "asc" ? "desc" : "asc"));
    else { setSort(campo); setDir("asc"); }
  };

  return (
    <>
      <div className="card">
        <div className="card-pad admin-filtros-bar" style={{ borderBottom: "1px solid var(--border)" }}>
          <div className="row" style={{ flex: 1, minWidth: 220, gap: 10, background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: "var(--radius-sm)", padding: "9px 13px" }}>
            <Icon name="search" size={17} color="var(--ink-3)" />
            <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Pesquisar por email…"
              style={{ border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontSize: 14, fontWeight: 500, fontFamily: "inherit", width: "100%" }} />
          </div>
          <div className="admin-filtros">
            <select className="input" value={filtroPlano} onChange={(e) => setFiltroPlano(e.target.value)}>
              <option value="">Todos os planos</option>
              <option value="free">Free</option>
              <option value="premium">Premium</option>
            </select>
            <select className="input" value={filtroRole} onChange={(e) => setFiltroRole(e.target.value)}>
              <option value="">Todos os roles</option>
              <option value="user">User</option>
              <option value="admin">Admin</option>
            </select>
          </div>
          <span className="muted tiny" style={{ fontWeight: 600, flex: "none" }}>{total} {total === 1 ? "utilizador" : "utilizadores"}</span>
          <button type="button" className="btn btn-ghost" style={{ flex: "none" }} disabled={exportando} onClick={exportarCSV}>
            <Icon name="download" size={15} /> {exportando ? "A exportar…" : "Exportar CSV"}
          </button>
        </div>

        <div className="row admin-filtros-bar" style={{ padding: "12px 16px", gap: 8, borderBottom: "1px solid var(--border)" }}>
          <span className="tiny muted" style={{ fontWeight: 600 }}>Atividade:</span>
          {[
            { label: "Todos", v: "" },
            { label: "Inativos 7+ dias", v: "7" },
            { label: "Inativos 30+ dias", v: "30" },
          ].map((op) => (
            <button key={op.v} type="button" className={"chip" + (inativoDias === op.v ? " sel" : "")} style={{ cursor: "pointer", border: "1px solid var(--border)" }}
              onClick={() => setInativoDias(op.v)}>
              {op.label}
            </button>
          ))}
        </div>

        {erro && <div className="alert bad" style={{ margin: 16, padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>}

        {!erro && (
          <div style={{ overflowX: "auto" }}>
            <table className="t">
              <thead>
                <tr>
                  <AdminThSort campo="email" sort={sort} dir={dir} onSort={toggleSort}>Email</AdminThSort>
                  <th>Nome</th>
                  <AdminThSort campo="plano" sort={sort} dir={dir} onSort={toggleSort}>Plano</AdminThSort>
                  <th>Role</th>
                  <th style={{ textAlign: "right" }}>Despesas</th>
                  <th style={{ textAlign: "right" }}>Rendimentos</th>
                  <AdminThSort campo="createdAt" sort={sort} dir={dir} onSort={toggleSort}>Criado em</AdminThSort>
                  <th>Último login</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {busy && <tr><td colSpan={9} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>A carregar…</td></tr>}
                {!busy && linhas.length === 0 && <tr><td colSpan={9} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>Sem resultados.</td></tr>}
                {!busy && linhas.map((u) => (
                  <tr key={u.id}>
                    <td style={{ fontWeight: 600 }}>
                      <button type="button" className="admin-link-btn" onClick={() => setVerUser(u)} title="Ver detalhes">
                        {u.email}
                        {!u.emailVerificado && <span className="tiny muted" style={{ fontWeight: 500 }}> (não verificado)</span>}
                      </button>
                    </td>
                    <td>{u.nome || "—"}</td>
                    <td><span className={"chip" + (u.plano === "premium" ? " sel" : "")}>{u.plano}</span></td>
                    <td>{u.role === "admin" ? <span className="chip sel">admin</span> : "user"}</td>
                    <td className="tnum" style={{ textAlign: "right" }}>{u.totalDespesas}</td>
                    <td className="tnum" style={{ textAlign: "right" }}>{u.totalRendimentos}</td>
                    <td className="tiny muted">{new Date(u.createdAt).toLocaleDateString("pt-PT")}</td>
                    <td className="tiny muted">{u.ultimoLogin ? new Date(u.ultimoLogin).toLocaleDateString("pt-PT") : "Nunca"}</td>
                    <td><button type="button" className="btn btn-ghost" style={{ padding: "6px 12px", fontSize: 12.5 }} onClick={() => setEditUser(u)}>Mudar plano</button></td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}

        <AdminPaginacao pagina={pagina} totalPaginas={totalPaginas} onChange={(p) => carregar(p)} />
      </div>

      {editUser && (
        <AdminMudarPlanoModal
          utilizador={editUser}
          onClose={() => setEditUser(null)}
          onSaved={() => { setEditUser(null); carregar(pagina); }}
        />
      )}
      {verUser && (
        <AdminDetalheModal
          utilizador={verUser}
          onClose={() => setVerUser(null)}
          onAlterado={() => carregar(pagina)}
        />
      )}
    </>
  );
}

/* Um campo do "Detalhe do utilizador" — caixa com borda própria (ver
   .admin-detalhe-grid em styles.css), nunca texto solto sem contorno. */
function AdminDetalheField({ icon, label, value, mono }) {
  return (
    <div>
      <span className="admin-detalhe-ico"><Icon name={icon} size={16} color="var(--accent)" /></span>
      <div className="admin-detalhe-txt">
        <span>{label}</span>
        <b style={mono ? { fontFamily: "monospace", fontSize: 12 } : undefined}>{value}</b>
      </div>
    </div>
  );
}

/* Detalhe completo de um utilizador (GET /utilizadores/:id/detalhe) — tudo o
   que a listagem não tem espaço para mostrar, mais o histórico de mudanças de
   plano (auditoria) e os campos do Stripe (se existirem), mais as 3 ações de
   conta (forçar logout, reenviar verificação, eliminar conta). */
function AdminDetalheModal({ utilizador, onClose, onAlterado }) {
  const [d, setD] = React.useState(null);
  const [erro, setErro] = React.useState("");
  const [aviso, setAviso] = React.useState("");
  const [busyAcao, setBusyAcao] = React.useState("");
  const [confirmDelete, setConfirmDelete] = React.useState(false);

  const carregar = React.useCallback(() => {
    API.adminUtilizadorDetalhe(utilizador.id).then(setD).catch((e) => setErro(e.message || "Não foi possível carregar o detalhe."));
  }, [utilizador.id]);

  React.useEffect(() => { carregar(); }, [carregar]);

  const forcarLogout = async () => {
    setBusyAcao("logout"); setAviso(""); setErro("");
    try {
      const r = await API.adminForcarLogout(utilizador.id);
      setAviso(r.mensagem || "Sessão terminada.");
    } catch (e) {
      setErro(e.message || "Não foi possível forçar o logout.");
    } finally {
      setBusyAcao("");
    }
  };

  const reenviarVerificacao = async () => {
    setBusyAcao("verificacao"); setAviso(""); setErro("");
    try {
      const r = await API.adminReenviarVerificacao(utilizador.id);
      setAviso(r.mensagem || "Email reenviado.");
    } catch (e) {
      setErro(e.message || "Não foi possível reenviar o email.");
    } finally {
      setBusyAcao("");
    }
  };

  if (confirmDelete && d) {
    return (
      <AdminEliminarContaModal
        utilizador={d}
        onClose={() => setConfirmDelete(false)}
        onEliminado={() => { setConfirmDelete(false); onClose(); onAlterado && onAlterado(); }}
      />
    );
  }

  return (
    <Modal title="Detalhe do utilizador" sub={utilizador.email} icon="user" size="grande" onClose={onClose}
      footer={<button type="button" className="btn btn-ghost" onClick={onClose}>Fechar</button>}>
      {erro && <div className="alert bad" style={{ padding: "9px 12px", marginBottom: 12 }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>}
      {aviso && <div className="alert ok" style={{ padding: "9px 12px", marginBottom: 12 }}><Icon name="check" size={16} /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{aviso}</span></div>}
      {!erro && !d && <div className="muted tiny" style={{ padding: 12 }}>A carregar…</div>}
      {d && (
        <>
          <div className="admin-detalhe-grid">
            <AdminDetalheField icon="user" label="Nome" value={d.nome || "—"} />
            <AdminDetalheField icon="coins" label="Moeda" value={d.moeda} />
            <AdminDetalheField icon="spark" label="Plano" value={d.plano + (d.planoExpira ? " · até " + new Date(d.planoExpira).toLocaleDateString("pt-PT") : "")} />
            <AdminDetalheField icon="key" label="Role" value={d.role} />
            <AdminDetalheField icon="check" label="Email verificado" value={d.emailVerificado ? "Sim" : "Não"} />
            <AdminDetalheField icon="cal" label="Criado em" value={new Date(d.createdAt).toLocaleDateString("pt-PT")} />
            <AdminDetalheField icon="history" label="Último login" value={d.ultimoLogin ? new Date(d.ultimoLogin).toLocaleString("pt-PT") : "Nunca"} />
            <AdminDetalheField icon="wallet" label="Despesas" value={d.totalDespesas} />
            <AdminDetalheField icon="arrowsDown" label="Rendimentos" value={d.totalRendimentos} />
            <AdminDetalheField icon="target" label="Metas" value={d.totalMetas} />
            <AdminDetalheField icon="card" label="Contas" value={d.totalContas} />
            {(d.stripeCustomerId || d.stripeSubId) && (
              <>
                <AdminDetalheField icon="link" label="Stripe · cliente" value={d.stripeCustomerId || "—"} mono />
                <AdminDetalheField icon="link" label="Stripe · subscrição" value={d.stripeSubId || "—"} mono />
              </>
            )}
          </div>

          <div className="admin-metrics-section-title" style={{ marginTop: 18 }}>Ações de conta</div>
          <div className="row" style={{ gap: 10, flexWrap: "wrap" }}>
            <button type="button" className="btn btn-ghost" style={{ borderColor: "var(--accent)" }} disabled={busyAcao === "logout"} onClick={forcarLogout}>
              <Icon name="logout" size={15} color="var(--accent)" /> {busyAcao === "logout" ? "A terminar…" : "Forçar logout"}
            </button>
            <button type="button" className="btn btn-ghost" style={{ borderColor: "var(--accent)" }} disabled={busyAcao === "verificacao" || d.emailVerificado} onClick={reenviarVerificacao}
              title={d.emailVerificado ? "Este email já está verificado" : ""}>
              <Icon name="sync" size={15} color="var(--accent)" /> {busyAcao === "verificacao" ? "A enviar…" : "Reenviar verificação"}
            </button>
            <button type="button" className="btn btn-ghost" style={{ color: "var(--neg)", borderColor: "var(--neg)" }} onClick={() => setConfirmDelete(true)}>
              <Icon name="trash" size={15} color="var(--neg)" /> Eliminar conta
            </button>
          </div>

          <div className="admin-metrics-section-title" style={{ marginTop: 18 }}>Histórico de plano</div>
          {d.historicoPlano.length === 0 && (
            <div className="admin-detalhe-nota">
              <span className="admin-detalhe-ico"><Icon name="shield" size={16} color="var(--accent)" /></span>
              <div>
                <b>Utilizador criado em {new Date(d.createdAt).toLocaleDateString("pt-PT")}</b>
                <span>Sem alterações de plano registadas até ao momento.</span>
              </div>
            </div>
          )}
          {d.historicoPlano.length > 0 && (
            <div style={{ overflowX: "auto" }}>
              <table className="t">
                <thead><tr><th>Admin</th><th>Detalhes</th><th>Quando</th></tr></thead>
                <tbody>
                  {d.historicoPlano.map((r) => (
                    <tr key={r.id}>
                      <td style={{ fontWeight: 600 }}>{r.adminEmail}</td>
                      <td className="tiny">{formatarDetalhesAuditoria("mudar_plano", r.detalhes)}</td>
                      <td className="tiny muted">{new Date(r.createdAt).toLocaleString("pt-PT")}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </>
      )}
    </Modal>
  );
}

/* Modal de confirmação para eliminar a conta de um utilizador (destrutivo e
   irreversível) — só ativa o botão quando o admin escreve exatamente o email
   da conta, tal como pedido: uma checkbox ou "sim, tenho a certeza" não seria
   suficiente para uma ação desta gravidade. */
function AdminEliminarContaModal({ utilizador, onClose, onEliminado }) {
  const [confirmarEmail, setConfirmarEmail] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [erro, setErro] = React.useState("");
  const emailCorreto = confirmarEmail.trim().toLowerCase() === utilizador.email.toLowerCase();

  const eliminar = async () => {
    setBusy(true); setErro("");
    try {
      await API.adminEliminarConta(utilizador.id, confirmarEmail.trim());
      onEliminado();
    } catch (e) {
      setErro(e.message || "Não foi possível eliminar a conta.");
      setBusy(false);
    }
  };

  return (
    <ConfirmModal title="Tens a certeza?" busy={busy} confirmDisabled={!emailCorreto}
      confirmLabel={busy ? "A eliminar…" : "Eliminar conta"} onConfirm={eliminar} onClose={() => !busy && onClose()}>
      <p className="modal-confirm-msg">
        Vais apagar a conta <strong style={{ color: "var(--ink)" }}>{utilizador.email}</strong> e <strong style={{ color: "var(--ink)" }}>todos</strong> os dados associados — despesas, rendimentos, metas, contas e categorias. Esta ação <strong style={{ color: "var(--ink)" }}>não pode ser revertida</strong>.
      </p>
      <Field label={`Escreve "${utilizador.email}" para confirmar`} hint="Tem de corresponder exatamente ao email da conta.">
        <input className="input" value={confirmarEmail} onChange={(e) => setConfirmarEmail(e.target.value)} placeholder={utilizador.email} autoFocus />
      </Field>
      {erro && <div className="alert bad" style={{ marginTop: 4, padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>}
    </ConfirmModal>
  );
}

/* Modal simples para mudar o plano de um utilizador manualmente. */
function AdminMudarPlanoModal({ utilizador, onClose, onSaved }) {
  const [plano, setPlano] = React.useState(utilizador.plano || "free");
  const [planoExpira, setPlanoExpira] = React.useState(utilizador.planoExpira ? String(utilizador.planoExpira).slice(0, 10) : "");
  const [busy, setBusy] = React.useState(false);
  const [erro, setErro] = React.useState("");

  const guardar = async () => {
    setBusy(true); setErro("");
    try {
      await API.adminMudarPlano(utilizador.id, { plano, planoExpira: planoExpira || null });
      onSaved();
    } catch (e) {
      setErro(e.message || "Não foi possível mudar o plano.");
    } finally {
      setBusy(false);
    }
  };

  return (
    <Modal title="Mudar plano" sub={utilizador.email} icon="wallet" size="medio" onClose={onClose}
      footer={<>
        <button type="button" className="btn btn-ghost" onClick={onClose}>Cancelar</button>
        <button type="button" className="btn btn-primary" disabled={busy} onClick={guardar}>{busy ? "A guardar…" : "Guardar"}</button>
      </>}>
      <Field label="Plano">
        <select className="input" value={plano} onChange={(e) => setPlano(e.target.value)}>
          <option value="free">free</option>
          <option value="premium">premium</option>
        </select>
      </Field>
      <Field label="Válido até (opcional)" hint="Deixa em branco para não definir data de expiração.">
        <input className="input" type="date" value={planoExpira} onChange={(e) => setPlanoExpira(e.target.value)} />
      </Field>
      {erro && <div className="alert bad" style={{ marginTop: 4, padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>}
    </Modal>
  );
}

/* ---------- Separador: Métricas ---------- */
function AdminMetricas() {
  const [m, setM] = React.useState(null);
  const [erro, setErro] = React.useState("");

  React.useEffect(() => {
    API.adminMetricas().then(setM).catch((e) => setErro(e.message || "Não foi possível carregar as métricas."));
  }, []);

  if (erro) return <div className="alert bad" style={{ padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>;
  if (!m) return <div className="muted tiny" style={{ padding: 20 }}>A carregar…</div>;

  const totalFree = Math.max(0, m.totalUtilizadores - m.totalPremium);
  const pctVerificado = m.totalUtilizadores > 0 ? Math.round((m.totalEmailVerificado / m.totalUtilizadores) * 100) : 0;
  const pctPremium = m.totalUtilizadores > 0 ? Math.round((m.totalPremium / m.totalUtilizadores) * 100) : 0;

  return (
    <>
      <div className="admin-metrics-section-title">Utilizadores</div>
      <div className="grid kpi-row">
        <Kpi label="Total de utilizadores" value={String(m.totalUtilizadores)} icon="users" color="var(--accent)" />
        <Kpi label="Utilizadores premium" value={String(m.totalPremium)} icon="spark" color="var(--c-educacao)" sub={pctPremium + "% da base"} />
        <Kpi label="Novos (últimos 7 dias)" value={String(m.novosUtilizadores7dias)} icon="arrowsDown" color="var(--c-habitacao)" />
        <Kpi label="Novos (últimos 30 dias)" value={String(m.novosUtilizadores30dias)} icon="cal" color="var(--c-transporte)" />
        <Kpi label="Email verificado" value={pctVerificado + "%"} icon="check" color="var(--c-lazer)" sub={m.totalEmailVerificado + " de " + m.totalUtilizadores} />
      </div>

      <div className="admin-metrics-section-title">Onboarding</div>
      <div className="grid kpi-row">
        <Kpi label="Nunca começaram a usar a app" value={String(m.nuncaComecaram)} icon="flag"
          color={m.pctNuncaComecaram >= 30 ? "var(--neg)" : "var(--c-transporte)"}
          sub={m.pctNuncaComecaram + "% da base · 0 despesas e 0 rendimentos"} />
      </div>
      {m.pctNuncaComecaram >= 30 && (
        <div className="alert warn" style={{ marginTop: 4, marginBottom: 16, padding: "10px 14px" }}>
          <Icon name="info" size={16} />
          <span style={{ fontSize: 12.5, fontWeight: 600 }}>Mais de 30% dos utilizadores registados nunca chegaram a registar uma despesa ou rendimento — vale a pena olhar para o onboarding.</span>
        </div>
      )}

      <div className="admin-metrics-grid">
        <div className="card card-pad">
          <div className="admin-card-title">Novos utilizadores — últimos 6 meses</div>
          <BarPair data={m.novosPorMes.map((d) => ({ m: d.mes.slice(5), rec: d.total, gasto: 0 }))} color="var(--accent)" color2="var(--accent)" height={200} />
        </div>
        <div className="card card-pad" style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 16 }}>
          <div className="admin-card-title" style={{ alignSelf: "flex-start" }}>Free vs Premium</div>
          <DonutChart
            data={[{ valor: m.totalPremium, color: "var(--accent)" }, { valor: totalFree, color: "var(--border-strong)" }]}
            center={<div><div style={{ fontSize: 22, fontWeight: 700 }}>{m.totalUtilizadores}</div><div className="tiny muted">utilizadores</div></div>} />
          <div className="admin-donut-legend">
            <span><i style={{ background: "var(--accent)" }} /> Premium ({m.totalPremium})</span>
            <span><i style={{ background: "var(--border-strong)" }} /> Free ({totalFree})</span>
          </div>
        </div>
      </div>

      <div className="admin-metrics-section-title">Atividade</div>
      <div className="grid kpi-row">
        <Kpi label="Total de despesas registadas" value={String(m.totalDespesas)} icon="wallet" color="var(--c-transporte)" />
        <Kpi label="Total de rendimentos registados" value={String(m.totalRendimentos)} icon="arrowsDown" color="var(--accent)" />
        <Kpi label="Uso do assistente (mês atual)" value={String(m.usoAssistenteMesAtual)} icon="bot" color="var(--c-lazer)" sub={m.periodoAtual} />
      </div>
    </>
  );
}

/* ---------- Separador: Receita ----------
   Estimativa a partir dos dados da própria base (não há webhook do Stripe
   nem campo que diga se um premium é mensal ou anual — ver nota em
   src/routes/admin.js, rota /receita). Números tratados como aproximação,
   não como facturação exata. */
function AdminReceita() {
  const [r, setR] = React.useState(null);
  const [erro, setErro] = React.useState("");

  React.useEffect(() => {
    API.adminReceita().then(setR).catch((e) => setErro(e.message || "Não foi possível carregar a receita."));
  }, []);

  if (erro) return <div className="alert bad" style={{ padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>;
  if (!r) return <div className="muted tiny" style={{ padding: 20 }}>A carregar…</div>;

  return (
    <>
      <div className="admin-metrics-section-title">Subscrições</div>
      <div className="grid kpi-row">
        <Kpi label="Premium ativos" value={String(r.premiumAtivos)} icon="spark" color="var(--c-educacao)" />
        <Kpi label="MRR estimado" value={"≈ " + r.mrrEstimado.toLocaleString("pt-PT", { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + " €"}
          icon="wallet" color="var(--accent)" sub={"assume " + r.precoMensal.toLocaleString("pt-PT", { minimumFractionDigits: 2 }) + " €/mês por ativo"} />
        <Kpi label="Canceladas (últimos 30 dias)" value={String(r.canceladasUltimos30Dias)} icon="arrowsDown" color="var(--c-transporte)" />
        <Kpi label="Expiram nos próximos 7 dias" value={String(r.expiramProximos7Dias)} icon="cal" color="var(--c-habitacao)" />
      </div>
      <div className="alert warn" style={{ marginTop: 16, padding: "10px 14px" }}>
        <Icon name="info" size={16} />
        <span style={{ fontSize: 12.5, fontWeight: 600 }}>
          Estimativa calculada a partir dos dados guardados (não há ligação em tempo real ao Stripe nem distinção entre plano mensal/anual). Preços de referência: {r.precoMensal.toLocaleString("pt-PT", { minimumFractionDigits: 2 })} €/mês · {r.precoAnual.toLocaleString("pt-PT", { minimumFractionDigits: 2 })} €/ano.
        </span>
      </div>
    </>
  );
}

/* ---------- Separador: Assistente IA ---------- */
function AdminAssistenteUso() {
  const [periodo, setPeriodo] = React.useState("");
  const [linhas, setLinhas] = React.useState([]);
  const [busy, setBusy] = React.useState(true);
  const [erro, setErro] = React.useState("");

  React.useEffect(() => {
    API.adminAssistenteUso()
      .then((resp) => { setPeriodo(resp.periodoAtual); setLinhas(resp.utilizadores || []); })
      .catch((e) => setErro(e.message || "Não foi possível carregar o uso do assistente."))
      .finally(() => setBusy(false));
  }, []);

  if (erro) return <div className="alert bad" style={{ padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>;

  return (
    <div className="card">
      <div className="card-pad" style={{ borderBottom: "1px solid var(--border)" }}>
        <span className="muted tiny" style={{ fontWeight: 600 }}>Mês atual: {periodo}</span>
      </div>
      <div style={{ overflowX: "auto" }}>
        <table className="t">
          <thead>
            <tr><th>Utilizador</th><th>Plano</th><th style={{ textAlign: "right" }}>Perguntas usadas</th><th style={{ textAlign: "right" }}>Limite</th><th style={{ textAlign: "right" }}>Restantes</th></tr>
          </thead>
          <tbody>
            {busy && <tr><td colSpan={5} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>A carregar…</td></tr>}
            {!busy && linhas.length === 0 && <tr><td colSpan={5} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>Ninguém usou o assistente este mês.</td></tr>}
            {!busy && linhas.map((u) => (
              <tr key={u.userId}>
                <td style={{ fontWeight: 600 }}>{u.email}{u.nome ? <span className="tiny muted" style={{ fontWeight: 500 }}> · {u.nome}</span> : null}</td>
                <td><span className={"chip" + (u.plano === "premium" ? " sel" : "")}>{u.plano}</span></td>
                <td className="tnum" style={{ textAlign: "right" }}>{u.usadas}</td>
                <td className="tnum" style={{ textAlign: "right" }}>{u.limite}</td>
                <td className="tnum" style={{ textAlign: "right", color: u.restantes === 0 ? "var(--neg)" : undefined, fontWeight: u.restantes === 0 ? 700 : undefined }}>{u.restantes}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

/* ---------- Separador: Anúncios ---------- */
function AdminAnuncios() {
  const [linhas, setLinhas] = React.useState([]);
  const [busy, setBusy] = React.useState(true);
  const [erro, setErro] = React.useState("");
  const [titulo, setTitulo] = React.useState("");
  const [mensagem, setMensagem] = React.useState("");
  const [aCriar, setACriar] = React.useState(false);
  const [erroForm, setErroForm] = React.useState("");

  const carregar = React.useCallback(() => {
    setBusy(true); setErro("");
    API.adminListarAnuncios()
      .then((resp) => setLinhas(resp.anuncios || []))
      .catch((e) => setErro(e.message || "Não foi possível carregar os anúncios."))
      .finally(() => setBusy(false));
  }, []);

  React.useEffect(() => { carregar(); }, [carregar]);

  const criar = async () => {
    if (!titulo.trim() || !mensagem.trim()) {
      setErroForm("Preenche o título e a mensagem.");
      return;
    }
    setACriar(true); setErroForm("");
    try {
      await API.adminCriarAnuncio({ titulo: titulo.trim(), mensagem: mensagem.trim() });
      setTitulo(""); setMensagem("");
      carregar();
    } catch (e) {
      setErroForm(e.message || "Não foi possível criar o anúncio.");
    } finally {
      setACriar(false);
    }
  };

  const alternarEstado = async (a) => {
    try {
      await API.adminMudarEstadoAnuncio(a.id, !a.ativo);
      carregar();
    } catch (e) {
      setErro(e.message || "Não foi possível mudar o estado do anúncio.");
    }
  };

  return (
    <>
      <div className="card card-pad" style={{ marginBottom: 18 }}>
        <div className="admin-card-title">Novo anúncio</div>
        <Field label="Título">
          <input className="input" value={titulo} onChange={(e) => setTitulo(e.target.value)} placeholder="Ex.: Manutenção agendada" maxLength={120} />
        </Field>
        <Field label="Mensagem" hint="Aparece como banner no topo do painel dos utilizadores.">
          <textarea className="input" value={mensagem} onChange={(e) => setMensagem(e.target.value)} placeholder="Escreve a mensagem do aviso…" rows={3} maxLength={500} style={{ resize: "vertical", fontFamily: "inherit" }} />
        </Field>
        {erroForm && <div className="alert bad" style={{ padding: "9px 12px", marginBottom: 10 }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erroForm}</span></div>}
        <button type="button" className="btn btn-primary" disabled={aCriar} onClick={criar}>
          <Icon name="plus" size={15} color="#fff" /> {aCriar ? "A criar…" : "Criar anúncio"}
        </button>
      </div>

      {erro && <div className="alert bad" style={{ padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>}

      {!erro && (
        <div className="card">
          <div style={{ overflowX: "auto" }}>
            <table className="t">
              <thead><tr><th>Título</th><th>Mensagem</th><th>Estado</th><th>Criado em</th><th></th></tr></thead>
              <tbody>
                {busy && <tr><td colSpan={5} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>A carregar…</td></tr>}
                {!busy && linhas.length === 0 && <tr><td colSpan={5} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>Ainda não há anúncios.</td></tr>}
                {!busy && linhas.map((a) => (
                  <tr key={a.id}>
                    <td style={{ fontWeight: 600 }}>{a.titulo}</td>
                    <td className="tiny" style={{ maxWidth: 320, whiteSpace: "normal", wordBreak: "break-word" }}>{a.mensagem}</td>
                    <td><span className={"chip" + (a.ativo ? " sel" : "")}>{a.ativo ? "Ativo" : "Inativo"}</span></td>
                    <td className="tiny muted">{new Date(a.criadoEm).toLocaleString("pt-PT")}</td>
                    <td>
                      <button type="button" className="btn btn-ghost" style={{ padding: "6px 12px", fontSize: 12.5 }} onClick={() => alternarEstado(a)}>
                        {a.ativo ? "Desativar" : "Ativar"}
                      </button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}
    </>
  );
}

/* ---------- Separador: Envio de email (reengajamento) ----------
   Campanhas de reativação para utilizadores registados que nunca fizeram
   login. Não existe rastreio de abertura (sem webhook do Resend configurado)
   — por isso não há nenhum cartão de "taxa de abertura" aqui; inventar um
   número era pior do que não mostrar nada. "Emails enviados" é uma contagem
   real (ReengajamentoEmail dos últimos 30 dias, ver GET .../utilizadores-inativos),
   não um contador local que se perde ao recarregar a página. */
const TEMPLATE_PADRAO = `Olá {{nome}},

Reparámos que criaste uma conta no Rende+, mas ainda não começaste a utilizar a aplicação.

O Rende+ foi criado para te ajudar a ter uma vida financeira mais organizada, simples e inteligente.

Começa agora e transforma a forma como gere o teu dinheiro!

Estamos contigo,
Equipa Rende+`;

function AdminEnvioEmail() {
  // "campanha" = em massa, a partir da lista de utilizadores inativos (já existia).
  // "individual" = procurar UM utilizador qualquer (ativo ou não) e enviar-lhe um email avulso.
  const [modo, setModo] = React.useState("campanha");

  const [linhas, setLinhas] = React.useState([]);
  const [pagina, setPagina] = React.useState(1);
  const [totalPaginas, setTotalPaginas] = React.useState(1);
  const [total, setTotal] = React.useState(0);
  const [emailsEnviados30d, setEmailsEnviados30d] = React.useState(0);
  const [diasRegistado, setDiasRegistado] = React.useState("7");
  const [fonteRegisto, setFonteRegisto] = React.useState("");
  const [pais, setPais] = React.useState("");
  const [busy, setBusy] = React.useState(true);
  const [erro, setErro] = React.useState("");
  const [selecionados, setSelecionados] = React.useState(() => new Set());
  const [selecionarTodos, setSelecionarTodos] = React.useState(false); // "todos os N filtrados", não só a página visível

  // ---- modo individual: procurar por email entre TODOS os utilizadores (não só inativos) ----
  const [buscaTexto, setBuscaTexto] = React.useState("");
  const [buscaResultados, setBuscaResultados] = React.useState([]);
  const [buscaBusy, setBuscaBusy] = React.useState(false);
  const [buscaErro, setBuscaErro] = React.useState("");
  const [individualSelecionado, setIndividualSelecionado] = React.useState(null);

  React.useEffect(() => {
    if (modo !== "individual" || individualSelecionado) return;
    const texto = buscaTexto.trim();
    if (!texto) { setBuscaResultados([]); return; }
    setBuscaBusy(true); setBuscaErro("");
    const t = setTimeout(() => {
      API.adminListarUtilizadores({ page: 1, pesquisa: texto })
        .then((resp) => setBuscaResultados((resp.utilizadores || []).slice(0, 8)))
        .catch((e) => setBuscaErro(e.message || "Não foi possível pesquisar."))
        .finally(() => setBuscaBusy(false));
    }, 350);
    return () => clearTimeout(t);
  }, [buscaTexto, modo, individualSelecionado]);

  const escolherIndividual = (u) => { setIndividualSelecionado(u); setBuscaResultados([]); };
  const limparIndividual = () => { setIndividualSelecionado(null); setBuscaTexto(""); };

  // ---- histórico de envios (campanhas + individuais), partilhado pelos dois modos ----
  const [historico, setHistorico] = React.useState([]);
  const [histPagina, setHistPagina] = React.useState(1);
  const [histTotalPaginas, setHistTotalPaginas] = React.useState(1);
  const [histBusy, setHistBusy] = React.useState(true);
  const [histErro, setHistErro] = React.useState("");
  const carregarHistorico = React.useCallback(async (p) => {
    setHistBusy(true); setHistErro("");
    try {
      const resp = await API.adminHistoricoCampanhas({ page: p });
      setHistorico(resp.campanhas || []);
      setHistPagina(resp.pagina || 1);
      setHistTotalPaginas(resp.totalPaginas || 1);
    } catch (e) {
      setHistErro(e.message || "Não foi possível carregar o histórico.");
    } finally {
      setHistBusy(false);
    }
  }, []);
  React.useEffect(() => { carregarHistorico(1); }, [carregarHistorico]);

  const [assunto, setAssunto] = React.useState("Ainda não conheces o Rende+?");
  const [mensagem, setMensagem] = React.useState(TEMPLATE_PADRAO);
  const [preview, setPreview] = React.useState(false);

  const [confirmarEnvio, setConfirmarEnvio] = React.useState(false);
  const [enviando, setEnviando] = React.useState(false);
  const [resultadoEnvio, setResultadoEnvio] = React.useState(null);
  const [erroEnvio, setErroEnvio] = React.useState("");

  const carregar = React.useCallback(async (p) => {
    setBusy(true); setErro("");
    try {
      const resp = await API.adminUtilizadoresInativos({ page: p, diasRegistado, fonteRegisto, pais });
      setLinhas(resp.utilizadores || []);
      setPagina(resp.pagina || 1);
      setTotalPaginas(resp.totalPaginas || 1);
      setTotal(resp.total || 0);
      setEmailsEnviados30d(resp.emailsEnviados30d || 0);
    } catch (e) {
      setErro(e.message || "Não foi possível carregar os utilizadores.");
    } finally {
      setBusy(false);
    }
  }, [diasRegistado, fonteRegisto, pais]);

  React.useEffect(() => { carregar(1); setSelecionados(new Set()); setSelecionarTodos(false); }, [carregar]);

  const trocarModo = (novoModo) => {
    setModo(novoModo);
    setResultadoEnvio(null); setErroEnvio("");
    setSelecionados(new Set()); setSelecionarTodos(false);
    limparIndividual();
  };

  const toggleUm = (id) => {
    setSelecionarTodos(false);
    setSelecionados((s) => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; });
  };
  const todosDaPaginaMarcados = linhas.length > 0 && linhas.every((u) => selecionados.has(u.id));
  const toggleTodosDaPagina = () => {
    setSelecionados((s) => {
      const n = new Set(s);
      linhas.forEach((u) => (todosDaPaginaMarcados ? n.delete(u.id) : n.add(u.id)));
      return n;
    });
    setSelecionarTodos(false);
  };

  const individualBloqueado = modo === "individual" && individualSelecionado && individualSelecionado.aceitaEmailsReengajamento === false;
  const nSelecionados = modo === "individual" ? (individualSelecionado ? 1 : 0) : (selecionarTodos ? total : selecionados.size);
  const prontoParaEnviar = !!assunto.trim() && !!mensagem.trim() && nSelecionados > 0 && !individualBloqueado;

  const corpoHtmlFinal = () => mensagem.split("\n").map((l) => (l.trim() ? `<p style="margin:0 0 12px;">${l}</p>` : "<br/>")).join("");

  const disparar = async () => {
    setEnviando(true); setErroEnvio("");
    try {
      const payload = { assunto: assunto.trim(), corpoHtml: corpoHtmlFinal() };
      if (modo === "individual") payload.userIds = [individualSelecionado.id];
      else if (selecionarTodos) { payload.selecionarTodos = true; payload.filtros = { diasRegistado, fonteRegisto, pais }; }
      else payload.userIds = Array.from(selecionados);
      const resp = await API.adminEnviarCampanha(payload);
      setResultadoEnvio(resp);
      setConfirmarEnvio(false);
      if (modo === "individual") limparIndividual();
      else { setSelecionados(new Set()); setSelecionarTodos(false); carregar(pagina); }
      carregarHistorico(1); // a entrada nova já aparece (as contagens de sucesso/falha vão-se atualizando ao atualizar a página)
    } catch (e) {
      setErroEnvio(e.message || "Não foi possível enviar o email.");
    } finally {
      setEnviando(false);
    }
  };

  return (
    <>
      <div className="grid kpi-row">
        <Kpi label="Utilizadores inativos" value={String(total)} icon="users" color="var(--accent)" sub="Registados e sem atividade" />
        <Kpi label="Emails enviados" value={String(emailsEnviados30d)} icon="send" color="var(--c-habitacao)" sub="Últimos 30 dias" />
      </div>
      <div className="alert" style={{ background: "var(--info-soft)", marginBottom: 16, padding: "10px 14px" }}>
        <Icon name="info" size={16} color="var(--info)" />
        <span style={{ fontSize: 12.5, fontWeight: 600, color: "var(--info)" }}>Sem rastreio de abertura configurado (não há webhook do Resend) — por isso não mostramos "taxa de abertura".</span>
      </div>

      <div className="seg" role="group" aria-label="Tipo de envio" style={{ marginBottom: 16, width: "fit-content" }}>
        <button type="button" className={modo === "campanha" ? "on" : ""} style={{ display: "inline-flex", alignItems: "center", gap: 6 }} onClick={() => trocarModo("campanha")}><Icon name="users" size={14} /> Campanha em massa</button>
        <button type="button" className={modo === "individual" ? "on" : ""} style={{ display: "inline-flex", alignItems: "center", gap: 6 }} onClick={() => trocarModo("individual")}><Icon name="user" size={14} /> Individual</button>
      </div>

      <div className="grid" style={{ gridTemplateColumns: "1.4fr 1fr", gap: 16, alignItems: "start" }}>
        {/* ---- Coluna esquerda: escolher destinatário(s) ---- */}
        {modo === "campanha" ? (
          <div className="card">
            <div className="card-pad" style={{ borderBottom: "1px solid var(--border)" }}>
              <div className="admin-card-title" style={{ marginBottom: 10 }}>1. Selecionar utilizadores inativos</div>
              <div className="row" style={{ gap: 10, flexWrap: "wrap" }}>
                <Field label="Registados há mais de">
                  <select className="input" value={diasRegistado} onChange={(e) => setDiasRegistado(e.target.value)}>
                    <option value="1">1 dia</option>
                    <option value="7">7 dias</option>
                    <option value="14">14 dias</option>
                    <option value="30">30 dias</option>
                    <option value="90">90 dias</option>
                  </select>
                </Field>
                <Field label="Fonte de registo">
                  <select className="input" value={fonteRegisto} onChange={(e) => setFonteRegisto(e.target.value)}>
                    <option value="">Todas</option>
                    <option value="web">Web</option>
                    <option value="app">App</option>
                  </select>
                </Field>
                <Field label="País" hint="Código de 2 letras, ex.: PT">
                  <input className="input" value={pais} onChange={(e) => setPais(e.target.value.toUpperCase().slice(0, 2))} placeholder="Todos" style={{ width: 90 }} />
                </Field>
              </div>
            </div>

            {erro && <div className="alert bad" style={{ margin: 16, padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>}

            {!erro && (
              <div style={{ overflowX: "auto" }}>
                <table className="t">
                  <thead>
                    <tr>
                      <th style={{ width: 30 }}><input type="checkbox" checked={todosDaPaginaMarcados} onChange={toggleTodosDaPagina} aria-label="Selecionar todos os desta página" /></th>
                      <th>Utilizador</th>
                      <th>Registado em</th>
                      <th>País</th>
                      <th>Fonte</th>
                    </tr>
                  </thead>
                  <tbody>
                    {busy && <tr><td colSpan={5} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>A carregar…</td></tr>}
                    {!busy && linhas.length === 0 && <tr><td colSpan={5} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>Sem utilizadores inativos com estes filtros.</td></tr>}
                    {!busy && linhas.map((u) => (
                      <tr key={u.id}>
                        <td><input type="checkbox" checked={selecionarTodos || selecionados.has(u.id)} disabled={u.aceitaEmailsReengajamento === false} onChange={() => toggleUm(u.id)} aria-label={"Selecionar " + u.email} /></td>
                        <td style={{ fontWeight: 600 }}>
                          {u.nome || u.email}
                          {u.aceitaEmailsReengajamento === false && <span className="tiny muted" style={{ fontWeight: 500 }}> · não aceita emails</span>}
                          {u.nome && <div className="tiny muted" style={{ fontWeight: 500 }}>{u.email}</div>}
                        </td>
                        <td className="tiny muted">{new Date(u.createdAt).toLocaleDateString("pt-PT")}</td>
                        <td className="tiny muted">{u.pais || "—"}</td>
                        <td className="tiny muted">{u.fonteRegisto === "app" ? "App" : "Web"}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            )}

            {!busy && total > linhas.length && (
              <div className="card-pad" style={{ borderTop: "1px solid var(--border)" }}>
                <button type="button" className={"btn " + (selecionarTodos ? "btn-primary" : "btn-ghost")} onClick={() => setSelecionarTodos((v) => !v)}>
                  <Icon name="users" size={14} /> {selecionarTodos ? `Todos os ${total} filtrados selecionados` : `Selecionar todos os ${total} filtrados`}
                </button>
              </div>
            )}

            <AdminPaginacao pagina={pagina} totalPaginas={totalPaginas} onChange={(p) => carregar(p)} />
          </div>
        ) : (
          <div className="card card-pad">
            <div className="admin-card-title" style={{ marginBottom: 10 }}>1. Procurar utilizador</div>
            <div className="tiny muted" style={{ fontWeight: 500, marginBottom: 12 }}>Pesquisa por qualquer utilizador (ativo ou inativo) pelo email, para lhe enviar um email avulso.</div>

            {individualSelecionado ? (
              <div className="admin-detalhe-nota" style={{ display: "flex", alignItems: "center", gap: 12, justifyContent: "space-between" }}>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontWeight: 700, fontSize: 14 }}>{individualSelecionado.nome || individualSelecionado.email}</div>
                  <div className="tiny muted" style={{ fontWeight: 500, marginTop: 2 }}>{individualSelecionado.email} · {individualSelecionado.plano === "premium" ? "Premium" : "Free"}</div>
                  {individualSelecionado.aceitaEmailsReengajamento === false && (
                    <div className="tiny" style={{ fontWeight: 700, color: "var(--neg)", marginTop: 4 }}>Este utilizador não aceita emails de reengajamento — o envio será bloqueado.</div>
                  )}
                </div>
                <button type="button" className="btn btn-ghost" onClick={limparIndividual}><Icon name="close" size={14} /> Trocar</button>
              </div>
            ) : (
              <>
                <div className="row" style={{ gap: 10, background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: "var(--radius-sm)", padding: "9px 13px", marginBottom: 10 }}>
                  <Icon name="search" size={17} color="var(--ink-3)" />
                  <input value={buscaTexto} onChange={(e) => setBuscaTexto(e.target.value)} placeholder="Pesquisar por email…" autoFocus
                    style={{ border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontSize: 14, fontWeight: 500, fontFamily: "inherit", width: "100%" }} />
                </div>
                {buscaErro && <div className="alert bad" style={{ padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{buscaErro}</span></div>}
                {buscaBusy && <div className="tiny muted" style={{ fontWeight: 500, padding: "6px 2px" }}>A procurar…</div>}
                {!buscaBusy && buscaTexto.trim() && buscaResultados.length === 0 && !buscaErro && (
                  <div className="tiny muted" style={{ fontWeight: 500, padding: "6px 2px" }}>Nenhum utilizador encontrado com este email.</div>
                )}
                {buscaResultados.length > 0 && (
                  <div className="hdr-dropdown-pop" style={{ position: "static", boxShadow: "none", border: "1px solid var(--border)" }}>
                    {buscaResultados.map((u) => (
                      <button type="button" key={u.id} onClick={() => escolherIndividual(u)}>
                        <Icon name="user" size={15} color="var(--ink-3)" />
                        <span style={{ minWidth: 0 }}>
                          <span style={{ display: "block", fontWeight: 600 }}>{u.nome || u.email}</span>
                          <span className="tiny muted" style={{ display: "block" }}>{u.email}{u.aceitaEmailsReengajamento === false ? " · não aceita emails" : ""}</span>
                        </span>
                      </button>
                    ))}
                  </div>
                )}
              </>
            )}
          </div>
        )}

        {/* ---- Coluna direita: composição + envio ---- */}
        <div className="card card-pad">
          <div className="admin-card-title" style={{ marginBottom: 12 }}>2. Compor email</div>
          <Field label="Assunto"><input className="input" value={assunto} onChange={(e) => setAssunto(e.target.value)} /></Field>
          <Field label="Mensagem" hint="Usa {{nome}} para inserir o nome de cada utilizador.">
            <textarea className="input" rows={10} value={mensagem} onChange={(e) => setMensagem(e.target.value)} style={{ resize: "vertical", fontFamily: "inherit", lineHeight: 1.5 }} />
          </Field>
          <button type="button" className="btn btn-soft" style={{ marginBottom: 16 }} onClick={() => setMensagem((m) => m + " {{nome}}")}>
            <Icon name="plus" size={13} /> Inserir {"{{nome}}"}
          </button>

          <button type="button" className="btn btn-ghost" style={{ width: "100%", justifyContent: "center", marginBottom: 18 }} onClick={() => setPreview((v) => !v)}>
            <Icon name="eye" size={15} /> {preview ? "Ocultar pré-visualização" : "Pré-visualizar email"}
          </button>
          {preview && (
            <div className="modal-info" style={{ marginBottom: 18 }}>
              <div className="modal-info-title">Pré-visualização (exemplo com "{individualSelecionado ? (individualSelecionado.nome || individualSelecionado.email.split("@")[0]) : "Ana"}")</div>
              <div style={{ fontSize: 13, fontWeight: 700, color: "var(--ink)" }}>{assunto.replace(/\{\{\s*nome\s*\}\}/g, individualSelecionado ? (individualSelecionado.nome || individualSelecionado.email.split("@")[0]) : "Ana")}</div>
              <div style={{ fontSize: 13, whiteSpace: "pre-wrap", color: "var(--ink-2)", lineHeight: 1.6 }}>{mensagem.replace(/\{\{\s*nome\s*\}\}/g, individualSelecionado ? (individualSelecionado.nome || individualSelecionado.email.split("@")[0]) : "Ana")}</div>
            </div>
          )}

          <div className="admin-card-title" style={{ marginBottom: 10 }}>3. Enviar {modo === "individual" ? "email" : "campanha"}</div>
          {erroEnvio && <div className="alert bad" style={{ marginBottom: 12, padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 600 }}>{erroEnvio}</span></div>}
          {resultadoEnvio && (
            <div className="alert ok" style={{ marginBottom: 12, padding: "9px 12px" }}>
              <Icon name="check" size={16} color="var(--accent)" />
              <span style={{ fontSize: 12.5, fontWeight: 600 }}>
                A enviar para {resultadoEnvio.aEnviar} utilizador(es){resultadoEnvio.ignoradosSemConsentimento > 0 && ` · ${resultadoEnvio.ignoradosSemConsentimento} ignorado(s) por não aceitarem emails`}.
              </span>
            </div>
          )}
          <button type="button" className="btn btn-primary" style={{ width: "100%", justifyContent: "center" }} disabled={!prontoParaEnviar} onClick={() => setConfirmarEnvio(true)}>
            <Icon name="send" size={15} color="#fff" /> {modo === "individual" ? "Enviar email individual" : `Enviar email para ${nSelecionados} utilizador${nSelecionados === 1 ? "" : "es"}`}
          </button>
        </div>
      </div>

      {/* ---- Histórico de envios (campanhas + individuais), com contagens reais de sucesso/falha ---- */}
      <div className="card" style={{ marginTop: 16 }}>
        <div className="card-pad row" style={{ justifyContent: "space-between", borderBottom: "1px solid var(--border)" }}>
          <div className="admin-card-title">Histórico de envios</div>
          <button type="button" className="btn btn-ghost" onClick={() => carregarHistorico(histPagina)}><Icon name="sync" size={14} /> Atualizar</button>
        </div>
        {histErro && <div className="alert bad" style={{ margin: 16, padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{histErro}</span></div>}
        {!histErro && (
          <div style={{ overflowX: "auto" }}>
            <table className="t">
              <thead>
                <tr>
                  <th>Quando</th>
                  <th>Tipo</th>
                  <th>Assunto</th>
                  <th style={{ textAlign: "right" }}>Enviados</th>
                  <th style={{ textAlign: "right" }}>Falharam</th>
                  <th>Admin</th>
                </tr>
              </thead>
              <tbody>
                {histBusy && <tr><td colSpan={6} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>A carregar…</td></tr>}
                {!histBusy && historico.length === 0 && <tr><td colSpan={6} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>Ainda não foi enviado nenhum email.</td></tr>}
                {!histBusy && historico.map((c) => (
                  <tr key={c.id}>
                    <td className="tiny muted">{new Date(c.criadoEm).toLocaleString("pt-PT", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" })}</td>
                    <td><span className="chip" style={{ fontWeight: 600 }}>{c.individual ? "Individual" : "Campanha"}</span></td>
                    <td style={{ fontWeight: 600, maxWidth: 260, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{c.assunto}</td>
                    <td className="tnum" style={{ textAlign: "right", color: "var(--accent)", fontWeight: 700 }}>{c.enviados}</td>
                    <td className="tnum" style={{ textAlign: "right", color: c.falharam > 0 ? "var(--neg)" : "var(--ink-3)", fontWeight: 700 }}>{c.falharam}</td>
                    <td className="tiny muted">{c.adminEmail}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
        <AdminPaginacao pagina={histPagina} totalPaginas={histTotalPaginas} onChange={(p) => carregarHistorico(p)} />
      </div>

      {confirmarEnvio && (
        <ConfirmModal title={modo === "individual" ? "Enviar este email?" : "Enviar campanha de email?"} confirmLabel={enviando ? "A enviar…" : "Enviar"} busy={enviando} destructive={false}
          mensagem={modo === "individual"
            ? <>Vais enviar este email para <b>{individualSelecionado && (individualSelecionado.nome || individualSelecionado.email)}</b> agora mesmo. Esta ação envia um email real — não pode ser desfeita.</>
            : <>Vais enviar este email para <b>{nSelecionados}</b> utilizador{nSelecionados === 1 ? "" : "es"} agora mesmo. Esta ação envia emails reais — não pode ser desfeita.</>}
          onConfirm={disparar} onClose={() => setConfirmarEnvio(false)} />
      )}
    </>
  );
}

/* ---------- Separador: Erros ---------- */
function AdminErros() {
  const [linhas, setLinhas] = React.useState([]);
  const [busy, setBusy] = React.useState(true);
  const [erro, setErro] = React.useState("");
  const [aberto, setAberto] = React.useState(null);

  React.useEffect(() => {
    API.adminErros()
      .then((resp) => setLinhas(resp.erros || []))
      .catch((e) => setErro(e.message || "Não foi possível carregar os erros."))
      .finally(() => setBusy(false));
  }, []);

  if (erro) return <div className="alert bad" style={{ padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>;

  return (
    <div className="card">
      <div style={{ overflowX: "auto" }}>
        <table className="t">
          <thead><tr><th>Mensagem</th><th>Rota</th><th>Quando</th><th></th></tr></thead>
          <tbody>
            {busy && <tr><td colSpan={4} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>A carregar…</td></tr>}
            {!busy && linhas.length === 0 && <tr><td colSpan={4} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>Sem erros registados. Boa notícia!</td></tr>}
            {!busy && linhas.map((e) => (
              <React.Fragment key={e.id}>
                <tr>
                  <td className="tiny" style={{ maxWidth: 360, whiteSpace: "normal", wordBreak: "break-word", fontWeight: 600 }}>{e.mensagem}</td>
                  <td className="tiny muted" style={{ fontFamily: "monospace" }}>{e.rota}</td>
                  <td className="tiny muted">{new Date(e.criadoEm).toLocaleString("pt-PT")}</td>
                  <td>
                    {e.stack && (
                      <button type="button" className="btn btn-ghost" style={{ padding: "6px 12px", fontSize: 12.5 }} onClick={() => setAberto((id) => (id === e.id ? null : e.id))}>
                        {aberto === e.id ? "Esconder stack" : "Ver stack"}
                      </button>
                    )}
                  </td>
                </tr>
                {aberto === e.id && (
                  <tr>
                    <td colSpan={4}>
                      <pre style={{ margin: 0, padding: 12, background: "var(--surface-2)", borderRadius: "var(--radius-sm)", fontSize: 11.5, overflowX: "auto", whiteSpace: "pre-wrap", wordBreak: "break-word" }}>{e.stack}</pre>
                    </td>
                  </tr>
                )}
              </React.Fragment>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

/* ---------- Separador: Auditoria ---------- */
// Traduz o JSON em bruto guardado no backend para uma frase legível — cai
// para o JSON tal como está se a ação não for reconhecida (nunca esconde
// informação, só a apresenta melhor quando sabe o formato).
function formatarDetalhesAuditoria(acao, detalhesStr) {
  let d;
  try { d = JSON.parse(detalhesStr); } catch (e) { return detalhesStr; }
  if (acao === "mudar_plano") {
    const validade = d.planoExpiraDepois ? ` (válido até ${new Date(d.planoExpiraDepois).toLocaleDateString("pt-PT")})` : "";
    return `Plano: ${d.planoAntes || "—"} → ${d.planoDepois || "—"}${validade}`;
  }
  if (acao === "forcar_logout") return "Sessão terminada pelo admin.";
  if (acao === "reenviar_verificacao") return "Email de verificação reenviado.";
  if (acao === "eliminar_conta") return `Conta eliminada${d.email ? " (" + d.email + ")" : ""}.`;
  if (acao === "criar_anuncio") return `Anúncio criado: "${d.titulo || "—"}"`;
  if (acao === "mudar_estado_anuncio") return `Anúncio "${d.titulo || "—"}" → ${d.ativo ? "ativo" : "inativo"}`;
  const entradas = Object.entries(d);
  return entradas.length ? entradas.map(([k, v]) => `${k}: ${v}`).join(" · ") : "—";
}

const ACAO_LABEL = {
  mudar_plano: "Mudar plano",
  forcar_logout: "Forçar logout",
  reenviar_verificacao: "Reenviar verificação",
  eliminar_conta: "Eliminar conta",
  criar_anuncio: "Criar anúncio",
  mudar_estado_anuncio: "Mudar estado do anúncio",
};

function AdminAuditoria() {
  const [linhas, setLinhas] = React.useState([]);
  const [pagina, setPagina] = React.useState(1);
  const [totalPaginas, setTotalPaginas] = React.useState(1);
  const [busy, setBusy] = React.useState(true);
  const [erro, setErro] = React.useState("");

  const carregar = React.useCallback(async (p) => {
    setBusy(true); setErro("");
    try {
      const resp = await API.adminAuditoria({ page: p });
      setLinhas(resp.registos || []);
      setPagina(resp.pagina || 1);
      setTotalPaginas(resp.totalPaginas || 1);
    } catch (e) {
      setErro(e.message || "Não foi possível carregar a auditoria.");
    } finally {
      setBusy(false);
    }
  }, []);

  React.useEffect(() => { carregar(1); }, []);

  if (erro) return <div className="alert bad" style={{ padding: "9px 12px" }}><Icon name="info" size={16} color="var(--neg)" /><span style={{ fontSize: 12.5, fontWeight: 700 }}>{erro}</span></div>;

  return (
    <div className="card">
      <div style={{ overflowX: "auto" }}>
        <table className="t">
          <thead>
            <tr><th>Admin</th><th>Ação</th><th>Utilizador afetado</th><th>Detalhes</th><th>Quando</th></tr>
          </thead>
          <tbody>
            {busy && <tr><td colSpan={5} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>A carregar…</td></tr>}
            {!busy && linhas.length === 0 && <tr><td colSpan={5} className="muted tiny" style={{ padding: 20, textAlign: "center" }}>Ainda sem registos de auditoria.</td></tr>}
            {!busy && linhas.map((r) => (
              <tr key={r.id}>
                <td>
                  <div className="admin-audit-admin">
                    <span className="admin-audit-avatar">{initials(r.adminEmail)}</span>
                    <span style={{ fontWeight: 600 }}>{r.adminEmail}</span>
                  </div>
                </td>
                <td><span className="chip admin-audit-chip">{ACAO_LABEL[r.acao] || r.acao.replace(/_/g, " ")}</span></td>
                <td className="tiny muted" style={{ fontFamily: "monospace" }}>{r.alvoUserId}</td>
                <td className="tiny" style={{ maxWidth: 320, whiteSpace: "normal", wordBreak: "break-word" }}>{formatarDetalhesAuditoria(r.acao, r.detalhes)}</td>
                <td className="tiny muted">{new Date(r.createdAt).toLocaleString("pt-PT")}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <AdminPaginacao pagina={pagina} totalPaginas={totalPaginas} onChange={carregar} />
    </div>
  );
}

Object.assign(window, { AdminPanel, AdminSidebar });
