// Admin screens: GodView, Approvals, KillSwitch, Settings, ExchangeBalance, AdminBotDetail.

const REGIME_SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT'];

function _recommendationTone(rec) {
  if (!rec) return 'muted';
  const r = rec.toUpperCase();
  if (r.includes('MOMENTUM')) return 'pos';
  if (r.includes('MEAN') || r.includes('VWAP')) return 'warn';
  if (r.includes('BREAKOUT')) return 'warn';
  if (r.includes('STAND') || r.includes('FLAT') || r.includes('ASIDE')) return 'neg';
  return 'muted';
}

function _volTone(vol) {
  if (vol === 'LOW') return 'pos';
  if (vol === 'NORMAL') return 'warn';
  if (vol === 'HIGH') return 'warn';
  if (vol === 'EXTREME') return 'neg';
  return 'muted';
}

function RegimeCard() {
  const [symbol, setSymbol] = React.useState('BTCUSDT');
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const [refreshedAt, setRefreshedAt] = React.useState(null);

  const fetchSnap = React.useCallback(async (sym) => {
    setLoading(true); setErr(null);
    try {
      const d = await api.get('/api/admin/regime/' + encodeURIComponent(sym));
      setData(d);
      setRefreshedAt(new Date());
    } catch (e) {
      setErr(e.detail || e.message || 'fetch failed');
    } finally {
      setLoading(false);
    }
  }, []);

  React.useEffect(() => {
    let alive = true;
    fetchSnap(symbol);
    const t = setInterval(() => { if (alive) fetchSnap(symbol); }, 5 * 60 * 1000);
    return () => { alive = false; clearInterval(t); };
  }, [symbol, fetchSnap]);

  const recTone = data ? _recommendationTone(data.recommendation) : 'muted';
  const volTone = data ? _volTone(data.vol_regime) : 'muted';

  return (
    <div className="card">
      <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-start' }}>
        <div>
          <h2 className="h2">Market regime</h2>
          <div className="tiny muted">
            Live read from Bybit V5 · auto-refresh 5 min
            {refreshedAt && <span> · last: {refreshedAt.toLocaleTimeString()}</span>}
          </div>
        </div>
        <div className="row gap-8">
          <select
            value={symbol}
            onChange={e => setSymbol(e.target.value)}
            style={{ background: 'var(--card)', color: 'var(--ink)', border: '1px solid var(--line)', borderRadius: 8, padding: '6px 10px', fontWeight: 600 }}
          >
            {REGIME_SYMBOLS.map(s => <option key={s} value={s}>{s}</option>)}
          </select>
          <button className="btn ghost sm" onClick={() => fetchSnap(symbol)} disabled={loading}>
            {loading ? '...' : 'Refresh'}
          </button>
        </div>
      </div>

      {err && <div className="tiny" style={{ color: 'var(--neg)', marginTop: 12 }}>Error: {err}</div>}

      {data && (
        <div style={{ marginTop: 18 }}>
          <div className="row" style={{ justifyContent: 'space-between', alignItems: 'baseline', flexWrap: 'wrap', gap: 12 }}>
            <div className="num" style={{ fontSize: 32, fontWeight: 700 }}>
              ${fmtNum(data.price, 2)}
            </div>
            <div className="row gap-8" style={{ flexWrap: 'wrap' }}>
              <span className="pill"><span className="dot"/>Trend {data.trend_score}/4</span>
              <span className={'pill ' + volTone}><span className="dot"/>Vol {data.vol_regime}</span>
              <span className={'pill ' + recTone}><span className="dot"/>{data.recommendation}</span>
            </div>
          </div>

          <div className="tiny muted" style={{ marginTop: 10 }}>{data.rationale}</div>

          <div className="grid" style={{ gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginTop: 18 }}>
            <RegimeMetric label="Daily EMA50" value={data.daily?.ema50 ? '$' + fmtNum(data.daily.ema50, 2) : '—'}/>
            <RegimeMetric label="Daily EMA200" value={data.daily?.ema200 ? '$' + fmtNum(data.daily.ema200, 2) : '—'}/>
            <RegimeMetric label="Daily ADX(14)" value={(data.daily?.adx14 ?? 0).toFixed(1)}/>
            <RegimeMetric label="30d realized vol" value={((data.daily?.realized_vol_30d ?? 0) * 100).toFixed(1) + '%'}/>
            <RegimeMetric label="1H RSI(14)" value={(data.hourly?.rsi14 ?? 0).toFixed(1)}/>
            <RegimeMetric label="1H ADX(14)" value={(data.hourly?.adx14 ?? 0).toFixed(1)}/>
            <RegimeMetric label="1H ATR(14)" value={'$' + fmtNum(data.hourly?.atr14 ?? 0, 2)}/>
            <RegimeMetric label="20D swing range" value={
              data.structure_20d
                ? '$' + fmtNum(data.structure_20d.swing_low, 0) + ' – $' + fmtNum(data.structure_20d.swing_high, 0)
                : '—'
            }/>
          </div>
        </div>
      )}

      {!data && !err && loading && (
        <div className="muted" style={{ marginTop: 14 }}>Fetching regime…</div>
      )}
    </div>
  );
}

function RegimeMetric({ label, value }) {
  return (
    <div>
      <div className="tiny muted" style={{ textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>{label}</div>
      <div className="num" style={{ fontSize: 16, fontWeight: 600, marginTop: 4 }}>{value}</div>
    </div>
  );
}

function AdminGodView({ go }) {
  const [dash] = useApi('/api/dashboard/admin');
  const [bots] = useApi('/api/bots');
  const [snapshots, setSnapshots] = React.useState({});
  const [bal, balActions] = useApi('/api/admin/exchange/balance');

  // Per-bot scanner snapshot polling (admin overview)
  React.useEffect(() => {
    if (!bots) return;
    let alive = true;
    const fetchAll = async () => {
      const out = {};
      await Promise.all(bots.filter(b => b.is_active && !b.is_kill_switched).map(async (b) => {
        try { out[b.id] = await api.get(`/api/admin/bots/${b.id}/scanner`); } catch {}
      }));
      if (alive) setSnapshots(out);
    };
    fetchAll();
    const t = setInterval(fetchAll, 15000);
    return () => { alive = false; clearInterval(t); };
  }, [bots]);

  return (
    <>
      <div className="topbar">
        <div>
          <div className="crumb">Admin · God view</div>
          <h1 className="h1">Command center</h1>
        </div>
        <div className="row gap-8">
          {dash && <span className="pill pos"><span className="dot"/>All systems live</span>}
          <button className="btn ghost" onClick={() => go('settings')}><IconSettings size={16}/> Settings</button>
        </div>
      </div>
      <div className="content">
        <div className="grid grid-4">
          <KPI label="Live AUM" value={USD(dash?.total_aum)} sub={((dash?.total_active_bots || 0) - (dash?.paper_active_bots || 0)) + ' live bots'}/>
          <KPI label="Live PnL" value={USD(dash?.platform_pnl, { sign: true })} pos={dash?.platform_pnl >= 0} neg={dash?.platform_pnl < 0}/>
          <KPI label="Company reserve" value={USD(dash?.company_reserve)} sub="5% of live AUM"/>
          <KPI label="Members" value={String(dash?.total_users || 0)} sub={(dash?.pending_kyc || 0) + ' pending KYC'}/>
        </div>

        {/* Bybit wallet vs platform AUM reconciliation. The platform's
            internal accounting (Live AUM + Company Reserve) is bookkeeping —
            Bybit Wallet Balance is the real exchange total. The gap is
            UNINVESTED CASH on the exchange that hasn't been allocated to
            any bot via the subscribe flow. */}
        {bal && (() => {
          const platformTotal = (dash?.total_aum || 0) + (dash?.company_reserve || 0);
          const bybitTotal = bal.total_margin_balance || bal.total_wallet_balance || 0;
          const diff = bybitTotal - platformTotal;
          const borderColor = Math.abs(diff) < 1 ? 'var(--pos)'
                            : (diff > 0 ? 'var(--ink-3)' : 'var(--neg)');
          const label = Math.abs(diff) < 1 ? 'Reconciled'
                      : (diff > 0 ? 'Uninvested cash on exchange' : 'PLATFORM AUM > BYBIT — investigate');
          return (
            <div className="card" style={{ padding: '14px 18px', borderLeft: '3px solid ' + borderColor }}>
              <div className="row" style={{ justifyContent: 'space-between', alignItems: 'center' }}>
                <div>
                  <div className="tiny muted" style={{ textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>
                    Exchange reconciliation {bal.testnet ? '(testnet)' : '(mainnet)'}
                  </div>
                  <div className="row gap-16" style={{ marginTop: 6 }}>
                    <div>
                      <span className="tiny muted">Bybit wallet </span>
                      <span className="num" style={{ fontWeight: 600 }}>{USD(bybitTotal)}</span>
                    </div>
                    <div>
                      <span className="tiny muted">Platform (AUM + Reserve) </span>
                      <span className="num" style={{ fontWeight: 600 }}>{USD(platformTotal)}</span>
                    </div>
                    <div>
                      <span className="tiny muted">Diff </span>
                      <span className="num" style={{ fontWeight: 600, color: borderColor }}>{USD(diff, { sign: true })}</span>
                    </div>
                  </div>
                </div>
                <span className="pill" style={{ color: borderColor, borderColor: borderColor }}>
                  <span className="dot" style={{ background: borderColor }}/>{label}
                </span>
              </div>
            </div>
          );
        })()}

        {(dash?.paper_active_bots || 0) > 0 && (
          <div className="card" style={{ padding: '14px 18px', borderLeft: '3px solid var(--warn, #d4a72c)' }}>
            <div className="row" style={{ justifyContent: 'space-between', alignItems: 'center' }}>
              <div>
                <div className="tiny muted" style={{ textTransform: 'uppercase', letterSpacing: '0.08em', fontWeight: 600 }}>
                  Paper-trading totals (simulated, not real money)
                </div>
                <div className="row gap-16" style={{ marginTop: 6 }}>
                  <div>
                    <span className="tiny muted">AUM </span>
                    <span className="num" style={{ fontWeight: 600 }}>{USD(dash?.paper_aum)}</span>
                  </div>
                  <div>
                    <span className="tiny muted">PnL </span>
                    <span className="num" style={{ fontWeight: 600, color: (dash?.paper_pnl || 0) >= 0 ? 'var(--pos)' : 'var(--neg)' }}>
                      {USD(dash?.paper_pnl, { sign: true })}
                    </span>
                  </div>
                  <div>
                    <span className="tiny muted">Bots </span>
                    <span className="num" style={{ fontWeight: 600 }}>{dash?.paper_active_bots}</span>
                  </div>
                </div>
              </div>
              <span className="pill warn"><span className="dot"/>Paper</span>
            </div>
          </div>
        )}

        <RegimeCard/>

        <div className="grid" style={{ gridTemplateColumns: '1.4fr 1fr', gap: 22 }}>
          <ExchangeCard bal={bal} reload={balActions.reload}/>
          <div className="card dark">
            <h2 className="h2" style={{ color: 'inherit' }}>Pending queue</h2>
            <p className="muted" style={{ fontSize: 13.5, marginTop: 8, lineHeight: 1.5 }}>Deposits, withdrawals, and KYC awaiting your review.</p>
            <div style={{ marginTop: 18, padding: 18, borderRadius: 14, background: 'rgba(255,255,255,0.06)', display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 12 }}>
              <QueueStat n={dash?.pending_deposits || 0} l="Deposits"/>
              <QueueStat n={dash?.pending_withdrawals || 0} l="Withdrawals"/>
              <QueueStat n={dash?.pending_kyc || 0} l="KYC"/>
            </div>
            <button className="btn accent lg block" style={{ marginTop: 14 }} onClick={() => go('approvals')}>Open queue <IconArrowRt size={16}/></button>
          </div>
        </div>

        <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
          <div style={{ padding: '20px 22px 8px' }}>
            <div className="row" style={{ justifyContent: 'space-between' }}>
              <div>
                <h2 className="h2">Bot scanner activity</h2>
                <div className="tiny muted">Live per-bot status · auto-refreshes every 15s</div>
              </div>
              <button className="btn ghost sm" onClick={() => go('killswitch')}>Manage <IconArrowRt size={14}/></button>
            </div>
          </div>
          <table className="tbl">
            <thead><tr><th>Bot</th><th>Unit price</th><th>AUM</th><th>Status</th><th>Last scan</th><th>Top candidate</th><th></th></tr></thead>
            <tbody>
              {(bots || []).map(b => {
                const snap = snapshots[b.id];
                const top = snap?.top_candidates?.[0];
                const ageS = snap?.last_scan_at ? Math.round((Date.now() - new Date(snap.last_scan_at).getTime()) / 1000) : null;
                return (
                  <tr key={b.id} style={{ cursor: 'pointer' }} onClick={() => go('admin-bot:' + b.id)}>
                    <td>
                      <div className="row gap-12">
                        <div className="bot-emblem" style={{ width: 32, height: 32, borderRadius: 9, background: colorFor(b.id), fontSize: 13 }}>{emojiFor(b.risk_category)}</div>
                        <div>
                          <div className="row gap-8" style={{ alignItems: 'center' }}>
                            <span style={{ fontWeight: 600 }}>{b.name}</span>
                            {b.paper_mode && <span className="pill warn" style={{ fontSize: 10, padding: '2px 6px' }}>PAPER</span>}
                          </div>
                          <div className="tiny muted" style={{ textTransform: 'capitalize' }}>{b.risk_category}</div>
                        </div>
                      </div>
                    </td>
                    <td className="num" style={{ fontWeight: 600 }}>${fmtNum(b.unit_price, 4)}</td>
                    <td className="num" style={b.paper_mode ? { opacity: 0.6 } : {}}>{USD(b.current_value, { decimals: 2 })}</td>
                    <td>
                      <span className={'pill ' + (b.is_kill_switched ? 'neg' : b.is_active ? 'pos' : 'warn')}><span className="dot"/>{b.is_kill_switched ? 'KILLED' : b.is_active ? 'Active' : 'Off'}</span>
                    </td>
                    <td className="muted tiny">{ageS != null ? ageS + 's ago' : '—'}</td>
                    <td className="tiny">
                      {top ? (
                        <span><strong className="num">{top.symbol}</strong> · <span className={(top.score ?? 0) >= 0.70 ? 'num' : 'num muted'} style={(top.score ?? 0) >= 0.70 ? { color: 'var(--pos)' } : {}}>{(top.score ?? 0).toFixed(2)}</span></span>
                      ) : <span className="muted">—</span>}
                    </td>
                    <td><IconArrowRt size={16} style={{ color: 'var(--ink-4)' }}/></td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
    </>
  );
}

function QueueStat({ n, l }) {
  return (
    <div>
      <div style={{ fontSize: 11, opacity: 0.6, letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 600 }}>{l}</div>
      <div className="num" style={{ fontSize: 28, fontWeight: 700, marginTop: 4 }}>{n}</div>
    </div>
  );
}

function ExchangeCard({ bal, reload }) {
  if (!bal) return <div className="card"><div className="muted">Loading exchange balance…</div></div>;
  if (bal._error || bal.detail) {
    return (
      <div className="card">
        <h2 className="h2">Exchange balance</h2>
        <div style={{ fontSize: 13, color: 'var(--neg)', background: 'var(--neg-soft)', padding: 10, borderRadius: 8, marginTop: 10 }}>
          {typeof bal.detail === 'string' ? bal.detail : bal._error || 'Connection failed'}
        </div>
      </div>
    );
  }
  return (
    <div className="card">
      <div className="row" style={{ justifyContent: 'space-between', marginBottom: 6 }}>
        <h2 className="h2">Bybit balance</h2>
        <div className="row gap-8">
          <span className={'pill ' + (bal.testnet ? 'warn' : 'neg')}><span className="dot"/>{bal.testnet ? 'TESTNET' : 'MAINNET'}</span>
          <button className="btn ghost sm" onClick={reload}><IconArrowRt size={14}/> Refresh</button>
        </div>
      </div>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 14 }}>
        <div className="num chart-value" style={{ fontSize: 32, fontWeight: 700, letterSpacing: '-0.025em' }}>{USD(bal.total_wallet_balance, { decimals: 4 })}</div>
        <span className={'pill ' + (bal.total_unrealized_pnl >= 0 ? 'pos' : 'neg')}>
          {bal.total_unrealized_pnl >= 0 ? <IconArrowUp size={11}/> : <IconArrowDn size={11}/>}
          {USD(bal.total_unrealized_pnl, { sign: true, decimals: 4 })}
        </span>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12, fontSize: 13 }}>
        <div><div className="tiny muted">Available</div><div className="num" style={{ fontWeight: 600 }}>{USD(bal.available_balance, { decimals: 4 })}</div></div>
        <div><div className="tiny muted">Margin</div><div className="num" style={{ fontWeight: 600 }}>{USD(bal.total_margin_balance, { decimals: 4 })}</div></div>
        <div><div className="tiny muted">Open positions</div><div className="num" style={{ fontWeight: 600 }}>{bal.open_positions_count}</div></div>
      </div>
    </div>
  );
}

/* ---------- Approvals ---------- */
function Approvals() {
  const [items, { reload: reloadTx }] = useApi('/api/transactions/pending');
  const [pendingUsers, { reload: reloadUsers }] = useApi('/api/admin/users?approval_status=pending');
  const [filter, setFilter] = React.useState('ALL');
  const txs = items || [];
  const users = pendingUsers || [];
  const filtered = txs.filter(i => filter === 'ALL' || i.transaction_type.toUpperCase() === filter);
  const showUsers = filter === 'ALL' || filter === 'SIGNUP';
  const visibleUsers = showUsers ? users : [];
  const totalPending = txs.length + users.length;
  const hasPendingDeposit = txs.some(i => i.transaction_type === 'deposit');

  const approveTx = async (id) => { await api.put(`/api/transactions/${id}/approve`); reloadTx(); };
  const rejectTx = async (id) => {
    const reason = prompt('Rejection reason:'); if (reason == null) return;
    await api.put(`/api/transactions/${id}/reject?reason=${encodeURIComponent(reason)}`);
    reloadTx();
  };
  const approveUser = async (id) => { await api.post(`/api/admin/users/${id}/approve`); reloadUsers(); };
  const rejectUser = async (id) => {
    const reason = prompt('Rejection reason:'); if (reason == null) return;
    await api.post(`/api/admin/users/${id}/reject?reason=${encodeURIComponent(reason)}`);
    reloadUsers();
  };

  return (
    <>
      <div className="topbar">
        <div>
          <div className="crumb">Admin · Queue</div>
          <h1 className="h1">Approvals <span className="muted" style={{ fontWeight: 500 }}>· {totalPending} pending</span></h1>
        </div>
        <div className="row gap-6">
          {['ALL','SIGNUP','DEPOSIT','WITHDRAWAL'].map(f => (
            <button key={f} className={'btn ' + (filter === f ? 'primary' : 'ghost') + ' sm'} onClick={() => setFilter(f)}>{f}</button>
          ))}
        </div>
      </div>
      <div className="content">
        {hasPendingDeposit && filter !== 'WITHDRAWAL' && filter !== 'SIGNUP' && <DepositVerifyPanel/>}

        {/* Pending user signups (with KYC details) */}
        {visibleUsers.map(u => {
          const kycReady = u.kyc_status === 'pending' && !!u.ktp_number;
          return (
            <div key={'u' + u.id} className="card" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
              <div style={{ display: 'grid', gridTemplateColumns: '40px 1fr auto', gap: 16, alignItems: 'center' }}>
                <div style={{ width: 40, height: 40, borderRadius: 12, background: 'var(--accent-soft)', color: 'var(--accent)', display: 'grid', placeItems: 'center' }}>
                  <IconUser size={18}/>
                </div>
                <div style={{ minWidth: 0 }}>
                  <div className="row gap-8">
                    <strong>{u.full_name || u.username}</strong>
                    <span className="pill accent"><span className="dot"/>signup</span>
                    {kycReady
                      ? <span className="pill pos"><span className="dot"/>KYC submitted</span>
                      : <span className="pill warn"><span className="dot"/>KYC missing</span>}
                  </div>
                  <div className="tiny muted" style={{ marginTop: 2 }}>
                    {u.email} · @{u.username} · joined {fmtDateTime(u.created_at)}
                  </div>
                </div>
                <div className="row gap-8">
                  <button className="btn ghost sm" onClick={() => rejectUser(u.id)}><IconX size={14}/> Reject</button>
                  <button className="btn primary sm" onClick={() => approveUser(u.id)} disabled={!kycReady}
                          title={kycReady ? 'Approve signup + KYC' : 'Waiting for KYC submission'}>
                    <IconCheck size={14}/> Approve
                  </button>
                </div>
              </div>
              {/* KYC details strip — only when submitted */}
              {kycReady && (
                <div style={{
                  paddingTop: 12, borderTop: '1px solid var(--line-2)',
                  display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 14, fontSize: 13,
                }}>
                  <div>
                    <div className="tiny muted" style={{ letterSpacing: '0.14em', textTransform: 'uppercase', fontSize: 10 }}>Full name (KTP)</div>
                    <div style={{ marginTop: 2, fontWeight: 500 }}>{u.ktp_full_name || '—'}</div>
                  </div>
                  <div>
                    <div className="tiny muted" style={{ letterSpacing: '0.14em', textTransform: 'uppercase', fontSize: 10 }}>NIK</div>
                    <div className="num" style={{ marginTop: 2, fontWeight: 500 }}>{u.ktp_number || '—'}</div>
                  </div>
                  <div>
                    <div className="tiny muted" style={{ letterSpacing: '0.14em', textTransform: 'uppercase', fontSize: 10 }}>Submitted</div>
                    <div className="tiny muted" style={{ marginTop: 2 }}>{u.kyc_submitted_at ? fmtDateTime(u.kyc_submitted_at) : '—'}</div>
                  </div>
                </div>
              )}
              {!kycReady && (
                <div className="tiny" style={{ color: 'var(--warn)' }}>
                  This user hasn't filled in KYC yet. They'll be redirected to the KYC form when they sign in; approval is blocked until they submit.
                </div>
              )}
            </div>
          );
        })}

        {/* Pending transactions */}
        {filtered.map(item => (
          <div key={item.id} className="card" style={{ display: 'grid', gridTemplateColumns: '40px 1fr auto auto', gap: 16, alignItems: 'center' }}>
            <div style={{ width: 40, height: 40, borderRadius: 12,
              background: item.transaction_type === 'deposit' ? 'var(--pos-soft)' : 'var(--warn-soft)',
              color: item.transaction_type === 'deposit' ? 'var(--pos)' : 'var(--warn)',
              display: 'grid', placeItems: 'center' }}>
              {item.transaction_type === 'deposit' ? <IconPlus size={18}/> : <IconMinus size={18}/>}
            </div>
            <div style={{ minWidth: 0 }}>
              <div className="row gap-8">
                <strong>User #{item.user_id}</strong>
                <span className="pill" style={{ textTransform: 'capitalize' }}><span className="dot"/>{item.transaction_type}</span>
              </div>
              <div className="tiny muted" style={{ marginTop: 2 }}>#{item.id} · {fmtDateTime(item.created_at)}{item.description ? ' · ' + item.description : ''}</div>
            </div>
            <div className="num" style={{ textAlign: 'right', fontWeight: 700, fontSize: 18 }}>{USD(item.amount)}</div>
            <div className="row gap-8">
              <button className="btn ghost sm" onClick={() => rejectTx(item.id)}><IconX size={14}/> Reject</button>
              <button className="btn primary sm" onClick={() => approveTx(item.id)}><IconCheck size={14}/> Approve</button>
            </div>
          </div>
        ))}

        {filtered.length === 0 && visibleUsers.length === 0 && items && pendingUsers && (
          <div className="card" style={{ textAlign: 'center', padding: 60, color: 'var(--ink-4)' }}>
            <IconCheck size={32} style={{ color: 'var(--pos)', marginBottom: 8 }}/>
            <div style={{ fontWeight: 600, color: 'var(--ink-2)' }}>Inbox zero</div>
            <div className="tiny muted" style={{ marginTop: 4 }}>Nothing waiting in this queue.</div>
          </div>
        )}
      </div>
    </>
  );
}

/* Deposit verification — shows the live Bybit USDT wallet balance + recent
   on-chain deposit history so admin can match a pending request to the actual
   incoming transfer before approving. Pure read-only — approval itself goes
   through the existing per-row Approve button. */
function DepositVerifyPanel() {
  const [bal, balActions] = useApi('/api/admin/exchange/balance');
  const [deps, depActions] = useApi('/api/admin/exchange/deposits/recent');
  const refresh = () => { balActions.reload(); depActions.reload(); };
  const usdt = (bal?.assets || []).find(a => a.asset === 'USDT');
  return (
    <div className="card" style={{ borderColor: 'var(--accent-soft)' }}>
      <div className="row" style={{ justifyContent: 'space-between', marginBottom: 14 }}>
        <div>
          <div className="crumb">Verify on Bybit</div>
          <div className="tiny muted" style={{ marginTop: 2 }}>Check that the on-chain transfer landed before approving.</div>
        </div>
        <button className="btn ghost sm" onClick={refresh}>↻ Refresh</button>
      </div>
      <div className="grid grid-3" style={{ marginBottom: 14 }}>
        <div className="kpi">
          <div className="kpi-label">USDT wallet</div>
          <div className="kpi-value num">{usdt ? USD(usdt.wallet_balance) : '—'}</div>
          <div className="tiny muted">total balance now</div>
        </div>
        <div className="kpi">
          <div className="kpi-label">Available</div>
          <div className="kpi-value num">{usdt ? USD(usdt.available_balance) : '—'}</div>
          <div className="tiny muted">not in open positions</div>
        </div>
        <div className="kpi">
          <div className="kpi-label">Unrealized PnL</div>
          <div className="kpi-value num" style={{ color: (usdt?.unrealized_pnl ?? 0) >= 0 ? 'var(--pos)' : 'var(--neg)' }}>
            {usdt ? USD(usdt.unrealized_pnl, { sign: true }) : '—'}
          </div>
          <div className="tiny muted">{bal?.open_positions_count ?? 0} open position(s)</div>
        </div>
      </div>
      <div className="tiny muted" style={{ marginBottom: 6, letterSpacing: '0.14em', textTransform: 'uppercase', fontSize: 10 }}>Recent USDT deposits (Bybit)</div>
      {!deps && <div className="muted tiny">Loading…</div>}
      {deps && deps.deposits && deps.deposits.length === 0 && (
        <div className="muted tiny">No deposit records returned by Bybit yet.</div>
      )}
      {deps && deps.deposits && deps.deposits.length > 0 && (
        <table className="tbl">
          <thead><tr><th>When</th><th>Amount</th><th>Chain</th><th>Status</th><th>Tx</th></tr></thead>
          <tbody>
            {deps.deposits.map(d => (
              <tr key={d.tx_id || (d.success_at + '-' + d.amount)}>
                <td className="num tiny">{d.success_at ? new Date(d.success_at).toLocaleString() : '—'}</td>
                <td className="num" style={{ fontWeight: 600 }}>{USD(d.amount)}</td>
                <td className="tiny">{d.chain}</td>
                <td>
                  <span className={'pill ' + (d.status === 'success' ? 'pos' : d.status === 'pending' || d.status === 'pending_credit' ? 'warn' : 'neg')}>
                    <span className="dot"/>{d.status}
                  </span>
                </td>
                <td className="num tiny" style={{ maxWidth: 220, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={d.tx_id}>{d.tx_id || '—'}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

/* ---------- KillSwitch / Bot Control ---------- */
function KillSwitch({ go }) {
  const [bots, { reload }] = useApi('/api/bots');
  const toggle = async (id) => { await api.post(`/api/bots/${id}/toggle-kill-switch`); reload(); };
  const activeCount = (bots || []).filter(b => b.is_active && !b.is_kill_switched).length;
  return (
    <>
      <div className="topbar">
        <div>
          <div className="crumb">Admin · Risk</div>
          <h1 className="h1">Bot control</h1>
        </div>
        <span className="pill pos"><span className="dot"/>{activeCount}/{(bots || []).length} active</span>
      </div>
      <div className="content">
        <div className="grid grid-2">
          {(bots || []).map(b => (
            <div key={b.id} className="card" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
              <div className="row" style={{ justifyContent: 'space-between' }}>
                <div className="row gap-12">
                  <div className="bot-emblem" style={{ background: colorFor(b.id), width: 38, height: 38, fontSize: 15 }}>{emojiFor(b.risk_category)}</div>
                  <div>
                    <div style={{ fontWeight: 700 }}>{b.name}</div>
                    <div className="tiny muted" style={{ textTransform: 'capitalize' }}>{b.risk_category} · {USD(b.current_value, { decimals: 2 })}</div>
                  </div>
                </div>
                <ToggleSwitch on={b.is_active && !b.is_kill_switched} onChange={() => toggle(b.id)}/>
              </div>
              <div className="row gap-8">
                <button className="btn ghost sm" onClick={() => go('admin-bot:' + b.id)}>Details <IconArrowRt size={13}/></button>
                <span className={'pill ' + (b.is_kill_switched ? 'neg' : b.is_active ? 'pos' : 'warn')} style={{ marginLeft: 'auto' }}>
                  <span className="dot"/>{b.is_kill_switched ? 'KILLED' : b.is_active ? 'Active' : 'Off'}
                </span>
              </div>
            </div>
          ))}
        </div>
      </div>
    </>
  );
}

/* ---------- Settings (Platform Settings) ---------- */
function Settings() {
  const [list, { reload }] = useApi('/api/admin/settings');
  return (
    <>
      <div className="topbar">
        <div>
          <div className="crumb">Admin · Config</div>
          <h1 className="h1">Platform settings</h1>
        </div>
      </div>
      <div className="content" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        <PerformanceFeeCard/>
        <div className="card" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {(list || []).map(s => <SettingRow key={s.key} s={s} onSaved={reload}/>)}
        </div>
      </div>
    </>
  );
}

function PerformanceFeeCard() {
  const [data, { reload, loading }] = useApi('/api/admin/settings/performance-fee');
  const [busy, setBusy] = React.useState({});
  const [err, setErr] = React.useState(null);
  const [flash, setFlash] = React.useState(null);
  const sweep = async (botId) => {
    if (!confirm('Sweep accumulated performance fee for this bot? This zeroes the counter (placeholder — no company wallet credit yet).')) return;
    setBusy(b => ({ ...b, [botId]: true })); setErr(null); setFlash(null);
    try {
      const r = await api.post('/api/admin/settings/performance-fee/sweep', { bot_id: botId });
      setFlash(`Swept ${r.swept_amount.toFixed(8)} USDT from bot ${botId}.`);
      reload();
    } catch (e) {
      setErr(e.detail || e.message || 'sweep failed');
    }
    setBusy(b => ({ ...b, [botId]: false }));
  };
  return (
    <div className="card">
      <div className="row gap-8" style={{ alignItems: 'baseline', marginBottom: 10 }}>
        <div className="crumb">Company share</div>
        <span className="pill"><span className="dot"/>rate {data ? `${(data.rate * 100).toFixed(1)}%` : '—'}</span>
        <span className="pill accent" style={{ marginLeft: 'auto' }}>
          <span className="dot"/>total accumulated {data ? data.total_accumulated.toFixed(8) : '—'} USDT
        </span>
      </div>
      <div className="tiny muted" style={{ marginBottom: 10 }}>
        Rate is edited below as <code>PERFORMANCE_FEE_RATE</code>. Sweep zeroes the bot's counter — for now this is a placeholder; a future iteration will credit a company wallet record and emit an audit trail.
      </div>
      {err && <div style={{ fontSize: 13, color: 'var(--neg)', marginBottom: 8 }}>{err}</div>}
      {flash && <div style={{ fontSize: 13, color: 'var(--pos)', marginBottom: 8 }}>{flash}</div>}
      {loading && !data ? <div className="tiny muted">Loading…</div> : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {(data?.per_bot || []).map(b => (
            <div key={b.bot_id} style={{ display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 12, alignItems: 'center', padding: '8px 0', borderTop: '1px solid var(--line-2)' }}>
              <div>
                <div style={{ fontWeight: 600 }}>{b.bot_name} <span className="tiny muted">· bot {b.bot_id}</span></div>
              </div>
              <div className="num" style={{ fontSize: 14, fontVariantNumeric: 'tabular-nums' }}>
                {(b.accumulated_performance_fee ?? 0).toFixed(8)} USDT
              </div>
              <button
                className="btn sm"
                onClick={() => sweep(b.bot_id)}
                disabled={!!busy[b.bot_id] || b.accumulated_performance_fee <= 0}
                title={b.accumulated_performance_fee <= 0 ? 'nothing accrued yet' : 'sweep (placeholder)'}
              >
                {busy[b.bot_id] ? 'Sweeping…' : 'Sweep'}
              </button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function SettingRow({ s, onSaved }) {
  const [val, setVal] = React.useState(
    s.type === 'bool' ? String(!!s.current_value)
    : s.type === 'secret' ? ''
    : (s.current_value ?? '')
  );
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);
  const save = async () => {
    if (s.type === 'secret' && val === '') {
      setError('Paste a value to set/replace this secret. Blank is a no-op.');
      return;
    }
    setSaving(true); setError(null);
    try { await api.put(`/api/admin/settings/${encodeURIComponent(s.key)}`, { value: String(val) }); onSaved(); }
    catch (e) { setError(e.detail || e.message); }
    setSaving(false);
  };
  const sourceLabel = ({ db: { c:'pill accent', l:'db' }, env: { c:'pill', l:'env' }, default: { c:'pill', l:'default' } })[s.source] || { c:'pill', l:s.source };
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr auto auto auto', gap: 14, alignItems: 'center', paddingBottom: 14, borderBottom: '1px solid var(--line-2)' }}>
      <div>
        <div className="row gap-8">
          <span className="num" style={{ fontWeight: 600 }}>{s.key}</span>
          <span className={sourceLabel.c}><span className="dot"/>{sourceLabel.l}</span>
          {s.type === 'secret' && <span className={'pill ' + (s.is_set ? 'pos' : 'neg')}><span className="dot"/>{s.is_set ? 'set' : 'unset'}</span>}
          {s.requires_restart && <span className="pill warn"><span className="dot"/>restart</span>}
        </div>
        <div className="tiny muted" style={{ marginTop: 4, maxWidth: 600, lineHeight: 1.4 }}>{s.description}</div>
        {error && <div style={{ fontSize: 12, color: 'var(--neg)', marginTop: 6 }}>{error}</div>}
      </div>
      <div className="tiny muted">type: {s.type}</div>
      {s.type === 'bool' ? (
        <select className="select" value={val} onChange={(e) => setVal(e.target.value)} style={{ width: 110 }}>
          <option value="true">true</option><option value="false">false</option>
        </select>
      ) : s.type === 'secret' ? (
        <input className="input num" type="password" autoComplete="off" value={val} onChange={(e) => setVal(e.target.value)}
               placeholder={s.is_set ? '(set — paste to replace)' : '(unset)'} style={{ width: 280, fontSize: 13 }}/>
      ) : (
        <input className="input num" value={val} onChange={(e) => setVal(e.target.value)} style={{ width: 220, fontSize: 13 }}/>
      )}
      <button className="btn primary sm" onClick={save} disabled={saving}>Save</button>
    </div>
  );
}

/* ---------- Exchange Balance (dedicated page) ---------- */
function ExchangeBalance() {
  const [bal, { reload, loading }] = useApi('/api/admin/exchange/balance');
  return (
    <>
      <div className="topbar">
        <div>
          <div className="crumb">Admin · Exchange</div>
          <h1 className="h1">Bybit account</h1>
        </div>
        <button className="btn ghost" onClick={reload}><IconArrowRt size={14}/> Refresh</button>
      </div>
      <div className="content">
        {loading && !bal && <div className="card muted">Loading…</div>}
        {bal && <ExchangeCard bal={bal} reload={reload}/>}
        {bal && bal.assets && bal.assets.length > 0 && (
          <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
            <div style={{ padding: '20px 22px 8px' }}>
              <h2 className="h2">Assets</h2>
              <div className="tiny muted">Per-coin breakdown</div>
            </div>
            <table className="tbl">
              <thead><tr><th>Asset</th><th>Wallet</th><th>Available</th><th>Margin</th><th>Unrealized</th></tr></thead>
              <tbody>
                {bal.assets.map(a => (
                  <tr key={a.asset}>
                    <td><strong className="num">{a.asset}</strong></td>
                    <td className="num">{fmtNum(a.wallet_balance, 4)}</td>
                    <td className="num">{fmtNum(a.available_balance, 4)}</td>
                    <td className="num">{fmtNum(a.margin_balance, 4)}</td>
                    <td className="num" style={{ color: a.unrealized_pnl >= 0 ? 'var(--pos)' : 'var(--neg)', fontWeight: 600 }}>{a.unrealized_pnl >= 0 ? '+' : ''}{fmtNum(a.unrealized_pnl, 4)}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
      </div>
    </>
  );
}

/* ---------- Admin Bot Detail (tabs: positions, trades, performance, scanner, config, keys) ---------- */
function AdminBotDetail({ botId, go }) {
  const [bot] = useApi('/api/bots/' + botId);
  const [tab, setTab] = React.useState('positions');
  if (!bot) return <div style={{ padding: 32 }} className="muted">Loading…</div>;
  const color = colorFor(bot.id);
  const flatten = async () => {
    if (!confirm('Flatten all open positions at market?')) return;
    try { const r = await api.post(`/api/admin/bots/${botId}/flatten`); alert(`Flattened ${r.closed_count} position(s)`); }
    catch (e) { alert('Flatten failed: ' + (e.detail || e.message)); }
  };
  return (
    <>
      <div className="topbar">
        <div>
          <button className="btn ghost sm" onClick={() => go('admin')} style={{ marginBottom: 8 }}>← Admin</button>
          <div className="row gap-12">
            <div className="bot-emblem" style={{ background: color, width: 52, height: 52, fontSize: 20 }}>{emojiFor(bot.risk_category)}</div>
            <div>
              <div className="row gap-8" style={{ marginBottom: 4 }}>
                <span className="pill" style={{ background: color + '22', color, textTransform: 'capitalize' }}><span className="dot"/>{bot.risk_category}</span>
                <span className={'pill ' + (bot.is_kill_switched ? 'neg' : bot.is_active ? 'pos' : 'warn')}><span className="dot"/>{bot.is_kill_switched ? 'KILLED' : bot.is_active ? 'Active' : 'Off'}</span>
              </div>
              <h1 className="h1" style={{ marginTop: 0 }}>{bot.name}</h1>
            </div>
          </div>
        </div>
        <button className="btn danger" onClick={flatten}><IconX size={16}/> Flatten Now</button>
      </div>
      <div className="content">
        <div className="row gap-6 range-row" style={{ borderBottom: '1px solid var(--line)', paddingBottom: 0 }}>
          {['positions','trades','performance','scanner','config','keys'].map(t => (
            <button key={t} onClick={() => setTab(t)} className={'btn ' + (tab === t ? 'soft' : 'ghost') + ' sm'} style={{ textTransform: 'capitalize', borderRadius: '12px 12px 0 0' }}>{t}</button>
          ))}
        </div>
        {tab === 'positions'   && <PositionsTab botId={botId}/>}
        {tab === 'trades'      && <TradesTab botId={botId}/>}
        {tab === 'performance' && <PerformanceTab botId={botId} color={color}/>}
        {tab === 'scanner'     && <ScannerTab botId={botId}/>}
        {tab === 'config'      && <ConfigTab botId={botId}/>}
        {tab === 'keys'        && <KeysTab botId={botId}/>}
      </div>
    </>
  );
}

function PositionsTab({ botId }) {
  const [positions] = useApi(`/api/admin/bots/${botId}/positions`);
  if (!positions) return <div className="card muted">Loading…</div>;
  if (positions.length === 0) return <div className="card" style={{ textAlign: 'center', padding: 40, color: 'var(--ink-4)' }}>No open positions</div>;
  return (
    <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
      <table className="tbl">
        <thead><tr><th>ID</th><th>Symbol</th><th>Side</th><th>Qty rem</th><th>Entry</th><th>Mark</th><th>SL</th><th>TP1/TP2</th><th>Unrealized</th><th>Status</th></tr></thead>
        <tbody>
          {positions.map(p => (
            <tr key={p.id}>
              <td>#{p.id}</td>
              <td><strong className="num">{p.symbol}</strong></td>
              <td><span className={'pill ' + (p.side === 'LONG' ? 'pos' : 'neg')}><span className="dot"/>{p.side}</span></td>
              <td className="num">{fmtNum(p.qty_remaining, 6)}</td>
              <td className="num">{p.entry_px != null ? fmtNum(p.entry_px, 4) : '—'}</td>
              <td className="num" style={{ color: 'var(--accent)' }}>{p.mark_price != null ? fmtNum(p.mark_price, 4) : '—'}</td>
              <td className="num" style={{ color: 'var(--neg)' }}>{fmtNum(p.sl_px, 4)}</td>
              <td className="num tiny" style={{ color: 'var(--pos)' }}>{p.tp1_px != null ? fmtNum(p.tp1_px, 4) : '—'} / {p.tp2_px != null ? fmtNum(p.tp2_px, 4) : '—'}</td>
              <td className="num" style={{ color: p.unrealized_pnl == null ? 'var(--ink-4)' : p.unrealized_pnl >= 0 ? 'var(--pos)' : 'var(--neg)', fontWeight: 600 }}>
                {p.unrealized_pnl == null ? '—' : (p.unrealized_pnl >= 0 ? '+' : '') + fmtNum(p.unrealized_pnl, 4) + ' (' + (p.unrealized_pnl_pct != null ? p.unrealized_pnl_pct.toFixed(2) : '0.00') + '%)'}
              </td>
              <td>
                <span className="pill warn"><span className="dot"/>{p.status}</span>
                {p.tp1_hit && <span className="pill pos sm" style={{ marginLeft: 4, padding: '1px 4px', fontSize: '9px', textTransform: 'uppercase', verticalAlign: 'middle' }}>TP1 Hit</span>}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function TradesTab({ botId }) {
  const [trades] = useApi(`/api/admin/bots/${botId}/trades?limit=100`);
  if (!trades) return <div className="card muted">Loading…</div>;
  if (trades.length === 0) return <div className="card" style={{ textAlign: 'center', padding: 40, color: 'var(--ink-4)' }}>No closed trades yet</div>;
  return (
    <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
      <table className="tbl">
        <thead><tr><th>ID</th><th>Symbol</th><th>Side</th><th>Qty</th><th>Entry</th><th>SL</th><th>TP1 / TP2</th><th>Realized</th><th>Fees</th><th>Closed</th><th>Reason</th></tr></thead>
        <tbody>
          {trades.map(t => (
            <tr key={t.id}>
              <td>#{t.id}</td>
              <td><strong className="num">{t.symbol}</strong></td>
              <td><span className={'pill ' + (t.side === 'LONG' ? 'pos' : 'neg')}><span className="dot"/>{t.side}</span></td>
              <td className="num">{fmtNum(t.qty, 6)}</td>
              <td className="num">{t.entry_px != null ? fmtNum(t.entry_px, 4) : '—'}</td>
              <td className="num" style={{ color: 'var(--neg)' }}>{t.sl_px != null ? fmtNum(t.sl_px, 4) : '—'}</td>
              <td className="num tiny" style={{ color: 'var(--pos)' }}>{t.tp1_px != null ? fmtNum(t.tp1_px, 4) : '—'} / {t.tp2_px != null ? fmtNum(t.tp2_px, 4) : '—'}</td>
              <td className="num" style={{ color: t.realized_pnl >= 0 ? 'var(--pos)' : 'var(--neg)', fontWeight: 600 }}>{t.realized_pnl >= 0 ? '+' : ''}{fmtNum(t.realized_pnl, 4)}</td>
              <td className="num muted">{fmtNum(t.fees, 4)}</td>
              <td className="muted tiny">{t.closed_at ? fmtDateTime(t.closed_at) : '—'}</td>
              <td className="tiny">
                {t.close_reason || '—'}
                {t.tp1_hit && <span className="pill pos sm" style={{ marginLeft: 4, padding: '1px 4px', fontSize: '9px', textTransform: 'uppercase', verticalAlign: 'middle' }}>TP1 Hit</span>}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function PerformanceTab({ botId, color }) {
  const [bucket, setBucket] = React.useState('hour');
  const [points] = useApi(`/api/admin/bots/${botId}/unit-price-history?bucket=${bucket}`, [bucket]);
  const prices = (points || []).map(p => p.unit_price);
  const last = prices.length ? prices[prices.length - 1] : null;
  const first = prices.length ? prices[0] : null;
  const peak = prices.length ? Math.max(...prices) : null;
  const trough = prices.length ? Math.min(...prices) : null;
  const change = last && first ? last - first : 0;
  const changePct = first ? (change / first * 100) : 0;
  return (
    <>
      <div className="card" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        <div className="row" style={{ justifyContent: 'space-between' }}>
          <div>
            <div className="tiny muted">Unit price</div>
            <div className="num chart-value" style={{ fontSize: 32, fontWeight: 700 }}>${last != null ? fmtNum(last, 4) : '—'}</div>
          </div>
          <div className="row gap-6 range-row">
            {[['hour','1H'],['day','1D'],['week','1W'],['month','1M']].map(([b, lab]) => (
              <button key={b} className={'btn ' + (bucket === b ? 'soft' : 'ghost') + ' sm'} onClick={() => setBucket(b)}>{lab}</button>
            ))}
          </div>
        </div>
        <div className="grid grid-4">
          <KPI label="Change" value={fmtPct(changePct)} pos={change >= 0} neg={change < 0}/>
          <KPI label="Peak" value={peak != null ? '$' + fmtNum(peak, 4) : '—'} pos/>
          <KPI label="Trough" value={trough != null ? '$' + fmtNum(trough, 4) : '—'} neg/>
          <KPI label="Samples" value={String(prices.length)} sub={bucket + 'ly buckets'}/>
        </div>
        {prices.length > 0
          ? <LineChart data={prices} color={color || 'var(--accent)'} height={320} baseline={1.0}/>
          : <div className="muted" style={{ padding: '60px 0', textAlign: 'center' }}>No data yet at this resolution. Equity loop writes one snapshot per minute when the worker is running.</div>
        }
      </div>
    </>
  );
}

function ScannerTab({ botId }) {
  const [snap, { reload }] = useApi(`/api/admin/bots/${botId}/scanner`);
  React.useEffect(() => {
    const t = setInterval(reload, 15000);
    return () => clearInterval(t);
  }, [reload]);
  if (!snap) return <div className="card muted">Loading…</div>;
  if (!snap.last_scan_at) return <div className="card" style={{ textAlign: 'center', padding: 40, color: 'var(--ink-4)' }}>No scan yet — worker may not be running, or first 3m candle hasn't closed since startup.</div>;
  const ageS = Math.round((Date.now() - new Date(snap.last_scan_at).getTime()) / 1000);
  return (
    <>
      <div className="card">
        <div className="row" style={{ justifyContent: 'space-between', marginBottom: 8 }}>
          <h2 className="h2">Last scan</h2>
          <span className={'pill ' + (snap.decision === 'entered' ? 'pos' : snap.decision === 'halted' ? 'neg' : '')}><span className="dot"/>{snap.decision || '—'}</span>
        </div>
        <div className="tiny muted">{ageS}s ago · trigger {snap.tick_symbol || '—'} · {snap.decision_reason || ''}</div>
      </div>
      <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
        <div style={{ padding: '20px 22px 8px' }}>
          <h2 className="h2">Candidates</h2>
          <div className="tiny muted">Top {snap.top_candidates.length} by composite score</div>
        </div>
        <table className="tbl">
          <thead><tr><th>Symbol</th><th>Side</th><th>Score</th><th>Trend</th><th>Momentum</th><th>Pullback</th><th>Breakout</th><th>S/R</th><th>Vol</th><th>Regime</th></tr></thead>
          <tbody>
            {snap.top_candidates.map((c, i) => (
              <tr key={i}>
                <td><strong className="num">{c.symbol}</strong></td>
                <td>{c.side ? <span className={'pill ' + (c.side === 'LONG' ? 'pos' : 'neg')}><span className="dot"/>{c.side}</span> : <span className="muted">—</span>}</td>
                <td className="num" style={{ fontWeight: 700, color: (c.score ?? 0) >= 0.7 ? 'var(--pos)' : (c.score ?? 0) >= 0.5 ? 'var(--warn)' : 'var(--ink-4)' }}>{(c.score ?? 0).toFixed(2)}</td>
                <td className="num tiny">{(c.trend ?? 0).toFixed(2)}</td>
                <td className="num tiny">{(c.momentum ?? 0).toFixed(2)}</td>
                <td className="num tiny">{(c.pullback ?? 0).toFixed(2)}</td>
                <td className="num tiny">{(c.breakout ?? 0).toFixed(2)}</td>
                <td className="num tiny">{(c.sr ?? 0).toFixed(2)}</td>
                <td className="num tiny">{(c.vol_regime ?? 0).toFixed(2)}</td>
                <td>{c.regime_ok ? <IconCheck size={14} style={{ color: 'var(--pos)' }}/> : <IconX size={14} style={{ color: 'var(--ink-4)' }}/>}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </>
  );
}

function ConfigTab({ botId }) {
  const [cfg, { reload, error: loadErr }] = useApi(`/api/admin/bots/${botId}/config`);
  const [draft, setDraft] = React.useState(null);
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);
  React.useEffect(() => { if (cfg) setDraft({ ...cfg, symbols_str: JSON.stringify(cfg.symbols || []), sp_str: JSON.stringify(cfg.strategy_params || {}, null, 2), so_str: JSON.stringify(cfg.symbol_overrides || {}, null, 2) }); }, [cfg]);
  if (loadErr) return <div className="card" style={{ textAlign: 'center', padding: 40 }}><div style={{ color: 'var(--neg)', fontWeight: 600, marginBottom: 8 }}>Failed to load config</div><div className="muted" style={{ fontSize: 13 }}>{loadErr.detail || loadErr.message || 'Unknown error'}</div><button className="btn ghost sm" style={{ marginTop: 12 }} onClick={reload}>Retry</button></div>;
  if (!draft) return <div className="card muted">Loading…</div>;
  const save = async () => {
    setSaving(true); setError(null);
    try {
      let symbols, sp, so;
      try { symbols = JSON.parse(draft.symbols_str); } catch { setError('Symbols must be a JSON array'); setSaving(false); return; }
      try { sp = JSON.parse(draft.sp_str); } catch { setError('Strategy params must be valid JSON'); setSaving(false); return; }
      try { so = JSON.parse(draft.so_str || '{}'); } catch { setError('Symbol overrides must be valid JSON'); setSaving(false); return; }
      const body = {
        symbols, leverage: Number(draft.leverage),
        risk_per_trade_pct: Number(draft.risk_per_trade_pct),
        max_concurrent_positions: Number(draft.max_concurrent_positions),
        max_position_notional_pct: Number(draft.max_position_notional_pct),
        htf_interval: draft.htf_interval, ltf_interval: draft.ltf_interval,
        daily_loss_pct: Number(draft.daily_loss_pct),
        weekly_dd_pct: Number(draft.weekly_dd_pct),
        cooldown_seconds: Number(draft.cooldown_seconds),
        liq_buffer_pct: Number(draft.liq_buffer_pct),
        paper_mode: !!draft.paper_mode, enabled: !!draft.enabled,
        strategy_params: sp,
        symbol_overrides: so,
      };
      await api.put(`/api/admin/bots/${botId}/config`, body); reload();
    } catch (e) { setError(e.detail || e.message); }
    setSaving(false);
  };
  const setF = (k, v) => setDraft({ ...draft, [k]: v });
  return (
    <div className="card" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div className="grid grid-2">
        <div className="field"><label className="field-label">Symbols (JSON array)</label>
          <input className="input num" value={draft.symbols_str} onChange={(e) => setF('symbols_str', e.target.value)} style={{ fontSize: 12 }}/></div>
        <div className="field"><label className="field-label">Leverage</label>
          <input className="input num" type="number" min="1" max="125" value={draft.leverage} onChange={(e) => setF('leverage', e.target.value)}/></div>
        <div className="field"><label className="field-label">Risk per trade %</label>
          <input className="input num" type="number" step="0.0001" value={draft.risk_per_trade_pct} onChange={(e) => setF('risk_per_trade_pct', e.target.value)}/></div>
        <div className="field"><label className="field-label">Max concurrent positions</label>
          <input className="input num" type="number" min="1" value={draft.max_concurrent_positions} onChange={(e) => setF('max_concurrent_positions', e.target.value)}/></div>
        <div className="field"><label className="field-label">Max position notional %</label>
          <input className="input num" type="number" step="0.01" value={draft.max_position_notional_pct} onChange={(e) => setF('max_position_notional_pct', e.target.value)}/></div>
        <div className="field"><label className="field-label">HTF / LTF</label>
          <div className="row gap-8"><input className="input num" value={draft.htf_interval} onChange={(e) => setF('htf_interval', e.target.value)}/><input className="input num" value={draft.ltf_interval} onChange={(e) => setF('ltf_interval', e.target.value)}/></div></div>
        <div className="field"><label className="field-label">Daily loss %</label>
          <input className="input num" type="number" step="0.001" value={draft.daily_loss_pct} onChange={(e) => setF('daily_loss_pct', e.target.value)}/></div>
        <div className="field"><label className="field-label">Weekly DD %</label>
          <input className="input num" type="number" step="0.001" value={draft.weekly_dd_pct} onChange={(e) => setF('weekly_dd_pct', e.target.value)}/></div>
        <div className="field"><label className="field-label">Cooldown seconds</label>
          <input className="input num" type="number" value={draft.cooldown_seconds} onChange={(e) => setF('cooldown_seconds', e.target.value)}/></div>
        <div className="field"><label className="field-label">Liq buffer %</label>
          <input className="input num" type="number" step="0.01" value={draft.liq_buffer_pct} onChange={(e) => setF('liq_buffer_pct', e.target.value)}/></div>
      </div>
      <div className="row gap-16">
        <label className="row gap-8" style={{ fontSize: 13.5 }}><input type="checkbox" checked={!!draft.paper_mode} onChange={(e) => setF('paper_mode', e.target.checked)}/> Paper mode</label>
        <label className="row gap-8" style={{ fontSize: 13.5 }}><input type="checkbox" checked={!!draft.enabled} onChange={(e) => setF('enabled', e.target.checked)}/> Enabled</label>
      </div>
      <div className="field">
        <label className="field-label">Strategy params (JSON)</label>
        <textarea className="textarea num" rows="14" value={draft.sp_str} onChange={(e) => setF('sp_str', e.target.value)} style={{ fontSize: 12 }}/>
        <div className="field-hint">Object — mix of numbers, booleans, and strings. Unknown keys rejected.</div>
      </div>
      <div className="field">
        <label className="field-label">Symbol overrides (JSON)</label>
        <textarea className="textarea num" rows="12" value={draft.so_str} onChange={(e) => setF('so_str', e.target.value)} style={{ fontSize: 12 }} placeholder='{ "BTCUSDT": { "squeeze_min_bars": 6, "tp2_r": 4.0, "filter": "noLS_Q4" } }'/>
        <div className="field-hint">Per-symbol parameter overrides. Each value is an object of strategy_params keys (plus optional "filter": "noOI_Q3" | "noLS_Q4"). Merged with strategy_params at scan time; symbol overrides win on conflict. Empty {} = same params for all symbols (legacy).</div>
      </div>
      {error && <div style={{ fontSize: 13, color: 'var(--neg)', background: 'var(--neg-soft)', padding: 10, borderRadius: 8 }}>{error}</div>}
      <div className="row gap-8">
        <button className="btn primary" onClick={save} disabled={saving}>{saving ? 'Saving…' : 'Save changes'}</button>
        <button className="btn ghost" onClick={reload}>Reset</button>
      </div>
    </div>
  );
}

function KeysTab({ botId }) {
  const [status, { reload, error: loadErr }] = useApi(`/api/admin/bots/${botId}/keys`);
  const [apiKey, setApiKey] = React.useState('');
  const [secret, setSecret] = React.useState('');
  const [testnet, setTestnet] = React.useState(true);
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);
  React.useEffect(() => { if (status) setTestnet(status.is_testnet ?? true); }, [status]);
  if (loadErr) return <div className="card" style={{ textAlign: 'center', padding: 40 }}><div style={{ color: 'var(--neg)', fontWeight: 600, marginBottom: 8 }}>Failed to load keys status</div><div className="muted" style={{ fontSize: 13 }}>{loadErr.detail || loadErr.message || 'Unknown error'}</div><button className="btn ghost sm" style={{ marginTop: 12 }} onClick={reload}>Retry</button></div>;
  if (!status) return <div className="card muted">Loading…</div>;
  const save = async () => {
    if (!apiKey || !secret) { setError('Both key and secret are required'); return; }
    if (!testnet && !confirm('You are saving MAINNET keys. Live trading uses real money. Continue?')) return;
    setSaving(true); setError(null);
    try { await api.put(`/api/admin/bots/${botId}/keys`, { api_key: apiKey, api_secret: secret, is_testnet: testnet }); reload(); setApiKey(''); setSecret(''); }
    catch (e) { setError(e.detail || e.message); }
    setSaving(false);
  };
  return (
    <>
      <div className="card">
        <div className="grid grid-3">
          <div><div className="tiny muted">Keys set</div><div className={'row gap-6 ' + (status.has_keys ? '' : '')} style={{ color: status.has_keys ? 'var(--pos)' : 'var(--neg)', fontWeight: 600 }}>{status.has_keys ? <IconCheck size={16}/> : <IconX size={16}/>}{status.has_keys ? 'Yes' : 'No'}</div></div>
          <div><div className="tiny muted">Environment</div><div style={{ color: status.is_testnet ? 'var(--warn)' : 'var(--neg)', fontWeight: 700 }}>{status.is_testnet ? 'TESTNET' : 'MAINNET'}</div></div>
          <div><div className="tiny muted">Subaccount</div><div>{status.subaccount_label || '—'}</div></div>
        </div>
        <div className="tiny muted" style={{ marginTop: 10 }}>
          <IconShield size={13}/> Secrets encrypted at rest with Fernet. The platform never echoes the secret back.
        </div>
      </div>
      <div className="card" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        <h3 className="h3">{status.has_keys ? 'Rotate' : 'Set'} API keys (this bot)</h3>
        <div className="field">
          <label className="field-label">Bybit API key</label>
          <input className="input num" autoComplete="off" value={apiKey} onChange={(e) => setApiKey(e.target.value)}/>
        </div>
        <div className="field">
          <label className="field-label">Bybit API secret</label>
          <input className="input num" type="password" autoComplete="off" value={secret} onChange={(e) => setSecret(e.target.value)}/>
        </div>
        <label className="row gap-8" style={{ fontSize: 13.5 }}>
          <input type="checkbox" checked={testnet} onChange={(e) => setTestnet(e.target.checked)}/>
          Testnet keys (uncheck only for production)
        </label>
        {error && <div style={{ fontSize: 13, color: 'var(--neg)', background: 'var(--neg-soft)', padding: 10, borderRadius: 8 }}>{error}</div>}
        <button className="btn primary" onClick={save} disabled={saving}>{saving ? 'Saving…' : 'Save keys'}</button>
        <div className="tiny muted">Per-bot keys override the platform fallback. Leave empty here to use the platform-wide keys from Settings.</div>
      </div>
    </>
  );
}

/* ---------- Users (admin) ---------- */

function Users() {
  const [filter, setFilter] = React.useState('all');  // all | pending | approved | rejected
  const path = filter === 'all' ? '/api/admin/users' : `/api/admin/users?approval_status=${filter}`;
  const [list, { reload }] = useApi(path, [filter]);
  const [creating, setCreating] = React.useState(false);
  const [editing, setEditing] = React.useState(null);
  const [adjusting, setAdjusting] = React.useState(null);
  const [pwReset, setPwReset] = React.useState(null);

  const approve = async (u) => { await api.post(`/api/admin/users/${u.id}/approve`); reload(); };
  const reject = async (u) => {
    if (!confirm(`Reject ${u.username}? They won't be able to sign in.`)) return;
    await api.post(`/api/admin/users/${u.id}/reject`); reload();
  };

  return (
    <>
      <div className="topbar">
        <div>
          <div className="crumb">Admin · Users</div>
          <h1 className="h1">User management <span className="muted" style={{ fontWeight: 500 }}>· {list ? list.length : 0}</span></h1>
        </div>
        <div className="row gap-8">
          <div className="row gap-6 range-row">
            {['all','pending','approved','rejected'].map(t => (
              <button key={t} className={'btn ' + (filter === t ? 'primary' : 'ghost') + ' sm'} onClick={() => setFilter(t)} style={{ textTransform: 'capitalize' }}>{t}</button>
            ))}
          </div>
          <button className="btn primary" onClick={() => setCreating(true)}><IconPlus size={16}/> Add user</button>
        </div>
      </div>
      <div className="content">
        <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
          {!list ? <div className="muted" style={{ padding: 32 }}>Loading…</div> : (
            <table className="tbl">
              <thead><tr><th>User</th><th>Role</th><th>Approval</th><th>Active</th><th>Holdings</th><th style={{ textAlign: 'right' }}>Cash</th><th>Created</th><th></th></tr></thead>
              <tbody>
                {list.map(u => {
                  const approvalPill =
                    u.approval_status === 'approved' ? 'pos' :
                    u.approval_status === 'pending'  ? 'warn' :
                    'neg';
                  return (
                    <tr key={u.id}>
                      <td>
                        <div className="row gap-12">
                          <div className="avatar" style={{ width: 32, height: 32, fontSize: 11 }}>
                            {(u.full_name || u.username).split(' ').map(s => s[0]).join('').slice(0,2).toUpperCase()}
                          </div>
                          <div>
                            <div style={{ fontWeight: 600 }}>{u.full_name || u.username}</div>
                            <div className="tiny muted">{u.email}</div>
                            {u.ktp_number && (
                              <div className="tiny muted" style={{ marginTop: 2, padding: '2px 6px', background: 'var(--bg-soft)', borderRadius: 4, display: 'inline-block' }}>
                                <strong>KTP:</strong> {u.ktp_full_name} ({u.ktp_number})
                              </div>
                            )}
                          </div>
                        </div>
                      </td>
                      <td><span className={'pill ' + (u.role === 'admin' ? 'accent' : '')}><span className="dot"/>{u.role}</span></td>
                      <td><span className={'pill ' + approvalPill}><span className="dot"/>{u.approval_status}</span></td>
                      <td><span className={'pill ' + (u.is_active ? 'pos' : 'neg')}><span className="dot"/>{u.is_active ? 'Active' : 'Disabled'}</span></td>
                      <td className="num">{u.holdings_count}</td>
                      <td className="num" style={{ textAlign: 'right', fontWeight: 600 }}>{USD(u.cash_balance)}</td>
                      <td className="muted tiny">{fmtDate(u.created_at)}</td>
                      <td>
                        <div className="row gap-6">
                          {u.approval_status === 'pending' && (
                            <>
                              <button className="btn primary sm" onClick={() => approve(u)}><IconCheck size={13}/> Approve</button>
                              <button className="btn ghost sm" onClick={() => reject(u)}><IconX size={13}/> Reject</button>
                            </>
                          )}
                          {u.approval_status !== 'pending' && (
                            <>
                              <button className="btn ghost sm" onClick={() => setEditing(u)}>Edit</button>
                              <button className="btn ghost sm" onClick={() => setAdjusting(u)}>Cash</button>
                              <button className="btn ghost sm" onClick={() => setPwReset(u)} title="Reset password"><IconKey size={14}/></button>
                            </>
                          )}
                        </div>
                      </td>
                    </tr>
                  );
                })}
                {list.length === 0 && <tr><td colSpan="8" style={{ padding: 32, textAlign: 'center', color: 'var(--ink-4)' }}>No users{filter !== 'all' ? ' in ' + filter : ''}.</td></tr>}
              </tbody>
            </table>
          )}
        </div>
      </div>
      {creating && <UserCreateModal onClose={() => setCreating(false)} onSaved={() => { reload(); setCreating(false); }}/>}
      {editing && <UserEditModal user={editing} onClose={() => setEditing(null)} onSaved={() => { reload(); setEditing(null); }}/>}
      {adjusting && <UserCashModal user={adjusting} onClose={() => setAdjusting(null)} onSaved={() => { reload(); setAdjusting(null); }}/>}
      {pwReset && <PasswordResetModal user={pwReset} onClose={() => setPwReset(null)} onSaved={() => setPwReset(null)}/>}
    </>
  );
}

function ModalShell({ title, sub, onClose, children }) {
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-hd">
          <div>
            {sub && <div className="tiny muted">{sub}</div>}
            <strong>{title}</strong>
          </div>
          <button className="modal-x" onClick={onClose}><IconX size={14}/></button>
        </div>
        <div className="modal-bd">{children}</div>
      </div>
    </div>
  );
}

function UserCreateModal({ onClose, onSaved }) {
  const [form, setForm] = React.useState({ username: '', email: '', password: '', full_name: '', role: 'user', is_active: true });
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const set = (k, v) => setForm({ ...form, [k]: v });
  const submit = async () => {
    setBusy(true); setError(null);
    try { await api.post('/api/admin/users', form); onSaved(); }
    catch (e) { setError(e.detail || e.message); }
    setBusy(false);
  };
  return (
    <ModalShell title="Add user" sub="New account" onClose={onClose}>
      <div className="field"><label className="field-label">Username</label><input className="input" value={form.username} onChange={(e) => set('username', e.target.value)}/></div>
      <div className="field"><label className="field-label">Email</label><input className="input" type="email" value={form.email} onChange={(e) => set('email', e.target.value)}/></div>
      <div className="field"><label className="field-label">Full name</label><input className="input" value={form.full_name} onChange={(e) => set('full_name', e.target.value)}/></div>
      <div className="field"><label className="field-label">Password</label><input className="input" type="password" value={form.password} onChange={(e) => set('password', e.target.value)}/></div>
      <div className="field"><label className="field-label">Role</label>
        <select className="select" value={form.role} onChange={(e) => set('role', e.target.value)}>
          <option value="user">user</option><option value="admin">admin</option>
        </select>
      </div>
      <label className="row gap-8"><input type="checkbox" checked={form.is_active} onChange={(e) => set('is_active', e.target.checked)}/> Active</label>
      {error && <div style={{ fontSize: 13, color: 'var(--neg)', background: 'var(--neg-soft)', padding: 10, borderRadius: 8 }}>{error}</div>}
      <button className="btn primary block lg" onClick={submit} disabled={busy || !form.username || !form.email || !form.password}>{busy ? 'Creating…' : 'Create user'}</button>
    </ModalShell>
  );
}

function UserEditModal({ user, onClose, onSaved }) {
  const [form, setForm] = React.useState({
    email: user.email,
    full_name: user.full_name || '',
    role: user.role,
    is_active: user.is_active,
    ktp_number: user.ktp_number || '',
    ktp_full_name: user.ktp_full_name || '',
  });
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const set = (k, v) => setForm({ ...form, [k]: v });
  const submit = async () => {
    setBusy(true); setError(null);
    try { await api.put(`/api/admin/users/${user.id}`, form); onSaved(); }
    catch (e) { setError(e.detail || e.message); }
    setBusy(false);
  };
  return (
    <ModalShell title={`Edit ${user.username}`} sub="Update profile" onClose={onClose}>
      <div className="field"><label className="field-label">Email</label><input className="input" type="email" value={form.email} onChange={(e) => set('email', e.target.value)}/></div>
      <div className="field"><label className="field-label">Full name</label><input className="input" value={form.full_name} onChange={(e) => set('full_name', e.target.value)}/></div>
      <div className="field"><label className="field-label">NIK (KTP number)</label><input className="input" value={form.ktp_number} onChange={(e) => set('ktp_number', e.target.value)} placeholder="16-digit national ID"/></div>
      <div className="field"><label className="field-label">KTP Full Name</label><input className="input" value={form.ktp_full_name} onChange={(e) => set('ktp_full_name', e.target.value)} placeholder="Name exactly as printed on KTP"/></div>
      <div className="field"><label className="field-label">Role</label>
        <select className="select" value={form.role} onChange={(e) => set('role', e.target.value)}>
          <option value="user">user</option><option value="admin">admin</option>
        </select>
      </div>
      <label className="row gap-8"><input type="checkbox" checked={form.is_active} onChange={(e) => set('is_active', e.target.checked)}/> Active</label>
      {error && <div style={{ fontSize: 13, color: 'var(--neg)', background: 'var(--neg-soft)', padding: 10, borderRadius: 8 }}>{error}</div>}
      <button className="btn primary block lg" onClick={submit} disabled={busy}>{busy ? 'Saving…' : 'Save changes'}</button>
    </ModalShell>
  );
}

function PasswordResetModal({ user, onClose, onSaved }) {
  const [pw, setPw] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const submit = async () => {
    setBusy(true); setError(null);
    try { await api.post(`/api/admin/users/${user.id}/reset-password`, { password: pw }); alert('Password reset.'); onSaved(); }
    catch (e) { setError(e.detail || e.message); }
    setBusy(false);
  };
  return (
    <ModalShell title={`Reset password — ${user.username}`} sub="New password" onClose={onClose}>
      <div className="field"><label className="field-label">New password</label><input className="input" type="password" value={pw} onChange={(e) => setPw(e.target.value)}/></div>
      <div className="tiny muted">User will need to sign in with this password next time. They aren't notified — share it through your own channel.</div>
      {error && <div style={{ fontSize: 13, color: 'var(--neg)', background: 'var(--neg-soft)', padding: 10, borderRadius: 8 }}>{error}</div>}
      <button className="btn primary block lg" onClick={submit} disabled={busy || pw.length < 6}>{busy ? 'Saving…' : 'Set password'}</button>
    </ModalShell>
  );
}

function UserCashModal({ user, onClose, onSaved }) {
  const [delta, setDelta] = React.useState(0);
  const [note, setNote] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const submit = async () => {
    setBusy(true); setError(null);
    try { await api.post(`/api/admin/users/${user.id}/cash`, { delta: Number(delta), note }); onSaved(); }
    catch (e) { setError(e.detail || e.message); }
    setBusy(false);
  };
  return (
    <ModalShell title={`Adjust cash — ${user.username}`} sub={'Current balance: ' + USD(user.cash_balance)} onClose={onClose}>
      <div className="field">
        <label className="field-label">Delta (positive = credit, negative = debit)</label>
        <input className="input num" type="number" step="0.01" value={delta} onChange={(e) => setDelta(e.target.value)}/>
        <div className="field-hint">Example: <strong className="num">10</strong> credits $10. <strong className="num">-5</strong> debits $5.</div>
      </div>
      <div className="field"><label className="field-label">Note (optional)</label><input className="input" value={note} onChange={(e) => setNote(e.target.value)} placeholder="e.g. off-channel deposit, correction"/></div>
      <div className="card flat" style={{ padding: 12, fontSize: 13 }}>
        <div className="row" style={{ justifyContent: 'space-between' }}><span className="muted">After adjustment</span><strong className="num">{USD(Number(user.cash_balance) + Number(delta || 0))}</strong></div>
      </div>
      {error && <div style={{ fontSize: 13, color: 'var(--neg)', background: 'var(--neg-soft)', padding: 10, borderRadius: 8 }}>{error}</div>}
      <button className="btn primary block lg" onClick={submit} disabled={busy || Number(delta) === 0}>{busy ? 'Saving…' : 'Apply adjustment'}</button>
    </ModalShell>
  );
}

/* ---------- Stats (historical trade review) ---------- */
function Stats() {
  // No GET /api/admin/bots exists — admins use the user-facing /api/bots list
  // (same data, just no per-bot internal state). All admins are auth'd.
  const [bots] = useApi('/api/bots');
  const [botId, setBotId] = React.useState(null);
  React.useEffect(() => {
    if (botId == null && bots && bots.length) setBotId(bots[0].id);
  }, [bots]);

  // Range state: which time window to slice by. Defaults to inception so
  // the page loads with the full picture; admins drill down via presets or
  // milestone anchors.
  const [range, setRange] = React.useState({ kind: 'preset', value: 'inception' });
  const [showAdd, setShowAdd] = React.useState(false);
  const [milestones, milestoneActions] = useApi(
    botId ? `/api/admin/bots/${botId}/milestones` : null, [botId]);

  // Map range → ISO `since` string sent to the stats endpoint.
  const since = React.useMemo(() => {
    if (range.kind === 'preset') {
      if (range.value === 'inception') return null;
      const ms = { '24h': 86400e3, '7d': 7*86400e3, '30d': 30*86400e3 }[range.value];
      return ms ? new Date(Date.now() - ms).toISOString() : null;
    }
    if (range.kind === 'milestone') {
      const m = (milestones || []).find(x => x.id === range.id);
      return m ? m.ts : null;
    }
    if (range.kind === 'custom') return range.since || null;
    return null;
  }, [range, milestones]);

  const statsUrl = botId
    ? `/api/admin/bots/${botId}/stats${since ? '?since=' + encodeURIComponent(since) : ''}`
    : null;
  const [s, { loading }] = useApi(statsUrl, [statsUrl]);

  const addMilestone = async (label, description) => {
    await api.post(`/api/admin/bots/${botId}/milestones`, { label, description });
    milestoneActions.reload();
    setShowAdd(false);
  };
  const removeMilestone = async (id) => {
    if (!confirm('Delete this milestone?')) return;
    await api.del(`/api/admin/bots/${botId}/milestones/${id}`);
    milestoneActions.reload();
    // If we were filtering by the milestone we just removed, fall back.
    if (range.kind === 'milestone' && range.id === id) {
      setRange({ kind: 'preset', value: 'inception' });
    }
  };

  return (
    <>
      <div className="topbar">
        <div>
          <div className="crumb">Admin · Review</div>
          <h1 className="h1">Stats</h1>
        </div>
        <div className="row gap-8">
          {(bots || []).map(b => (
            <button key={b.id} className={'btn ' + (b.id === botId ? 'soft' : 'ghost') + ' sm'}
                    onClick={() => setBotId(b.id)}>{b.name}</button>
          ))}
        </div>
      </div>
      <div className="content">
        <StatsRangeSelector range={range} setRange={setRange}
                            milestones={milestones || []}
                            onAddClick={() => setShowAdd(true)}
                            onRemoveMilestone={removeMilestone}/>
        {showAdd && <AddMilestoneInline onSave={addMilestone} onCancel={() => setShowAdd(false)}/>}
        {loading && <div className="muted">Loading…</div>}
        {s && s.total_trades === 0 && <div className="card muted">No closed trades in this window.</div>}
        {s && s.total_trades > 0 && <StatsBody s={s}/>}
      </div>
    </>
  );
}

function StatsRangeSelector({ range, setRange, milestones, onAddClick, onRemoveMilestone }) {
  const presets = [['24h', '24h'], ['7d', '7d'], ['30d', '30d'], ['inception', 'Since inception']];
  const isPreset = (k) => range.kind === 'preset' && range.value === k;
  const isMilestone = (id) => range.kind === 'milestone' && range.id === id;
  const isCustom = range.kind === 'custom';
  const onCustomDate = (e) => {
    const v = e.target.value;
    if (!v) return setRange({ kind: 'preset', value: 'inception' });
    // <input type="date"> gives YYYY-MM-DD; treat as local midnight.
    setRange({ kind: 'custom', since: new Date(v).toISOString() });
  };
  const customValue = isCustom && range.since ? range.since.slice(0, 10) : '';
  return (
    <div className="card" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div className="row gap-8" style={{ flexWrap: 'wrap', alignItems: 'center' }}>
        <span className="tiny muted" style={{ letterSpacing: '0.14em', textTransform: 'uppercase', fontSize: 10 }}>Range</span>
        {presets.map(([k, lab]) => (
          <button key={k} className={'btn ' + (isPreset(k) ? 'soft' : 'ghost') + ' sm'}
                  onClick={() => setRange({ kind: 'preset', value: k })}>{lab}</button>
        ))}
        <span className="muted tiny" style={{ marginLeft: 8 }}>or</span>
        <input type="date" className="input num" value={customValue} onChange={onCustomDate}
               style={{ width: 160, fontSize: 13, padding: '8px 10px' }}/>
        <div style={{ flex: 1 }}/>
        <button className="btn primary sm" onClick={onAddClick}>+ Mark milestone</button>
      </div>
      {milestones.length > 0 && (
        <div className="row gap-6" style={{ flexWrap: 'wrap', alignItems: 'center' }}>
          <span className="tiny muted" style={{ letterSpacing: '0.14em', textTransform: 'uppercase', fontSize: 10 }}>Milestones</span>
          {milestones.map(m => (
            <span key={m.id} style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              padding: '4px 4px 4px 10px', borderRadius: 999,
              background: isMilestone(m.id) ? 'var(--accent-soft)' : 'var(--bg-soft)',
              border: '1px solid ' + (isMilestone(m.id) ? 'var(--accent)' : 'var(--line)'),
              color: isMilestone(m.id) ? 'var(--accent)' : 'var(--ink)',
              fontSize: 12,
            }}>
              <button style={{ background: 'transparent', border: 0, color: 'inherit', cursor: 'pointer', padding: 0, fontFamily: 'inherit', fontSize: 12 }}
                      onClick={() => setRange({ kind: 'milestone', id: m.id })}
                      title={m.description || ''}>
                {m.label}<span style={{ marginLeft: 6, opacity: 0.6 }}>· {new Date(m.ts).toLocaleDateString()}</span>
              </button>
              <button onClick={() => onRemoveMilestone(m.id)} title="Delete"
                      style={{ background: 'transparent', border: 0, color: 'inherit', opacity: 0.5, cursor: 'pointer', padding: '0 4px', fontSize: 14, lineHeight: 1 }}>×</button>
            </span>
          ))}
        </div>
      )}
    </div>
  );
}

function AddMilestoneInline({ onSave, onCancel }) {
  const [label, setLabel] = React.useState('');
  const [desc, setDesc] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const save = async () => {
    if (!label.trim()) return;
    setBusy(true); setError(null);
    try { await onSave(label.trim(), desc.trim() || null); }
    catch (e) { setError(e.detail || e.message || 'Save failed'); }
    setBusy(false);
  };
  return (
    <div className="card">
      <div className="row gap-12" style={{ alignItems: 'flex-end', flexWrap: 'wrap' }}>
        <div className="field" style={{ flex: '1 1 200px' }}>
          <label className="field-label">Label</label>
          <input className="input" autoFocus placeholder="e.g. long_only enabled"
                 value={label} onChange={(e) => setLabel(e.target.value)}
                 onKeyDown={(e) => e.key === 'Enter' && save()}/>
        </div>
        <div className="field" style={{ flex: '2 1 320px' }}>
          <label className="field-label">Description (optional)</label>
          <input className="input" placeholder="What changed and why"
                 value={desc} onChange={(e) => setDesc(e.target.value)}/>
        </div>
        <button className="btn primary" onClick={save} disabled={busy || !label.trim()}>{busy ? 'Saving…' : 'Save'}</button>
        <button className="btn ghost" onClick={onCancel}>Cancel</button>
      </div>
      {error && <div style={{ fontSize: 13, color: 'var(--neg)', marginTop: 8 }}>{error}</div>}
      <div className="tiny muted" style={{ marginTop: 8 }}>
        Marks NOW as a named anchor. The Range row will list this for quick filtering.
      </div>
    </div>
  );
}

function StatsBody({ s }) {
  const cumPnls = (s.equity_curve || []).map(p => p.cum_pnl);
  return (
    <>
      {/* Headline KPIs */}
      <div className="grid grid-4">
        <div className="card kpi">
          <div className="kpi-label">Total P&L</div>
          <div className="kpi-value num" style={{ color: s.total_pnl >= 0 ? 'var(--pos)' : 'var(--neg)' }}>
            {USD(s.total_pnl, { sign: true })}
          </div>
          <div className="tiny muted">{s.total_trades} closed trades</div>
        </div>
        <div className="card kpi">
          <div className="kpi-label">Win rate</div>
          <div className="kpi-value num">{(s.win_rate_pct ?? 0).toFixed(1)}%</div>
          <div className="tiny muted">{s.wins} W · {s.losses} L · {s.breakevens} BE</div>
        </div>
        <div className="card kpi">
          <div className="kpi-label">Profit factor</div>
          <div className="kpi-value num">{(s.profit_factor ?? 0) >= 99999 ? '∞' : (s.profit_factor ?? 0).toFixed(2)}</div>
          <div className="tiny muted">Σ wins / |Σ losses|</div>
        </div>
        <div className="card kpi">
          <div className="kpi-label">Expectancy</div>
          <div className="kpi-value num" style={{ color: s.expectancy_per_trade >= 0 ? 'var(--pos)' : 'var(--neg)' }}>
            {USD(s.expectancy_per_trade, { sign: true })}
          </div>
          <div className="tiny muted">avg P&L per trade</div>
        </div>
      </div>

      {/* Equity curve + secondary KPIs */}
      <div className="grid" style={{ gridTemplateColumns: '2fr 1fr', gap: 18 }}>
        <div className="card">
          <div className="row" style={{ justifyContent: 'space-between', marginBottom: 14 }}>
            <div>
              <div className="tiny muted" style={{ marginBottom: 4, letterSpacing: '0.16em', textTransform: 'uppercase', fontSize: 10 }}>Cumulative P&L curve</div>
              <div className="row gap-12" style={{ alignItems: 'baseline' }}>
                <MorphNumber value={s.total_pnl} decimals={4} prefix="$" size={28} weight={600}
                             color={s.total_pnl >= 0 ? 'var(--pos)' : 'var(--neg)'}/>
                <span className="tiny muted">since {s.equity_curve.length ? new Date(s.equity_curve[0].ts).toISOString().slice(0,10) : '—'}</span>
              </div>
            </div>
          </div>
          {cumPnls.length > 1
            ? <LineChart data={cumPnls} color={s.total_pnl >= 0 ? 'var(--pos)' : 'var(--neg)'} height={260}/>
            : <div className="muted" style={{ padding: '80px 0', textAlign: 'center' }}>Not enough data yet.</div>}
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <div className="card kpi">
            <div className="kpi-label">Avg win</div>
            <div className="kpi-value num" style={{ fontSize: 20, color: 'var(--pos)' }}>{USD(s.avg_win)}</div>
          </div>
          <div className="card kpi">
            <div className="kpi-label">Avg loss</div>
            <div className="kpi-value num" style={{ fontSize: 20, color: 'var(--neg)' }}>{USD(s.avg_loss)}</div>
          </div>
          <div className="card kpi">
            <div className="kpi-label">Avg hold</div>
            <div className="kpi-value num" style={{ fontSize: 20 }}>{(s.avg_hold_minutes ?? 0).toFixed(1)} min</div>
          </div>
        </div>
      </div>

      {/* Best / worst */}
      <div className="grid grid-2">
        <div className="card">
          <div className="kpi-label">Best trade</div>
          {s.best_trade && (
            <div style={{ marginTop: 6 }}>
              <div className="row gap-10" style={{ alignItems: 'baseline' }}>
                <span style={{ fontSize: 16, fontWeight: 500 }}>{s.best_trade.symbol}</span>
                <span className="pill">{s.best_trade.side}</span>
              </div>
              <div className="num" style={{ fontSize: 24, color: 'var(--pos)', fontWeight: 600, marginTop: 6, letterSpacing: '-0.02em' }}>
                {USD(s.best_trade.pnl, { sign: true })}
              </div>
              <div className="tiny muted">trade #{s.best_trade.id}</div>
            </div>
          )}
        </div>
        <div className="card">
          <div className="kpi-label">Worst trade</div>
          {s.worst_trade && (
            <div style={{ marginTop: 6 }}>
              <div className="row gap-10" style={{ alignItems: 'baseline' }}>
                <span style={{ fontSize: 16, fontWeight: 500 }}>{s.worst_trade.symbol}</span>
                <span className="pill">{s.worst_trade.side}</span>
              </div>
              <div className="num" style={{ fontSize: 24, color: 'var(--neg)', fontWeight: 600, marginTop: 6, letterSpacing: '-0.02em' }}>
                {USD(s.worst_trade.pnl, { sign: true })}
              </div>
              <div className="tiny muted">trade #{s.worst_trade.id}</div>
            </div>
          )}
        </div>
      </div>

      {/* Group tables */}
      <div className="grid grid-2">
        <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
          <div style={{ padding: '18px 18px 10px' }}>
            <h3 className="h3">By symbol</h3>
            <div className="tiny muted">Sorted by total P&L</div>
          </div>
          <table className="tbl">
            <thead><tr><th>Symbol</th><th>N</th><th>Win %</th><th>Avg P&L</th><th>Total</th></tr></thead>
            <tbody>
              {s.by_symbol.map(g => (
                <tr key={g.key}>
                  <td style={{ fontWeight: 500 }}>{g.key}</td>
                  <td className="num">{g.n}</td>
                  <td className="num">{(g.win_rate_pct ?? 0).toFixed(0)}%</td>
                  <td className="num" style={{ color: g.avg_pnl >= 0 ? 'var(--pos)' : 'var(--neg)' }}>{USD(g.avg_pnl, { sign: true })}</td>
                  <td className="num" style={{ color: g.pnl >= 0 ? 'var(--pos)' : 'var(--neg)', fontWeight: 600 }}>{USD(g.pnl, { sign: true })}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
          <div style={{ padding: '18px 18px 10px' }}>
            <h3 className="h3">By close reason</h3>
            <div className="tiny muted">Includes errored rows so failure modes show up</div>
          </div>
          <table className="tbl">
            <thead><tr><th>Reason</th><th>N</th><th>Win %</th><th>Total</th></tr></thead>
            <tbody>
              {s.by_close_reason.map(g => (
                <tr key={g.key}>
                  <td style={{ fontWeight: 500, maxWidth: 220, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={g.key}>{g.key}</td>
                  <td className="num">{g.n}</td>
                  <td className="num">{g.n ? (g.win_rate_pct ?? 0).toFixed(0) : 0}%</td>
                  <td className="num" style={{ color: g.pnl >= 0 ? 'var(--pos)' : 'var(--neg)', fontWeight: 600 }}>{USD(g.pnl, { sign: true })}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </>
  );
}

// ---------------------- Funding panel (Phase B) ----------------------

function FundingPanel() {
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [refreshedAt, setRefreshedAt] = React.useState(null);

  const fetchData = React.useCallback(async () => {
    setLoading(true); setErr(null);
    try {
      const d = await api.get('/api/admin/funding?lookback=90&samples=30');
      setData(d);
      setRefreshedAt(new Date());
    } catch (e) {
      setErr(e.detail || e.message || 'fetch failed');
    } finally {
      setLoading(false);
    }
  }, []);

  React.useEffect(() => {
    let alive = true;
    fetchData();
    const t = setInterval(() => { if (alive) fetchData(); }, 5 * 60 * 1000);
    return () => { alive = false; clearInterval(t); };
  }, [fetchData]);

  const rows = data?.symbols || [];

  return (
    <>
      <div className="topbar">
        <div>
          <div className="crumb">Admin · Funding</div>
          <h1 className="h1">Funding rates</h1>
        </div>
        <div className="row gap-8">
          {refreshedAt && <span className="tiny muted">Updated {refreshedAt.toLocaleTimeString()}</span>}
          <button className="btn ghost" onClick={fetchData} disabled={loading}>
            {loading ? '...' : 'Refresh'}
          </button>
        </div>
      </div>
      <div className="content">
        <div className="card" style={{ padding: 16 }}>
          <div className="tiny muted">
            Per-symbol Bybit V5 funding rate. Z-score is computed over the last 90 samples (~30 days
            at 3 samples/day). Flagged red when |z| &gt; 2 — that's a sign the perpetual crowd is
            heavily one-sided, which often precedes a mean-reversion against the consensus.
          </div>
        </div>

        {err && <div className="card" style={{ color: 'var(--neg)', marginTop: 16 }}>Error: {err}</div>}

        {rows.length === 0 && !err && !loading && (
          <div className="card" style={{ marginTop: 16 }}>
            <div className="muted">
              No funding rate data yet. Make sure <code>FUNDING_RATE_ENABLED=1</code> is set on the
              worker container, and that it has had time to do its first poll (it polls 5 min past
              each 8h funding cycle — 00:05, 08:05, 16:05 UTC).
            </div>
          </div>
        )}

        {rows.length > 0 && (
          <div className="card" style={{ padding: 0, overflow: 'hidden', marginTop: 16 }}>
            <table className="tbl">
              <thead>
                <tr>
                  <th>Symbol</th>
                  <th className="num">Latest rate</th>
                  <th>Last paid</th>
                  <th className="num">Z-score (90d)</th>
                  <th className="num">Samples</th>
                  <th>Trend (last 30)</th>
                  <th>Signal</th>
                </tr>
              </thead>
              <tbody>
                {rows.map(s => {
                  const r = s.latest_rate;
                  const z = s.z_score;
                  const extreme = z != null && Math.abs(z) > 2;
                  const sparkData = (s.sparkline || []).map(p => p.r);
                  const lastT = s.latest_time ? new Date(s.latest_time) : null;
                  const ageH = lastT ? Math.round((Date.now() - lastT.getTime()) / 3600000) : null;
                  return (
                    <tr key={s.symbol}>
                      <td style={{ fontWeight: 600 }}>{s.symbol}</td>
                      <td className="num" style={{ color: r > 0 ? 'var(--pos)' : r < 0 ? 'var(--neg)' : 'inherit' }}>
                        {(r * 100).toFixed(4)}%
                      </td>
                      <td className="tiny muted">{ageH != null ? ageH + 'h ago' : '—'}</td>
                      <td className="num">
                        {z == null ? <span className="muted">collecting</span> :
                         extreme ? <span className="pill neg" style={{ display: 'inline-block' }}>z={z.toFixed(2)}</span> :
                         z.toFixed(2)}
                      </td>
                      <td className="num tiny muted">{s.sample_count}</td>
                      <td style={{ width: 180 }}>
                        {sparkData.length >= 2 ? (
                          <Sparkline data={sparkData} color={r > 0 ? 'var(--pos)' : 'var(--neg)'} fill={false} height={32}/>
                        ) : <span className="tiny muted">—</span>}
                      </td>
                      <td className="tiny">
                        {z == null ? '—' :
                         z > 2 ? <span style={{ color: 'var(--neg)' }}>Longs over-paying — fade-short candidate</span> :
                         z < -2 ? <span style={{ color: 'var(--neg)' }}>Shorts over-paying — fade-long candidate</span> :
                         <span className="muted">Within normal range</span>}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        )}
      </div>
    </>
  );
}

// ---------------------- Backtest panel (Phase C) ----------------------

function BacktestPanel() {
  const [jobs, setJobs] = React.useState([]);
  const [selectedId, setSelectedId] = React.useState(null);
  const [detail, setDetail] = React.useState(null);
  const [form, setForm] = React.useState({
    symbol: 'BTCUSDT',
    start: '2024-01-01',
    end: new Date().toISOString().slice(0, 10),
    ltf: '15m', ltf15: '15m', htf1: '1h', htf4: '4h',
    equity: 10000,
    risk: 0.01,
  });
  const [submitting, setSubmitting] = React.useState(false);
  const [submitErr, setSubmitErr] = React.useState(null);

  const refreshJobs = React.useCallback(async () => {
    try {
      const j = await api.get('/api/admin/backtests?limit=30');
      setJobs(j);
    } catch (e) {
      console.error('list jobs failed', e);
    }
  }, []);

  React.useEffect(() => {
    refreshJobs();
    const t = setInterval(refreshJobs, 3000);
    return () => clearInterval(t);
  }, [refreshJobs]);

  React.useEffect(() => {
    if (selectedId == null) { setDetail(null); return; }
    let alive = true;
    const load = async () => {
      try {
        const d = await api.get('/api/admin/backtests/' + selectedId);
        if (alive) setDetail(d);
      } catch (e) {
        if (alive) setDetail({ error: e.detail || 'load failed' });
      }
    };
    load();
    // Poll while running/queued
    const t = setInterval(() => {
      if (detail && (detail.status === 'done' || detail.status === 'failed' || detail.status === 'cancelled')) return;
      load();
    }, 3000);
    return () => { alive = false; clearInterval(t); };
  }, [selectedId, detail?.status]);

  const submitJob = async (e) => {
    e?.preventDefault();
    setSubmitting(true); setSubmitErr(null);
    try {
      const job = await api.post('/api/admin/backtests', form);
      await refreshJobs();
      setSelectedId(job.id);
    } catch (e) {
      setSubmitErr(e.detail || e.message || 'submit failed');
    } finally {
      setSubmitting(false);
    }
  };

  const setF = (k) => (e) => setForm({ ...form, [k]: e.target.value });
  const setFNum = (k) => (e) => setForm({ ...form, [k]: parseFloat(e.target.value) });

  const cancelJob = async (id) => {
    if (!confirm('Cancel job ' + id + '?')) return;
    try {
      await api.del('/api/admin/backtests/' + id);
      await refreshJobs();
      if (selectedId === id) setSelectedId(null);
    } catch (e) { alert(e.detail || 'cancel failed'); }
  };

  return (
    <>
      <div className="topbar">
        <div>
          <div className="crumb">Admin · Backtest</div>
          <h1 className="h1">Strategy backtester</h1>
        </div>
      </div>
      <div className="content">
        <div className="grid" style={{ gridTemplateColumns: '360px 1fr', gap: 18 }}>
          {/* Submit form */}
          <div className="card">
            <h2 className="h2" style={{ marginBottom: 14 }}>Run a backtest</h2>
            <form onSubmit={submitJob}>
              <BTField label="Symbol">
                <select value={form.symbol} onChange={setF('symbol')} className="bt-input">
                  {['BTCUSDT','ETHUSDT','SOLUSDT','BNBUSDT'].map(s => <option key={s} value={s}>{s}</option>)}
                </select>
              </BTField>
              <div className="row gap-8">
                <BTField label="Start" style={{ flex: 1 }}>
                  <input type="date" value={form.start} onChange={setF('start')} className="bt-input"/>
                </BTField>
                <BTField label="End" style={{ flex: 1 }}>
                  <input type="date" value={form.end} onChange={setF('end')} className="bt-input"/>
                </BTField>
              </div>
              <div className="row gap-8">
                <BTField label="LTF" style={{ flex: 1 }}>
                  <select value={form.ltf} onChange={setF('ltf')} className="bt-input">
                    {['5m','15m','30m','1h'].map(s => <option key={s} value={s}>{s}</option>)}
                  </select>
                </BTField>
                <BTField label="LTF15" style={{ flex: 1 }}>
                  <select value={form.ltf15} onChange={setF('ltf15')} className="bt-input">
                    {['15m','30m'].map(s => <option key={s} value={s}>{s}</option>)}
                  </select>
                </BTField>
              </div>
              <div className="row gap-8">
                <BTField label="HTF1" style={{ flex: 1 }}>
                  <select value={form.htf1} onChange={setF('htf1')} className="bt-input">
                    {['1h','2h'].map(s => <option key={s} value={s}>{s}</option>)}
                  </select>
                </BTField>
                <BTField label="HTF4" style={{ flex: 1 }}>
                  <select value={form.htf4} onChange={setF('htf4')} className="bt-input">
                    {['4h','6h'].map(s => <option key={s} value={s}>{s}</option>)}
                  </select>
                </BTField>
              </div>
              <BTField label="Initial equity ($)">
                <input type="number" step="100" value={form.equity} onChange={setFNum('equity')} className="bt-input"/>
              </BTField>
              <BTField label="Risk per trade">
                <input type="number" step="0.005" min="0.001" max="0.25" value={form.risk} onChange={setFNum('risk')} className="bt-input"/>
                <div className="tiny muted" style={{ marginTop: 2 }}>{(form.risk * 100).toFixed(1)}% of equity per trade</div>
              </BTField>
              {submitErr && <div className="tiny" style={{ color: 'var(--neg)', marginBottom: 8 }}>Error: {submitErr}</div>}
              <button type="submit" className="btn accent lg block" disabled={submitting}>
                {submitting ? 'Queueing…' : 'Queue backtest'}
              </button>
              <div className="tiny muted" style={{ marginTop: 8 }}>
                Backtests are run by the backtester sidecar (max 3 concurrent). A 1-year 15m
                backtest takes ~30–90s; 5-year is several minutes.
              </div>
            </form>
          </div>

          {/* Jobs list + detail */}
          <div>
            {!selectedId && (
              <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
                <div style={{ padding: '16px 20px 4px' }}>
                  <h2 className="h2">Recent jobs</h2>
                  <div className="tiny muted">Live · auto-refresh 3s</div>
                </div>
                {jobs.length === 0 ? (
                  <div className="muted" style={{ padding: 20 }}>No backtests yet — submit one on the left.</div>
                ) : (
                  <table className="tbl">
                    <thead><tr>
                      <th>ID</th><th>Symbol</th><th>Range</th><th>Status</th>
                      <th className="num">Sharpe</th><th className="num">CAGR</th><th className="num">Trades</th><th></th>
                    </tr></thead>
                    <tbody>
                      {jobs.map(j => (
                        <tr key={j.id} style={{ cursor: 'pointer' }} onClick={() => setSelectedId(j.id)}>
                          <td className="num">#{j.id}</td>
                          <td style={{ fontWeight: 600 }}>{j.symbol || '—'}</td>
                          <td className="tiny muted">{j.start || '—'} → {j.end || '—'}</td>
                          <td>
                            <BTStatusPill status={j.status}/>
                            {j.status === 'running' && <BTProgressBar pct={j.progress_pct}/>}
                          </td>
                          <td className="num">{j.sharpe != null ? j.sharpe.toFixed(2) : '—'}</td>
                          <td className="num">{j.cagr != null ? (j.cagr * 100).toFixed(1) + '%' : '—'}</td>
                          <td className="num">{j.trades ?? '—'}</td>
                          <td>
                            {(j.status === 'queued' || j.status === 'running') && (
                              <button className="btn ghost sm" onClick={(e) => { e.stopPropagation(); cancelJob(j.id); }}>Cancel</button>
                            )}
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                )}
              </div>
            )}

            {selectedId && (
              <BacktestDetailView
                detail={detail}
                onBack={() => setSelectedId(null)}
                onDelete={() => cancelJob(selectedId)}
              />
            )}
          </div>
        </div>
      </div>

      <style>{`
        .bt-input { width: 100%; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); color: var(--ink); font-family: inherit; font-size: 13px; }
        .bt-input:focus { outline: 1px solid var(--accent); }
      `}</style>
    </>
  );
}

function BTField({ label, children, style = {} }) {
  return (
    <div style={{ marginBottom: 12, ...style }}>
      <div className="tiny muted" style={{ marginBottom: 4, fontWeight: 600 }}>{label}</div>
      {children}
    </div>
  );
}

function BTProgressBar({ pct }) {
  const v = Math.max(0, Math.min(100, pct || 0));
  return (
    <div style={{ marginTop: 4, display: 'flex', alignItems: 'center', gap: 6 }}>
      <div style={{ flex: 1, height: 4, background: 'var(--line)', borderRadius: 2, overflow: 'hidden', maxWidth: 100 }}>
        <div style={{ width: v + '%', height: '100%', background: 'var(--accent)', transition: 'width 0.5s ease' }}/>
      </div>
      <span className="tiny muted">{v}%</span>
    </div>
  );
}

function BTStatusPill({ status }) {
  const map = {
    queued: { tone: 'warn', label: 'Queued' },
    running: { tone: 'pos', label: 'Running' },
    done: { tone: 'pos', label: 'Done' },
    failed: { tone: 'neg', label: 'Failed' },
    cancelled: { tone: 'muted', label: 'Cancelled' },
  };
  const { tone, label } = map[status] || { tone: 'muted', label: status };
  return <span className={'pill ' + tone}><span className="dot"/>{label}</span>;
}

function BacktestDetailView({ detail, onBack, onDelete }) {
  if (!detail) return <div className="card">Loading…</div>;
  if (detail.error) return <div className="card" style={{ color: 'var(--neg)' }}>{detail.error}</div>;

  const m = detail.metrics || {};
  const p = detail.params || {};
  const eqCurve = (detail.equity_curve || []).map(e => e.eq);

  return (
    <div className="card">
      <div className="row" style={{ justifyContent: 'space-between' }}>
        <div>
          <button className="btn ghost sm" onClick={onBack}>← Back to list</button>
          <h2 className="h2" style={{ marginTop: 8 }}>
            Job #{detail.id} · {p.symbol} · <BTStatusPill status={detail.status}/>
          </h2>
          <div className="tiny muted">{p.start} → {p.end} · {p.ltf}/{p.ltf15}/{p.htf1}/{p.htf4} · equity ${p.equity} · risk {(p.risk * 100).toFixed(1)}%</div>
        </div>
        <div className="row gap-8">
          {detail.status === 'done' && (detail.trades?.length > 0) && (
            <a href={'/api/admin/backtests/' + detail.id + '/trades.csv'}
               download
               className="btn ghost sm"
               onClick={(e) => {
                 // Need to add auth header — use a fetch + blob workaround
                 e.preventDefault();
                 (async () => {
                   const tok = api.getToken();
                   const r = await fetch('/api/admin/backtests/' + detail.id + '/trades.csv', {
                     headers: tok ? { 'Authorization': 'Bearer ' + tok } : {},
                   });
                   const blob = await r.blob();
                   const url = URL.createObjectURL(blob);
                   const a = document.createElement('a');
                   a.href = url;
                   a.download = 'backtest-' + detail.id + '-trades.csv';
                   a.click();
                   URL.revokeObjectURL(url);
                 })();
               }}>Download CSV</a>
          )}
          {(detail.status === 'done' || detail.status === 'failed' || detail.status === 'cancelled') && (
            <button className="btn ghost sm" onClick={onDelete}>Delete</button>
          )}
        </div>
      </div>

      {detail.status === 'failed' && (
        <div className="card" style={{ marginTop: 16, background: 'rgba(220,38,38,0.08)' }}>
          <div style={{ fontWeight: 600, color: 'var(--neg)' }}>Job failed</div>
          <div className="tiny" style={{ marginTop: 6, fontFamily: 'var(--font-mono)' }}>{detail.error_message}</div>
        </div>
      )}

      {detail.status === 'queued' && (
        <div className="muted" style={{ marginTop: 16 }}>Waiting for the backtester sidecar to pick up this job…</div>
      )}

      {detail.status === 'running' && (
        <div style={{ marginTop: 16 }}>
          <div className="muted" style={{ marginBottom: 8 }}>
            Running simulation in a subprocess — typically 1–3 minutes depending on date range.
          </div>
          <BTProgressBar pct={detail.progress_pct}/>
        </div>
      )}

      {detail.status === 'done' && (
        <>
          <div className="grid" style={{ gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginTop: 18 }}>
            <BTMetric label="Final equity" value={m.final_equity != null ? '$' + fmtNum(m.final_equity, 2) : '—'}/>
            <BTMetric label="Total return" value={m.total_return_pct != null ? (m.total_return_pct * 100).toFixed(2) + '%' : '—'} tone={m.total_return_pct >= 0 ? 'pos' : 'neg'}/>
            <BTMetric label="CAGR" value={m.cagr != null ? (m.cagr * 100).toFixed(2) + '%' : '—'} tone={m.cagr >= 0 ? 'pos' : 'neg'}/>
            <BTMetric label="Sharpe" value={m.sharpe != null ? m.sharpe.toFixed(2) : '—'}/>
            <BTMetric label="Sortino" value={m.sortino != null ? m.sortino.toFixed(2) : '—'}/>
            <BTMetric label="Max drawdown" value={m.max_drawdown != null ? (m.max_drawdown * 100).toFixed(2) + '%' : '—'} tone="neg"/>
            <BTMetric label="DD duration" value={m.max_drawdown_duration_days != null ? m.max_drawdown_duration_days.toFixed(0) + ' d' : '—'}/>
            <BTMetric label="Win rate" value={m.win_rate != null ? (m.win_rate * 100).toFixed(1) + '%' : '—'}/>
            <BTMetric label="Avg R" value={m.avg_r != null ? m.avg_r.toFixed(2) : '—'}/>
            <BTMetric label="Profit factor" value={m.profit_factor != null && isFinite(m.profit_factor) ? m.profit_factor.toFixed(2) : (m.profit_factor === Infinity ? '∞' : '—')}/>
            <BTMetric label="Trades" value={m.trades ?? '—'}/>
            <BTMetric label="Time in market" value={m.time_in_market != null ? (m.time_in_market * 100).toFixed(1) + '%' : '—'}/>
          </div>

          {eqCurve.length >= 2 && (
            <div style={{ marginTop: 18 }}>
              <div className="tiny muted" style={{ marginBottom: 6, fontWeight: 600 }}>Equity curve</div>
              <Sparkline data={eqCurve} color="var(--accent)" fill={true} height={180}/>
            </div>
          )}

          {detail.trades?.length > 0 && (
            <div style={{ marginTop: 18 }}>
              <div className="tiny muted" style={{ marginBottom: 6, fontWeight: 600 }}>Trade ledger ({detail.trades.length} trades, first 50)</div>
              <div style={{ maxHeight: 320, overflow: 'auto', border: '1px solid var(--line)', borderRadius: 8 }}>
                <table className="tbl">
                  <thead><tr>
                    <th>Entry</th><th>Exit</th><th>Side</th><th className="num">Entry</th>
                    <th className="num">Stop</th><th className="num">R</th><th className="num">PnL%</th><th>Reason</th>
                  </tr></thead>
                  <tbody>
                    {detail.trades.slice(0, 50).map((t, i) => (
                      <tr key={i}>
                        <td className="tiny">{t.entry_time?.slice(0, 16).replace('T', ' ')}</td>
                        <td className="tiny">{t.exit_time?.slice(0, 16).replace('T', ' ')}</td>
                        <td><span className="pill">{t.side}</span></td>
                        <td className="num tiny">{fmtNum(t.entry_px, 2)}</td>
                        <td className="num tiny">{fmtNum(t.stop_px, 2)}</td>
                        <td className="num" style={{ color: t.realized_r >= 0 ? 'var(--pos)' : 'var(--neg)' }}>{t.realized_r?.toFixed(2)}</td>
                        <td className="num tiny">{(t.realized_pnl_pct * 100)?.toFixed(2)}%</td>
                        <td className="tiny muted">{t.exit_reason}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </div>
          )}
        </>
      )}
    </div>
  );
}

function BTMetric({ label, value, tone }) {
  const color = tone === 'pos' ? 'var(--pos)' : tone === 'neg' ? 'var(--neg)' : 'inherit';
  return (
    <div>
      <div className="tiny muted" style={{ textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>{label}</div>
      <div className="num" style={{ fontSize: 18, fontWeight: 700, marginTop: 4, color }}>{value}</div>
    </div>
  );
}

Object.assign(window, { AdminGodView, Approvals, KillSwitch, Settings, ExchangeBalance, AdminBotDetail, Users, Stats, FundingPanel, BacktestPanel });
