// ============================================================================
// CWR SYNTH  ·  turn ASTRO summary work objects into rich CWR objects
// ----------------------------------------------------------------------------
// Catalog rows like {id:'w_11',title:'Glasshouse Mornings',writers:['Solange',
// 'Devonté'],shares:'70/30',iswc:'T-410.992.557-3'} only carry summary data —
// not enough to render every CWR record. This module fills in deterministic,
// realistic synthetic data: IPI numbers, society codes, publisher chains,
// territories, alternate titles, recordings, performers.
//
// Determinism: same work id always yields the same synthetic numbers so the
// generator output is stable across loads (good for screenshots and demos).
//
// EXPORT: window.CwrSynth = { hydrateWork }
// ============================================================================

(function () {
  'use strict';

  // Tiny stable hash — works as a seed
  function hash(s) {
    let h = 0x811c9dc5;
    for (let i = 0; i < s.length; i++) {
      h ^= s.charCodeAt(i); h = (h * 0x01000193) >>> 0;
    }
    return h;
  }
  function rng(seed) {
    let s = seed || 1;
    return () => { s = (s * 1664525 + 1013904223) >>> 0; return (s >>> 0) / 0xffffffff; };
  }

  // Generate IPI name number with VALID mod-101 check digit
  function ipi(seed) {
    const r = rng(seed);
    const digits = [];
    for (let i = 0; i < 9; i++) digits.push(Math.floor(r() * 10));
    let sum = 0;
    for (let i = 0; i < 9; i++) sum += digits[i] * (10 - i);
    const check = (101 - (sum % 101)) % 101;
    return digits.join('') + String(check).padStart(2, '0');
  }
  // Generate IPI base number with VALID mod-11 check digit
  function ipiBase(seed) {
    const r = rng(seed);
    const digits = [];
    for (let i = 0; i < 9; i++) digits.push(Math.floor(r() * 10));
    let sum = 0;
    for (let i = 0; i < 9; i++) sum += digits[i] * (10 - i);
    let check = sum % 11;
    if (check === 10) check = 0;
    return 'I' + digits.join('') + check;
  }
  // Compute valid ISWC check digit
  function iswcCheck(nineDigits) {
    let sum = 1;
    for (let i = 0; i < 9; i++) sum += parseInt(nineDigits[i], 10) * (i + 1);
    return (10 - (sum % 10)) % 10;
  }
  // Repair an ISWC string so its check digit is valid
  function fixIswc(iswc) {
    if (!iswc) return iswc;
    // Multi-ISWC: catalog occasionally stores comma-joined IDs ("T9150...,T9110...")
    // CWR allows only one — pick the first non-empty entry.
    const first = String(iswc).split(/[,;|]/)[0].trim();
    if (!first) return '';
    const s = first.toUpperCase().replace(/[\s\-.]/g, '');
    if (!/^T\d{10}$/.test(s)) return first;
    const nine = s.slice(1, 10);
    const c = iswcCheck(nine);
    return 'T-' + nine.slice(0, 3) + '.' + nine.slice(3, 6) + '.' + nine.slice(6, 9) + '-' + c;
  }

  const SOC_CODES = {
    'ASCAP': '010', 'BMI': '021', 'SESAC': '071',
    'PRS': '052', 'PRS for Music': '052', 'MCPS': '052',
    'GEMA': '035', 'SACEM': '058', 'SIAE': '040', 'SGAE': '079',
    'SOCAN': '101', 'JASRAC': '008', 'KOMCA': '019',
    'APRA': '023', 'AMRA': '054', 'STIM': '079', 'TONO': '090',
    'MLC': '044', 'HFA': '073',
  };
  const TIS_WORLD = 2136;
  const TIS_USA = 840;
  const TIS_UK = 826;
  const TIS_EU = 2100;

  function splitShares(sharesStr, count) {
    if (!sharesStr) return Array(count).fill(100 / count);
    const parts = String(sharesStr).split('/').map((x) => parseFloat(x) || 0);
    if (parts.length !== count) {
      // distribute equal
      return Array(count).fill(100 / count);
    }
    return parts;
  }

  function lastFirst(name) {
    if (!name) return ['', ''];
    const parts = String(name).trim().split(/\s+/);
    if (parts.length === 1) return [parts[0].toUpperCase(), ''];
    const first = parts.slice(0, -1).join(' ');
    const last = parts[parts.length - 1];
    return [last.toUpperCase(), first.toUpperCase()];
  }

  // ═══════════════════════════════════════════════════════════════════════
  // REAL ROCKET SCIENCE CATALOG → CWR HYDRATION
  // ═══════════════════════════════════════════════════════════════════════
  // Pulls real writer shares, real IPIs, real publishers from the RS data
  // already loaded into window.WORKS / PROFILES / PUBLISHERS / RECORDINGS.
  // This is what makes our CWR output reflect actual catalog state.
  function hydrateFromRsCatalog(work, opts) {
    opts = opts || {};
    const senderIpi = opts.senderIpi || 199900001;
    const senderName = opts.senderName || 'PLURALIS MUSIC';

    const writerShares = work.__rsWriterShares || [];
    if (!writerShares.length) return null;

    const profiles = window.PROFILES || [];
    const publishers = window.PUBLISHERS || [];

    // Find profile by writer name (matches RS bridge logic)
    const profileForWriter = (writerName) => {
      if (!writerName) return null;
      const key = writerName.toLowerCase();
      return profiles.find(p =>
        (p.realName || '').toLowerCase() === key ||
        (p.name || '').toLowerCase() === key ||
        (p.__rsRaw?.artist_name || '').toLowerCase() === key
      ) || null;
    };
    const publisherFor = (name) => {
      if (!name) return null;
      const key = name.toLowerCase();
      return publishers.find(p => (p.name || '').toLowerCase() === key) || null;
    };

    // PRO name → CISAC society code
    const pro = (name) => {
      const m = { ASCAP:'010', BMI:'021', SESAC:'023', GMR:'071', PRS:'052', GEMA:'035',
        SACEM:'058', SIAE:'040', JASRAC:'011', SOCAN:'101', APRA:'040',
        STIM:'079', AKM:'012', MLC:'776', HFA:'073' };
      return m[name] || m[(name||'').toUpperCase()] || '010';
    };

    // Parse "50.00%" → 5000 basis points (CWR canonical)
    const parsePct = (s) => {
      if (!s) return 0;
      const m = String(s).match(/(\d+(?:\.\d+)?)/);
      return m ? Math.round(parseFloat(m[1]) * 100) : 0;
    };

    // Writer designation from "Composer/Author" etc.
    const designation = (role) => {
      if (!role) return 'CA';
      const r = role.toLowerCase();
      if (/composer.*author|author.*composer/.test(r)) return 'CA';
      if (/composer/.test(r)) return 'C';
      if (/author|lyric/.test(r)) return 'A';
      return 'CA';
    };
    const pubType = (role) => {
      if (!role) return 'E';
      const r = role.toLowerCase();
      if (/admin/.test(r)) return 'AM';
      if (/sub-?pub/.test(r)) return 'SE';
      if (/acqu/.test(r)) return 'AQ';
      return 'E';
    };

    const seed = hash(work.id || 'x'); // stable for any synthetic fallback ids

    // Build writer + publisher chains from real RS data
    // CWR share semantics: writer.prShare + (sum of publisher.prShare in that writer's chain) = writer's overall slice.
    // The publisher's prShare is what THE PUBLISHER COLLECTS from the writer's slice.
    // The writer's prShare on SWR is what THE WRITER RETAINS after publisher cut.
    const writers = writerShares.map((ws, i) => {
      const profile = ws.profile || profileForWriter(ws.Writer);
      const fullName = profile?.last_name && profile?.first_name
        ? [profile.last_name, profile.first_name].join(' ')
        : (profile?.realName || profile?.name || ws.Writer || '');
      const [last, first] = lastFirst(fullName);
      const realIpi = profile?.ipi_name_number || profile?.ipi || profile?.cae || '';
      const proName = profile?.pro_affiliation || profile?.pro || ws['USA License'];
      const proCode = pro(proName);

      const writerTotalPR = parsePct(ws['Performance Share']); // writer's overall slice
      const writerTotalMR = parsePct(ws['Mech Share']);
      const writerTotalSR = parsePct(ws['Sync Share']);

      // Build PWR + SPU chain for this writer's publishers
      const pwrLinks = [];
      const writerPublishers = (ws.publishingShares || []).map((ps, pi) => {
        const pubRecord = ps.publisher ? publisherFor(ps.publisher.name) : publisherFor(ps.Publisher);
        const pubIpi = pubRecord?.ipi || pubRecord?.__rsRaw?.IPI || '';
        const pubName = (ps.Publisher || pubRecord?.name || '').toUpperCase().slice(0, 45);
        const pubId = 'P' + String(hash(pubName + i + pi)).slice(-9).padStart(9, '0');
        const ag = 'AGR' + String(hash(work.id + pubName)).slice(-8).padStart(8, '0');

        if (i === 0 && pi === 0) {
          pwrLinks.push({
            publisherId: pubId,
            publisherName: pubName,
            submitterAgreementNum: ag,
            societyAgreementNum: '',
          });
        }
        return {
          id: pubId,
          controlled: ps.Controlled === 'Yes',
          name: pubName,
          role: pubType(ps['Publisher Role'] || pubRecord?.role),
          ipiName: realIpi || '', // Will fill from publisher's actual ipi below
          publisherIpi: pubIpi,
          intStandardCode: '',
          prSocCode: pro(pubRecord?.society || pubRecord?.__rsRaw?.['PRO Affiliation'] || ws['USA License']),
          prShare: parsePct(ps['Performance Share']),
          mrSocCode: pro(pubRecord?.society || ws['USA License']),
          mrShare: parsePct(ps['Mech Share']),
          srSocCode: pro(pubRecord?.society || ws['USA License']),
          srShare: parsePct(ps['Sync Share']),
          agreement: ag,
          agreementNum: parseInt(ag.replace(/\D/g,'').slice(0,10) || '0', 10),
          agreementType: 'OS',
          prAffiliation: parseInt(pro(pubRecord?.society || ws['USA License']), 10),
          territories: (ps.territories || []).map((t, ti) => ({
            tisNum: 2136,
            prShare: parsePct(t['Performance Share']),
            mrShare: parsePct(t['Mech Share']),
            srShare: parsePct(t['Sync Share']),
            sequence: ti + 1,
          })),
        };
      });
      // Publisher takings from this writer's slice
      const pubTakeS = writerPublishers.reduce((acc, p) => ({
        pr: acc.pr + (p.prShare || 0),
        mr: acc.mr + (p.mrShare || 0),
        sr: acc.sr + (p.srShare || 0),
      }), { pr: 0, mr: 0, sr: 0 });

      // Splice each writer's publishers into the work-level publisher list
      writers_publishers_buffer.push(...writerPublishers.map(p => ({ ...p, ipiName: p.publisherIpi || ipi(seed + i + 17) })));

      // Writer SWR PR/MR/SR = writer's overall share MINUS publisher take
      // (writer's own collection portion)
      const writerSwrPR = Math.max(0, writerTotalPR - pubTakeS.pr);
      const writerSwrMR = Math.max(0, writerTotalMR - pubTakeS.mr);
      const writerSwrSR = Math.max(0, writerTotalSR - pubTakeS.sr);

      return {
        id: 'W' + String(hash(work.id + i)).slice(-9).padStart(9, '0'),
        controlled: ws.Controlled === 'Yes',
        lastName: last,
        firstName: first,
        designation: designation(ws['Writer Role']),
        ipiName: realIpi || ipi(seed + i),
        intStandardCode: '',
        prSocCode: proCode,
        prShare: writerSwrPR,
        mrSocCode: proCode,
        mrShare: writerSwrMR,
        srSocCode: proCode,
        srShare: writerSwrSR,
        prAffiliation: parseInt(proCode, 10),
        territories: (ws.territories || []).map((t, ti) => ({
          tisNum: 2136,
          prShare: writerSwrPR,
          mrShare: writerSwrMR,
          srShare: writerSwrSR,
        })),
        pwrLinks,
      };
    });

    // Dedupe publishers by id
    const seenPub = new Set();
    const dedupedPublishers = [];
    for (const p of writers_publishers_buffer) {
      if (seenPub.has(p.id)) continue;
      seenPub.add(p.id);
      dedupedPublishers.push(p);
    }

    // Reset buffer for next call
    writers_publishers_buffer.length = 0;

    // Real recordings → REC records
    const realRecs = work.__rsRecordings || [];
    const recordings = realRecs.map((rec) => ({
      releaseDate: (rec['Release Date'] || '').replace(/-/g, '') || '20240101',
      releaseDuration: rec.Duration || work.duration || '000300',
      albumTitle: rec['Release Title'] || rec.Album || work.title,
      albumLabel: rec.Label || '',
      releaseCatalogNum: '',
      eanUpc: '',
      isrc: rec.ISRC || '',
      recFormat: 'CD',
      recordingTechnique: 'A',
      mediaType: 'CD ',
      recordingTitle: rec['Recording Title'] || work.title,
      versionTitle: '',
      displayArtist: rec['Recording Artist'] || rec['Release Artist'] || (writers[0]?.lastName || ''),
      recordLabel: rec.Label || '',
      submitterRecordingId: rec['Recording ID'] || '',
    }));

    // Performers from RS recording artistShares
    const performers = [];
    realRecs.slice(0, 3).forEach((rec) => {
      (rec.artistShares || []).slice(0, 2).forEach((a) => {
        const profile = profileForWriter(a.profile?.artist_name || a.Artist);
        const [last, first] = lastFirst(profile?.realName || a.profile?.artist_name || a.Artist || '');
        if (last) performers.push({ lastName: last, firstName: first, ipiName: profile?.ipi || profile?.cae || '' });
      });
    });

    return {
      id: work.id,
      title: work.title,
      iswc: fixIswc(work.iswc),
      lang: work.__rsRaw?.Language || 'EN',
      submitterWorkId: work.id,
      duration: work.duration || 0,
      copyrightDate: work.copyrightDate || '',
      copyrightNumber: '',
      musicalWorkDistribution: 'POP',
      recorded: realRecs.length > 0,
      textMusicRel: 'MTX',
      versionType: 'ORI',
      contactName: senderName,
      contactId: 'CONTACT01',
      workType: 'PO',
      grandRights: false,
      writers,
      publishers: dedupedPublishers,
      alternateTitles: work.title ? [
        { title: String(work.title).toUpperCase(), titleType: 'AT', lang: 'EN' },
      ] : [],
      performers,
      recordings,
      origins: [],
      instrumentations: [],
      instrumentDetails: [],
      components: [],
      relatedInfo: [],
      xrefs: work.iswc ? [{
        organisationCode: '099', identifier: work.iswc, identifierType: 'W', validityIndicator: 'Y',
      }] : [],
      messages: [],
      _isRealCatalog: true,
    };
  }
  // Buffer used by hydrateFromRsCatalog to collect publishers across writer-shares
  const writers_publishers_buffer = [];

  function hydrateWorkSynthetic(work, opts) {
    const seed = hash(work.id || work.title || 'x');
    const r = rng(seed);
    opts = opts || {};
    const senderIpi = opts.senderIpi || 199900001;
    const senderName = opts.senderName || 'PLURALIS MUSIC';

    // ─── Writers
    const wNames = work.writers || [];
    const wShares = splitShares(work.shares, wNames.length || 1);
    const proCode = SOC_CODES[work.pro] || '010';
    const writers = wNames.map((name, i) => {
      const [last, first] = lastFirst(name);
      const wseed = seed + i * 7919;
      return {
        id: 'W' + String(seed + i).slice(-9).padStart(9, '0'),
        controlled: i === 0,
        lastName: last,
        firstName: first,
        designation: i === 0 ? 'CA' : 'C',
        ipiName: ipi(wseed),
        intStandardCode: ipiBase(wseed + 1),
        prSocCode: proCode,
        prShare: wShares[i] / 2,        // half writer share is the writer-direct portion (rest assigned via PWR)
        mrSocCode: '044',
        mrShare: wShares[i],
        srSocCode: proCode,
        srShare: wShares[i],
        prAffiliation: parseInt(proCode, 10),
        territories: [{ tisNum: TIS_WORLD, prShare: wShares[i] / 2, mrShare: wShares[i], srShare: wShares[i] }],
        pwrLinks: i === 0 ? [{
          publisherId: 'P' + String(seed).slice(-9).padStart(9, '0'),
          publisherName: (work.catalog || senderName).toUpperCase().slice(0, 45),
          submitterAgreementNum: 'AGR' + String(seed).slice(-8),
          societyAgreementNum: '',
        }] : [],
      };
    });

    // ─── Publishers (controlled — owner; non-controlled — co-pub if hinted)
    const totalShare = wShares.reduce((a, b) => a + b, 0) || 100;
    const ownerName = (work.catalog || senderName).toUpperCase();
    const publishers = [{
      id: 'P' + String(seed).slice(-9).padStart(9, '0'),
      controlled: true,
      name: ownerName.slice(0, 45),
      role: 'E', // E = original publisher
      ipiName: ipi(seed + 17),
      intStandardCode: ipiBase(seed + 19),
      prSocCode: proCode,
      prShare: totalShare / 2,           // collected publisher share = writer's missing half
      mrSocCode: '044',
      mrShare: totalShare,
      srSocCode: proCode,
      srShare: totalShare,
      agreement: 'AGR' + String(seed).slice(-8),
      agreementNum: parseInt(String(seed).slice(-10), 10),
      agreementType: 'OS',                // Original specific
      prAffiliation: parseInt(proCode, 10),
      shareInOtherTerritories: 0,
      territories: [{ tisNum: TIS_WORLD, prShare: totalShare / 2, mrShare: totalShare, srShare: totalShare, sequence: 1 }],
    }];
    // 30% chance of an admin/sub-pub
    if (r() > 0.7) {
      publishers.push({
        id: 'P' + String(seed + 1).slice(-9).padStart(9, '0'),
        controlled: false,
        name: 'KOBALT MUSIC PUBLISHING',
        role: 'AM',
        ipiName: ipi(seed + 211),
        prSocCode: '052',
        prShare: 0,
        mrSocCode: '052',
        mrShare: 0,
        territories: [{ tisNum: TIS_EU, sequence: 1 }],
      });
    }

    // ─── Alternate titles
    const alts = [];
    if (work.title) {
      // Capitalised variant + a translation guess
      alts.push({ title: String(work.title).toUpperCase(), titleType: 'AT', lang: 'EN' });
      if (r() > 0.6) alts.push({ title: 'INSTRUMENTAL VERSION', titleType: 'AT', lang: 'EN' });
      if (r() > 0.85) alts.push({ title: '(' + String(work.title).toUpperCase() + ' MIX)', titleType: 'AT', lang: 'EN' });
    }

    // ─── Performers
    const performers = wNames.slice(0, 1).map((name) => {
      const [last, first] = lastFirst(name);
      return { lastName: last, firstName: first, ipiName: ipi(seed + 91) };
    });

    // ─── Recordings (from work itself)
    const recordings = [];
    if (work.duration && work.plays) {
      recordings.push({
        releaseDate: '20240315',
        releaseDuration: work.duration,
        albumTitle: work.album || work.title,
        albumLabel: work.label || (work.catalog || ''),
        releaseCatalogNum: '',
        eanUpc: '',
        isrc: 'USRC1' + String(2400000 + (seed % 99999)).slice(-7),
        recFormat: 'CD',
        recordingTechnique: 'A',
        mediaType: 'CD ',
        recordingTitle: work.title,
        versionTitle: '',
        displayArtist: (wNames[0] || ''),
        recordLabel: work.label || '',
      });
    }

    return {
      id: work.id,
      title: work.title,
      iswc: fixIswc(work.iswc),
      lang: 'EN',
      submitterWorkId: work.id,
      duration: work.duration || 0,
      copyrightDate: work.copyrightDate || '',
      copyrightNumber: '',
      musicalWorkDistribution: 'POP',
      recorded: !!work.duration,
      textMusicRel: 'MTX',
      versionType: 'ORI',
      contactName: senderName,
      contactId: 'CONTACT01',
      workType: 'PO',
      grandRights: false,
      writers,
      publishers,
      alternateTitles: alts,
      performers,
      recordings,
      origins: [],
      instrumentations: [],
      instrumentDetails: [],
      components: [],
      relatedInfo: [],
      xrefs: work.iswc ? [{
        organisationCode: '099', identifier: work.iswc, identifierType: 'W', validityIndicator: 'Y',
      }] : [],
      messages: [],
    };
  }

  // ─── Top-level dispatcher: real RS data first, synthetic fallback ──
  function hydrateWork(work, opts) {
    if (work && work.__rsWriterShares && work.__rsWriterShares.length > 0) {
      try {
        const real = hydrateFromRsCatalog(work, opts);
        if (real) return real;
      } catch (e) {
        console.warn('[cwr-synth] real catalog hydration failed, falling back to synthetic:', e);
      }
    }
    return hydrateWorkSynthetic(work, opts);
  }

  window.CwrSynth = { hydrateWork };
})();
