// views/Admin.jsx — Admin / Integrations dashboard
function AdminView({ onRoute }) {
  const [authed, setAuthed] = React.useState(window.localStorage.getItem('wcl_admin') === '1');
  if (!authed) return <AdminLogin onAuth={() => { window.localStorage.setItem('wcl_admin', '1'); setAuthed(true); }} />;
  return <AdminConsole onRoute={onRoute} onLogout={() => { window.localStorage.removeItem('wcl_admin'); setAuthed(false); }} />;
}

// ---------- Login ----------
function AdminLogin({ onAuth }) {
  const [email, setEmail] = React.useState('admin@worldcup.live');
  const [password, setPassword] = React.useState('••••••••••••');
  const [submitting, setSubmitting] = React.useState(false);

  function submit(e) {
    e.preventDefault();
    setSubmitting(true);
    setTimeout(() => { onAuth(); }, 500);
  }

  return (
    <div style={{
      minHeight: 'calc(100vh - var(--shell-header-h) - var(--shell-footer-h) - 40px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
    }}>
      <form onSubmit={submit} style={{
        width: 400,
        background: 'var(--card)',
        border: '1px solid var(--border)',
        borderRadius: 'var(--radius-lg)',
        padding: '32px 28px',
      }}>
        <div style={{
          width: 44, height: 44, borderRadius: 12,
          background: 'linear-gradient(135deg, #006699 0%, #00477a 100%)',
          color: 'white',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          marginBottom: 20,
        }}><SoccerBall size={26} /></div>
        <div className="strong" style={{ fontSize: 18, fontWeight: 600, marginBottom: 4 }}>Admin sign in</div>
        <div className="subtle" style={{ fontSize: 13, marginBottom: 24 }}>World Cup Live · staff console</div>

        <div style={{ marginBottom: 14 }}>
          <label className="label">Email</label>
          <input className="input" type="email" value={email} onChange={e => setEmail(e.target.value)} required />
        </div>
        <div style={{ marginBottom: 18 }}>
          <label className="label">Password</label>
          <input className="input" type="password" value={password} onChange={e => setPassword(e.target.value)} required />
        </div>
        <button className="btn btn-primary" type="submit" disabled={submitting} style={{ width: '100%', height: 38 }}>
          {submitting ? 'Signing in…' : 'Sign in'}
        </button>
        <div className="subtle" style={{ fontSize: 11, textAlign: 'center', marginTop: 16 }}>
          SSO · Google · GitHub <span style={{ margin: '0 6px' }}>·</span> Forgot password?
        </div>

        <div style={{ marginTop: 24, padding: 12, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--radius-md)' }}>
          <div className="eyebrow" style={{ marginBottom: 6 }}>Demo</div>
          <div className="subtle" style={{ fontSize: 12 }}>This is a prototype console. Any credentials will sign you in.</div>
        </div>
      </form>
    </div>
  );
}

// ---------- Console ----------
function AdminConsole({ onRoute, onLogout }) {
  const integrations = [
    {
      id: 'api-football',
      name: 'API-Football',
      vendor: 'API-Sports',
      url: 'https://www.api-football.com',
      logo: '⚽',
      plan: 'Free',
      planLimit: '100 requests / day',
      requestsUsed: 87,
      requestsCap: 100,
      lastSync: '14m ago',
      status: 'live',
      role: 'Primary fixtures, live scores, lineups, events',
      key: 'X-RapidAPI-Key',
      keyMasked: 'cm·····························8a',
      docs: 'api-sports.io/documentation/football/v3',
      endpoints: [
        { p: '/v3/fixtures?live=all',     n: 'Live fixtures',     hits: 312, status: 'ok' },
        { p: '/v3/fixtures?league=1&season=2026', n: 'WC fixtures', hits: 41,  status: 'ok' },
        { p: '/v3/fixtures/lineups',      n: 'Match lineups',     hits: 24,  status: 'ok' },
        { p: '/v3/fixtures/events',       n: 'Match events',      hits: 116, status: 'ok' },
        { p: '/v3/fixtures/statistics',   n: 'Match statistics',  hits: 22,  status: 'ok' },
        { p: '/v3/players/topscorers',    n: 'Top scorers',       hits: 6,   status: 'ok' },
        { p: '/v3/standings',             n: 'Group standings',   hits: 8,   status: 'ok' },
        { p: '/v3/odds?bookmaker=8',      n: 'Match odds',        hits: 18,  status: 'warn' },
      ],
      note: 'Free plan caps at 100 req/day with 10 req/min. We poll critical endpoints during live windows only and snapshot to S3.',
    },
    {
      id: 'statsbomb',  name: 'StatsBomb',         vendor: 'StatsBomb',      url: 'https://statsbomb.com', logo: '◆',
      plan: 'Trial',    planLimit: '5 matches preview',
      requestsUsed: 4,  requestsCap: 5,
      lastSync: '2h ago', status: 'preview',
      role: 'Advanced xG / pressure / shot-quality data',
      key: 'STATSBOMB_API_KEY', keyMasked: 'sb·····························3f',
      docs: 'github.com/statsbomb/open-data',
      endpoints: [
        { p: 'matches/{match_id}/events.json',   n: 'Event data',  hits: 4, status: 'ok' },
        { p: 'lineups/{match_id}.json',          n: 'Lineups',     hits: 4, status: 'ok' },
      ],
      note: 'Trial expires 2026-07-15. Upgrade to Pro for full live coverage at €4,800/mo.',
    },
    {
      id: 'wikipedia', name: 'Wikipedia REST API', vendor: 'Wikimedia', url: 'https://en.wikipedia.org/api/rest_v1', logo: 'W',
      plan: 'Public',  planLimit: 'No key · 200 req/s',
      requestsUsed: 1820, requestsCap: null,
      lastSync: '12s ago', status: 'live',
      role: 'Player & team imagery (CC-BY-SA licensed)',
      key: '— none —', keyMasked: '— public endpoint —',
      docs: 'wikitech.wikimedia.org/wiki/API',
      endpoints: [
        { p: '/page/summary/{title}', n: 'Page summary + thumbnail', hits: 1820, status: 'ok' },
      ],
      note: 'Used for player photos on the Players page. We cache thumbnails for 24h.',
    },
    {
      id: 'fifa',      name: 'FIFA Rankings',     vendor: 'FIFA.com',       url: 'https://www.fifa.com/ranking', logo: 'F',
      plan: 'Scraped', planLimit: '—',
      requestsUsed: 1, requestsCap: 1,
      lastSync: '6d ago', status: 'live',
      role: 'Monthly FIFA/Coca-Cola world ranking',
      key: '—', keyMasked: '— scrape —',
      docs: '—',
      endpoints: [
        { p: '/ranking/men · /ranking/women', n: 'World ranking', hits: 1, status: 'ok' },
      ],
      note: 'Refreshed monthly on ranking release date. Cached as JSON.',
    },
    {
      id: 'odds-api',  name: 'The Odds API',      vendor: 'odds-api.com',   url: 'https://the-odds-api.com', logo: '∑',
      plan: 'Free',    planLimit: '500 requests / month',
      requestsUsed: 318, requestsCap: 500,
      lastSync: '7m ago', status: 'live',
      role: 'Market consensus odds across 40+ bookmakers',
      key: 'ODDS_API_KEY', keyMasked: 'od·····························d2',
      docs: 'the-odds-api.com/liveapi/guides',
      endpoints: [
        { p: '/v4/sports/soccer_fifa_world_cup/odds', n: 'WC market odds', hits: 318, status: 'ok' },
      ],
      note: 'Used for the "market" column in Odds & Model. Refresh during pre-match windows only.',
    },
    {
      id: 'sendgrid',  name: 'SendGrid',          vendor: 'Twilio',         url: 'https://sendgrid.com', logo: '✉',
      plan: 'Free',    planLimit: '100 emails / day',
      requestsUsed: 41, requestsCap: 100,
      lastSync: '34m ago', status: 'live',
      role: 'Daily challenge result emails',
      key: 'SENDGRID_API_KEY', keyMasked: 'sg·····························a7',
      docs: 'docs.sendgrid.com',
      endpoints: [
        { p: '/v3/mail/send', n: 'Transactional', hits: 41, status: 'ok' },
      ],
      note: 'Will exceed 100/day if we cross 100 active picks-game users.',
    },
    {
      id: 'plausible', name: 'Plausible Analytics',vendor: 'Plausible',     url: 'https://plausible.io', logo: 'P',
      plan: 'Starter', planLimit: '10k pageviews / mo',
      requestsUsed: 6840, requestsCap: 10000,
      lastSync: '40s ago', status: 'live',
      role: 'Privacy-friendly analytics',
      key: 'PLAUSIBLE_SITE_ID', keyMasked: 'worldcup.live',
      docs: 'plausible.io/docs',
      endpoints: [],
      note: 'Self-hosted Plausible instance · we own the data.',
    },
    {
      id: 'stripe',    name: 'Stripe',            vendor: 'Stripe',         url: 'https://stripe.com', logo: 'S',
      plan: 'Test mode',planLimit: '—',
      requestsUsed: 0, requestsCap: null,
      lastSync: '—',   status: 'preview',
      role: 'Premium subscriptions (planned · disabled at launch)',
      key: 'STRIPE_SECRET_KEY', keyMasked: 'sk_test_······························',
      docs: 'stripe.com/docs',
      endpoints: [],
      note: 'Not active. Premium tier ships post-tournament.',
    },
    {
      id: 'github',    name: 'GitHub',            vendor: 'GitHub',         url: 'https://github.com', logo: 'G',
      plan: 'Team',    planLimit: '—',
      requestsUsed: 248,requestsCap: null,
      lastSync: '3m ago', status: 'live',
      role: 'Source control · deploys via Vercel',
      key: 'GITHUB_PAT', keyMasked: 'ghp_······························x4',
      docs: 'docs.github.com/rest',
      endpoints: [],
      note: 'Main branch auto-deploys to production. Preview on every PR.',
    },
  ];

  const live = integrations.filter(i => i.status === 'live').length;
  const warn = integrations.filter(i => i.status === 'warn' || i.status === 'preview').length;
  const totalCalls = integrations.reduce((s, i) => s + (i.requestsUsed || 0), 0);
  const [tab, setTab] = React.useState('dashboard');

  return (
    <div>
      <AdminTabs tab={tab} setTab={setTab} onLogout={onLogout} />

      {tab === 'dashboard' && <AdminDashboard integrations={integrations} stats={{ live, warn, totalCalls }} onRoute={onRoute} go={setTab} />}
      {tab === 'data' && <AdminData onRoute={onRoute} />}
      {tab === 'integrations' && (
      <>
      <div className="page-head">
        <div>
          <div className="breadcrumb" style={{ display: 'flex', gap: 6, color: 'var(--foreground-muted)', fontSize: 12, marginBottom: 8 }}>
            <span className="subtle">Admin</span>
            <I.ChevR size={12} />
            <span>Integrations</span>
          </div>
          <h1 className="type-h1" style={{ margin: 0 }}>Integrations &amp; data sources</h1>
          <div className="sub">Every external service powering World Cup Live · keys, limits, health.</div>
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn btn-secondary btn-sm"><I.Plus size={14} /> Add integration</button>
        </div>
      </div>

      <div className="col4" style={{ marginBottom: 16 }}>
        <div className="kpi"><div className="kpi-label">Integrations</div><div className="kpi-value">{integrations.length}</div><div className="kpi-delta">{live} live · {warn} preview</div></div>
        <div className="kpi"><div className="kpi-label">API calls · today</div><div className="kpi-value">{totalCalls.toLocaleString()}</div><div className="kpi-delta">all sources combined</div></div>
        <div className="kpi"><div className="kpi-label">Primary feed</div><div className="kpi-value" style={{ fontSize: 18, fontWeight: 600 }}>API-Football</div><div className="kpi-delta">87/100 free quota today</div></div>
        <div className="kpi"><div className="kpi-label">Free-tier headroom</div><div className="kpi-value">13</div><div className="kpi-delta" style={{ color: 'var(--warning)' }}>requests left today</div></div>
      </div>

      {/* Primary spotlight: API-Football free plan */}
      <ApiFootballCard integration={integrations[0]} />

      <section className="panel" style={{ marginTop: 16 }}>
        <div className="panel-head">
          <h3>All integrations</h3>
          <div style={{ display: 'flex', gap: 6 }}>
            <button className="btn btn-ghost btn-sm">Export keys</button>
            <button className="btn btn-ghost btn-sm">Rotate all</button>
          </div>
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {integrations.slice(1).map(i => <IntegrationRow key={i.id} i={i} />)}
        </div>
      </section>

      <section className="panel" style={{ marginTop: 16 }}>
        <div className="panel-head">
          <h3>Page & feature tests</h3>
          <span className="subtle" style={{ fontSize: 11 }}>per page \u00b7 per feature \u00b7 live checks</span>
        </div>
        <FeatureTests onRoute={onRoute} />
      </section>

      <section className="panel" style={{ marginTop: 16 }}>
        <div className="panel-head">
          <h3>Test integrations</h3>
          <span className="subtle" style={{ fontSize: 11 }}>simulated request \u00b7 prototype mode</span>
        </div>
        <IntegrationTester integrations={integrations} />
      </section>

      <section className="panel" style={{ marginTop: 16 }}>
        <div className="panel-head"><h3>Recent API events</h3><span className="subtle" style={{ fontSize: 11 }}>last 100</span></div>
        <table className="table">
          <thead>
            <tr>
              <th style={{ width: 90 }}>Time</th>
              <th style={{ width: 120 }}>Source</th>
              <th style={{ width: 60 }}>Method</th>
              <th>Endpoint</th>
              <th style={{ width: 60, textAlign: 'right' }}>Status</th>
              <th style={{ width: 70, textAlign: 'right' }}>ms</th>
              <th style={{ width: 80, textAlign: 'right' }}>Bytes</th>
            </tr>
          </thead>
          <tbody>
            {[
              ['00:01:24', 'API-Football',  'GET', '/v3/fixtures?live=all',                            200,  142,  '12.4k'],
              ['00:01:14', 'Odds API',      'GET', '/v4/sports/soccer_fifa_world_cup/odds',            200,  211,  '48.1k'],
              ['00:01:04', 'API-Football',  'GET', '/v3/fixtures/events?fixture=1158211',              200,  98,   '4.2k'],
              ['00:00:54', 'API-Football',  'GET', '/v3/fixtures/statistics?fixture=1158211',          200,  121,  '3.1k'],
              ['00:00:44', 'Wikipedia',     'GET', '/page/summary/Kylian_Mbappé',                      200,  64,   '8.7k'],
              ['00:00:24', 'API-Football',  'GET', '/v3/standings?league=1&season=2026',               200,  187,  '6.6k'],
              ['00:00:14', 'SendGrid',      'POST','/v3/mail/send',                                     202,  240,  '1.1k'],
              ['00:00:04', 'API-Football',  'GET', '/v3/odds?fixture=1158211&bookmaker=8',             429,  41,   '0.4k'],
            ].map((r, i) => (
              <tr key={i}>
                <td className="mono subtle">{r[0]}</td>
                <td className="strong">{r[1]}</td>
                <td className="mono">{r[2]}</td>
                <td className="mono subtle" style={{ fontSize: 12 }}>{r[3]}</td>
                <td className="mono" style={{ textAlign: 'right', color: r[4] >= 400 ? 'var(--destructive)' : r[4] >= 300 ? 'var(--warning)' : 'var(--success)' }}>{r[4]}</td>
                <td className="mono" style={{ textAlign: 'right' }}>{r[5]}</td>
                <td className="mono subtle" style={{ textAlign: 'right' }}>{r[6]}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </section>
      </>
      )}
    </div>
  );
}

// ---------- API-Football spotlight ----------
function ApiFootballCard({ integration: i }) {
  const pct = (i.requestsUsed / i.requestsCap) * 100;
  return (
    <section className="panel" style={{ background: 'linear-gradient(135deg, color-mix(in oklch, var(--brand) 10%, var(--card)), var(--card))', borderColor: 'color-mix(in oklch, var(--brand) 30%, var(--border))' }}>
      <div style={{ display: 'flex', gap: 24, alignItems: 'flex-start', flexWrap: 'wrap' }}>
        <div style={{
          width: 64, height: 64, borderRadius: 14, flexShrink: 0,
          background: 'linear-gradient(135deg, #006699 0%, #00477a 100%)',
          color: 'white',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontWeight: 700, fontSize: 28,
        }}>{i.logo}</div>

        <div style={{ flex: 1, minWidth: 280 }}>
          <div className="eyebrow" style={{ marginBottom: 6 }}>Primary data source</div>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
            <h2 style={{ margin: 0, fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em' }}>{i.name}</h2>
            <span className="badge live"><span className="badge-dot" style={{ background: 'var(--success)' }} /> Live</span>
            <span className="mono subtle" style={{ fontSize: 12 }}>{i.plan} plan</span>
          </div>
          <div className="subtle" style={{ fontSize: 13, marginTop: 6, maxWidth: 540 }}>
            Fixtures, live scores, lineups, events, statistics and standings for every World Cup match. Free plan capped at 100 requests/day · 10/min.
          </div>
          <div style={{ display: 'flex', gap: 18, marginTop: 14, fontSize: 12, flexWrap: 'wrap' }}>
            <span><span className="eyebrow">Vendor</span> <span className="strong" style={{ marginLeft: 8 }}>{i.vendor}</span></span>
            <span><span className="eyebrow">API key</span> <span className="mono" style={{ marginLeft: 8 }}>{i.keyMasked}</span></span>
            <span><span className="eyebrow">Docs</span> <a className="link mono" href={'https://' + i.docs} target="_blank" rel="noreferrer" style={{ marginLeft: 8, color: 'var(--brand-fg)' }}>{i.docs} <I.ArrowUR size={10} /></a></span>
          </div>
        </div>

        <div style={{ flex: '0 0 280px', minWidth: 240 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 4 }}>
            <span className="eyebrow">Daily quota</span>
            <span className="mono" style={{ color: pct > 80 ? 'var(--warning)' : 'var(--foreground)', fontSize: 13, fontWeight: 600 }}>{i.requestsUsed} / {i.requestsCap}</span>
          </div>
          <div style={{ height: 8, background: 'var(--muted)', borderRadius: 999, overflow: 'hidden', border: '1px solid var(--border)' }}>
            <div style={{
              height: '100%',
              width: pct + '%',
              background: pct > 90 ? 'var(--destructive)' : pct > 70 ? 'var(--warning)' : 'var(--pitch)',
              transition: 'width var(--dur-slow) var(--ease-out)',
            }} />
          </div>
          <div className="subtle" style={{ fontSize: 11, marginTop: 6 }}>Resets in 13h 24m · 100% covers ~6 live-match minutes</div>
          <div style={{ display: 'flex', gap: 6, marginTop: 12 }}>
            <button className="btn btn-secondary btn-sm" style={{ flex: 1 }}><I.ArrowUR size={12} /> Upgrade plan</button>
            <button className="btn btn-ghost btn-sm">Rotate</button>
          </div>
        </div>
      </div>

      {/* Endpoints */}
      <div className="divider" />
      <div className="eyebrow" style={{ marginBottom: 10 }}>Wired endpoints</div>
      <table className="table">
        <thead>
          <tr>
            <th>Endpoint</th>
            <th>Used for</th>
            <th style={{ width: 90, textAlign: 'right' }}>Today</th>
            <th style={{ width: 80, textAlign: 'right' }}>Health</th>
          </tr>
        </thead>
        <tbody>
          {i.endpoints.map(e => (
            <tr key={e.p}>
              <td className="mono" style={{ fontSize: 12 }}>{e.p}</td>
              <td className="strong">{e.n}</td>
              <td className="mono" style={{ textAlign: 'right' }}>{e.hits}</td>
              <td style={{ textAlign: 'right' }}>
                {e.status === 'ok' ? <span className="badge live"><span className="badge-dot" style={{ background: 'var(--success)' }} /> ok</span>
                 : e.status === 'warn' ? <span className="badge warn"><span className="badge-dot" style={{ background: 'var(--warning)' }} /> 429</span>
                 : <span className="badge fail">fail</span>}
              </td>
            </tr>
          ))}
        </tbody>
      </table>

      <div style={{
        marginTop: 16, padding: 14,
        background: 'color-mix(in oklch, var(--warning) 10%, transparent)',
        border: '1px solid color-mix(in oklch, var(--warning) 30%, transparent)',
        borderRadius: 'var(--radius-md)',
        display: 'flex', alignItems: 'flex-start', gap: 12, fontSize: 13,
      }}>
        <span style={{ color: 'var(--warning)', fontSize: 16, lineHeight: 1 }}>⚠</span>
        <div>
          <div className="strong" style={{ fontWeight: 600, marginBottom: 4 }}>Free-tier strategy</div>
          <div className="subtle" style={{ lineHeight: 1.55 }}>{i.note} For matches outside live windows we snapshot the daily fixtures payload once at 06:00 UTC, then serve from cache.</div>
        </div>
      </div>
    </section>
  );
}

// ---------- Individual integration card ----------
function IntegrationCard({ i }) {
  const pct = i.requestsCap ? (i.requestsUsed / i.requestsCap) * 100 : null;
  return (
    <article style={{
      padding: 16,
      background: 'var(--surface)',
      border: '1px solid var(--border)',
      borderRadius: 'var(--radius-md)',
      display: 'flex', flexDirection: 'column', gap: 10,
    }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
        <div style={{
          width: 36, height: 36, borderRadius: 8, flexShrink: 0,
          background: 'var(--muted)', color: 'var(--foreground)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontWeight: 700, fontSize: 16,
        }}>{i.logo}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="strong" style={{ fontSize: 14, fontWeight: 600 }}>{i.name}</div>
          <div className="subtle" style={{ fontSize: 11, marginTop: 2 }}>{i.vendor} · {i.role}</div>
        </div>
        <span className={'badge ' + (i.status === 'live' ? 'live' : i.status === 'preview' ? 'preview' : i.status === 'warn' ? 'warn' : 'neutral')}>
          <span className="badge-dot" style={{ background: i.status === 'live' ? 'var(--success)' : i.status === 'preview' ? 'var(--info)' : 'var(--warning)' }} />
          {i.status}
        </span>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, fontSize: 11 }}>
        <div><span className="eyebrow">Plan</span><div className="mono strong" style={{ marginTop: 2 }}>{i.plan}</div></div>
        <div><span className="eyebrow">Limit</span><div className="mono" style={{ marginTop: 2 }}>{i.planLimit}</div></div>
        <div><span className="eyebrow">Key</span><div className="mono" style={{ marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis' }}>{i.keyMasked}</div></div>
        <div><span className="eyebrow">Last sync</span><div className="mono" style={{ marginTop: 2 }}>{i.lastSync}</div></div>
      </div>

      {pct != null && (
        <div>
          <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, marginBottom: 4 }}>
            <span className="subtle">Usage</span>
            <span className="mono" style={{ color: pct > 80 ? 'var(--warning)' : 'var(--foreground)' }}>{i.requestsUsed} / {i.requestsCap}</span>
          </div>
          <div style={{ height: 4, background: 'var(--muted)', borderRadius: 999, overflow: 'hidden' }}>
            <div style={{ height: '100%', width: pct + '%', background: pct > 90 ? 'var(--destructive)' : pct > 70 ? 'var(--warning)' : 'var(--pitch)' }} />
          </div>
        </div>
      )}

      <div style={{ display: 'flex', gap: 6, marginTop: 4 }}>
        <button className="btn btn-ghost btn-sm" style={{ flex: 1 }}>Manage</button>
        <button className="btn btn-ghost btn-sm" style={{ flex: 1 }}><I.ArrowUR size={11} /> Docs</button>
      </div>
    </article>
  );
}

Object.assign(window, { AdminView, AdminLogin, AdminConsole, ApiFootballCard, IntegrationCard });

// ---------- Integration tester ----------
// Realistic-shaped fake responses for each integration's endpoint(s).
function fakeResponseFor(intId, endpoint) {
  const base = { status: 200, latencyMs: 80 + Math.floor(Math.random() * 240), source: intId, ts: new Date().toISOString() };
  if (intId === 'api-football') {
    if (endpoint.includes('fixtures?live=all')) {
      return { ...base, count: 2, response: [
        { fixture: { id: 1158211, status: { elapsed: 67, short: '2H' }, venue: { name: 'MetLife Stadium', city: 'East Rutherford' } },
          teams: { home: { name: 'Argentina' }, away: { name: 'Croatia' } },
          goals: { home: 2, away: 1 },
        },
        { fixture: { id: 1158212, status: { elapsed: 12, short: '1H' }, venue: { name: 'Estadio BBVA', city: 'Monterrey' } },
          teams: { home: { name: 'Mexico' }, away: { name: 'Canada' } },
          goals: { home: 0, away: 0 },
        },
      ]};
    }
    if (endpoint.includes('lineups')) {
      return { ...base, count: 2, response: [
        { team: { name: 'Argentina' }, formation: '4-3-3', startXI: [{ player: { name: 'L. Messi', pos: 'F', number: 10 } }] },
      ]};
    }
    if (endpoint.includes('events')) {
      return { ...base, count: 11, response: [
        { time: { elapsed: 23 }, team: { name: 'Argentina' }, player: { name: 'L. Messi' }, type: 'Goal', detail: 'Normal Goal' },
        { time: { elapsed: 41 }, team: { name: 'Croatia' }, player: { name: 'A. Kramari\u0107' }, type: 'Goal', detail: 'Penalty' },
        { time: { elapsed: 54 }, team: { name: 'Argentina' }, player: { name: 'J. \u00c1lvarez' }, type: 'Goal', detail: 'Normal Goal' },
      ]};
    }
    if (endpoint.includes('statistics')) {
      return { ...base, count: 2, response: [
        { team: { name: 'Argentina' }, statistics: [
          { type: 'Shots on Goal', value: 6 }, { type: 'Total Shots', value: 14 },
          { type: 'Ball Possession', value: '58%' }, { type: 'Passes accurate', value: 421 },
        ]},
      ]};
    }
    if (endpoint.includes('standings')) {
      return { ...base, response: [{ league: { id: 1, name: 'World Cup', season: 2026 }, standings: [[
        { rank: 1, team: { name: 'Argentina' }, all: { played: 2, win: 2, draw: 0, lose: 0 }, points: 6 },
        { rank: 2, team: { name: 'Croatia' }, all: { played: 2, win: 0, draw: 1, lose: 1 }, points: 1 },
      ]]}]};
    }
    if (endpoint.includes('topscorers')) {
      return { ...base, response: [
        { player: { name: 'Kylian Mbapp\u00e9' }, statistics: [{ goals: { total: 3 } }] },
        { player: { name: 'Lionel Messi' }, statistics: [{ goals: { total: 3 } }] },
      ]};
    }
    if (endpoint.includes('odds')) {
      // 429 rate-limit demo
      return { ...base, status: 429, error: 'Too Many Requests', message: 'You exceeded 10 req/min on the free plan. Retry in 47s.' };
    }
    if (endpoint.includes('fixtures')) {
      return { ...base, count: 6, response: [
        { fixture: { id: 1158300, date: '2026-06-22T20:00:00Z' }, teams: { home: { name: 'Argentina' }, away: { name: 'Croatia' } } },
      ]};
    }
  }
  if (intId === 'statsbomb') {
    return { ...base, response: [{ event_id: 'a1b2', minute: 23, player: { name: 'L. Messi' }, type: 'Shot', xg: 0.42, outcome: 'Goal' }] };
  }
  if (intId === 'wikipedia') {
    return { ...base, response: {
      title: endpoint.split('/').pop().replace(/_/g, ' '),
      description: 'Argentine professional footballer',
      thumbnail: { source: 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/89/Lionel_Messi_20180626.jpg/200px-Lionel_Messi_20180626.jpg', width: 200, height: 267 },
      extract: 'Lionel Andr\u00e9s Messi is an Argentine professional footballer who plays as a forward for...',
    }};
  }
  if (intId === 'fifa') {
    return { ...base, response: [
      { rank: 1, team: 'Argentina', points: 1855 },
      { rank: 2, team: 'France',    points: 1843 },
      { rank: 3, team: 'Spain',     points: 1817 },
    ]};
  }
  if (intId === 'odds-api') {
    return { ...base, response: [
      { sport_key: 'soccer_fifa_world_cup', commence_time: '2026-07-19T20:00:00Z',
        home_team: 'Argentina', away_team: 'Brazil',
        bookmakers: [{ key: 'pinnacle', markets: [{ key: 'h2h', outcomes: [
          { name: 'Argentina', price: 2.32 }, { name: 'Draw', price: 3.20 }, { name: 'Brazil', price: 3.05 }
        ]}]}],
      }
    ]};
  }
  if (intId === 'sendgrid') {
    return { ...base, status: 202, response: { message: 'Accepted · message queued', message_id: 'wcl-' + Math.random().toString(36).slice(2, 10) } };
  }
  if (intId === 'plausible') {
    return { ...base, response: { pageviews: 6840, visitors: 2418, bounce_rate: 0.41, top_page: '/match/arg-cro' } };
  }
  if (intId === 'stripe') {
    return { ...base, status: 403, error: 'forbidden', message: 'Test mode key cannot create live charges. Switch to live keys to test billing.' };
  }
  if (intId === 'github') {
    return { ...base, response: { repo: 'worldcup-live/app', default_branch: 'main', last_commit: { sha: 'a1b2c3d', author: 'mat', message: 'fix: rate limit Odds API polling' } } };
  }
  return { ...base, response: { ok: true } };
}

function IntegrationTester({ integrations }) {
  const flat = integrations.flatMap(i => (i.endpoints?.length ? i.endpoints : [{ p: '/', n: 'Default ping' }]).map(e => ({ i, e })));
  const [sel, setSel] = React.useState(0);
  const [running, setRunning] = React.useState(false);
  const [result, setResult] = React.useState(null);
  const [history, setHistory] = React.useState([]);

  const cur = flat[sel];

  function runTest() {
    setRunning(true);
    setResult(null);
    const start = performance.now();
    setTimeout(() => {
      const res = fakeResponseFor(cur.i.id, cur.e.p);
      const took = Math.round(performance.now() - start);
      setResult(res);
      setHistory(h => [{ time: new Date(), int: cur.i.id, ep: cur.e.p, status: res.status, ms: took, count: res.count || (Array.isArray(res.response) ? res.response.length : 1) }, ...h].slice(0, 8));
      setRunning(false);
    }, res_delay(cur.i.id));
  }

  function res_delay(id) {
    if (id === 'wikipedia') return 220;
    if (id === 'statsbomb') return 460;
    if (id === 'api-football') return 320;
    return 280;
  }

  function runAll() {
    setRunning(true);
    const results = [];
    let idx = 0;
    function next() {
      if (idx >= flat.length) {
        setRunning(false);
        setHistory(h => [...results.reverse(), ...h].slice(0, 16));
        setResult({ status: 200, summary: { passed: results.filter(r => r.status < 400).length, failed: results.filter(r => r.status >= 400).length, total: results.length } });
        return;
      }
      const c = flat[idx++];
      const r = fakeResponseFor(c.i.id, c.e.p);
      results.push({ time: new Date(), int: c.i.id, ep: c.e.p, status: r.status, ms: r.latencyMs, count: r.count || 1 });
      setTimeout(next, 80);
    }
    next();
  }

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1.3fr 2fr', gap: 16 }}>
      <div>
        <div className="eyebrow" style={{ marginBottom: 6 }}>Endpoint</div>
        <select className="input" value={sel} onChange={e => { setSel(+e.target.value); setResult(null); }} style={{ width: '100%', fontFamily: 'var(--font-mono)', fontSize: 12 }}>
          {flat.map((f, k) => (
            <option key={k} value={k}>{f.i.name} \u2014 {f.e.p}</option>
          ))}
        </select>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, marginTop: 12 }}>
          <button className="btn btn-primary btn-sm" onClick={runTest} disabled={running} style={{ width: '100%' }}>
            {running ? 'Running\u2026' : 'Send test request'}
          </button>
          <button className="btn btn-secondary btn-sm" onClick={runAll} disabled={running} style={{ width: '100%' }}>
            Run all ({flat.length})
          </button>
        </div>

        <div className="divider" />
        <div className="eyebrow" style={{ marginBottom: 6 }}>Recent tests</div>
        {history.length === 0 ? (
          <div className="subtle" style={{ fontSize: 12 }}>No tests run yet. Click \u201cSend test request\u201d above.</div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
            {history.map((h, i) => (
              <div key={i} style={{
                display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 10,
                padding: '6px 8px',
                background: 'var(--surface)', border: '1px solid var(--border)',
                borderRadius: 6, fontSize: 11, fontFamily: 'var(--font-mono)',
              }}>
                <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{h.int} \u00b7 {h.ep}</span>
                <span style={{ color: h.status >= 400 ? 'var(--destructive)' : h.status >= 300 ? 'var(--warning)' : 'var(--success)' }}>{h.status}</span>
                <span className="subtle">{h.ms}ms</span>
              </div>
            ))}
          </div>
        )}
      </div>

      <div>
        <div className="eyebrow" style={{ marginBottom: 6 }}>Response</div>
        <pre style={{
          margin: 0,
          padding: 14,
          background: 'var(--muted)',
          border: '1px solid var(--border)',
          borderRadius: 'var(--radius-md)',
          fontFamily: 'var(--font-mono)',
          fontSize: 11.5,
          lineHeight: 1.55,
          color: 'var(--foreground)',
          maxHeight: 360,
          overflowY: 'auto',
          whiteSpace: 'pre-wrap',
          wordBreak: 'break-word',
        }}>{running ? '\u2026 sending request to ' + (cur?.i.name) + ' \u2026' : (result ? formatJSON(result) : 'Click \u201cSend test request\u201d to call ' + (cur?.i.name || '') + '.\nResponses here mirror real shape but use deterministic fake data.')}</pre>

        {result && (
          <div style={{ display: 'flex', gap: 14, marginTop: 10, fontSize: 12 }}>
            <span><span className="eyebrow">Status</span> <span className="mono strong" style={{ color: result.status >= 400 ? 'var(--destructive)' : result.status >= 300 ? 'var(--warning)' : 'var(--success)', marginLeft: 8 }}>{result.status}{result.status >= 400 ? ' \u2715' : ' \u2713'}</span></span>
            {result.latencyMs && <span><span className="eyebrow">Latency</span> <span className="mono" style={{ marginLeft: 8 }}>{result.latencyMs}ms</span></span>}
            {result.count != null && <span><span className="eyebrow">Items</span> <span className="mono" style={{ marginLeft: 8 }}>{result.count}</span></span>}
            {result.summary && <span><span className="eyebrow">Summary</span> <span className="mono" style={{ marginLeft: 8, color: result.summary.failed === 0 ? 'var(--success)' : 'var(--warning)' }}>{result.summary.passed}/{result.summary.total} passed</span></span>}
          </div>
        )}
      </div>
    </div>
  );
}
function formatJSON(o) { try { return JSON.stringify(o, null, 2); } catch (e) { return String(o); } }
Object.assign(window, { IntegrationTester, fakeResponseFor });

// ---------- Page & feature tests ----------
// Each page lists feature-level checks. Checks are real, lightweight assertions
// against the running app (globals, data shape, view registration) so a failed
// build or missing dataset surfaces here rather than as a blank screen.
function featureTestCatalog() {
  const ok = (cond, pass, fail) => () => (cond() ? { ok: true, detail: pass } : { ok: false, detail: fail });
  const fn = (name) => () => typeof window[name] === 'function';
  return [
    { page: 'Home · Overview', route: 'home', features: [
      { name: 'View renders', check: ok(fn('HubView'), 'HubView registered', 'HubView missing') },
      { name: 'Tournament clock', check: ok(() => !!window.WCL_CLOCK && typeof window.WCL_CLOCK.compute === 'function', 'clock available', 'WCL_CLOCK missing') },
      { name: 'Section cards route', check: ok(() => !!window.WCL_2026, '2026 meta present', 'WCL_2026 missing') },
    ]},
    { page: 'Live Today', route: 'live', features: [
      { name: 'View renders', check: ok(fn('LiveView'), 'LiveView registered', 'LiveView missing') },
      { name: 'Featured match data', check: ok(() => window.WCL_FEATURED && window.WCL_FEATURED.live, 'live slice present', 'no live data') },
      { name: 'Pre/Post states', check: ok(() => fn('LiveClosedState')() && fn('LivePostState')(), 'phase states present', 'phase states missing') },
      { name: 'Today\u2019s fixtures', check: ok(() => Array.isArray(window.WCL_TODAY) && window.WCL_TODAY.length >= 4, window.WCL_TODAY?.length + ' fixtures', 'no fixtures') },
    ]},
    { page: 'Fixtures', route: 'matches', features: [
      { name: 'View renders', check: ok(fn('MatchesView'), 'MatchesView registered', 'MatchesView missing') },
    ]},
    { page: 'Group Table', route: 'table', features: [
      { name: 'View renders', check: ok(fn('TableView'), 'TableView registered', 'TableView missing') },
      { name: '12 groups present', check: ok(() => window.WCL_2026?.groups?.length === 12, '12 groups', (window.WCL_2026?.groups?.length || 0) + ' groups') },
    ]},
    { page: 'Knockout / Bracket', route: 'bracket', features: [
      { name: 'View renders', check: ok(fn('BracketView'), 'BracketView registered', 'BracketView missing') },
      { name: 'Bracket data', check: ok(() => window.WCL_BRACKET?.r16?.length === 8, 'R16 = 8 ties', 'bracket malformed') },
    ]},
    { page: 'Odds & Model', route: 'odds', features: [
      { name: 'View renders', check: ok(fn('OddsView'), 'OddsView registered', 'OddsView missing') },
      { name: 'Trophy odds', check: ok(() => window.WCL_TROPHY_ODDS?.length >= 40, window.WCL_TROPHY_ODDS?.length + ' teams priced', 'odds missing') },
    ]},
    { page: 'Players', route: 'players', features: [
      { name: 'View renders', check: ok(fn('PlayersView'), 'PlayersView registered', 'PlayersView missing') },
    ]},
    { page: 'Teams', route: 'teams', features: [
      { name: 'View renders', check: ok(fn('TeamsView'), 'TeamsView registered', 'TeamsView missing') },
      { name: '48-team field', check: ok(() => window.WCL_FIELD_2026?.length === 48, '48 teams', (window.WCL_FIELD_2026?.length || 0) + ' teams') },
    ]},
    { page: 'Pick\u2019em', route: 'leaderboard', features: [
      { name: 'View renders', check: ok(fn('LeaderboardView'), 'LeaderboardView registered', 'LeaderboardView missing') },
      { name: 'Daily challenge', check: ok(() => !!window.WCL_DAILY, 'challenge present', 'no challenge') },
    ]},
    { page: 'Agent · WorldCupAI', route: 'agent', features: [
      { name: 'View renders', check: ok(fn('AgentView'), 'AgentView registered', 'AgentView missing') },
      { name: 'Claude bridge', check: ok(() => !!(window.claude && window.claude.complete), 'window.claude.complete ready', 'Claude bridge unavailable') },
    ]},
    { page: 'Statistics', route: 'stats', features: [
      { name: 'View renders', check: ok(fn('StatsView'), 'StatsView registered', 'StatsView missing') },
      { name: 'All-time dataset', check: ok(() => window.WCL_ALLTIME?.championsByNation?.length >= 8, 'champions loaded', 'all-time missing') },
    ]},
    { page: 'Guide', route: 'guide', features: [
      { name: 'View renders', check: ok(fn('GuideView'), 'GuideView registered', 'GuideView missing') },
      { name: 'Editions archive', check: ok(() => window.WCL_HISTORY?.length === 22, '22 editions', (window.WCL_HISTORY?.length || 0) + ' editions') },
    ]},
    { page: 'History & editions', route: 'history', features: [
      { name: 'View renders', check: ok(fn('HistoryView'), 'HistoryView registered', 'HistoryView missing') },
      { name: 'Tournament page', check: ok(fn('TournamentView'), 'TournamentView registered', 'TournamentView missing') },
      { name: 'Per-edition deep data', check: ok(() => Object.keys(window.WCL_EDITIONS || {}).length >= 22, 'deep data present', 'editions thin') },
    ]},
    { page: 'Sitemap', route: 'sitemap', features: [
      { name: 'View renders', check: ok(fn('SitemapView'), 'SitemapView registered', 'SitemapView missing') },
    ]},
    { page: 'Routing & shell', route: null, features: [
      { name: 'URL hash routing', check: ok(() => typeof window.WCL_parseHash === 'function', 'hash router live', 'router missing') },
      { name: 'Shell + nav', check: ok(fn('Shell'), 'Shell registered', 'Shell missing') },
      { name: 'Build stamp', check: ok(() => !!window.WCL_BUILD?.version, window.WCL_BUILD?.version, 'no build stamp') },
    ]},
  ];
}

function FeatureTests({ onRoute }) {
  const catalog = React.useMemo(featureTestCatalog, []);
  const [results, setResults] = React.useState({});   // key `${pi}-${fi}` -> {ok, detail}
  const [running, setRunning] = React.useState(false);

  const total = catalog.reduce((s, p) => s + p.features.length, 0);
  const done = Object.keys(results).length;
  const passed = Object.values(results).filter(r => r.ok).length;
  const failed = done - passed;

  function runOne(pi, fi) {
    const r = catalog[pi].features[fi].check();
    setResults(s => ({ ...s, [`${pi}-${fi}`]: r }));
    return r;
  }
  function runPage(pi) { catalog[pi].features.forEach((_, fi) => runOne(pi, fi)); }
  function runAll() {
    setRunning(true);
    setResults({});
    let pi = 0;
    const step = () => {
      if (pi >= catalog.length) { setRunning(false); return; }
      runPage(pi); pi++;
      setTimeout(step, 70);
    };
    step();
  }

  return (
    <div>
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', marginBottom: 14, flexWrap: 'wrap' }}>
        <button className="btn btn-primary btn-sm" onClick={runAll} disabled={running}>{running ? 'Running\u2026' : 'Run all tests (' + total + ')'}</button>
        {done > 0 && (
          <div style={{ display: 'flex', gap: 12, fontSize: 12, alignItems: 'center' }}>
            <span><span className="eyebrow">Passed</span> <span className="mono strong" style={{ color: 'var(--success)', marginLeft: 6 }}>{passed}</span></span>
            <span><span className="eyebrow">Failed</span> <span className="mono strong" style={{ color: failed ? 'var(--destructive)' : 'var(--foreground-muted)', marginLeft: 6 }}>{failed}</span></span>
            <span><span className="eyebrow">Coverage</span> <span className="mono" style={{ marginLeft: 6 }}>{done}/{total}</span></span>
          </div>
        )}
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(340px, 1fr))', gap: 12 }}>
        {catalog.map((p, pi) => {
          const pf = p.features.map((_, fi) => results[`${pi}-${fi}`]).filter(Boolean);
          const allPass = pf.length === p.features.length && pf.every(r => r.ok);
          const anyFail = pf.some(r => r && !r.ok);
          return (
            <article key={p.page} style={{ padding: 14, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--radius-md)' }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <span className="strong" style={{ fontSize: 13, fontWeight: 600 }}>{p.page}</span>
                  {pf.length > 0 && (
                    <span className={'badge ' + (anyFail ? 'fail' : 'live')}>
                      <span className="badge-dot" style={{ background: anyFail ? 'var(--destructive)' : 'var(--success)' }} />
                      {anyFail ? 'fail' : 'pass'}
                    </span>
                  )}
                </div>
                <div style={{ display: 'flex', gap: 4 }}>
                  {p.route && <button className="btn btn-ghost btn-sm" onClick={() => onRoute(p.route)} data-tt="Open page"><I.ArrowUR size={12} /></button>}
                  <button className="btn btn-ghost btn-sm" onClick={() => runPage(pi)}>Test</button>
                </div>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                {p.features.map((f, fi) => {
                  const r = results[`${pi}-${fi}`];
                  return (
                    <div key={f.name} style={{ display: 'grid', gridTemplateColumns: '16px 1fr auto', gap: 8, alignItems: 'center', fontSize: 12 }}>
                      <span>{r ? (r.ok ? <I.Check size={13} style={{ color: 'var(--success)' }} /> : <I.X size={13} style={{ color: 'var(--destructive)' }} />) : <I.Dot size={10} style={{ color: 'var(--foreground-muted)' }} />}</span>
                      <span className="strong" style={{ fontWeight: 500 }}>{f.name}</span>
                      <span className="mono subtle" style={{ fontSize: 10.5, color: r ? (r.ok ? 'var(--success)' : 'var(--destructive)') : 'var(--foreground-muted)', textAlign: 'right' }}>{r ? r.detail : 'idle'}</span>
                    </div>
                  );
                })}
              </div>
            </article>
          );
        })}
      </div>
    </div>
  );
}
Object.assign(window, { FeatureTests, featureTestCatalog });

// ---------- Admin tab bar ----------
function AdminTabs({ tab, setTab, onLogout }) {
  const tabs = [['dashboard', 'Dashboard', I.Grid], ['integrations', 'Integrations', I.Layers], ['data', 'Data', I.List]];
  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20, borderBottom: '1px solid var(--border)' }}>
      <div className="tabs-row" style={{ border: 'none', margin: 0 }}>
        {tabs.map(([id, label, Ic]) => (
          <button key={id} className={'tab-btn' + (tab === id ? ' active' : '')} onClick={() => setTab(id)} style={{ display: 'inline-flex', alignItems: 'center', gap: 7 }}>
            <Ic size={15} /> {label}
          </button>
        ))}
      </div>
      <button className="btn btn-ghost btn-sm" onClick={onLogout}>Sign out</button>
    </div>
  );
}

// ---------- Admin · Dashboard ----------
function AdminDashboard({ integrations, stats, onRoute, go }) {
  const links = [
    { label: 'Integrations', desc: 'Keys, quotas, health & live tests', Icon: I.Layers, onClick: () => go('integrations') },
    { label: 'Data', desc: 'CRUD the Supabase-backed datasets', Icon: I.List, onClick: () => go('data') },
    { label: 'Live app', desc: 'Open the public Home page', Icon: I.Home, onClick: () => onRoute('home') },
  ];
  const preview = [
    ['Home · Overview', 'home'], ['Live Today', 'live'], ['Players', 'players'], ['Statistics', 'stats'],
  ];
  return (
    <div>
      <div className="page-head">
        <div>
          <div className="breadcrumb" style={{ display: 'flex', gap: 6, color: 'var(--foreground-muted)', fontSize: 12, marginBottom: 8 }}>
            <span className="subtle">Admin</span><I.ChevR size={12} /><span>Dashboard</span>
          </div>
          <h1 className="type-h1" style={{ margin: 0 }}>Admin dashboard</h1>
          <div className="sub">Operational overview of World Cup Live · settings, health and data at a glance.</div>
        </div>
      </div>

      <div className="col4" style={{ marginBottom: 16 }}>
        <div className="kpi"><div className="kpi-label">Integrations</div><div className="kpi-value">{integrations.length}</div><div className="kpi-delta">{stats.live} live · {stats.warn} preview</div></div>
        <div className="kpi"><div className="kpi-label">API calls · today</div><div className="kpi-value">{stats.totalCalls.toLocaleString()}</div><div className="kpi-delta">all sources</div></div>
        <div className="kpi"><div className="kpi-label">Datasets</div><div className="kpi-value">{ADMIN_DATASETS.length}</div><div className="kpi-delta">Supabase-backed</div></div>
        <div className="kpi"><div className="kpi-label">Build</div><div className="kpi-value" style={{ fontSize: 18 }}>{window.WCL_BUILD.version}</div><div className="kpi-delta mono">#{window.WCL_BUILD.build}</div></div>
      </div>

      <div className="col2">
        <section className="panel">
          <div className="panel-head"><h3>Jump to</h3><span className="subtle" style={{ fontSize: 11 }}>admin sections</span></div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {links.map(l => (
              <button key={l.label} className="hub-card" onClick={l.onClick} style={{ padding: 14 }}>
                <span className="hub-ic" style={{ color: 'var(--brand)', background: 'color-mix(in oklch, var(--brand) 14%, transparent)', borderColor: 'color-mix(in oklch, var(--brand) 30%, transparent)' }}><l.Icon size={18} /></span>
                <span style={{ flex: 1 }}><span className="hub-title">{l.label}</span><span className="hub-desc">{l.desc}</span></span>
                <I.ChevR size={15} className="subtle" />
              </button>
            ))}
          </div>
        </section>

        <section className="panel">
          <div className="panel-head"><h3>Settings</h3><span className="subtle" style={{ fontSize: 11 }}>prototype</span></div>
          {[
            ['Live polling', 'API-Football · live windows only', true],
            ['Daily challenge emails', 'SendGrid · 100/day free tier', true],
            ['Premium (Stripe)', 'Disabled until post-tournament', false],
            ['Public registrations', 'Open · email + SSO', true],
            ['Maintenance mode', 'Off', false],
          ].map(([k, sub, on]) => (
            <div key={k} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 0', borderBottom: '1px solid var(--border)' }}>
              <div><div className="strong" style={{ fontSize: 13, fontWeight: 600 }}>{k}</div><div className="subtle" style={{ fontSize: 11, marginTop: 2 }}>{sub}</div></div>
              <span style={{ width: 34, height: 20, borderRadius: 999, background: on ? 'var(--pitch)' : 'var(--muted)', border: '1px solid var(--border)', position: 'relative', flexShrink: 0 }}>
                <span style={{ position: 'absolute', top: 2, left: on ? 16 : 2, width: 14, height: 14, borderRadius: 999, background: '#fff', transition: 'left var(--dur-fast) var(--ease-out)' }} />
              </span>
            </div>
          ))}
        </section>
      </div>

      <section className="panel" style={{ marginTop: 16 }}>
        <div className="panel-head"><h3>Live preview</h3><span className="subtle" style={{ fontSize: 11 }}>public pages</span></div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: 10 }}>
          {preview.map(([label, route]) => (
            <button key={route} onClick={() => onRoute(route)} style={{ textAlign: 'left', cursor: 'pointer', fontFamily: 'inherit', color: 'inherit', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', overflow: 'hidden' }}>
              <div style={{ aspectRatio: '16/10', background: 'linear-gradient(135deg, color-mix(in oklch, var(--brand) 16%, var(--muted)), var(--muted))', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative' }}>
                <SoccerBall size={26} />
                <span style={{ position: 'absolute', inset: 0, background: 'repeating-linear-gradient(90deg, transparent 0 18px, rgba(255,255,255,0.03) 18px 19px)' }} />
              </div>
              <div style={{ padding: '8px 10px' }}><span className="strong" style={{ fontSize: 12, fontWeight: 600 }}>{label}</span><span className="mono subtle" style={{ display: 'block', fontSize: 10, marginTop: 2 }}>#/{route}</span></div>
            </button>
          ))}
        </div>
      </section>

      <section className="panel" style={{ marginTop: 16 }}>
        <div className="panel-head"><h3>Page &amp; feature tests</h3><span className="subtle" style={{ fontSize: 11 }}>per page · per feature · live checks</span></div>
        <FeatureTests onRoute={onRoute} />
      </section>
    </div>
  );
}

// ---------- Collapsible integration row (overview + test + status) ----------
function IntegrationRow({ i }) {
  const [open, setOpen] = React.useState(false);
  const [testing, setTesting] = React.useState(false);
  const [res, setRes] = React.useState(null);
  const pct = i.requestsCap ? (i.requestsUsed / i.requestsCap) * 100 : null;

  function test() {
    setTesting(true); setRes(null);
    setTimeout(() => {
      const ep = i.endpoints?.[0]?.p || '/';
      const r = fakeResponseFor(i.id, ep);
      setRes(r); setTesting(false);
    }, 400 + Math.random() * 500);
  }

  const statusColor = i.status === 'live' ? 'var(--success)' : i.status === 'preview' ? 'var(--info)' : i.status === 'warn' ? 'var(--warning)' : 'var(--foreground-muted)';

  return (
    <article style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', overflow: 'hidden' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px', cursor: 'pointer' }} onClick={() => setOpen(o => !o)}>
        <div style={{ width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: 'var(--muted)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700, fontSize: 15 }}>{i.logo}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="strong" style={{ fontSize: 14, fontWeight: 600 }}>{i.name}</div>
          <div className="subtle" style={{ fontSize: 11, marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{i.vendor} · {i.role}</div>
        </div>
        {res && <span className="mono" style={{ fontSize: 11, color: res.status >= 400 ? 'var(--destructive)' : 'var(--success)' }}>{res.status}{res.status >= 400 ? ' ✕' : ' ✓'} · {res.latencyMs}ms</span>}
        <span className={'badge ' + (i.status === 'live' ? 'live' : i.status === 'preview' ? 'preview' : i.status === 'warn' ? 'warn' : 'neutral')}>
          <span className="badge-dot" style={{ background: statusColor }} /> {i.status}
        </span>
        <button className="btn btn-secondary btn-sm" onClick={e => { e.stopPropagation(); test(); }} disabled={testing}>{testing ? '…' : 'Test'}</button>
        <I.ChevDown size={15} style={{ color: 'var(--foreground-muted)', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }} />
      </div>

      {open && (
        <div style={{ padding: '0 14px 14px', borderTop: '1px solid var(--border)' }}>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8, fontSize: 11, margin: '12px 0' }}>
            <div><span className="eyebrow">Plan</span><div className="mono strong" style={{ marginTop: 2 }}>{i.plan}</div></div>
            <div><span className="eyebrow">Limit</span><div className="mono" style={{ marginTop: 2 }}>{i.planLimit}</div></div>
            <div><span className="eyebrow">Key</span><div className="mono" style={{ marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis' }}>{i.keyMasked}</div></div>
            <div><span className="eyebrow">Last sync</span><div className="mono" style={{ marginTop: 2 }}>{i.lastSync}</div></div>
          </div>
          {pct != null && (
            <div style={{ marginBottom: 12 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, marginBottom: 4 }}><span className="subtle">Usage</span><span className="mono" style={{ color: pct > 80 ? 'var(--warning)' : 'var(--foreground)' }}>{i.requestsUsed} / {i.requestsCap}</span></div>
              <div style={{ height: 5, background: 'var(--muted)', borderRadius: 999, overflow: 'hidden' }}><div style={{ height: '100%', width: pct + '%', background: pct > 90 ? 'var(--destructive)' : pct > 70 ? 'var(--warning)' : 'var(--pitch)' }} /></div>
            </div>
          )}
          {i.endpoints?.length > 0 && (
            <table className="table" style={{ marginBottom: 10 }}>
              <thead><tr><th>Endpoint</th><th>Used for</th><th style={{ width: 60, textAlign: 'right' }}>Today</th></tr></thead>
              <tbody>{i.endpoints.map(e => (<tr key={e.p}><td className="mono" style={{ fontSize: 11 }}>{e.p}</td><td className="strong">{e.n}</td><td className="mono" style={{ textAlign: 'right' }}>{e.hits}</td></tr>))}</tbody>
            </table>
          )}
          <div className="subtle" style={{ fontSize: 12, lineHeight: 1.5, textWrap: 'pretty' }}>{i.note}</div>
          {res && (
            <pre style={{ marginTop: 10, padding: 12, background: 'var(--muted)', border: '1px solid var(--border)', borderRadius: 'var(--radius-md)', fontFamily: 'var(--font-mono)', fontSize: 11, lineHeight: 1.5, maxHeight: 200, overflow: 'auto', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{formatJSON(res)}</pre>
          )}
        </div>
      )}
    </article>
  );
}

// ---------- Admin · Data (CRUD over Supabase-backed datasets) ----------
const ADMIN_DATASETS = [
  { id: 'matches', label: 'matches', table: 'public.matches', cols: [['group','Group'],['home','Home'],['away','Away'],['state','State'],['score','Score']],
    rows: () => (window.WCL_TODAY || []).map(m => ({ group: m.group, home: m.home, away: m.away, state: m.state, score: m.score ? m.score.join('-') : (m.ko || '—') })) },
  { id: 'table', label: 'table', table: 'public.standings', cols: [['group','Group'],['teams','Teams'],['host','Host group']],
    rows: () => (window.WCL_2026?.groups || []).map(g => ({ group: g.id, teams: g.teams.join(', '), host: g.host || '—' })) },
  { id: 'knockout', label: 'knockout', table: 'public.knockout', cols: [['id','Tie'],['home','Home'],['away','Away'],['prob','Model %']],
    rows: () => (window.WCL_BRACKET?.r16 || []).map(t => ({ id: t.id, home: t.home, away: t.away, prob: Math.round(t.prob * 100) + '%' })) },
  { id: 'players', label: 'players', table: 'public.players', cols: [['name','Name'],['team','Nat'],['pos','Pos'],['caps','Caps'],['goals','Goals']],
    rows: () => (window.WCL_PLAYERS || []).map(p => ({ name: p.name, team: p.team, pos: p.pos, caps: p.caps, goals: p.goals })) },
  { id: 'teams', label: 'teams', table: 'public.teams', cols: [['code','Code'],['name','Name'],['confed','Confederation'],['fifa','FIFA']],
    rows: () => (window.WCL_FIELD_2026 || []).map(c => ({ code: c, name: window.WCL_TEAMS[c]?.name || c, confed: window.WCL_TEAMS[c]?.confed || '—', fifa: window.WCL_TEAMS[c]?.fifa || '—' })) },
  { id: 'tournaments', label: 'tournaments', table: 'public.tournaments', cols: [['y','Year'],['host','Host'],['winner','Winner'],['runner','Runner-up'],['goals','Goals']],
    rows: () => (window.WCL_HISTORY || []).map(h => ({ y: h.y, host: h.host, winner: h.winner, runner: h.runner, goals: h.goals })) },
  { id: 'records', label: 'records', table: 'public.team_records', cols: [['team','Team'],['titles','Titles'],['finals','Finals'],['top4','Top 4']],
    rows: () => (window.WCL_ALLTIME?.championsByNation || []).map(c => ({ team: c.team, titles: c.titles, finals: c.titles + c.runnerUp.length, top4: c.top4 })) },
  { id: 'medals', label: 'medals', table: 'public.medals', cols: [['team','Team'],['gold','Gold'],['silver','Silver'],['bronze','Bronze']],
    rows: () => (window.WCL_ALLTIME?.championsByNation || []).map(c => ({ team: c.team, gold: c.titles, silver: c.runnerUp.length, bronze: c.thirdPlace.length })).sort((a,b)=>b.gold-a.gold) },
];

function AdminData({ onRoute }) {
  const [sel, setSel] = React.useState('matches');
  const ds = ADMIN_DATASETS.find(d => d.id === sel);
  const [rows, setRows] = React.useState(() => ds.rows());
  const [dirty, setDirty] = React.useState(false);
  const [savedAt, setSavedAt] = React.useState(null);

  React.useEffect(() => { setRows(ds.rows()); setDirty(false); setSavedAt(null); }, [sel]);

  function edit(ri, key, v) { setRows(rs => rs.map((r, i) => i === ri ? { ...r, [key]: v } : r)); setDirty(true); }
  function del(ri) { setRows(rs => rs.filter((_, i) => i !== ri)); setDirty(true); }
  function add() { const blank = {}; ds.cols.forEach(([k]) => blank[k] = ''); setRows(rs => [blank, ...rs]); setDirty(true); }
  function save() { setDirty(false); setSavedAt(new Date().toLocaleTimeString()); }

  return (
    <div>
      <div className="page-head">
        <div>
          <div className="breadcrumb" style={{ display: 'flex', gap: 6, color: 'var(--foreground-muted)', fontSize: 12, marginBottom: 8 }}>
            <span className="subtle">Admin</span><I.ChevR size={12} /><span>Data</span>
          </div>
          <h1 className="type-h1" style={{ margin: 0 }}>Data</h1>
          <div className="sub">Datasets ingested from the integrations and persisted to Supabase · full CRUD.</div>
        </div>
        <span className="badge live" style={{ alignSelf: 'center' }}><span className="badge-dot" style={{ background: 'var(--success)' }} /> Supabase connected</span>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '200px 1fr', gap: 16, alignItems: 'start' }}>
        <section className="panel" style={{ padding: 8 }}>
          <div className="eyebrow" style={{ padding: '6px 8px' }}>Tables</div>
          {ADMIN_DATASETS.map(d => {
            const active = d.id === sel;
            return (
              <button key={d.id} onClick={() => setSel(d.id)} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%', padding: '8px 10px', marginBottom: 2, background: active ? 'var(--card-hover)' : 'transparent', border: 'none', borderRadius: 'var(--radius-sm)', cursor: 'pointer', color: 'inherit', fontFamily: 'inherit', textAlign: 'left' }}>
                <span className="mono" style={{ fontSize: 12, fontWeight: active ? 700 : 500, color: active ? 'var(--foreground)' : 'var(--foreground-muted)' }}>{d.label}</span>
                <span className="mono subtle" style={{ fontSize: 10 }}>{d.rows().length}</span>
              </button>
            );
          })}
        </section>

        <section className="panel">
          <div className="panel-head">
            <div>
              <h3 style={{ display: 'flex', alignItems: 'center', gap: 8 }}>{ds.label}<span className="mono subtle" style={{ fontSize: 11, fontWeight: 400 }}>{ds.table}</span></h3>
            </div>
            <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
              {savedAt && !dirty && <span className="subtle" style={{ fontSize: 11 }}>saved {savedAt}</span>}
              <button className="btn btn-ghost btn-sm" onClick={add}><I.Plus size={13} /> Add row</button>
              <button className="btn btn-primary btn-sm" onClick={save} disabled={!dirty}>{dirty ? 'Save to Supabase' : 'Saved'}</button>
            </div>
          </div>
          <div style={{ overflowX: 'auto' }}>
            <table className="table">
              <thead>
                <tr>
                  <th style={{ width: 28 }}>#</th>
                  {ds.cols.map(([k, l]) => <th key={k}>{l}</th>)}
                  <th style={{ width: 40 }}></th>
                </tr>
              </thead>
              <tbody>
                {rows.map((r, ri) => (
                  <tr key={ri}>
                    <td className="mono subtle">{ri + 1}</td>
                    {ds.cols.map(([k]) => (
                      <td key={k}>
                        <input value={r[k] ?? ''} onChange={e => edit(ri, k, e.target.value)}
                          style={{ width: '100%', minWidth: 60, background: 'transparent', border: '1px solid transparent', borderRadius: 4, padding: '3px 6px', color: 'var(--foreground)', fontFamily: 'var(--font-sans)', fontSize: 13 }}
                          onFocus={e => { e.target.style.borderColor = 'var(--border)'; e.target.style.background = 'var(--card)'; }}
                          onBlur={e => { e.target.style.borderColor = 'transparent'; e.target.style.background = 'transparent'; }} />
                      </td>
                    ))}
                    <td><button className="btn btn-ghost btn-sm btn-icon" onClick={() => del(ri)} data-tt="Delete row" style={{ color: 'var(--destructive)' }}><I.Trash size={13} /></button></td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          <div className="subtle" style={{ fontSize: 11, marginTop: 10 }}>{rows.length} rows · changes are in-memory in this prototype; “Save to Supabase” simulates an upsert.</div>
        </section>
      </div>
    </div>
  );
}
Object.assign(window, { AdminTabs, AdminDashboard, AdminData, IntegrationRow, ADMIN_DATASETS });
