// api.jsx — REST API developer console
// ─────────────────────────────────────────────────────────────────────
// Pluralis ASTRO exposes a REST API for catalog operations, statement
// ingestion, registration generation, and webhooks. This screen is the
// developer-facing console:
//
//   01 Overview        — base URL, auth, conventions, quotas
//   02 Endpoints       — full reference grouped by resource
//   03 Try it          — interactive request inspector
//   04 Keys            — token mgmt: create/rotate/revoke
//   05 Webhooks        — event subscriptions + delivery log
//   06 SDKs            — JS/Python/cURL snippets per endpoint
//   07 Changelog       — versioned API changes
// ─────────────────────────────────────────────────────────────────────

(function () {
  'use strict';
  if (typeof window === 'undefined' || !window.React) return;
  const { useState, useMemo } = React;

  const BASE = 'https://api.pluralis.io/v3';

  // ─── ENDPOINTS — REST surface ────────────────────────────────────
  const RESOURCES = [
    { id:'works', label:'Works', count:18 },
    { id:'recordings', label:'Recordings', count:14 },
    { id:'releases', label:'Releases', count:11 },
    { id:'parties', label:'Parties', count:9 },
    { id:'agreements', label:'Agreements', count:12 },
    { id:'statements', label:'Statements', count:8 },
    { id:'royalties', label:'Royalties', count:7 },
    { id:'registrations', label:'Registrations', count:6 },
    { id:'webhooks', label:'Webhooks', count:5 },
    { id:'search', label:'Search', count:3 },
  ];

  const ENDPOINTS = [
    // Works
    { id:'works.list',     m:'GET',    p:'/works',                              r:'works', sum:'List works', scope:'catalog:read', rate:'600/min' },
    { id:'works.get',      m:'GET',    p:'/works/{id}',                         r:'works', sum:'Get a work by ID', scope:'catalog:read', rate:'600/min' },
    { id:'works.create',   m:'POST',   p:'/works',                              r:'works', sum:'Create a work', scope:'catalog:write', rate:'120/min' },
    { id:'works.update',   m:'PATCH',  p:'/works/{id}',                         r:'works', sum:'Update a work', scope:'catalog:write', rate:'120/min' },
    { id:'works.delete',   m:'DELETE', p:'/works/{id}',                         r:'works', sum:'Delete a work', scope:'catalog:write', rate:'60/min' },
    { id:'works.shares',   m:'GET',    p:'/works/{id}/shares',                  r:'works', sum:'Get writer/publisher shares', scope:'catalog:read', rate:'600/min' },
    { id:'works.byiswc',   m:'GET',    p:'/works/by-iswc/{iswc}',               r:'works', sum:'Lookup by ISWC', scope:'catalog:read', rate:'600/min' },
    { id:'works.bulk',     m:'POST',   p:'/works/bulk',                         r:'works', sum:'Bulk create (≤500)', scope:'catalog:write', rate:'30/min' },

    // Recordings
    { id:'rec.list',       m:'GET',    p:'/recordings',                         r:'recordings', sum:'List recordings', scope:'catalog:read', rate:'600/min' },
    { id:'rec.get',        m:'GET',    p:'/recordings/{id}',                    r:'recordings', sum:'Get recording', scope:'catalog:read', rate:'600/min' },
    { id:'rec.create',     m:'POST',   p:'/recordings',                         r:'recordings', sum:'Create recording', scope:'catalog:write', rate:'120/min' },
    { id:'rec.update',     m:'PATCH',  p:'/recordings/{id}',                    r:'recordings', sum:'Update recording', scope:'catalog:write', rate:'120/min' },
    { id:'rec.byisrc',     m:'GET',    p:'/recordings/by-isrc/{isrc}',          r:'recordings', sum:'Lookup by ISRC', scope:'catalog:read', rate:'600/min' },
    { id:'rec.audio',      m:'GET',    p:'/recordings/{id}/audio-features',     r:'recordings', sum:'BPM/key/energy + audio features', scope:'catalog:read', rate:'300/min' },

    // Releases
    { id:'rel.list',       m:'GET',    p:'/releases',                           r:'releases', sum:'List releases', scope:'catalog:read', rate:'600/min' },
    { id:'rel.get',        m:'GET',    p:'/releases/{id}',                      r:'releases', sum:'Get release', scope:'catalog:read', rate:'600/min' },
    { id:'rel.byupc',      m:'GET',    p:'/releases/by-upc/{upc}',              r:'releases', sum:'Lookup by UPC/EAN', scope:'catalog:read', rate:'600/min' },
    { id:'rel.tracks',     m:'GET',    p:'/releases/{id}/tracks',               r:'releases', sum:'Get tracklist', scope:'catalog:read', rate:'600/min' },
    { id:'rel.platforms',  m:'GET',    p:'/releases/{id}/platforms',            r:'releases', sum:'DSP availability map', scope:'catalog:read', rate:'600/min' },

    // Parties
    { id:'party.list',     m:'GET',    p:'/parties',                            r:'parties', sum:'List parties (writers, publishers, labels)', scope:'catalog:read', rate:'600/min' },
    { id:'party.get',      m:'GET',    p:'/parties/{id}',                       r:'parties', sum:'Get party', scope:'catalog:read', rate:'600/min' },
    { id:'party.byipi',    m:'GET',    p:'/parties/by-ipi/{ipi}',               r:'parties', sum:'Lookup by IPI', scope:'catalog:read', rate:'600/min' },

    // Agreements
    { id:'agr.list',       m:'GET',    p:'/agreements',                         r:'agreements', sum:'List agreements', scope:'agreements:read', rate:'600/min' },
    { id:'agr.get',        m:'GET',    p:'/agreements/{id}',                    r:'agreements', sum:'Get agreement', scope:'agreements:read', rate:'600/min' },
    { id:'agr.create',     m:'POST',   p:'/agreements',                         r:'agreements', sum:'Create agreement', scope:'agreements:write', rate:'60/min' },
    { id:'agr.versions',   m:'GET',    p:'/agreements/{id}/versions',           r:'agreements', sum:'List versions / redlines', scope:'agreements:read', rate:'600/min' },
    { id:'agr.send',       m:'POST',   p:'/agreements/{id}/send',               r:'agreements', sum:'Send for e-signature', scope:'agreements:write', rate:'30/min' },
    { id:'agr.waterfall',  m:'GET',    p:'/agreements/{id}/waterfall',          r:'agreements', sum:'Compute waterfall projection', scope:'agreements:read', rate:'120/min' },

    // Statements
    { id:'stmt.list',      m:'GET',    p:'/statements',                         r:'statements', sum:'List statements', scope:'royalties:read', rate:'600/min' },
    { id:'stmt.upload',    m:'POST',   p:'/statements/upload',                  r:'statements', sum:'Upload + parse statement file', scope:'royalties:write', rate:'30/min' },
    { id:'stmt.get',       m:'GET',    p:'/statements/{id}',                    r:'statements', sum:'Get statement', scope:'royalties:read', rate:'600/min' },
    { id:'stmt.lines',     m:'GET',    p:'/statements/{id}/lines',              r:'statements', sum:'Get line items', scope:'royalties:read', rate:'300/min' },
    { id:'stmt.match',     m:'POST',   p:'/statements/{id}/match',              r:'statements', sum:'Run reconciliation matcher', scope:'royalties:write', rate:'60/min' },

    // Royalties
    { id:'roy.byparty',    m:'GET',    p:'/royalties/by-party/{partyId}',       r:'royalties', sum:'Earnings by party + period', scope:'royalties:read', rate:'600/min' },
    { id:'roy.bywork',     m:'GET',    p:'/royalties/by-work/{workId}',         r:'royalties', sum:'Earnings by work + period', scope:'royalties:read', rate:'600/min' },
    { id:'roy.byterritory',m:'GET',    p:'/royalties/by-territory/{tis}',       r:'royalties', sum:'Earnings by TIS territory', scope:'royalties:read', rate:'600/min' },
    { id:'roy.payouts',    m:'GET',    p:'/royalties/payouts',                  r:'royalties', sum:'List payouts', scope:'royalties:read', rate:'600/min' },

    // Registrations
    { id:'reg.cwr.gen',    m:'POST',   p:'/registrations/cwr/generate',         r:'registrations', sum:'Generate CWR file', scope:'registrations:write', rate:'30/min' },
    { id:'reg.cwr.send',   m:'POST',   p:'/registrations/cwr/{id}/send',        r:'registrations', sum:'Transmit CWR to society', scope:'registrations:write', rate:'30/min' },
    { id:'reg.acks',       m:'GET',    p:'/registrations/cwr/acks',             r:'registrations', sum:'List acknowledgments', scope:'registrations:read', rate:'600/min' },
    { id:'reg.caf.gen',    m:'POST',   p:'/registrations/caf/generate',         r:'registrations', sum:'Generate CAF file', scope:'registrations:write', rate:'30/min' },

    // Webhooks
    { id:'wh.list',        m:'GET',    p:'/webhooks',                           r:'webhooks', sum:'List subscriptions', scope:'webhooks:read', rate:'120/min' },
    { id:'wh.create',      m:'POST',   p:'/webhooks',                           r:'webhooks', sum:'Create subscription', scope:'webhooks:write', rate:'30/min' },
    { id:'wh.delete',      m:'DELETE', p:'/webhooks/{id}',                      r:'webhooks', sum:'Delete subscription', scope:'webhooks:write', rate:'30/min' },
    { id:'wh.deliveries',  m:'GET',    p:'/webhooks/{id}/deliveries',           r:'webhooks', sum:'Get delivery log', scope:'webhooks:read', rate:'120/min' },
    { id:'wh.test',        m:'POST',   p:'/webhooks/{id}/test',                 r:'webhooks', sum:'Send test event', scope:'webhooks:write', rate:'30/min' },

    // Search
    { id:'search.global',  m:'GET',    p:'/search?q={query}',                   r:'search', sum:'Global fuzzy search', scope:'catalog:read', rate:'300/min' },
    { id:'search.iswc',    m:'GET',    p:'/search/iswc/{q}',                    r:'search', sum:'ISWC fuzzy search', scope:'catalog:read', rate:'300/min' },
    { id:'search.isrc',    m:'GET',    p:'/search/isrc/{q}',                    r:'search', sum:'ISRC fuzzy search', scope:'catalog:read', rate:'300/min' },
  ];

  // ─── EVENT TYPES ─────────────────────────────────────────────────
  const EVENTS = [
    { id:'work.created',          group:'Catalog',  desc:'A new work was created' },
    { id:'work.updated',          group:'Catalog',  desc:'A work was updated' },
    { id:'work.deleted',          group:'Catalog',  desc:'A work was deleted' },
    { id:'recording.created',     group:'Catalog',  desc:'A new recording was created' },
    { id:'release.published',     group:'Catalog',  desc:'A release went live on a DSP' },
    { id:'agreement.signed',      group:'Agreements', desc:'All parties signed an agreement' },
    { id:'agreement.executed',    group:'Agreements', desc:'Agreement reached effective date' },
    { id:'statement.parsed',      group:'Royalties', desc:'A statement finished parsing' },
    { id:'statement.matched',     group:'Royalties', desc:'Reconciliation matcher completed' },
    { id:'payout.computed',       group:'Royalties', desc:'A payout was computed' },
    { id:'cwr.acknowledged',      group:'Registrations', desc:'Society returned a CWR ack' },
    { id:'cwr.rejected',          group:'Registrations', desc:'Society rejected a CWR transaction' },
    { id:'claim.opened',          group:'Disputes',  desc:'A black-box claim was opened' },
    { id:'claim.resolved',        group:'Disputes',  desc:'A black-box claim was resolved' },
  ];

  // ─── KEYS ────────────────────────────────────────────────────────
  const KEYS = [
    { id:'k_prod_acct',  name:'Accounting service',     env:'prod', prefix:'sk_live_4f29••••', scopes:['catalog:read','royalties:read','royalties:write'], created:'2026-01-14', lastUsed:'12m ago', requests24h:184220 },
    { id:'k_prod_rfx',   name:'Royalty FX worker',       env:'prod', prefix:'sk_live_8a11••••', scopes:['royalties:read','royalties:write'], created:'2025-11-02', lastUsed:'4m ago', requests24h:42180 },
    { id:'k_prod_pluralis', name:'Pluralis composer hooks', env:'prod', prefix:'sk_live_c1de••••', scopes:['catalog:read','agreements:read'], created:'2025-09-08', lastUsed:'1m ago', requests24h:88420 },
    { id:'k_test_local', name:'Local dev (a.cohen)',     env:'test', prefix:'sk_test_de44••••', scopes:['catalog:read','catalog:write','royalties:read','royalties:write','agreements:read','agreements:write','registrations:read','registrations:write','webhooks:read','webhooks:write'], created:'2026-04-22', lastUsed:'2h ago', requests24h:188 },
    { id:'k_prod_pls_an',name:'Analytics pipeline',      env:'prod', prefix:'sk_live_aa78••••', scopes:['catalog:read','royalties:read'], created:'2025-07-19', lastUsed:'3h ago', requests24h:24180 },
    { id:'k_prod_legacy',name:'Legacy CRM (deprecating)',env:'prod', prefix:'sk_live_99zz••••', scopes:['catalog:read'], created:'2024-08-03', lastUsed:'14d ago', requests24h:0, deprecated:true },
  ];

  // ─── WEBHOOK SUBSCRIPTIONS ───────────────────────────────────────
  const SUBS = [
    { id:'wh_pls', url:'https://hooks.pluralis.com/astro/v3', events:['statement.parsed','statement.matched','payout.computed','cwr.acknowledged'], status:'ok', delivered24h:1420, failed24h:0, lastDelivery:'4m ago' },
    { id:'wh_lake',url:'https://lake.rocktscience.com/cdc/astro', events:['work.created','work.updated','recording.created','release.published'], status:'ok', delivered24h:842, failed24h:0, lastDelivery:'2m ago' },
    { id:'wh_sgi', url:'https://sgi.acct/sigil/astro-events', events:['agreement.signed','agreement.executed'], status:'ok', delivered24h:18, failed24h:0, lastDelivery:'2h ago' },
    { id:'wh_lgcy',url:'https://legacy-bus.azure.com/inbound', events:['cwr.acknowledged','cwr.rejected'], status:'degraded', delivered24h:42, failed24h:8, lastDelivery:'1h ago' },
  ];

  const DELIVERIES = [
    { id:'d1', ts:'2026-04-26 23:14:08', event:'statement.parsed', sub:'wh_pls', status:200, latency:142, attempt:1 },
    { id:'d2', ts:'2026-04-26 23:13:55', event:'work.created',     sub:'wh_lake',status:200, latency:188, attempt:1 },
    { id:'d3', ts:'2026-04-26 23:12:41', event:'cwr.acknowledged', sub:'wh_pls', status:200, latency:121, attempt:1 },
    { id:'d4', ts:'2026-04-26 23:11:12', event:'cwr.acknowledged', sub:'wh_lgcy',status:504, latency:30002, attempt:3, error:'gateway timeout' },
    { id:'d5', ts:'2026-04-26 23:08:33', event:'payout.computed',  sub:'wh_pls', status:200, latency:88,  attempt:1 },
    { id:'d6', ts:'2026-04-26 23:04:19', event:'recording.created',sub:'wh_lake',status:200, latency:144, attempt:1 },
    { id:'d7', ts:'2026-04-26 23:00:01', event:'agreement.signed', sub:'wh_sgi', status:200, latency:241, attempt:1 },
    { id:'d8', ts:'2026-04-26 22:58:48', event:'cwr.rejected',     sub:'wh_lgcy',status:502, latency:8412, attempt:2, error:'bad gateway' },
    { id:'d9', ts:'2026-04-26 22:55:22', event:'statement.matched',sub:'wh_pls', status:200, latency:111, attempt:1 },
  ];

  // ─── CHANGELOG ───────────────────────────────────────────────────
  const CHANGES = [
    { ver:'v3.4', date:'2026-04-12', kind:'add',     title:'Added /royalties/by-territory/{tis}', desc:'Earnings rollup keyed by 4-digit TIS code.' },
    { ver:'v3.4', date:'2026-04-12', kind:'add',     title:'Added webhook event statement.matched', desc:'Fires when reconciliation matcher completes.' },
    { ver:'v3.3', date:'2026-02-08', kind:'change',  title:'Pagination cursors changed format',     desc:'cursor now opaque base64; old offset-based still accepted via X-Legacy-Pagination header.' },
    { ver:'v3.3', date:'2026-02-08', kind:'add',     title:'Added /agreements/{id}/waterfall',     desc:'Server-side waterfall projection — was previously client-only.' },
    { ver:'v3.2', date:'2025-12-01', kind:'remove',  title:'Removed legacy /royalties/v1 endpoints', desc:'Sunsetted after 12-month grace.' },
    { ver:'v3.2', date:'2025-12-01', kind:'add',     title:'Added /recordings/{id}/audio-features', desc:'BPM/key/energy/danceability/valence — sources policy in Enrichment policy.' },
    { ver:'v3.1', date:'2025-09-04', kind:'add',     title:'CAF generation endpoints',              desc:'Mirror to existing CWR generation.' },
    { ver:'v3.1', date:'2025-09-04', kind:'change',  title:'Idempotency-Key header now required for all writes', desc:'Returns 409 Conflict if reused with different body.' },
    { ver:'v3.0', date:'2025-06-01', kind:'add',     title:'v3 launched', desc:'Breaking from v2: ID format change, pagination cursor, scope-based auth.' },
  ];

  // ─── helpers ─────────────────────────────────────────────────────
  function Mono({ children, size=10, color='var(--ink-3)', upper=true, style={} }) {
    return <span className="ff-mono" style={{ fontSize:size, color, letterSpacing:'.12em',
      textTransform: upper ? 'uppercase' : 'none', ...style }}>{children}</span>;
  }
  const METHOD_TONE = {
    GET:    { fg:'#0c6f4a', bg:'rgba(12,111,74,.08)' },
    POST:   { fg:'#1f4d99', bg:'rgba(31,77,153,.08)' },
    PATCH:  { fg:'#8a5a00', bg:'rgba(138,90,0,.08)' },
    DELETE: { fg:'#9a1f2c', bg:'rgba(154,31,44,.08)' },
  };
  function MethodPill({ m }) {
    const t = METHOD_TONE[m] || METHOD_TONE.GET;
    return <span className="ff-mono" style={{display:'inline-block',padding:'2px 7px',fontSize:10,fontWeight:600,
      letterSpacing:'.06em',background:t.bg,color:t.fg,minWidth:54,textAlign:'center'}}>{m}</span>;
  }
  function Code({ children, lang }) {
    return (
      <pre style={{margin:0,padding:14,background:'var(--bg-2)',border:'1px solid var(--rule)',
        fontSize:11,lineHeight:1.6,overflowX:'auto',fontFamily:'ui-monospace,monospace',whiteSpace:'pre'}}>
        {children}
      </pre>
    );
  }

  // ═════════════════════════════════════════════════════════════════
  // 01 OVERVIEW
  // ═════════════════════════════════════════════════════════════════
  function ApiOverviewTab() {
    return (
      <div>
        <div style={{display:'grid',gridTemplateColumns:'repeat(5, 1fr)',borderTop:'1px solid var(--rule)',borderBottom:'1px solid var(--rule)'}}>
          {[
            { l:'BASE URL',     v:'api.pluralis.io', sub:'/v3 · TLS 1.3 only' },
            { l:'ENDPOINTS',    v:ENDPOINTS.length, sub:`across ${RESOURCES.length} resources` },
            { l:'EVENT TYPES',  v:EVENTS.length,    sub:'webhook subscriptions' },
            { l:'ACTIVE KEYS',  v:KEYS.filter(k=>!k.deprecated).length, sub:`${KEYS.filter(k=>k.env==='prod').length} prod · ${KEYS.filter(k=>k.env==='test').length} test` },
            { l:'API CALLS 24H',v:'342k',           sub:'p95 142ms · 0.04% errors' },
          ].map((k, i) => (
            <div key={k.l} style={{padding:'18px 22px', borderRight: i<4?'1px solid var(--rule)':0}}>
              <Mono size={9}>{k.l}</Mono>
              <div className="ff-mono" style={{fontSize:18,fontWeight:600,marginTop:6,wordBreak:'break-all'}}>{k.v}</div>
              {k.sub && <div style={{fontSize:11,color:'var(--ink-3)',marginTop:6,lineHeight:1.4}}>{k.sub}</div>}
            </div>
          ))}
        </div>

        {/* 2-column body */}
        <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:30,marginTop:30}}>
          <div>
            <Mono size={10} style={{display:'block',marginBottom:12}}>AUTHENTICATION</Mono>
            <div style={{fontSize:12,color:'var(--ink-2)',lineHeight:1.6,marginBottom:14}}>
              All requests require a Bearer token in the <code style={{background:'var(--bg-2)',padding:'1px 5px'}}>Authorization</code> header.
              Tokens are scoped — see <span style={{color:'var(--ink)'}}>Keys</span> tab.
            </div>
            <Code>{`curl ${BASE}/works/W-RWOR10001 \\
  -H "Authorization: Bearer sk_live_4f29••••" \\
  -H "Idempotency-Key: req-uuid-here"`}</Code>

            <Mono size={10} style={{display:'block',marginTop:24,marginBottom:12}}>CONVENTIONS</Mono>
            <div style={{border:'1px solid var(--rule)'}}>
              {[
                ['IDs', 'Stable, opaque strings (W-, R-, L-, P-, A- prefixes by entity)'],
                ['Pagination', 'Cursor-based: ?cursor=abc&limit=100 · max 500/page'],
                ['Idempotency', 'Required header on writes. 24h replay window.'],
                ['Errors', 'RFC 7807 Problem Details JSON. type, title, status, detail.'],
                ['Versioning', 'URL-path major (v3) · header minor (X-Api-Version: 3.4)'],
                ['Time', 'ISO 8601 UTC. Periods are inclusive-exclusive [start, end).'],
                ['Money', 'Minor units (cents) + ISO 4217 currency code.'],
              ].map(([k, v], i, arr) => (
                <div key={k} style={{display:'grid',gridTemplateColumns:'140px 1fr',padding:'10px 14px',gap:14,
                  borderBottom: i<arr.length-1?'1px solid var(--rule-soft, var(--bg-2))':0,fontSize:12}}>
                  <Mono size={9}>{k.toUpperCase()}</Mono>
                  <span style={{color:'var(--ink-2)'}}>{v}</span>
                </div>
              ))}
            </div>
          </div>

          <div>
            <Mono size={10} style={{display:'block',marginBottom:12}}>RATE LIMITS</Mono>
            <div style={{border:'1px solid var(--rule)',marginBottom:24}}>
              {[
                ['Read endpoints',  '600 req/min',    'per key'],
                ['Write endpoints', '120 req/min',    'per key'],
                ['Bulk writes',     '30 req/min',     'per key'],
                ['Audio features',  '300 req/min',    'per key · 3rd-party throttled'],
                ['Search',          '300 req/min',    'per key'],
                ['Burst',           '+50% over 10s', 'token-bucket'],
                ['Headers',         'X-RateLimit-Limit / Remaining / Reset', '— on every response'],
              ].map(([k, v, sub], i, arr) => (
                <div key={k} style={{padding:'10px 14px',borderBottom:i<arr.length-1?'1px solid var(--rule-soft, var(--bg-2))':0,
                  display:'flex',alignItems:'baseline',gap:12,fontSize:12}}>
                  <span style={{flex:1}}>{k}</span>
                  <span className="ff-mono" style={{color:'var(--ink)'}}>{v}</span>
                  {sub && <Mono size={9}>{sub.toUpperCase()}</Mono>}
                </div>
              ))}
            </div>

            <Mono size={10} style={{display:'block',marginBottom:12}}>RESPONSE STATUS</Mono>
            <div style={{border:'1px solid var(--rule)'}}>
              {[
                [200, 'OK',           'Read or update succeeded'],
                [201, 'Created',      'Resource created'],
                [202, 'Accepted',     'Async work queued (statement parse, CWR build)'],
                [204, 'No Content',   'Delete succeeded'],
                [400, 'Bad Request',  'Validation failure — see problem.detail'],
                [401, 'Unauthorized', 'Missing or invalid token'],
                [403, 'Forbidden',    'Token lacks scope'],
                [404, 'Not Found',    'Unknown ID or resource'],
                [409, 'Conflict',     'Idempotency-Key reused with different body'],
                [422, 'Unprocessable','Valid syntax, semantic violation (e.g., shares >100%)'],
                [429, 'Too Many',     'Rate limited — retry after Retry-After'],
                [5  , '5xx',          'Server error — retry with backoff'],
              ].map(([code, label, desc], i, arr) => (
                <div key={code} style={{display:'grid',gridTemplateColumns:'40px 110px 1fr',padding:'8px 14px',gap:10,
                  borderBottom:i<arr.length-1?'1px solid var(--rule-soft, var(--bg-2))':0,fontSize:11,alignItems:'center'}}>
                  <span className="ff-mono" style={{fontWeight:600}}>{code === 5 ? '5xx' : code}</span>
                  <span style={{color:'var(--ink-2)'}}>{label}</span>
                  <span style={{color:'var(--ink-3)'}}>{desc}</span>
                </div>
              ))}
            </div>
          </div>
        </div>
      </div>
    );
  }

  // ═════════════════════════════════════════════════════════════════
  // 02 ENDPOINTS
  // ═════════════════════════════════════════════════════════════════
  function ApiEndpointsTab({ go, setTab, setTryEndpoint }) {
    const [filter, setFilter] = useState('all');
    const [query, setQuery] = useState('');

    const visible = useMemo(() => {
      let list = ENDPOINTS;
      if (filter !== 'all') list = list.filter(e => e.r === filter);
      if (query) {
        const q = query.toLowerCase();
        list = list.filter(e => e.p.toLowerCase().includes(q) || e.sum.toLowerCase().includes(q) || e.id.toLowerCase().includes(q));
      }
      return list;
    }, [filter, query]);

    const grouped = useMemo(() => {
      const g = {};
      visible.forEach(e => { (g[e.r] ||= []).push(e); });
      return g;
    }, [visible]);

    return (
      <div>
        {/* Filter strip */}
        <div style={{display:'flex',gap:6,flexWrap:'wrap',marginBottom:14,alignItems:'center'}}>
          <input value={query} onChange={e=>setQuery(e.target.value)} placeholder="Search path, summary, id…"
            style={{padding:'6px 10px',border:'1px solid var(--rule)',background:'var(--paper)',
              fontSize:12,minWidth:240,fontFamily:'ui-monospace,monospace'}}/>
          <button onClick={()=>setFilter('all')} className="ff-mono upper" style={{
            fontSize:10,letterSpacing:'.08em',padding:'6px 10px',
            background: filter==='all' ? 'var(--ink)' : 'transparent',
            color: filter==='all' ? 'var(--bg)' : 'var(--ink-2)',
            border:'1px solid '+(filter==='all'?'var(--ink)':'var(--rule)'),cursor:'pointer'}}>
            ALL <span className="ff-mono num" style={{marginLeft:4,opacity:.7}}>{ENDPOINTS.length}</span>
          </button>
          {RESOURCES.map(r => (
            <button key={r.id} onClick={()=>setFilter(r.id)} className="ff-mono upper" style={{
              fontSize:10,letterSpacing:'.08em',padding:'6px 10px',
              background: filter===r.id ? 'var(--ink)' : 'transparent',
              color: filter===r.id ? 'var(--bg)' : 'var(--ink-2)',
              border:'1px solid '+(filter===r.id?'var(--ink)':'var(--rule)'),cursor:'pointer'}}>
              {r.label} <span className="ff-mono num" style={{marginLeft:4,opacity:.7}}>{ENDPOINTS.filter(e=>e.r===r.id).length}</span>
            </button>
          ))}
        </div>

        {Object.entries(grouped).map(([resId, items]) => {
          const res = RESOURCES.find(r => r.id === resId);
          return (
            <div key={resId} style={{marginBottom:24}}>
              <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.12em',color:'var(--ink-3)',
                marginBottom:8,paddingBottom:6,borderBottom:'1px solid var(--rule)'}}>
                {res?.label || resId} <span style={{color:'var(--ink-4)',marginLeft:8}}>{items.length}</span>
              </div>
              <div style={{border:'1px solid var(--rule)'}}>
                {items.map((e, i) => (
                  <div key={e.id} style={{display:'grid',gridTemplateColumns:'70px 1fr 1.6fr 130px 90px 90px',gap:14,
                    padding:'10px 14px',alignItems:'center',
                    borderBottom: i<items.length-1?'1px solid var(--rule-soft, var(--bg-2))':0}}>
                    <MethodPill m={e.m}/>
                    <span className="ff-mono" style={{fontSize:12,fontWeight:500}}>{e.p}</span>
                    <span style={{fontSize:12,color:'var(--ink-2)'}}>{e.sum}</span>
                    <Mono size={9}>{e.scope.toUpperCase()}</Mono>
                    <Mono size={9} style={{color:'var(--ink-2)'}}>{e.rate}</Mono>
                    <button onClick={()=>{ setTryEndpoint(e.id); setTab('try'); }} className="ff-mono upper" style={{
                      fontSize:9,letterSpacing:'.08em',padding:'4px 8px',
                      background:'transparent',border:'1px solid var(--rule)',color:'var(--ink-2)',cursor:'pointer'}}>
                      TRY →
                    </button>
                  </div>
                ))}
              </div>
            </div>
          );
        })}
        {visible.length === 0 && (
          <div style={{padding:30,textAlign:'center',color:'var(--ink-3)',fontSize:12}}>No endpoints match this filter.</div>
        )}
      </div>
    );
  }

  // ═════════════════════════════════════════════════════════════════
  // 03 TRY IT — request inspector
  // ═════════════════════════════════════════════════════════════════
  function ApiTryTab({ initial }) {
    const [endpointId, setEndpointId] = useState(initial || 'works.get');
    const [pathParam, setPathParam] = useState('W-RWOR10001');
    const [queryParam, setQueryParam] = useState('limit=20');
    const [body, setBody] = useState('{\n  "title": "New Work Title",\n  "language": "en"\n}');
    const [keyId, setKeyId] = useState('k_test_local');
    const [running, setRunning] = useState(false);
    const [response, setResponse] = useState(null);

    const ep = ENDPOINTS.find(e => e.id === endpointId) || ENDPOINTS[0];
    const hasBody = ep.m === 'POST' || ep.m === 'PATCH';
    const hasPath = ep.p.includes('{');

    const run = () => {
      setRunning(true);
      setResponse(null);
      setTimeout(() => {
        // Synthesize a plausible response per endpoint
        let status = 200, body_, latency = 84 + Math.floor(Math.random()*200);
        if (ep.m === 'POST' && ep.p === '/works')        { status = 201; body_ = { id:'W-RWOR99999', title:'New Work Title', language:'en', createdAt:new Date().toISOString() }; }
        else if (ep.m === 'DELETE')                       { status = 204; body_ = null; latency = 32; }
        else if (ep.id === 'works.get')                   { body_ = { id:pathParam, title:'Eres Tú', language:'es', iswc:'T-927.853.142-7', shares:[{partyId:'P-RPRO00073',role:'CA',share:50},{partyId:'P-RPRO00088',role:'CA',share:50}], createdAt:'2009-04-12T00:00:00Z', updatedAt:'2026-04-04T17:21:00Z' }; }
        else if (ep.id === 'works.shares')                { body_ = { workId:pathParam, totalShare:100.0, parties:[{ partyId:'P-RPRO00073', name:'Edgar Fernández', role:'CA', pr:50, mr:50, sr:50 },{ partyId:'P-RPRO00088', name:'Daniel Mella', role:'CA', pr:50, mr:50, sr:50 }] }; }
        else if (ep.id === 'rec.audio')                   { body_ = { recordingId:pathParam, bpm:108, key:'F#', mode:'minor', timeSignature:'4/4', energy:0.62, danceability:0.71, valence:0.34, sources:{ bpm:'acousticbrainz', key:'acousticbrainz', energy:'spotify-feat' } }; }
        else if (ep.id === 'roy.byparty')                 { body_ = { partyId:pathParam, period:'2026-Q1', currency:'USD', totalCents:1842241, byTerritory:[{tis:'2840',cents:1284221},{tis:'2826',cents:188422},{tis:'2724',cents:142811}], byRight:[{kind:'PR',cents:888412},{kind:'MR',cents:721422},{kind:'SR',cents:232407}] }; }
        else if (ep.id === 'stmt.upload')                 { status = 202; body_ = { jobId:'job_AbC4d', statementId:'S-2026Q1-ASCAP', status:'queued', estimatedSeconds:18 }; }
        else if (ep.id === 'reg.cwr.gen')                 { status = 202; body_ = { jobId:'job_cwr_9k', registrationId:'CWR-26041', estimatedSeconds:42 }; }
        else if (ep.id === 'wh.create')                   { status = 201; body_ = { id:'wh_new', url:'https://example.com/hooks', events:['statement.parsed'], secret:'whsec_abc123', createdAt:new Date().toISOString() }; }
        else if (ep.id === 'search.global')               { body_ = { query:'eres tu', hits:[{kind:'work',id:'W-RWOR10035',title:'Eres Tu (Me Gustas Tu)',score:0.94},{kind:'recording',id:'R-RWOR10035-R1',title:'Eres Tu',score:0.88}], total:2 }; }
        else                                              { body_ = { id:pathParam || 'sample', _note:'synthesized response · click an endpoint with a real fixture for richer data' }; }
        setResponse({ status, body: body_, latency, headers: {
          'X-RateLimit-Limit': ep.rate.split('/')[0],
          'X-RateLimit-Remaining': String(parseInt(ep.rate)-1),
          'X-RateLimit-Reset': '60s',
          'X-Request-Id': 'req_'+Math.random().toString(36).slice(2, 12),
          'X-Api-Version': '3.4',
        }});
        setRunning(false);
      }, 600);
    };

    const fullPath = ep.p.replace(/\{[^}]+\}/, pathParam) + (queryParam && ep.m === 'GET' ? (ep.p.includes('?')?'&':'?')+queryParam : '');
    const statusTone = response ? (response.status < 300 ? 'var(--ok)' : response.status < 400 ? 'var(--ink-2)' : 'var(--danger)') : 'var(--ink-3)';

    return (
      <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:24}}>
        {/* Request */}
        <div>
          <Mono size={10} style={{display:'block',marginBottom:10}}>REQUEST</Mono>
          <div style={{border:'1px solid var(--rule)',padding:14,marginBottom:12}}>
            <div style={{display:'grid',gridTemplateColumns:'90px 1fr',gap:10,marginBottom:10}}>
              <Mono size={9} style={{alignSelf:'center'}}>ENDPOINT</Mono>
              <select value={endpointId} onChange={e=>setEndpointId(e.target.value)} style={{padding:'6px 8px',border:'1px solid var(--rule)',background:'var(--paper)',fontSize:11,fontFamily:'ui-monospace,monospace'}}>
                {RESOURCES.map(r => (
                  <optgroup key={r.id} label={r.label}>
                    {ENDPOINTS.filter(e=>e.r===r.id).map(e => (
                      <option key={e.id} value={e.id}>{e.m} {e.p}</option>
                    ))}
                  </optgroup>
                ))}
              </select>
            </div>
            <div style={{display:'grid',gridTemplateColumns:'90px 1fr',gap:10,marginBottom:10,alignItems:'center'}}>
              <Mono size={9}>METHOD</Mono>
              <div><MethodPill m={ep.m}/> <span className="ff-mono" style={{fontSize:11,marginLeft:8,color:'var(--ink-3)'}}>{ep.scope}</span></div>
            </div>
            {hasPath && (
              <div style={{display:'grid',gridTemplateColumns:'90px 1fr',gap:10,marginBottom:10,alignItems:'center'}}>
                <Mono size={9}>PATH PARAM</Mono>
                <input value={pathParam} onChange={e=>setPathParam(e.target.value)} className="ff-mono" style={{padding:'6px 8px',border:'1px solid var(--rule)',background:'var(--paper)',fontSize:11}}/>
              </div>
            )}
            {ep.m === 'GET' && (
              <div style={{display:'grid',gridTemplateColumns:'90px 1fr',gap:10,marginBottom:10,alignItems:'center'}}>
                <Mono size={9}>QUERY</Mono>
                <input value={queryParam} onChange={e=>setQueryParam(e.target.value)} placeholder="limit=20&cursor=…" className="ff-mono" style={{padding:'6px 8px',border:'1px solid var(--rule)',background:'var(--paper)',fontSize:11}}/>
              </div>
            )}
            {hasBody && (
              <div style={{display:'grid',gridTemplateColumns:'90px 1fr',gap:10,marginBottom:10}}>
                <Mono size={9} style={{alignSelf:'flex-start',paddingTop:6}}>BODY</Mono>
                <textarea value={body} onChange={e=>setBody(e.target.value)} className="ff-mono" rows={8}
                  style={{padding:8,border:'1px solid var(--rule)',background:'var(--paper)',fontSize:11,fontFamily:'ui-monospace,monospace',resize:'vertical'}}/>
              </div>
            )}
            <div style={{display:'grid',gridTemplateColumns:'90px 1fr',gap:10,marginBottom:10,alignItems:'center'}}>
              <Mono size={9}>KEY</Mono>
              <select value={keyId} onChange={e=>setKeyId(e.target.value)} style={{padding:'6px 8px',border:'1px solid var(--rule)',background:'var(--paper)',fontSize:11,fontFamily:'ui-monospace,monospace'}}>
                {KEYS.filter(k=>!k.deprecated).map(k => (
                  <option key={k.id} value={k.id}>[{k.env}] {k.name} · {k.prefix}</option>
                ))}
              </select>
            </div>
            <button onClick={run} disabled={running} className="ff-mono upper" style={{
              padding:'10px 14px',fontSize:11,letterSpacing:'.1em',marginTop:6,
              background: running ? 'var(--bg-2)' : 'var(--ink)',
              color: running ? 'var(--ink-3)' : 'var(--bg)',
              border:0,cursor:running?'wait':'pointer',width:'100%'}}>
              {running ? 'SENDING…' : `▶  SEND ${ep.m} REQUEST`}
            </button>
          </div>

          <Mono size={9} style={{display:'block',marginBottom:6}}>EQUIVALENT cURL</Mono>
          <Code>{`curl -X ${ep.m} '${BASE}${fullPath}' \\
  -H 'Authorization: Bearer ${KEYS.find(k=>k.id===keyId)?.prefix || 'sk_••••'}' \\
  -H 'Content-Type: application/json'${hasBody ? ` \\\n  -d '${body.replace(/\n/g,' ').replace(/  +/g,' ')}'` : ''}`}</Code>
        </div>

        {/* Response */}
        <div>
          <Mono size={10} style={{display:'block',marginBottom:10}}>RESPONSE</Mono>
          {!response && !running && (
            <div style={{border:'1px dashed var(--rule)',padding:40,textAlign:'center',color:'var(--ink-3)',fontSize:12}}>
              Send a request to inspect the response.
            </div>
          )}
          {running && (
            <div style={{border:'1px solid var(--rule)',padding:40,textAlign:'center',color:'var(--ink-3)',fontSize:12}}>
              <span className="ff-mono">…awaiting response</span>
            </div>
          )}
          {response && (
            <>
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:0,border:'1px solid var(--rule)',marginBottom:12}}>
                <div style={{padding:'10px 14px',borderRight:'1px solid var(--rule)'}}>
                  <Mono size={9}>STATUS</Mono>
                  <div className="ff-mono" style={{fontSize:18,fontWeight:600,marginTop:4,color:statusTone}}>{response.status}</div>
                </div>
                <div style={{padding:'10px 14px',borderRight:'1px solid var(--rule)'}}>
                  <Mono size={9}>LATENCY</Mono>
                  <div className="ff-mono" style={{fontSize:18,fontWeight:600,marginTop:4}}>{response.latency}ms</div>
                </div>
                <div style={{padding:'10px 14px'}}>
                  <Mono size={9}>SIZE</Mono>
                  <div className="ff-mono" style={{fontSize:18,fontWeight:600,marginTop:4}}>{response.body ? JSON.stringify(response.body).length+'b' : '0b'}</div>
                </div>
              </div>

              <Mono size={9} style={{display:'block',marginBottom:6}}>HEADERS</Mono>
              <div style={{border:'1px solid var(--rule)',marginBottom:12}}>
                {Object.entries(response.headers).map(([k,v], i, arr) => (
                  <div key={k} style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:10,padding:'6px 12px',
                    borderBottom: i<arr.length-1?'1px solid var(--rule-soft, var(--bg-2))':0,fontSize:11}}>
                    <span className="ff-mono" style={{color:'var(--ink-2)'}}>{k}</span>
                    <span className="ff-mono" style={{color:'var(--ink)'}}>{v}</span>
                  </div>
                ))}
              </div>

              <Mono size={9} style={{display:'block',marginBottom:6}}>BODY</Mono>
              <Code>{response.body == null ? '(no body)' : JSON.stringify(response.body, null, 2)}</Code>
            </>
          )}
        </div>
      </div>
    );
  }

  // ═════════════════════════════════════════════════════════════════
  // 04 KEYS
  // ═════════════════════════════════════════════════════════════════
  function ApiKeysTab() {
    const [showCreate, setShowCreate] = useState(false);
    return (
      <div>
        <div style={{display:'flex',alignItems:'baseline',justifyContent:'space-between',marginBottom:14}}>
          <div style={{fontSize:12,color:'var(--ink-2)',maxWidth:560,lineHeight:1.5}}>
            API keys are scoped — give the smallest set of scopes a service needs. Test keys
            (<span className="ff-mono">sk_test_…</span>) hit the test environment with synthesized
            data; live keys (<span className="ff-mono">sk_live_…</span>) hit production. Rotate quarterly.
          </div>
          <button onClick={()=>setShowCreate(s=>!s)} className="ff-mono upper" style={{
            padding:'8px 14px',fontSize:10,letterSpacing:'.1em',
            background:'var(--ink)',color:'var(--bg)',border:0,cursor:'pointer'}}>
            + NEW KEY
          </button>
        </div>

        {showCreate && (
          <div style={{border:'1px solid var(--ink)',padding:14,marginBottom:18}}>
            <Mono size={9} style={{display:'block',marginBottom:10}}>NEW API KEY</Mono>
            <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:14}}>
              <div>
                <Mono size={8} style={{display:'block',marginBottom:4}}>NAME</Mono>
                <input placeholder="e.g. Accounting service" style={{padding:'6px 8px',border:'1px solid var(--rule)',background:'var(--paper)',fontSize:12,width:'100%'}}/>
              </div>
              <div>
                <Mono size={8} style={{display:'block',marginBottom:4}}>ENVIRONMENT</Mono>
                <select style={{padding:'6px 8px',border:'1px solid var(--rule)',background:'var(--paper)',fontSize:12,width:'100%'}}>
                  <option>test (sk_test_…)</option>
                  <option>prod (sk_live_…)</option>
                </select>
              </div>
            </div>
            <Mono size={8} style={{display:'block',marginTop:14,marginBottom:6}}>SCOPES</Mono>
            <div style={{display:'flex',flexWrap:'wrap',gap:6}}>
              {['catalog:read','catalog:write','royalties:read','royalties:write','agreements:read','agreements:write','registrations:read','registrations:write','webhooks:read','webhooks:write'].map(s => (
                <label key={s} style={{display:'inline-flex',alignItems:'center',gap:4,padding:'4px 8px',border:'1px solid var(--rule)',cursor:'pointer',fontSize:11}}>
                  <input type="checkbox" /> {s}
                </label>
              ))}
            </div>
            <div style={{display:'flex',gap:8,marginTop:14}}>
              <button onClick={()=>setShowCreate(false)} className="ff-mono upper" style={{padding:'8px 12px',fontSize:10,letterSpacing:'.08em',background:'var(--ink)',color:'var(--bg)',border:0,cursor:'pointer'}}>CREATE KEY</button>
              <button onClick={()=>setShowCreate(false)} className="ff-mono upper" style={{padding:'8px 12px',fontSize:10,letterSpacing:'.08em',background:'transparent',color:'var(--ink-2)',border:'1px solid var(--rule)',cursor:'pointer'}}>CANCEL</button>
            </div>
          </div>
        )}

        <div style={{border:'1px solid var(--rule)'}}>
          <div style={{display:'grid',gridTemplateColumns:'1.4fr 70px 1.6fr 2fr 110px 110px 100px 80px',gap:14,padding:'10px 14px',background:'var(--bg-2)',borderBottom:'1px solid var(--rule)'}}>
            {['Name','Env','Prefix','Scopes','Created','Last used','24h calls','Actions'].map(h => <Mono key={h} size={9}>{h.toUpperCase()}</Mono>)}
          </div>
          {KEYS.map((k, i) => (
            <div key={k.id} style={{display:'grid',gridTemplateColumns:'1.4fr 70px 1.6fr 2fr 110px 110px 100px 80px',gap:14,padding:'12px 14px',
              alignItems:'center',borderBottom:i<KEYS.length-1?'1px solid var(--rule-soft, var(--bg-2))':0,fontSize:11,
              opacity: k.deprecated?0.55:1}}>
              <div>
                <div style={{fontSize:12,fontWeight:600}}>{k.name}</div>
                {k.deprecated && <Mono size={8} style={{color:'var(--warn, #d4881f)',marginTop:2}}>DEPRECATED</Mono>}
              </div>
              <Mono size={9} style={{color: k.env==='prod' ? 'var(--danger)' : 'var(--ok)'}}>{k.env.toUpperCase()}</Mono>
              <span className="ff-mono" style={{fontSize:11}}>{k.prefix}</span>
              <div style={{display:'flex',flexWrap:'wrap',gap:3}}>
                {k.scopes.slice(0,3).map(s => <span key={s} className="ff-mono" style={{fontSize:9,padding:'1px 5px',background:'var(--bg-2)',border:'1px solid var(--rule)'}}>{s}</span>)}
                {k.scopes.length > 3 && <Mono size={8} style={{color:'var(--ink-3)'}}>+{k.scopes.length-3}</Mono>}
              </div>
              <span className="ff-mono" style={{color:'var(--ink-2)'}}>{k.created}</span>
              <span className="ff-mono" style={{color:'var(--ink-2)'}}>{k.lastUsed}</span>
              <span className="ff-mono num" style={{textAlign:'right'}}>{k.requests24h.toLocaleString()}</span>
              <div style={{display:'flex',gap:4}}>
                <button title="Rotate" className="ff-mono" style={{fontSize:9,padding:'3px 6px',background:'transparent',border:'1px solid var(--rule)',color:'var(--ink-2)',cursor:'pointer'}}>↻</button>
                <button title="Revoke" className="ff-mono" style={{fontSize:9,padding:'3px 6px',background:'transparent',border:'1px solid var(--rule)',color:'var(--danger)',cursor:'pointer'}}>×</button>
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  // ═════════════════════════════════════════════════════════════════
  // 05 WEBHOOKS
  // ═════════════════════════════════════════════════════════════════
  function ApiWebhooksTab() {
    const [tab, setTab] = useState('subs');
    return (
      <div>
        <div style={{display:'flex',gap:0,marginBottom:18,borderBottom:'1px solid var(--rule)'}}>
          {[['subs','Subscriptions',SUBS.length],['events','Event types',EVENTS.length],['deliveries','Recent deliveries',DELIVERIES.length]].map(([k,l,n]) => (
            <button key={k} onClick={()=>setTab(k)} className="ff-mono upper" style={{
              padding:'10px 16px',background:'transparent',border:0,
              borderBottom:'2px solid '+(tab===k?'var(--ink)':'transparent'),
              color: tab===k?'var(--ink)':'var(--ink-3)', fontSize:11,letterSpacing:'.08em',
              cursor:'pointer',display:'flex',alignItems:'baseline',gap:6,fontWeight:tab===k?600:400}}>
              {l} <span className="ff-mono num" style={{fontSize:10,opacity:.7}}>{n}</span>
            </button>
          ))}
        </div>

        {tab === 'subs' && (
          <div style={{border:'1px solid var(--rule)'}}>
            <div style={{display:'grid',gridTemplateColumns:'2.4fr 2fr 90px 100px 90px 110px',gap:14,padding:'10px 14px',background:'var(--bg-2)',borderBottom:'1px solid var(--rule)'}}>
              {['URL','Events','Status','Delivered 24h','Failed 24h','Last delivery'].map(h => <Mono key={h} size={9}>{h.toUpperCase()}</Mono>)}
            </div>
            {SUBS.map((s, i) => (
              <div key={s.id} style={{display:'grid',gridTemplateColumns:'2.4fr 2fr 90px 100px 90px 110px',gap:14,padding:'12px 14px',
                alignItems:'center',borderBottom:i<SUBS.length-1?'1px solid var(--rule-soft, var(--bg-2))':0,fontSize:11}}>
                <span className="ff-mono" style={{fontSize:11}}>{s.url}</span>
                <div style={{display:'flex',flexWrap:'wrap',gap:3}}>
                  {s.events.slice(0,3).map(e => <span key={e} className="ff-mono" style={{fontSize:9,padding:'1px 5px',background:'var(--bg-2)',border:'1px solid var(--rule)'}}>{e}</span>)}
                  {s.events.length > 3 && <Mono size={8}>+{s.events.length-3}</Mono>}
                </div>
                <Mono size={9} style={{color: s.status==='ok'?'var(--ok)':'var(--warn, #d4881f)'}}>{s.status.toUpperCase()}</Mono>
                <span className="ff-mono num" style={{textAlign:'right'}}>{s.delivered24h.toLocaleString()}</span>
                <span className="ff-mono num" style={{textAlign:'right',color: s.failed24h>0?'var(--danger)':'var(--ink-3)'}}>{s.failed24h}</span>
                <span className="ff-mono" style={{color:'var(--ink-2)'}}>{s.lastDelivery}</span>
              </div>
            ))}
          </div>
        )}

        {tab === 'events' && (
          <div>
            {[...new Set(EVENTS.map(e=>e.group))].map(g => (
              <div key={g} style={{marginBottom:24}}>
                <Mono size={10} style={{display:'block',marginBottom:8,paddingBottom:6,borderBottom:'1px solid var(--rule)'}}>{g.toUpperCase()}</Mono>
                <div style={{border:'1px solid var(--rule)'}}>
                  {EVENTS.filter(e=>e.group===g).map((e, i, arr) => (
                    <div key={e.id} style={{display:'grid',gridTemplateColumns:'240px 1fr',gap:14,padding:'10px 14px',
                      borderBottom:i<arr.length-1?'1px solid var(--rule-soft, var(--bg-2))':0,fontSize:12,alignItems:'baseline'}}>
                      <span className="ff-mono" style={{fontWeight:600}}>{e.id}</span>
                      <span style={{color:'var(--ink-2)'}}>{e.desc}</span>
                    </div>
                  ))}
                </div>
              </div>
            ))}
          </div>
        )}

        {tab === 'deliveries' && (
          <div style={{border:'1px solid var(--rule)'}}>
            <div style={{display:'grid',gridTemplateColumns:'170px 200px 90px 80px 80px 70px 1fr',gap:14,padding:'10px 14px',background:'var(--bg-2)',borderBottom:'1px solid var(--rule)'}}>
              {['Timestamp','Event','Subscription','Status','Latency','Attempt','Error'].map(h => <Mono key={h} size={9}>{h.toUpperCase()}</Mono>)}
            </div>
            {DELIVERIES.map((d, i) => (
              <div key={d.id} style={{display:'grid',gridTemplateColumns:'170px 200px 90px 80px 80px 70px 1fr',gap:14,padding:'10px 14px',
                alignItems:'center',borderBottom:i<DELIVERIES.length-1?'1px solid var(--rule-soft, var(--bg-2))':0,fontSize:11}}>
                <span className="ff-mono" style={{color:'var(--ink-2)'}}>{d.ts}</span>
                <span className="ff-mono">{d.event}</span>
                <span className="ff-mono" style={{color:'var(--ink-2)'}}>{d.sub}</span>
                <span className="ff-mono num" style={{color: d.status<300?'var(--ok)':'var(--danger)',fontWeight:600}}>{d.status}</span>
                <span className="ff-mono num">{d.latency}ms</span>
                <span className="ff-mono num">{d.attempt}</span>
                <span style={{color:'var(--danger)',fontSize:11}}>{d.error || ''}</span>
              </div>
            ))}
          </div>
        )}
      </div>
    );
  }

  // ═════════════════════════════════════════════════════════════════
  // 06 SDKs
  // ═════════════════════════════════════════════════════════════════
  function ApiSDKsTab() {
    const [lang, setLang] = useState('node');
    const examples = {
      node: {
        install: `npm install @pluralis/astro@^3.4`,
        init: `import { AstroClient } from '@pluralis/astro';

const astro = new AstroClient({
  apiKey: process.env.ASTRO_API_KEY,
  // baseUrl defaults to https://api.pluralis.io/v3
});`,
        example: `// Get a work + its shares
const work = await astro.works.get('W-RWOR10001');
const shares = await astro.works.shares('W-RWOR10001');

// Upload a statement file
const stmt = await astro.statements.upload({
  file: fs.createReadStream('./ascap-q1.csv'),
  source: 'ASCAP',
  period: '2026-Q1',
});

// Subscribe to events
await astro.webhooks.create({
  url: 'https://my.app/hooks',
  events: ['statement.parsed', 'payout.computed'],
});`,
      },
      python: {
        install: `pip install pluralis-astro==3.4`,
        init: `from pluralis_astro import AstroClient
import os

astro = AstroClient(api_key=os.environ['ASTRO_API_KEY'])`,
        example: `# Get a work + its shares
work = astro.works.get('W-RWOR10001')
shares = astro.works.shares('W-RWOR10001')

# Upload a statement file
with open('ascap-q1.csv', 'rb') as f:
    stmt = astro.statements.upload(
        file=f,
        source='ASCAP',
        period='2026-Q1',
    )

# Subscribe to events
astro.webhooks.create(
    url='https://my.app/hooks',
    events=['statement.parsed', 'payout.computed'],
)`,
      },
      curl: {
        install: `# No SDK — direct HTTP. Set:
export ASTRO_KEY="sk_live_4f29••••"`,
        init: `# All requests go to ${BASE}
# All writes need an Idempotency-Key header.`,
        example: `# Get a work
curl ${BASE}/works/W-RWOR10001 \\
  -H "Authorization: Bearer $ASTRO_KEY"

# Upload a statement (multipart)
curl -X POST ${BASE}/statements/upload \\
  -H "Authorization: Bearer $ASTRO_KEY" \\
  -H "Idempotency-Key: $(uuidgen)" \\
  -F "file=@ascap-q1.csv" \\
  -F "source=ASCAP" \\
  -F "period=2026-Q1"

# Create webhook
curl -X POST ${BASE}/webhooks \\
  -H "Authorization: Bearer $ASTRO_KEY" \\
  -H "Idempotency-Key: $(uuidgen)" \\
  -H "Content-Type: application/json" \\
  -d '{"url":"https://my.app/hooks","events":["statement.parsed","payout.computed"]}'`,
      },
      go: {
        install: `go get github.com/pluralis/astro-go@v3.4.0`,
        init: `import "github.com/pluralis/astro-go"

client := astro.NewClient(os.Getenv("ASTRO_API_KEY"))`,
        example: `// Get a work
work, err := client.Works.Get(ctx, "W-RWOR10001")
if err != nil { return err }

// Upload a statement
f, _ := os.Open("ascap-q1.csv")
stmt, err := client.Statements.Upload(ctx, &astro.UploadParams{
    File:   f,
    Source: "ASCAP",
    Period: "2026-Q1",
})

// Create webhook
hook, err := client.Webhooks.Create(ctx, &astro.WebhookParams{
    URL:    "https://my.app/hooks",
    Events: []string{"statement.parsed", "payout.computed"},
})`,
      },
    };
    const ex = examples[lang];

    return (
      <div>
        <div style={{display:'flex',gap:0,marginBottom:18,borderBottom:'1px solid var(--rule)'}}>
          {[['node','Node.js'],['python','Python'],['go','Go'],['curl','cURL']].map(([k,l]) => (
            <button key={k} onClick={()=>setLang(k)} className="ff-mono upper" style={{
              padding:'10px 18px',background:'transparent',border:0,
              borderBottom:'2px solid '+(lang===k?'var(--ink)':'transparent'),
              color: lang===k?'var(--ink)':'var(--ink-3)', fontSize:11,letterSpacing:'.08em',
              cursor:'pointer',fontWeight:lang===k?600:400}}>
              {l}
            </button>
          ))}
        </div>

        <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:24}}>
          <div>
            <Mono size={10} style={{display:'block',marginBottom:8}}>INSTALL</Mono>
            <Code>{ex.install}</Code>
            <Mono size={10} style={{display:'block',marginTop:18,marginBottom:8}}>INITIALIZE</Mono>
            <Code>{ex.init}</Code>
          </div>
          <div>
            <Mono size={10} style={{display:'block',marginBottom:8}}>EXAMPLE</Mono>
            <Code>{ex.example}</Code>
          </div>
        </div>
      </div>
    );
  }

  // ═════════════════════════════════════════════════════════════════
  // 07 CHANGELOG
  // ═════════════════════════════════════════════════════════════════
  function ApiChangelogTab() {
    const KIND_TONE = { add:{l:'ADDED',c:'var(--ok)'}, change:{l:'CHANGED',c:'var(--warn, #d4881f)'}, remove:{l:'REMOVED',c:'var(--danger)'} };
    return (
      <div>
        <div style={{fontSize:12,color:'var(--ink-2)',maxWidth:680,lineHeight:1.5,marginBottom:18}}>
          The major version (v3) bumps only on breaking changes. Minor versions bump for additive
          changes — track via the <span className="ff-mono">X-Api-Version</span> response header.
          Removed endpoints get a 12-month deprecation window with the <span className="ff-mono">Sunset</span> header.
        </div>
        <div style={{border:'1px solid var(--rule)'}}>
          {CHANGES.map((c, i) => {
            const k = KIND_TONE[c.kind];
            return (
              <div key={i} style={{display:'grid',gridTemplateColumns:'80px 100px 90px 1fr',gap:14,padding:'12px 14px',
                borderBottom:i<CHANGES.length-1?'1px solid var(--rule-soft, var(--bg-2))':0,alignItems:'baseline'}}>
                <Mono size={10} style={{color:'var(--ink)'}}>{c.ver}</Mono>
                <Mono size={10} style={{color:'var(--ink-3)'}}>{c.date}</Mono>
                <Mono size={9} style={{color:k.c}}>{k.l}</Mono>
                <div>
                  <div style={{fontSize:13,fontWeight:600,marginBottom:4}}>{c.title}</div>
                  <div style={{fontSize:11,color:'var(--ink-2)',lineHeight:1.5}}>{c.desc}</div>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    );
  }

  // ═════════════════════════════════════════════════════════════════
  // MAIN
  // ═════════════════════════════════════════════════════════════════
  function ScreenApi({ go, payload }) {
    const PageHeader = window.PageHeader;
    const [tab, setTab] = useState(payload?.tab || 'overview');
    const [tryEndpoint, setTryEndpoint] = useState(null);

    const TABS = [
      { k:'overview',  l:'Overview' },
      { k:'endpoints', l:'Endpoints', n:ENDPOINTS.length },
      { k:'try',       l:'Try it' },
      { k:'keys',      l:'Keys',      n:KEYS.filter(k=>!k.deprecated).length },
      { k:'webhooks',  l:'Webhooks',  n:SUBS.length },
      { k:'sdks',      l:'SDKs' },
      { k:'changelog', l:'Changelog' },
    ];

    return (
      <div>
        {PageHeader && (
          <PageHeader
            eyebrow={['DEVELOPER', 'REST API', `v3.4 · ${ENDPOINTS.length} ENDPOINTS`]}
            title="API"
            highlight="API"
            sub={`${BASE} — REST + JSON. OAuth-style scoped Bearer tokens, idempotency-keyed writes, cursor pagination, RFC 7807 errors, and webhooks for ${EVENTS.length} event types. Try requests live and grab SDK snippets in Node, Python, Go, or cURL.`}
            actions={
              <div style={{display:'flex',gap:8}}>
                <a href={BASE} target="_blank" rel="noreferrer" className="ff-mono upper" style={{
                  padding:'8px 14px',fontSize:10,letterSpacing:'.1em',
                  background:'transparent',border:'1px solid var(--rule)',color:'var(--ink-2)',
                  textDecoration:'none',display:'inline-flex',alignItems:'center',gap:6}}>
                  OPENAPI SPEC ↗
                </a>
              </div>
            }
          />
        )}

        <div style={{borderBottom:'1px solid var(--rule)',display:'flex',gap:0,overflowX:'auto'}}>
          {TABS.map(t => (
            <button key={t.k} onClick={()=>setTab(t.k)} style={{
              padding:'14px 22px',background:'transparent',border:0,
              borderBottom:'2px solid '+(tab===t.k?'var(--ink)':'transparent'),
              cursor:'pointer',color:tab===t.k?'var(--ink)':'var(--ink-3)',
              fontSize:13,fontWeight:tab===t.k?600:400,whiteSpace:'nowrap',
              display:'flex',alignItems:'baseline',gap:8}}>
              {t.l}
              {t.n != null && <span className="ff-mono num" style={{fontSize:10,color:'var(--ink-3)'}}>{t.n}</span>}
            </button>
          ))}
        </div>

        <div style={{paddingTop:20}}>
          {tab === 'overview'  && <ApiOverviewTab/>}
          {tab === 'endpoints' && <ApiEndpointsTab go={go} setTab={setTab} setTryEndpoint={setTryEndpoint}/>}
          {tab === 'try'       && <ApiTryTab initial={tryEndpoint}/>}
          {tab === 'keys'      && <ApiKeysTab/>}
          {tab === 'webhooks'  && <ApiWebhooksTab/>}
          {tab === 'sdks'      && <ApiSDKsTab/>}
          {tab === 'changelog' && <ApiChangelogTab/>}
        </div>
      </div>
    );
  }

  Object.assign(window, { ScreenApi });
  console.log('[api] loaded');
})();
