// songs.jsx — "Songs" tab: works as the spine, recordings nested, with sample/interpolation/release graph.

const { useState: useSongs, useMemo: useSongsMemo } = React;

// ─────────────────────────────────────────────────────────────
// EXTENDED CATALOG GRAPH
// Songs (works) ↔ Recordings (many) ↔ Releases (many-to-many)
// Recordings can derive from other recordings (sample / interpolation / cover / remix),
// and can pull in MORE works (the sampled work) on top of their primary work.
// ─────────────────────────────────────────────────────────────

// Extra works that are referenced as samples/interpolations only —
// they extend the Songs list with a few unrecorded + classic works.
const EXTRA_WORKS = [
  // Unrecorded (Pluralis pitch slate)
  {id:'w_11',iswc:'T-410.992.557-3',title:'Glasshouse Mornings',writers:['Solange Knowles','Devonté Hynes'],pro:'ASCAP',status:'registered',shares:'70/30',catalog:'SK Catalog',copyright:'© 2024 SK / Domino',duration:0,plays:0,societies:2,unrecorded:true},
  {id:'w_12',iswc:'T-300.118.404-9',title:'Even Tide',writers:['Sam Shepherd'],pro:'PRS',status:'pending',shares:'100',catalog:'PluralPub',copyright:'© 2025 Pluralis Music',duration:0,plays:0,societies:1,unrecorded:true},
  {id:'w_13',iswc:'T-887.654.221-0',title:'Telephone Pole Etude',writers:['Roberto Lange','Mary Lattimore'],pro:'ASCAP',status:'pending',shares:'60/40',catalog:'HN Pub',copyright:'© 2025 Helado Negro',duration:0,plays:0,societies:1,unrecorded:true},
  // Sampled / interpolated source works (older catalog)
  {id:'w_s1',iswc:'T-100.244.881-6',title:'Stretch You Out',writers:['Solange Knowles','Devonté Hynes'],pro:'ASCAP',status:'registered',shares:'80/20',catalog:'SK Catalog',copyright:'© 2019 SK / Domino',duration:222,plays:48211049,societies:5,external:true},
  {id:'w_s2',iswc:'T-455.001.992-7',title:'Spaceship',writers:['Karriem Riggins','Madlib'],pro:'BMI',status:'registered',shares:'50/50',catalog:'Madlib Inv. Music',copyright:'© 2003 Stones Throw',duration:198,plays:14400226,societies:4,external:true},
  {id:'w_s3',iswc:'T-722.331.010-2',title:'Slow Burn',writers:['Maxine Funke'],pro:'APRA',status:'registered',shares:'100',catalog:'Funke Music',copyright:'© 2008 Disciples',duration:181,plays:2204881,societies:3,external:true},
];

// Recording graph — many recordings per work + parent/child sample chain + multiple releases per recording.
// Each recording has:
//   workId        — the primary underlying work
//   alsoWorks[]   — additional works pulled in via sampling/interpolation, with cleared %
//   derivesFrom[] — parent recordings (samples, master-use clears, remixes)
//   releaseIds[]  — every release the recording appears on
const RECORDING_GRAPH = [
  // Cranes In The Sky — 3 recordings (album, live, cover)
  {id:'r_01',workId:'w_01',isrc:'USQX91600922',title:'Cranes In The Sky',artist:'Solange',album:'A Seat at the Table',label:'Saint / Columbia',duration:243,year:2016,plays:412,art:'#f4d34a',explicit:false,derivesFrom:[],alsoWorks:[],releaseIds:['rl_01','rl_01d','rl_comp_qro']},
  {id:'r_01b',workId:'w_01',isrc:'USQX91800145',title:'Cranes In The Sky (Live at the Guggenheim)',artist:'Solange',album:'When I Get Home (Live Companion)',label:'Saint / Columbia',duration:298,year:2018,plays:21,art:'#d4a02a',explicit:false,derivesFrom:[{kind:'live',ofRecordingId:'r_01'}],alsoWorks:[],releaseIds:['rl_01d']},
  {id:'r_01c',workId:'w_01',isrc:'USDM12200881',title:'Cranes In The Sky (Cover)',artist:'Lianne La Havas',album:'On the Roof / KEXP',label:'Nonesuch',duration:251,year:2022,plays:5,art:'#a35d3a',explicit:false,derivesFrom:[{kind:'cover'}],alsoWorks:[],releaseIds:['rl_kexp_v3']},

  // Birds — 1 recording
  {id:'r_02',workId:'w_02',isrc:'GBHJN1900184',title:'Birds',artist:'Floating Points',album:'Crush',label:'Ninja Tune',duration:362,year:2019,plays:88,art:'#2a4d8f',explicit:false,derivesFrom:[],alsoWorks:[],releaseIds:['rl_02']},

  // Bunny Is A Rider — 2 recordings (orig + Erol Alkan remix)
  {id:'r_03',workId:'w_03',isrc:'USQX52102201',title:'Bunny Is A Rider',artist:'Caroline Polachek',album:'Desire, I Want To Turn Into You',label:'Perpetual Novice',duration:171,year:2021,plays:64,art:'#e8693a',explicit:false,derivesFrom:[],alsoWorks:[],releaseIds:['rl_03']},
  {id:'r_03b',workId:'w_03',isrc:'GBKPL2200992',title:'Bunny Is A Rider (Erol Alkan Re-Edit)',artist:'Caroline Polachek',album:'Bunny Remixes',label:'Perpetual Novice',duration:402,year:2022,plays:8,art:'#cc4d2a',explicit:false,derivesFrom:[{kind:'remix',ofRecordingId:'r_03'}],alsoWorks:[],releaseIds:['rl_bunny_rmx']},

  // Send Me — 1 recording
  {id:'r_04',workId:'w_04',isrc:'GBKPL1801244',title:'Send Me',artist:'Tirzah',album:'Devotion',label:'Domino',duration:204,year:2018,plays:18,art:'#5a3d1c',explicit:false,derivesFrom:[],alsoWorks:[],releaseIds:['rl_04']},

  // Far In — 1 recording, interpolates "Slow Burn" by Maxine Funke (cleared 15% to that work)
  {id:'r_05',workId:'w_05',isrc:'USRC12100487',title:'Far In',artist:'Helado Negro',album:'Far In',label:'4AD',duration:194,year:2021,plays:12,art:'#b04a3a',explicit:false,derivesFrom:[],alsoWorks:[{workId:'w_s3',type:'interpolation',share:15,clearance:'cleared'}],releaseIds:['rl_05']},

  // 10% — KAYTRANADA × Kali Uchis. Samples "Spaceship" (Madlib) — 22% to sampled work, master-use cleared.
  {id:'r_06',workId:'w_06',isrc:'CAJ371900201',title:'10%',artist:'KAYTRANADA · Kali Uchis',album:'Bubba',label:'RCA',duration:207,year:2019,plays:822,art:'#5c2a8a',explicit:true,
    derivesFrom:[{kind:'sample',ofRecordingId:'r_spaceship',clearance:'cleared'}],
    alsoWorks:[{workId:'w_s2',type:'sample',share:22,clearance:'cleared'}],
    releaseIds:['rl_06','rl_bubba_dlx','rl_summer_mix_22']},
  {id:'r_06b',workId:'w_06',isrc:'USRH72000334',title:'10% (Pomo Remix)',artist:'KAYTRANADA',album:'Bubba Remixes',label:'RCA',duration:248,year:2020,plays:3,art:'#7d3da8',explicit:true,
    derivesFrom:[{kind:'remix',ofRecordingId:'r_06'},{kind:'sample',ofRecordingId:'r_spaceship',clearance:'cleared'}],
    alsoWorks:[{workId:'w_s2',type:'sample',share:22,clearance:'cleared'}],
    releaseIds:['rl_bubba_rmx']},

  // Raingurl — 1 recording
  {id:'r_07',workId:'w_07',isrc:'USA371700554',title:'Raingurl',artist:'Yaeji',album:'EP2',label:'Godmode',duration:265,year:2017,plays:42,art:'#0f4c2a',explicit:false,derivesFrom:[],alsoWorks:[],releaseIds:['rl_07','rl_summer_mix_22']},

  // Nonbinary — 1 recording
  {id:'r_08x',workId:'w_08',isrc:'GBR0Q2000118',title:'Nonbinary',artist:'Arca',album:'KiCk i',label:'XL Recordings',duration:173,year:2020,plays:15,art:'#2a2a2a',explicit:true,derivesFrom:[],alsoWorks:[],releaseIds:['rl_kicki']},

  // Wildfires — Sault. Interpolates "Stretch You Out" (Solange/Hynes) — 12% to that work, pending.
  {id:'r_08',workId:'w_09',isrc:'GBLFP2002113',title:'Wildfires',artist:'Sault',album:'UNTITLED (Black Is)',label:'Forever Living Originals',duration:288,year:2020,plays:194,art:'#d4a02a',explicit:false,
    derivesFrom:[],
    alsoWorks:[{workId:'w_s1',type:'interpolation',share:12,clearance:'pending'}],
    releaseIds:['rl_blackis']},

  // Two Face — 1 recording
  {id:'r_10',workId:'w_10',isrc:'USMS22100277',title:'Two Face',artist:'L\u2019Rain',album:'Fatigue',label:'Mexican Summer',duration:199,year:2021,plays:4,art:'#3a6b3f',explicit:false,derivesFrom:[],alsoWorks:[],releaseIds:['rl_fatigue']},

  // External "source" recordings shown when expanding sample chains — not counted as the catalog's own
  {id:'r_spaceship',workId:'w_s2',isrc:'USSTH0301122',title:'Spaceship',artist:'Madlib',album:'Shades of Blue',label:'Stones Throw',duration:198,year:2003,plays:14,art:'#1a3a6e',explicit:false,external:true,derivesFrom:[],alsoWorks:[],releaseIds:[]},
];

// Releases — LITE COMPAT SHIM.
//
// The CANONICAL release dataset lives in screens3.jsx as `window.RELEASES` (rich:
// includes UPC, format, territory, P/C-line, status, edition labels, group IDs,
// DSP delivery state). Every release ID referenced here also exists in RELEASES.
//
// This array is kept for:
//   • Recording.releaseIds[] resolution before screens3.jsx loads
//   • Old surfaces that imported `RELEASES_X` directly (e.g. marketing.jsx)
//   • Quick `kind`-based filtering when full edition metadata isn't needed
//
// New code should read `window.RELEASES` (rich) instead. The compat fallback in
// entities.jsx ReleaseDrawer reads either source.
const RELEASES_X = [
  // ── Solange / A Seat at the Table — 2 editions
  {id:'rl_01',          releaseGroupId:'rg_seat',     editionLabel:'Standard', isPrimary:true,  title:'A Seat at the Table',           artist:'Solange',            kind:'Album'},
  {id:'rl_01d',         releaseGroupId:'rg_seat',     editionLabel:'Deluxe',                    title:'A Seat at the Table (Deluxe)',  artist:'Solange',            kind:'Deluxe'},

  // Various-artists comps live in their own single-edition groups
  {id:'rl_comp_qro',    releaseGroupId:'rg_comp_qro', editionLabel:'Standard', isPrimary:true,  title:'Quarantine Mixtape Vol. 4',     artist:'Various Artists',    kind:'Compilation'},
  {id:'rl_kexp_v3',     releaseGroupId:'rg_kexp_v3',  editionLabel:'Standard', isPrimary:true,  title:'On the Roof, Vol. 3',           artist:'Various Artists',    kind:'Live comp'},

  {id:'rl_02',          releaseGroupId:'rg_crush',    editionLabel:'Standard', isPrimary:true,  title:'Crush',                          artist:'Floating Points',    kind:'Album'},

  // ── Caroline Polachek / Desire — 2 editions (album + remixes companion)
  {id:'rl_03',          releaseGroupId:'rg_desire',   editionLabel:'Standard', isPrimary:true,  title:'Desire, I Want To Turn Into You',artist:'Caroline Polachek',  kind:'Album'},
  {id:'rl_bunny_rmx',   releaseGroupId:'rg_desire',   editionLabel:'Remixes',                   title:'Bunny Remixes',                 artist:'Caroline Polachek',  kind:'EP'},

  {id:'rl_04',          releaseGroupId:'rg_devo',     editionLabel:'Standard', isPrimary:true,  title:'Devotion',                       artist:'Tirzah',             kind:'Album'},
  {id:'rl_05',          releaseGroupId:'rg_farin',    editionLabel:'Standard', isPrimary:true,  title:'Far In',                         artist:'Helado Negro',       kind:'Album'},

  // ── KAYTRANADA / Bubba — 3 editions
  {id:'rl_06',          releaseGroupId:'rg_bubba',    editionLabel:'Standard', isPrimary:true,  title:'Bubba',                          artist:'KAYTRANADA',         kind:'Album'},
  {id:'rl_bubba_dlx',   releaseGroupId:'rg_bubba',    editionLabel:'Deluxe',                    title:'Bubba (Deluxe)',                 artist:'KAYTRANADA',         kind:'Deluxe'},
  {id:'rl_bubba_rmx',   releaseGroupId:'rg_bubba',    editionLabel:'Remixes',                   title:'Bubba Remixes',                  artist:'KAYTRANADA',         kind:'EP'},

  {id:'rl_summer_mix_22',releaseGroupId:'rg_summer22', editionLabel:'Standard', isPrimary:true, title:'Summer Mix \u201922',           artist:'Various Artists',    kind:'Compilation'},
  {id:'rl_07',          releaseGroupId:'rg_ep2',      editionLabel:'Standard', isPrimary:true,  title:'EP2',                            artist:'Yaeji',              kind:'EP'},
  {id:'rl_kicki',       releaseGroupId:'rg_kicki',    editionLabel:'Standard', isPrimary:true,  title:'KiCk i',                         artist:'Arca',               kind:'Album'},
  {id:'rl_blackis',     releaseGroupId:'rg_blackis',  editionLabel:'Standard', isPrimary:true,  title:'UNTITLED (Black Is)',            artist:'Sault',              kind:'Album'},
  {id:'rl_fatigue',     releaseGroupId:'rg_fatigue',  editionLabel:'Standard', isPrimary:true,  title:'Fatigue',                        artist:'L\u2019Rain',        kind:'Album'},
];

// Release groups — derived/aggregated index. The group is the conceptual
// release; editions hang off it. Title comes from the primary edition (or the
// shortest title in the group as a fallback).
function _stripEditionSuffix(t) {
  return (t || '').replace(/\s*\((Deluxe|Remixes|Remix|Live|Acoustic|Bonus|Japan|Japanese|Special|Anniversary|Edition).*?\)\s*$/i, '').trim();
}
const RELEASE_GROUPS = (() => {
  const byId = {};
  RELEASES_X.forEach(r => {
    const gid = r.releaseGroupId;
    if (!gid) return;
    if (!byId[gid]) {
      byId[gid] = {
        id: gid,
        title: _stripEditionSuffix(r.title),
        artist: r.artist,
        kind: r.kind, // primary kind = first encountered; we'll override if a primary edition shows up
        editionIds: [],
        primaryEditionId: null,
      };
    }
    byId[gid].editionIds.push(r.id);
    if (r.isPrimary) {
      byId[gid].primaryEditionId = r.id;
      byId[gid].kind = r.kind;
      byId[gid].title = _stripEditionSuffix(r.title) || byId[gid].title;
    }
    // Default the primary to the first edition if none is flagged.
    if (!byId[gid].primaryEditionId) byId[gid].primaryEditionId = r.id;
  });
  return Object.values(byId);
})();

// ─────────────────────────────────────────────────────────────
// LOOKUPS — exposed so the Recording drawer + Work detail can reuse them
// ─────────────────────────────────────────────────────────────
const ALL_WORKS = (typeof WORKS !== 'undefined' ? WORKS : []).concat(EXTRA_WORKS);
const WORK_BY_ID = Object.fromEntries(ALL_WORKS.map(w=>[w.id,w]));
const REC_BY_ID  = Object.fromEntries(RECORDING_GRAPH.map(r=>[r.id,r]));
const REL_BY_ID  = Object.fromEntries(RELEASES_X.map(r=>[r.id,r]));

function recordingsForWork(workId) {
  return RECORDING_GRAPH.filter(r => r.workId===workId && !r.external);
}
function recordingsThatSampleOrInterpolate(workId) {
  // Recordings whose alsoWorks references this work (covers sample/interp/cover)
  return RECORDING_GRAPH.filter(r => (r.alsoWorks||[]).some(aw=>aw.workId===workId));
}
function songCounts(work) {
  const recs = recordingsForWork(work.id);
  const releaseIds = new Set(recs.flatMap(r=>r.releaseIds||[]));
  const sampledByCount = recordingsThatSampleOrInterpolate(work.id).length;
  return { recordings: recs.length, releases: releaseIds.size, sampledBy: sampledByCount };
}
// Derive a list of publishers per work — 1–3 names depending on writer count + co-pub patterns.
// Stable & deterministic per work id. Uses w.catalog as the primary publisher.
const _COPUB_POOL = ['Pluralis Music','Kobalt','Warner Chappell','Sony Music Pub.','UMPG','Concord','BMG','Reservoir','Downtown'];
function publishersForWork(work) {
  if (!work) return [];
  if (Array.isArray(work.publishers) && work.publishers.length) return work.publishers;
  const primary = work.catalog || 'Pluralis Music';
  const writers = (work.writers || []).length || 1;
  // Hash from id for stable variety
  const id = String(work.id || '');
  let h = 0; for (let i=0;i<id.length;i++) h = (h*31 + id.charCodeAt(i)) >>> 0;
  const list = [primary];
  if (writers >= 2) {
    list.push(_COPUB_POOL[h % _COPUB_POOL.length]);
  }
  if (writers >= 3 && (h % 3) === 0) {
    list.push(_COPUB_POOL[(h>>3) % _COPUB_POOL.length]);
  }
  // Dedupe preserving order
  return [...new Set(list)];
}

function groupRecordingsByArtist(recs) {
  const map = new Map();
  recs.forEach(r => {
    const key = r.artist;
    if (!map.has(key)) map.set(key, []);
    map.get(key).push(r);
  });
  // Sort: original artist first (no derivesFrom on first item), others alphabetical
  return [...map.entries()].sort((a,b)=>{
    const aOrig = a[1].some(r=>!r.derivesFrom?.length);
    const bOrig = b[1].some(r=>!r.derivesFrom?.length);
    if (aOrig !== bOrig) return aOrig ? -1 : 1;
    return a[0].localeCompare(b[0]);
  });
}

// Expose globally so other scripts can use them (drawer, work detail)
// Group helpers
const RG_BY_ID = Object.fromEntries(RELEASE_GROUPS.map(g => [g.id, g]));
function editionsForGroup(rgId) {
  return RELEASES_X.filter(r => r.releaseGroupId === rgId);
}
function groupForRelease(relId) {
  const r = REL_BY_ID[relId];
  return r ? RG_BY_ID[r.releaseGroupId] : null;
}

Object.assign(window, {
  ALL_WORKS, WORK_BY_ID, RECORDING_GRAPH, REC_BY_ID, RELEASES_X, REL_BY_ID,
  RELEASE_GROUPS, RG_BY_ID, editionsForGroup, groupForRelease,
  recordingsForWork, recordingsThatSampleOrInterpolate, songCounts, groupRecordingsByArtist, publishersForWork,
});

// ─────────────────────────────────────────────────────────────
// SONGS VIEW
// ─────────────────────────────────────────────────────────────
function SongsView({ go, q='', sort='edited', view='grid', labelFilter='', publisherFilter='', bulkScope='' }) {
  const [filter, setFilter] = useSongs('all'); // all | recorded | unrecorded | sampled | sampled-by
  const [expanded, setExpanded] = useSongs(()=> new Set());
  const lf = (labelFilter||'').trim().toLowerCase();
  const pf = (publisherFilter||'').trim().toLowerCase();

  const songs = useSongsMemo(() => ALL_WORKS.filter(w=>!w.external), []);

  const decorated = useSongsMemo(() => songs.map(w => {
    const c = songCounts(w);
    const samplesOthers = recordingsForWork(w.id).some(r => (r.alsoWorks||[]).length>0 || (r.derivesFrom||[]).some(d=>d.kind==='sample'||d.kind==='interpolation'));
    const isUnrecorded = c.recordings === 0 || w.unrecorded;
    return { ...w, _c:c, _samplesOthers:samplesOthers, _isUnrecorded:isUnrecorded };
  }), [songs]);

  const filtered = decorated.filter(w => {
    if (lf) {
      const recs = recordingsForWork(w.id) || [];
      if (!recs.some(r => (r.label||'').toLowerCase().includes(lf))) return false;
    }
    if (pf) {
      const pubs = publishersForWork(w).map(p => (p||'').toLowerCase());
      if (!pubs.some(p => p.includes(pf))) return false;
    }
    if (q) {
      const blob = `${w.title} ${w.iswc} ${w.writers.join(' ')}`.toLowerCase();
      if (!blob.includes(q.toLowerCase())) return false;
    }
    if (filter==='recorded'   && w._isUnrecorded) return false;
    if (filter==='unrecorded' && !w._isUnrecorded) return false;
    if (filter==='sampled'    && !w._samplesOthers) return false;
    if (filter==='sampled-by' && w._c.sampledBy===0) return false;
    return true;
  }).slice().sort((a,b) => {
    switch (sort) {
      case 'title':       return a.title.localeCompare(b.title);
      case 'title-desc':  return b.title.localeCompare(a.title);
      case 'plays':       return (b.plays||0) - (a.plays||0);
      case 'royalties':   return (b.royalties||0) - (a.royalties||0);
      case 'created':     return (b.id||'').localeCompare(a.id||'');
      case 'edited':
      default:            return (a.id||'').localeCompare(b.id||'');
    }
  });

  const toggle = (id) => {
    setExpanded(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  };

  const counts = {
    all:        decorated.length,
    recorded:   decorated.filter(w=>!w._isUnrecorded).length,
    unrecorded: decorated.filter(w=>w._isUnrecorded).length,
    sampled:    decorated.filter(w=>w._samplesOthers).length,
    'sampled-by': decorated.filter(w=>w._c.sampledBy>0).length,
  };

  return (
    <div style={{borderTop:'1px solid var(--rule)',marginTop:0}}>
      {/* Filter chips */}
      <div style={{display:'flex',gap:0,padding:'10px 14px',borderBottom:'1px solid var(--rule)',background:'var(--bg-2)',flexWrap:'wrap'}}>
        {[
          {k:'all',         l:'All'},
          {k:'recorded',    l:'Has recording'},
          {k:'unrecorded',  l:'Unrecorded'},
          {k:'sampled',     l:'Samples / interpolates'},
          {k:'sampled-by',  l:'Sampled by others'},
        ].map((f,i)=>(
          <button key={f.k} onClick={()=>setFilter(f.k)} className="ff-mono upper"
            style={{padding:'6px 12px',fontSize:10,letterSpacing:'.08em',
              background: filter===f.k ? 'var(--ink)' : 'transparent',
              color: filter===f.k ? 'var(--bg)' : 'var(--ink-2)',
              border:'1px solid var(--rule)',
              borderLeft: i===0 ? '1px solid var(--rule)' : 'none',
              cursor:'pointer'}}>
            {f.l} <span style={{opacity:.5,marginLeft:6}}>{counts[f.k]}</span>
          </button>
        ))}
        <span style={{flex:1}}/>
        <span className="ff-mono upper" style={{fontSize:10,color:'var(--ink-3)',letterSpacing:'.1em',padding:'6px 0',marginLeft:14}}>
          {filtered.length} WORKS · {filtered.reduce((s,w)=>s+w._c.recordings,0)} RECORDINGS
        </span>
      </div>

      {/* Header */}
      <div className="ff-mono upper" style={{display:'grid',gridTemplateColumns: view==='table' ? '28px 1fr 220px 120px 80px 80px 80px' : '28px 24px 1fr 220px 120px 80px 80px 80px',gap:14,
        padding: view==='table' ? '8px 14px' : '10px 14px',fontSize:10,color:'var(--ink-3)',background:'var(--bg-2)',borderBottom:'1px solid var(--rule)'}}>
        <span style={{display:'flex',alignItems:'center'}}>{bulkScope ? <BulkHeaderCheckbox scope={bulkScope} allIds={filtered.map(w=>w.id)}/> : null}</span>
        {view==='grid' && <span/>}<span>WORK / ISWC</span><span>WRITERS</span><span>PUBLISHERS</span>
        <span style={{textAlign:'right'}}>RECS</span><span style={{textAlign:'right'}}>RELEASES</span><span style={{textAlign:'right'}}>STATUS</span>
      </div>

      {/* Rows */}
      {filtered.map(w => {
        const open = view==='grid' && expanded.has(w.id);
        const recs = recordingsForWork(w.id);
        const sampledBy = recordingsThatSampleOrInterpolate(w.id);
        return (
          <div key={w.id} data-work-row={w.id} style={{borderBottom:'1px solid var(--rule-soft)'}}>
            <div onClick={()=> view==='table' ? (go && go('work', w)) : toggle(w.id)}
              style={{display:'grid',gridTemplateColumns: view==='table' ? '28px 1fr 220px 120px 80px 80px 80px' : '28px 24px 1fr 220px 120px 80px 80px 80px',gap:14,
                padding: view==='table' ? '7px 14px' : '14px 14px',alignItems:'center',cursor:'pointer',
                background: open ? 'var(--bg-2)' : 'transparent'}}
              onMouseEnter={e=>{ if(!open) e.currentTarget.style.background='var(--bg-2)'; }}
              onMouseLeave={e=>{ if(!open) e.currentTarget.style.background='transparent'; }}>
              <span style={{display:'flex',alignItems:'center'}}>{bulkScope ? <BulkCheckbox scope={bulkScope} id={w.id} allIds={filtered.map(x=>x.id)}/> : null}</span>
              {view==='grid' && <span style={{fontFamily:'ui-monospace,monospace',fontSize:14,color:'var(--ink-3)',transform: open?'rotate(90deg)':'none',transition:'transform .12s',display:'inline-block'}}>›</span>}
              <div>
                <div style={{display:'flex',alignItems:'center',gap:8,flexWrap:'wrap'}}>
                  <span style={{fontSize: view==='table' ? 13 : 15,fontWeight:600,letterSpacing:'-0.01em'}}>{w.title}</span>
                  {w._isUnrecorded && <Tag color="var(--ink-3)">UNRECORDED</Tag>}
                  {w._samplesOthers && <Tag color="#9b6a18">SAMPLES</Tag>}
                  {w._c.sampledBy>0 && <Tag color="#5b8a3a">SAMPLED ×{w._c.sampledBy}</Tag>}
                  {w.status==='conflict' && <Tag color="#c0392b">CONFLICT</Tag>}
                  {w.status==='pending' && <Tag color="#b78113">PENDING</Tag>}
                </div>
                {view==='grid' && <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:3}}>{window.iswcDisplay ? window.iswcDisplay(w.iswc) : w.iswc} · shares {w.shares}</div>}
                {view==='table' && <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:1}}>{window.iswcDisplay ? window.iswcDisplay(w.iswc) : w.iswc}</div>}
              </div>
              <span style={{fontSize:12,color:'var(--ink-2)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{w.writers.join(', ')}</span>
              {(()=>{ const pubs = publishersForWork(w); const first = pubs[0] || '—'; const more = pubs.length - 1; return (
                <span style={{fontSize:11,color:'var(--ink-2)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',display:'flex',alignItems:'baseline',gap:6}} title={pubs.join(' · ')}>
                  <span style={{overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{first}</span>
                  {more > 0 && <span className="ff-mono num" style={{fontSize:10,color:'var(--ink-3)',flex:'0 0 auto'}}>+{more}</span>}
                </span>
              ); })()}
              <span className="ff-mono num" style={{fontSize:13,textAlign:'right',color: w._c.recordings===0 ? 'var(--ink-3)' : 'var(--ink)'}}>{w._c.recordings}</span>
              <span className="ff-mono num" style={{fontSize:13,textAlign:'right',color: w._c.releases===0 ? 'var(--ink-3)' : 'var(--ink)'}}>{w._c.releases}</span>
              <span className="ff-mono upper" style={{fontSize:10,letterSpacing:'.08em',textAlign:'right',color:
                w.status==='registered' ? 'var(--ink-2)' :
                w.status==='conflict' ? '#c0392b' : '#b78113'}}>
                {w.status}
              </span>
            </div>

            {open && (
              <div style={{padding:'4px 14px 18px 38px',background:'var(--bg-2)',borderTop:'1px solid var(--rule-soft)'}}>
                {recs.length === 0 ? (
                  <UnrecordedPanel w={w} go={go}/>
                ) : (
                  <RecordingsTree work={w} recs={recs} sampledBy={sampledBy} go={go}/>
                )}
                <div style={{display:'flex',gap:8,marginTop:14}}>
                  <button onClick={(e)=>{ e.stopPropagation(); go && go('work', w); }}
                    className="ff-mono upper" style={{padding:'7px 12px',fontSize:10,letterSpacing:'.08em',background:'var(--ink)',color:'var(--bg)',border:0,cursor:'pointer'}}>
                    Open work detail →
                  </button>
                  <button onClick={(e)=>{ e.stopPropagation(); window.dispatchEvent(new CustomEvent('astro-add-recording',{detail:{workId: w.id}})); }}
                    className="ff-mono upper" style={{padding:'7px 12px',fontSize:10,letterSpacing:'.08em',background:'transparent',color:'var(--ink-2)',border:'1px solid var(--rule)',cursor:'pointer'}}>
                    + Add recording
                  </button>
                  {w._c.sampledBy>0 && (
                    <button onClick={(e)=>{ e.stopPropagation(); window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:`${w._c.sampledBy} sample clearance${w._c.sampledBy>1?'s':''} for "${w.title}"`,tone:'ok'}})); }}
                      className="ff-mono upper" style={{padding:'7px 12px',fontSize:10,letterSpacing:'.08em',background:'transparent',color:'var(--ink-2)',border:'1px solid var(--rule)',cursor:'pointer'}}>
                      Manage sample clearances
                    </button>
                  )}
                </div>
              </div>
            )}
          </div>
        );
      })}

      {filtered.length === 0 && (
        <div style={{padding:'56px 24px',textAlign:'center',color:'var(--ink-3)'}}>
          <div className="ff-mono upper" style={{fontSize:11,letterSpacing:'.12em',marginBottom:8}}>NO WORKS MATCH</div>
          <div className="ff-mono" style={{fontSize:12,color:'var(--ink-2)',lineHeight:1.6}}>
            {pf && <>No works in this catalog are credited to <b style={{color:'var(--ink)'}}>{publisherFilter}</b>.<br/></>}
            {lf && <>No works have recordings on the label <b style={{color:'var(--ink)'}}>{labelFilter}</b>.<br/></>}
            {q && <>No matches for "<b style={{color:'var(--ink)'}}>{q}</b>".</>}
            {!pf && !lf && !q && <>Try a different filter or status.</>}
          </div>
        </div>
      )}
    </div>
  );
}

// ── Sub-pieces ────────────────────────────────────────────────

function Tag({ children, color }) {
  return (
    <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.08em',padding:'2px 6px',border:`1px solid ${color}`,color:color,fontWeight:600}}>{children}</span>
  );
}

function UnrecordedPanel({ w, go }) {
  return (
    <div style={{padding:'14px 0 0',display:'flex',gap:18,alignItems:'flex-start'}}>
      <div style={{width:48,height:48,background:'repeating-linear-gradient(45deg,var(--rule) 0 4px,transparent 4px 8px)',flexShrink:0}}/>
      <div style={{flex:1}}>
        <div style={{fontSize:13,fontWeight:600,marginBottom:4}}>No recording yet — pitch-eligible</div>
        <div style={{fontSize:12,color:'var(--ink-2)',lineHeight:1.5,maxWidth:560}}>
          This composition is registered with <SocietyLink code={w.pro}/> but has not been commercially released. Add it to a pitch list,
          attach a demo, or link an existing recording you've matched.
        </div>
      </div>
    </div>
  );
}

function RecordingsTree({ work, recs, sampledBy, go }) {
  const groups = groupRecordingsByArtist(recs);
  return (
    <div>
      {/* Recordings of THIS work, grouped by artist */}
      <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',padding:'12px 0 8px'}}>
        RECORDINGS OF THIS WORK · {recs.length}
      </div>
      {groups.map(([artist, list], gi) => (
        <div key={artist} style={{marginBottom: gi<groups.length-1 ? 12 : 0}}>
          <div style={{display:'flex',alignItems:'center',gap:8,padding:'4px 0 6px'}}>
            <span style={{fontSize:12,fontWeight:600}}>{artist}</span>
            <span className="ff-mono" style={{fontSize:10,color:'var(--ink-3)'}}>{list.length} recording{list.length>1?'s':''}</span>
          </div>
          {list.map(rec => <RecordingRow key={rec.id} rec={rec} go={go}/>)}
        </div>
      ))}

      {/* Sampled by other recordings */}
      {sampledBy.length>0 && (
        <>
          <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',padding:'18px 0 8px',display:'flex',alignItems:'center',gap:8}}>
            <span>USED IN OTHER RECORDINGS · {sampledBy.length}</span>
          </div>
          {sampledBy.map(rec => {
            const aw = (rec.alsoWorks||[]).find(a=>a.workId===work.id);
            return <SampledByRow key={rec.id} rec={rec} share={aw?.share} type={aw?.type} clearance={aw?.clearance} go={go}/>;
          })}
        </>
      )}
    </div>
  );
}

function RecordingRow({ rec, go }) {
  const releases = (rec.releaseIds||[]).map(id => REL_BY_ID[id]).filter(Boolean);
  const derivBadge = rec.derivesFrom && rec.derivesFrom[0];
  const samples = (rec.alsoWorks||[]).filter(a => a.type==='sample' || a.type==='interpolation');
  return (
    <div onClick={()=>{
        // Open the recording drawer in the parent RecordingsView — we re-use that pattern
        // Simplest: dispatch a custom event the recordings view can listen to. For now, navigate to work and let user click.
        window.dispatchEvent(new CustomEvent('astro-open-recording', {detail:{id:rec.id}}));
      }}
      style={{display:'grid',gridTemplateColumns:'40px 1fr 90px 80px 70px',gap:12,padding:'9px 10px',
        background:'var(--bg)',border:'1px solid var(--rule-soft)',marginBottom:6,alignItems:'center',cursor:'pointer'}}
      onMouseEnter={e=>e.currentTarget.style.borderColor='var(--ink)'}
      onMouseLeave={e=>e.currentTarget.style.borderColor='var(--rule-soft)'}>
      <div style={{width:36,height:36,background:rec.art,position:'relative'}}>
        <Ic.Disc width={14} height={14} style={{position:'absolute',top:11,left:11,color:'rgba(0,0,0,.55)'}}/>
      </div>
      <div style={{minWidth:0}}>
        <div style={{display:'flex',alignItems:'center',gap:6,flexWrap:'wrap'}}>
          <span style={{fontSize:13,fontWeight:500}}>{rec.title}</span>
          {derivBadge && <Tag color="#7d3da8">{derivBadge.kind.toUpperCase()}</Tag>}
          {rec.explicit && <span className="ff-mono" style={{fontSize:8,padding:'1px 4px',background:'var(--ink)',color:'var(--bg)',fontWeight:600}}>E</span>}
        </div>
        <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2,display:'flex',gap:8,flexWrap:'wrap'}}>
          <span>{(window.isrcDisplay ? window.isrcDisplay(rec.isrc) : rec.isrc)}</span>
          <span>·</span>
          <span>{rec.year}</span>
          <span>·</span>
          <span>{rec.label}</span>
          {samples.length>0 && (<><span>·</span><span style={{color:'#9b6a18'}}>+{samples.length} source work{samples.length>1?'s':''}</span></>)}
        </div>
      </div>
      <span className="ff-mono num" style={{fontSize:11,textAlign:'right',color:'var(--ink-2)'}}>{Math.floor(rec.duration/60)}:{String(rec.duration%60).padStart(2,'0')}</span>
      <span className="ff-mono num" style={{fontSize:12,textAlign:'right'}}>{rec.plays}M</span>
      <span className="ff-mono" style={{fontSize:10,textAlign:'right',color:'var(--ink-3)'}}>{releases.length} rel.</span>
    </div>
  );
}

function SampledByRow({ rec, share, type, clearance, go }) {
  const cColor = clearance==='cleared' ? '#5b8a3a' : clearance==='pending' ? '#b78113' : '#c0392b';
  return (
    <div onClick={()=>window.dispatchEvent(new CustomEvent('astro-open-recording', {detail:{id:rec.id}}))}
      style={{display:'grid',gridTemplateColumns:'40px 1fr 110px 90px',gap:12,padding:'9px 10px',
        background:'var(--bg)',border:'1px dashed var(--rule)',marginBottom:6,alignItems:'center',cursor:'pointer'}}
      onMouseEnter={e=>e.currentTarget.style.borderStyle='solid'}
      onMouseLeave={e=>e.currentTarget.style.borderStyle='dashed'}>
      <div style={{width:36,height:36,background:rec.art,position:'relative'}}>
        <Ic.Disc width={14} height={14} style={{position:'absolute',top:11,left:11,color:'rgba(0,0,0,.55)'}}/>
      </div>
      <div style={{minWidth:0}}>
        <div style={{display:'flex',alignItems:'center',gap:6,flexWrap:'wrap'}}>
          <span style={{fontSize:13,fontWeight:500}}>{rec.title}</span>
          <span className="ff-mono" style={{fontSize:10,color:'var(--ink-3)'}}>·</span>
          <span style={{fontSize:12,color:'var(--ink-2)'}}>{rec.artist}</span>
          {type && <Tag color="#9b6a18">{type.toUpperCase()}</Tag>}
        </div>
        <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2}}>
          {rec.isrc} · {rec.year} · {rec.label}
        </div>
      </div>
      <span className="ff-mono num" style={{fontSize:12,textAlign:'right'}}>{share}% share</span>
      <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',textAlign:'right',color:cColor,fontWeight:600}}>{clearance}</span>
    </div>
  );
}

// Expose
Object.assign(window, { SongsView });
