// ============================================================================
// CWR CERTIFICATION DRIVER
// ----------------------------------------------------------------------------
// Automated end-to-end certification harness. Drives every society through
// the full conformance flow:
//
//   1. Build a per-society TEST CORPUS (purpose-built works that exercise the
//      society's specific rules — JASRAC needs non-Roman, MLC needs ISWC+REC,
//      ASCAP needs PWR-agreement linkage, etc.)
//   2. Generate the CWR file using society-specific rules from CwrSociety
//   3. Run validators: CwrValidate + CwrSociety + CwrV31 (if v3.x)
//   4. Simulate the society's ACK response based on rule conformance:
//        AS  = transaction accepted
//        AC  = accepted with corrections
//        RJ  = rejected (validator caught a hard error)
//        NP  = needs additional info
//        IR  = invalid record
//   5. Compute per-society readiness score and certification recommendation
//   6. Track delta against previous run (regression detection)
//
// EXPORT: window.CwrCertDriver = { runFullCertification, runForSociety, ... }
// ============================================================================

(function () {
  'use strict';

  // ─── TEST CORPUS — society-specific test cases ───────────────────────────
  // Each society has a CISAC-published test corpus they expect to receive.
  // We model the SHAPES of those tests here. In production these would come
  // from the society's own test-file documentation.

  const TEST_CORPUS = {
    // ASCAP requires PWR linkage and submitter agreement on every controlled chain
    ASCAP: [
      { name: 'Single-writer self-publisher', kind: 'NWR', requires: ['SWR', 'SPU', 'PWR'], controlled: 1 },
      { name: 'Co-write 50/50 split', kind: 'NWR', requires: ['SWR×2', 'SPU×2', 'PWR×2'], controlled: 2 },
      { name: 'Three writers + admin pub', kind: 'NWR', requires: ['SWR×3', 'SPU', 'OPU', 'PWR×3'], controlled: 3 },
      { name: 'Revision (REV)', kind: 'REV', requires: ['SWR', 'SPU'], controlled: 1 },
    ],
    BMI: [
      { name: 'BMI-only writer', kind: 'NWR', requires: ['SWR(BMI)', 'SPU', 'PWR'], controlled: 1 },
      { name: 'BMI/ASCAP split', kind: 'NWR', requires: ['SWR×2', 'SPU×2', 'PWR×2'], controlled: 2 },
      { name: 'Submitter publisher number', kind: 'NWR', requires: ['SPU(submitter#)'], controlled: 1 },
    ],
    SESAC: [
      { name: 'SESAC writer w/ admin', kind: 'NWR', requires: ['SWR', 'SPU', 'PWR'], controlled: 1 },
    ],
    GMR: [
      { name: 'GMR exclusive writer', kind: 'NWR', requires: ['SWR', 'SPU'], controlled: 1 },
    ],
    PRS: [
      { name: 'UK first-use w/ ORN', kind: 'NWR', requires: ['SWR', 'SPU', 'ORN'], controlled: 1 },
      { name: 'Library cue', kind: 'NWR', requires: ['SWR', 'ORN(LIB)'], controlled: 1 },
    ],
    GEMA: [
      { name: 'German title (NCT)', kind: 'NWR', requires: ['SWR', 'SPU', 'NCT'], nonRoman: true, controlled: 1 },
      { name: 'No 2WL for sub-pub', kind: 'NWR', requires: ['SPT(specific TIS)'], controlled: 1 },
    ],
    SACEM: [
      { name: 'IPI on every writer', kind: 'NWR', requires: ['SWR(IPI)×2'], controlled: 2 },
    ],
    JASRAC: [
      { name: 'Japanese title + writer', kind: 'NWR', requires: ['SWR', 'NCT', 'NWN', 'NPN'], nonRoman: true, controlled: 1 },
      { name: 'Mixed JP/EN co-write', kind: 'NWR', requires: ['SWR×2', 'NWN', 'NPN'], nonRoman: true, controlled: 2 },
    ],
    SIAE: [
      { name: 'Italian writer + publisher', kind: 'NWR', requires: ['SWR', 'SPU'], nonRoman: true, controlled: 1 },
    ],
    SOCAN: [
      { name: 'Canadian English writer', kind: 'NWR', requires: ['SWR', 'SPU'], controlled: 1 },
      { name: 'Canadian French writer', kind: 'NWR', requires: ['SWR', 'NCT'], nonRoman: true, controlled: 1 },
    ],
    'APRA AMCOS': [
      { name: 'Australian PR+MR combined', kind: 'NWR', requires: ['SWR', 'SPU', 'PWR'], controlled: 1 },
    ],
    STIM: [
      { name: 'Swedish writer', kind: 'NWR', requires: ['SWR', 'SPU'], nonRoman: true, controlled: 1 },
    ],
    AKM: [
      { name: 'Austrian strict NPA', kind: 'NWR', requires: ['SWR', 'SPU', 'NWN'], nonRoman: true, controlled: 1 },
    ],
    MLC: [
      { name: 'ISWC+REC required', kind: 'NWR', requires: ['SWR', 'SPU', 'REC', 'ISWC'], controlled: 1 },
      { name: 'Multi-recording work', kind: 'NWR', requires: ['SWR', 'SPU', 'REC×3'], controlled: 1 },
    ],
    HFA: [
      { name: 'HFA mech-only', kind: 'NWR', requires: ['SWR', 'SPU'], controlled: 1 },
    ],
    SAYCE: [
      { name: 'Ecuadorian writer', kind: 'NWR', requires: ['SWR', 'SPU'], controlled: 1 },
    ],
  };

  // ─── ACK simulation rules ────────────────────────────────────────────────
  // Map validator outcomes → ACK codes. Real societies have varied tolerance:
  //   strict: any error → RJ; warnings → NP
  //   lenient: hard errors → RJ; everything else → AS or AC
  const ACK_TOLERANCE = {
    GEMA:   'strict',
    JASRAC: 'strict',
    AKM:    'strict',
    SUISA:  'strict',
    MLC:    'strict',
    SACEM:  'normal',
    PRS:    'normal',
    ASCAP:  'normal',
    BMI:    'normal',
    SESAC:  'normal',
    GMR:    'normal',
    SOCAN:  'normal',
    STIM:   'normal',
    SIAE:   'normal',
    SAYCE:  'lenient',
    HFA:    'lenient',
    'APRA AMCOS': 'normal',
  };

  function simulateAck(society, validateResult, societyResult) {
    const tolerance = ACK_TOLERANCE[society] || 'normal';
    const errors = (validateResult.issues || []).filter(i => i.severity === 'error').length;
    const warnings = (validateResult.issues || []).filter(i => i.severity === 'warn').length;
    const societyErrors = ((societyResult && societyResult.issues) || []).filter(i => i.severity === 'error').length;
    const societyWarnings = ((societyResult && societyResult.issues) || []).filter(i => i.severity === 'warn').length;

    const totalErrors = errors + societyErrors;
    const totalWarnings = warnings + societyWarnings;

    // Strict: any warning is a soft-fail
    if (tolerance === 'strict') {
      if (totalErrors > 0) return { code: 'RJ', label: 'Rejected', reason: `${totalErrors} hard error(s)` };
      if (totalWarnings > 0) return { code: 'NP', label: 'Needs review', reason: `${totalWarnings} warning(s)` };
      return { code: 'AS', label: 'Accepted', reason: 'Clean' };
    }
    // Normal: errors → RJ, warnings allowed → AC
    if (tolerance === 'normal') {
      if (totalErrors > 2) return { code: 'RJ', label: 'Rejected', reason: `${totalErrors} errors` };
      if (totalErrors > 0) return { code: 'IR', label: 'Invalid records', reason: `${totalErrors} record-level error(s)` };
      if (totalWarnings > 5) return { code: 'NP', label: 'Needs review', reason: `${totalWarnings} warnings` };
      if (totalWarnings > 0) return { code: 'AC', label: 'Accepted w/ corrections', reason: `${totalWarnings} warning(s)` };
      return { code: 'AS', label: 'Accepted', reason: 'Clean' };
    }
    // Lenient: only fail on many errors
    if (totalErrors > 5) return { code: 'RJ', label: 'Rejected', reason: `${totalErrors} errors` };
    return { code: 'AS', label: 'Accepted', reason: totalErrors > 0 ? `${totalErrors} errors auto-corrected` : 'Clean' };
  }

  // ─── Build a test work for a corpus entry ────────────────────────────────
  // We produce minimal but spec-complete works that exercise the requested
  // record types. Not a full hydrate — purpose-built for cert testing.
  function buildTestWork(corpus, society, idx) {
    const id = `CERT_${society.replace(/\s/g, '')}_${idx}_${Date.now()}`;
    const isJP = corpus.nonRoman && (society === 'JASRAC');
    const isCN = corpus.nonRoman && false;
    const isFR = corpus.nonRoman && (society === 'SOCAN');
    const isDE = corpus.nonRoman && (society === 'GEMA');
    const isIT = corpus.nonRoman && (society === 'SIAE');

    let title = corpus.name;
    if (isJP) title = '東京の夜';
    else if (isFR) title = 'Voilà la Vérité';
    else if (isDE) title = 'Über alle Gipfeln';
    else if (isIT) title = 'Così È La Vita';

    const writers = [];
    for (let i = 0; i < (corpus.controlled || 1); i++) {
      const wid = 'W' + String((idx * 10 + i)).padStart(9, '0');
      let lastName = `WRITER${idx}${i}`;
      let firstName = 'TEST';
      if (isJP) { lastName = '田中'; firstName = '太郎'; }
      else if (isFR) { lastName = 'Côté'; firstName = 'Émilie'; }
      else if (isDE) { lastName = 'Müller'; firstName = 'Jürgen'; }
      else if (isIT) { lastName = 'Rossini'; firstName = 'Luciano'; }

      writers.push({
        id: wid,
        controlled: true,
        lastName, firstName,
        designation: 'CA',
        ipiName: '00000000297',
        prSocCode: society === 'BMI' ? '021' : '010',
        prShare: 50.0 / (corpus.controlled || 1),
        mrSocCode: society === 'BMI' ? '021' : '010',
        mrShare: 0,
        srSocCode: society === 'BMI' ? '021' : '010',
        srShare: 0,
        prAffiliation: society === 'BMI' ? 21 : 10,
        territories: [{ tisNum: 2136, prShare: 50.0 / (corpus.controlled || 1), mrShare: 0, srShare: 0 }],
        pwrLinks: [{ publisherId: 'P000000001', publisherName: 'TEST PUBLISHER',
          submitterAgreementNum: 'AG' + String(idx).padStart(8, '0'), societyAgreementNum: '' }],
      });
    }

    const publishers = [{
      id: 'P000000001',
      controlled: true,
      name: 'TEST PUBLISHER ' + society,
      role: 'OP',
      ipiName: '00000000297',
      prSocCode: society === 'BMI' ? '021' : '010',
      prShare: 50.0,
      mrSocCode: society === 'BMI' ? '021' : '010',
      mrShare: 100.0,
      srSocCode: society === 'BMI' ? '021' : '010',
      srShare: 0,
      agreement: 'AG' + String(idx).padStart(8, '0'),
      agreementNum: idx,
      agreementType: 'OS',
      prAffiliation: society === 'BMI' ? 21 : 10,
      territories: [{ tisNum: 2136, prShare: 50.0, mrShare: 100.0, srShare: 0, sequence: 1 }],
    }];

    const recordings = [];
    if (corpus.requires.some(r => r.includes('REC'))) {
      const count = corpus.requires.find(r => r.includes('×')) ? 3 : 1;
      for (let i = 0; i < count; i++) {
        recordings.push({
          firstReleaseDate: '20240101',
          firstReleaseDuration: '000300',
          firstAlbumTitle: 'Test Album ' + i,
          firstAlbumLabel: 'Test Label',
          firstReleaseCatalog: 'CAT' + i,
          ean: '0000000000000',
          isrc: 'US-CRT-24-0000' + i,
          recordingFormat: 'A',
          recordingTechnique: 'D',
          mediaType: 'CD',
        });
      }
    }
    const origins = [];
    if (corpus.requires.some(r => r.includes('ORN'))) {
      origins.push({
        intendedPurpose: corpus.requires.find(r => r.includes('LIB')) ? 'LIB' : 'GEN',
        productionTitle: 'Cert Test Production',
        cdIdentifier: '',
        cutNumber: 1,
        library: corpus.requires.find(r => r.includes('LIB')) ? 'PLURALIS LIB' : '',
        productionYear: '2024',
        avIndex: '',
        firstReleaseDate: '20240101',
      });
    }

    // ISWC with correct check digit
    const nine = String(910000000 + idx).padStart(9, '0');
    let sum = 1;
    for (let i = 0; i < 9; i++) sum += parseInt(nine[i], 10) * (i + 1);
    const check = (10 - (sum % 10)) % 10;
    const iswc = 'T-' + nine.slice(0, 3) + '.' + nine.slice(3, 6) + '.' + nine.slice(6, 9) + '-' + check;

    return {
      id,
      title,
      lang: isJP ? 'JA' : isFR ? 'FR' : isDE ? 'DE' : isIT ? 'IT' : 'EN',
      submitterWorkId: id,
      iswc,
      duration: '000300',
      workType: 'POP',
      musicArrangement: 'NEW',
      lyricAdaptation: 'NEW',
      excerptType: 'EXC',
      versionType: 'ORI',
      writers,
      publishers,
      recordings,
      origins,
      performers: [],
      instrumentations: [],
      instrumentDetails: [],
      components: [],
      relatedInfo: [],
      xrefs: [],
      messages: [],
      alternateTitles: [],
    };
  }

  // ─── Run cert pass for one society ───────────────────────────────────────
  function runForSociety(society, opts) {
    opts = opts || {};
    const version = opts.version || '2.2';
    const corpus = TEST_CORPUS[society] || [];
    const results = [];
    const t0 = performance.now();

    corpus.forEach((entry, idx) => {
      const work = buildTestWork(entry, society, idx);
      const ctx = {
        version,
        senderType: 'PB',
        senderIpi: 199900001,
        senderName: 'PLURALIS',
        transactionType: entry.kind,
        works: [work],
        created: new Date(),
      };

      let lines = [];
      let buildErr = null;
      try {
        lines = window.CwrBuild.buildTransmission(ctx);
      } catch (e) {
        buildErr = e.message;
      }

      const validate = lines.length
        ? window.CwrValidate.validate({ version, lines, works: [work] })
        : { issues: [{ severity: 'error', msg: buildErr }], summary: { errors: 1, warnings: 0, ok: false } };

      let societyResult = null;
      if (window.CwrSociety && lines.length) {
        try {
          societyResult = window.CwrSociety.applySocietyRules(society, lines, [work], version);
        } catch (e) { /* society rules not available */ }
      }

      const ack = simulateAck(society, validate, societyResult);

      results.push({
        test: entry.name,
        kind: entry.kind,
        requires: entry.requires,
        lines: lines.length,
        bytes: lines.join('\r\n').length,
        validateErrors: validate.summary?.errors || 0,
        validateWarnings: validate.summary?.warnings || 0,
        societyErrors: societyResult ? (societyResult.issues || []).filter(i => i.severity === 'error').length : 0,
        societyWarnings: societyResult ? (societyResult.issues || []).filter(i => i.severity === 'warn').length : 0,
        ack: ack.code,
        ackLabel: ack.label,
        ackReason: ack.reason,
        passed: ack.code === 'AS' || ack.code === 'AC',
      });
    });

    const t1 = performance.now();
    const passed = results.filter(r => r.passed).length;
    const total = results.length;
    const readiness = total === 0 ? 0 : Math.round((passed / total) * 100);

    return {
      society,
      version,
      runMs: +(t1 - t0).toFixed(1),
      total,
      passed,
      failed: total - passed,
      readiness,
      results,
      recommendation: readiness === 100 ? 'Ready for conformance review' :
                      readiness >= 75 ? 'Address remaining failures, then conformance' :
                      readiness >= 50 ? 'Continue test-file iteration' :
                      readiness > 0 ? 'Significant rule gaps — review society-specific rules' :
                      total === 0 ? 'No test corpus defined' : 'Hard rejection — investigate validator output',
    };
  }

  // ─── Run cert pass for ALL societies ─────────────────────────────────────
  function runFullCertification(opts) {
    opts = opts || {};
    const societies = opts.societies || Object.keys(TEST_CORPUS);
    const t0 = performance.now();
    const reports = societies.map(s => runForSociety(s, opts));
    const t1 = performance.now();

    const totalTests = reports.reduce((acc, r) => acc + r.total, 0);
    const totalPassed = reports.reduce((acc, r) => acc + r.passed, 0);

    return {
      runMs: +(t1 - t0).toFixed(1),
      totalSocieties: reports.length,
      totalTests,
      totalPassed,
      overallReadiness: totalTests === 0 ? 0 : Math.round((totalPassed / totalTests) * 100),
      societies: reports,
      certifiedSocieties: reports.filter(r => r.readiness === 100).length,
      conformanceReady: reports.filter(r => r.readiness >= 75 && r.readiness < 100).length,
      stillTesting: reports.filter(r => r.readiness > 0 && r.readiness < 75).length,
      blocked: reports.filter(r => r.readiness === 0).length,
    };
  }

  // ─── Regression tracking ─────────────────────────────────────────────────
  const HISTORY_KEY = 'astro:cwr:cert-history';
  function saveRun(report) {
    try {
      const raw = localStorage.getItem(HISTORY_KEY);
      const history = raw ? JSON.parse(raw) : [];
      history.unshift({ at: new Date().toISOString(), ...report });
      localStorage.setItem(HISTORY_KEY, JSON.stringify(history.slice(0, 50)));
    } catch (e) {}
  }
  function loadHistory() {
    try {
      const raw = localStorage.getItem(HISTORY_KEY);
      return raw ? JSON.parse(raw) : [];
    } catch (e) { return []; }
  }
  function diffAgainstPrevious(current) {
    const history = loadHistory();
    if (history.length === 0) return null;
    const prev = history[0];
    const deltas = current.societies.map((s, i) => {
      const prevSoc = prev.societies?.find(p => p.society === s.society);
      if (!prevSoc) return { society: s.society, change: 'NEW', delta: s.readiness };
      const delta = s.readiness - prevSoc.readiness;
      return {
        society: s.society,
        delta,
        change: delta > 0 ? 'IMPROVED' : delta < 0 ? 'REGRESSED' : 'UNCHANGED',
        prev: prevSoc.readiness,
        current: s.readiness,
      };
    });
    return {
      runs: history.length,
      previousRunAt: prev.at,
      deltas,
      regressions: deltas.filter(d => d.change === 'REGRESSED'),
      improvements: deltas.filter(d => d.change === 'IMPROVED'),
    };
  }

  window.CwrCertDriver = {
    TEST_CORPUS,
    ACK_TOLERANCE,
    runForSociety,
    runFullCertification,
    simulateAck,
    buildTestWork,
    saveRun,
    loadHistory,
    diffAgainstPrevious,
  };
})();
