// ============================================================================
// CAF BUILDER · spec-driven · v1.2 + v3.0
// ----------------------------------------------------------------------------
// Reads window.CafSpec field definitions and renders agreements as fixed-width
// CAF records. Replaces the hand-rolled v1.2-only builder.
//
// Lifecycle: hydrated agreement → recordObjects[] → fixed-width lines.
// EXPORT: window.CafBuild = { buildTransmission, buildFilename, formatRecord, ... }
// ============================================================================
(function () {
  'use strict';
  const Spec = window.CafSpec;
  if (!Spec) { console.error('caf-build-v3: CafSpec missing'); return; }

  // ─── Pad/format helpers driven by field type ─────────────────────────────
  function asciiFold(s) {
    if (!s || typeof s !== 'string') return s || '';
    if (!/[^\x00-\x7F]/.test(s)) return s;
    return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^\x00-\x7F]/g, '?');
  }
  function fitR(s, n) { s = String(s == null ? '' : s); return s.length >= n ? s.slice(0, n) : s + ' '.repeat(n - s.length); }
  function fitL(s, n) { s = String(s == null ? '' : s); return s.length >= n ? s.slice(0, n) : ' '.repeat(n - s.length) + s; }
  function padNum(v, n) { v = (v == null || v === '') ? 0 : v; const s = String(Math.abs(parseInt(v, 10) || 0)); return s.length >= n ? s.slice(-n) : '0'.repeat(n - s.length) + s; }
  function padDate(d, n) {
    if (!d) return ' '.repeat(n);
    if (d instanceof Date) {
      return String(d.getUTCFullYear()) + String(d.getUTCMonth() + 1).padStart(2,'0') + String(d.getUTCDate()).padStart(2,'0');
    }
    const s = String(d).replace(/[^\d]/g, '');
    return s.length >= n ? s.slice(0, n) : s + ' '.repeat(n - s.length);
  }
  function padTime(d, n) {
    if (!d) return '0'.repeat(n);
    if (d instanceof Date) {
      return String(d.getUTCHours()).padStart(2,'0') + String(d.getUTCMinutes()).padStart(2,'0') + String(d.getUTCSeconds()).padStart(2,'0');
    }
    const s = String(d).replace(/[^\d]/g, '');
    return s.length >= n ? s.slice(0, n) : '0'.repeat(n - s.length) + s;
  }
  function padPCT(v, n) {
    // v can be 50 (percent), 50.0, 0.50, "5000" (already bp)
    if (v == null || v === '') return '0'.repeat(n);
    let bp;
    if (typeof v === 'string' && /^\d+$/.test(v) && v.length === n) return v;
    const num = parseFloat(v);
    bp = Math.round(num * 100);
    if (bp > 10000) bp = 10000;
    return padNum(bp, n);
  }
  function padIPI(v, n) {
    if (v == null || v === '') return ' '.repeat(n);
    return fitL(String(v).trim(), n);
  }
  function formatField(field, value, isUtf8) {
    const len = field.len;
    if (field.default && (value == null || value === '')) value = field.default;
    if (value == null) value = '';
    switch (field.type) {
      case 'A':
      case 'AN':
        return fitR(asciiFold(String(value)), len);
      case 'NRA':
        // Non-Roman: keep UTF-8 if v3.0 char set is UTF-8; else fall back to ASCII fold
        if (isUtf8) return fitR(String(value), len);
        return fitR(asciiFold(String(value)), len);
      case 'L':
        return fitR(String(value), len);
      case 'N':
        return padNum(value, len);
      case 'D':
        return padDate(value, len);
      case 'T':
        return padTime(value, len);
      case 'TIS':
        return /^\d+$/.test(String(value)) ? padNum(value, len) : fitR(value, len);
      case 'SOC':
        return padNum(value, len);
      case 'IPI':
        return padIPI(value, len);
      case 'PCT':
        return padPCT(value, len);
      default:
        return fitR(String(value), len);
    }
  }

  // ─── Format one record from a values object ──────────────────────────────
  function formatRecord(version, type, values, opts = {}) {
    const rec = Spec.getRecord(version, type);
    if (!rec) throw new Error('unknown record type: ' + type);
    const isUtf8 = opts.charset === 'UTF-8' || (version || '').startsWith('3');
    let out = '';
    for (const f of rec.fields) {
      const v = values[f.name] != null ? values[f.name] : (f.default != null ? f.default : '');
      out += formatField(f, v, isUtf8);
    }
    // Truncate/pad to declared length defensively
    if (out.length > rec.length) out = out.slice(0, rec.length);
    if (out.length < rec.length) out = out + ' '.repeat(rec.length - out.length);
    return out;
  }

  // ─── Build the full transmission ─────────────────────────────────────────
  function buildTransmission(opts) {
    const {
      senderType = 'SP', senderId, senderName,
      agreements, created = new Date(),
      version = '1.2',
      txnType = 'AGR',
    } = opts;
    const lines = [];
    const recordObjects = []; // for downstream introspection / JSON sibling
    const isV3 = String(version).startsWith('3');
    const charset = isV3 ? 'UTF-8' : 'ASCII';

    // HDR
    const hdrVals = {
      recordType: 'HDR', senderType, senderId, senderName,
      ediStandardVersion: isV3 ? '03.00' : '01.20',
      creationDate: created, creationTime: created,
      transmissionDate: created,
      ...(isV3 ? { characterSet: 'UTF-8          ' } : {}),
    };
    lines.push(formatRecord(version, 'HDR', hdrVals, { charset }));
    recordObjects.push({ type: 'HDR', values: hdrVals });

    // GRH
    const groupId = 1;
    const grhVals = {
      recordType: 'GRH', transactionType: txnType, groupId,
      versionNumber: isV3 ? '03.00' : '01.20',
      batchRequest: 0,
      ...(isV3 ? { submissionDistributionType: 'SU' } : {}),
    };
    lines.push(formatRecord(version, 'GRH', grhVals, { charset }));
    recordObjects.push({ type: 'GRH', values: grhVals });

    let txnSeq = 0;
    let totalRecords = 2; // HDR + GRH
    let groupRecords = 1; // GRH

    for (const ag of (agreements || [])) {
      let recSeq = 0;
      const txnLines = [];

      // AGR
      const agrVals = {
        recordType: 'AGR',
        transactionSequence: txnSeq,
        recordSequence: recSeq++,
        submitterAgreementNumber: ag.submitterAgreementNumber,
        internationalAgreementCode: ag.internationalAgreementCode || '',
        agreementType: ag.agreementType,
        agreementStartDate: ag.start,
        agreementEndDate: ag.end,
        retentionEndDate: ag.retentionEnd,
        priorRoyaltyStatus: ag.priorRoyaltyStatus || 'N',
        priorRoyaltyStartDate: ag.priorRoyaltyStartDate,
        postTermCollectionStatus: ag.postTermCollectionStatus || 'N',
        postTermCollectionEndDate: ag.postTermCollectionEndDate,
        dateOfSignature: ag.dateOfSignature,
        numberOfWorks: ag.numberOfWorks || 0,
        salesManufactureClause: ag.salesManufactureClause || ' ',
        sharesChange: ag.sharesChange ? 'Y' : 'N',
        advanceGiven: ag.advanceGiven ? 'Y' : 'N',
        societyAssignedAgreementNumber: ag.societyAssignedNumber || '',
        ...(isV3 ? { subPublisherFlag: ag.subPub ? 'Y' : 'N', electronicSignatureFlag: ag.eSignature ? 'Y' : 'N' } : {}),
      };
      txnLines.push(formatRecord(version, 'AGR', agrVals, { charset }));
      recordObjects.push({ type: 'AGR', values: agrVals });

      for (const t of (ag.territories || [])) {
        const v = {
          recordType: 'TER',
          transactionSequence: txnSeq, recordSequence: recSeq++,
          inclusionExclusionIndicator: t.inclusion || 'I',
          tisNumericCode: t.tisCode,
        };
        txnLines.push(formatRecord(version, 'TER', v, { charset }));
        recordObjects.push({ type: 'TER', values: v });
      }
      for (const p of (ag.parties || [])) {
        const v = {
          recordType: 'IPA',
          transactionSequence: txnSeq, recordSequence: recSeq++,
          agreementRoleCode: p.role,
          ipiNameNumber: p.ipiNameNumber,
          ipiBaseNumber: p.ipiBaseNumber,
          ipNumber: p.partyId,
          ipLastName: p.lastName,
          ipWriterFirstName: p.firstName,
          prAffiliationSocietyCode: p.prSoc, prShare: p.prShare,
          mrAffiliationSocietyCode: p.mrSoc, mrShare: p.mrShare,
          srAffiliationSocietyCode: p.srSoc, srShare: p.srShare,
          ...(isV3 ? { altIdSystem: p.altIdSystem, altIdValue: p.altIdValue } : {}),
        };
        txnLines.push(formatRecord(version, 'IPA', v, { charset }));
        recordObjects.push({ type: 'IPA', values: v });
        if (p.nonRomanName) {
          const nv = {
            recordType: 'NPA',
            transactionSequence: txnSeq, recordSequence: recSeq++,
            ipiNameNumber: p.ipiNameNumber,
            ipName: p.nonRomanName,
            ipWriterFirstName: p.nonRomanFirst || '',
            languageCode: p.nonRomanLang || '',
          };
          txnLines.push(formatRecord(version, 'NPA', nv, { charset }));
          recordObjects.push({ type: 'NPA', values: nv });
        }
      }
      for (const u of (ag.usages || [])) {
        const v = {
          recordType: 'USA',
          transactionSequence: txnSeq, recordSequence: recSeq++,
          rightTypeCode: u.rightCode, usageCode: u.usageCode || '',
          shareInRight: u.share == null ? 100 : u.share,
          inclusionExclusionIndicator: u.includeFlag || 'I',
        };
        txnLines.push(formatRecord(version, 'USA', v, { charset }));
        recordObjects.push({ type: 'USA', values: v });
      }
      for (const m of (ag.messages || [])) {
        const v = {
          recordType: 'AGM',
          transactionSequence: txnSeq, recordSequence: recSeq++,
          messageLevel: m.msgLevel || 'F', messageType: m.msgType || 'I',
          messageCode: m.msgCode || '', messageText: m.msgText || '',
        };
        txnLines.push(formatRecord(version, 'AGM', v, { charset }));
        recordObjects.push({ type: 'AGM', values: v });
      }

      lines.push(...txnLines);
      groupRecords += txnLines.length;
      totalRecords += txnLines.length;
      txnSeq += 1;
    }

    // GRT
    const grtVals = { recordType: 'GRT', groupId, transactionCount: txnSeq, recordCount: groupRecords + 1 };
    lines.push(formatRecord(version, 'GRT', grtVals, { charset }));
    recordObjects.push({ type: 'GRT', values: grtVals });

    // TRL — record count includes itself
    const trlVals = { recordType: 'TRL', groupCount: 1, transactionCount: txnSeq, recordCount: totalRecords + 2 };
    lines.push(formatRecord(version, 'TRL', trlVals, { charset }));
    recordObjects.push({ type: 'TRL', values: trlVals });

    return { lines, recordObjects, version, charset };
  }

  // ─── Build CAF v3.0 JSON sibling format ──────────────────────────────────
  // CAF v3.0 introduces an optional XML-like JSON envelope. Same data, structured.
  function buildJsonSibling(opts) {
    const built = buildTransmission({ ...opts, version: opts.version || '3.0' });
    const txns = [];
    let cur = null;
    for (const r of built.recordObjects) {
      if (r.type === 'AGR') {
        cur = { agreement: stripValues(r.values), territories: [], parties: [], usages: [], messages: [] };
        txns.push(cur);
        continue;
      }
      if (!cur) continue;
      if (r.type === 'TER') cur.territories.push(stripValues(r.values));
      else if (r.type === 'IPA') cur.parties.push(stripValues(r.values));
      else if (r.type === 'NPA') {
        const last = cur.parties[cur.parties.length - 1];
        if (last) last.nonRomanName = stripValues(r.values);
      }
      else if (r.type === 'USA') cur.usages.push(stripValues(r.values));
      else if (r.type === 'AGM') cur.messages.push(stripValues(r.values));
    }
    return {
      caf: {
        '@version': '3.0',
        '@charset': 'UTF-8',
        header: stripValues(built.recordObjects[0].values),
        groupHeader: stripValues(built.recordObjects[1].values),
        agreements: txns,
        groupTrailer: stripValues(built.recordObjects[built.recordObjects.length - 2].values),
        trailer: stripValues(built.recordObjects[built.recordObjects.length - 1].values),
      },
    };
  }
  function stripValues(v) {
    const out = {};
    for (const [k, val] of Object.entries(v)) {
      if (val instanceof Date) out[k] = val.toISOString();
      else if (k === 'recordType') continue;
      else out[k] = val;
    }
    return out;
  }

  // ─── Filename ─────────────────────────────────────────────────────────────
  function buildFilename({ senderCode = 'PLU', recipientCode = 'ASCAP', sequence = 1, created = new Date(), version = '1.2' }) {
    const yy = String(created.getUTCFullYear()).slice(-2);
    const seq = String(sequence).padStart(4, '0');
    const v = version.replace('.', '');
    return `CA${yy}${seq}${senderCode.slice(0,3)}_${recipientCode.slice(0,3)}.V${v}`;
  }

  window.CafBuild = {
    buildTransmission,
    buildJsonSibling,
    buildFilename,
    formatRecord,
    RECORD_LENGTHS: Object.fromEntries(Object.entries(Spec.RECORDS_V12).map(([k, r]) => [k, r.length])),
  };
})();
