// ─────────────────────────────────────────────────────────────────────
// rs.jsx — Rocket Science real-data layer
//
// Loads the parsed CSVs (db/rocket-science/db.json) and exposes a clean,
// fully-indexed API on `window.RS`. Additive layer — does NOT mutate the
// existing mock globals (WORKS, RECORDING_GRAPH, etc). Screens can opt in
// by reading window.RS.* directly.
//
//   window.RS.profiles       — Person/entity directory (419)
//   window.RS.works          — Compositions (553)
//   window.RS.recordings     — Recordings/masters (1035)
//   window.RS.releases       — Albums/EPs (391)
//   window.RS.videos         — Music videos (88)
//   window.RS.publishers     — Synthesized from publishing_share (205)
//   window.RS.agreements     — Contracts (14)
//
//   window.RS.byId.profile(id), .work(id), .recording(id), etc.
//   window.RS.findProfile(nameOrAlias)  → profile or null
//   window.RS.getWork(id)               → work + nested writers/publishers/territories/recordings
//   window.RS.getRecording(id)          → recording + nested artists/masters/territories/videos
//   window.RS.getRelease(id)            → release + nested recordings + agreement
//   window.RS.getProfile(id)            → profile + works + recordings (anywhere they're credited)
// ─────────────────────────────────────────────────────────────────────

(function loadRS() {
  const norm = s => (s||'').toLowerCase().normalize('NFKD').replace(/[\u0300-\u036f]/g,'').trim();
  const splitMulti = s => (s||'').split(',').map(x=>x.trim()).filter(Boolean);
  const pct = s => { if (!s) return 0; const m = String(s).match(/-?[\d.]+/); return m ? parseFloat(m[0]) : 0; };

  const raw = window.__RS_RAW;
  if (!raw) {
    console.warn('[RS] window.__RS_RAW not loaded — RS layer disabled');
    window.RS = { loaded: false };
    return;
  }

  // ── Profiles ────────────────────────────────────────────────────────
  const profileById = Object.fromEntries(raw.profiles.map(p => [p.profile_id, p]));
  const profileByName = new Map();
  const addName = (k, p) => { if (k && !profileByName.has(norm(k))) profileByName.set(norm(k), p); };
  for (const p of raw.profiles) {
    if (p.artist_name) addName(p.artist_name, p);
    const f=p.first_name||'', m=p.middle_name||'', l=p.last_name||'';
    addName([f,l].filter(Boolean).join(' '), p);
    addName([f,m,l].filter(Boolean).join(' '), p);
    const lf = l.split(' ')[0]; const ll = l.split(' ').slice(-1)[0];
    if (lf && lf !== l) { addName([f,lf].filter(Boolean).join(' '), p); addName([f,m,lf].filter(Boolean).join(' '), p); }
    if (ll && ll !== l && ll !== lf) { addName([f,ll].filter(Boolean).join(' '), p); addName([f,m,ll].filter(Boolean).join(' '), p); }
    if (p.alias) p.alias.split(',').map(a=>a.trim()).filter(Boolean).forEach(a=>addName(a, p));
  }
  const findProfile = name => profileByName.get(norm(name)) || null;

  // ── Publishers (derived from publishing_share) ─────────────────────
  const pubMap = new Map();
  for (const ps of raw.publishing_share) {
    const name = ps.Publisher;
    if (!name) continue;
    if (!pubMap.has(name)) {
      pubMap.set(name, { id: 'RPUB' + String(pubMap.size+1).padStart(5,'0'), name, role: ps['Publisher Role'], works: new Set() });
    }
    pubMap.get(name).works.add(ps['Song Name (from Writer Share)']);
  }
  const publishers = [...pubMap.values()].map(p => ({ id: p.id, name: p.name, role: p.role, workCount: p.works.size }));
  const publisherByName = Object.fromEntries(publishers.map(p => [p.name, p]));

  // ── Indexes ─────────────────────────────────────────────────────────
  const workById = Object.fromEntries(raw.work.map(w => [w['Work ID'], w]));
  const writerShareById = Object.fromEntries(raw.writer_share.map(w => [w['Writer Share ID'], w]));
  const writerTerritoryById = Object.fromEntries(raw.writer_territory.map(t => [t['Writer Territory ID'], t]));
  const publishingShareById = Object.fromEntries(raw.publishing_share.map(p => [p['Publishing Share ID'], p]));
  const publisherTerritoryById = Object.fromEntries(raw.publisher_territory.map(t => [t['Publisher Territory ID'], t]));
  const recordingById = Object.fromEntries(raw.recording.map(r => [r['Recording ID'], r]));
  const artistShareById = Object.fromEntries(raw.artist_share.map(a => [a['Artist Share ID'], a]));
  const masterById = Object.fromEntries(raw.master_ownership.map(m => [m['Master Ownership ID'], m]));
  const videoById = Object.fromEntries(raw.video.map(v => [v['Video ID'], v]));
  const releaseById = Object.fromEntries(raw.release.map(r => [r['Release ID'], r]));
  const agreementById = Object.fromEntries(raw.agreement.map(a => [a['Agreement ID'], a]));

  // by Work ID
  const writerSharesByWork = {};
  for (const w of raw.writer_share) {
    const wid = w['Work ID']; if (!wid) continue;
    (writerSharesByWork[wid] ||= []).push(w);
  }
  // by Writer Share ID
  const publishingSharesByWriterShare = {};
  for (const p of raw.publishing_share) {
    const ws = p['Writer Share']; if (!ws) continue;
    (publishingSharesByWriterShare[ws] ||= []).push(p);
  }
  const writerTerritoriesByShare = {};
  for (const t of raw.writer_territory) {
    const ws = t['Writer Share']; if (!ws) continue;
    (writerTerritoriesByShare[ws] ||= []).push(t);
  }
  const publisherTerritoriesByShare = {};
  for (const t of raw.publisher_territory) {
    const ps = t['Publishing Share']; if (!ps) continue;
    (publisherTerritoriesByShare[ps] ||= []).push(t);
  }
  // by song name (works)
  const recordingsBySongName = {};
  for (const r of raw.recording) {
    const sn = r['Song Name']; if (!sn) continue;
    (recordingsBySongName[sn] ||= []).push(r);
  }
  // by recording name
  const artistSharesByRec = {};
  for (const a of raw.artist_share) {
    const rec = a.Recording; if (!rec) continue;
    (artistSharesByRec[rec] ||= []).push(a);
  }
  const mastersByRec = {};
  for (const m of raw.master_ownership) {
    const rec = m.Recording; if (!rec) continue;
    (mastersByRec[rec] ||= []).push(m);
  }
  const videosByRec = {};
  for (const v of raw.video) {
    const rec = v.Recording; if (!rec) continue;
    (videosByRec[rec] ||= []).push(v);
  }
  // releases — recordings is comma-separated track list (track names)
  const releasesByRecordingName = {};
  for (const rel of raw.release) {
    splitMulti(rel.Recordings).forEach(name => {
      (releasesByRecordingName[name] ||= []).push(rel);
    });
  }
  // agreements
  const partiesByAgreement = {};
  for (const p of raw.agreement_party) {
    const aid = p.Agreement; if (!aid) continue;
    (partiesByAgreement[aid] ||= []).push(p);
  }

  // ── Nested accessors ────────────────────────────────────────────────
  function getWork(workId) {
    const w = workById[workId];
    if (!w) return null;
    const writerShares = (writerSharesByWork[workId] || []).map(ws => {
      const profile = findProfile(ws.Writer);
      const pubShares = (publishingSharesByWriterShare[ws['Writer Share ID']] || []).map(p => ({
        ...p,
        publisher: publisherByName[p.Publisher] || null,
        territories: publisherTerritoriesByShare[p['Publishing Share ID']] || [],
      }));
      return {
        ...ws,
        profile,
        territories: writerTerritoriesByShare[ws['Writer Share ID']] || [],
        publishingShares: pubShares,
      };
    });
    const songName = w['Work Title'];
    const recordings = (recordingsBySongName[songName] || []).map(r => ({
      id: r['Recording ID'],
      title: r['Track Name'],
      isrc: r['ISRC (Audio)'],
      artist: r.Artist,
      duration: r.Duration,
      releases: r.Release,
    }));
    return { ...w, writerShares, recordings, agreement: agreementById[w.Agreement] || null };
  }

  function getRecording(recId) {
    const r = recordingById[recId];
    if (!r) return null;
    const work = raw.work.find(w => w['Work Title'] === r['Song Name']) || null;
    const artistShares = (artistSharesByRec[r['Track Name']] || []).map(a => ({ ...a, profile: findProfile(a.Artist) }));
    const masters = (mastersByRec[r['Track Name']] || []);
    const videos = (videosByRec[r['Track Name']] || []);
    const releases = (releasesByRecordingName[r['Track Name']] || []);
    return { ...r, work, artistShares, masters, videos, releases };
  }

  function getRelease(relId) {
    const r = releaseById[relId];
    if (!r) return null;
    const trackNames = splitMulti(r.Recordings);
    const tracks = trackNames.map(name => {
      const rec = raw.recording.find(rr => rr['Track Name'] === name);
      return rec ? { id: rec['Recording ID'], title: name, isrc: rec['ISRC (Audio)'], artist: rec.Artist, duration: rec.Duration } : { title: name };
    });
    const agreement = agreementById[r.Agreement] || null;
    return { ...r, tracks, agreement };
  }

  function getProfile(profileId) {
    const p = profileById[profileId];
    if (!p) return null;
    // Find all writer shares where Writer matches this profile
    const writerCredits = raw.writer_share.filter(ws => findProfile(ws.Writer)?.profile_id === profileId);
    // Find all artist shares
    const artistCredits = raw.artist_share.filter(a => findProfile(a.Artist)?.profile_id === profileId);
    // Find recordings where this person is performer/producer/etc
    const recordingCredits = raw.recording.filter(r => {
      return splitMulti(r.Artist).some(a => findProfile(a)?.profile_id === profileId)
          || splitMulti(r.Producer || '').some(a => findProfile(a)?.profile_id === profileId);
    });
    return {
      ...p,
      worksAsWriter: [...new Set(writerCredits.map(ws => ws['Work ID']))].map(id => workById[id]).filter(Boolean),
      worksAsArtist: artistCredits,
      recordings: recordingCredits,
    };
  }

  function getAgreement(agId) {
    const a = agreementById[agId];
    if (!a) return null;
    const parties = (partiesByAgreement[agId] || []);
    const works = splitMulti(a.Works);
    const releases = splitMulti(a.Releases);
    return { ...a, parties, workTitles: works, releaseTitles: releases };
  }

  // ── Expose ──────────────────────────────────────────────────────────
  // Eagerly enrich works so list views can render shares/recordings without per-row hydration.
  const enrichedWorks = raw.work.map(w => {
    const writerShares = (writerSharesByWork[w['Work ID']] || []).map(ws => {
      const profile = findProfile(ws.Writer);
      const pubShares = (publishingSharesByWriterShare[ws['Writer Share ID']] || []).map(p => ({
        ...p,
        publisher: publisherByName[p.Publisher] || null,
        territories: publisherTerritoriesByShare[p['Publishing Share ID']] || [],
      }));
      return {
        ...ws,
        profile,
        publishingShares: pubShares,
        territories: writerTerritoriesByShare[ws['Writer Share ID']] || [],
      };
    });
    const recordings = (recordingsBySongName[w['Work Title']] || []).map(r => ({
      ...r,
      title: r['Recording Name'],
      artistShares: (artistSharesByRec[r['Recording ID']] || []).map(a => ({ ...a, profile: findProfile(a.Artist) })),
      masterOwnerships: mastersByRec[r['Recording ID']] || [],
      videos: videosByRec[r['Recording ID']] || [],
      releases: releasesByRecordingName[r['Recording Name']] || [],
    }));
    return { ...w, writerShares, recordings };
  });

  // ── Directory data (Airtable exports — labels / richer profiles + pubs) ────────
  const dir = window.__RS_DIRECTORY || { labels: [], profiles: [], publishers: [] };
  // Index richer Airtable rows by their RS IDs so we can merge into the seeded layer.
  const dirProfileById = Object.fromEntries((dir.profiles || []).map(p => [p.profile_id, p]));
  const dirPublisherByIPI = Object.fromEntries((dir.publishers || []).filter(p => p.IPI).map(p => [p.IPI, p]));
  const dirPublisherByName = Object.fromEntries((dir.publishers || []).map(p => [p.Name, p]));

  window.RS = {
    loaded: true,
    profiles: raw.profiles,
    works: enrichedWorks,
    recordings: raw.recording,
    releases: raw.release,
    videos: raw.video,
    publishers,
    agreements: raw.agreement,
    directory: dir,
    dirProfileById, dirPublisherByIPI, dirPublisherByName,
    raw,

    byId: {
      profile: id => profileById[id],
      work: id => workById[id],
      recording: id => recordingById[id],
      release: id => releaseById[id],
      video: id => videoById[id],
      agreement: id => agreementById[id],
      writerShare: id => writerShareById[id],
      publishingShare: id => publishingShareById[id],
      publisher: id => publishers.find(p => p.id === id) || null,
    },

    publisherByName: name => publisherByName[name] || null,
    findProfile,
    getWork,
    getRecording,
    getRelease,
    getProfile,
    getAgreement,

    // helpers
    pct,
    splitMulti,
    norm,
  };

  console.log('[RS] loaded:', {
    profiles: raw.profiles.length,
    works: raw.work.length,
    recordings: raw.recording.length,
    releases: raw.release.length,
    publishers: publishers.length,
    agreements: raw.agreement.length,
  });
  window.dispatchEvent(new CustomEvent('rs-loaded'));
})();
