// Admin page — wired to /api/admin/config + /api/admin/entitlement.
//
// Replaces the old hardcoded-stats mockup. Gated at the UI level by
// BFEntitlement.isAdmin() (client hint only — every endpoint we call
// re-verifies the JWT → email → ADMIN_EMAILS allow-list server-side).
//
// Three sections:
//   1. Master switches — global.free_everything + global.trial_days_on_signup
//   2. Feature tier matrix — per-feature tier dropdowns (10 features seeded
//      in migration 0003; fetched from the live config row so new features
//      added via POST are visible without a code change)
//   3. User entitlement grants — email lookup, tier dropdown, trial days,
//      expiry override. Updates reflect in public.entitlements immediately.
//
// Uses the existing theme tokens so it feels part of the app — no iframe,
// no chromed browser frame, no hardcoded stats. Shown inside the normal
// app shell.

function BFAdmin({ width, height, theme: themeIn, accentH, onBack }) {
  const theme = themeIn || BF_THEMES.dark;
  const aH    = typeof accentH === 'number' ? accentH : (BF_ACCENTS.aqua.h);

  const [loading, setLoading]   = React.useState(true);
  const [denied,  setDenied]    = React.useState(false);
  const [rows,    setRows]      = React.useState([]);
  const [saving,  setSaving]    = React.useState(null); // key currently saving
  const [error,   setError]     = React.useState(null);

  // ── Auth token helper ────────────────────────────────────────────
  // Every admin endpoint expects `Authorization: Bearer <jwt>`.
  const _authHeaders = React.useCallback(() => {
    const sess = window.BFAuth && window.BFAuth.getSession && window.BFAuth.getSession();
    const token = sess && sess.access_token;
    return token ? { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' }
                 : { 'Content-Type': 'application/json' };
  }, []);

  const loadConfig = React.useCallback(async () => {
    setLoading(true); setError(null);
    try {
      const r = await fetch('/api/admin/config', { headers: _authHeaders() });
      if (r.status === 401 || r.status === 403) { setDenied(true); return; }
      const j = await r.json();
      if (!j.ok) throw new Error(j.error || 'load failed');
      setRows(j.rows || []);
    } catch (e) {
      setError(e.message || String(e));
    } finally {
      setLoading(false);
    }
  }, [_authHeaders]);

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

  const setRowValue = React.useCallback(async (key, value, description) => {
    setSaving(key); setError(null);
    try {
      const body = { key, value };
      if (description) body.description = description;
      const r = await fetch('/api/admin/config', {
        method: 'POST',
        headers: _authHeaders(),
        body: JSON.stringify(body),
      });
      const j = await r.json();
      if (!j.ok) throw new Error(j.error || 'save failed');
      // Optimistically update local state so toggles feel immediate.
      setRows((prev) => {
        const existing = prev.find((p) => p.key === key);
        if (existing) return prev.map((p) => p.key === key ? Object.assign({}, p, { value }) : p);
        return [...prev, { key, value, description: description || null }];
      });
      if (window.BFConfig && window.BFConfig.refresh) {
        try { await window.BFConfig.refresh(); } catch (e) {}
      }
      if (window.BFToast) BFToast.success('Saved', key);
    } catch (e) {
      setError(e.message || String(e));
      if (window.BFToast) BFToast.error('Save failed', e.message || String(e));
    } finally {
      setSaving(null);
    }
  }, [_authHeaders]);

  // Client-side admin gate — if the user isn't an admin per BFEntitlement,
  // don't even bother fetching. Server still re-checks on every request.
  if (window.BFEntitlement && window.BFEntitlement.isAdmin && !window.BFEntitlement.isAdmin()) {
    return (
      <AdminDenied theme={theme} message="This screen is for maintainers only." />
    );
  }

  if (denied) return <AdminDenied theme={theme} message="Your email isn't in ADMIN_EMAILS. Ask to be added." />;

  return (
    <div style={{
      padding: '40px 22px calc(80px + env(safe-area-inset-bottom, 0px))',
      color: theme.fg, fontFamily: BF_FONTS.sans,
      maxWidth: 680, margin: '0 auto',
    }}>
      {/* Header */}
      <div style={{ marginBottom: 24 }}>
        {onBack && (
          <button onClick={onBack} style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            padding: '6px 10px 6px 6px', borderRadius: 999,
            background: 'transparent', color: theme.fgMuted,
            border: `1px solid ${theme.line}`, cursor: 'pointer',
            fontSize: 12, marginBottom: 16,
          }}>← Back</button>
        )}
        <div style={{ fontSize: 11, letterSpacing: 1, textTransform: 'uppercase', color: theme.fgFaint, fontFamily: BF_FONTS.mono }}>
          Admin · operations
        </div>
        <div style={{
          fontFamily: BF_FONTS.serif, fontSize: 34, fontStyle: 'italic',
          marginTop: 4, letterSpacing: -0.4,
        }}>
          BreatheFlow controls
        </div>
        <div style={{ fontSize: 13, color: theme.fgMuted, marginTop: 6, lineHeight: 1.45 }}>
          Live server-backed config. Changes take effect within 30s (serverless cache TTL).
        </div>
      </div>

      {error && (
        <div style={{
          background: 'oklch(0.32 0.14 25 / 0.25)', color: 'oklch(0.86 0.13 25)',
          border: '1px solid oklch(0.62 0.17 25 / 0.45)', borderRadius: 12,
          padding: '10px 14px', fontSize: 13, marginBottom: 16,
        }}>{error}</div>
      )}

      {loading ? (
        <div style={{ color: theme.fgMuted, fontSize: 13, padding: 20, textAlign: 'center' }}>Loading…</div>
      ) : (
        <>
          <AdminStats theme={theme} aH={aH} authHeaders={_authHeaders} />
          <AdminSwitches rows={rows} theme={theme} aH={aH} saving={saving} onSave={setRowValue} />
          <AdminFeatureMatrix rows={rows} theme={theme} aH={aH} saving={saving} onSave={setRowValue} />
          <AdminPatternAudience rows={rows} theme={theme} aH={aH} saving={saving} onSave={setRowValue} />
          <AdminEntitlements theme={theme} aH={aH} authHeaders={_authHeaders} />
          <AdminRawRows rows={rows} theme={theme} onReload={loadConfig} />
        </>
      )}
    </div>
  );
}

function AdminDenied({ theme, message }) {
  return (
    <div style={{
      padding: '60px 22px', color: theme.fg, fontFamily: BF_FONTS.sans,
      maxWidth: 480, margin: '0 auto', textAlign: 'center',
    }}>
      <div style={{
        fontFamily: BF_FONTS.serif, fontSize: 28, fontStyle: 'italic',
        letterSpacing: -0.4, marginBottom: 10,
      }}>Not authorised</div>
      <div style={{ fontSize: 13, color: theme.fgMuted, lineHeight: 1.5 }}>{message}</div>
    </div>
  );
}

// ── Master switches ─────────────────────────────────────────────────
function AdminSwitches({ rows, theme, aH, saving, onSave }) {
  const freeEverything = !!rowValue(rows, 'global.free_everything');
  const trialDays = Number(rowValue(rows, 'global.trial_days_on_signup') || 0);

  const cardStyle = {
    background: theme.card, border: `1px solid ${theme.line}`,
    borderRadius: 16, padding: 18, marginBottom: 16,
  };
  const rowStyle = {
    display: 'flex', alignItems: 'center', gap: 14,
    padding: '10px 0',
  };

  return (
    <div style={cardStyle}>
      <SectionTitle theme={theme}>Master switches</SectionTitle>

      <div style={rowStyle}>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 14, fontWeight: 500 }}>Free everything</div>
          <div style={{ fontSize: 12, color: theme.fgMuted, marginTop: 2 }}>
            When on, every feature is unlocked for every user. Launch default.
          </div>
        </div>
        <BigToggle
          on={freeEverything}
          saving={saving === 'global.free_everything'}
          aH={aH}
          onClick={() => onSave('global.free_everything', !freeEverything)}
        />
      </div>

      <div style={{ ...rowStyle, borderTop: `1px solid ${theme.line}`, paddingTop: 14, marginTop: 6 }}>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 14, fontWeight: 500 }}>Auto-trial on signup</div>
          <div style={{ fontSize: 12, color: theme.fgMuted, marginTop: 2 }}>
            Days of Plus granted automatically when a new user signs up. 0 = off.
          </div>
        </div>
        <NumberStepper
          value={trialDays}
          min={0} max={90}
          saving={saving === 'global.trial_days_on_signup'}
          theme={theme}
          onChange={(v) => onSave('global.trial_days_on_signup', v)}
        />
      </div>
    </div>
  );
}

// ── Feature matrix ──────────────────────────────────────────────────
function AdminFeatureMatrix({ rows, theme, aH, saving, onSave }) {
  // Pull every feature.* row + ensure the 10 seeded features always appear
  // (even if someone DELETE'd one via the API).
  const SEED = [
    { id: 'reminders.push',        label: 'Daily reminders' },
    { id: 'voice.premium_packs',   label: 'Premium voice packs' },
    { id: 'integrations.garmin',   label: 'Garmin Connect' },
    { id: 'integrations.gfit',     label: 'Google Fit' },
    { id: 'integrations.health',   label: 'Apple Health' },
    { id: 'leaderboard',           label: 'Leaderboard' },
    { id: 'stats.export',          label: 'Stats export' },
    { id: 'patterns.custom',       label: 'Custom patterns' },
    { id: 'profiles.multi',        label: 'Multiple profiles' },
    { id: 'family.dashboard',      label: 'Family dashboard' },
  ];

  const seedMap = new Map(SEED.map((f) => [f.id, f.label]));
  const featureRows = rows.filter((r) => r.key && r.key.startsWith('feature.'));
  const seen = new Set();
  const merged = [];

  featureRows.forEach((r) => {
    const id = r.key.replace(/^feature\./, '');
    seen.add(id);
    merged.push({ id, label: seedMap.get(id) || id, tier: (r.value && r.value.tier) || 'free' });
  });
  SEED.forEach((f) => {
    if (!seen.has(f.id)) merged.push({ id: f.id, label: f.label, tier: 'free' });
  });

  return (
    <div style={{
      background: theme.card, border: `1px solid ${theme.line}`,
      borderRadius: 16, padding: 18, marginBottom: 16,
    }}>
      <SectionTitle theme={theme}>Per-feature tier</SectionTitle>
      <div style={{ fontSize: 12, color: theme.fgMuted, lineHeight: 1.5, marginBottom: 10 }}>
        Which tier is required to access each feature. Ignored while <em>Free everything</em> is on.
      </div>

      {merged.map((f, i) => (
        <div key={f.id} style={{
          display: 'flex', alignItems: 'center', gap: 12,
          padding: '10px 0',
          borderTop: i === 0 ? `1px solid ${theme.line}` : `1px solid ${theme.line}`,
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 500 }}>{f.label}</div>
            <div style={{
              fontSize: 11, color: theme.fgFaint, marginTop: 2,
              fontFamily: BF_FONTS.mono, letterSpacing: 0.2,
            }}>{f.id}</div>
          </div>
          <TierPicker
            value={f.tier}
            saving={saving === ('feature.' + f.id)}
            aH={aH}
            theme={theme}
            onChange={(v) => onSave('feature.' + f.id, { tier: v })}
          />
        </div>
      ))}
    </div>
  );
}

// ── Pattern audience overrides ──────────────────────────────────────
// Each built-in pattern has a hardcoded `audience` default ('all' | 'adult'
// | 'kids'). This section lets a maintainer reclassify a pattern at
// runtime — the app reads `pattern.<id>` from BFConfig, falling back to
// the pattern's built-in default. Useful if we want to test a new kids
// pattern with an adult-only group, or pull a pattern from kids mode
// without a redeploy.
//
// Writes `pattern.<id> = { audience: 'all'|'adult'|'kids' }`. bfPatternAudience
// in theme.jsx already reads these overrides, so nothing else needs wiring.
function AdminPatternAudience({ rows, theme, aH, saving, onSave }) {
  const patterns = (typeof BF_PATTERNS !== 'undefined') ? BF_PATTERNS : [];

  // For each pattern, work out the effective audience: override row if
  // present, else the pattern's built-in default. Keep both values so
  // we can flag a pattern as "overridden" visually.
  const effective = patterns.map((p) => {
    const row = rows.find((r) => r.key === ('pattern.' + p.id));
    const override = row && row.value && row.value.audience;
    const builtin = p.audience || 'all';
    return {
      id: p.id,
      label: p.name,
      builtin,
      value: override || builtin,
      overridden: !!override && override !== builtin,
    };
  });

  return (
    <div style={{
      background: theme.card, border: `1px solid ${theme.line}`,
      borderRadius: 16, padding: 18, marginBottom: 16,
    }}>
      <SectionTitle theme={theme}>Pattern audience</SectionTitle>
      <div style={{ fontSize: 12, color: theme.fgMuted, lineHeight: 1.5, marginBottom: 10 }}>
        Who sees each pattern in Library / Home. <em>All</em> shows it everywhere,
        <em> Adult</em> hides it from kid profiles, <em>Kids</em> shows it only
        to kid profiles. Overrides the built-in default.
      </div>

      {effective.map((p, i) => (
        <div key={p.id} style={{
          display: 'flex', alignItems: 'center', gap: 12,
          padding: '10px 0',
          borderTop: `1px solid ${theme.line}`,
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 500 }}>
              {p.label}
              {p.overridden && (
                <span style={{
                  marginLeft: 8, fontSize: 10, letterSpacing: 0.3,
                  textTransform: 'uppercase', color: `oklch(0.88 0.08 ${aH})`,
                }}>override</span>
              )}
            </div>
            <div style={{
              fontSize: 11, color: theme.fgFaint, marginTop: 2,
              fontFamily: BF_FONTS.mono, letterSpacing: 0.2,
            }}>
              pattern.{p.id} · default: {p.builtin}
            </div>
          </div>
          <AudiencePicker
            value={p.value}
            saving={saving === ('pattern.' + p.id)}
            aH={aH}
            theme={theme}
            onChange={(v) => onSave('pattern.' + p.id, { audience: v })}
          />
        </div>
      ))}
    </div>
  );
}

function AudiencePicker({ value, onChange, saving, theme, aH }) {
  const opts = ['all', 'adult', 'kids'];
  return (
    <div style={{
      display: 'flex', gap: 4, background: 'oklch(0.20 0.018 245)',
      borderRadius: 999, padding: 3,
      opacity: saving ? 0.6 : 1,
    }}>
      {opts.map((o) => (
        <button key={o} onClick={() => onChange(o)} disabled={saving} style={{
          padding: '5px 12px', borderRadius: 999, border: 'none', cursor: 'pointer',
          background: value === o ? `oklch(0.78 0.08 ${aH})` : 'transparent',
          color: value === o ? (theme.bg || '#222') : (theme.fgMuted || '#bbb'),
          fontSize: 11, fontWeight: 500, textTransform: 'capitalize',
        }}>{o}</button>
      ))}
    </div>
  );
}

// ── User entitlements ───────────────────────────────────────────────
function AdminEntitlements({ theme, aH, authHeaders }) {
  const [email, setEmail]       = React.useState('');
  const [row, setRow]           = React.useState(null);
  const [busy, setBusy]         = React.useState(false);
  const [msg, setMsg]           = React.useState(null);
  const [tier, setTier]         = React.useState('plus');
  const [trialDays, setTrialDays] = React.useState(0);

  const lookup = async () => {
    if (!email) return;
    setBusy(true); setMsg(null);
    try {
      const r = await fetch('/api/admin/entitlement?email=' + encodeURIComponent(email), { headers: authHeaders() });
      const j = await r.json();
      if (!j.ok) throw new Error(j.error || 'lookup failed');
      setRow(j.row || null);
      if (j.row && j.row.tier) setTier(j.row.tier);
      setMsg(j.row ? null : 'No entitlement row — will be created as fresh.');
    } catch (e) {
      setMsg(e.message || String(e));
    } finally { setBusy(false); }
  };

  const grant = async () => {
    if (!email) return;
    setBusy(true); setMsg(null);
    try {
      const r = await fetch('/api/admin/entitlement', {
        method: 'POST',
        headers: authHeaders(),
        body: JSON.stringify({
          email,
          tier,
          source: 'promo',
          trial_days: Number(trialDays) || 0,
        }),
      });
      const j = await r.json();
      if (!j.ok) throw new Error(j.error || 'grant failed');
      setRow(j.row);
      setMsg('Saved.');
      if (window.BFToast) BFToast.success('Granted', email + ' → ' + tier);
    } catch (e) {
      setMsg(e.message || String(e));
      if (window.BFToast) BFToast.error('Grant failed', e.message || String(e));
    } finally { setBusy(false); }
  };

  const revoke = async () => {
    if (!email || !row) return;
    if (window.BFDialog) {
      const ok = await BFDialog.confirm({
        title: 'Revoke entitlement?',
        body:  email + ' will drop back to free.',
        confirmLabel: 'Revoke', cancelLabel: 'Keep',
        danger: true,
      });
      if (!ok) return;
    }
    setBusy(true); setMsg(null);
    try {
      const r = await fetch('/api/admin/entitlement?email=' + encodeURIComponent(email), {
        method: 'DELETE',
        headers: authHeaders(),
      });
      const j = await r.json();
      if (!j.ok) throw new Error(j.error || 'revoke failed');
      setRow({ email, tier: 'free', source: 'default' });
      setMsg('Revoked.');
      if (window.BFToast) BFToast.info('Revoked', email);
    } catch (e) {
      setMsg(e.message || String(e));
      if (window.BFToast) BFToast.error('Revoke failed', e.message || String(e));
    } finally { setBusy(false); }
  };

  const inputStyle = {
    flex: 1, minWidth: 0,
    padding: '10px 14px', borderRadius: 12,
    background: theme.cardSoft, color: theme.fg,
    border: `1px solid ${theme.line}`,
    font: 'inherit', fontSize: 13, outline: 'none',
  };
  const btnStyle = (primary) => ({
    padding: '10px 16px', borderRadius: 999, cursor: 'pointer',
    background: primary ? `oklch(0.78 0.08 ${aH})` : 'transparent',
    color: primary ? theme.bg : theme.fgMuted,
    border: primary ? 'none' : `1px solid ${theme.line}`,
    font: 'inherit', fontSize: 12, fontWeight: 600,
    opacity: busy ? 0.6 : 1,
  });

  return (
    <div style={{
      background: theme.card, border: `1px solid ${theme.line}`,
      borderRadius: 16, padding: 18, marginBottom: 16,
    }}>
      <SectionTitle theme={theme}>User entitlements</SectionTitle>
      <div style={{ fontSize: 12, color: theme.fgMuted, lineHeight: 1.5, marginBottom: 12 }}>
        Grant or revoke a tier by email. Trial days starts a Plus/Family trial (0 = no trial).
      </div>

      <div style={{ display: 'flex', gap: 8, marginBottom: 10 }}>
        <input
          type="email" placeholder="user@example.com"
          value={email} onChange={(e) => setEmail(e.target.value.trim())}
          onKeyDown={(e) => { if (e.key === 'Enter') lookup(); }}
          style={inputStyle} autoComplete="off"
        />
        <button onClick={lookup} disabled={busy || !email} style={btnStyle(false)}>Look up</button>
      </div>

      {row && (
        <div style={{
          background: theme.cardSoft, border: `1px solid ${theme.line}`,
          borderRadius: 12, padding: 12, fontSize: 12, color: theme.fgMuted,
          lineHeight: 1.6, marginBottom: 10, fontFamily: BF_FONTS.mono,
        }}>
          <div><strong style={{ color: theme.fg }}>{row.email}</strong></div>
          <div>tier: <strong style={{ color: theme.fg }}>{row.tier}</strong> · source: {row.source || '—'}</div>
          {row.expires_at  && <div>expires_at: {row.expires_at}</div>}
          {row.trial_ends_at && <div>trial_ends_at: {row.trial_ends_at}</div>}
        </div>
      )}

      <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 10 }}>
        <TierPicker value={tier} onChange={setTier} theme={theme} aH={aH} />
        <div style={{ fontSize: 12, color: theme.fgMuted }}>Trial days:</div>
        <input
          type="number" min={0} max={365}
          value={trialDays}
          onChange={(e) => setTrialDays(e.target.value)}
          style={{ ...inputStyle, maxWidth: 80, flex: '0 0 80px' }}
        />
      </div>

      <div style={{ display: 'flex', gap: 8 }}>
        <button onClick={grant} disabled={busy || !email} style={btnStyle(true)}>Grant / update</button>
        {row && row.tier !== 'free' && (
          <button onClick={revoke} disabled={busy} style={{
            ...btnStyle(false),
            color: 'oklch(0.78 0.12 25)', borderColor: 'oklch(0.78 0.12 25 / 0.4)',
          }}>Revoke</button>
        )}
      </div>

      {msg && <div style={{ fontSize: 12, color: theme.fgMuted, marginTop: 10 }}>{msg}</div>}
    </div>
  );
}

// ── Raw config debug table ──────────────────────────────────────────
// ── Stats (L2 operator snapshot) ────────────────────────────────────
// Reads a single aggregate endpoint rather than re-running counts in JS
// on each render. Keeps this component dumb — just fetch + paint.
function AdminStats({ theme, aH, authHeaders }) {
  const [loading, setLoading] = React.useState(false);
  const [err,     setErr]     = React.useState(null);
  const [data,    setData]    = React.useState(null);
  const [at,      setAt]      = React.useState(null);

  const load = React.useCallback(async () => {
    setLoading(true); setErr(null);
    try {
      const r = await fetch('/api/admin/stats', { headers: authHeaders() });
      const j = await r.json();
      if (!j.ok) throw new Error(j.error || 'stats failed');
      setData(j.stats); setAt(j.at);
    } catch (e) {
      setErr(e.message || String(e));
    } finally { setLoading(false); }
  }, [authHeaders]);

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

  const card = {
    background: theme.card, border: `1px solid ${theme.line}`,
    borderRadius: 16, padding: 18, marginBottom: 16,
  };

  return (
    <div style={card}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
        <SectionTitle theme={theme} noMargin>Stats</SectionTitle>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          {at && (
            <div style={{ fontSize: 10, color: theme.fgFaint, fontFamily: BF_FONTS.mono }}>
              {new Date(at).toLocaleTimeString()}
            </div>
          )}
          <button onClick={load} disabled={loading} style={{
            padding: '6px 12px', borderRadius: 999, border: `1px solid ${theme.line}`,
            background: 'transparent', color: theme.fgMuted, fontSize: 11,
            cursor: loading ? 'default' : 'pointer', opacity: loading ? 0.5 : 1,
          }}>{loading ? 'Loading…' : 'Reload'}</button>
        </div>
      </div>

      {err && (
        <div style={{
          background: 'oklch(0.32 0.14 25 / 0.25)', color: 'oklch(0.86 0.13 25)',
          border: '1px solid oklch(0.62 0.17 25 / 0.45)', borderRadius: 10,
          padding: '8px 12px', fontSize: 12, marginBottom: 10,
        }}>{err}</div>
      )}

      {!data && !err && (
        <div style={{ color: theme.fgMuted, fontSize: 13 }}>Loading aggregates…</div>
      )}

      {data && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <StatsGroup theme={theme} title="Signups" tiles={[
            { label: 'Total',    value: data.signups.total },
            { label: 'Last 7d',  value: data.signups.last7d },
            { label: 'Last 30d', value: data.signups.last30d },
          ]}/>
          <StatsSparkline theme={theme} aH={aH} series={data.signups.series} />

          <StatsGroup theme={theme} title="Active users" tiles={[
            { label: 'DAU', value: data.active.dau },
            { label: 'WAU', value: data.active.wau },
            { label: 'MAU', value: data.active.mau },
          ]}/>

          <StatsGroup theme={theme} title="Sessions" tiles={[
            { label: 'Total',        value: data.sessions.total },
            { label: '7d',           value: data.sessions.last7d },
            { label: 'Min all-time', value: data.sessions.minutesAllTime },
            { label: 'Min 7d',       value: data.sessions.minutesLast7d },
          ]}/>

          <StatsGroup theme={theme} title="Tier mix" tiles={[
            { label: 'Free',       value: data.tiers.free },
            { label: 'Plus',       value: data.tiers.plus },
            { label: 'Family',     value: data.tiers.family },
            { label: 'Unassigned', value: data.tiers.unassigned },
          ]}/>

          <StatsGroup theme={theme} title="Reminders" tiles={[
            { label: 'Off',       value: data.reminders.off },
            { label: 'Daily',     value: data.reminders.daily },
            { label: 'Smart',     value: data.reminders.smart },
            { label: 'Push subs', value: data.reminders.pushSubs },
          ]}/>

          <StatsGroup theme={theme} title="Family" tiles={[
            { label: 'Profiles',      value: data.family.totalProfiles },
            { label: 'Kid profiles',  value: data.family.kidProfiles },
            { label: 'Owners w/ kid', value: data.family.ownersWithKid },
            { label: 'Multi-profile', value: data.family.multiProfileOwners },
          ]}/>

          <StatsPatterns theme={theme} aH={aH} patterns={data.patterns} />
        </div>
      )}
    </div>
  );
}

function StatsGroup({ theme, title, tiles }) {
  return (
    <div>
      <div style={{ fontSize: 10, letterSpacing: 1, textTransform: 'uppercase', color: theme.fgFaint, fontFamily: BF_FONTS.mono, marginBottom: 6 }}>{title}</div>
      <div style={{ display: 'grid', gridTemplateColumns: `repeat(${tiles.length}, 1fr)`, gap: 8 }}>
        {tiles.map((t) => (
          <div key={t.label} style={{
            background: theme.bgSoft || 'oklch(0.20 0.018 245)',
            border: `1px solid ${theme.line}`, borderRadius: 10,
            padding: '10px 12px',
          }}>
            <div style={{ fontSize: 9, color: theme.fgFaint, letterSpacing: 0.5, textTransform: 'uppercase' }}>{t.label}</div>
            <div style={{ fontFamily: BF_FONTS.mono, fontSize: 18, color: theme.fg, marginTop: 2 }}>
              {typeof t.value === 'number' ? t.value.toLocaleString() : t.value}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// 30-day signup sparkline — one bar per day, normalised to the window max.
function StatsSparkline({ theme, aH, series }) {
  if (!Array.isArray(series) || series.length === 0) return null;
  const max = Math.max(1, ...series.map((p) => p.n));
  const bar = `oklch(0.78 0.08 ${aH})`;
  return (
    <div>
      <div style={{ fontSize: 10, letterSpacing: 1, textTransform: 'uppercase', color: theme.fgFaint, fontFamily: BF_FONTS.mono, marginBottom: 6 }}>
        Signups · last 30 days
      </div>
      <div style={{
        display: 'flex', alignItems: 'flex-end', gap: 2,
        height: 44, padding: '6px 8px',
        background: theme.bgSoft || 'oklch(0.20 0.018 245)',
        border: `1px solid ${theme.line}`, borderRadius: 10,
      }}>
        {series.map((p) => (
          <div key={p.d}
               title={`${p.d}: ${p.n}`}
               style={{
                 flex: 1, height: `${(p.n / max) * 100}%`, minHeight: 2,
                 background: p.n > 0 ? bar : theme.line,
                 borderRadius: 2, opacity: p.n > 0 ? 0.85 : 0.4,
               }}/>
        ))}
      </div>
    </div>
  );
}

function StatsPatterns({ theme, aH, patterns }) {
  if (!patterns || patterns.length === 0) {
    return (
      <div>
        <div style={{ fontSize: 10, letterSpacing: 1, textTransform: 'uppercase', color: theme.fgFaint, fontFamily: BF_FONTS.mono, marginBottom: 6 }}>Top patterns</div>
        <div style={{ fontSize: 12, color: theme.fgMuted }}>No sessions yet.</div>
      </div>
    );
  }
  const max = Math.max(1, ...patterns.map((p) => p.n));
  const bar = `oklch(0.78 0.08 ${aH})`;
  return (
    <div>
      <div style={{ fontSize: 10, letterSpacing: 1, textTransform: 'uppercase', color: theme.fgFaint, fontFamily: BF_FONTS.mono, marginBottom: 6 }}>
        Top patterns · all time
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
        {patterns.map((p) => (
          <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ flex: '0 0 140px', fontSize: 12, color: theme.fg, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</div>
            <div style={{ flex: 1, height: 8, background: theme.line, borderRadius: 4, overflow: 'hidden' }}>
              <div style={{ width: `${(p.n / max) * 100}%`, height: '100%', background: bar }}/>
            </div>
            <div style={{ flex: '0 0 40px', textAlign: 'right', fontFamily: BF_FONTS.mono, fontSize: 12, color: theme.fgMuted }}>{p.n}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

function AdminRawRows({ rows, theme, onReload }) {
  const [open, setOpen] = React.useState(false);
  return (
    <div style={{
      background: theme.card, border: `1px solid ${theme.line}`,
      borderRadius: 16, padding: 18, marginBottom: 16,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
        <SectionTitle theme={theme} noMargin>Raw config rows</SectionTitle>
        <div style={{ display: 'flex', gap: 6 }}>
          <button onClick={onReload} style={{
            padding: '6px 12px', borderRadius: 999, border: `1px solid ${theme.line}`,
            background: 'transparent', color: theme.fgMuted, fontSize: 11, cursor: 'pointer',
          }}>Reload</button>
          <button onClick={() => setOpen(!open)} style={{
            padding: '6px 12px', borderRadius: 999, border: `1px solid ${theme.line}`,
            background: 'transparent', color: theme.fgMuted, fontSize: 11, cursor: 'pointer',
          }}>{open ? 'Hide' : 'Show'} ({rows.length})</button>
        </div>
      </div>
      {open && (
        <pre style={{
          marginTop: 12, padding: 12, borderRadius: 10,
          background: theme.bgSoft || 'oklch(0.2 0.018 245)',
          color: theme.fgMuted, fontSize: 11, lineHeight: 1.5,
          overflow: 'auto', maxHeight: 320,
          fontFamily: BF_FONTS.mono, whiteSpace: 'pre-wrap',
        }}>{JSON.stringify(rows, null, 2)}</pre>
      )}
    </div>
  );
}

// ── Shared controls ─────────────────────────────────────────────────
function SectionTitle({ children, theme, noMargin }) {
  return (
    <div style={{
      fontSize: 11, letterSpacing: 1, textTransform: 'uppercase',
      color: theme.fgFaint, fontFamily: BF_FONTS.mono,
      marginBottom: noMargin ? 0 : 8,
    }}>{children}</div>
  );
}

function BigToggle({ on, saving, aH, onClick }) {
  return (
    <button
      onClick={onClick}
      disabled={saving}
      style={{
        width: 56, height: 32, borderRadius: 999, border: 'none', cursor: 'pointer',
        background: on ? `oklch(0.78 0.08 ${aH})` : 'oklch(0.27 0.018 245)',
        position: 'relative', transition: 'background .2s',
        opacity: saving ? 0.6 : 1,
      }}
    >
      <div style={{
        position: 'absolute', top: 3, left: on ? 27 : 3,
        width: 26, height: 26, borderRadius: 999, background: '#fff',
        transition: 'left .2s', boxShadow: '0 1px 3px rgba(0,0,0,.3)',
      }}/>
    </button>
  );
}

function NumberStepper({ value, min, max, saving, theme, onChange }) {
  const dec = () => { if (value > min) onChange(value - 1); };
  const inc = () => { if (value < max) onChange(value + 1); };
  const btn = {
    width: 30, height: 30, borderRadius: 999, border: `1px solid ${theme.line}`,
    background: 'transparent', color: theme.fgMuted, fontSize: 14, cursor: 'pointer',
    fontWeight: 600,
  };
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, opacity: saving ? 0.6 : 1 }}>
      <button onClick={dec} disabled={saving || value <= min} style={btn}>−</button>
      <div style={{
        minWidth: 28, textAlign: 'center',
        fontFamily: BF_FONTS.mono, fontSize: 14, color: theme.fg,
      }}>{value}</div>
      <button onClick={inc} disabled={saving || value >= max} style={btn}>+</button>
    </div>
  );
}

function TierPicker({ value, onChange, saving, theme, aH }) {
  const opts = ['free', 'plus', 'family'];
  return (
    <div style={{
      display: 'flex', gap: 4, background: 'oklch(0.20 0.018 245)',
      borderRadius: 999, padding: 3,
      opacity: saving ? 0.6 : 1,
    }}>
      {opts.map((o) => (
        <button key={o} onClick={() => onChange(o)} disabled={saving} style={{
          padding: '5px 12px', borderRadius: 999, border: 'none', cursor: 'pointer',
          background: value === o ? `oklch(0.78 0.08 ${aH})` : 'transparent',
          color: value === o ? (theme.bg || '#222') : (theme.fgMuted || '#bbb'),
          fontSize: 11, fontWeight: 500, textTransform: 'capitalize',
        }}>{o}</button>
      ))}
    </div>
  );
}

// Helper: pull a config value out of the rows array.
function rowValue(rows, key) {
  const r = rows.find((x) => x.key === key);
  return r ? r.value : null;
}

Object.assign(window, { BFAdmin });
