// recording-page.jsx — Full-page Recording detail (parallel to ScreenWork)
//
// Routes from app.jsx:
//   go('recording', { id })   — preferred (fetches from REC_BY_ID)
//   go('recording', record)   — legacy (passes the record object directly)
//
// Mirrors the editorial / brutalist tone of ScreenWork: tabbed layout with
// numbered Section blocks, pill metadata, mono accents.

const _RecPage_useS = React.useState;
const _RecPage_useM = React.useMemo;
const _RecPage_useE = React.useEffect;

function ScreenRecording({ go, payload }) {
  // Resolve the recording from the route payload
  const recId = payload?.id || payload?.aid || (payload && payload.isrc ? payload.id : null) || (typeof payload === 'string' ? payload : null);
  const r = _RecPage_useM(() => {
    if (payload && typeof payload === 'object' && payload.title) return payload; // legacy: full obj
    const id = recId || (payload && payload.id);
    if (!id) return null;
    return (typeof REC_BY_ID !== 'undefined' && REC_BY_ID[id]) || null;
  }, [payload, recId]);

  if (!r) {
    return (
      <div style={{padding:'80px 0',textAlign:'center'}}>
        <div className="ff-mono upper" style={{fontSize:11,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:14}}>RECORDING NOT FOUND</div>
        <Btn variant="secondary" onClick={()=>go('catalog',{tab:'recordings'})}>Back to recordings</Btn>
      </div>
    );
  }

  const [tab, setTab] = _RecPage_useS('overview');
  _RecPage_useE && _RecPage_useE(() => {
    const onSetTab = (e) => { if (e.detail && e.detail.tab) setTab(e.detail.tab); };
    window.addEventListener('astro-rec-set-tab', onSetTab);
    return () => window.removeEventListener('astro-rec-set-tab', onSetTab);
  }, []);
  const [editing, setEditing] = _RecPage_useS(false);
  // Local patches to support inline edits without mutating shared graph data.
  // patchR overrides on the recording, patchE overrides on the synthesized ext.
  const [patchR, setPatchR] = _RecPage_useS({});
  const [patchE, setPatchE] = _RecPage_useS({});
  // Edit history — each entry is a snapshot of {patchR, patchE, label}.
  // cursor points to the next index to be added; undo decrements, redo increments.
  const [history, setHistory] = _RecPage_useS([]);
  const [cursor, setCursor] = _RecPage_useS(0);
  const [historyOpen, setHistoryOpen] = _RecPage_useS(false);
  const pushHistory = (label, nextR, nextE) => {
    setHistory(h => [...h.slice(0, cursor), { label, patchR: nextR, patchE: nextE, ts: Date.now() }]);
    setCursor(c => c + 1);
  };
  const editR = (k, v) => {
    const nextR = { ...patchR, [k]: v };
    setPatchR(nextR);
    pushHistory(`Updated ${k}`, nextR, patchE);
  };
  const editE = (k, v) => {
    const nextE = { ...patchE, [k]: v };
    setPatchE(nextE);
    pushHistory(`Updated ${k}`, patchR, nextE);
  };
  const undo = () => {
    if (cursor <= 0) return;
    const target = cursor - 1;
    const prev = target === 0 ? { patchR: {}, patchE: {} } : history[target - 1];
    setPatchR(prev.patchR);
    setPatchE(prev.patchE);
    setCursor(target);
  };
  const redo = () => {
    if (cursor >= history.length) return;
    const next = history[cursor];
    setPatchR(next.patchR);
    setPatchE(next.patchE);
    setCursor(cursor + 1);
  };
  const revertTo = (idx) => {
    // Revert to state at idx (i.e. drop idx+1 onward). idx === -1 means clean slate.
    if (idx < 0) {
      setPatchR({}); setPatchE({}); setCursor(0);
    } else {
      const target = history[idx];
      setPatchR(target.patchR); setPatchE(target.patchE); setCursor(idx + 1);
    }
    setHistoryOpen(false);
  };
  // Cmd/Ctrl-Z / Cmd-Shift-Z keyboard shortcuts
  React.useEffect(() => {
    if (!editing) return;
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'z') {
        e.preventDefault();
        if (e.shiftKey) redo(); else undo();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [editing, cursor, history.length, patchR, patchE]);
  const toast = (msg, tone='ok') => window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg,tone}}));

  // Linked entities (graph)
  const primaryWork = (typeof WORK_BY_ID !== 'undefined') ? WORK_BY_ID[r.workId] : null;
  const sourceWorks = (r.alsoWorks || []).map(aw => ({...aw, work: WORK_BY_ID?.[aw.workId]})).filter(x=>x.work);
  const derives = (r.derivesFrom || []).map(d => ({...d, parent: d.ofRecordingId ? REC_BY_ID?.[d.ofRecordingId] : null}));
  const releases = (r.releaseIds || []).map(id => REL_BY_ID?.[id]).filter(Boolean);
  const siblings = primaryWork && typeof recordingsForWork === 'function'
    ? recordingsForWork(primaryWork.id).filter(x => x.id !== r.id) : [];

  // Synthesized "DDEX-like" metadata so the page has something to render even
  // when the demo record is sparse. In production these come from the real row.
  const ext = _RecPage_useM(() => {
    const seed = (r.id || r.isrc || '').split('').reduce((a,c)=>a+c.charCodeAt(0),0) % 1000;
    const formats = ['WAV','FLAC','AIFF'];
    const channels = ['Stereo','Stereo','Stereo','Stereo','Atmos · 16-track ADM'];
    return {
      // Track
      versionType: r.versionType || (r.title.toLowerCase().includes('remix') ? 'Remix' : r.title.toLowerCase().includes('live') ? 'Live' : r.title.toLowerCase().includes('acoustic') ? 'Acoustic' : 'Original'),
      versionLabel: r.versionLabel || (r.title.toLowerCase().includes('remix') ? r.title.split('(')[1]?.replace(')','') : ''),
      catalogNumber: r.catalogNumber || `${(r.label||'PLR').replace(/\s/g,'').slice(0,3).toUpperCase()}-${(seed*37).toString().padStart(6,'0').slice(-6)}`,
      genre: r.genre || ['Pop','R&B','Soul','Indie','Electronic','Hip-Hop','Jazz','Folk','Rock','Alternative'][seed % 10],
      subGenre: r.subGenre || ['Bedroom','Neo-Soul','Lo-fi','Boom-bap','Dance','Vocal','Synth','Acoustic'][seed % 8],
      recordingDate: r.recordingDate || (r.year ? `${r.year-1}-${String((seed%9)+3).padStart(2,'0')}-${String((seed%24)+1).padStart(2,'0')}` : ''),
      firstReleaseDate: r.firstReleaseDate || (r.year ? `${r.year}-${String((seed%9)+3).padStart(2,'0')}-${String((seed%24)+1).padStart(2,'0')}` : ''),
      pYear: r.pYear || r.year,
      countryOfRecording: r.countryOfRecording || ['US','GB','FR','SE','JP','CA','AU'][seed%7],
      countryOfMastering: r.countryOfMastering || ['US','US','GB','US','DE'][seed%5],
      language: r.language || 'English',
      parental: r.parental || (r.explicit ? 'Explicit' : 'NotExplicit'),
      recordingType: r.recordingType || (r.title.toLowerCase().includes('live') ? 'Live' : 'Studio'),
      // Audio
      audioFormat: r.audioFormat || formats[seed%3],
      sampleRate: r.sampleRate || ['44.1 kHz','48 kHz','96 kHz'][seed%3],
      bitDepth: r.bitDepth || ['16-bit','24-bit','24-bit','24-bit'][seed%4],
      channels: r.channels || channels[seed%5],
      fileSize: r.fileSize || `${(r.duration*0.176).toFixed(1)} MB`,
      lufs: r.lufs || `-${(8 + (seed%6)).toFixed(1)} LUFS`,
      truePeak: r.truePeak || `-${(0.5 + (seed%9)*0.1).toFixed(1)} dBTP`,
      // Personnel
      contributors: r.contributors || [
        {role:'Producer', name:r.artist + ' (self-prod)', isni:''},
        {role:'Mixer', name:'Tom Elmhirst', isni:'0000000114831056'},
        {role:'Mastering Engineer', name:'Joe LaPorta', isni:'0000000368512941'},
        {role:'Recording Engineer', name:'Riccardo Damian', isni:''},
      ],
      // Rights / ownership
      pLineYear: r.pYear || r.year,
      pLineOwner: r.label,
      marketingLabel: r.label,
      pLineText: r.pLine || `℗ ${r.pYear || r.year} ${r.label}`,
      recordingOwners: r.recordingOwners || [
        {owner: r.label, share: 100, territories: ['World']},
      ],
    };
  }, [r.id]);

  // DSP distribution (mock)
  const dsps = [
    {n:'Spotify',     plays:Math.round(r.plays*0.42*10)/10, share:42},
    {n:'Apple Music', plays:Math.round(r.plays*0.21*10)/10, share:21},
    {n:'YouTube',     plays:Math.round(r.plays*0.18*10)/10, share:18},
    {n:'Amazon',      plays:Math.round(r.plays*0.09*10)/10, share:9},
    {n:'Tidal',       plays:Math.round(r.plays*0.05*10)/10, share:5},
    {n:'Other',       plays:Math.round(r.plays*0.05*10)/10, share:5},
  ];

  // Apply local edit overrides — render-time merge so inline edits show immediately.
  const _r = { ...r, ...patchR };
  const _ext = { ...ext, ...patchE };

  return (
    <div>
      {/* Title block */}
      <div style={{display:'grid',gridTemplateColumns:'1fr 360px',gap:48,alignItems:'end',marginBottom:24}}>
        <div>
          <button onClick={() => go('catalog',{tab:'recordings'})} className="ff-mono upper" style={{fontSize:10,color:'var(--ink-3)',marginBottom:14,display:'inline-flex',alignItems:'center',gap:6}}>
            <Ic.Left width={12} height={12}/> Catalog / Recordings
          </button>
          <div className="ff-mono upper" style={{fontSize:10,color:'var(--ink-3)',marginBottom:8}}>RECORDING · {editing
            ? <InlineText value={_r.isrc} onCommit={(v)=>{ editR('isrc', v); toast('ISRC updated'); }} style={{fontSize:10,fontFamily:'inherit',letterSpacing:'.14em',display:'inline-block'}}/>
            : _r.isrc}</div>
          {editing
            ? <InlineText as="h1" value={_r.title}
                onCommit={(v)=>{ editR('title', v); toast('Title updated'); }}
                className="heading-swap ff-display"
                style={{fontSize:'clamp(48px,7vw,108px)',fontWeight:700,letterSpacing:'-0.05em',lineHeight:0.9,margin:0,fontFamily:'inherit',display:'block'}}/>
            : <h1 className="heading-swap ff-display" style={{fontSize:'clamp(48px,7vw,108px)',fontWeight:700,letterSpacing:'-0.05em',lineHeight:0.9,margin:0}}>{_r.title}</h1>}

          {/* Performer credit line — primary artist + featured */}
          <div style={{marginTop:16,fontSize:18,color:'var(--ink)',fontWeight:500,letterSpacing:'-0.005em'}}>
            {editing
              ? <InlineText value={_r.artist} onCommit={(v)=>{ editR('artist', v); toast('Artist updated'); }} style={{fontSize:18,fontFamily:'inherit',fontWeight:500,display:'inline-block'}}/>
              : _r.artist}
          </div>

          {/* Meta strip — duration, ISRC, year, version */}
          <div className="ff-mono" style={{display:'flex',flexWrap:'wrap',alignItems:'baseline',gap:'4px 22px',marginTop:18,fontSize:12,color:'var(--ink-2)'}}>
            <span><span className="upper" style={{fontSize:9,letterSpacing:'.14em',color:'var(--ink-3)',marginRight:8}}>VERSION</span>{editing
              ? <InlineSelect value={_ext.versionType} options={['Original','Remix','Live','Acoustic','Demo','Edit','Extended','Instrumental','Radio Edit','Remaster']}
                  onCommit={(v)=>editE('versionType', v)} style={{fontSize:12}}/>
              : _ext.versionType}{_ext.versionLabel ? ` — ${_ext.versionLabel}` : ''}</span>
            <span><span className="upper" style={{fontSize:9,letterSpacing:'.14em',color:'var(--ink-3)',marginRight:8}}>DURATION</span><span className="num">{Math.floor(_r.duration/60)}:{String(_r.duration%60).padStart(2,'0')}</span></span>
            <span><span className="upper" style={{fontSize:9,letterSpacing:'.14em',color:'var(--ink-3)',marginRight:8}}>RECORDED</span>{editing
              ? <><InlineSelect value={_ext.countryOfRecording} options={['US','GB','FR','SE','JP','CA','AU','DE','NL','IE','IT','BR','MX','ES']} onCommit={(v)=>editE('countryOfRecording', v)} style={{fontSize:12}}/> · <InlineText value={_ext.recordingDate} onCommit={(v)=>editE('recordingDate', v)} placeholder="YYYY-MM-DD" style={{fontSize:12,fontFamily:'inherit',display:'inline-block'}}/></>
              : <>{_ext.countryOfRecording} · {formatDate(_ext.recordingDate)}</>}</span>
            <span><span className="upper" style={{fontSize:9,letterSpacing:'.14em',color:'var(--ink-3)',marginRight:8}}>RELEASED</span>{editing
              ? <DateField value={_ext.firstReleaseDate} editing={true} onChange={(v)=>editE('firstReleaseDate', v)} size="sm" style={{display:'inline-block',height:22,fontSize:12}}/>
              : formatDate(_ext.firstReleaseDate)}</span>
          </div>

          <div style={{display:'flex',gap:10,marginTop:18,flexWrap:'wrap',alignItems:'center'}}>
            <Pill tone="ok" dot>delivered</Pill>
            <Pill>{_ext.audioFormat} · {_ext.sampleRate} · {_ext.bitDepth}</Pill>
            <Pill>{_ext.channels}</Pill>
            <Pill tone="soft">{editing
              ? <InlineSelect value={_ext.genre} options={(typeof refGenreLabels==='function'?refGenreLabels():['Pop','R&B','Soul','Indie','Electronic','Hip-Hop','Jazz','Folk','Rock','Alternative'])} onCommit={(v)=>editE('genre', v)} style={{fontSize:11}}/>
              : _ext.genre}{_ext.subGenre ? <> · {editing
              ? <InlineText value={_ext.subGenre} onCommit={(v)=>editE('subGenre', v)} style={{fontSize:11,fontFamily:'inherit',display:'inline-block'}}/>
              : _ext.subGenre}</> : ''}</Pill>
            {_ext.parental === 'Explicit' && <Pill tone="danger">explicit</Pill>}
            <Pill tone="soft">{editing
              ? <InlineText value={_r.label} onCommit={(v)=>editR('label', v)} style={{fontSize:11,fontFamily:'inherit',display:'inline-block'}}/>
              : _r.label}</Pill>
            <Pill tone="soft">{(_r.plays||0).toLocaleString('en-US',{maximumFractionDigits:1})}M plays</Pill>
          </div>
        </div>
        <div style={{display:'flex',flexDirection:'column',gap:10,alignItems:'flex-end'}}>
          <Btn variant="accent" icon={<Ic.Send/>} onClick={()=>toast(`DDEX ERN draft created for "${_r.title}"`)}>Generate DDEX</Btn>
          <Btn variant="secondary" icon={<Ic.Play/>} onClick={()=>toast(`Preview opened for ${_r.title}`)}>Preview audio</Btn>
          {editing
            ? <Btn variant="primary" icon={<Ic.Check/>} onClick={()=>{ setEditing(false); toast('Changes saved'); }}>Done editing</Btn>
            : <Btn variant="ghost" icon={<Ic.Edit/>} onClick={()=>setEditing(true)}>Edit Recording</Btn>}
          <Btn variant="ghost" icon={<Ic.Ext/>} onClick={()=>toast('Public page opened in new tab')}>Public page</Btn>
          {window.DetailDelete && (
            <window.DetailDelete kind="recording" record={_r} title={_r.title} subtitle={_r.isrc || _r.id} onDeleted={() => go && go('catalog', { tab: 'recordings' })} />
          )}
        </div>
      </div>

      {/* Edit-mode floating pill (bottom-right). Quiet, out of layout. */}
      {editing &&
      <div style={{
        position:'fixed',bottom:24,right:24,zIndex:60,
        display:'flex',alignItems:'center',gap:0,
        background:'var(--ink)',color:'var(--bg)',
        boxShadow:'0 6px 24px rgba(0,0,0,.18)',
        borderRadius:999,padding:'4px 4px 4px 14px'}}>
          <span style={{width:6,height:6,background:'oklch(0.78 0.16 140)',borderRadius:'50%',display:'inline-block',marginRight:8,boxShadow:'0 0 0 3px oklch(0.78 0.16 140 / .25)'}}/>
          <span className="ff-mono upper" style={{fontSize:10,letterSpacing:'.12em',fontWeight:600,marginRight:14}}>EDITING · AUTO-SAVE</span>
          <button onClick={undo} disabled={cursor <= 0} title="Undo (⌘Z)"
            style={{width:28,height:28,display:'inline-flex',alignItems:'center',justifyContent:'center',
              background:'transparent',border:0,color:cursor<=0?'oklch(1 0 0 / .35)':'var(--bg)',
              cursor:cursor<=0?'default':'pointer',fontSize:14,borderRadius:999}}>↶</button>
          <button onClick={redo} disabled={cursor >= history.length} title="Redo (⌘⇧Z)"
            style={{width:28,height:28,display:'inline-flex',alignItems:'center',justifyContent:'center',
              background:'transparent',border:0,color:cursor>=history.length?'oklch(1 0 0 / .35)':'var(--bg)',
              cursor:cursor>=history.length?'default':'pointer',fontSize:14,borderRadius:999}}>↷</button>
          <button onClick={() => setHistoryOpen(true)} title="Edit history"
            className="ff-mono" style={{fontSize:10,padding:'6px 10px',background:'oklch(1 0 0 / .12)',border:0,color:'var(--bg)',cursor:'pointer',borderRadius:999,marginLeft:4,fontWeight:600,letterSpacing:'.04em'}}>
            {history.length} change{history.length===1?'':'s'}
          </button>
          <button onClick={() => { setEditing(false); toast('Changes saved'); }} title="Done editing"
            className="ff-mono upper" style={{fontSize:10,letterSpacing:'.1em',padding:'6px 12px',background:'var(--bg)',border:0,color:'var(--ink)',cursor:'pointer',borderRadius:999,marginLeft:6,fontWeight:600}}>
            DONE
          </button>
        </div>
      }

      {/* Edit history modal */}
      {historyOpen &&
      <div onClick={()=>setHistoryOpen(false)} style={{position:'fixed',inset:0,background:'rgba(0,0,0,.32)',zIndex:70,display:'flex',alignItems:'center',justifyContent:'center'}}>
        <div onClick={e=>e.stopPropagation()} style={{background:'var(--bg)',width:480,maxHeight:'70vh',display:'flex',flexDirection:'column',border:'1px solid var(--ink)',boxShadow:'0 20px 60px rgba(0,0,0,.25)'}}>
          <div style={{padding:'18px 22px',borderBottom:'1px solid var(--rule)',display:'flex',alignItems:'center',justifyContent:'space-between'}}>
            <div>
              <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:4}}>EDIT HISTORY</div>
              <div className="ff-display" style={{fontSize:20,fontWeight:700,letterSpacing:'-0.01em'}}>{history.length} change{history.length===1?'':'s'}</div>
            </div>
            <button onClick={()=>setHistoryOpen(false)} aria-label="Close" style={{width:30,height:30,background:'transparent',border:'1px solid var(--rule)',cursor:'pointer',fontSize:16,lineHeight:1}}>×</button>
          </div>
          <div style={{flex:1,overflowY:'auto',padding:'8px 0'}}>
            {history.length === 0 &&
              <div className="ff-mono" style={{padding:'24px 22px',color:'var(--ink-3)',fontSize:12,fontStyle:'italic'}}>No changes yet.</div>
            }
            {history.map((h, i) => {
              const isCurrent = i === cursor - 1;
              const isFuture = i >= cursor;
              return (
                <div key={i} style={{
                  display:'grid',gridTemplateColumns:'24px 1fr auto',gap:10,alignItems:'center',
                  padding:'10px 22px',borderBottom:'1px solid var(--rule-soft)',
                  opacity:isFuture?0.4:1, background:isCurrent?'var(--bg-2)':'transparent'}}>
                  <span className="ff-mono num" style={{fontSize:11,color:'var(--ink-3)'}}>{String(i+1).padStart(2,'0')}</span>
                  <div>
                    <div style={{fontSize:13,fontWeight:500}}>{h.label}</div>
                    <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)'}}>{new Date(h.ts).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit', second:'2-digit'})}</div>
                  </div>
                  {!isCurrent &&
                    <button onClick={()=>revertTo(i)} className="ff-mono upper" style={{fontSize:10,letterSpacing:'.08em',padding:'4px 10px',background:'transparent',border:'1px solid var(--rule)',cursor:'pointer',color:'var(--ink-2)'}}>REVERT</button>
                  }
                  {isCurrent &&
                    <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)'}}>CURRENT</span>
                  }
                </div>
              );
            })}
          </div>
          {history.length > 0 &&
            <div style={{padding:'12px 22px',borderTop:'1px solid var(--rule)',background:'var(--bg-2)',display:'flex',justifyContent:'space-between',alignItems:'center'}}>
              <span className="ff-mono" style={{fontSize:11,color:'var(--ink-3)'}}>Cursor at change {cursor} / {history.length}</span>
              <button onClick={()=>revertTo(-1)} className="ff-mono upper" style={{fontSize:10,letterSpacing:'.08em',padding:'6px 12px',background:'transparent',border:'1px solid var(--rule)',cursor:'pointer',color:'var(--ink-2)'}}>DISCARD ALL</button>
            </div>
          }
        </div>
      </div>
      }

      {/* Mini hero strip — album art + waveform peek */}
      <RecHero r={_r} editing={editing}/>

      {/* Tabs */}
      <div style={{borderBottom:'1px solid var(--rule)',display:'flex',gap:0,marginBottom:24,marginTop:0}}>
        {['overview','audio','platforms','contributors','rights','releases','distribution','earnings','predict','activity'].map(t => (
          <button key={t} onClick={()=>setTab(t)} className="ff-mono upper"
            style={{padding:'14px 18px',fontSize:11,fontWeight:600,letterSpacing:'.08em',
              borderBottom: tab===t ? '3px solid var(--ink)' : '3px solid transparent',
              marginBottom:-1, color: tab===t ? 'var(--ink)' : 'var(--ink-3)'}}>{t}</button>
        ))}
      </div>

      {tab === 'overview' && <RecOverview r={_r} ext={_ext} primaryWork={primaryWork} sourceWorks={sourceWorks} derives={derives} siblings={siblings} releases={releases} go={go} setTab={setTab} editing={editing} editE={editE}/>}
      {tab === 'audio' && <RecAudio r={_r} ext={_ext} editing={editing} editE={editE}/>}
      {tab === 'platforms' && <RecPlatforms r={_r} ext={_ext}/>}
      {tab === 'contributors' && <RecContributors r={_r} ext={_ext} editing={editing} primaryWork={primaryWork} sourceWorks={sourceWorks} go={go}/>}
      {tab === 'rights' && <RecRights r={_r} ext={_ext} editing={editing} editE={editE}/>}
      {tab === 'releases' && <RecReleases r={_r} releases={releases} go={go}/>}
      {tab === 'distribution' && <RecDistribution r={_r} dsps={dsps}/>}
      {tab === 'earnings' && <RecEarnings r={_r} dsps={dsps} ext={ext} go={go} setTab={setTab}/>}
      {tab === 'predict' && (window.PredictScorecard
        ? <window.PredictScorecard rec={_r} embed/>
        : <div style={{padding:24,color:'var(--ink-3)'}}>Prediction engine offline.</div>)}
      {tab === 'activity' && <RecActivity r={_r} go={go} setTab={setTab}/>}
    </div>
  );
}

// ─────────────────────────────── Hero strip
// Library model — keyed by recording id. Each entry: { id, name, url, kind, addedAt, available }
// `kind` = 'demo' | 'upload'
// blob URLs only live in this in-memory store; localStorage holds names + ids only,
// so after reload uploaded entries appear with available=false until re-uploaded.
window._REC_AUDIO_LIB = window._REC_AUDIO_LIB || {};

function getRecLibrary(recId) {
  if (!window._REC_AUDIO_LIB[recId]) {
    // Build initial library: demo + any persisted entries (without urls)
    const demo = {
      id: 'demo',
      name: 'Sine demo · synthesized',
      kind: 'demo',
      url: null, // populated lazily
      addedAt: 0,
      available: true,
    };
    let persisted = [];
    try {
      const meta = JSON.parse(localStorage.getItem('astro:rec:lib:'+recId) || '[]');
      persisted = meta.map(m => ({ ...m, url: null, available: false }));
    } catch {}
    window._REC_AUDIO_LIB[recId] = [demo, ...persisted];
  }
  return window._REC_AUDIO_LIB[recId];
}

function ensureDemoUrl(recId, durSec) {
  const lib = getRecLibrary(recId);
  const demo = lib.find(e => e.kind === 'demo');
  if (demo && !demo.url) {
    const seed = (recId || 'x').split('').reduce((s,c)=>s+c.charCodeAt(0), 0) + 13;
    const blob = window.makeDemoToneWav(seed, Math.max(durSec||30, 12));
    demo.url = URL.createObjectURL(blob);
  }
  return demo ? demo.url : null;
}

function persistRecLibMeta(recId) {
  const lib = getRecLibrary(recId);
  const persistable = lib
    .filter(e => e.kind !== 'demo')
    .map(e => ({ id: e.id, name: e.name, kind: e.kind, addedAt: e.addedAt }));
  try { localStorage.setItem('astro:rec:lib:'+recId, JSON.stringify(persistable)); } catch {}
}

function notifyRecLibChange(recId) {
  window.dispatchEvent(new CustomEvent('astro-rec-lib-change', { detail: { recId } }));
}

function addAudioToRecLib(recId, file) {
  const lib = getRecLibrary(recId);
  const url = URL.createObjectURL(file);
  const entry = {
    id: 'u_' + Date.now() + '_' + Math.random().toString(36).slice(2,7),
    name: file.name,
    kind: 'upload',
    url,
    addedAt: Date.now(),
    available: true,
  };
  lib.push(entry);
  persistRecLibMeta(recId);
  notifyRecLibChange(recId);
  return entry;
}

function removeAudioFromRecLib(recId, entryId) {
  const lib = getRecLibrary(recId);
  const idx = lib.findIndex(e => e.id === entryId);
  if (idx >= 0 && lib[idx].kind !== 'demo') {
    if (lib[idx].url) try { URL.revokeObjectURL(lib[idx].url); } catch {}
    lib.splice(idx, 1);
    persistRecLibMeta(recId);
    notifyRecLibChange(recId);
  }
}

function useRecLibrary(recId, durSec) {
  const [, force] = React.useState(0);
  React.useEffect(() => {
    ensureDemoUrl(recId, durSec);
    const onChange = (e) => { if (e.detail.recId === recId) force(x => x + 1); };
    window.addEventListener('astro-rec-lib-change', onChange);
    return () => window.removeEventListener('astro-rec-lib-change', onChange);
  }, [recId, durSec]);
  return getRecLibrary(recId);
}

function RecHero({ r, editing }) {
  const audioRef = React.useRef(null);
  const imgFileRef = React.useRef(null);
  const rafRef = React.useRef(0);
  const [playing, setPlaying] = React.useState(false);
  const [time, setTime] = React.useState(0);
  const [duration, setDuration] = React.useState(0);
  const [activeId, setActiveId] = React.useState('demo');
  const [coverUrl, setCoverUrl] = React.useState(null);
  const [coverName, setCoverName] = React.useState(null);
  const [sourceMenuOpen, setSourceMenuOpen] = React.useState(false);

  const library = useRecLibrary(r.id, r.duration);
  const activeEntry = library.find(e => e.id === activeId && e.available) || library.find(e => e.kind === 'demo');
  const src = activeEntry && activeEntry.url ? { url: activeEntry.url, label: activeEntry.name, kind: activeEntry.kind, filename: activeEntry.name } : null;
  const peaks = useAudioPeaks(src ? src.url : null);

  const mediaKey = 'astro:rec:media:' + (r.id || 'unknown');

  // Restore cover
  React.useEffect(() => {
    setPlaying(false); setTime(0); setActiveId('demo');
    try {
      const meta = JSON.parse(localStorage.getItem(mediaKey) || 'null');
      if (meta && meta.coverName) setCoverName(meta.coverName);
    } catch {}
  }, [r.id]);

  // Audio events
  React.useEffect(() => {
    const a = audioRef.current; if (!a) return;
    const onDur = () => setDuration(a.duration||0);
    const onEnd = () => { setPlaying(false); setTime(0); };
    const onPlay = () => setPlaying(true);
    const onPause = () => setPlaying(false);
    a.addEventListener('loadedmetadata', onDur);
    a.addEventListener('ended', onEnd);
    a.addEventListener('play', onPlay);
    a.addEventListener('pause', onPause);
    return () => {
      a.removeEventListener('loadedmetadata', onDur);
      a.removeEventListener('ended', onEnd);
      a.removeEventListener('play', onPlay);
      a.removeEventListener('pause', onPause);
    };
  }, [src && src.url]);

  // rAF for smooth progress
  React.useEffect(() => {
    if (!playing) { cancelAnimationFrame(rafRef.current); return; }
    const tick = () => {
      const a = audioRef.current; if (a) setTime(a.currentTime);
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [playing]);

  // Listen for "play this entry" requests from the Audio tab
  React.useEffect(() => {
    const onSelect = (e) => {
      if (e.detail && e.detail.recId === r.id && e.detail.entryId) {
        setActiveId(e.detail.entryId);
        setPlaying(false); setTime(0);
      }
    };
    window.addEventListener('astro-rec-select-audio', onSelect);
    return () => window.removeEventListener('astro-rec-select-audio', onSelect);
  }, [r.id]);

  const togglePlay = (e) => {
    e && e.stopPropagation();
    const a = audioRef.current; if (!a) return;
    if (a.paused) a.play().catch(()=>{}); else a.pause();
  };

  const onSeek = (e) => {
    const a = audioRef.current; if (!a || !duration) return;
    const rect = e.currentTarget.getBoundingClientRect();
    const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
    a.currentTime = pct * duration;
    setTime(a.currentTime);
  };

  const onUploadImage = (e) => {
    const file = e.target.files && e.target.files[0]; if (!file) return;
    const url = URL.createObjectURL(file);
    setCoverUrl(url); setCoverName(file.name);
    try { const meta = JSON.parse(localStorage.getItem(mediaKey)||'{}'); meta.coverName = file.name; localStorage.setItem(mediaKey, JSON.stringify(meta)); } catch {}
    window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:`Cover "${file.name}" uploaded`,tone:'ok'}}));
    e.target.value = '';
  };

  const fmt = (s) => { if (!isFinite(s)||s<0) s=0; return `${Math.floor(s/60)}:${String(Math.floor(s%60)).padStart(2,'0')}`; };
  const totalDur = duration || r.duration || 0;
  const progress = totalDur>0 ? time/totalDur : 0;

  // Close menu on outside click
  React.useEffect(() => {
    if (!sourceMenuOpen) return;
    const onDoc = (e) => {
      if (!e.target.closest('[data-source-menu]')) setSourceMenuOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [sourceMenuOpen]);

  const switchTo = (entryId) => {
    setActiveId(entryId);
    setPlaying(false); setTime(0);
    setSourceMenuOpen(false);
  };

  const goToAudioTab = () => {
    window.dispatchEvent(new CustomEvent('astro-rec-set-tab', { detail: { tab: 'audio' } }));
    setSourceMenuOpen(false);
  };

  return (
    <div style={{display:'grid',gridTemplateColumns:'180px 1fr',gap:24,padding:'18px 0 24px',borderTop:'1px solid var(--rule)',borderBottom:'1px solid var(--rule)',marginBottom:24}}>
      <audio ref={audioRef} src={src ? src.url : null} preload="metadata"/>
      <input ref={imgFileRef} type="file" accept="image/*" style={{display:'none'}} onChange={onUploadImage}/>

      {/* Album art */}
      <button
        onClick={(e)=>{
          if (editing) { imgFileRef.current && imgFileRef.current.click(); }
          else { togglePlay(e); }
        }}
        title={editing ? (coverUrl ? 'Replace cover image' : 'Upload cover image') : (playing ? 'Pause' : 'Play')}
        style={{width:180,height:180,padding:0,border:0,cursor:'pointer',position:'relative',overflow:'hidden',
          background: coverUrl ? '#000' : r.art,
          display:'flex',alignItems:'center',justifyContent:'center'}}
        onMouseEnter={e=>{ const o=e.currentTarget.querySelector('[data-cover-overlay]'); if(o) o.style.opacity='1'; }}
        onMouseLeave={e=>{ const o=e.currentTarget.querySelector('[data-cover-overlay]'); if(o) o.style.opacity = (!editing && playing) ? '1' : '0'; }}>
        {coverUrl
          ? <img src={coverUrl} alt={coverName||'Cover'} style={{width:'100%',height:'100%',objectFit:'cover',display:'block'}}/>
          : <Ic.Disc width={72} height={72} style={{color:'rgba(0,0,0,.45)'}}/>}
        <div data-cover-overlay style={{position:'absolute',inset:0,background:'rgba(0,0,0,.55)',color:'#fff',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',gap:6,opacity: (!editing && playing) ? 1 : 0,transition:'opacity .15s',pointerEvents:'none'}}>
          {editing ? (
            <>
              <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6"><path d="M12 16V4M5 11l7-7 7 7M4 20h16"/></svg>
              <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.14em'}}>{coverUrl ? 'Replace cover' : 'Upload cover'}</div>
              <div className="ff-mono" style={{fontSize:9,color:'rgba(255,255,255,.7)'}}>JPG · PNG · WEBP</div>
            </>
          ) : (
            <span style={{width:48,height:48,borderRadius:'50%',background:'rgba(255,255,255,.92)',display:'inline-flex',alignItems:'center',justifyContent:'center'}}>
              {playing
                ? <svg width="16" height="16" viewBox="0 0 18 18" fill="#0b0b0b"><rect x="3" y="2" width="4" height="14"/><rect x="11" y="2" width="4" height="14"/></svg>
                : <Ic.Play width={18} height={18} style={{color:'#0b0b0b',marginLeft:2}}/>}
            </span>
          )}
        </div>
      </button>

      {/* Waveform area */}
      <div style={{display:'flex',flexDirection:'column',justifyContent:'center',minWidth:0}}>
        <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.14em',color:'var(--ink-3)',marginBottom:8,display:'flex',justifyContent:'space-between',alignItems:'center',gap:12}}>
          <span style={{minWidth:0,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>
            WAVEFORM · {fmt(totalDur)} · {activeEntry ? (activeEntry.kind==='demo' ? 'SINE DEMO' : (activeEntry.available ? 'UPLOADED · '+activeEntry.name : 'UNAVAILABLE')) : '—'}{peaks ? ' · ANALYZED' : (src ? ' · DECODING…' : '')}
          </span>

          {/* Source switcher */}
          <div data-source-menu style={{position:'relative',flex:'0 0 auto'}}>
            <button onClick={()=>setSourceMenuOpen(o=>!o)} className="ff-mono upper"
              style={{fontSize:9,letterSpacing:'.14em',padding:'5px 10px',background:'var(--bg)',border:'1px solid var(--ink)',cursor:'pointer',color:'var(--ink)',display:'inline-flex',alignItems:'center',gap:6}}>
              SOURCE · {library.filter(e=>e.available).length}
              <svg width="9" height="9" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.6"><path d="M2 4 L6 8 L10 4"/></svg>
            </button>
            {sourceMenuOpen && (
              <div style={{position:'absolute',right:0,top:'100%',marginTop:4,minWidth:280,maxWidth:380,background:'var(--bg)',border:'1px solid var(--ink)',boxShadow:'0 4px 16px rgba(0,0,0,.15)',zIndex:30,padding:6}}>
                {library.map(e => {
                  const isActive = e.id === activeEntry?.id;
                  return (
                    <button key={e.id}
                      onClick={() => e.available && switchTo(e.id)}
                      disabled={!e.available}
                      style={{display:'flex',alignItems:'center',gap:10,width:'100%',padding:'8px 10px',background: isActive ? 'var(--bg-2)' : 'transparent',border:0,cursor: e.available ? 'pointer' : 'not-allowed',textAlign:'left',opacity: e.available ? 1 : 0.5}}>
                      <span style={{width:18,display:'inline-flex',justifyContent:'center'}}>
                        {isActive ? <span style={{width:6,height:6,borderRadius:'50%',background:'var(--accent)'}}/> : <Ic.Wave width={12} height={12} style={{color:'var(--ink-3)'}}/>}
                      </span>
                      <div style={{flex:1,minWidth:0}}>
                        <div style={{fontSize:12,fontWeight:500,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{e.name}</div>
                        <div className="ff-mono upper" style={{fontSize:8,letterSpacing:'.12em',color:'var(--ink-3)',marginTop:2}}>
                          {e.kind === 'demo' ? 'SYNTHESIZED' : (e.available ? 'UPLOADED' : 'NEEDS RE-UPLOAD')}
                        </div>
                      </div>
                    </button>
                  );
                })}
                <div style={{borderTop:'1px solid var(--rule)',marginTop:4,paddingTop:4}}>
                  <button onClick={goToAudioTab} className="ff-mono upper"
                    style={{display:'flex',alignItems:'center',gap:8,width:'100%',padding:'8px 10px',background:'transparent',border:0,cursor:'pointer',fontSize:9,letterSpacing:'.12em',color:'var(--ink-2)'}}>
                    <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 16V4M5 11l7-7 7 7M4 20h16"/></svg>
                    Manage in Audio tab
                  </button>
                </div>
              </div>
            )}
          </div>
        </div>

        <div style={{display:'flex',alignItems:'center',gap:12}}>
          <div onClick={onSeek} style={{flex:'1 1 0',minWidth:0,position:'relative',cursor:'pointer',height:72,overflow:'hidden'}}>
            <Waveform h={72} responsive bars={200} seed={(r.id || 'x').split('').reduce((s,c)=>s+c.charCodeAt(0),0)+13} peaks={peaks} progress={progress} color="var(--ink-2)"/>
            <div style={{position:'absolute',top:0,bottom:0,left:`${progress*100}%`,width:1.5,background:'var(--accent, #c0392b)',pointerEvents:'none'}}/>
          </div>

          <div className="ff-mono num" style={{fontSize:13,minWidth:90,textAlign:'right',flex:'0 0 auto'}}>{fmt(time)} / {fmt(totalDur)}</div>
        </div>

        <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',marginTop:10,display:'flex',justifyContent:'space-between'}}>
          <span>0:00</span>
          <span>{r.format || 'FLAC 24-bit · 96 kHz'} · master delivered {r.masterDate || 'Mar 2024'}</span>
          <span>{fmt(totalDur)}</span>
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────── Overview tab
function RecOverview({ r, ext, primaryWork, sourceWorks, derives, siblings, releases, go, setTab, editing, editE }) {
  return (
    <div style={{display:'grid',gridTemplateColumns:'1.4fr 1fr',gap:32}}>
      <div>
        <Section num="01">Underlying work</Section>
        {primaryWork ? (
          <button onClick={()=>go('work', primaryWork)}
            style={{display:'flex',alignItems:'center',gap:14,width:'100%',padding:'14px 16px',border:'1px solid var(--rule)',background:'transparent',cursor:'pointer',textAlign:'left'}}>
            <span style={{width:8,height:48,background:'var(--accent)'}}/>
            <div style={{flex:1,minWidth:0}}>
              <div style={{fontSize:15,fontWeight:600}}>{primaryWork.title}</div>
              <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',marginTop:3}}>ISWC {window.iswcDisplay ? window.iswcDisplay(primaryWork.iswc) : primaryWork.iswc} · {primaryWork.pro} · shares {primaryWork.shares} · {primaryWork.writers.join(', ')}</div>
            </div>
            <Ic.Right width={14} height={14} style={{color:'var(--ink-3)'}}/>
          </button>
        ) : (
          <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',padding:'10px 0'}}>No underlying work matched. <button onClick={()=>window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:'Match-to-work flow opened',tone:'ok'}}))} style={{textDecoration:'underline',background:'transparent',border:0,padding:0,fontSize:11,fontFamily:'inherit',cursor:'pointer',color:'var(--ink-2)'}}>Match manually</button></div>
        )}

        {sourceWorks.length > 0 && (
          <>
            <Section num="02">Also incorporates · {sourceWorks.length} source work{sourceWorks.length>1?'s':''}</Section>
            {sourceWorks.map((sw,i)=>(
              <button key={i} onClick={()=>go('work', sw.work)}
                style={{display:'grid',gridTemplateColumns:'8px 1fr 90px 80px 14px',alignItems:'center',gap:12,
                  width:'100%',padding:'12px 14px',marginBottom:8,
                  border:'1px dashed var(--rule)',background:'transparent',cursor:'pointer',textAlign:'left'}}>
                <span style={{width:8,height:36,background:'#9b6a18'}}/>
                <div style={{minWidth:0}}>
                  <div style={{fontSize:13,fontWeight:500}}>{sw.work.title}</div>
                  <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2}}>ISWC {window.iswcDisplay ? window.iswcDisplay(sw.work.iswc) : sw.work.iswc} · {sw.work.writers.join(', ')}</div>
                </div>
                <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'#9b6a18',fontWeight:600,textAlign:'right'}}>{sw.type}</span>
                <span className="ff-mono num" style={{fontSize:12,textAlign:'right'}}>{sw.share}%</span>
                <Ic.Right width={12} height={12} style={{color:'var(--ink-3)'}}/>
              </button>
            ))}
          </>
        )}

        {derives.length > 0 && (
          <>
            <Section num={sourceWorks.length>0?'03':'02'}>Derives from</Section>
            {derives.map((d,i)=>{
              if (d.parent) return (
                <button key={i} onClick={()=>go('recording',{id:d.parent.id})}
                  style={{display:'grid',gridTemplateColumns:'48px 1fr 90px 14px',alignItems:'center',gap:12,
                    width:'100%',padding:'10px 12px',marginBottom:8,
                    border:'1px solid var(--rule)',background:'transparent',cursor:'pointer',textAlign:'left'}}>
                  <div style={{width:42,height:42,background:d.parent.art,position:'relative'}}>
                    <Ic.Disc width={16} height={16} style={{position:'absolute',top:13,left:13,color:'rgba(0,0,0,.55)'}}/>
                  </div>
                  <div style={{minWidth:0}}>
                    <div style={{fontSize:13,fontWeight:500}}>{d.parent.title}</div>
                    <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2}}>{d.parent.artist} · {d.parent.year} · ISRC {(window.isrcDisplay ? window.isrcDisplay(d.parent.isrc) : d.parent.isrc)}</div>
                  </div>
                  <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'#7d3da8',fontWeight:600,textAlign:'right'}}>{d.kind}</span>
                  <Ic.Right width={12} height={12} style={{color:'var(--ink-3)'}}/>
                </button>
              );
              return (
                <div key={i} className="ff-mono" style={{fontSize:11,color:'var(--ink-2)',padding:'8px 12px',border:'1px dashed var(--rule)',marginBottom:6}}>
                  {d.kind.toUpperCase()} · external source (cleared off-platform)
                </div>
              );
            })}
          </>
        )}

        <Section num={['02','03','04'][[sourceWorks.length>0,derives.length>0].filter(Boolean).length]}>Identifiers</Section>
        <div className="ff-mono" style={{fontSize:12,display:'grid',gridTemplateColumns:'160px 1fr',rowGap:10,alignItems:'center'}}>
          <span style={{color:'var(--ink-3)'}}>ISRC</span><span>{(window.isrcDisplay ? window.isrcDisplay(r.isrc) : r.isrc)}{window.IdChip && r.isrc ? <span style={{marginLeft:8,verticalAlign:'middle'}}><window.IdChip kind="isrc" value={r.isrc} compact/></span> : null}</span>
          <span style={{color:'var(--ink-3)'}}>Catalog #</span><span>{ext.catalogNumber}</span>
          <span style={{color:'var(--ink-3)'}}>ASTRO ID</span><span>{r.id}</span>
          {r.upc && (<><span style={{color:'var(--ink-3)'}}>UPC (release)</span><span>{r.upc}</span></>)}
          <span style={{color:'var(--ink-3)'}}>P-line</span><span>{ext.pLineText}</span>
          <span style={{color:'var(--ink-3)'}}>Recording type</span><span>{ext.recordingType}</span>
          <span style={{color:'var(--ink-3)'}}>Country of recording</span><span>{ext.countryOfRecording}</span>
          <span style={{color:'var(--ink-3)'}}>Country of mastering</span><span>{ext.countryOfMastering}</span>
          <span style={{color:'var(--ink-3)'}}>Performance language</span>
          <span>{editing
            ? <InlineSelect value={(() => {
                const REF = window.REF;
                const v = ext.language;
                if (REF && REF.ready && v && v.length === 2 && REF.languageByIso1.has(v.toLowerCase())) {
                  return REF.languageByIso1.get(v.toLowerCase()).name;
                }
                return v || 'English';
              })()}
              options={refLanguageOptions()}
              onCommit={(v)=>editE && editE('language', v)}
              style={{fontSize:12}}/>
            : (() => {
                const REF = window.REF;
                const v = ext.language;
                if (!v) return '—';
                if (REF && REF.ready && v.length === 2 && REF.languageByIso1.has(v.toLowerCase())) {
                  return REF.languageByIso1.get(v.toLowerCase()).name;
                }
                return v;
              })()}</span>
          <span style={{color:'var(--ink-3)'}}>Parental advisory</span><span>{ext.parental}</span>
        </div>
      </div>

      {/* Sidebar */}
      <div>
        <Section num="·">At a glance</Section>
        <button onClick={()=>go('analytics',{focus:r.id})}
          onMouseEnter={e=>{ e.currentTarget.style.background='var(--bg-2)'; e.currentTarget.style.borderColor='var(--ink)'; }}
          onMouseLeave={e=>{ e.currentTarget.style.background='transparent'; e.currentTarget.style.borderColor='var(--rule)'; }}
          style={{display:'block',width:'100%',textAlign:'left',border:'1px solid var(--rule)',padding:14,marginBottom:24,background:'transparent',cursor:'pointer',transition:'all .12s'}}>
          <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:10,display:'flex',justifyContent:'space-between',alignItems:'center'}}>
            <span>30-DAY STREAMS</span>
            <span style={{display:'inline-flex',alignItems:'center',gap:4,color:'var(--ink-2)'}}>open in analytics <Ic.Right width={10} height={10}/></span>
          </div>
          <div className="ff-mono num" style={{fontSize:32,fontWeight:600,letterSpacing:'-0.02em'}}>{(r.plays||0).toLocaleString('en-US',{maximumFractionDigits:1})}M</div>
          <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:4}}>across {dsps_count(r)} platforms · ISRC validated</div>
          <div style={{marginTop:14,paddingTop:12,borderTop:'1px solid var(--rule-soft)'}}>
            <Spark width={260} height={36} data={[3,5,4,6,8,7,9,11,10,12,14,13,15,16,17,15,18,17,19,21,20,22,21,23,22,24,23,25,24,26]}/>
            <div className="ff-mono upper" style={{display:'flex',justifyContent:'space-between',fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginTop:6}}>
              <span>30D AGO</span><span style={{color:'#5b8a3a'}}>+18.2%</span><span>TODAY</span>
            </div>
          </div>
        </button>

        {window.RecordingVideosBlock ? (
          <window.RecordingVideosBlock recordingTitle={r.title} recordingId={r.id} />
        ) : null}

        {releases.length > 0 && (
          <>
            <Section num="·" action={<button onClick={()=>setTab('releases')} className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-2)',background:'transparent',border:0,cursor:'pointer',padding:0}}>view all →</button>}>Releases · {releases.length}</Section>
            <div style={{marginBottom:24}}>
            {releases.slice(0,4).map(rl => (
              <button key={rl.id} onClick={()=>window.dispatchEvent(new CustomEvent('astro-open-release',{detail:{id:rl.id}}))}
                onMouseEnter={e=>e.currentTarget.style.background='var(--bg-2)'}
                onMouseLeave={e=>e.currentTarget.style.background='transparent'}
                style={{display:'grid',gridTemplateColumns:'40px 1fr 70px 12px',gap:10,padding:'10px 8px 10px 0',
                  width:'100%',background:'transparent',border:0,borderTop:'1px solid var(--rule-soft)',cursor:'pointer',textAlign:'left',alignItems:'center',transition:'background .12s'}}>
                <div style={{width:36,height:36,background:rl.art || r.art,flexShrink:0}}/>
                <div style={{minWidth:0}}>
                  <div style={{fontSize:13,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{rl.title}</div>
                  <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2}}>{rl.artist} · {rl.year || ''}</div>
                </div>
                <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',textAlign:'right'}}>{rl.kind}</span>
                <Ic.Right width={10} height={10} style={{color:'var(--ink-3)'}}/>
              </button>
            ))}
            {releases.length > 4 && (
              <button onClick={()=>setTab('releases')} className="ff-mono upper"
                style={{display:'block',width:'100%',padding:'12px 0',marginTop:0,fontSize:10,letterSpacing:'.1em',color:'var(--ink-2)',
                  background:'transparent',border:0,borderTop:'1px solid var(--rule-soft)',cursor:'pointer',textAlign:'center',fontWeight:600}}
                onMouseEnter={e=>e.currentTarget.style.color='var(--ink)'}
                onMouseLeave={e=>e.currentTarget.style.color='var(--ink-2)'}>
                + {releases.length - 4} more →
              </button>
            )}
            </div>
          </>
        )}

        {siblings.length > 0 && (
          <>
            <Section num="·">Other recordings of this work · {siblings.length}</Section>
            {siblings.slice(0,6).map(sib => (
              <button key={sib.id} onClick={()=>go('recording',{id:sib.id})}
                style={{display:'grid',gridTemplateColumns:'36px 1fr 60px',alignItems:'center',gap:10,
                  width:'100%',padding:'8px 10px',marginBottom:4,
                  border:'1px solid var(--rule-soft)',background:'transparent',cursor:'pointer',textAlign:'left'}}>
                <div style={{width:32,height:32,background:sib.art}}/>
                <div style={{minWidth:0,fontSize:12}}>{sib.title} <span style={{color:'var(--ink-3)'}}>· {sib.artist}</span></div>
                <span className="ff-mono num" style={{fontSize:10,color:'var(--ink-3)',textAlign:'right'}}>{sib.year}</span>
              </button>
            ))}
          </>
        )}
      </div>
    </div>
  );
}

function dsps_count(r) { return 14; }

// ─────────────────────────────── Audio tab
function RecAudio({ r, ext, editing, editE }) {
  const FORMATS = ['WAV','FLAC','AIFF','ALAC','DSD'];
  const SAMPLE_RATES = ['44.1 kHz','48 kHz','88.2 kHz','96 kHz','176.4 kHz','192 kHz'];
  const BIT_DEPTHS = ['16-bit','20-bit','24-bit','32-bit float'];
  const CHANNELS = ['Mono','Stereo','Stereo (downmix)','Atmos · 16-track ADM','Atmos · binaural','5.1 surround'];
  const audioFileRef = React.useRef(null);
  const library = useRecLibrary(r.id, r.duration);
  const [pendingDetect, setPendingDetect] = React.useState(null); // {file, entry}
  const onUpload = (e) => {
    const file = e.target.files && e.target.files[0]; if (!file) return;
    const entry = addAudioToRecLib(r.id, file);
    window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:`Audio "${file.name}" added · analyzing tempo + key…`,tone:'ok'}}));
    // Auto-select the newly uploaded entry on the hero
    window.dispatchEvent(new CustomEvent('astro-rec-select-audio', { detail: { recId: r.id, entryId: entry.id } }));
    // Trigger combined tempo + key auto-detect
    if (window.AudioAutoDetectModal) {
      setPendingDetect({ file, entry });
    }
    e.target.value = '';
  };
  const acceptDetect = (result) => {
    // Persist to recording extension data
    try {
      const key = 'astro.rec.audio-features.' + r.id;
      const existing = JSON.parse(localStorage.getItem(key) || '{}');
      const merged = { ...existing, ...result, source: 'auto-detect-upload', updatedAt: Date.now() };
      localStorage.setItem(key, JSON.stringify(merged));
    } catch (err) {}
    // Notify the audio-intelligence panel to refresh
    window.dispatchEvent(new CustomEvent('astro-audio-features-updated', { detail: { recId: r.id, ...result } }));
    window.dispatchEvent(new CustomEvent('astro-toast', { detail: { msg: `Saved · ${result.bpm} BPM · ${result.key} ${result.mode === 'Major' ? 'maj' : 'min'}`, tone: 'ok' } }));
  };
  const playEntry = (entryId) => {
    window.dispatchEvent(new CustomEvent('astro-rec-select-audio', { detail: { recId: r.id, entryId } }));
  };
  const removeEntry = (entryId) => {
    removeAudioFromRecLib(r.id, entryId);
    window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:'Removed from library',tone:'ok'}}));
  };
  const fld = (k, val, opts) => editing
    ? (opts
        ? <InlineSelect value={val} options={opts} onCommit={(v)=>editE && editE(k, v)} style={{fontSize:13}}/>
        : <InlineText value={val||''} onCommit={(v)=>editE && editE(k, v)} style={{fontSize:13,fontFamily:'inherit',display:'inline-block'}}/>)
    : val;
  const specs = [
    ['Format', fld('audioFormat', ext.audioFormat, FORMATS)],
    ['Sample rate', fld('sampleRate', ext.sampleRate, SAMPLE_RATES)],
    ['Bit depth', fld('bitDepth', ext.bitDepth, BIT_DEPTHS)],
    ['Channels', fld('channels', ext.channels, CHANNELS)],
    ['File size', fld('fileSize', ext.fileSize)],
    ['Integrated loudness', fld('lufs', ext.lufs)],
    ['True peak', fld('truePeak', ext.truePeak)],
    ['Duration', `${Math.floor(r.duration/60)}:${String(r.duration%60).padStart(2,'0')}`],
  ];
  return (
    <>
      {window.RecAudioIntelligence && <window.RecAudioIntelligence r={r} ext={ext}/>}
      {pendingDetect && window.AudioAutoDetectModal && (
        <window.AudioAutoDetectModal
          file={pendingDetect.file}
          onAccept={acceptDetect}
          onReject={() => {}}
          onClose={() => setPendingDetect(null)}
        />
      )}
      <div style={{display:'grid',gridTemplateColumns:'1.4fr 1fr',gap:32}}>
      <input ref={audioFileRef} type="file" accept="audio/*" style={{display:'none'}} onChange={onUpload}/>
      <div>
        <Section num="01" action={
          <button onClick={()=>audioFileRef.current && audioFileRef.current.click()} className="ff-mono upper"
            style={{fontSize:9,letterSpacing:'.12em',padding:'5px 10px',background:'transparent',border:'1px solid var(--ink)',cursor:'pointer',color:'var(--ink)',display:'inline-flex',alignItems:'center',gap:6}}>
            <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4"><path d="M12 16V4M5 11l7-7 7 7M4 20h16"/></svg>
            UPLOAD AUDIO
          </button>
        }>Audio library · {library.length}</Section>
        <div style={{border:'1px solid var(--rule)',marginBottom:18}}>
          {library.map((e, i) => (
            <div key={e.id} style={{display:'grid',gridTemplateColumns:'40px 1fr auto auto',alignItems:'center',gap:14,padding:'12px 14px',borderBottom: i===library.length-1 ? 'none' : '1px solid var(--rule-soft)'}}>
              <button onClick={()=>e.available && playEntry(e.id)} disabled={!e.available} title={e.available ? 'Play this source' : 'Re-upload to play'}
                style={{width:36,height:36,padding:0,border:0,background: e.kind==='demo' ? 'var(--ink)' : 'var(--accent, #c0392b)',display:'flex',alignItems:'center',justifyContent:'center',cursor: e.available ? 'pointer' : 'not-allowed',opacity: e.available ? 1 : 0.4}}>
                <Ic.Play width={14} height={14} style={{color:'#fff',marginLeft:1}}/>
              </button>
              <div style={{minWidth:0}}>
                <div style={{fontSize:13,fontWeight:500,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{e.name}</div>
                <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginTop:3}}>
                  {e.kind === 'demo'
                    ? 'SYNTHESIZED · ALWAYS AVAILABLE'
                    : (e.available
                        ? `UPLOADED · ${new Date(e.addedAt).toLocaleDateString()}`
                        : 'NEEDS RE-UPLOAD AFTER REFRESH')}
                </div>
              </div>
              <button onClick={()=>e.available && playEntry(e.id)} disabled={!e.available} className="ff-mono upper"
                style={{fontSize:9,letterSpacing:'.12em',padding:'5px 10px',background:'transparent',border:'1px solid var(--rule)',cursor: e.available ? 'pointer' : 'not-allowed',color:'var(--ink-2)',opacity: e.available ? 1 : 0.4}}>
                LOAD ON HERO
              </button>
              {e.kind !== 'demo' && (
                <button onClick={()=>removeEntry(e.id)} title="Remove from library"
                  style={{width:28,height:28,padding:0,border:'1px solid var(--rule)',background:'transparent',cursor:'pointer',color:'var(--ink-3)',display:'flex',alignItems:'center',justifyContent:'center'}}
                  onMouseEnter={ev=>{ ev.currentTarget.style.borderColor='var(--accent)'; ev.currentTarget.style.color='var(--accent)'; }}
                  onMouseLeave={ev=>{ ev.currentTarget.style.borderColor='var(--rule)'; ev.currentTarget.style.color='var(--ink-3)'; }}>
                  <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.6"><path d="M3 3l6 6M9 3l-6 6"/></svg>
                </button>
              )}
            </div>
          ))}
          <button onClick={()=>audioFileRef.current && audioFileRef.current.click()}
            style={{display:'flex',alignItems:'center',justifyContent:'center',gap:8,width:'100%',padding:'14px',background:'var(--bg-2)',border:0,borderTop:'1px dashed var(--rule)',cursor:'pointer',color:'var(--ink-2)'}}
            onMouseEnter={ev=>ev.currentTarget.style.background='var(--bg)'}
            onMouseLeave={ev=>ev.currentTarget.style.background='var(--bg-2)'}>
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 16V4M5 11l7-7 7 7M4 20h16"/></svg>
            <span className="ff-mono upper" style={{fontSize:10,letterSpacing:'.14em'}}>Upload audio file</span>
            <span className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginLeft:6}}>WAV · FLAC · MP3 · AAC</span>
          </button>
        </div>

        <Section num="02">Master file metadata</Section>
        <div style={{border:'1px solid var(--rule)',padding:18,marginBottom:18}}>
          <div style={{display:'flex',alignItems:'center',gap:14,paddingBottom:14,borderBottom:'1px solid var(--rule-soft)',marginBottom:14}}>
            <div style={{width:40,height:40,background:'var(--ink)',display:'flex',alignItems:'center',justifyContent:'center'}}>
              <Ic.Wave width={20} height={20} style={{color:'var(--bg)'}}/>
            </div>
            <div style={{flex:1,minWidth:0}}>
              <div style={{fontSize:13,fontWeight:600}}>{r.title.replace(/\s+/g,'_')}_master_{ext.audioFormat.toLowerCase()}.{ext.audioFormat.toLowerCase()}</div>
              <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2}}>uploaded 2026-01-14 · sha256 {(r.id||'a').slice(0,8)}…{(r.id||'a').slice(-4)} · verified</div>
            </div>
            <Btn variant="ghost" icon={<Ic.Ext/>} size="sm" onClick={()=>window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:'Master download starting',tone:'ok'}}))}>Download</Btn>
          </div>
          <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:'12px 24px'}}>
            {specs.map(([k,v])=>(
              <div key={k}>
                <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginBottom:3}}>{k}</div>
                <div className="ff-mono" style={{fontSize:13}}>{v}</div>
              </div>
            ))}
          </div>
        </div>

        <Section num="03">Loudness</Section>
        <div style={{border:'1px solid var(--rule)',padding:18,marginBottom:18}}>
          <div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:24}}>
            <div>
              <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginBottom:6}}>INTEGRATED</div>
              <div className="ff-mono num" style={{fontSize:24,fontWeight:600,letterSpacing:'-0.01em'}}>{ext.lufs}</div>
              <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:4}}>target: -14 LUFS (streaming)</div>
            </div>
            <div>
              <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginBottom:6}}>TRUE PEAK</div>
              <div className="ff-mono num" style={{fontSize:24,fontWeight:600,letterSpacing:'-0.01em'}}>{ext.truePeak}</div>
              <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:4}}>limit: -1.0 dBTP</div>
            </div>
            <div>
              <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginBottom:6}}>DYNAMIC RANGE</div>
              <div className="ff-mono num" style={{fontSize:24,fontWeight:600,letterSpacing:'-0.01em'}}>DR8</div>
              <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:4}}>typical for genre</div>
            </div>
          </div>
        </div>

        <Section num="04">DDEX ERN payload preview</Section>
        <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',marginBottom:10,lineHeight:1.5}}>
          The audio metadata block ASTRO will write into the next ERN message for this recording. Editing fields above updates this payload.
        </div>
        <pre data-ddex-preview className="ff-mono" style={{fontSize:11,lineHeight:1.55,padding:14,background:'var(--bg-2)',border:'1px solid var(--rule-soft)',overflowX:'auto',color:'var(--ink-2)',margin:0}}>{`<SoundRecording>
  <SoundRecordingId>
    <ISRC>${r.isrc}</ISRC>
    <ProprietaryId Namespace="DPID:ASTRO">${r.id}</ProprietaryId>
  </SoundRecordingId>
  <ReferenceTitle>
    <TitleText>${r.title}</TitleText>
  </ReferenceTitle>
  <Duration>PT${Math.floor(r.duration/60)}M${r.duration%60}S</Duration>
  <TechnicalDetails>
    <AudioCodecType>${ext.audioFormat}</AudioCodecType>
    <SamplingRate UnitOfMeasure="Hz">${(ext.sampleRate||'').replace(/[^0-9.]/g,'')}000</SamplingRate>
    <BitsPerSample>${(ext.bitDepth||'').replace(/[^0-9]/g,'')}</BitsPerSample>
    <NumberOfChannels>${(ext.channels||'').toLowerCase().includes('mono') ? 1 : 2}</NumberOfChannels>
  </TechnicalDetails>
  <PLine>
    <Year>${ext.pLineYear||new Date().getFullYear()}</Year>
    <PLineText>${ext.pLineText||''}</PLineText>
  </PLine>
  <LanguageOfPerformance>${(window.refLangIso ? window.refLangIso(ext.language) : (ext.language||'en').slice(0,2).toLowerCase()) || 'en'}</LanguageOfPerformance>
  <ParentalWarningType>${ext.parental||'NotExplicit'}</ParentalWarningType>
</SoundRecording>`}</pre>
        <div style={{display:'flex',gap:8,marginTop:10}}>
          <button className="ff-mono upper" onClick={()=>{ navigator.clipboard?.writeText(document.querySelector('[data-ddex-preview]')?.innerText || ''); window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:'DDEX payload copied',tone:'ok'}})); }}
            style={{fontSize:9,letterSpacing:'.12em',padding:'6px 12px',background:'transparent',border:'1px solid var(--rule)',cursor:'pointer',color:'var(--ink-2)',display:'inline-flex',alignItems:'center',gap:6}}>
            COPY XML
          </button>
          <button className="ff-mono upper" onClick={()=>window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:'ERN 4.3 message generated',tone:'ok'}}))}
            style={{fontSize:9,letterSpacing:'.12em',padding:'6px 12px',background:'transparent',border:'1px solid var(--rule)',cursor:'pointer',color:'var(--ink-2)',display:'inline-flex',alignItems:'center',gap:6}}>
            GENERATE ERN 4.3 MESSAGE
          </button>
        </div>
      </div>

      <div>
        <Section num="·">Fingerprints</Section>
        <div className="ff-mono" style={{fontSize:11,display:'grid',gridTemplateColumns:'auto 1fr',rowGap:8,columnGap:14,alignItems:'center'}}>
          <span style={{color:'var(--ink-3)'}}>AcoustID</span>
          <span style={{wordBreak:'break-all',fontSize:10}}>{(r.id||'').padEnd(8,'a').slice(0,8)}-{(r.id||'').padEnd(12,'b').slice(0,4)}-{(r.isrc||'').replace(/-/g,'').slice(0,4)}-{(r.isrc||'').replace(/-/g,'').slice(4,8)}-{(r.isrc||'').replace(/-/g,'').slice(8,20).padEnd(12,'c')}</span>
          <span style={{color:'var(--ink-3)'}}>YouTube CID</span>
          <span style={{fontSize:10}}>matched · {Math.round(r.plays/250)} UGC refs</span>
          <span style={{color:'var(--ink-3)'}}>Shazam</span>
          <span style={{fontSize:10}}>indexed</span>
          <span style={{color:'var(--ink-3)'}}>SoundExchange</span>
          <span style={{fontSize:10}}>{(window.isrcDisplay ? window.isrcDisplay(r.isrc) : r.isrc)}</span>
        </div>

        <Section num="·">Submission notes</Section>
        <div style={{padding:14,background:'var(--bg-2)',border:'1px solid var(--rule-soft)',fontSize:12,color:'var(--ink-2)',lineHeight:1.5}}>
          Master delivered at -10.4 LUFS with a true peak of -1.2 dBTP — within streaming spec for all current DSPs. ASTRO does not transcode audio; the metadata above is what gets written into ERN, CWR and CAF messages on delivery.
        </div>
      </div>
    </div>
    </>
  );
}

// ─────────────────────────────── Contributors tab
// ─────────────────────────────── Directory autocomplete picker
// When editing a contributor's name, search the artists/contacts directory.
// User can pick an existing entry (which links by id) or create a new one
// (which captures the name only — the new directory record is added on save).
function DirectoryPicker({ value, linkedId, onCommit, placeholder = 'Search directory or add new…', kindFilter = null, autoFocus = false }) {
  const [q, setQ] = React.useState(value || '');
  const [open, setOpen] = React.useState(false);
  const [hoverIdx, setHoverIdx] = React.useState(0);
  const inputRef = React.useRef(null);
  const wrapRef = React.useRef(null);

  React.useEffect(() => { setQ(value || ''); }, [value]);
  React.useEffect(() => {
    if (autoFocus && inputRef.current) inputRef.current.focus();
  }, [autoFocus]);
  React.useEffect(() => {
    const onDocClick = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDocClick);
    return () => document.removeEventListener('mousedown', onDocClick);
  }, []);

  const directory = (typeof ARTISTS !== 'undefined') ? ARTISTS : [];
  const needle = q.trim().toLowerCase();
  const matches = needle.length === 0 ? [] : directory
    .filter(a => kindFilter ? (kindFilter === 'person' ? a.type === 'Person' : a.type !== 'Person') : true)
    .filter(a => (a.name||'').toLowerCase().includes(needle) || (a.realName||'').toLowerCase().includes(needle))
    .slice(0, 8);
  const exactMatch = matches.find(a => (a.name||'').toLowerCase() === needle);
  const showCreate = needle.length > 0 && !exactMatch;

  const pickExisting = (a) => {
    onCommit({ name: a.name, directoryId: a.id, ipi: a.ipi, pro: a.pro });
    setQ(a.name);
    setOpen(false);
    inputRef.current?.blur();
  };
  const pickCreate = () => {
    onCommit({ name: q.trim(), directoryId: null, isNew: true });
    setOpen(false);
    inputRef.current?.blur();
  };
  const totalRows = matches.length + (showCreate ? 1 : 0);
  const onKey = (e) => {
    if (e.key === 'ArrowDown') { e.preventDefault(); setHoverIdx(i => Math.min(totalRows - 1, i + 1)); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); setHoverIdx(i => Math.max(0, i - 1)); }
    else if (e.key === 'Enter') {
      e.preventDefault();
      if (hoverIdx < matches.length) pickExisting(matches[hoverIdx]);
      else if (showCreate) pickCreate();
    } else if (e.key === 'Escape') {
      setOpen(false);
      setQ(value || '');
    }
  };

  return (
    <div ref={wrapRef} style={{position:'relative',display:'inline-block',minWidth:200}}>
      <input ref={inputRef} value={q}
        onChange={e=>{ setQ(e.target.value); setOpen(true); setHoverIdx(0); }}
        onFocus={()=>{ if (q.trim().length > 0) setOpen(true); }}
        onKeyDown={onKey}
        placeholder={placeholder}
        style={{width:'100%',padding:'4px 6px',fontSize:14,fontWeight:500,fontFamily:'inherit',background:'var(--bg-2)',border:'1px solid var(--rule)',color:'var(--ink)',outline:'none'}}/>
      {linkedId && (
        <span className="ff-mono upper" style={{position:'absolute',right:8,top:'50%',transform:'translateY(-50%)',fontSize:8,letterSpacing:'.12em',color:'var(--ink-3)',background:'var(--bg)',padding:'2px 6px',pointerEvents:'none'}}>LINKED</span>
      )}
      {open && (matches.length > 0 || showCreate) && (
        <div style={{position:'absolute',top:'calc(100% + 2px)',left:0,right:0,minWidth:280,background:'var(--bg)',border:'1px solid var(--rule)',boxShadow:'0 4px 12px rgba(0,0,0,.08)',zIndex:50,maxHeight:280,overflow:'auto'}}>
          {matches.map((a, i) => (
            <button key={a.id}
              onMouseDown={(e)=>{ e.preventDefault(); pickExisting(a); }}
              onMouseEnter={()=>setHoverIdx(i)}
              style={{display:'flex',alignItems:'center',gap:10,width:'100%',padding:'8px 10px',background: hoverIdx===i ? 'var(--bg-2)' : 'transparent',border:0,borderBottom:'1px solid var(--rule-soft)',cursor:'pointer',textAlign:'left',fontFamily:'inherit'}}>
              <span style={{width:24,height:24,borderRadius:'50%',background:a.color||'var(--ink-3)',color:'#fff',display:'flex',alignItems:'center',justifyContent:'center',fontSize:10,fontWeight:600,flexShrink:0}}>{(a.name||'?').slice(0,1).toUpperCase()}</span>
              <div style={{flex:1,minWidth:0}}>
                <div style={{fontSize:13,fontWeight:500,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{a.name}</div>
                <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:1}}>{a.type || 'Person'} · {a.pro||'—'} · IPI {(window.ipiDisplayCompact ? window.ipiDisplayCompact(a.ipi) : a.ipi)||'—'}</div>
              </div>
            </button>
          ))}
          {showCreate && (
            <button onMouseDown={(e)=>{ e.preventDefault(); pickCreate(); }}
              onMouseEnter={()=>setHoverIdx(matches.length)}
              style={{display:'flex',alignItems:'center',gap:10,width:'100%',padding:'10px',background: hoverIdx===matches.length ? 'var(--bg-2)' : 'transparent',border:0,cursor:'pointer',textAlign:'left',fontFamily:'inherit'}}>
              <span style={{width:24,height:24,border:'1px dashed var(--rule)',display:'flex',alignItems:'center',justifyContent:'center',fontSize:14,color:'var(--ink-3)',flexShrink:0}}>+</span>
              <div style={{flex:1}}>
                <div style={{fontSize:13,fontWeight:500}}>Add "{q.trim()}" to directory</div>
                <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:1}}>creates a new contact record on save</div>
              </div>
            </button>
          )}
        </div>
      )}
    </div>
  );
}

function RecContributors({ r, ext, editing, primaryWork, sourceWorks, go }) {
  const isGroupName = (name) => {
    if (!name) return false;
    const n = name.toLowerCase();
    return /\b(orchestra|ensemble|choir|quartet|quintet|sextet|trio|duo|band|the |&|and )\b/.test(n) || / band$/.test(n);
  };
  // Initial buckets
  const seedPrimary = [
    {id:'pa-0', role:'Primary', name:r.artist, creditedAs:r.artist},
    ...(r.title.toLowerCase().includes('feat') || (r.featuring && r.featuring.length)
      ? (r.featuring || ['Cleo Sol']).map((name,j)=>({id:`pa-f${j}`, role:'Featuring', name, creditedAs:name}))
      : []),
  ];
  // Split incoming ext.contributors by role bucket
  const PRODUCER_ROLES = new Set(['Producer','Co-Producer','Executive Producer','Vocal Producer']);
  const ENG_ROLES = new Set(['Mixer','Mastering Engineer','Recording Engineer','Assistant Engineer','Programmer','Arranger']);
  const PERF_ROLES_SET = new Set(['Drums','Bass','Guitar','Keyboards','Strings','Brass','Multi-instrumentalist']);
  const seedProducers = [];
  const seedEng = [];
  const seedPerformers = [];
  (ext.contributors || []).forEach((c, j) => {
    const entry = { id: `c-${j}`, role: c.role, name: c.name, creditedAs: c.creditedAs || c.name };
    if (PRODUCER_ROLES.has(c.role)) seedProducers.push(entry);
    else if (ENG_ROLES.has(c.role)) seedEng.push(entry);
    else if (PERF_ROLES_SET.has(c.role)) seedPerformers.push(entry);
    else seedEng.push(entry);
  });

  const [primary, setPrimary] = React.useState(seedPrimary);
  const [performers, setPerformers] = React.useState(seedPerformers);
  const [producers, setProducers] = React.useState(seedProducers);
  const [engineering, setEngineering] = React.useState(seedEng);

  const PRIMARY_ROLE_OPTS = ['Primary','Featuring','Remixer'];
  const PERFORMER_ROLE_OPTS = ['Lead vocals','Backing vocals','Choir','Guest vocals','Soloist','Drums','Bass','Guitar','Keyboards','Piano','Strings','Brass','Woodwinds','Percussion','Synthesizer','Multi-instrumentalist'];
  const PRODUCER_ROLE_OPTS = ['Producer','Co-Producer','Executive Producer','Vocal Producer'];
  const ENG_ROLE_OPTS = ['Mixer','Mastering Engineer','Recording Engineer','Assistant Engineer','Programmer','Arranger','Editor'];

  const ContribRow = ({ entry, onChange, roleOpts, primaryArtist=false, primaryStyle=false }) => {
    // Linkability rule:
    //   - primary artist row: linkable regardless (person OR group)
    //   - everywhere else: persons only
    const isLinkable = primaryArtist || !isGroupName(entry.name);
    const open = () => window.dispatchEvent(new CustomEvent('astro-open-artist',{detail:{name:entry.name}}));
    return (
      <tr style={{borderBottom:'1px solid var(--rule-soft)'}}>
        <td style={{padding:'14px 0'}}>
          {editing
            ? <InlineSelect value={entry.role} options={roleOpts} onCommit={(v)=>onChange({role:v})} style={{fontSize:11}}/>
            : (primaryStyle && entry.role==='Primary'
                ? <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',padding:'3px 7px',background:'var(--ink)',color:'var(--bg)',fontWeight:600}}>{entry.role}</span>
                : <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'var(--ink-2)',fontWeight:500}}>{entry.role}</span>)}
        </td>
        <td style={{padding:'14px 0',fontSize:14,fontWeight:500}}>
          {editing
            ? <DirectoryPicker
                value={entry.name}
                linkedId={entry.directoryId}
                kindFilter={primaryArtist ? null : 'person'}
                onCommit={(patch)=>onChange(patch)}/>
            : (isLinkable
                ? <button onClick={open}
                    style={{background:'transparent',border:0,padding:0,fontSize:14,fontWeight:500,fontFamily:'inherit',color:'inherit',cursor:'pointer',textAlign:'left',borderBottom:'1px solid var(--rule-soft)'}}
                    onMouseEnter={e=>{ e.currentTarget.style.borderBottomColor='var(--ink)'; }}
                    onMouseLeave={e=>{ e.currentTarget.style.borderBottomColor='var(--rule-soft)'; }}>
                    {entry.name}
                  </button>
                : <span>{entry.name}</span>)}
        </td>
        <td style={{padding:'14px 0',fontSize:13,color:'var(--ink-2)'}}>
          {editing
            ? <InlineText value={entry.creditedAs||''} onCommit={(v)=>onChange({creditedAs:v})} style={{fontSize:13,fontFamily:'inherit',display:'inline-block'}}/>
            : (entry.creditedAs || entry.name)}
        </td>
      </tr>
    );
  };

  const Headers = () => (
    <tr style={{borderBottom:'1px solid var(--rule)'}}>
      <th className="ff-mono upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500,width:200}}>ROLE</th>
      <th className="ff-mono upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500}}>NAME</th>
      <th className="ff-mono upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500,width:240}}>CREDITED AS</th>
    </tr>
  );

  const updateAt = (setter) => (idx, patch) => setter(arr => arr.map((x,j)=> j===idx ? {...x, ...patch} : x));
  const removeAt = (setter) => (idx) => setter(arr => arr.filter((_,j)=> j!==idx));
  const addRow = (setter, defaults) => () => setter(arr => [...arr, {id:`new-${Date.now()}`, ...defaults}]);

  const renderTable = (rows, setter, roleOpts, opts={}) => (
    <table style={{width:'100%',borderCollapse:'collapse',marginBottom: rows.length===0 ? 12 : 24}}>
      <thead><Headers/></thead>
      <tbody>
        {rows.length === 0 && !editing && (
          <tr><td colSpan={3} className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',padding:'14px 0',fontStyle:'italic'}}>No entries</td></tr>
        )}
        {rows.map((entry, idx) => (
          <ContribRow key={entry.id || idx} entry={entry}
            onChange={(p)=>updateAt(setter)(idx, p)}
            roleOpts={roleOpts}
            primaryArtist={opts.primaryArtist}
            primaryStyle={opts.primaryStyle}/>
        ))}
      </tbody>
    </table>
  );

  // Writers — derived from the linked work(s). Read-only here since they live
  // at the work level (ISWC/CWR), not the recording level (ISRC).
  const normalizeWriter = (w) => {
    if (typeof w === 'string') return { name: w, role: 'Composer/Author' };
    return { name: w.name, role: w.role || 'Composer/Author' };
  };
  const writerEntries = [];
  if (primaryWork) {
    (primaryWork.writers || []).forEach(w => {
      const nw = normalizeWriter(w);
      writerEntries.push({ ...nw, sourceWork: primaryWork, sourceLabel: 'primary work' });
    });
  }
  (sourceWorks || []).forEach(sw => {
    if (!sw.work) return;
    (sw.work.writers || []).forEach(w => {
      const nw = normalizeWriter(w);
      if (writerEntries.some(e => e.name === nw.name)) return;
      writerEntries.push({ ...nw, sourceWork: sw.work, sourceLabel: sw.type || 'source work' });
    });
  });

  return (
    <div>
      <Section num="01" action={editing && (
        <button onClick={addRow(setPrimary, {role:'Featuring', name:'New artist', creditedAs:'New artist'})} className="ff-mono upper" style={{display:'inline-flex',alignItems:'center',gap:6,padding:'5px 10px',background:'transparent',color:'var(--ink-2)',border:'1px dashed var(--rule)',cursor:'pointer',fontSize:9,letterSpacing:'.12em'}}>+ Add</button>
      )}>Primary artists · {primary.length}</Section>
      {renderTable(primary, setPrimary, PRIMARY_ROLE_OPTS, {primaryArtist:true, primaryStyle:true})}

      <Section num="02" action={editing && (
        <button onClick={addRow(setPerformers, {role:'Lead vocals', name:'New performer', creditedAs:'New performer'})} className="ff-mono upper" style={{display:'inline-flex',alignItems:'center',gap:6,padding:'5px 10px',background:'transparent',color:'var(--ink-2)',border:'1px dashed var(--rule)',cursor:'pointer',fontSize:9,letterSpacing:'.12em'}}>+ Add</button>
      )}>Performers · {performers.length}</Section>
      {renderTable(performers, setPerformers, PERFORMER_ROLE_OPTS)}

      <Section num="03" action={editing && (
        <button onClick={addRow(setProducers, {role:'Producer', name:'New producer', creditedAs:'New producer'})} className="ff-mono upper" style={{display:'inline-flex',alignItems:'center',gap:6,padding:'5px 10px',background:'transparent',color:'var(--ink-2)',border:'1px dashed var(--rule)',cursor:'pointer',fontSize:9,letterSpacing:'.12em'}}>+ Add</button>
      )}>Producers · {producers.length}</Section>
      {renderTable(producers, setProducers, PRODUCER_ROLE_OPTS)}

      <Section num="04" action={editing && (
        <button onClick={addRow(setEngineering, {role:'Mixer', name:'New engineer', creditedAs:'New engineer'})} className="ff-mono upper" style={{display:'inline-flex',alignItems:'center',gap:6,padding:'5px 10px',background:'transparent',color:'var(--ink-2)',border:'1px dashed var(--rule)',cursor:'pointer',fontSize:9,letterSpacing:'.12em'}}>+ Add</button>
      )}>Production & engineering · {engineering.length}</Section>
      {renderTable(engineering, setEngineering, ENG_ROLE_OPTS)}

      <Section num="05" action={primaryWork && (
        <button onClick={()=>go && go('work', primaryWork)} className="ff-mono upper" style={{display:'inline-flex',alignItems:'center',gap:6,padding:'5px 10px',background:'transparent',color:'var(--ink-2)',border:'1px solid var(--rule)',cursor:'pointer',fontSize:9,letterSpacing:'.12em'}}>Edit on work →</button>
      )}>Writers · {writerEntries.length}</Section>
      <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',marginBottom:10,lineHeight:1.5}}>
        Derived from the linked work{(sourceWorks && sourceWorks.length) ? '(s)' : ''} (ISWC). Writers register under their legal name for CWR — edits live on the work, not the recording.
      </div>
      <table style={{width:'100%',borderCollapse:'collapse',marginBottom:24}}>
        <thead>
          <tr style={{borderBottom:'1px solid var(--rule)'}}>
            <th className="ff-mono upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500,width:200}}>ROLE</th>
            <th className="ff-mono upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500}}>WRITER</th>
            <th className="ff-mono upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500,width:280}}>SOURCE WORK</th>
          </tr>
        </thead>
        <tbody>
          {writerEntries.length === 0 && (
            <tr><td colSpan={3} className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',padding:'14px 0',fontStyle:'italic'}}>No linked work — link a work to populate writers</td></tr>
          )}
          {writerEntries.map((w, idx) => (
            <tr key={idx} style={{borderBottom:'1px solid var(--rule-soft)'}}>
              <td style={{padding:'14px 0'}}>
                <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'var(--ink-2)',fontWeight:500}}>{w.role}</span>
              </td>
              <td style={{padding:'14px 0',fontSize:14,fontWeight:500}}>
                <button onClick={()=>window.dispatchEvent(new CustomEvent('astro-open-artist',{detail:{name:w.name}}))}
                  style={{background:'transparent',border:0,padding:0,fontSize:14,fontWeight:500,fontFamily:'inherit',color:'inherit',cursor:'pointer',textAlign:'left',borderBottom:'1px solid var(--rule-soft)'}}
                  onMouseEnter={e=>{ e.currentTarget.style.borderBottomColor='var(--ink)'; }}
                  onMouseLeave={e=>{ e.currentTarget.style.borderBottomColor='var(--rule-soft)'; }}>
                  {w.name}
                </button>
              </td>
              <td style={{padding:'14px 0',fontSize:13}}>
                <button onClick={()=>go && go('work', w.sourceWork)}
                  style={{background:'transparent',border:0,padding:0,fontFamily:'inherit',color:'inherit',cursor:'pointer',textAlign:'left',display:'flex',flexDirection:'column',gap:2}}>
                  <span style={{fontSize:13}}>{w.sourceWork.title}</span>
                  <span className="ff-mono" style={{fontSize:10,color:'var(--ink-3)'}}>ISWC {window.iswcDisplay ? window.iswcDisplay(w.sourceWork.iswc) : w.sourceWork.iswc} · {w.sourceLabel}</span>
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

// ─────────────────────────────── Rights tab
function RecRights({ r, ext, editing, editE }) {
  const owners = ext.recordingOwners;
  const total = owners.reduce((s,o)=>s+(+o.share||0),0);
  const setOwners = (next) => editE && editE('recordingOwners', typeof next === 'function' ? next(owners) : next);
  const setOwn = (i, patch) => setOwners(owners.map((o,j)=> j===i ? {...o, ...patch} : o));
  // Controlled % — DERIVED via window.getShareControlled. Each owner is treated
  // as a Party (label/imprint); controlled if it carries an active ★/◆ indicator.
  const ctrlAgg = (() => {
    if (!window.getShareControlled || !owners.length) return null;
    let ctrl = 0;
    for (const o of owners) {
      if (window.getShareControlled({ name: o.owner, share: o.share }).controlled) ctrl += (+o.share || 0);
    }
    const t = total || 100;
    return {
      controlledPct: Math.round((ctrl / t) * 100),
      nonControlledPct: Math.round(((t - ctrl) / t) * 100),
    };
  })();
  return (
    <div style={{display:'grid',gridTemplateColumns:'1.4fr 1fr',gap:32}}>
      <div>
        <Section num="01">Master ownership</Section>
        {ctrlAgg && (
          <div className="ff-mono"
            title="Derived from owner indicators (★ Owned / ◆ Administered). Master shares not flagged Controlled fall outside ASTRO's distribution scope."
            style={{fontSize:11,color:'var(--ink-3)',marginTop:-12,marginBottom:14,display:'flex',gap:14,alignItems:'baseline'}}>
            <span><strong style={{color:'var(--ink)'}} className="num">{ctrlAgg.controlledPct}%</strong> controlled</span>
            <span>·</span>
            <span><strong style={{color:'var(--ink-2)'}} className="num">{ctrlAgg.nonControlledPct}%</strong> non-controlled</span>
            <span style={{color:'var(--ink-4)'}}>· derived from owner ★/◆</span>
          </div>
        )}
        <table style={{width:'100%',borderCollapse:'collapse',marginBottom:18}}>
          <thead>
            <tr style={{borderBottom:'1px solid var(--rule)'}}>
              <th className="ff-mono upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500}}>OWNER</th>
              <th className="ff-mono upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500,width:240}}>TERRITORIES</th>
              <th className="ff-mono upper" style={{textAlign:'right',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500,width:80}}>SHARE</th>
            </tr>
          </thead>
          <tbody>
            {owners.map((o,i)=>(
              <tr key={i} onClick={editing ? undefined : ()=>window.dispatchEvent(new CustomEvent('astro-open-label',{detail:{name:o.owner}}))}
                style={{borderBottom:'1px solid var(--rule-soft)',cursor: editing ? 'default' : 'pointer'}}
                onMouseEnter={e=>{ if(!editing) e.currentTarget.style.background='var(--bg-2)'; }}
                onMouseLeave={e=>{ if(!editing) e.currentTarget.style.background='transparent'; }}>
                <td style={{padding:'14px 0'}}>
                  <div style={{fontSize:14,fontWeight:500}}>
                    {editing
                      ? <InlineText value={o.owner} onCommit={(v)=>setOwn(i,{owner:v})} style={{fontSize:14,fontFamily:'inherit',fontWeight:500,display:'inline-block'}}/>
                      : o.owner}
                  </div>
                  <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2,display:'flex',alignItems:'center',gap:8}}>
                    <span>label · master rights holder</span>
                    {!editing && window.ControlledLabel && (
                      <window.ControlledLabel share={{ name: o.owner, share: o.share }} compact={true}/>
                    )}
                  </div>
                </td>
                <td style={{padding:'14px 0',fontSize:12,color:'var(--ink-2)'}}>
                  {editing
                    ? <InlineText value={(o.territories||['World']).join(', ')} onCommit={(v)=>setOwn(i,{territories: v.split(',').map(s=>s.trim()).filter(Boolean)})} style={{fontSize:12,fontFamily:'inherit',display:'inline-block'}}/>
                    : (o.territories||['World']).join(', ')}
                </td>
                <td style={{padding:'14px 0',textAlign:'right'}} className="ff-mono num">
                  {editing
                    ? <><InlineText value={String(o.share)} onCommit={(v)=>setOwn(i,{share: +v.replace(/[^\d.]/g,'') || 0})} style={{fontSize:13,fontFamily:'inherit',display:'inline-block',maxWidth:50,textAlign:'right'}}/>%</>
                    : <>{o.share}%</>}
                </td>
              </tr>
            ))}
            <tr>
              <td style={{padding:'14px 0',borderTop:'2px solid var(--rule)'}} className="ff-mono upper" colSpan={2}><span style={{fontSize:10,color:'var(--ink-3)',letterSpacing:'.1em'}}>TOTAL</span></td>
              <td style={{padding:'14px 0',textAlign:'right',borderTop:'2px solid var(--rule)',fontWeight:600,color: total===100 ? 'var(--ink)' : '#c0392b'}} className="ff-mono num">{total}%{total!==100 && (total<100?` · ${100-total}% short`:` · ${total-100}% over`)}</td>
            </tr>
          </tbody>
        </table>
        {editing && (
          <button onClick={()=>setOwners([...owners,{owner:'New owner',share:0,territories:['World']}])} className="ff-mono upper" style={{display:'inline-flex',alignItems:'center',gap:6,padding:'8px 12px',background:'transparent',color:'var(--ink-2)',border:'1px dashed var(--rule)',cursor:'pointer',fontSize:10,letterSpacing:'.08em',marginBottom:18}}>
            + Add owner
          </button>
        )}

        <Section num="02">Notice & lines</Section>
        <div className="ff-mono" style={{fontSize:12,display:'grid',gridTemplateColumns:'180px 1fr',rowGap:10,alignItems:'center'}}>
          <span style={{color:'var(--ink-3)'}}>P-line year</span>
          <span>{editing ? <InlineText value={String(ext.pLineYear)} onCommit={(v)=>editE('pLineYear', +v.replace(/\D/g,'')||ext.pLineYear)} style={{fontSize:12,fontFamily:'inherit',display:'inline-block',maxWidth:80}}/> : ext.pLineYear}</span>
          <span style={{color:'var(--ink-3)'}}>P-line owner</span>
          <span>{editing ? <InlineText value={ext.pLineOwner} onCommit={(v)=>editE('pLineOwner', v)} style={{fontSize:12,fontFamily:'inherit',display:'inline-block'}}/> : ext.pLineOwner}</span>
          <span style={{color:'var(--ink-3)'}}>P-line text</span>
          <span>{editing ? <InlineText value={ext.pLineText} onCommit={(v)=>editE('pLineText', v)} style={{fontSize:12,fontFamily:'inherit',display:'inline-block'}}/> : ext.pLineText}</span>
          <span style={{color:'var(--ink-3)'}}>Marketing label</span>
          <span>{editing ? <InlineText value={ext.marketingLabel} onCommit={(v)=>editE('marketingLabel', v)} style={{fontSize:12,fontFamily:'inherit',display:'inline-block'}}/> : ext.marketingLabel}</span>
          <span style={{color:'var(--ink-3)'}}>Recording date</span>
          <span>{editing ? <DateField value={ext.recordingDate} editing={true} onChange={(v)=>editE('recordingDate', v)} size="sm" style={{display:'inline-block',height:22,fontSize:12}}/> : formatDate(ext.recordingDate)}</span>
          <span style={{color:'var(--ink-3)'}}>First release</span>
          <span>{editing ? <DateField value={ext.firstReleaseDate} editing={true} onChange={(v)=>editE('firstReleaseDate', v)} size="sm" style={{display:'inline-block',height:22,fontSize:12}}/> : formatDate(ext.firstReleaseDate)}</span>
        </div>
      </div>

      <div>
        <Section num="·">Neighboring rights</Section>
        {[
          {soc:'SOUNDEXCHANGE · US', status:'Registered', note:`ISRC ${r.isrc} · last collection 2026-Q1 · $4,212.18`},
          {soc:'PPL · UK',           status:'Registered', note:'via direct membership · 2026-Q1 distribution pending'},
          {soc:'GVL · DE',           status:'Pending registration', note:'submitted 2026-03-22'},
        ].map(c => (
          <button key={c.soc} onClick={()=>window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:`${c.soc} — society profile`,tone:'ok'}}))}
            style={{display:'block',width:'100%',textAlign:'left',padding:14,border:'1px solid var(--rule)',marginBottom:18,background:'transparent',cursor:'pointer'}}
            onMouseEnter={e=>e.currentTarget.style.background='var(--bg-2)'}
            onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
            <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginBottom:6}}>{c.soc}</div>
            <div style={{fontSize:13,fontWeight:600}}>{c.status}</div>
            <div className="ff-mono" style={{fontSize:11,color:'var(--ink-2)',marginTop:4}}>{c.note}</div>
          </button>
        ))}

        <Section num="·">Notes</Section>
        <div style={{padding:14,background:'var(--bg-2)',border:'1px solid var(--rule-soft)',fontSize:12,color:'var(--ink-2)',lineHeight:1.5}}>
          Ownership splits flow into DDEX <span className="ff-mono" style={{color:'var(--ink)'}}>RightsController</span> blocks on every ERN delivery. Territorial overrides are honoured per delivery target.
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────── Releases tab
function RecReleases({ r, releases, go }) {
  // Pull the live release graph if we have it; the global RELEASES_X may be
  // mutated by the Add Release flow with track-level override data we can show.
  const RELS = (typeof window.REL_BY_ID !== 'undefined') ? window.REL_BY_ID : {};
  const REC_ID_KEYS = ['recId', 'recordingId', 'recording_id', 'rid'];

  // Build a per-release appearance row, reading any override data persisted on
  // the release.tracks[] array if present. Falls back to synthesised metadata.
  const buildAppearance = (rl) => {
    const tracks = Array.isArray(rl.tracks) ? rl.tracks : [];
    let trackRow = null;
    let trackIdx = -1;
    tracks.forEach((t, i) => {
      if (!t) return;
      const tid = REC_ID_KEYS.map(k => t[k]).find(Boolean);
      if (tid && tid === r.id) { trackRow = t; trackIdx = i; }
    });
    // Override-aware display values:
    const titleAs   = (trackRow && trackRow.trackTitle) || r.title;
    const isrcAs    = (trackRow && Array.isArray(trackRow.isrcs) && trackRow.isrcs.find(x => x && x.primary)?.isrc)
                   || (trackRow && Array.isArray(trackRow.isrcs) && trackRow.isrcs[0]?.isrc)
                   || (trackRow && trackRow.isrc) || r.isrc;
    const durAs     = (trackRow && (trackRow.durationOverride || trackRow.duration)) || r.duration;
    // Artist credit override: primary-row override + any extras.
    const overrides = (trackRow && Array.isArray(trackRow.artistOverrides)) ? trackRow.artistOverrides : [];
    const primaryOvr = overrides.find(o => o && o.role === 'primary' && !o._extra && o.displayNameOverride);
    let artistAs = primaryOvr ? primaryOvr.displayNameOverride : r.artist;
    const feats = overrides.filter(o => o && o._extra && o.role === 'featured' && (o.displayNameOverride || '').trim());
    const remixes = overrides.filter(o => o && o._extra && o.role === 'remixer' && (o.displayNameOverride || '').trim());
    if (feats.length && !/feat\.|featuring/i.test(artistAs)) {
      artistAs += ' feat. ' + feats.map(f => f.displayNameOverride.trim()).join(', ');
    }
    if (remixes.length) {
      artistAs += ' (' + remixes.map(f => f.displayNameOverride.trim()).join(' & ') + (remixes.length === 1 ? ' Remix)' : ' Remixes)');
    }
    const hasOverrides = !!(window._arTrackHasOverrides && window._arTrackHasOverrides(trackRow));
    const trackNo = (trackRow && (trackRow.position || trackRow.track_number)) || (trackIdx >= 0 ? trackIdx + 1 : (rl.trackNo || null));
    return { rl, trackRow, trackIdx, titleAs, artistAs, isrcAs, durAs, hasOverrides, trackNo };
  };

  // Repointer dialog state: which release/track we're repointing, search query,
  // and the candidate result list pulled from RECORDING_GRAPH.
  const [repointing, setRepointing] = _RecPage_useS(null); // { rlId, trackIdx } | null
  const [pq, setPq]         = _RecPage_useS('');
  const [pHit, setPHit]     = _RecPage_useS(null); // chosen recording

  const repointCandidates = _RecPage_useM(() => {
    const all = (typeof window.RECORDING_GRAPH !== 'undefined') ? window.RECORDING_GRAPH : [];
    const qn  = pq.trim().toLowerCase();
    if (!qn) return all.filter(x => x.id !== r.id).slice(0, 25);
    return all.filter(x =>
      x.id !== r.id &&
      ((x.title || '').toLowerCase().includes(qn) ||
       (x.artist || '').toLowerCase().includes(qn) ||
       (x.isrc || '').toLowerCase().includes(qn))
    ).slice(0, 50);
  }, [pq]);

  const commitRepoint = () => {
    if (!repointing || !pHit) return;
    const rel = RELS[repointing.rlId];
    if (!rel || !Array.isArray(rel.tracks) || !rel.tracks[repointing.trackIdx]) {
      window.dispatchEvent(new CustomEvent('astro-toast', { detail: { msg: 'Cannot repoint: release lacks tracklist data', tone: 'warn' } }));
      setRepointing(null); setPq(''); setPHit(null);
      return;
    }
    // Mutate live release graph in-place. Strip overrides, point to new recording.
    rel.tracks[repointing.trackIdx] = {
      ...rel.tracks[repointing.trackIdx],
      recId: pHit.id,
      // Don't carry the old overrides over to a different master.
      trackTitle: undefined, durationOverride: undefined, isrcs: undefined, artistOverrides: undefined,
    };
    // Recording side: drop this release from the old recording's releaseIds[]
    // and push it onto the new one's. Best-effort; bail if shape isn't there.
    if (Array.isArray(r.releaseIds)) {
      r.releaseIds = r.releaseIds.filter(id => id !== rel.id);
    }
    if (Array.isArray(pHit.releaseIds)) {
      if (!pHit.releaseIds.includes(rel.id)) pHit.releaseIds.push(rel.id);
    } else {
      pHit.releaseIds = [rel.id];
    }
    window.dispatchEvent(new CustomEvent('astro-toast', { detail: { msg: `Track ${repointing.trackIdx + 1} on “${rel.title}” now points to ${pHit.title}`, tone: 'ok' } }));
    setRepointing(null); setPq(''); setPHit(null);
  };

  if (releases.length === 0) {
    return (
      <div style={{padding:'60px 0',textAlign:'center'}}>
        <div className="ff-mono upper" style={{fontSize:11,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:14}}>NOT YET ON A RELEASE</div>
        <Btn variant="primary" icon={<Ic.Plus/>} onClick={()=>window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:'Add-to-release flow opened',tone:'ok'}}))}>Add to release</Btn>
      </div>
    );
  }
  const appearances = releases.map(buildAppearance);
  const overrideCount = appearances.filter(a => a.hasOverrides).length;
  return (
    <div>
      <Section num="01" action={overrideCount > 0 &&
        <span className="ff-mono upper" title="Some appearances have per-edition overrides applied"
          style={{fontSize:9,letterSpacing:'.1em',padding:'2px 6px',border:'1px solid var(--accent)',color:'var(--accent)'}}>
          {overrideCount} OVR
        </span>
      }>Appearances · {releases.length} release{releases.length>1?'s':''}</Section>
      <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginBottom:14,marginTop:-12,lineHeight:1.55,maxWidth:600}}>
        Each row is a track on a release pointing back to this master. Title, artist credit, and ISRC may be overridden per edition — when they are, an OVR chip appears.
      </div>
      {appearances.map(({ rl, trackRow, trackIdx, titleAs, artistAs, isrcAs, durAs, hasOverrides, trackNo }) => {
        const isRepointTarget = repointing && repointing.rlId === rl.id;
        return (
          <div key={rl.id} style={{borderBottom:'1px solid var(--rule-soft)'}}>
            <div onClick={()=>window.dispatchEvent(new CustomEvent('astro-open-release',{detail:{id:rl.id}}))}
              style={{display:'grid',gridTemplateColumns:'56px minmax(0,1fr) 100px 70px 90px 24px',alignItems:'center',gap:14,padding:'14px 0',cursor:'pointer'}}
              onMouseEnter={e=>e.currentTarget.style.background='var(--bg-2)'}
              onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
              <div style={{width:56,height:56,background:rl.art || r.art,flexShrink:0}}/>
              <div style={{minWidth:0}}>
                <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:2}}>
                  <span style={{fontSize:14,fontWeight:600,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{rl.title}</span>
                  <span className="ff-mono upper" style={{fontSize:8,letterSpacing:'.1em',color:'var(--ink-3)',padding:'1px 4px',border:'1px solid var(--rule)',flexShrink:0}}>{rl.kind}</span>
                  {hasOverrides &&
                    <span className="ff-mono upper" title="Track-level overrides on this appearance"
                      style={{fontSize:8,letterSpacing:'.1em',padding:'1px 5px',border:'1px solid var(--accent)',color:'var(--accent)',flexShrink:0}}>OVR</span>}
                </div>
                <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>
                  Track {trackNo || '—'} · {titleAs}
                  {titleAs !== r.title && <span style={{color:'var(--accent)'}}> · titled here</span>}
                  {' '}— {artistAs}
                  {artistAs !== r.artist && <span style={{color:'var(--accent)'}}> · credited here</span>}
                </div>
              </div>
              <span className="ff-mono" style={{fontSize:10,color:'var(--ink-2)',whiteSpace:'nowrap'}}>
                {window.isrcDisplay ? window.isrcDisplay(isrcAs) : isrcAs}
                {isrcAs !== r.isrc && <span style={{color:'var(--accent)'}}> ·</span>}
              </span>
              <span className="ff-mono num" style={{fontSize:11,color:'var(--ink-2)',textAlign:'right'}}>
                {durAs ? `${Math.floor(durAs/60)}:${String(durAs%60).padStart(2,'0')}` : '—'}
              </span>
              <button onClick={(e)=>{ e.stopPropagation(); setRepointing(isRepointTarget ? null : { rlId: rl.id, trackIdx }); setPq(''); setPHit(null); }} className="ff-mono upper"
                title="Repoint this track to a different recording"
                style={{padding:'5px 8px',fontSize:9,letterSpacing:'.08em',background: isRepointTarget ? 'var(--accent)' : 'transparent',
                  color: isRepointTarget ? 'var(--bg)' : 'var(--ink-2)',
                  border:'1px solid '+(isRepointTarget ? 'var(--accent)' : 'var(--rule)'),cursor:'pointer'}}>
                ⇄ REPOINT
              </button>
              <Ic.Right width={12} height={12} style={{color:'var(--ink-3)'}}/>
            </div>
            {isRepointTarget && (
              <div style={{background:'var(--rule-soft)',padding:'14px 16px 16px',borderTop:'1px solid var(--rule-soft)'}}>
                <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',marginBottom:8}}>
                  Repoint track {trackIdx >= 0 ? trackIdx + 1 : '—'} on “{rl.title}” to a different master
                </div>
                {trackIdx < 0 && (
                  <div className="ff-mono" style={{fontSize:10,color:'var(--accent)',marginBottom:8}}>
                    This release does not yet carry a structured tracklist with this recording linked. Repointing will become available after the release is migrated to the new track schema.
                  </div>
                )}
                <input value={pq} onChange={e=>{ setPq(e.target.value); setPHit(null); }}
                  placeholder="Search recordings by title, artist, or ISRC…"
                  style={{width:'100%',padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,color:'var(--ink)',marginBottom:8,boxSizing:'border-box'}}/>
                <div style={{maxHeight:240,overflow:'auto',border:'1px solid var(--rule)',background:'var(--bg)',marginBottom:8}}>
                  {repointCandidates.length === 0 && (
                    <div className="ff-mono" style={{padding:'14px',fontSize:11,color:'var(--ink-3)',textAlign:'center'}}>No matches</div>
                  )}
                  {repointCandidates.map(rec => (
                    <div key={rec.id} onClick={()=>setPHit(rec)}
                      style={{padding:'10px 12px',borderBottom:'1px solid var(--rule-soft)',cursor:'pointer',
                        background: pHit?.id === rec.id ? 'var(--rule-soft)' : 'transparent',
                        display:'grid',gridTemplateColumns:'minmax(0,1fr) 110px 50px',alignItems:'center',gap:10}}>
                      <div style={{minWidth:0}}>
                        <div style={{fontSize:13,fontWeight:500,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{rec.title}</div>
                        <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:1,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{rec.artist}</div>
                      </div>
                      <span className="ff-mono" style={{fontSize:10,color:'var(--ink-2)'}}>
                        {window.isrcDisplay ? window.isrcDisplay(rec.isrc) : rec.isrc}
                      </span>
                      <span className="ff-mono num" style={{fontSize:10,color:'var(--ink-3)',textAlign:'right'}}>
                        {rec.duration ? `${Math.floor(rec.duration/60)}:${String(rec.duration%60).padStart(2,'0')}` : '—'}
                      </span>
                    </div>
                  ))}
                </div>
                <div style={{display:'flex',gap:8,justifyContent:'flex-end'}}>
                  <button onClick={()=>{ setRepointing(null); setPq(''); setPHit(null); }} className="ff-mono upper"
                    style={{padding:'6px 14px',fontSize:9,letterSpacing:'.08em',background:'transparent',border:'1px solid var(--rule)',color:'var(--ink-2)',cursor:'pointer'}}>
                    Cancel
                  </button>
                  <button onClick={commitRepoint} disabled={!pHit || trackIdx < 0} className="ff-mono upper"
                    style={{padding:'6px 14px',fontSize:9,letterSpacing:'.08em',
                      background: (pHit && trackIdx >= 0) ? 'var(--ink)' : 'var(--rule-soft)',
                      color: (pHit && trackIdx >= 0) ? 'var(--bg)' : 'var(--ink-3)',
                      border:'1px solid '+((pHit && trackIdx >= 0) ? 'var(--ink)' : 'var(--rule)'),
                      cursor: (pHit && trackIdx >= 0) ? 'pointer' : 'not-allowed'}}>
                    Repoint to selected
                  </button>
                </div>
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}

// ─────────────────────────────── Distribution tab
function RecDistribution({ r, dsps }) {
  const dsps_full = [
    {n:'Spotify',     status:'Live',     date:'2024-09-04', terr:'World ex JP', plays:dsps[0].plays},
    {n:'Apple Music', status:'Live',     date:'2024-09-04', terr:'World',        plays:dsps[1].plays},
    {n:'YouTube Music', status:'Live',   date:'2024-09-04', terr:'World',        plays:dsps[2].plays},
    {n:'Amazon Music',status:'Live',     date:'2024-09-04', terr:'World',        plays:dsps[3].plays},
    {n:'Tidal',       status:'Live',     date:'2024-09-04', terr:'World',        plays:dsps[4].plays},
    {n:'Deezer',      status:'Live',     date:'2024-09-04', terr:'World',        plays:0.4},
    {n:'Pandora',     status:'Live',     date:'2024-09-11', terr:'US · CA · AU', plays:0.6},
    {n:'SoundCloud',  status:'Live',     date:'2024-09-04', terr:'World',        plays:0.2},
    {n:'Bandcamp',    status:'Live',     date:'2024-09-04', terr:'World',        plays:0.1},
    {n:'NetEase',     status:'Pending',  date:'2026-04-22', terr:'CN',            plays:0},
    {n:'Yandex',      status:'Withheld', date:'—',          terr:'RU',            plays:0},
  ];
  return (
    <div>
      <Section num="01">DSP delivery · 14 destinations</Section>
      <table className="ff-mono" style={{width:'100%',fontSize:12,borderCollapse:'collapse',marginBottom:24}}>
        <thead>
          <tr style={{borderBottom:'1px solid var(--rule)'}}>
            <th className="upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500}}>DSP</th>
            <th className="upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500,width:120}}>STATUS</th>
            <th className="upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500,width:140}}>DELIVERED</th>
            <th className="upper" style={{textAlign:'left',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500,width:200}}>TERRITORIES</th>
            <th className="upper" style={{textAlign:'right',padding:'10px 0',fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',fontWeight:500,width:100}}>30D PLAYS</th>
          </tr>
        </thead>
        <tbody>
          {dsps_full.map(d => (
            <tr key={d.n} onClick={()=>window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:`${d.n} delivery details`,tone:'ok'}}))}
              style={{borderBottom:'1px solid var(--rule-soft)',cursor:'pointer'}}
              onMouseEnter={e=>e.currentTarget.style.background='var(--bg-2)'}
              onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
              <td style={{padding:'12px 0',fontSize:13,fontWeight:500}}>{d.n}</td>
              <td style={{padding:'12px 0'}}><Pill tone={d.status==='Live'?'ok':d.status==='Pending'?'accent':'soft'} dot>{d.status.toLowerCase()}</Pill></td>
              <td style={{padding:'12px 0',color:'var(--ink-2)'}}>{d.date}</td>
              <td style={{padding:'12px 0',color:'var(--ink-2)'}}>{d.terr}</td>
              <td style={{padding:'12px 0',textAlign:'right'}} className="num">{d.plays ? `${d.plays}M` : '—'}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <Section num="02">Recent transmissions</Section>
      <div>
        {[
          {when:'2 days ago', what:'DDEX ERN 4.3 · TakedownNotification · Yandex (RU)', status:'queued'},
          {when:'2 weeks ago', what:'DDEX ERN 4.3 · NewReleaseMessage · NetEase (CN)', status:'pending ack'},
          {when:'1 month ago', what:'DDEX ERN 4.3 · UpdateRelease · 13 DSPs (metadata fix)', status:'acked'},
          {when:'18 months ago', what:'DDEX ERN 4.3 · NewReleaseMessage · 13 DSPs', status:'acked'},
        ].map((tx,i)=>(
          <div key={i} onClick={()=>window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:`Transmission — ${tx.what}`,tone:'ok'}}))}
            style={{display:'grid',gridTemplateColumns:'140px 1fr 110px',gap:16,padding:'12px 0',borderBottom:'1px solid var(--rule-soft)',alignItems:'center',cursor:'pointer'}}
            onMouseEnter={e=>e.currentTarget.style.background='var(--bg-2)'}
            onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
            <span className="ff-mono" style={{fontSize:11,color:'var(--ink-3)'}}>{tx.when}</span>
            <span style={{fontSize:13}}>{tx.what}</span>
            <span><Pill tone={tx.status==='acked'?'ok':tx.status==='queued'?'accent':'soft'} dot>{tx.status}</Pill></span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─────────────────────────────── Earnings tab
// Mirrors WorkRoyaltiesTab depth, but tuned for MASTER economics:
//   - Sources are streaming/downloads/sync(master)/neighboring/physical (not perf/mech)
//   - "By writer" is replaced with "By recipient" (owners + producer points + feat artists + distro)
//   - DSP-level table is preserved as a 4th breakdown view
function RecEarnings({ r, dsps, ext, go, setTab }) {
  const _useS = (typeof React !== 'undefined' && React.useState) || ((v)=>[v,()=>{}]);
  const [period, setPeriod] = _useS('12mo');
  const [view, setView]     = _useS('source'); // source | territory | recipient | dsp

  // Stable per-recording PRNG so numbers don't shuffle on re-render
  const seedKey = (r.isrc || r.id || r.title || '').split('').reduce((s,c)=>s+c.charCodeAt(0),0);
  const rnd = (n) => ((Math.sin(seedKey*0.137 + n) + 1) / 2);

  // Master rate is much lower per stream than song-side; numbers reflect that
  const ratePerStream = 0.0036;
  const totalStreams30d = dsps.reduce((s,d)=>s+d.plays,0); // millions
  const earnedQ = Math.round(totalStreams30d*1e6*ratePerStream*3 + rnd(1)*4000);    // ~3 months @ 30d rate
  const earned12mo = Math.round(totalStreams30d*1e6*ratePerStream*12 + rnd(2)*22000);
  const earnedAll = Math.round(earned12mo * (2.4 + rnd(3)*1.6));

  const totals = {
    'q':    { l:'This quarter',   earned: earnedQ,    pending: Math.round(earnedQ*0.18),    period:'Apr–Jun 2026' },
    '12mo': { l:'Last 12 months', earned: earned12mo, pending: Math.round(earned12mo*0.09), period:'May 2025 – Apr 2026' },
    'all':  { l:'All time',       earned: earnedAll,  pending: Math.round(earnedAll*0.04),  period:`Since ${(r.year || ext.pLineYear || '2023')}` },
  };
  const T = totals[period];
  const fmt = (n) => '$' + n.toLocaleString();

  // Streams scale by period
  const streamsForPeriod = period==='q' ? totalStreams30d*3 : period==='12mo' ? totalStreams30d*12 : totalStreams30d*30;

  // ─── Breakdown 1: by source (master-side: streaming dominant)
  const sourceRows = [
    { k:'streaming',   l:'Streaming',     detail:'DSP statements · ad-supported & premium tiers',  pct: 0.78 + rnd(4)*0.04 },
    { k:'downloads',   l:'Downloads',     detail:'Permanent download · iTunes, Beatport',          pct: 0.04 + rnd(5)*0.02 },
    { k:'sync',        l:'Sync (master)', detail:'Film · TV · advertising · games',                pct: 0.09 + rnd(6)*0.03 },
    { k:'neighbouring',l:'Neighboring',   detail:'NRO · digital perf · public perf of master',      pct: 0.06 + rnd(7)*0.02 },
    { k:'physical',    l:'Physical & other', detail:'Vinyl · CD · merch sync · stem licensing',    pct: 0.03 + rnd(8)*0.01 },
  ];
  const sourceTot = sourceRows.reduce((a,b)=>a+b.pct,0);
  sourceRows.forEach(s => { s.pct = s.pct/sourceTot; s.amt = Math.round(T.earned * s.pct); });

  // ─── Breakdown 2: by territory
  const territoryRows = [
    { k:'us', l:'United States', flag:'🇺🇸', pct: 0.46 + rnd(10)*0.04 },
    { k:'gb', l:'United Kingdom',flag:'🇬🇧', pct: 0.11 + rnd(11)*0.02 },
    { k:'de', l:'Germany',       flag:'🇩🇪', pct: 0.07 + rnd(12)*0.02 },
    { k:'fr', l:'France',        flag:'🇫🇷', pct: 0.06 + rnd(13)*0.02 },
    { k:'jp', l:'Japan',         flag:'🇯🇵', pct: 0.05 + rnd(14)*0.02 },
    { k:'br', l:'Brazil',        flag:'🇧🇷', pct: 0.04 + rnd(15)*0.02 },
    { k:'mx', l:'Mexico',        flag:'🇲🇽', pct: 0.04 + rnd(16)*0.01 },
    { k:'ww', l:'Rest of world', flag:'🌐', pct: 0.17 + rnd(17)*0.02 },
  ];
  const tTot = territoryRows.reduce((a,b)=>a+b.pct,0);
  territoryRows.forEach(s => { s.pct = s.pct/tTot; s.amt = Math.round(T.earned * s.pct); });

  // ─── Breakdown 3: by recipient (master payees — replaces "by writer")
  // Pull master owners from ext, then layer producer points + featured + distro fee.
  const ownerShareOf = 0.80; // 80% of net flows to master owners after distro fee + producer points
  const owners = (ext?.recordingOwners || []).filter(o => +o.share > 0);
  const ownerTotalShare = owners.reduce((s,o)=>s+(+o.share||0), 0) || 100;
  const recipientRows = [
    ...owners.map(o => ({
      kind:'owner', name:o.owner, role:'Master rights holder',
      pct: ownerShareOf * (o.share/ownerTotalShare),
    })),
    { kind:'distro',   name:'Distributor', role:'15% admin fee · per agreement', pct: 0.15 },
    { kind:'producer', name:'Producer pool', role:'5 producer points · post-distro', pct: 0.05 },
  ];
  // Some recordings have featured-artist royalties carved out of owner share — fold in if any
  const featured = (ext?.featuredRoyalties || []).filter(f => +f.share > 0);
  if (featured.length) {
    const ftTot = featured.reduce((s,f)=>s+(+f.share||0),0);
    // Take feat payout from owner share (already in ownerShareOf)
    recipientRows.unshift(...featured.map(f => ({
      kind:'featured', name:f.name, role:`Feat. royalty · ${f.share}%`,
      pct: ownerShareOf * (f.share/100),
    })));
  }
  const recTot = recipientRows.reduce((s,o)=>s+o.pct,0);
  recipientRows.forEach(s => { s.pct = s.pct/recTot; s.amt = Math.round(T.earned * s.pct); });

  // ─── Breakdown 4: by DSP (preserves the original platform table)
  const dspRows = dsps.map(d => {
    const streams = d.plays * (period==='q'?3:period==='12mo'?12:30); // millions, scaled
    const gross = Math.round(streams * 1e6 * ratePerStream);
    return { k:d.n, l:d.n, detail:`${streams.toFixed(1)}M streams · $${(gross/streams/1e6).toFixed(4)}/stream`, pct: gross/T.earned, amt: gross, streams };
  }).sort((a,b)=>b.amt-a.amt);
  const dspTot = dspRows.reduce((s,d)=>s+d.pct,0) || 1;
  dspRows.forEach(d => { d.pct = d.pct/dspTot; }); // normalise to 100%

  // ─── Recent statements — master side: DSP statements + NRO + sub-pub for sync
  const statements = [
    { src:'Spotify',     ts:'2026-04-08', amount: Math.round(T.earned * 0.30), period:'Mar 2026', status:'parsed', kind:'DSP' },
    { src:'Apple Music', ts:'2026-04-05', amount: Math.round(T.earned * 0.16), period:'Mar 2026', status:'parsed', kind:'DSP' },
    { src:'YouTube',     ts:'2026-04-02', amount: Math.round(T.earned * 0.13), period:'Mar 2026', status:'parsed', kind:'DSP' },
    { src:'Amazon',      ts:'2026-03-28', amount: Math.round(T.earned * 0.06), period:'Mar 2026', status:'parsed', kind:'DSP' },
    { src:'PPL (NRO)',   ts:'2026-03-12', amount: Math.round(T.earned * 0.04), period:'Q4 2025', status:'parsed', kind:'NRO' },
    { src:'Tidal',       ts:'2026-03-08', amount: Math.round(T.earned * 0.03), period:'Mar 2026', status:'pending', kind:'DSP' },
    { src:'SoundExchange', ts:'2026-02-22', amount: Math.round(T.earned * 0.05), period:'Q4 2025', status:'parsed', kind:'NRO' },
  ];

  // 12-month spark trend
  const trend = (typeof window !== 'undefined' && window.sparkRoyalty) ? window.sparkRoyalty : Array.from({length:30},(_,i)=>20+i*3+rnd(i)*10);

  const PALETTE = ['oklch(0.62 0.16 50)','oklch(0.55 0.13 240)','oklch(0.68 0.14 140)','oklch(0.58 0.17 25)','oklch(0.50 0.10 290)','oklch(0.65 0.12 85)','oklch(0.45 0.08 200)','oklch(0.72 0.10 60)'];

  const rowsByView = view === 'source' ? sourceRows : view === 'territory' ? territoryRows : view === 'recipient' ? recipientRows : dspRows;

  // Top DSP for hero
  const topDsp = dspRows[0];

  const toast = (msg) => window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg,tone:'ok'}}));

  return (
    <div>
      {/* Header */}
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', padding:'0 0 14px', marginBottom:18, borderBottom:'1px solid var(--rule)' }}>
        <div>
          <div className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.12em', color:'var(--ink-3)', marginBottom:4 }}>EARNINGS</div>
          <div className="ff-display" style={{ fontSize:22, fontWeight:600, letterSpacing:'-0.01em' }}>
            Master royalties & statements
          </div>
        </div>
        <div style={{ display:'flex', gap:8 }}>
          <button onClick={()=>go && go('analytics')}
            className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.08em', padding:'8px 14px', background:'var(--bg)', color:'var(--ink-2)', border:'1px solid var(--rule)', cursor:'pointer', display:'flex', alignItems:'center', gap:6 }}>
            <Ic.Bar width={12} height={12}/> Open analytics
          </button>
          <button onClick={()=>toast(`Master statement for "${r.title}" exported as CSV`)}
            className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.08em', padding:'8px 14px', background:'var(--ink)', color:'var(--bg)', border:0, cursor:'pointer', display:'flex', alignItems:'center', gap:6 }}>
            <Ic.File width={12} height={12}/> Export statement
          </button>
        </div>
      </div>

      {/* Period selector */}
      <div style={{ display:'flex', gap:6, marginBottom:24 }}>
        {Object.entries(totals).map(([k, v]) => {
          const active = period === k;
          return (
            <button key={k} onClick={()=>setPeriod(k)}
              className="ff-mono upper" style={{
                fontSize:10, letterSpacing:'.08em', padding:'8px 14px',
                background: active ? 'var(--ink)' : 'var(--bg)',
                color: active ? 'var(--bg)' : 'var(--ink-2)',
                border:'1px solid '+(active ? 'var(--ink)' : 'var(--rule)'),
                cursor:'pointer'
              }}>{v.l}</button>
          );
        })}
      </div>

      {/* Hero KPIs */}
      <div style={{ display:'grid', gridTemplateColumns:'1.4fr 1fr 1fr', gap:0, border:'1px solid var(--rule)', marginBottom:28 }}>
        <div style={{ padding:'22px 24px', borderRight:'1px solid var(--rule)' }}>
          <div className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.12em', color:'var(--ink-3)', marginBottom:6 }}>EARNED · {T.period}</div>
          <div className="ff-display num" style={{ fontSize:48, fontWeight:600, letterSpacing:'-0.04em', lineHeight:1 }}>{fmt(T.earned)}</div>
          <div className="ff-mono num" style={{ fontSize:11, color:'#5b8a3a', marginTop:6 }}>▲ +{(8 + rnd(20)*15).toFixed(1)}% vs prior period</div>
          <div style={{ marginTop:14, marginLeft:-4 }}>
            <Spark data={trend} w={400} h={68} stroke="var(--ink)" area fill="var(--ink-2)" strokeWidth={1.5} />
          </div>
        </div>
        <div style={{ padding:'22px 24px', borderRight:'1px solid var(--rule)' }}>
          <div className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.12em', color:'var(--ink-3)', marginBottom:6 }}>PENDING</div>
          <div className="ff-display num" style={{ fontSize:36, fontWeight:600, letterSpacing:'-0.03em', lineHeight:1 }}>{fmt(T.pending)}</div>
          <div className="ff-mono" style={{ fontSize:11, color:'var(--ink-3)', marginTop:6 }}>across {Math.round(2 + rnd(21)*4)} sources</div>
          <div className="ff-mono upper" style={{ fontSize:9, letterSpacing:'.1em', color:'var(--ink-3)', marginTop:18 }}>NEXT PAYOUT</div>
          <div className="ff-mono" style={{ fontSize:13, color:'var(--ink)', marginTop:2 }}>Spotify · 2026-05-15</div>
        </div>
        <div style={{ padding:'22px 24px' }}>
          <div className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.12em', color:'var(--ink-3)', marginBottom:6 }}>STREAMS · {period==='q'?'90D':period==='12mo'?'12MO':'ALL TIME'}</div>
          <div className="ff-display num" style={{ fontSize:36, fontWeight:600, letterSpacing:'-0.03em', lineHeight:1 }}>{streamsForPeriod.toFixed(1)}M</div>
          <div className="ff-mono" style={{ fontSize:11, color:'var(--ink-3)', marginTop:6 }}>${(T.earned/streamsForPeriod/1e6).toFixed(4)} per stream</div>
          <div className="ff-mono upper" style={{ fontSize:9, letterSpacing:'.1em', color:'var(--ink-3)', marginTop:18 }}>TOP DSP</div>
          <div className="ff-mono" style={{ fontSize:13, color:'var(--ink)', marginTop:2 }}>{topDsp ? `${topDsp.l} · ${(topDsp.pct*100).toFixed(0)}%` : '—'}</div>
        </div>
      </div>

      {/* Breakdown view toggle */}
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom:14 }}>
        <div className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.12em', color:'var(--ink-3)' }}>BREAKDOWN</div>
        <div style={{ display:'flex', gap:0, border:'1px solid var(--rule)' }}>
          {[{k:'source',l:'By source'},{k:'territory',l:'By territory'},{k:'recipient',l:'By recipient'},{k:'dsp',l:'By DSP'}].map((v, i, a) => {
            const active = view === v.k;
            return (
              <button key={v.k} onClick={()=>setView(v.k)}
                className="ff-mono upper" style={{
                  fontSize:10, letterSpacing:'.08em', padding:'7px 12px',
                  background: active ? 'var(--ink)' : 'var(--bg)',
                  color: active ? 'var(--bg)' : 'var(--ink-2)',
                  border:0, borderRight: i<a.length-1 ? '1px solid var(--rule)' : 0,
                  cursor:'pointer'
                }}>{v.l}</button>
            );
          })}
        </div>
      </div>

      {/* Stacked bar */}
      <div style={{ display:'flex', height:32, border:'1px solid var(--ink)', marginBottom:2, overflow:'hidden' }}>
        {rowsByView.map((row, i) => {
          const pct = row.pct;
          if (pct < 0.001) return null;
          return (
            <div key={i} title={`${row.l || row.name}: ${(pct*100).toFixed(1)}%`}
              style={{ width: (pct*100)+'%', background: PALETTE[i%PALETTE.length], borderRight: i < rowsByView.length-1 ? '1px solid var(--bg)' : 0 }}/>
          );
        })}
      </div>

      {/* Breakdown rows */}
      <div style={{ border:'1px solid var(--rule)', borderTop:0, marginBottom:32 }}>
        {rowsByView.length === 0 ? (
          <div style={{ padding:30, textAlign:'center', color:'var(--ink-3)', fontSize:13 }}>No data for this view yet.</div>
        ) : rowsByView.map((row, i) => {
          const pct = row.pct;
          const amt = row.amt;
          const labelL = row.l || row.name;
          const labelR = view==='source' ? row.detail
                       : view==='territory' ? row.k.toUpperCase()
                       : view==='recipient' ? row.role
                       : row.detail; // dsp
          return (
            <div key={i} style={{
              display:'grid', gridTemplateColumns:'12px 1.4fr 1fr 80px 110px',
              gap:14, padding:'12px 14px', alignItems:'center',
              borderBottom: i < rowsByView.length-1 ? '1px solid var(--rule-soft)' : 'none',
            }}>
              <span style={{ width:10, height:10, background:PALETTE[i%PALETTE.length], display:'inline-block' }}/>
              <div>
                <div style={{ fontSize:13, fontWeight:600 }}>
                  {view==='territory' && <span style={{marginRight:6}}>{row.flag}</span>}
                  {labelL}
                </div>
                <div className="ff-mono" style={{ fontSize:10, color:'var(--ink-3)', marginTop:2, letterSpacing:'.04em' }}>{labelR}</div>
              </div>
              <div style={{ height:4, background:'var(--rule-soft)', position:'relative' }}>
                <div style={{ position:'absolute', inset:0, width:(pct*100)+'%', background:PALETTE[i%PALETTE.length] }}/>
              </div>
              <span className="ff-mono num" style={{ fontSize:12, color:'var(--ink-2)', textAlign:'right' }}>{(pct*100).toFixed(1)}%</span>
              <span className="ff-mono num" style={{ fontSize:13, fontWeight:600, textAlign:'right' }}>{fmt(amt)}</span>
            </div>
          );
        })}
      </div>

      {/* Recent statements */}
      <div style={{ marginBottom:28 }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom:14 }}>
          <div className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.12em', color:'var(--ink-3)' }}>RECENT STATEMENTS</div>
          <button onClick={()=>go && go('royalties')}
            className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.06em', padding:'4px 8px', background:'transparent', color:'var(--ink-2)', border:'1px solid var(--rule)', cursor:'pointer' }}>
            View all in royalties →
          </button>
        </div>
        <div style={{ border:'1px solid var(--rule)' }}>
          <div className="ff-mono upper" style={{
            display:'grid', gridTemplateColumns:'1.4fr 60px 1fr 130px 100px 100px',
            gap:14, padding:'10px 14px', fontSize:10, color:'var(--ink-3)',
            background:'var(--bg-2)', borderBottom:'1px solid var(--rule)' }}>
            <span>SOURCE</span><span>KIND</span><span>PERIOD</span><span>RECEIVED</span><span>STATUS</span><span style={{textAlign:'right'}}>AMOUNT</span>
          </div>
          {statements.map((s, i) => (
            <div key={i} onClick={()=>toast(`${s.src} · ${s.period} statement opened`)}
              style={{
                display:'grid', gridTemplateColumns:'1.4fr 60px 1fr 130px 100px 100px',
                gap:14, padding:'12px 14px', alignItems:'center', cursor:'pointer',
                borderBottom: i < statements.length-1 ? '1px solid var(--rule-soft)' : 'none',
              }}
              onMouseEnter={el=>el.currentTarget.style.background='var(--bg-2)'}
              onMouseLeave={el=>el.currentTarget.style.background='transparent'}>
              <span style={{ fontSize:13, fontWeight:600 }}>{s.src}</span>
              <span className="ff-mono upper" style={{ fontSize:9, letterSpacing:'.08em', color:'var(--ink-3)' }}>{s.kind}</span>
              <span style={{ fontSize:13, color:'var(--ink)' }}>{s.period}</span>
              <span className="ff-mono num" style={{ fontSize:11, color:'var(--ink-2)' }}>{s.ts}</span>
              <span className="ff-mono upper" style={{ fontSize:9, letterSpacing:'.1em', fontWeight:600,
                color: s.status==='parsed' ? '#5b8a3a' : '#b78113',
                display:'inline-flex', alignItems:'center', gap:5 }}>
                <span style={{ width:6, height:6, borderRadius:'50%', background: s.status==='parsed' ? '#5b8a3a' : '#b78113', display:'inline-block' }}/>
                {s.status}
              </span>
              <span className="ff-mono num" style={{ fontSize:13, fontWeight:600, textAlign:'right' }}>{fmt(s.amount)}</span>
            </div>
          ))}
        </div>
      </div>

      {/* Pipeline + help — two columns at the bottom */}
      <div style={{ display:'grid', gridTemplateColumns:'1.4fr 1fr', gap:32 }}>
        <div>
          <div className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.12em', color:'var(--ink-3)', marginBottom:14 }}>ROYALTY PIPELINE · {T.period}</div>
          <ol style={{listStyle:'none',padding:0,margin:0}}>
            {[
              {label:'DSP statements ingested', done:true, note:`Spotify, Apple, YT, Amazon, Tidal · ${10+Math.round(rnd(30)*4)} of 14`},
              {label:'Recording → release matched', done:true, note:'ISRC + UPC join'},
              {label:'Owner shares applied', done:true, note: owners.length ? `${owners.map(o=>o.share+'% '+o.owner).join(' · ')}` : 'No master owners on record'},
              {label:'Producer points deducted', done:true, note:'5% to producer pool · post-distro'},
              {label:'Distributor fee applied', done:true, note:'15% · per agreement'},
              {label:'Featured artist royalties applied', done: featured.length>0, note: featured.length ? `${featured.length} featured carve-out${featured.length>1?'s':''}` : 'None on record'},
              {label:'Released to royalty statements', done:false, note:'Cycle closes 2026-05-15'},
            ].map((s,i,a)=>(
              <li key={i} style={{position:'relative',paddingLeft:28,paddingBottom:14}}>
                <span style={{position:'absolute',left:0,top:2,width:14,height:14,border:'1.5px solid var(--ink)',background: s.done ? 'var(--ink)' : 'var(--bg)'}}>
                  {s.done && <span style={{position:'absolute',inset:1,display:'flex',alignItems:'center',justifyContent:'center',color:'var(--bg)',fontSize:9,fontWeight:700}}>✓</span>}
                </span>
                {i<a.length-1 && <span style={{position:'absolute',left:6,top:18,bottom:0,width:2,background:'var(--rule)'}}/>}
                <div style={{fontSize:13,fontWeight:500,color: s.done ? 'var(--ink)' : 'var(--ink-3)'}}>{s.label}</div>
                <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2}}>{s.note}</div>
              </li>
            ))}
          </ol>
        </div>

        <div>
          <div style={{ padding:16, background:'var(--bg-2)', border:'1px solid var(--rule-soft)', display:'flex', gap:14, alignItems:'flex-start', marginBottom:14 }}>
            <div className="ff-display" style={{ width:28, height:28, border:'1px solid var(--rule)', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0, background:'var(--bg)', fontSize:14, fontStyle:'italic', fontWeight:600 }}>
              i
            </div>
            <div>
              <div style={{ fontSize:13, fontWeight:600, marginBottom:4 }}>About these figures</div>
              <div style={{ fontSize:12, color:'var(--ink-2)', lineHeight:1.55 }}>
                Master royalties only — composition royalties from streaming, mechanical, and performance flow to the underlying <button onClick={()=>{ const w = (typeof window.WORK_BY_ID !== 'undefined') ? window.WORK_BY_ID[r.workId] : null; if (w && go) go('work', w); else toast('Underlying work record'); }} className="ff-mono" style={{ color:'var(--ink)', background:'transparent', border:0, padding:0, cursor:'pointer', textDecoration:'underline' }}>work</button>. Owner shares come from <button onClick={()=>setTab && setTab('rights')} className="ff-mono" style={{ color:'var(--ink)', background:'transparent', border:0, padding:0, cursor:'pointer', textDecoration:'underline' }}>Rights</button>.
              </div>
            </div>
          </div>

          <div style={{ padding:14, border:'1px solid var(--rule)' }}>
            <div className="ff-mono upper" style={{ fontSize:10, letterSpacing:'.12em', color:'var(--ink-3)', marginBottom:10 }}>MASTER ECONOMICS</div>
            <div style={{ display:'grid', gridTemplateColumns:'1fr auto', rowGap:8, columnGap:14, fontSize:12 }}>
              <span style={{color:'var(--ink-3)'}}>Blended rate / stream</span>
              <span className="ff-mono num">${ratePerStream.toFixed(4)}</span>
              <span style={{color:'var(--ink-3)'}}>Distributor fee</span>
              <span className="ff-mono num">15.0%</span>
              <span style={{color:'var(--ink-3)'}}>Producer points</span>
              <span className="ff-mono num">5.0%</span>
              <span style={{color:'var(--ink-3)'}}>Owner net (effective)</span>
              <span className="ff-mono num">{(ownerShareOf*100).toFixed(1)}%</span>
              <span style={{color:'var(--ink-3)'}}>Sub-publishers (CN/JP)</span>
              <span className="ff-mono num">manual</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────── Activity tab
function RecActivity({ r, go, setTab }) {
  const toast = (msg) => window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg,tone:'ok'}}));
  const events = [
    {when:'2 days ago', what:'DDEX ERN 4.3 takedown queued · Yandex (RU)', who:'system', kind:'distribution', cta:'View transmission',  fire:()=>setTab('distribution')},
    {when:'4 days ago', what:'New UGC reference detected · YouTube Content ID', who:'system', kind:'detection', cta:'Review match', fire:()=>toast('YouTube Content ID match queued for review')},
    {when:'1 week ago', what:'Royalty cycle 2026-Q1 closed · $4,212.18 distributed', who:'system', kind:'royalty', cta:'Open earnings', fire:()=>setTab('earnings')},
    {when:'2 weeks ago', what:'Pending: NetEase delivery awaiting CN sub-publisher confirmation', who:'a.cohen', kind:'distribution', cta:'View delivery', fire:()=>setTab('distribution')},
    {when:'3 weeks ago', what:'Metadata edited: producer credits added (Riccardo Damian)', who:'a.cohen', kind:'edit', cta:'View contributors', fire:()=>setTab('contributors')},
    {when:'1 month ago', what:'UpdateRelease delivered · 13 DSPs · explicit flag corrected', who:'system', kind:'distribution', cta:'View transmission', fire:()=>setTab('distribution')},
    {when:'5 months ago', what:'Linked to work T-308.901.522-0', who:'a.cohen', kind:'link', cta:'Open work', fire:()=>{ const w = (typeof WORK_BY_ID !== 'undefined') ? WORK_BY_ID[r.workId] : null; if (w) go('work', w); else toast('Underlying work record'); }},
    {when:'9 months ago', what:'Cleared sample: "When the Sun Goes Down" (Source: ATCO 1976)', who:'k.weiss', kind:'rights', cta:'View rights', fire:()=>setTab('rights')},
    {when:'1 year ago', what:'ISRC validated · IFPI registry', who:'system', kind:'validation', cta:'Copy ISRC', fire:()=>{ navigator.clipboard?.writeText(r.isrc); toast(`ISRC ${r.isrc} copied`); }},
    {when:'1 year ago', what:'Imported from label feed (Saint / Columbia)', who:'feed.sc', kind:'ingestion', cta:'View label', fire:()=>window.dispatchEvent(new CustomEvent('astro-open-label',{detail:{name:r.label}}))},
    {when:'18 months ago', what:'NewReleaseMessage delivered to 13 DSPs', who:'system', kind:'distribution', cta:'View transmission', fire:()=>setTab('distribution')},
  ];
  const colors = {distribution:'var(--accent)',detection:'#7d3da8',royalty:'#5b8a3a',edit:'#0b0b0b',link:'var(--ink-3)',rights:'#9b6a18',validation:'#5b8a3a',ingestion:'#3056c8'};
  const KINDS = ['all','distribution','royalty','edit','rights','detection','link','validation','ingestion'];
  const [filter, setFilter] = React.useState('all');
  const filtered = filter === 'all' ? events : events.filter(e => e.kind === filter);
  return (
    <div>
      <Section num="01">Activity · {filtered.length}{filter !== 'all' ? ` of ${events.length}` : ''} events</Section>
      <div style={{display:'flex',flexWrap:'wrap',gap:6,marginBottom:18}}>
        {KINDS.map(k => (
          <button key={k} onClick={()=>setFilter(k)} className="ff-mono upper"
            style={{padding:'5px 10px',fontSize:10,letterSpacing:'.08em',fontWeight:600,
              background: filter===k ? 'var(--ink)' : 'transparent',
              color:    filter===k ? 'var(--bg)'  : 'var(--ink-2)',
              border:'1px solid', borderColor: filter===k ? 'var(--ink)' : 'var(--rule)', cursor:'pointer'}}>
            {k}
          </button>
        ))}
      </div>
      <div style={{position:'relative'}}>
        <span style={{position:'absolute',left:6,top:0,bottom:0,width:1,background:'var(--rule)'}}/>
        {filtered.map((e,i)=>(
          <div key={i} onClick={e.fire}
            style={{display:'grid',gridTemplateColumns:'140px 1fr 140px 110px 14px',gap:16,padding:'14px 0 14px 28px',borderBottom:'1px solid var(--rule-soft)',position:'relative',alignItems:'baseline',cursor:'pointer'}}
            onMouseEnter={el=>el.currentTarget.style.background='var(--bg-2)'}
            onMouseLeave={el=>el.currentTarget.style.background='transparent'}>
            <span style={{position:'absolute',left:2,top:18,width:8,height:8,background:colors[e.kind]||'var(--ink)',borderRadius:'50%'}}/>
            <span className="ff-mono" style={{fontSize:11,color:'var(--ink-3)'}}>{e.when}</span>
            <span style={{fontSize:13}}>{e.what}</span>
            <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'var(--ink-2)',fontWeight:600}}>{e.cta}</span>
            <span className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',textAlign:'right'}}>{e.who}</span>
            <Ic.Right width={12} height={12} style={{color:'var(--ink-3)'}}/>
          </div>
        ))}
        {filtered.length === 0 && (
          <div className="ff-mono upper" style={{fontSize:11,color:'var(--ink-3)',padding:'24px 0 12px 28px',letterSpacing:'.1em'}}>No {filter} events</div>
        )}
      </div>
      <div style={{display:'flex',gap:10,marginTop:24}}>
        <Btn variant="ghost" icon={<Ic.Down/>} onClick={()=>toast('Activity log exported as CSV')}>Export log</Btn>
        <Btn variant="ghost" icon={<Ic.Bell/>} onClick={()=>toast('Subscribed to recording activity')}>Subscribe</Btn>
      </div>
    </div>
  );
}

Object.assign(window, { ScreenRecording });
