// Admin Panel — full-screen workspace for operators.
// Sections: Overview · Tools · Analytics · Functions · Revenue · Users · Activity · Settings.
//
// All admin RPCs require role='admin' in user_roles. RLS enforces gating
// server-side so the UI is the convenience layer, not the security boundary.

const ADMIN_FMT_USD = (cents) =>
  `$${((cents || 0) / 100).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;

window.AdminPanel = function AdminPanel({ user, pricing, setPricing, onClose }) {
  const [section, setSection] = React.useState('overview');
  const sb = window.getSupabase?.();

  // ---- Cross-section state ---------------------------------------------------
  const [users, setUsers] = React.useState([]);
  const [activityLog, setActivityLog] = React.useState([]);
  const [settings, setSettings] = React.useState({});

  // Real analytics
  const [analyticsRange, setAnalyticsRange] = React.useState(30);     // days
  const [analyticsKpis, setAnalyticsKpis] = React.useState(null);
  const [analyticsTools, setAnalyticsTools] = React.useState([]);
  const [analyticsHourly, setAnalyticsHourly] = React.useState([]);

  // Functions monitoring
  const [fnRange, setFnRange] = React.useState(7);
  const [fnStats, setFnStats] = React.useState(null);
  const [apiKeysList, setApiKeysList] = React.useState([]);

  // Revenue
  const [revRange, setRevRange] = React.useState(30);
  const [revKpis, setRevKpis] = React.useState(null);
  const [revDaily, setRevDaily] = React.useState([]);
  const [recentPayments, setRecentPayments] = React.useState([]);

  // Tool management — fresh DB rows (with metadata + pricing in one shot).
  // Includes the new `requires_auth` flag (sign-in gate, separate from tier).
  const [toolRows, setToolRows] = React.useState({});  // tool_id → row

  // Sports section — analytics range + working copy of editable hero/group
  // settings. We read from `settings` and `tool_usage` lazily on tab focus.
  const [sportsRange, setSportsRange] = React.useState(30);
  const [sportsTools, setSportsTools] = React.useState([]);  // analytics-style rows, sport-only
  const [sportsConfig, setSportsConfig] = React.useState({});

  // Saved-calculations store — must be subscribed at the top level so hook
  // count stays stable across section switches (renderSports() reads it).
  const myAllSaves = window.MMSaves?.useAllSaves?.() || {};

  // Tool editing (used across overview + tools + errors)
  const [editingToolId, setEditingToolId] = React.useState(null);

  // Error log
  const [errorRange, setErrorRange] = React.useState(7);
  const [errorStats, setErrorStats] = React.useState([]);

  // ---- Dashboards v2 state ----
  const [overviewV2, setOverviewV2] = React.useState(null);   // current+previous KPIs
  const [dailySeries, setDailySeries] = React.useState([]);   // [{day, opens, completes, ...}]
  const [actives, setActives] = React.useState(null);         // { dau, wau, mau, total_users }
  const [insights, setInsights] = React.useState([]);         // tools insights buckets
  const [revenueByTier, setRevenueByTier] = React.useState([]);

  // ---- SEO editor state ----
  // { 'home': { title, description, og_image, keywords, noindex }, 'tool:<id>': {...}, ... }
  const [seoOverrides, setSeoOverrides] = React.useState({});
  const [seoSearch, setSeoSearch] = React.useState('');
  const [seoEditingKey, setSeoEditingKey] = React.useState(null);

  // ---------- Initial loads (split per section to avoid waste) ----------
  React.useEffect(() => {
    if (!sb) return;
    sb.rpc('admin_list_users', { p_limit: 200, p_offset: 0 }).then(({ data }) => {
      setUsers(data || []);
    });
    sb.from('activity_log').select('*').order('created_at', { ascending: false }).limit(50)
      .then(({ data }) => setActivityLog(data || []));
    sb.from('settings').select('key, value').then(({ data }) => {
      const m = {}; for (const r of data || []) m[r.key] = r.value;
      setSettings(m);
      if (m['seo.overrides'] && typeof m['seo.overrides'] === 'object') {
        setSeoOverrides(m['seo.overrides']);
      }
    });
  }, []);

  React.useEffect(() => {
    if (!sb || (section !== 'tools' && section !== 'settings')) return;
    sb.from('tool_pricing').select('*').order('sort_order').then(({ data }) => {
      const map = {}; for (const r of data || []) map[r.tool_id] = r;
      setToolRows(map);
    });
  }, [section]); // refetch when entering tools tab

  React.useEffect(() => {
    if (!sb || (section !== 'overview' && section !== 'analytics')) return;
    sb.rpc('admin_analytics_kpis',  { p_days: analyticsRange }).then(({ data }) => setAnalyticsKpis((data || [])[0] || null));
    sb.rpc('admin_analytics_tools', { p_days: analyticsRange }).then(({ data }) => setAnalyticsTools(data || []));
    sb.rpc('admin_analytics_hourly',{ p_days: Math.min(analyticsRange, 14) }).then(({ data }) => setAnalyticsHourly(data || []));
    // v2 dashboards: period-compare + daily timeseries + active rollups
    sb.rpc('admin_overview_v2',   { p_days: analyticsRange }).then(({ data }) => setOverviewV2((data || [])[0] || null));
    sb.rpc('admin_daily_series',  { p_days: analyticsRange }).then(({ data }) => setDailySeries(data || []));
    sb.rpc('admin_user_actives').then(({ data }) => setActives((data || [])[0] || null));
    sb.rpc('admin_tools_insights',{ p_days: analyticsRange }).then(({ data }) => setInsights(data || []));
  }, [section, analyticsRange]);

  React.useEffect(() => {
    if (!sb || section !== 'functions') return;
    sb.rpc('admin_function_stats', { p_days: fnRange }).then(({ data }) => setFnStats(data || []));
    sb.rpc('admin_api_keys', { p_days: fnRange }).then(({ data }) => setApiKeysList(data || []));
  }, [section, fnRange]);

  React.useEffect(() => {
    if (!sb || (section !== 'overview' && section !== 'revenue')) return;
    sb.rpc('admin_revenue_kpis',  { p_days: revRange }).then(({ data }) => setRevKpis((data || [])[0] || null));
    sb.rpc('admin_revenue_daily', { p_days: revRange }).then(({ data }) => setRevDaily(data || []));
    sb.rpc('admin_revenue_by_tier', { p_days: revRange }).then(({ data }) => setRevenueByTier(data || []));
    if (section === 'revenue') {
      sb.rpc('admin_recent_payments', { p_limit: 50 }).then(({ data }) => setRecentPayments(data || []));
    }
  }, [section, revRange]);

  // Errors — fetch grouped error stats lazily on tab focus.
  React.useEffect(() => {
    if (!sb || section !== 'errors') return;
    sb.rpc('admin_error_stats', { p_days: errorRange }).then(({ data }) => setErrorStats(data || []));
  }, [section, errorRange]);

  // Sports — fetch analytics filtered to sport tool ids and the current
  // settings keys we expose on the page. Sports config is now rendered
  // inside the Tools tab (when category filter = 'sports'), so fetch on
  // either tab.
  React.useEffect(() => {
    if (!sb || (section !== 'tools' && section !== 'sports')) return;
    sb.rpc('admin_analytics_tools', { p_days: sportsRange }).then(({ data }) => {
      const ids = new Set(window.TOOLS.filter((t) => t.cat === 'sports').map((t) => t.id));
      setSportsTools((data || []).filter((r) => ids.has(r.tool_id)));
    });
    sb.from('settings').select('key, value').in('key', [
      'featured_sports_tool',
      'sports.hero_badge',
      'sports.hero_title',
      'sports.hero_title_hl',
      'sports.hero_sub',
      'sports.groups',
    ]).then(({ data }) => {
      const m = {}; for (const r of data || []) m[r.key] = r.value;
      setSportsConfig(m);
    });
  }, [section, sportsRange]);

  // ---------- Overview (v2) ----------
  // Hero KPIs with previous-period deltas + sparklines drawn from the daily
  // time series. Health panel for at-a-glance state, "Needs attention"
  // surfaces problem tools + fresh errors so the operator catches issues fast.
  const renderOverview = () => {
    const C = window.MMCharts || {};
    const KPICard = C.KPICard;
    const Sparkline = C.Sparkline;
    const TimeseriesChart = C.TimeseriesChart;
    const HealthBadge = C.HealthBadge;

    const v2 = overviewV2 || {};
    const opens     = Number(v2.cur_opens     || 0);
    const completes = Number(v2.cur_completes || 0);
    const errors    = Number(v2.cur_errors    || 0);
    const uniqueU   = Number(v2.cur_unique_users || 0);
    const newU      = Number(v2.cur_new_users || 0);
    const gross     = Number(v2.cur_gross_cents || 0);
    const payments  = Number(v2.cur_payments || 0);

    const seriesOpens     = dailySeries.map((d) => Number(d.opens) || 0);
    const seriesCompletes = dailySeries.map((d) => Number(d.completes) || 0);
    const seriesErrors    = dailySeries.map((d) => Number(d.errors) || 0);
    const seriesUsers     = dailySeries.map((d) => Number(d.unique_users) || 0);
    const seriesNew       = dailySeries.map((d) => Number(d.new_users) || 0);
    const seriesRevenue   = dailySeries.map((d) => Number(d.gross_cents) || 0);

    const dropOffCur  = opens > 0 ? Math.round((1 - completes / opens) * 1000) / 10 : null;
    const prevOpens   = Number(v2.prev_opens || 0);
    const prevCompl   = Number(v2.prev_completes || 0);
    const dropOffPrev = prevOpens > 0 ? Math.round((1 - prevCompl / prevOpens) * 1000) / 10 : null;

    const topTools = analyticsTools.slice(0, 8);
    const maxOpens = Math.max(1, ...topTools.map((t) => Number(t.opens) || 0));

    // Insights view buckets — used in "Needs attention" column.
    const insightProblem = insights.filter((r) => r.bucket === 'problem');
    const insightErrors  = insights.filter((r) => r.bucket === 'errors');

    // Health rules of thumb. These are intentionally lenient at low traffic.
    const errorRate = opens > 0 ? errors / opens : 0;
    const health = {
      tracking: opens > 0 ? 'ok' : 'idle',
      errors: errorRate < 0.02 ? 'ok' : errorRate < 0.08 ? 'warn' : 'bad',
      revenue: gross > 0 ? 'ok' : 'idle',
      auth: actives?.dau > 0 ? 'ok' : 'idle',
    };
    const healthLabel = (s) => s === 'ok' ? 'Healthy' : s === 'warn' ? 'Watch' : s === 'bad' ? 'Issue' : 'Idle';

    return (
      <>
        <div className="adm-h2-row">
          <h2 className="dash-h2" style={{ margin: 0 }}>Overview</h2>
          <div className="filter-row" style={{ marginBottom: 0 }}>
            {[7, 30, 90].map((d) => (
              <button key={d}
                      className={`filter-pill ${analyticsRange === d ? 'active' : ''}`}
                      onClick={() => setAnalyticsRange(d)}>{d}d</button>
            ))}
          </div>
        </div>
        <p className="dash-sub">
          Last {analyticsRange} days vs. previous {analyticsRange}d. Sparklines are daily, deltas are period-over-period.
        </p>

        {/* KPI hero strip */}
        <div className="mm-kpi-grid">
          {KPICard ? <>
            <KPICard label="Tool opens"
              value={opens.toLocaleString()}
              current={opens} previous={Number(v2.prev_opens || 0)}
              sparkline={seriesOpens} color="#2563eb" />
            <KPICard label="Completed"
              value={completes.toLocaleString()}
              sub={dropOffCur != null ? `${dropOffCur}% drop-off` : ' '}
              current={completes} previous={Number(v2.prev_completes || 0)}
              sparkline={seriesCompletes} color="#10805a" />
            <KPICard label="Drop-off"
              value={dropOffCur != null ? dropOffCur + '%' : '—'}
              current={dropOffCur} previous={dropOffPrev} suffix="%" negativeGood={true}
              sparkline={dailySeries.map((d) => {
                const o = Number(d.opens) || 0; const c = Number(d.completes) || 0;
                return o > 0 ? Math.round((1 - c / o) * 100) : 0;
              })}
              color="#b8700a" />
            <KPICard label="Active users"
              value={uniqueU.toLocaleString()}
              sub={actives ? `DAU ${actives.dau} · WAU ${actives.wau} · MAU ${actives.mau}` : ' '}
              current={uniqueU} previous={Number(v2.prev_unique_users || 0)}
              sparkline={seriesUsers} color="#7c3aed" />
            <KPICard label="New sign-ups"
              value={newU.toLocaleString()}
              current={newU} previous={Number(v2.prev_new_users || 0)}
              sparkline={seriesNew} color="#16a34a" />
            <KPICard label="Errors"
              value={errors.toLocaleString()}
              sub={opens > 0 ? `${(errorRate * 100).toFixed(2)}% error rate` : ' '}
              current={errors} previous={Number(v2.prev_errors || 0)} negativeGood={true}
              sparkline={seriesErrors} color="#c8321f" />
            <KPICard label="Gross revenue"
              value={ADMIN_FMT_USD(gross)}
              sub={`${payments} payments`}
              current={gross} previous={Number(v2.prev_gross_cents || 0)}
              sparkline={seriesRevenue} color="#ea580c" />
            <KPICard label="Total users"
              value={(actives?.total_users || users.length || 0).toLocaleString()}
              sub="lifetime accounts"
              accent="#475569" />
          </> : (
            <div className="dash-empty">Loading dashboard primitives…</div>
          )}
        </div>

        {/* Daily traffic chart */}
        <div className="dash-card" style={{ marginTop: 14 }}>
          <div className="dash-card-head">
            <h3>Daily traffic · {analyticsRange}d</h3>
            <button className="dash-linkbtn" onClick={() => setSection('analytics')}>Full analytics →</button>
          </div>
          {TimeseriesChart && dailySeries.length > 0 ? (
            <TimeseriesChart
              series={[
                { id: 'opens',     label: 'Opens',     values: seriesOpens,     color: '#2563eb' },
                { id: 'completes', label: 'Completes', values: seriesCompletes, color: '#10805a' },
                { id: 'errors',    label: 'Errors',    values: seriesErrors,    color: '#c8321f' },
              ]}
              labels={dailySeries.map((d) => new Date(d.day).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }))}
              height={240}
            />
          ) : <div className="dash-empty">Building chart…</div>}
        </div>

        {/* Health + Needs attention */}
        <div className="dash-row" style={{ marginTop: 14 }}>
          <div className="dash-card">
            <div className="dash-card-head"><h3>Health</h3></div>
            <div className="adm-health-grid">
              <div className="adm-health-row">
                <div className="adm-health-l"><window.Icon name="bolt" size={14} /> Telemetry</div>
                {HealthBadge && <HealthBadge status={health.tracking} label={healthLabel(health.tracking)} />}
                <div className="adm-health-d">{opens > 0 ? `${opens.toLocaleString()} events` : 'no traffic this window'}</div>
              </div>
              <div className="adm-health-row">
                <div className="adm-health-l"><window.Icon name="x" size={14} /> Errors</div>
                {HealthBadge && <HealthBadge status={health.errors} label={healthLabel(health.errors)} />}
                <div className="adm-health-d">{errors > 0 ? `${(errorRate * 100).toFixed(2)}% rate · ${errors} events` : 'no errors captured'}</div>
              </div>
              <div className="adm-health-row">
                <div className="adm-health-l"><window.Icon name="lock" size={14} /> Stripe</div>
                {HealthBadge && <HealthBadge status={settings['stripe.configured'] ? (gross > 0 ? 'ok' : 'idle') : 'warn'} label={
                  settings['stripe.configured'] ? (gross > 0 ? 'Live' : 'Configured · idle') : 'Not configured'
                } />}
                <div className="adm-health-d">{gross > 0 ? `${ADMIN_FMT_USD(gross)} this period` : 'no payments yet'}</div>
              </div>
              <div className="adm-health-row">
                <div className="adm-health-l"><window.Icon name="grid" size={14} /> Active users</div>
                {HealthBadge && <HealthBadge status={health.auth} label={healthLabel(health.auth)} />}
                <div className="adm-health-d">{actives ? `${actives.dau} today / ${actives.wau} this week` : '—'}</div>
              </div>
            </div>
          </div>

          <div className="dash-card">
            <div className="dash-card-head">
              <h3>Needs attention</h3>
              <button className="dash-linkbtn" onClick={() => setSection('errors')}>Errors →</button>
            </div>
            {(insightProblem.length === 0 && insightErrors.length === 0) ? (
              <div className="dash-empty">Nothing flagged. ✓</div>
            ) : (
              <div className="adm-attention">
                {insightProblem.slice(0, 4).map((r) => {
                  const t = window.TOOLS.find((x) => x.id === r.tool_id);
                  return (
                    <div key={'pr-' + r.tool_id} className="adm-attn-row" onClick={() => setEditingToolId(r.tool_id)}>
                      <span className="adm-attn-tag adm-attn-warn">DROP {r.drop_off_pct}%</span>
                      <span className="adm-attn-name">{t?.name || r.tool_id}</span>
                      <span className="adm-attn-meta">{r.opens} opens</span>
                    </div>
                  );
                })}
                {insightErrors.slice(0, 4).map((r) => {
                  const t = window.TOOLS.find((x) => x.id === r.tool_id);
                  return (
                    <div key={'er-' + r.tool_id} className="adm-attn-row" onClick={() => setSection('errors')}>
                      <span className="adm-attn-tag adm-attn-bad">ERR {r.errors}</span>
                      <span className="adm-attn-name">{t?.name || r.tool_id}</span>
                      <span className="adm-attn-meta">in last {analyticsRange}d</span>
                    </div>
                  );
                })}
              </div>
            )}
          </div>
        </div>

        {/* Top tools + recent activity */}
        <div className="dash-row" style={{ marginTop: 14 }}>
          <div className="dash-card">
            <div className="dash-card-head"><h3>Top tools by opens</h3><button className="dash-linkbtn" onClick={() => setSection('analytics')}>Analytics →</button></div>
            {topTools.length === 0 ? (
              <div className="dash-empty">No events yet. Open a tool to populate.</div>
            ) : (
              <div className="admin-bars">
                {topTools.map((r) => {
                  const t = window.TOOLS.find((x) => x.id === r.tool_id);
                  const cat = t ? window.CATEGORIES.find((c) => c.id === t.cat) : null;
                  const pct = Math.round((Number(r.opens) / maxOpens) * 100);
                  return (
                    <div key={r.tool_id} className="admin-bar-row" onClick={() => setEditingToolId(r.tool_id)} style={{ cursor: 'pointer' }}>
                      <div className="admin-bar-label" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                        {cat && (
                          <span style={{ width: 22, height: 22, borderRadius: 6, background: cat.soft, color: cat.tint, display: 'grid', placeItems: 'center' }}>
                            <window.Icon name={t.icon} size={12} />
                          </span>
                        )}
                        {t?.name || r.tool_id}
                      </div>
                      <div className="admin-bar-track"><div className="admin-bar-fill" style={{ width: pct + '%', background: cat ? `linear-gradient(90deg, ${cat.tint}, ${cat.tint}cc)` : undefined }} /></div>
                      <div className="admin-bar-v">{r.opens} <span className="cmp-meta">· {r.drop_off_pct != null ? r.drop_off_pct + '% drop' : '—'}</span></div>
                    </div>
                  );
                })}
              </div>
            )}
          </div>
          <div className="dash-card">
            <div className="dash-card-head"><h3>Recent admin activity</h3><button className="dash-linkbtn" onClick={() => setSection('activity')}>See all →</button></div>
            {activityLog.length === 0 ? (
              <div className="dash-empty">Nothing yet.</div>
            ) : (
              <div className="dash-table">
                {activityLog.slice(0, 6).map((a) => (
                  <div key={a.id} className="dash-row-item" style={{ cursor: 'default' }}>
                    <div className="dash-row-icon" style={{ background: 'var(--id-brand-blue-soft)', color: 'var(--id-brand-blue)' }}>
                      <window.Icon name="bolt" size={14} />
                    </div>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div className="dash-row-title">{a.action} · <span className="cmp-meta">{a.target}</span></div>
                      <div className="dash-row-sub">{new Date(a.created_at).toLocaleString()}</div>
                    </div>
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>
      </>
    );
  };

  // ---------- Tools (full management) ----------
  const upsertTool = async (toolId, patch) => {
    if (!sb) return;
    const cur = toolRows[toolId] || {};
    const next = { ...cur, ...patch };
    const { error } = await sb.rpc('admin_upsert_tool', {
      p_tool_id:      toolId,
      p_name:         next.name        ?? null,
      p_description:  next.description ?? null,
      p_icon:         next.icon        ?? null,
      p_category:     next.category    ?? null,
      p_slug:         next.slug        ?? toolId,
      p_sort_order:   next.sort_order  ?? 0,
      p_featured:     next.featured    ?? false,
      p_working:      next.working     ?? true,
      p_hidden:       next.hidden      ?? false,
      p_tier:         next.tier        ?? 'free',
      p_price_cents:  next.price_cents ?? 0,
      p_active:       next.active      ?? true,
      p_requires_auth: next.requires_auth ?? false,
    });
    if (error) return alert('Save failed: ' + error.message);
    setToolRows((m) => ({ ...m, [toolId]: next }));
    setPricing?.((p) => ({ ...p, [toolId]: {
      tier: next.tier, price_cents: next.price_cents, active: next.active,
      stripe_price_id: next.stripe_price_id, requires_auth: next.requires_auth === true,
    } }));
    window.refreshTools?.();
  };

  const deleteTool = async (toolId) => {
    if (!sb || !confirm(`Delete tool row "${toolId}"? The handler stays in the code; the row + pricing are removed.`)) return;
    const { error } = await sb.rpc('admin_delete_tool', { p_tool_id: toolId });
    if (error) return alert(error.message);
    setToolRows((m) => { const n = { ...m }; delete n[toolId]; return n; });
    window.refreshTools?.();
  };

  const [toolFilter, setToolFilter] = React.useState('all');         // category
  const [toolTierFilter, setToolTierFilter] = React.useState('all'); // tier
  const [toolStatusFilter, setToolStatusFilter] = React.useState('all');
  const [toolSearch, setToolSearch] = React.useState('');

  const renderTools = () => {
    // Merge baked-in tools (window.TOOLS) with DB rows so the list shows
    // every known tool — including ones that don't yet have a tool_pricing
    // row. When the admin edits an unbacked tool, upsertTool creates the row.
    const merged = {};
    for (const t of window.TOOLS || []) {
      merged[t.id] = {
        tool_id:    t.id,
        name:       t.name,
        description: t.desc,
        icon:       t.icon,
        category:   t.cat,
        slug:       t.id,
        sort_order: 999,
        featured:   t.featured === true,
        working:    t.working === true,
        hidden:     false,
        tier:       'free',
        price_cents: 0,
        active:     true,
        stripe_price_id: null,
        group:      t.group,
        _baked:     true,
      };
    }
    for (const [id, row] of Object.entries(toolRows)) {
      merged[id] = { ...(merged[id] || {}), ...row, _baked: false };
    }
    const all = Object.values(merged);

    const counts = all.reduce((acc, r) => {
      acc.total += 1;
      const tier = r.tier || 'free';
      acc[tier] = (acc[tier] || 0) + 1;
      if (r.featured) acc.featured += 1;
      if (r.hidden)   acc.hidden += 1;
      if (r.working)  acc.live += 1;
      if (r.active === false) acc.inactive += 1;
      if (r.requires_auth) acc.auth += 1;
      return acc;
    }, { total: 0, free: 0, premium: 0, featured: 0, hidden: 0, live: 0, inactive: 0, auth: 0 });

    const rows = all
      .filter((r) => toolFilter === 'all' || r.category === toolFilter)
      .filter((r) => toolTierFilter === 'all' || (r.tier || 'free') === toolTierFilter)
      .filter((r) => {
        switch (toolStatusFilter) {
          case 'all':      return true;
          case 'active':   return r.active !== false;
          case 'inactive': return r.active === false;
          case 'featured': return r.featured === true;
          case 'hidden':   return r.hidden === true;
          case 'live':     return r.working === true;
          case 'auth':     return r.requires_auth === true;
          default: return true;
        }
      })
      .filter((r) => {
        if (!toolSearch) return true;
        const q = toolSearch.toLowerCase();
        return (r.name || '').toLowerCase().includes(q)
            || (r.tool_id || '').toLowerCase().includes(q)
            || (r.description || '').toLowerCase().includes(q);
      })
      .sort((a, b) => (a.sort_order ?? 999) - (b.sort_order ?? 999));

    const editingRow = editingToolId ? merged[editingToolId] : null;

    const C = window.MMCharts || {};
    const DonutChart = C.DonutChart;

    // Category mix donut (active, non-hidden tools)
    const catSlices = window.CATEGORIES.map((cat) => ({
      label: cat.name,
      value: all.filter((r) => r.category === cat.id && r.hidden !== true && r.active !== false).length,
      color: cat.tint,
    })).filter((s) => s.value > 0);

    // Insights buckets (joined with tool metadata)
    const renderBucket = (bucket, title, color, icon, format) => {
      const rows = insights.filter((r) => r.bucket === bucket).slice(0, 6);
      return (
        <div className="adm-insight-card" style={{ borderColor: color + '40' }}>
          <div className="adm-insight-head" style={{ color }}>
            <window.Icon name={icon} size={14} strokeWidth={2.2} />
            <span>{title}</span>
          </div>
          {rows.length === 0 ? (
            <div className="adm-insight-empty">No tools in this bucket.</div>
          ) : (
            <ol className="adm-insight-list">
              {rows.map((r) => {
                const t = window.TOOLS.find((x) => x.id === r.tool_id);
                const cat = t ? window.CATEGORIES.find((c) => c.id === t.cat) : null;
                return (
                  <li key={r.tool_id} onClick={() => setEditingToolId(r.tool_id)}>
                    <span className="adm-insight-icon" style={{ background: cat?.soft, color: cat?.tint }}>
                      <window.Icon name={t?.icon || 'doc'} size={12} />
                    </span>
                    <span className="adm-insight-name">{t?.name || r.tool_id}</span>
                    <span className="adm-insight-stat" style={{ color }}>{format(r)}</span>
                  </li>
                );
              })}
            </ol>
          )}
        </div>
      );
    };

    return (
      <>
        <h2 className="dash-h2">Tools · {counts.total}</h2>
        <p className="dash-sub">
          Click any row to open the full editor. Quick toggles inline; deletes and Stripe IDs in the drawer.
        </p>

        {/* Insights row — only renders if we have any data */}
        {insights.length > 0 && (
          <div className="adm-insight-grid">
            {renderBucket('engaged', 'Most engaged', '#10805a', 'check', (r) => `${r.completes} done`)}
            {renderBucket('growing', 'Gaining traction', '#2563eb', 'sparkle', (r) => (r.delta_pct > 0 ? '+' : '') + r.delta_pct + '%')}
            {renderBucket('problem', 'High drop-off',   '#b8700a', 'rotate', (r) => r.drop_off_pct + '% drop')}
            {renderBucket('errors',  'Most errors',     '#c8321f', 'x',      (r) => r.errors + ' errs')}
          </div>
        )}

        {/* Category mix */}
        {DonutChart && catSlices.length > 0 && (
          <div className="dash-card" style={{ marginTop: 14, marginBottom: 14 }}>
            <div className="dash-card-head"><h3>Category mix</h3><span className="cmp-meta">visible & active tools only</span></div>
            <DonutChart slices={catSlices} size={180}
              centerLabel={catSlices.reduce((s, x) => s + x.value, 0).toString()}
              centerSub="tools" />
          </div>
        )}

        {/* Compact KPI strip */}
        <div className="admin-tool-counts">
          <span><strong>{counts.total}</strong> total</span>
          <span><strong>{counts.free}</strong> free</span>
          <span className="atc-prem"><strong>{counts.premium}</strong> premium</span>
          <span className="atc-auth"><strong>{counts.auth}</strong> 🔐 sign-in</span>
          <span className="atc-feat"><strong>{counts.featured}</strong> ★ featured</span>
          <span className="atc-live"><strong>{counts.live}</strong> live demo</span>
          <span className="atc-hid"><strong>{counts.hidden}</strong> hidden</span>
          <span className="atc-off"><strong>{counts.inactive}</strong> inactive</span>
        </div>

        {/* Filters: category */}
        <div className="admin-filter-block">
          <span className="admin-filter-l">Category</span>
          <div className="filter-row" style={{ flex: 1 }}>
            <button className={`filter-pill ${toolFilter === 'all' ? 'active' : ''}`} onClick={() => setToolFilter('all')}>All · {counts.total}</button>
            {window.CATEGORIES.map((c) => {
              const n = all.filter((r) => r.category === c.id).length;
              return (
                <button key={c.id} className={`filter-pill ${toolFilter === c.id ? 'active' : ''}`}
                        onClick={() => setToolFilter(c.id)}
                        style={toolFilter === c.id ? { background: c.tint, boxShadow: `0 4px 12px -4px ${c.tint}80` } : {}}>
                  {c.name} · {n}
                </button>
              );
            })}
          </div>
        </div>

        {/* Filters: tier */}
        <div className="admin-filter-block">
          <span className="admin-filter-l">Tier</span>
          <div className="filter-row" style={{ flex: 1 }}>
            {[
              { id: 'all',     label: 'All',       n: counts.total },
              { id: 'free',    label: 'Free',      n: counts.free },
              { id: 'premium', label: 'Premium',   n: counts.premium },
            ].map((t) => (
              <button key={t.id} className={`filter-pill ${toolTierFilter === t.id ? 'active' : ''}`}
                      onClick={() => setToolTierFilter(t.id)}
                      style={toolTierFilter === t.id && t.id === 'premium' ? { background: '#2563eb' } : {}}>
                {t.label} · {t.n}
              </button>
            ))}
          </div>
        </div>

        {/* Filters: status */}
        <div className="admin-filter-block">
          <span className="admin-filter-l">Status</span>
          <div className="filter-row" style={{ flex: 1 }}>
            {[
              { id: 'all',      label: 'All',      n: counts.total },
              { id: 'active',   label: 'Active',   n: counts.total - counts.inactive },
              { id: 'featured', label: '★ Featured', n: counts.featured },
              { id: 'auth',     label: '🔐 Sign-in', n: counts.auth },
              { id: 'live',     label: 'Live demo', n: counts.live },
              { id: 'hidden',   label: 'Hidden',   n: counts.hidden },
              { id: 'inactive', label: 'Inactive', n: counts.inactive },
            ].map((s) => (
              <button key={s.id} className={`filter-pill ${toolStatusFilter === s.id ? 'active' : ''}`}
                      onClick={() => setToolStatusFilter(s.id)}>
                {s.label} · {s.n}
              </button>
            ))}
          </div>
        </div>

        <div className="admin-filter-block">
          <span className="admin-filter-l">Search</span>
          <input className="mini-input" style={{ flex: 1 }}
                 placeholder="Name, id or description…"
                 value={toolSearch} onChange={(e) => setToolSearch(e.target.value)} />
          {(toolFilter !== 'all' || toolTierFilter !== 'all' || toolStatusFilter !== 'all' || toolSearch) && (
            <button className="filter-pill" onClick={() => { setToolFilter('all'); setToolTierFilter('all'); setToolStatusFilter('all'); setToolSearch(''); }}>
              <window.Icon name="x" size={11} /> Clear filters
            </button>
          )}
        </div>

        {/* Result count */}
        <div className="cmp-meta" style={{ margin: '6px 0 8px' }}>
          Showing {rows.length} of {counts.total} tools{rows.length === 0 ? ' — adjust filters above' : ''}.
        </div>

        {/* Tool list */}
        <div className="admin-tool-list">
          <div className="admin-tool-list-row admin-tool-list-head">
            <div></div>
            <div>Tool</div>
            <div>Category</div>
            <div>Tier</div>
            <div>Status</div>
            <div>Order</div>
            <div></div>
          </div>
          {rows.length === 0 ? (
            <div className="dash-empty" style={{ padding: 24 }}>
              No tools match. <button className="dash-linkbtn" onClick={() => { setToolFilter('all'); setToolTierFilter('all'); setToolStatusFilter('all'); setToolSearch(''); }}>Clear filters</button>
            </div>
          ) : rows.map((r) => {
            const cat = window.CATEGORIES.find((c) => c.id === r.category);
            const tier = r.tier || 'free';
            return (
              <div key={r.tool_id}
                   className={`admin-tool-list-row ${r.hidden ? 'is-hidden' : ''} ${r.active === false ? 'is-inactive' : ''}`}
                   onClick={() => setEditingToolId(r.tool_id)}
                   role="button" tabIndex={0}
                   onKeyDown={(e) => { if (e.key === 'Enter') setEditingToolId(r.tool_id); }}>
                <div className="atlr-icon" style={{ background: cat?.soft || 'var(--id-surface-alt)', color: cat?.tint || 'var(--id-text-muted)' }}>
                  <window.Icon name={r.icon || 'doc'} size={14} />
                </div>
                <div className="atlr-name">
                  <div className="atlr-title">{r.name || r.tool_id}</div>
                  <code className="atlr-id">{r.tool_id}{r.group ? ` · ${r.group}` : ''}</code>
                </div>
                <div className="atlr-cat">
                  <span className="atlr-cat-dot" style={{ background: cat?.tint || '#94a3b8' }} />
                  {cat?.name || r.category || '—'}
                </div>
                <div className="atlr-tier">
                  {tier === 'free'
                    ? <span className="cmp-meta">Free</span>
                    : (
                      <>
                        <span className={`dash-tier-chip dash-tier-${tier}`} style={{ fontSize: 10, padding: '2px 8px' }}>{tier.toUpperCase()}</span>
                        {r.price_cents > 0 && <div className="atlr-price">{ADMIN_FMT_USD(r.price_cents)}</div>}
                      </>
                    )}
                </div>
                <div className="atlr-status">
                  {r.active === false && <span className="atlr-chip atlr-off">OFF</span>}
                  {r.requires_auth && <span className="atlr-chip atlr-auth" title="Requires sign-in">🔐 AUTH</span>}
                  {r.featured && <span className="atlr-chip atlr-feat" title="Featured">★</span>}
                  {r.working && <span className="atlr-chip atlr-live" title="Live demo">LIVE</span>}
                  {r.hidden && <span className="atlr-chip atlr-hid">HIDDEN</span>}
                  {r._baked && <span className="atlr-chip atlr-baked" title="Baked-in only — no DB row yet. Saving any field will create one.">CODE</span>}
                  {!r.featured && !r.working && !r.hidden && !r._baked && !r.requires_auth && r.active !== false && (
                    <span className="cmp-meta" style={{ fontSize: 11 }}>—</span>
                  )}
                </div>
                <div className="atlr-sort">{r.sort_order ?? 0}</div>
                <div className="atlr-cta">
                  <window.Icon name="arrow" size={14} />
                </div>
              </div>
            );
          })}
        </div>

        {editingRow && (
          <ToolDetailModal
            row={editingRow}
            analytics={analyticsTools.find((a) => a.tool_id === editingToolId) || null}
            onClose={() => setEditingToolId(null)}
            onPatch={(patch) => upsertTool(editingToolId, patch)}
            onDelete={() => { deleteTool(editingToolId); setEditingToolId(null); }}
          />
        )}

        {/* Sports configuration — appears under the list when filtering to
            the Sports category. Held in a <details> so admins can collapse it. */}
        {toolFilter === 'sports' && (
          <details className="admin-sports-block" open>
            <summary>
              <span className="asb-icon"><window.Icon name="run" size={14} strokeWidth={2.2} /></span>
              <span className="asb-title">Sports configuration</span>
              <span className="asb-sub">Hero copy, groups, KPI overview · saved to <code>settings</code></span>
              <window.Icon name="chevron" size={14} className="asb-chevron" />
            </summary>
            <div className="admin-sports-body">
              {renderSportsBody()}
            </div>
          </details>
        )}
      </>
    );
  };

  // ---------- Analytics (real) ----------
  const renderAnalytics = () => {
    if (analyticsKpis === null) return <div style={{ textAlign: 'center', padding: '60px 20px' }}><div className="cmp-meta">Loading...</div></div>;
    const k = analyticsKpis;
    const maxBucket = Math.max(1, ...analyticsHourly.map((b) => Number(b.opens) + Number(b.completes)));
    return (
      <>
        <h2 className="dash-h2">Analytics</h2>
        <div className="filter-row" style={{ marginBottom: 12 }}>
          {[7, 30, 90].map((d) => (
            <button key={d} className={`filter-pill ${analyticsRange === d ? 'active' : ''}`} onClick={() => setAnalyticsRange(d)}>{d}d</button>
          ))}
          <span className="cmp-meta" style={{ alignSelf: 'center', marginLeft: 'auto' }}>
            {k ? `${k.unique_users} users · ${k.unique_tools} tools` : ''}
          </span>
        </div>

        <div className="dash-kpis">
          <KPI label="Opens"     value={Number(k?.total_opens || 0)}     icon="bolt" />
          <KPI label="Completes" value={Number(k?.total_completes || 0)} icon="check" />
          <KPI label="Errors"    value={Number(k?.total_errors || 0)}    icon="x" />
          <KPI label="Drop-off"  value={k?.avg_drop_off != null ? k.avg_drop_off + '%' : '—'} icon="rotate" />
          <KPI label="Avg ms"    value={k?.avg_ms != null ? Math.round(k.avg_ms) : '—'} icon="scan" />
        </div>

        <div className="dash-card" style={{ marginTop: 14 }}>
          <div className="dash-card-head"><h3>Hourly traffic · last {Math.min(analyticsRange, 14)}d</h3></div>
          {analyticsHourly.length === 0 ? <div className="dash-empty">No traffic in this window.</div> : (
            <div className="admin-spark">
              {analyticsHourly.map((b) => {
                const total = Number(b.opens) + Number(b.completes);
                const h = Math.max(2, Math.round((total / maxBucket) * 100));
                const completePct = total === 0 ? 0 : Math.round(Number(b.completes) / total * 100);
                return (
                  <div key={b.bucket} className="admin-spark-col" title={`${new Date(b.bucket).toLocaleString()} · ${b.opens} opens · ${b.completes} completes`}>
                    <div className="admin-spark-bar" style={{ height: h + '%' }}>
                      <div className="admin-spark-complete" style={{ height: completePct + '%' }} />
                    </div>
                  </div>
                );
              })}
            </div>
          )}
          <div className="cmp-meta" style={{ marginTop: 8, display: 'flex', gap: 14 }}>
            <span><span className="admin-legend-swatch" style={{ background: 'var(--id-brand-blue)' }} /> Opens</span>
            <span><span className="admin-legend-swatch" style={{ background: '#10805a' }} /> Completed</span>
          </div>
        </div>

        <div className="dash-card" style={{ marginTop: 14 }}>
          <div className="dash-card-head"><h3>Per-tool breakdown</h3></div>
          {analyticsTools.length === 0 ? <div className="dash-empty">No usage yet.</div> : (
            <div className="admin-pricing-table" style={{ gridTemplateColumns: 'unset' }}>
              <div className="admin-fn-row admin-fn-head">
                <div>Tool</div><div>Opens</div><div>Completed</div><div>Drop-off</div><div>Avg ms</div><div>p95 ms</div><div>Users</div>
              </div>
              {analyticsTools.map((r) => {
                const t = window.TOOLS.find((x) => x.id === r.tool_id);
                return (
                  <div key={r.tool_id} className="admin-fn-row">
                    <div className="dash-row-title">{t?.name || r.tool_id}</div>
                    <div>{r.opens}</div>
                    <div>{r.completes}</div>
                    <div className={Number(r.drop_off_pct || 0) > 50 ? 'admin-warn' : ''}>
                      {r.drop_off_pct != null ? r.drop_off_pct + '%' : '—'}
                    </div>
                    <div>{r.avg_ms != null ? Math.round(r.avg_ms) : '—'}</div>
                    <div>{r.p95_ms != null ? Math.round(r.p95_ms) : '—'}</div>
                    <div>{r.unique_users}</div>
                  </div>
                );
              })}
            </div>
          )}
        </div>
      </>
    );
  };

  // ---------- Functions (Edge function monitoring + API key rate limits) ----------
  const setKeyLimits = async (key) => {
    const daily  = prompt(`Daily limit for ${key.label || key.id.slice(0,8)} (current: ${key.daily_limit}):`, key.daily_limit);
    const hourly = prompt(`Hourly limit (current: ${key.hourly_limit}):`, key.hourly_limit);
    if (!sb) return;
    const { error } = await sb.rpc('admin_set_api_key_limits', {
      p_id: key.id,
      p_daily_limit: daily ? Number(daily) : null,
      p_hourly_limit: hourly ? Number(hourly) : null,
    });
    if (error) return alert(error.message);
    sb.rpc('admin_api_keys', { p_days: fnRange }).then(({ data }) => setApiKeysList(data || []));
  };
  const banKey = async (key) => {
    if (!sb) return;
    const banned = !key.is_banned;
    const reason = banned ? prompt('Reason (shown in admin only):') : null;
    const { error } = await sb.rpc('admin_ban_api_key', { p_id: key.id, p_banned: banned, p_reason: reason });
    if (error) return alert(error.message);
    sb.rpc('admin_api_keys', { p_days: fnRange }).then(({ data }) => setApiKeysList(data || []));
  };

  const renderFunctions = () => {
    if (fnStats === null) return <div style={{ textAlign: 'center', padding: '60px 20px' }}><div className="cmp-meta">Loading...</div></div>;
    const totalCalls = fnStats.reduce((s, r) => s + Number(r.calls || 0), 0);
    const totalErrors = fnStats.reduce((s, r) => s + Number(r.errors || 0), 0);
    const overallErr = totalCalls === 0 ? 0 : Math.round(totalErrors / totalCalls * 1000) / 10;
    const maxCalls = Math.max(1, ...fnStats.map((r) => Number(r.calls)));

    return (
      <>
        <h2 className="dash-h2">Edge Functions & API Keys</h2>
        <p className="dash-sub">Per-function call volume, error rate and latency. API keys with their rate-limit headroom.</p>
        <div className="filter-row" style={{ marginBottom: 12 }}>
          {[1, 7, 30].map((d) => (
            <button key={d} className={`filter-pill ${fnRange === d ? 'active' : ''}`} onClick={() => setFnRange(d)}>{d}d</button>
          ))}
        </div>

        <div className="dash-kpis">
          <KPI label="Total calls"  value={totalCalls}  icon="bolt" />
          <KPI label="Errors"       value={totalErrors} icon="x" />
          <KPI label="Error rate"   value={overallErr + '%'} icon="rotate" />
          <KPI label="Distinct fns" value={fnStats.length} icon="grid" />
          <KPI label="Active keys"  value={apiKeysList.filter((k) => !k.is_banned).length} icon="lock" />
          <KPI label="Banned keys"  value={apiKeysList.filter((k) => k.is_banned).length} icon="x" />
        </div>

        <div className="dash-card" style={{ marginTop: 14 }}>
          <div className="dash-card-head"><h3>Per-function stats</h3></div>
          {fnStats.length === 0 ? <div className="dash-empty">No calls in this window. (Edge functions need to record into public.function_calls — see _shared/auth.ts.)</div> : (
            <div className="admin-bars">
              {fnStats.map((r) => {
                const pct = Math.round((Number(r.calls) / maxCalls) * 100);
                const errPct = Number(r.error_rate || 0);
                return (
                  <div key={r.function} className="admin-bar-row">
                    <div className="admin-bar-label">
                      <code style={{ fontFamily: 'var(--id-font-mono)', fontSize: 12 }}>{r.function}</code>
                      {errPct > 5 && <span className="admin-warn" style={{ marginLeft: 6, fontSize: 11 }}>{errPct}% err</span>}
                    </div>
                    <div className="admin-bar-track"><div className="admin-bar-fill" style={{ width: pct + '%', background: errPct > 10 ? '#c8321f' : undefined }} /></div>
                    <div className="admin-bar-v">{r.calls} <span className="cmp-meta">· {r.avg_ms || 0}ms · p95 {r.p95_ms || 0}</span></div>
                  </div>
                );
              })}
            </div>
          )}
        </div>

        <div className="dash-card" style={{ marginTop: 14 }}>
          <div className="dash-card-head"><h3>API keys · rate limits & abuse controls</h3></div>
          {apiKeysList.length === 0 ? <div className="dash-empty">No API keys created yet.</div> : (
            <div className="admin-pricing-table" style={{ gridTemplateColumns: 'unset' }}>
              <div className="admin-fn-row admin-fn-head">
                <div>Owner</div><div>Label</div><div>24h calls</div><div>Window calls</div><div>Errors</div><div>Limits (h/d)</div><div>Status</div>
              </div>
              {apiKeysList.map((k) => (
                <div key={k.id} className="admin-fn-row" style={{ opacity: k.is_banned ? 0.65 : 1 }}>
                  <div style={{ minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    <div className="dash-row-title" style={{ fontSize: 13 }}>{k.email || '—'}</div>
                    <div className="cmp-meta" style={{ fontSize: 10, fontFamily: 'var(--id-font-mono)' }}>{String(k.id).slice(0, 8)}…</div>
                  </div>
                  <div className="cmp-meta">{k.label || '—'}</div>
                  <div>{k.calls_24h}</div>
                  <div>{k.calls_7d}</div>
                  <div className={Number(k.errors_7d) > 0 ? 'admin-warn' : ''}>{k.errors_7d}</div>
                  <div className="cmp-meta" style={{ fontFamily: 'var(--id-font-mono)', fontSize: 11 }}>
                    {k.hourly_limit}/{k.daily_limit}
                  </div>
                  <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                    <button className="filter-pill" onClick={() => setKeyLimits(k)}>Limits</button>
                    <button className="filter-pill" style={{ color: k.is_banned ? '#10805a' : '#c8321f' }} onClick={() => banKey(k)}>
                      {k.is_banned ? 'Unban' : 'Ban'}
                    </button>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      </>
    );
  };

  // ---------- Revenue ----------
  const renderRevenue = () => {
    if (revKpis === null) return <div style={{ textAlign: 'center', padding: '60px 20px' }}><div className="cmp-meta">Loading...</div></div>;
    const k = revKpis || {};
    const maxDay = Math.max(1, ...revDaily.map((d) => Number(d.gross_cents) || 0));
    return (
      <>
        <h2 className="dash-h2">Revenue</h2>
        <p className="dash-sub">Stripe payments aggregated from public.payments. Refunds deducted from gross.</p>
        <div className="filter-row" style={{ marginBottom: 12 }}>
          {[7, 30, 90, 365].map((d) => (
            <button key={d} className={`filter-pill ${revRange === d ? 'active' : ''}`} onClick={() => setRevRange(d)}>{d}d</button>
          ))}
        </div>

        <div className="dash-kpis">
          <KPI label={`Gross (${revRange}d)`}    value={ADMIN_FMT_USD(k.gross_cents)}  icon="bolt" />
          <KPI label="Payments"                  value={Number(k.payments || 0)}        icon="check" />
          <KPI label="Paying users"              value={Number(k.paying_users || 0)}    icon="sparkle" />
          <KPI label="Refunds"                   value={Number(k.refunds || 0)}         icon="rotate" />
          <KPI label="Active premium"            value={Number(k.active_premium || 0)}  icon="lock" />
          <KPI label="Conversion"                value={(k.conversion_pct ?? 0) + '%'}  icon="grid" />
        </div>

        <div className="dash-card" style={{ marginTop: 14 }}>
          <div className="dash-card-head"><h3>Daily gross · {revRange}d</h3></div>
          {revDaily.length === 0 ? <div className="dash-empty">No payments yet. Revenue will appear once Stripe webhooks fire.</div> : (
            <div className="admin-spark">
              {revDaily.map((d) => {
                const h = Math.max(2, Math.round((Number(d.gross_cents) / maxDay) * 100));
                return (
                  <div key={d.day} className="admin-spark-col" title={`${d.day} · ${ADMIN_FMT_USD(d.gross_cents)} · ${d.payments} pmts`}>
                    <div className="admin-spark-bar" style={{ height: h + '%', background: 'var(--id-brand-blue)' }} />
                  </div>
                );
              })}
            </div>
          )}
        </div>

        {revenueByTier.length > 0 && (
          <div className="dash-card" style={{ marginTop: 14 }}>
            <div className="dash-card-head"><h3>Revenue by tier · {revRange}d</h3></div>
            <div className="admin-pricing-table" style={{ gridTemplateColumns: 'unset' }}>
              <div className="admin-fn-row admin-fn-head">
                <div>Tier</div><div>Gross</div><div>Payments</div><div>Paying users</div><div>ARPU</div>
              </div>
              {revenueByTier.map((t) => (
                <div key={t.tier} className="admin-fn-row">
                  <div>
                    <span className={`dash-tier-chip dash-tier-${t.tier}`} style={{ fontSize: 10, padding: '2px 8px' }}>
                      {t.tier.toUpperCase()}
                    </span>
                  </div>
                  <div>{ADMIN_FMT_USD(t.gross_cents)}</div>
                  <div>{t.payments}</div>
                  <div>{t.paying_users}</div>
                  <div>{ADMIN_FMT_USD(t.arpu_cents)}</div>
                </div>
              ))}
            </div>
          </div>
        )}

        <div className="dash-card" style={{ marginTop: 14 }}>
          <div className="dash-card-head"><h3>Recent payments</h3></div>
          {recentPayments.length === 0 ? <div className="dash-empty">No payments yet.</div> : (
            <div className="dash-table">
              {recentPayments.map((p) => (
                <div key={p.id} className="dash-row-item" style={{ cursor: 'default' }}>
                  <div className="dash-row-icon" style={{ background: 'var(--id-brand-blue-soft)', color: 'var(--id-brand-blue)' }}>
                    <window.Icon name="bolt" size={14} />
                  </div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div className="dash-row-title">
                      {ADMIN_FMT_USD(p.amount_cents)} <span className={`dash-tier-chip dash-tier-${p.tier}`} style={{ fontSize: 9, padding: '2px 6px', marginLeft: 6 }}>{(p.tier || '').toUpperCase()}</span>
                      {p.status === 'refunded' && <span className="admin-warn" style={{ marginLeft: 6, fontSize: 11 }}>REFUNDED</span>}
                    </div>
                    <div className="dash-row-sub">{p.email || '—'} · {new Date(p.created_at).toLocaleString()}</div>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      </>
    );
  };

  // ---------- Users ----------
  const [userSearch, setUserSearch] = React.useState('');
  const [userDetail, setUserDetail] = React.useState(null);
  const reloadUsers = () => sb?.rpc('admin_list_users', { p_limit: 200, p_offset: 0 }).then(({ data }) => setUsers(data || []));
  const openUserDetail = async (u) => {
    if (!sb) return;
    setUserDetail({ loading: true, user: u });
    const [{ data: detailArr }, { data: stats }] = await Promise.all([
      sb.rpc('admin_user_detail', { p_user_id: u.user_id }),
      sb.rpc('admin_user_tool_stats', { p_user_id: u.user_id, p_days: 90 }),
    ]);
    setUserDetail({ user: u, detail: (detailArr || [])[0] || null, stats: stats || [] });
  };
  const setUserRole = async (email, role) => {
    if (!sb) return;
    const { error } = await sb.rpc('admin_set_user_role', { p_email: email, p_role: role });
    if (error) return alert(error.message);
    reloadUsers();
  };
  const grantTier = async (email, tier) => {
    if (!sb) return;
    const days = prompt(`Days to grant ${tier} for ${email} (blank = forever):`);
    const expires = days ? new Date(Date.now() + Number(days) * 86400_000).toISOString() : null;
    const { error } = await sb.rpc('admin_grant_entitlement', { p_email: email, p_tier: tier, p_expires_at: expires, p_reason: 'Admin grant' });
    if (error) return alert(error.message);
    reloadUsers();
  };
  const revokeTier = async (email, tier) => {
    if (!sb || !confirm(`Revoke ${tier} from ${email}?`)) return;
    const { error } = await sb.rpc('admin_revoke_entitlement', { p_email: email, p_tier: tier });
    if (error) return alert(error.message);
    reloadUsers();
  };

  const renderUsers = () => {
    const C = window.MMCharts || {};
    const KPICard = C.KPICard;
    const TimeseriesChart = C.TimeseriesChart;
    const DonutChart = C.DonutChart;

    const filtered = users.filter((u) => !userSearch || (u.email || '').toLowerCase().includes(userSearch.toLowerCase()));

    // Tier composition donut (counts by entitlement state)
    const premiumCount = users.filter((u) => (u.tiers || []).includes('premium')).length;
    const adminCount   = users.filter((u) => u.role === 'admin').length;
    const freeCount    = Math.max(0, users.length - premiumCount);

    // CSV export of currently-visible users
    const exportCsv = () => C.downloadCsv?.(`users-${new Date().toISOString().slice(0,10)}`,
      filtered.map((u) => ({
        user_id:    u.user_id,
        email:      u.email,
        role:       u.role,
        tiers:      (u.tiers || []).join('|') || 'none',
        created_at: u.created_at,
      })));

    // Daily new-user series + cumulative
    const newSeries = dailySeries.map((d) => Number(d.new_users) || 0);
    const totalSeries = (() => {
      let acc = (actives?.total_users || users.length || 0) - newSeries.reduce((s, x) => s + x, 0);
      return newSeries.map((v) => (acc += v));
    })();

    return (
      <>
        <div className="adm-h2-row">
          <h2 className="dash-h2" style={{ margin: 0 }}>Users · {users.length}</h2>
          <div className="filter-row" style={{ marginBottom: 0 }}>
            {[7, 30, 90].map((d) => (
              <button key={d}
                      className={`filter-pill ${analyticsRange === d ? 'active' : ''}`}
                      onClick={() => setAnalyticsRange(d)}>{d}d</button>
            ))}
          </div>
        </div>
        <p className="dash-sub">
          Active rollups + new sign-up trend over the selected range. Click a row to drill into per-user activity.
        </p>

        {/* User KPIs */}
        <div className="mm-kpi-grid">
          {KPICard ? <>
            <KPICard label="DAU"           value={actives?.dau ?? '—'} sub="active in last 24h" color="#7c3aed" />
            <KPICard label="WAU"           value={actives?.wau ?? '—'} sub="active in last 7d"  color="#2563eb" />
            <KPICard label="MAU"           value={actives?.mau ?? '—'} sub="active in last 30d" color="#0e9488" />
            <KPICard label="Total users"   value={(actives?.total_users || users.length).toLocaleString()} sub="lifetime accounts" />
            <KPICard label={`New (${analyticsRange}d)`}
              value={Number(overviewV2?.cur_new_users || 0).toLocaleString()}
              current={Number(overviewV2?.cur_new_users || 0)}
              previous={Number(overviewV2?.prev_new_users || 0)}
              sparkline={newSeries} color="#16a34a" />
            <KPICard label="Premium users" value={premiumCount} sub="active entitlement" color="#2563eb" />
            <KPICard label="Admins"        value={adminCount} sub="user_roles.role='admin'" />
          </> : null}
        </div>

        {/* Growth chart + tier donut */}
        <div className="dash-row" style={{ marginTop: 14 }}>
          <div className="dash-card">
            <div className="dash-card-head"><h3>User growth · {analyticsRange}d</h3></div>
            {TimeseriesChart && dailySeries.length > 0 ? (
              <TimeseriesChart
                series={[
                  { id: 'new',    label: 'New sign-ups (daily)', values: newSeries,   color: '#16a34a' },
                  { id: 'cumul',  label: 'Total users (cumulative)', values: totalSeries, color: '#2563eb' },
                ]}
                labels={dailySeries.map((d) => new Date(d.day).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }))}
                height={220}
              />
            ) : <div className="dash-empty">No sign-ups in this window.</div>}
          </div>
          <div className="dash-card">
            <div className="dash-card-head"><h3>Tier mix</h3></div>
            {DonutChart && users.length > 0 ? (
              <DonutChart
                size={180}
                slices={[
                  { label: 'Free',    value: freeCount,    color: '#94a3b8' },
                  { label: 'Premium', value: premiumCount, color: '#2563eb' },
                ]}
                centerLabel={users.length.toString()}
                centerSub="users"
              />
            ) : <div className="dash-empty">No users yet.</div>}
          </div>
        </div>

        <div className="adm-toolbar" style={{ marginTop: 14 }}>
          <input className="mini-input" style={{ flex: 1 }}
                 placeholder="Search by email…"
                 value={userSearch} onChange={(e) => setUserSearch(e.target.value)} />
          <button className="filter-pill" onClick={exportCsv} disabled={!filtered.length}>
            <window.Icon name="download" size={12} /> Export CSV ({filtered.length})
          </button>
        </div>
        <div className="admin-users-table">
          <div className="admin-users-row admin-users-head">
            <div>Email</div><div>Joined</div><div>Role</div><div>Entitlements</div><div>Actions</div>
          </div>
          {filtered.map((u) => (
            <div key={u.user_id} className="admin-users-row admin-users-row-clickable" onClick={(e) => { if (!e.target.closest('button,select,input')) openUserDetail(u); }}>
              <div style={{ minWidth: 0 }}>
                <div className="dash-row-title" style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{u.email}</div>
                <div className="dash-row-sub" style={{ fontSize: 10, fontFamily: 'var(--id-font-mono)' }}>{String(u.user_id).slice(0, 8)}…</div>
              </div>
              <div className="cmp-meta">{new Date(u.created_at).toLocaleDateString()}</div>
              <div><span className={`admin-role-chip admin-role-${u.role}`}>{u.role.toUpperCase()}</span></div>
              <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                {(u.tiers || []).length === 0 ? <span className="cmp-meta">none</span> :
                  (u.tiers || []).map((t) => <span key={t} className={`dash-tier-chip dash-tier-${t}`} style={{ fontSize: 10, padding: '2px 8px' }}>{t.toUpperCase()}</span>)
                }
              </div>
              <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                <button className="filter-pill" onClick={() => setUserRole(u.email, u.role === 'admin' ? 'user' : 'admin')}>
                  {u.role === 'admin' ? 'Revoke admin' : 'Make admin'}
                </button>
                {(u.tiers || []).includes('premium')
                  ? <button className="filter-pill" onClick={() => revokeTier(u.email, 'premium')}>− Premium</button>
                  : <button className="filter-pill" onClick={() => grantTier(u.email, 'premium')}>+ Premium</button>}
              </div>
            </div>
          ))}
        </div>
      </>
    );
  };

  // ---------- Sports config (rendered inline inside the Tools tab when the
  // category filter is 'sports'). Returns just the body — Tools provides the
  // surrounding heading.
  const renderSportsBody = () => {
    const sportTools = window.TOOLS.filter((t) => t.cat === 'sports');
    const groups = window.SPORT_GROUPS || [];
    const groupCfg = Array.isArray(sportsConfig['sports.groups']) ? sportsConfig['sports.groups'] : null;
    const effectiveGroups = (groupCfg || groups).map((g) => {
      const baked = groups.find((x) => x.id === g.id);
      return { ...baked, ...g };
    });

    // Aggregate analytics → totals + per-group breakdown.
    const totals = sportsTools.reduce((acc, r) => {
      acc.opens += Number(r.opens) || 0;
      acc.completes += Number(r.completes) || 0;
      acc.errors += Number(r.errors) || 0;
      return acc;
    }, { opens: 0, completes: 0, errors: 0 });
    const dropOff = totals.opens === 0 ? null : Math.round((1 - totals.completes / totals.opens) * 1000) / 10;
    const uniqueUsers = sportsTools.reduce((max, r) => Math.max(max, Number(r.unique_users) || 0), 0);

    const groupAgg = {};
    for (const g of effectiveGroups) groupAgg[g.id] = { name: g.name, tint: g.tint, count: 0, opens: 0, completes: 0 };
    for (const t of sportTools) {
      const g = groupAgg[t.group];
      if (!g) continue;
      g.count++;
      const row = sportsTools.find((r) => r.tool_id === t.id);
      if (row) { g.opens += Number(row.opens) || 0; g.completes += Number(row.completes) || 0; }
    }
    const groupArr = Object.entries(groupAgg).map(([id, v]) => ({ id, ...v }));
    const maxGroupOpens = Math.max(1, ...groupArr.map((g) => g.opens));

    const topByOpens = [...sportsTools].sort((a, b) => Number(b.opens) - Number(a.opens)).slice(0, 12);
    const maxOpens = Math.max(1, ...topByOpens.map((r) => Number(r.opens)));

    // Admin's own saves (proxy for "engagement") — `myAllSaves` is subscribed
    // at the top of AdminPanel so the hook count never changes between renders.
    // Cross-user aggregation would need an admin RLS policy on `sync`.
    const mySavesTotal = Object.values(myAllSaves).reduce((s, list) => s + (Array.isArray(list) ? list.length : 0), 0);
    const mySavesByTool = Object.entries(myAllSaves).map(([tid, list]) => ({
      tid, count: list.length, tool: window.TOOLS.find((t) => t.id === tid),
    })).filter((r) => r.tool).sort((a, b) => b.count - a.count);

    // Settings setters
    const setHero = (key, value) => {
      saveSetting(key, value);
      setSportsConfig((c) => ({ ...c, [key]: value }));
    };
    const setGroupField = (gid, field, value) => {
      const next = effectiveGroups.map((g) => g.id === gid ? { ...g, [field]: value } : g);
      // Save the diff (only fields that differ from the baked-in defaults).
      const diff = next.map((g) => {
        const baked = groups.find((b) => b.id === g.id) || {};
        const out = { id: g.id };
        for (const k of ['name', 'desc', 'icon', 'tint']) {
          if (g[k] !== baked[k]) out[k] = g[k];
        }
        return out;
      });
      setHero('sports.groups', diff);
    };
    const resetGroups = () => {
      saveSetting('sports.groups', null);
      setSportsConfig((c) => ({ ...c, 'sports.groups': null }));
    };

    return (
      <>
        <div className="filter-row" style={{ marginBottom: 12 }}>
          <span className="cmp-meta" style={{ marginRight: 6, alignSelf: 'center', fontWeight: 700, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.06em' }}>Range</span>
          {[7, 30, 90].map((d) => (
            <button key={d} className={`filter-pill ${sportsRange === d ? 'active' : ''}`}
                    onClick={() => setSportsRange(d)}
                    style={sportsRange === d ? { background: 'var(--tt-sport)', boxShadow: '0 4px 12px -4px rgba(22,163,74,.5)' } : {}}>{d}d</button>
          ))}
          <span className="cmp-meta" style={{ alignSelf: 'center', marginLeft: 'auto' }}>
            {sportTools.length} sport tools across {effectiveGroups.length} groups
          </span>
        </div>

        <div className="dash-kpis">
          <KPI label="Sport tools"     value={sportTools.length} icon="run" />
          <KPI label="Sport opens"     value={totals.opens} icon="bolt" />
          <KPI label="Completed"       value={totals.completes} icon="check" />
          <KPI label="Drop-off"        value={dropOff != null ? dropOff + '%' : '—'} icon="rotate" />
          <KPI label="Top runners"     value={uniqueUsers} icon="grid" />
          <KPI label="Saves (this admin)" value={mySavesTotal} icon="check" />
        </div>

        <div className="dash-row">
          <div className="dash-card">
            <div className="dash-card-head">
              <h3>Top sport tools by opens</h3>
              <button className="dash-linkbtn" onClick={() => setSection('analytics')}>Full analytics →</button>
            </div>
            {topByOpens.length === 0 ? (
              <div className="dash-empty">No sport tool opens in this window yet.</div>
            ) : (
              <div className="admin-bars">
                {topByOpens.map((r) => {
                  const t = window.TOOLS.find((x) => x.id === r.tool_id);
                  if (!t) return null;
                  const grp = effectiveGroups.find((g) => g.id === t.group);
                  const tint = grp?.tint || '#16a34a';
                  const pct = Math.round((Number(r.opens) / maxOpens) * 100);
                  return (
                    <div key={r.tool_id} className="admin-bar-row">
                      <div className="admin-bar-label" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                        <div className="admin-tool-icon" style={{ background: tint + '22', color: tint, width: 22, height: 22, borderRadius: 6 }}>
                          <window.Icon name={t.icon} size={12} />
                        </div>
                        {t.name}
                        <span className="cmp-meta" style={{ fontSize: 11, marginLeft: 4 }}>· {grp?.name}</span>
                      </div>
                      <div className="admin-bar-track">
                        <div className="admin-bar-fill" style={{ width: pct + '%', background: `linear-gradient(90deg, ${tint}, ${tint}cc)` }} />
                      </div>
                      <div className="admin-bar-v">{r.opens} <span className="cmp-meta">· {Number(r.drop_off_pct) > 50 ? <span className="admin-warn">{r.drop_off_pct}% drop</span> : (r.drop_off_pct ?? '—') + '% drop'}</span></div>
                    </div>
                  );
                })}
              </div>
            )}
          </div>

          <div className="dash-card">
            <div className="dash-card-head"><h3>Group distribution</h3></div>
            <div className="admin-bars">
              {groupArr.map((g) => {
                const pct = Math.round((g.opens / maxGroupOpens) * 100);
                return (
                  <div key={g.id} className="admin-bar-row">
                    <div className="admin-bar-label" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <span style={{ width: 10, height: 10, borderRadius: 3, background: g.tint || '#16a34a' }} />
                      {g.name}
                      <span className="cmp-meta" style={{ fontSize: 11 }}>· {g.count} tools</span>
                    </div>
                    <div className="admin-bar-track">
                      <div className="admin-bar-fill" style={{ width: pct + '%', background: g.tint || '#16a34a' }} />
                    </div>
                    <div className="admin-bar-v">{g.opens}</div>
                  </div>
                );
              })}
            </div>
          </div>
        </div>

        {/* Hero copy + featured-tool overrides */}
        <div className="dash-card" style={{ marginTop: 14 }}>
          <div className="dash-card-head">
            <h3>Sports landing page · hero copy</h3>
            <span className="cmp-meta">live on <code>#/sports</code> after page reload</span>
          </div>
          <div className="admin-tool-grid-2" style={{ gap: 12 }}>
            <div>
              <label className="mini-label">Featured tool</label>
              <select className="mini-input"
                      value={sportsConfig['featured_sports_tool'] || 'pace-calculator'}
                      onChange={(e) => setHero('featured_sports_tool', e.target.value)}>
                {sportTools.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
              </select>
            </div>
            <div>
              <label className="mini-label">Hero badge</label>
              <input className="mini-input"
                     defaultValue={sportsConfig['sports.hero_badge'] || 'FOR RUNNERS · CYCLISTS · TRIATHLETES'}
                     onBlur={(e) => setHero('sports.hero_badge', e.target.value)} />
            </div>
            <div>
              <label className="mini-label">Hero title — first line</label>
              <input className="mini-input"
                     defaultValue={sportsConfig['sports.hero_title'] || 'Train smarter.'}
                     onBlur={(e) => setHero('sports.hero_title', e.target.value)} />
            </div>
            <div>
              <label className="mini-label">Hero title — highlighted</label>
              <input className="mini-input"
                     defaultValue={sportsConfig['sports.hero_title_hl'] || 'Run further.'}
                     onBlur={(e) => setHero('sports.hero_title_hl', e.target.value)} />
            </div>
          </div>
          <label className="mini-label" style={{ marginTop: 12 }}>Hero subtitle</label>
          <textarea className="mini-input mini-textarea" rows={2}
                    defaultValue={sportsConfig['sports.hero_sub'] || ''}
                    placeholder="N privacy-first tools for athletes — pace, zones, GPX, hydration and training load."
                    onBlur={(e) => setHero('sports.hero_sub', e.target.value)} />
        </div>

        {/* Group editor */}
        <div className="dash-card" style={{ marginTop: 14 }}>
          <div className="dash-card-head">
            <h3>Sport groups</h3>
            <button className="dash-linkbtn" onClick={resetGroups}>Reset to defaults</button>
          </div>
          <div className="admin-pricing-table" style={{ gridTemplateColumns: 'unset' }}>
            <div className="admin-fn-row admin-fn-head">
              <div>Id</div><div>Name</div><div>Description</div><div>Icon</div><div>Tint</div><div>Tools</div>
            </div>
            {effectiveGroups.map((g) => (
              <div key={g.id} className="admin-fn-row">
                <div><code style={{ fontFamily: 'var(--id-font-mono)', fontSize: 11 }}>{g.id}</code></div>
                <div>
                  <input className="mini-input" defaultValue={g.name || ''}
                         onBlur={(e) => e.target.value !== g.name && setGroupField(g.id, 'name', e.target.value)} />
                </div>
                <div>
                  <input className="mini-input" defaultValue={g.desc || ''}
                         onBlur={(e) => e.target.value !== g.desc && setGroupField(g.id, 'desc', e.target.value)} />
                </div>
                <div>
                  <input className="mini-input" defaultValue={g.icon || ''}
                         onBlur={(e) => e.target.value !== g.icon && setGroupField(g.id, 'icon', e.target.value)} />
                </div>
                <div>
                  <input type="color" className="mini-input" defaultValue={g.tint || '#16a34a'} style={{ height: 36, padding: 4 }}
                         onChange={(e) => setGroupField(g.id, 'tint', e.target.value)} />
                </div>
                <div>{groupAgg[g.id]?.count ?? 0}</div>
              </div>
            ))}
          </div>
        </div>

        {/* Saved-calculations leaderboard for the current admin */}
        <div className="dash-card" style={{ marginTop: 14 }}>
          <div className="dash-card-head">
            <h3>Saved calculations · this admin</h3>
            <span className="cmp-meta">cross-user aggregate needs an admin RLS on <code>sync</code></span>
          </div>
          {mySavesByTool.length === 0 ? (
            <div className="dash-empty">No saves yet on your account. Saves from your own use of the calculators show here.</div>
          ) : (
            <div className="admin-bars">
              {mySavesByTool.map((r) => {
                const max = Math.max(1, ...mySavesByTool.map((x) => x.count));
                const pct = Math.round((r.count / max) * 100);
                const grp = effectiveGroups.find((g) => g.id === r.tool.group);
                const tint = grp?.tint || '#16a34a';
                return (
                  <div key={r.tid} className="admin-bar-row">
                    <div className="admin-bar-label" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <div className="admin-tool-icon" style={{ background: tint + '22', color: tint, width: 22, height: 22, borderRadius: 6 }}>
                        <window.Icon name={r.tool.icon} size={12} />
                      </div>
                      {r.tool.name}
                    </div>
                    <div className="admin-bar-track"><div className="admin-bar-fill" style={{ width: pct + '%', background: tint }} /></div>
                    <div className="admin-bar-v">{r.count}</div>
                  </div>
                );
              })}
            </div>
          )}
        </div>
      </>
    );
  };

  // ---------- Errors (production error log) ----------
  const renderErrors = () => {
    const C = window.MMCharts || {};
    const TimeseriesChart = C.TimeseriesChart;
    const KPICard = C.KPICard;

    const total = errorStats.reduce((s, r) => s + Number(r.count), 0);
    const distinct = errorStats.length;
    const max = Math.max(1, ...errorStats.map((r) => Number(r.count)));
    const totalUsers = errorStats.reduce((s, r) => s + Number(r.user_count || 0), 0);

    // Per-tool ranking (sum counts per tool_id)
    const byTool = {};
    for (const r of errorStats) {
      const tid = r.tool_id || '(global)';
      byTool[tid] = (byTool[tid] || 0) + Number(r.count);
    }
    const toolRanking = Object.entries(byTool)
      .sort((a, b) => b[1] - a[1])
      .slice(0, 8);
    const maxToolErrs = Math.max(1, ...toolRanking.map(([, n]) => n));

    // Daily error count from the dailySeries we already loaded for Overview
    const errorSeries = dailySeries.map((d) => Number(d.errors) || 0);
    const dayLabels   = dailySeries.map((d) => new Date(d.day).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }));

    const exportCsv = () => C.downloadCsv?.(`errors-${new Date().toISOString().slice(0,10)}`,
      errorStats.map((r) => ({
        fingerprint: r.fingerprint,
        message:     r.message,
        tool_id:     r.tool_id || '',
        count:       r.count,
        user_count:  r.user_count,
        first_seen:  r.first_seen,
        last_seen:   r.last_seen,
      })));

    return (
      <>
        <div className="adm-h2-row">
          <h2 className="dash-h2" style={{ margin: 0 }}>Errors · last {errorRange} days</h2>
          <div className="filter-row" style={{ marginBottom: 0 }}>
            {[1, 7, 30].map((d) => (
              <button key={d} className={`filter-pill ${errorRange === d ? 'active' : ''}`} onClick={() => setErrorRange(d)}>{d}d</button>
            ))}
          </div>
        </div>
        <p className="dash-sub">
          Captured from <code>window.onerror</code>, <code>unhandledrejection</code> and <code>window.mmReportError</code>.
          Grouped by fingerprint (message + top stack frame); same fingerprint within 30s deduped client-side.
        </p>

        <div className="mm-kpi-grid">
          {KPICard ? <>
            <KPICard label="Total events"        value={total}    color="#c8321f" sparkline={errorSeries} />
            <KPICard label="Unique fingerprints" value={distinct} color="#b8700a" />
            <KPICard label="Affected users"      value={totalUsers || '—'} sub="rough — same browser counted once if signed in" />
            <KPICard label="Tools w/ errors"     value={toolRanking.filter(([k]) => k !== '(global)').length} />
          </> : null}
        </div>

        {/* Daily chart */}
        {TimeseriesChart && dailySeries.length > 0 && (
          <div className="dash-card" style={{ marginTop: 14 }}>
            <div className="dash-card-head"><h3>Daily errors</h3></div>
            <TimeseriesChart
              series={[{ id: 'errors', label: 'Errors', values: errorSeries, color: '#c8321f' }]}
              labels={dayLabels}
              height={200}
            />
          </div>
        )}

        {/* Per-tool ranking */}
        {toolRanking.length > 0 && (
          <div className="dash-card" style={{ marginTop: 14 }}>
            <div className="dash-card-head"><h3>Errors by tool</h3></div>
            <div className="admin-bars">
              {toolRanking.map(([tid, n]) => {
                const t = window.TOOLS.find((x) => x.id === tid);
                const cat = t ? window.CATEGORIES.find((c) => c.id === t.cat) : null;
                const pct = Math.round((n / maxToolErrs) * 100);
                return (
                  <div key={tid} className="admin-bar-row" onClick={() => t && setEditingToolId(t.id)} style={{ cursor: t ? 'pointer' : 'default' }}>
                    <div className="admin-bar-label" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      {cat && <span style={{ width: 22, height: 22, borderRadius: 6, background: cat.soft, color: cat.tint, display: 'grid', placeItems: 'center' }}>
                        <window.Icon name={t.icon} size={12} />
                      </span>}
                      {t?.name || tid}
                    </div>
                    <div className="admin-bar-track"><div className="admin-bar-fill" style={{ width: pct + '%', background: 'linear-gradient(90deg, #c8321f, #c8321f88)' }} /></div>
                    <div className="admin-bar-v">{n}</div>
                  </div>
                );
              })}
            </div>
          </div>
        )}

        <div className="adm-toolbar" style={{ marginTop: 14 }}>
          <span className="cmp-meta" style={{ flex: 1 }}>
            {total === 0 ? 'No errors captured yet.' : `${distinct} distinct fingerprints over ${total.toLocaleString()} events`}
          </span>
          <button className="filter-pill" onClick={exportCsv} disabled={!errorStats.length}>
            <window.Icon name="download" size={12} /> Export CSV
          </button>
        </div>

        {errorStats.length === 0 ? (
          <div className="dash-empty-lg">No errors captured. Either nothing's broken or nobody's been around to break it.</div>
        ) : (
          <div className="admin-bars" style={{ marginTop: 14 }}>
            {errorStats.map((r) => {
              const pct = Math.round((Number(r.count) / max) * 100);
              const fresh = Number(new Date(r.last_seen)) > Date.now() - 24 * 3600_000;
              return (
                <div key={r.fingerprint} className="admin-bar-row" style={{ alignItems: 'flex-start' }}>
                  <div className="admin-bar-label" style={{ minWidth: 0, flexBasis: 320 }}>
                    <div style={{ fontWeight: 700, color: 'var(--id-text)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                      {fresh && <span className="atlr-chip atlr-feat" style={{ fontSize: 9, marginRight: 6 }}>FRESH</span>}
                      {r.message}
                    </div>
                    <div className="cmp-meta" style={{ fontSize: 11, fontFamily: 'var(--id-font-mono)' }}>
                      {r.fingerprint} · {r.tool_id ? r.tool_id + ' · ' : ''}{r.user_count} users · {new Date(r.last_seen).toLocaleString()}
                    </div>
                  </div>
                  <div className="admin-bar-track">
                    <div className="admin-bar-fill" style={{ width: pct + '%', background: 'linear-gradient(90deg, #c8321f, #c8321f88)' }} />
                  </div>
                  <div className="admin-bar-v">{r.count}</div>
                </div>
              );
            })}
          </div>
        )}
      </>
    );
  };

  // ---------- Activity ----------
  // ---------- SEO editor ----------
  // Stores overrides as a single JSON blob in settings (key='seo.overrides').
  // Per-route + per-tool entries, each with title/description/og_image/keywords/noindex.
  const seoSaveOverrides = async (next) => {
    if (!sb) return;
    setSeoOverrides(next);
    await saveSetting('seo.overrides', next);
  };
  const seoDefaults = (key) => {
    if (key === 'home')    return { title: '',                                  description: '70+ privacy-first tools for PDFs, images, GPX and running. Free, no upload, no signup.' };
    if (key === 'sports')  return { title: 'Sports tools — pace, GPX, training', description: 'Free running and sports calculators: pace, race predictor, VO₂max, heart-rate zones, GPX viewer, activity share cards.' };
    if (key === 'privacy') return { title: 'Privacy Policy',                    description: 'How MiniMagics handles your data.' };
    if (key === 'terms')   return { title: 'Terms of Service',                  description: 'MiniMagics terms of use.' };
    if (key === 'contact') return { title: 'Contact',                           description: 'Get in touch with MiniMagics.' };
    if (key.startsWith('tool:')) {
      const t = window.TOOLS.find((x) => x.id === key.slice(5));
      if (t) return { title: t.name, description: `${t.desc} — runs entirely in your browser, no upload required.` };
    }
    return { title: '', description: '' };
  };
  const renderSeo = () => {
    const C = window.MMCharts || {};

    const routeRows = [
      { key: 'home',     label: 'Home',           path: '/' },
      { key: 'sports',   label: 'Sports landing', path: '/sports' },
      { key: 'privacy',  label: 'Privacy',        path: '/privacy' },
      { key: 'terms',    label: 'Terms',          path: '/terms' },
      { key: 'contact',  label: 'Contact',        path: '/contact' },
    ];
    const toolRows = (window.TOOLS || []).map((t) => ({
      key: 'tool:' + t.id,
      label: t.name,
      path: '/tools/' + t.id,
      cat: window.CATEGORIES.find((c) => c.id === t.cat),
      tool: t,
    }));
    const allRows = [...routeRows, ...toolRows];
    const filtered = !seoSearch ? allRows : allRows.filter((r) =>
      r.label.toLowerCase().includes(seoSearch.toLowerCase()) ||
      r.path.toLowerCase().includes(seoSearch.toLowerCase()) ||
      r.key.toLowerCase().includes(seoSearch.toLowerCase())
    );

    const stats = {
      total:       allRows.length,
      overridden:  Object.keys(seoOverrides).length,
      noindex:     Object.values(seoOverrides).filter((o) => o?.noindex === true).length,
    };

    const exportCsv = () => C.downloadCsv?.(`seo-overrides-${new Date().toISOString().slice(0,10)}`,
      Object.entries(seoOverrides).map(([key, o]) => ({ key, ...o })));

    const editing = seoEditingKey ? {
      key: seoEditingKey,
      row: allRows.find((r) => r.key === seoEditingKey),
      override: seoOverrides[seoEditingKey] || {},
      defaults: seoDefaults(seoEditingKey),
    } : null;

    return (
      <>
        <h2 className="dash-h2">SEO · {stats.total} pages</h2>
        <p className="dash-sub">
          Per-route metadata for Google + social shares. Live-edit applies to the SPA on reload; <strong>run <code>npm run build</code> to bake into pre-rendered HTML for crawlers.</strong>
        </p>

        <div className="mm-kpi-grid">
          {C.KPICard ? <>
            <C.KPICard label="Pages indexable"        value={stats.total - stats.noindex} sub={`of ${stats.total} routes + tools`} />
            <C.KPICard label="Overrides saved"        value={stats.overridden} sub={stats.overridden ? 'admin-customised' : 'using defaults everywhere'} />
            <C.KPICard label="Marked noindex"         value={stats.noindex} sub="hidden from Google" />
            <C.KPICard label="Locale"                 value="en-US" sub="hreflang + og:locale" />
          </> : null}
        </div>

        <div className="adm-toolbar" style={{ marginTop: 14 }}>
          <input className="mini-input" style={{ flex: 1 }}
                 placeholder="Search by name, path or key…"
                 value={seoSearch} onChange={(e) => setSeoSearch(e.target.value)} />
          <button className="filter-pill" onClick={exportCsv} disabled={!stats.overridden}>
            <window.Icon name="download" size={12} /> Export overrides
          </button>
        </div>
        <div className="cmp-meta" style={{ marginTop: 6 }}>
          Showing {filtered.length} of {stats.total} pages
        </div>

        <div className="seo-list" style={{ marginTop: 8 }}>
          <div className="seo-list-row seo-list-head">
            <div></div>
            <div>Page</div>
            <div>Title</div>
            <div>Description</div>
            <div>State</div>
            <div></div>
          </div>
          {filtered.map((r) => {
            const o = seoOverrides[r.key] || null;
            const d = seoDefaults(r.key);
            const title = (o?.title?.trim()) || d.title;
            const desc  = (o?.description?.trim()) || d.description;
            return (
              <div key={r.key} className={`seo-list-row ${o ? 'is-overridden' : ''}`}
                   onClick={() => setSeoEditingKey(r.key)} role="button" tabIndex={0}
                   onKeyDown={(e) => { if (e.key === 'Enter') setSeoEditingKey(r.key); }}>
                <div className="seo-icon">
                  {r.cat
                    ? <span style={{ background: r.cat.soft, color: r.cat.tint, width: 22, height: 22, borderRadius: 6, display: 'grid', placeItems: 'center' }}>
                        <window.Icon name={r.tool.icon} size={12} />
                      </span>
                    : <window.Icon name="doc" size={14} />}
                </div>
                <div className="seo-name">
                  <div className="seo-label">{r.label}</div>
                  <code className="seo-path">{r.path}</code>
                </div>
                <div className="seo-cell">
                  <div className="seo-text-1">{title || <span className="cmp-meta">(empty)</span>}</div>
                </div>
                <div className="seo-cell">
                  <div className="seo-text-2">{desc || <span className="cmp-meta">(empty)</span>}</div>
                </div>
                <div className="seo-state">
                  {o?.noindex && <span className="atlr-chip atlr-hid">NOINDEX</span>}
                  {o && !o.noindex && <span className="atlr-chip atlr-feat">CUSTOM</span>}
                  {!o && <span className="cmp-meta" style={{ fontSize: 11 }}>default</span>}
                </div>
                <div className="atlr-cta">
                  <window.Icon name="arrow" size={14} />
                </div>
              </div>
            );
          })}
        </div>

        {editing && (
          <SeoDetailModal
            data={editing}
            onClose={() => setSeoEditingKey(null)}
            onSave={(patch) => {
              const next = { ...seoOverrides };
              const cleaned = {};
              for (const [k, v] of Object.entries(patch)) {
                if (v != null && v !== '' && !(typeof v === 'string' && v.trim() === '')) {
                  cleaned[k] = typeof v === 'string' ? v.trim() : v;
                }
              }
              if (Object.keys(cleaned).length === 0) {
                delete next[editing.key];
              } else {
                next[editing.key] = cleaned;
              }
              seoSaveOverrides(next);
              setSeoEditingKey(null);
            }}
            onReset={() => {
              const next = { ...seoOverrides };
              delete next[editing.key];
              seoSaveOverrides(next);
              setSeoEditingKey(null);
            }}
          />
        )}
      </>
    );
  };

  const [activityFilter, setActivityFilter] = React.useState('all');
  const [activityOffset, setActivityOffset] = React.useState(0);
  const renderActivity = () => {
    const C = window.MMCharts || {};
    const actions = Array.from(new Set(activityLog.map((a) => a.action))).sort();
    const filtered = activityFilter === 'all'
      ? activityLog
      : activityLog.filter((a) => a.action === activityFilter);

    const exportCsv = () => C.downloadCsv?.(`activity-${new Date().toISOString().slice(0,10)}`,
      filtered.map((a) => ({
        id:         a.id,
        actor_id:   a.actor_id || '',
        action:     a.action,
        target:     a.target,
        metadata:   typeof a.metadata === 'object' ? JSON.stringify(a.metadata) : (a.metadata || ''),
        created_at: a.created_at,
      })));

    return (
      <>
        <h2 className="dash-h2">Activity log · {activityLog.length}</h2>
        <p className="dash-sub">Every admin action writes a row. Stored in public.activity_log.</p>

        <div className="adm-toolbar">
          <select className="mini-input" style={{ flex: '0 0 220px' }}
                  value={activityFilter} onChange={(e) => setActivityFilter(e.target.value)}>
            <option value="all">All actions ({activityLog.length})</option>
            {actions.map((a) => {
              const n = activityLog.filter((x) => x.action === a).length;
              return <option key={a} value={a}>{a} ({n})</option>;
            })}
          </select>
          <span className="cmp-meta" style={{ flex: 1 }}>
            Showing {filtered.length} of {activityLog.length}
          </span>
          <button className="btn btn-secondary" onClick={() => {
            sb.from('activity_log').select('*').order('created_at', { ascending: false }).limit(50)
              .then(({ data }) => { setActivityLog(data || []); setActivityOffset(0); });
          }}>
            <window.Icon name="rotate" size={14} /> Refresh
          </button>
          <button className="filter-pill" onClick={exportCsv} disabled={!filtered.length}>
            <window.Icon name="download" size={12} /> Export CSV
          </button>
        </div>

        <div className="dash-table" style={{ marginTop: 12 }}>
          {filtered.map((a) => (
            <div key={a.id} className="dash-row-item" style={{ cursor: 'default' }}>
              <div className="dash-row-icon" style={{ background: 'var(--id-brand-blue-soft)', color: 'var(--id-brand-blue)' }}>
                <window.Icon name="bolt" size={14} />
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="dash-row-title">
                  <code style={{ fontFamily: 'var(--id-font-mono)', fontSize: 12, marginRight: 8 }}>{a.action}</code>
                  <span className="cmp-meta">{a.target}</span>
                </div>
                <div className="dash-row-sub">{new Date(a.created_at).toLocaleString()} · {JSON.stringify(a.metadata || {})}</div>
              </div>
            </div>
          ))}
        </div>

        <div style={{ textAlign: 'center', marginTop: 16 }}>
          <button className="btn btn-secondary" onClick={() => {
            const nextOffset = activityOffset + 50;
            sb.from('activity_log').select('*').order('created_at', { ascending: false })
              .range(nextOffset, nextOffset + 49)
              .then(({ data }) => {
                if (data && data.length > 0) {
                  setActivityLog((prev) => [...prev, ...data]);
                  setActivityOffset(nextOffset);
                }
              });
          }}>
            Load more
          </button>
        </div>
      </>
    );
  };

  // ---------- Settings ----------
  const saveSetting = async (key, value) => {
    if (!sb) return;
    await sb.from('settings').upsert({ key, value, updated_at: new Date().toISOString(), updated_by: user.id });
    setSettings((s) => ({ ...s, [key]: value }));
  };

  const renderSettings = () => {
    const featured = settings.featured_tool || 'photo-finder';
    const stripeOn = settings['stripe.configured'] === true;
    const priceIdCount = Object.values(toolRows).filter((r) => r.stripe_price_id).length;
    return (
      <>
        <h2 className="dash-h2">Settings</h2>
        <div className="dash-sub-card" style={{ marginBottom: 16 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6 }}>
            <span className={`admin-role-chip ${stripeOn ? 'admin-role-admin' : 'admin-role-user'}`} style={{ background: stripeOn ? '#e5f5ef' : 'var(--id-surface-alt)', color: stripeOn ? '#10805a' : 'var(--id-text-muted)' }}>
              {stripeOn ? 'STRIPE · ON' : 'STRIPE · OFF'}
            </span>
            <h3 style={{ margin: 0, fontSize: 15, fontWeight: 700 }}>Payments</h3>
          </div>
          <p className="cmp-meta" style={{ margin: '6px 0 12px' }}>
            Endpoints <code>/functions/v1/stripe-checkout</code> + <code>/functions/v1/stripe-webhook</code> are deployed.
            Webhook v2 now also writes to <code>public.payments</code>. <strong>Price IDs set on {priceIdCount} tool{priceIdCount === 1 ? '' : 's'}.</strong>
          </p>
          <label className="pw-opt" style={{ marginTop: 10 }}>
            <input type="checkbox" checked={stripeOn}
                   onChange={(e) => saveSetting('stripe.configured', e.target.checked)} />
            Mark Stripe as configured (shows paid CTAs to users)
          </label>
        </div>

        {/* ---- Ads ---- */}
        <div className="dash-sub-card" style={{ marginBottom: 16 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6 }}>
            <span className={`admin-role-chip ${settings['ads.enabled'] === true ? 'admin-role-admin' : 'admin-role-user'}`}
                  style={{ background: settings['ads.enabled'] === true ? '#e5f5ef' : 'var(--id-surface-alt)',
                           color: settings['ads.enabled'] === true ? '#10805a' : 'var(--id-text-muted)' }}>
              {settings['ads.enabled'] === true ? 'ADS · ON' : 'ADS · OFF'}
            </span>
            <h3 style={{ margin: 0, fontSize: 15, fontWeight: 700 }}>Google AdSense</h3>
          </div>
          <p className="cmp-meta" style={{ margin: '6px 0 12px' }}>
            Ads are shown to free/anonymous users only. Premium users never see ads.
            Requires user consent (GDPR "Advertising" toggle).
          </p>
          <label className="pw-opt" style={{ marginTop: 10 }}>
            <input type="checkbox" checked={settings['ads.enabled'] === true}
                   onChange={(e) => {
                     saveSetting('ads.enabled', e.target.checked);
                     if (window.MM_ADS_CONFIG) window.MM_ADS_CONFIG.enabled = e.target.checked;
                   }} />
            Enable ads globally
          </label>
          {settings['ads.enabled'] === true && !settings['ads.client_id'] && (
            <div style={{
              marginTop: 10, padding: '8px 12px', borderRadius: 8,
              background: 'rgba(234,88,12,0.08)', border: '1px solid rgba(234,88,12,0.2)',
              fontSize: 12, color: '#ea580c', fontWeight: 500,
            }}>
              Test mode active — dashed placeholder boxes are visible on the site where ads will appear.
              Enter your Publisher ID and slot IDs below to switch to live ads.
            </div>
          )}

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginTop: 14 }}>
            <div className="mini-field">
              <label className="mini-label">Publisher ID (ca-pub-…)</label>
              <input className="mini-input" style={{ fontFamily: 'var(--id-font-mono)', fontSize: 11 }}
                     defaultValue={settings['ads.client_id'] || ''}
                     placeholder="ca-pub-1234567890123456"
                     onBlur={(e) => {
                       const v = e.target.value.trim();
                       if (v !== (settings['ads.client_id'] || '')) saveSetting('ads.client_id', v || null);
                     }} />
            </div>
            <div className="mini-field">
              <label className="mini-label">Leaderboard slot ID</label>
              <input className="mini-input" style={{ fontFamily: 'var(--id-font-mono)', fontSize: 11 }}
                     defaultValue={settings['ads.slot_leaderboard'] || ''}
                     placeholder="1234567890"
                     onBlur={(e) => {
                       const v = e.target.value.trim();
                       if (v !== (settings['ads.slot_leaderboard'] || '')) saveSetting('ads.slot_leaderboard', v || null);
                     }} />
            </div>
            <div className="mini-field">
              <label className="mini-label">In-grid slot ID</label>
              <input className="mini-input" style={{ fontFamily: 'var(--id-font-mono)', fontSize: 11 }}
                     defaultValue={settings['ads.slot_ingrid'] || ''}
                     placeholder="1234567890"
                     onBlur={(e) => {
                       const v = e.target.value.trim();
                       if (v !== (settings['ads.slot_ingrid'] || '')) saveSetting('ads.slot_ingrid', v || null);
                     }} />
            </div>
            <div className="mini-field">
              <label className="mini-label">Modal slot ID</label>
              <input className="mini-input" style={{ fontFamily: 'var(--id-font-mono)', fontSize: 11 }}
                     defaultValue={settings['ads.slot_modal'] || ''}
                     placeholder="1234567890"
                     onBlur={(e) => {
                       const v = e.target.value.trim();
                       if (v !== (settings['ads.slot_modal'] || '')) saveSetting('ads.slot_modal', v || null);
                     }} />
            </div>
            <div className="mini-field">
              <label className="mini-label">Footer slot ID</label>
              <input className="mini-input" style={{ fontFamily: 'var(--id-font-mono)', fontSize: 11 }}
                     defaultValue={settings['ads.slot_footer'] || ''}
                     placeholder="1234567890"
                     onBlur={(e) => {
                       const v = e.target.value.trim();
                       if (v !== (settings['ads.slot_footer'] || '')) saveSetting('ads.slot_footer', v || null);
                     }} />
            </div>
          </div>
          <div className="cmp-meta" style={{ marginTop: 8 }}>
            Create ad units in <a href="https://www.google.com/adsense/" target="_blank" rel="noopener">Google AdSense</a>, then paste the
            publisher ID and each slot's numeric ID above. Leave a slot blank to disable that placement.
          </div>
        </div>

        <div className="dash-form">
          <div className="mini-field">
            <label className="mini-label">Featured tool (Tool of the Month)</label>
            <select className="mini-input" value={featured} onChange={(e) => saveSetting('featured_tool', e.target.value)}>
              {window.TOOLS.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
            </select>
          </div>

          <div className="mini-field" style={{ marginTop: 14 }}>
            <label className="mini-label">Photo-finder backend URL</label>
            <input className="mini-input"
                   defaultValue={settings['photo_finder.backend_url'] || ''}
                   placeholder="https://photo-finder.your-domain.com  (or http://localhost:8888 for dev)"
                   style={{ fontFamily: 'var(--id-font-mono)', fontSize: 12 }}
                   onBlur={(e) => {
                     const v = e.target.value.trim();
                     if (v !== (settings['photo_finder.backend_url'] || '')) {
                       saveSetting('photo_finder.backend_url', v || null);
                     }
                   }} />
            <div className="cmp-meta" style={{ marginTop: 4 }}>
              The Face Recognition tool iframes this URL. End users only see a friendly
              "warming up" state when offline — no Python instructions are shown. To run
              locally during dev: <code>cd tools/photorecognation &amp;&amp; ./run.sh</code>,
              and leave this field blank (defaults to localhost:8888).
            </div>
          </div>

          <div className="mini-field" style={{ marginTop: 14 }}>
            <label className="mini-label">Photo-finder download paywall</label>
            <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 6 }}>
              <label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, fontWeight: 500, color: 'var(--id-text)' }}>
                <input type="checkbox"
                       checked={settings['photo_finder.paywall_enabled'] === true}
                       onChange={(e) => {
                         const on = e.target.checked;
                         saveSetting('photo_finder.paywall_enabled', on);
                         window.MM_PF_PAYWALL = on;
                       }} />
                {settings['photo_finder.paywall_enabled'] ? 'Paywall ACTIVE' : 'Paywall OFF'}
              </label>
              <span className={`atlr-chip ${settings['photo_finder.paywall_enabled'] ? 'atlr-live' : 'atlr-off'}`}>
                {settings['photo_finder.paywall_enabled'] ? 'ON' : 'OFF'}
              </span>
            </div>
            <div className="cmp-meta" style={{ marginTop: 4 }}>
              When <strong>on</strong>, free users see the first 2 matched photos clearly and the rest are
              blurred with an upgrade CTA. Downloads beyond 2 are blocked.
              When <strong>off</strong>, all results are visible and downloadable without limit.
            </div>
          </div>
        </div>
      </>
    );
  };

  // ---------- Layout ----------
  // Sports got merged into the Tools tab — pick category=Sports to reveal
  // the sports configuration block below the list.
  const section_map = {
    overview:  { title: 'Overview',     icon: 'grid',    render: renderOverview },
    tools:     { title: 'Tools',        icon: 'lock',    render: renderTools },
    analytics: { title: 'Analytics',    icon: 'scan',    render: renderAnalytics },
    functions: { title: 'Functions',    icon: 'bolt',    render: renderFunctions },
    revenue:   { title: 'Revenue',      icon: 'sparkle', render: renderRevenue },
    users:     { title: 'Users',        icon: 'cursor',  render: renderUsers },
    seo:       { title: 'SEO',          icon: 'search',  render: renderSeo },
    errors:    { title: 'Errors',       icon: 'x',       render: renderErrors },
    activity:  { title: 'Activity',     icon: 'clock',   render: renderActivity },
    settings:  { title: 'Settings',     icon: 'edit',    render: renderSettings },
  };

  return (
    <div className="dash-shell">
      {userDetail && <UserDetailModal state={userDetail} onClose={() => setUserDetail(null)} />}
      <aside className="dash-side dash-side-admin">
        <button className="dash-back" onClick={onClose} title="Back to MiniMagics">
          <window.Icon name="arrow" size={14} style={{ transform: 'rotate(180deg)' }} />
          <span>MiniMagics</span>
        </button>
        <div className="dash-side-head">
          <div className="dash-admin-badge"><window.Icon name="bolt" size={14} /></div>
          <div style={{ minWidth: 0 }}>
            <div className="dash-side-name">Admin Panel</div>
            <div className="dash-side-email">{user.email}</div>
          </div>
        </div>
        <nav className="dash-nav">
          {Object.entries(section_map).map(([key, s]) => (
            <button key={key} className={`dash-nav-item ${section === key ? 'on' : ''}`} onClick={() => setSection(key)}>
              <window.Icon name={s.icon} size={14} />
              <span>{s.title}</span>
            </button>
          ))}
        </nav>
        <button className="dash-nav-item" onClick={() => window.mmNavigate('dashboard')} style={{ marginTop: 12 }}>
          <window.Icon name="grid" size={14} />
          <span>My Dashboard</span>
        </button>
      </aside>

      <main className="dash-main">
        {section_map[section].render()}
      </main>
    </div>
  );
};

// Reuse KPI layout
function KPI({ label, value, icon }) {
  return (
    <div className="dash-kpi">
      <div className="dash-kpi-icon"><window.Icon name={icon} size={14} /></div>
      <div>
        <div className="dash-kpi-value">{value}</div>
        <div className="dash-kpi-label">{label}</div>
      </div>
    </div>
  );
}

// Drill-down tool editor — opened from the Tools list. Owns no state of its
// own; every onBlur/onChange calls onPatch with the diff and the parent
// re-renders with the updated row.
function ToolDetailModal({ row, analytics, onClose, onPatch, onDelete }) {
  React.useEffect(() => {
    const onKey = (e) => e.key === 'Escape' && onClose();
    document.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => {
      document.removeEventListener('keydown', onKey);
      document.body.style.overflow = '';
    };
  }, [onClose]);

  const cat = window.CATEGORIES.find((c) => c.id === row.category);
  const tier = row.tier || 'free';

  // Public landing-page link if hash routing supports it. Defaults to
  // re-opening the tool through the home grid using the tool id.
  const liveHref = row.category === 'sports' ? '#/sports' : '#';

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal tool-detail" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 720 }}>
        <div className="modal-head">
          <div className="mh-icon" style={{ background: cat?.soft || 'var(--id-surface-alt)', color: cat?.tint || 'var(--id-text-muted)' }}>
            <window.Icon name={row.icon || 'doc'} size={22} />
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="mh-name">{row.name || row.tool_id}</div>
            <div className="mh-desc" style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
              <code style={{ fontFamily: 'var(--id-font-mono)', fontSize: 11 }}>{row.tool_id}</code>
              {tier !== 'free' && <span className={`dash-tier-chip dash-tier-${tier}`} style={{ fontSize: 9, padding: '2px 6px' }}>{tier.toUpperCase()}</span>}
              {row.featured && <span className="atlr-chip atlr-feat" style={{ fontSize: 10 }}>★ Featured</span>}
              {row.working && <span className="atlr-chip atlr-live" style={{ fontSize: 10 }}>LIVE</span>}
              {row.requires_auth && <span className="atlr-chip atlr-auth" style={{ fontSize: 10 }}>🔐 Sign-in</span>}
              {row.hidden && <span className="atlr-chip atlr-hid" style={{ fontSize: 10 }}>HIDDEN</span>}
              {row.active === false && <span className="atlr-chip atlr-off" style={{ fontSize: 10 }}>INACTIVE</span>}
            </div>
          </div>
          <button className="mh-close" onClick={onClose} aria-label="Close"><window.Icon name="x" size={18} /></button>
        </div>

        <div className="modal-body">
          {/* Section: Identity */}
          <div className="td-section">
            <div className="td-section-h">Identity</div>
            <label className="mini-label">Name</label>
            <input className="mini-input" defaultValue={row.name || ''} key={`name-${row.tool_id}`}
                   onBlur={(e) => e.target.value !== row.name && onPatch({ name: e.target.value })} />

            <label className="mini-label" style={{ marginTop: 10 }}>Description</label>
            <textarea className="mini-input mini-textarea" rows={2}
                      defaultValue={row.description || ''} key={`desc-${row.tool_id}`}
                      onBlur={(e) => e.target.value !== row.description && onPatch({ description: e.target.value })} />

            <div className="admin-tool-grid-2" style={{ marginTop: 10 }}>
              <div>
                <label className="mini-label">Category</label>
                <select className="mini-input" defaultValue={row.category || 'pdf'} key={`cat-${row.tool_id}`}
                        onChange={(e) => onPatch({ category: e.target.value })}>
                  {window.CATEGORIES.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
                </select>
              </div>
              <div>
                <label className="mini-label">Icon name</label>
                <input className="mini-input" defaultValue={row.icon || ''} key={`icon-${row.tool_id}`}
                       onBlur={(e) => e.target.value !== row.icon && onPatch({ icon: e.target.value })} />
              </div>
              <div>
                <label className="mini-label">Sort order</label>
                <input type="number" className="mini-input" defaultValue={row.sort_order ?? 0} key={`sort-${row.tool_id}`}
                       onBlur={(e) => Number(e.target.value) !== row.sort_order && onPatch({ sort_order: Number(e.target.value) })} />
              </div>
              <div>
                <label className="mini-label">URL slug</label>
                <input className="mini-input" defaultValue={row.slug || row.tool_id} key={`slug-${row.tool_id}`}
                       style={{ fontFamily: 'var(--id-font-mono)', fontSize: 12 }}
                       onBlur={(e) => e.target.value !== row.slug && onPatch({ slug: e.target.value })} />
              </div>
            </div>
          </div>

          {/* Section: Pricing */}
          <div className="td-section">
            <div className="td-section-h">Pricing</div>
            <div className="admin-tool-grid-2">
              <div>
                <label className="mini-label">Tier</label>
                <select className="mini-input" defaultValue={tier} key={`tier-${row.tool_id}`}
                        onChange={(e) => onPatch({ tier: e.target.value })}>
                  <option value="free">Free</option>
                  <option value="premium">Premium</option>
                </select>
              </div>
              <div>
                <label className="mini-label">Price (USD)</label>
                <input type="number" min="0" step="1" className="mini-input"
                       defaultValue={(row.price_cents || 0) / 100} key={`price-${row.tool_id}`}
                       onBlur={(e) => onPatch({ price_cents: Math.round(Number(e.target.value) * 100) })}
                       disabled={tier === 'free'} />
              </div>
            </div>
            <label className="mini-label" style={{ marginTop: 10 }}>Stripe price ID</label>
            <input className="mini-input" defaultValue={row.stripe_price_id || ''}
                   key={`stripe-${row.tool_id}`}
                   placeholder="price_…"
                   style={{ fontFamily: 'var(--id-font-mono)', fontSize: 12 }}
                   onBlur={(e) => e.target.value !== row.stripe_price_id && onPatch({ stripe_price_id: e.target.value || null })}
                   disabled={tier === 'free'} />
            {tier === 'free' && (
              <div className="cmp-meta" style={{ marginTop: 6 }}>Free tools skip the paywall entirely. Switch to Premium or Pro to set a price.</div>
            )}
          </div>

          {/* Section: Visibility flags */}
          <div className="td-section">
            <div className="td-section-h">Visibility & state</div>
            <div className="td-toggles">
              <label className={`td-toggle ${row.active !== false ? 'on' : ''}`}>
                <input type="checkbox" checked={row.active !== false}
                       onChange={(e) => onPatch({ active: e.target.checked })} />
                <span>Active</span>
                <span className="td-toggle-help">Off → tool hidden from grid + dropdowns</span>
              </label>
              <label className={`td-toggle ${row.featured ? 'on' : ''}`}>
                <input type="checkbox" checked={row.featured === true}
                       onChange={(e) => onPatch({ featured: e.target.checked })} />
                <span>Featured</span>
                <span className="td-toggle-help">Adds the ★ badge on cards</span>
              </label>
              <label className={`td-toggle ${row.working ? 'on' : ''}`}>
                <input type="checkbox" checked={row.working === true}
                       onChange={(e) => onPatch({ working: e.target.checked })} />
                <span>Live demo</span>
                <span className="td-toggle-help">Shows the green ✓ Live demo pill</span>
              </label>
              <label className={`td-toggle td-auth ${row.requires_auth ? 'on' : ''} ${row.tier === 'premium' ? 'is-implicit' : ''}`}>
                <input type="checkbox"
                       checked={row.requires_auth === true || row.tier === 'premium'}
                       disabled={row.tier === 'premium'}
                       onChange={(e) => onPatch({ requires_auth: e.target.checked })} />
                <span>Requires sign-in 🔐</span>
                <span className="td-toggle-help">
                  {row.tier === 'premium'
                    ? 'Always on for paid tools — entitlement is per-user'
                    : 'Anonymous users get a sign-in modal instead of opening the tool'}
                </span>
              </label>
              <label className={`td-toggle ${row.hidden ? 'on' : ''}`}>
                <input type="checkbox" checked={row.hidden === true}
                       onChange={(e) => onPatch({ hidden: e.target.checked })} />
                <span>Hidden</span>
                <span className="td-toggle-help">Strong override — never appears anywhere</span>
              </label>
            </div>
          </div>

          {/* Section: Analytics */}
          {analytics && (
            <div className="td-section">
              <div className="td-section-h">Last-30d analytics</div>
              <div className="dash-kpis">
                <KPI label="Opens"      value={Number(analytics.opens) || 0} icon="bolt" />
                <KPI label="Completed"  value={Number(analytics.completes) || 0} icon="check" />
                <KPI label="Drop-off"   value={analytics.drop_off_pct != null ? analytics.drop_off_pct + '%' : '—'} icon="rotate" />
                <KPI label="Avg ms"     value={analytics.avg_ms != null ? Math.round(analytics.avg_ms) : '—'} icon="scan" />
                <KPI label="Users"      value={Number(analytics.unique_users) || 0} icon="grid" />
              </div>
            </div>
          )}

          {/* Section: Danger */}
          <div className="td-section td-danger">
            <div className="td-section-h" style={{ color: '#c8321f' }}>Danger zone</div>
            <p className="cmp-meta" style={{ margin: '0 0 10px' }}>
              Removing this row only deletes the database metadata. The handler stays in code so existing users keep working until you redeploy without it.
            </p>
            <button className="btn btn-secondary"
                    style={{ borderColor: '#c8321f', color: '#c8321f' }}
                    onClick={() => { if (confirm(`Delete tool row "${row.tool_id}"?`)) onDelete(); }}>
              <window.Icon name="x" size={14} /> Delete tool row
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// Per-route SEO editor modal. `data` carries the current row, override, and
// computed defaults. Empty fields fall back to defaults at render time so the
// editor can leave them blank to "use default".
function SeoDetailModal({ data, onClose, onSave, onReset }) {
  const { row, override, defaults, key } = data;

  const [title, setTitle]             = React.useState(override.title || '');
  const [description, setDescription] = React.useState(override.description || '');
  const [keywords, setKeywords]       = React.useState(override.keywords || '');
  const [ogImage, setOgImage]         = React.useState(override.og_image || '');
  const [noindex, setNoindex]         = React.useState(override.noindex === true);

  React.useEffect(() => {
    const onKey = (e) => e.key === 'Escape' && onClose();
    document.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => { document.removeEventListener('keydown', onKey); document.body.style.overflow = ''; };
  }, [onClose]);

  const liveTitle = (title.trim() || defaults.title) ? `${(title.trim() || defaults.title)} · MiniMagics` : 'MiniMagics — Free tools to make everything simple';
  const liveDesc  = (description.trim() || defaults.description || '').slice(0, 160);

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal seo-modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 720 }}>
        <div className="modal-head">
          <div className="mh-icon" style={{ background: 'var(--id-brand-blue-soft)', color: 'var(--id-brand-blue)' }}>
            <window.Icon name="scan" size={22} />
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="mh-name">{row?.label || key}</div>
            <div className="mh-desc"><code style={{ fontFamily: 'var(--id-font-mono)', fontSize: 12 }}>{row?.path}</code></div>
          </div>
          <button className="mh-close" onClick={onClose} aria-label="Close"><window.Icon name="x" size={18} /></button>
        </div>

        <div className="modal-body">
          {/* Live SERP preview */}
          <div className="seo-preview">
            <div className="seo-preview-l">SERP preview</div>
            <div className="seo-preview-card">
              <div className="seo-preview-url">{location.origin.replace(/^https?:\/\//, '')}{row?.path}</div>
              <div className="seo-preview-title">{liveTitle}</div>
              <div className="seo-preview-desc">{liveDesc || <span className="cmp-meta">(no description)</span>}</div>
            </div>
          </div>

          {/* Editor */}
          <div className="td-section">
            <label className="mini-label">Title <span className="seo-counter">{title.length}/60 (chars after the “· MiniMagics” suffix)</span></label>
            <input className="mini-input" value={title} maxLength={120}
                   placeholder={defaults.title || '(uses site default)'}
                   onChange={(e) => setTitle(e.target.value)} />

            <label className="mini-label" style={{ marginTop: 12 }}>Description <span className="seo-counter">{description.length}/160</span></label>
            <textarea className="mini-input mini-textarea" rows={3} maxLength={300}
                      value={description}
                      placeholder={defaults.description || '(uses site default)'}
                      onChange={(e) => setDescription(e.target.value)} />

            <label className="mini-label" style={{ marginTop: 12 }}>Keywords (comma-separated)</label>
            <input className="mini-input" value={keywords}
                   placeholder="optional · low SEO impact, used as a hint by some engines"
                   onChange={(e) => setKeywords(e.target.value)} />

            <label className="mini-label" style={{ marginTop: 12 }}>OG image URL <span className="seo-counter">1200×630 recommended</span></label>
            <input className="mini-input" value={ogImage}
                   placeholder="https://magictools.com/og/<page>.png"
                   onChange={(e) => setOgImage(e.target.value)} />

            <label className="td-toggle" style={{ marginTop: 14, gridTemplateRows: 'auto' }}>
              <input type="checkbox" checked={noindex} onChange={(e) => setNoindex(e.target.checked)} />
              <span>
                <strong>noindex</strong>
                <span className="td-toggle-help">Tell Google not to index this page (already on for /admin and /dashboard).</span>
              </span>
            </label>
          </div>

          <div className="cmp-actions" style={{ flexWrap: 'wrap' }}>
            {Object.keys(override).length > 0 && (
              <button className="btn btn-secondary"
                      style={{ borderColor: '#c8321f', color: '#c8321f' }}
                      onClick={() => { if (confirm('Reset to default? Override will be deleted.')) onReset(); }}>
                <window.Icon name="rotate" size={14} /> Reset to default
              </button>
            )}
            <button className="btn btn-secondary" onClick={onClose}>Cancel</button>
            <button className="btn btn-primary" onClick={() => onSave({
              title, description, keywords, og_image: ogImage, noindex,
            })}>
              <window.Icon name="check" size={14} /> Save
            </button>
          </div>

          <div className="cmp-meta" style={{ marginTop: 12 }}>
            Edits go live for SPA visitors on reload. Run <code>npm run build</code> to bake the change into the pre-rendered HTML that crawlers see.
          </div>
        </div>
      </div>
    </div>
  );
}

// Drill-down user detail modal — unchanged from previous version.
function UserDetailModal({ state, onClose }) {
  const { user, detail, stats, loading } = state;
  React.useEffect(() => {
    const onKey = (e) => e.key === 'Escape' && onClose();
    document.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => {
      document.removeEventListener('keydown', onKey);
      document.body.style.overflow = '';
    };
  }, [onClose]);

  const max = Number(stats?.[0]?.events) || 1;
  const total = (stats || []).reduce((s, r) => s + Number(r.events), 0);

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal user-detail" onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div className="mh-icon" style={{ background: 'var(--id-brand-blue-soft)', color: 'var(--id-brand-blue)' }}>
            <window.Icon name="sparkle" size={22} />
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="mh-name">{user.email}</div>
            <div className="mh-desc">
              <span className={`admin-role-chip admin-role-${user.role}`} style={{ fontSize: 9, marginRight: 6 }}>{user.role.toUpperCase()}</span>
              Joined {new Date(user.created_at).toLocaleDateString()}
            </div>
          </div>
          <button className="mh-close" onClick={onClose} aria-label="Close"><window.Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body">
          {loading ? <div className="dash-empty">Loading…</div>
           : !detail ? <div className="dash-empty">No data (RLS may have denied this).</div>
           : (
            <>
              <div className="dash-kpis" style={{ marginBottom: 16 }}>
                <KPI label="Events 30d" value={Number(detail.events_30d)} icon="bolt" />
                <KPI label="Events all-time" value={Number(detail.events_all)} icon="rotate" />
                <KPI label="API keys" value={Number(detail.keys_count)} icon="lock" />
                <KPI label="Last activity" value={detail.last_activity ? new Date(detail.last_activity).toLocaleDateString() : '—'} icon="scan" />
              </div>
              <div style={{ marginBottom: 14 }}>
                <div className="user-menu-label">Entitlements</div>
                {(!detail.entitlements || detail.entitlements.length === 0) ? <div className="cmp-meta">none (free tier)</div>
                 : <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 4 }}>
                     {detail.entitlements.map((e, i) => (
                       <span key={i} className={`dash-tier-chip dash-tier-${e.tier}`} title={`Source: ${e.source}${e.expires_at ? ' · expires ' + new Date(e.expires_at).toLocaleDateString() : ' · permanent'}`}>
                         {e.tier.toUpperCase()}<span style={{ marginLeft: 6, opacity: 0.8, fontWeight: 600 }}>{e.source}</span>
                       </span>
                     ))}
                   </div>
                }
              </div>
              <div className="user-menu-label">Top tools · last 90 days</div>
              {(stats || []).length === 0 ? <div className="cmp-meta" style={{ marginTop: 6 }}>No activity in this window.</div>
               : <>
                   <div className="cmp-meta" style={{ marginTop: 2, marginBottom: 8 }}>{total} events across {stats.length} tools</div>
                   <div className="admin-bars">
                     {stats.slice(0, 12).map((r) => {
                       const t = window.TOOLS.find((x) => x.id === r.tool_id);
                       const cat = t ? window.CATEGORIES.find((c) => c.id === t.cat) : null;
                       const pct = Math.round((Number(r.events) / max) * 100);
                       return (
                         <div key={r.tool_id} className="admin-bar-row">
                           <div className="admin-bar-label" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                             {cat && <div className="admin-tool-icon" style={{ background: cat.soft, color: cat.tint, width: 22, height: 22, borderRadius: 6 }}>
                               <window.Icon name={t.icon} size={12} />
                             </div>}
                             {t?.name || r.tool_id}
                           </div>
                           <div className="admin-bar-track">
                             <div className="admin-bar-fill" style={{ width: pct + '%', background: cat ? `linear-gradient(90deg, ${cat.tint}, ${cat.tint}cc)` : undefined }} />
                           </div>
                           <div className="admin-bar-v">{r.events}</div>
                         </div>
                       );
                     })}
                   </div>
                 </>
              }
            </>
          )}
        </div>
      </div>
    </div>
  );
}
