// ============================================================================
// CAF DECODE · turn a CAF line back into named fields
// ----------------------------------------------------------------------------
// Used by the inspector pane: click a record line, see every field labeled
// with offset, length, raw value, and decoded interpretation.
// EXPORT: window.CafDecode = { decodeLine, FIELD_SPECS }
// ============================================================================

(function () {
  'use strict';

  // FieldSpec: [name, length, hint, decode?]
  // decode is optional: (raw) => human label
  const SOC_LABELS = {
    '010':'ASCAP','021':'BMI','023':'SESAC','071':'GMR','073':'HFA','776':'MLC',
    '052':'PRS for Music','035':'GEMA','058':'SACEM','040':'SIAE','079':'SGAE',
    '011':'JASRAC','101':'SOCAN','019':'KOMCA','089':'SUISA','090':'TONO',
  };
  const RIGHT_LABELS = {
    'M':'Mechanical','MA':'Mech · Radio','MB':'Mech · Non-Interactive','MD':'Mech · Interactive',
    'MR':'Mech · Sound Carrier','MT':'Mech · TV','MV':'Mech · Video',
    'P':'Performing','OB':'Making Available · Non-Interactive','OD':'Making Available · Interactive',
    'PC':'Public Performance of Recording','PR':'Performing Right','PT':'Theatrical','RB':'Radio Broadcast',
  };
  const USAGE_LABELS = {
    'BT':'Blank Tape', 'MP':'Motion Picture',
    '10':'Audio Carriers','11':'Audio-Visual Carriers',
    '15':'A/V Streaming · Non-OD','16':'A/V Streaming · OD',
    '20':'Radio','21':'Television',
    '40':'Download · Music','41':'Download · A/V','42':'Download · Ringtones',
    '44':'Internet Radio','45':'Streaming · Non-OD','46':'Streaming · OD',
  };
  const AGREEMENT_TYPE_LABELS = {
    'OS':'Original Specific','OG':'Original General',
    'PS':'Packaged Specific','PG':'Packaged General',
  };
  const ROLE_LABELS = { 'AS':'Assignor', 'AC':'Acquirer' };
  const SENDER_LABELS = { 'SP':'Society', 'PB':'Publisher', 'AA':'Aggregator' };

  function pct(raw) {
    const n = parseInt(raw, 10);
    if (isNaN(n)) return raw;
    return (n / 100).toFixed(2) + '%';
  }
  function ipiFmt(raw) {
    const t = (raw||'').trim();
    if (!t) return '—';
    return t;
  }
  function dateFmt(raw) {
    const t = (raw||'').trim();
    if (!/^\d{8}$/.test(t)) return raw;
    return `${t.slice(0,4)}-${t.slice(4,6)}-${t.slice(6,8)}`;
  }
  function timeFmt(raw) {
    const t = (raw||'').trim();
    if (!/^\d{6}$/.test(t)) return raw;
    return `${t.slice(0,2)}:${t.slice(2,4)}:${t.slice(4,6)}`;
  }

  const FIELD_SPECS = {
    HDR: [
      ['Record type', 3, 'Always HDR'],
      ['Sender type', 2, 'SP=Society, PB=Publisher, AA=Aggregator', s => SENDER_LABELS[s.trim()] || s],
      ['Sender ID', 9, 'CAE/IPI of sender'],
      ['Sender name', 45, 'Sender display name'],
      ['CAF version', 5, 'e.g. 01.20'],
      ['Creation date', 8, 'YYYYMMDD', dateFmt],
      ['Creation time', 6, 'HHMMSS', timeFmt],
      ['Transmission date', 8, 'YYYYMMDD', dateFmt],
    ],
    GRH: [
      ['Record type', 3, 'Always GRH'],
      ['Transaction type', 3, 'AGR for agreements'],
      ['Group ID', 5, 'Sequential group number within transmission'],
      ['Version', 5, 'CAF version'],
      ['Reserved', 10, 'Reserved for future use'],
    ],
    GRT: [
      ['Record type', 3, 'Always GRT'],
      ['Group ID', 5, 'Matches GRH group ID'],
      ['Transaction count', 8, 'Agreements in this group'],
      ['Record count', 8, 'Total records in group incl. GRH/GRT'],
    ],
    TRL: [
      ['Record type', 3, 'Always TRL'],
      ['Group count', 5, 'Total groups in transmission'],
      ['Transaction count', 8, 'Total agreements'],
      ['Record count', 8, 'Total records incl. HDR/TRL'],
    ],
    AGR: [
      ['Record type', 3, 'Always AGR'],
      ['Transaction seq', 8, 'Zero-based agreement index'],
      ['Record seq', 8, 'Record index within transaction'],
      ['Submitter agreement #', 14, 'Sender\'s internal agreement ID'],
      ['International agreement code', 14, 'ISAN if assigned'],
      ['Agreement type', 2, 'OS/OG/PS/PG', s => AGREEMENT_TYPE_LABELS[s.trim()] || s],
      ['Agreement start', 8, 'YYYYMMDD', dateFmt],
      ['Agreement end', 8, 'YYYYMMDD — blank = open-ended', dateFmt],
      ['Retention end', 8, 'YYYYMMDD — when collection right ends', dateFmt],
      ['Prior royalty status', 1, 'N=None, A=All, D=Designated'],
      ['Prior royalty start', 8, 'YYYYMMDD', dateFmt],
      ['Post-term collection', 1, 'N=None, O=Open, D=Designated'],
      ['Post-term end', 8, 'YYYYMMDD', dateFmt],
      ['Date of signature', 8, 'YYYYMMDD', dateFmt],
      ['Number of works', 5, 'Works covered (specific agreements)'],
      ['Sales/manufacture clause', 1, 'S=Sales, M=Manufacture, blank'],
      ['Shares change', 1, 'Y/N — share splits change over term'],
      ['Advance given', 1, 'Y/N'],
      ['Society-assigned #', 14, 'Filled on ack'],
    ],
    TER: [
      ['Record type', 3, 'Always TER'],
      ['Transaction seq', 8, 'Parent AGR index'],
      ['Record seq', 8, 'Record index within transaction'],
      ['Inclusion/Exclusion', 1, 'I=Include, E=Exclude'],
      ['TIS code', 4, 'CISAC TIS territory code'],
    ],
    IPA: [
      ['Record type', 3, 'Always IPA'],
      ['Transaction seq', 8, 'Parent AGR index'],
      ['Record seq', 8, 'Record index within transaction'],
      ['Role', 2, 'AS=Assignor, AC=Acquirer', s => ROLE_LABELS[s.trim()] || s],
      ['IPI name #', 11, 'Mod-101 valid'],
      ['IPI base #', 13, 'Mod-11 valid'],
      ['Submitter party ID', 9, 'Sender-internal party identifier'],
      ['Last name / org', 45, 'Surname or company name'],
      ['First name', 30, 'Empty for organizations'],
      ['PR society', 3, '', s => SOC_LABELS[s] || s],
      ['PR share', 5, 'Basis points', pct],
      ['MR society', 3, '', s => SOC_LABELS[s] || s],
      ['MR share', 5, 'Basis points', pct],
      ['SR society', 3, '', s => SOC_LABELS[s] || s],
      ['SR share', 5, 'Basis points', pct],
    ],
    NPA: [
      ['Record type', 3, 'Always NPA'],
      ['Transaction seq', 8, 'Parent AGR index'],
      ['Record seq', 8, 'Record index within transaction'],
      ['IPI name #', 11, 'Matches IPA party'],
      ['Non-Roman name', 160, 'UTF-8'],
      ['Non-Roman first name', 160, 'UTF-8'],
      ['Language code', 2, 'ISO 639-1'],
    ],
    USA: [
      ['Record type', 3, 'Always USA'],
      ['Transaction seq', 8, 'Parent AGR index'],
      ['Record seq', 8, 'Record index within transaction'],
      ['Right code', 3, 'M, P, MA, OD…', s => RIGHT_LABELS[s.trim()] || s],
      ['Usage code', 5, '', s => USAGE_LABELS[s.trim()] || s || '—'],
      ['Share', 5, 'Basis points', pct],
      ['Inclusion', 1, 'I=Include, E=Exclude'],
    ],
    AGM: [
      ['Record type', 3, 'Always AGM'],
      ['Transaction seq', 8, 'Parent AGR index'],
      ['Record seq', 8, 'Record index within transaction'],
      ['Message level', 1, 'F=Field, R=Record, T=Transaction'],
      ['Message type', 1, 'I=Info, W=Warn, E=Error'],
      ['Message code', 3, 'CISAC message code'],
      ['Message text', 150, 'Free text'],
    ],
  };

  function decodeLine(line, _version) {
    if (!line || line.length < 3) return null;
    const type = line.slice(0, 3);
    const spec = FIELD_SPECS[type];
    if (!spec) return { type, fields: [{ label: 'Unknown record type', value: line, decoded: '' }] };
    const fields = [];
    let off = 0;
    for (const [name, len, hint, decode] of spec) {
      const raw = line.slice(off, off + len);
      const decoded = decode ? decode(raw) : '';
      fields.push({ name, len, offset: off, raw, decoded, hint });
      off += len;
    }
    return { type, fields };
  }

  window.CafDecode = { decodeLine, FIELD_SPECS, SOC_LABELS, RIGHT_LABELS, USAGE_LABELS, AGREEMENT_TYPE_LABELS, ROLE_LABELS };
})();
