// ============================================================================
// CWR ID-VALIDATION RULES — ISWC, IPI, ISRC, ISNI check-digit verification
//
// These are not field-position checks — they're content checks. The CWR builder
// can put a malformed ISWC at the right offset and pass the byte-diff. These
// rules catch identifiers that are syntactically valid but mathematically wrong.
//
// Standards:
//   ISWC (ISO 15707) — T-NNNNNNNNN-C, mod-10 check digit
//   ISRC (ISO 3901)  — CC-XXX-YY-NNNNN, no check digit but format strict
//   IPI Name #       — 11-digit numeric, mod-101 check (last 2 digits)
//   IPI Base #       — I-NNNNNNNNN-C, mod-101 check
//   ISNI (ISO 27729) — 16-char, mod-11 check
//   Submitter Work # — alphanumeric, max 14 chars, must be unique per submitter
//
// EXPORT: window.CwrIdValidate = { validateIswc, validateIpiName, ... auditTransmission }
// ============================================================================
(function () {

  // ─── ISWC: T-XXXXXXXXX-C, mod-10 ───────────────────────────────────────
  // Computation: sum digits weighted 1..9, mod 10, then (10 - r) mod 10
  function validateIswc(iswc) {
    if (!iswc || iswc.trim() === '') return { valid: true, skipped: true, reason: 'empty (optional)' };
    const cleaned = iswc.replace(/[\s\-\.]/g, '').toUpperCase();
    if (!/^T\d{10}$/.test(cleaned)) {
      return { valid: false, reason: 'malformed: expected T followed by 10 digits, got "' + iswc + '"' };
    }
    const digits = cleaned.slice(1, 10).split('').map(Number);
    const givenCheck = parseInt(cleaned[10], 10);
    let sum = 1; // ISWC starts sum at 1 per ISO 15707
    for (let i = 0; i < 9; i++) sum += digits[i] * (i + 1);
    const computed = (10 - (sum % 10)) % 10;
    if (computed !== givenCheck) {
      return { valid: false, reason: 'check digit mismatch: expected ' + computed + ', got ' + givenCheck };
    }
    return { valid: true, normalized: 'T-' + cleaned.slice(1, 4) + '.' + cleaned.slice(4, 7) + '.' + cleaned.slice(7, 10) + '-' + cleaned[10] };
  }

  // ─── IPI Name Number: 11 digits, mod-101 ────────────────────────────────
  // Last 2 digits = (sum of first 9 digits weighted 1..9) mod 101 — wait,
  // CISAC uses: weighted by reverse position 10..2, sum mod 101, check = 101 - r
  // Reference: CISAC IPI documentation
  function validateIpiName(ipi) {
    if (!ipi || String(ipi).trim() === '') return { valid: true, skipped: true, reason: 'empty' };
    const s = String(ipi).trim().replace(/^0+/, '').padStart(11, '0');
    if (!/^\d{11}$/.test(s)) {
      return { valid: false, reason: 'IPI Name # must be 11 digits, got "' + ipi + '"' };
    }
    const body = s.slice(0, 9).split('').map(Number);
    const givenCheck = parseInt(s.slice(9, 11), 10);
    let sum = 0;
    for (let i = 0; i < 9; i++) sum += body[i] * (10 - i);
    let computed = sum % 101;
    if (computed !== 0) computed = 101 - computed;
    if (computed !== givenCheck) {
      return { valid: false, reason: 'IPI Name check digits mismatch: expected ' + String(computed).padStart(2, '0') + ', got ' + s.slice(9, 11) };
    }
    return { valid: true, normalized: s };
  }

  // ─── IPI Base Number: I-NNNNNNNNN-C, mod-101 ────────────────────────────
  function validateIpiBase(base) {
    if (!base || String(base).trim() === '') return { valid: true, skipped: true, reason: 'empty (optional)' };
    const cleaned = String(base).trim().replace(/[\s\-\.]/g, '').toUpperCase();
    if (!/^I\d{10}$/.test(cleaned)) {
      return { valid: false, reason: 'IPI Base # must be I + 10 digits, got "' + base + '"' };
    }
    const body = cleaned.slice(1, 10).split('').map(Number);
    const givenCheck = parseInt(cleaned[10], 10);
    let sum = 0;
    for (let i = 0; i < 9; i++) sum += body[i] * (10 - i);
    const r = sum % 11;
    const computed = (r === 0) ? 0 : (11 - r) % 10; // simplified; CISAC uses mod-11 → 0-9 (10→0)
    // Note: CISAC's actual mod-101 truncation differs slightly per IPI doc; we use the
    // commonly-published mod-11 here. Treat as warning, not hard fail.
    if (computed !== givenCheck) {
      return { valid: 'warn', reason: 'IPI Base check digit suspicious: expected ' + computed + ', got ' + givenCheck };
    }
    return { valid: true, normalized: cleaned };
  }

  // ─── ISRC: CC-XXX-YY-NNNNN ──────────────────────────────────────────────
  function validateIsrc(isrc) {
    if (!isrc || isrc.trim() === '') return { valid: true, skipped: true, reason: 'empty (optional)' };
    const cleaned = isrc.replace(/[\s\-]/g, '').toUpperCase();
    if (!/^[A-Z]{2}[A-Z0-9]{3}\d{7}$/.test(cleaned)) {
      return { valid: false, reason: 'ISRC must be CC + XXX + YY + NNNNN (12 chars), got "' + isrc + '"' };
    }
    return { valid: true, normalized: cleaned.slice(0,2) + '-' + cleaned.slice(2,5) + '-' + cleaned.slice(5,7) + '-' + cleaned.slice(7,12) };
  }

  // ─── ISNI: 16 digits/X, mod-11-2 ────────────────────────────────────────
  function validateIsni(isni) {
    if (!isni || isni.trim() === '') return { valid: true, skipped: true, reason: 'empty (optional)' };
    const cleaned = isni.replace(/[\s\-]/g, '').toUpperCase();
    if (!/^\d{15}[\dX]$/.test(cleaned)) {
      return { valid: false, reason: 'ISNI must be 16 chars (15 digits + check digit/X), got "' + isni + '"' };
    }
    let total = 0;
    for (let i = 0; i < 15; i++) total = (total + parseInt(cleaned[i], 10)) * 2;
    const r = total % 11;
    const checkVal = (12 - r) % 11;
    const expected = checkVal === 10 ? 'X' : String(checkVal);
    if (expected !== cleaned[15]) {
      return { valid: false, reason: 'ISNI check digit mismatch: expected ' + expected + ', got ' + cleaned[15] };
    }
    return { valid: true, normalized: cleaned };
  }

  // ─── Submitter Work Number: max 14 alphanumeric ─────────────────────────
  function validateSubmitterWorkNum(num) {
    if (!num || num.trim() === '') return { valid: false, reason: 'submitter work # is required' };
    const cleaned = num.trim();
    if (cleaned.length > 14) return { valid: false, reason: 'submitter work # max 14 chars, got ' + cleaned.length };
    if (!/^[A-Z0-9]+$/i.test(cleaned)) return { valid: false, reason: 'submitter work # must be alphanumeric, got "' + num + '"' };
    return { valid: true, normalized: cleaned };
  }

  // ─── Tax ID format check (US EIN: 9 digits; varies by country) ──────────
  function validateTaxId(taxId, country) {
    if (!taxId || String(taxId).trim() === '') return { valid: true, skipped: true, reason: 'empty (optional)' };
    const cleaned = String(taxId).replace(/[\s\-]/g, '');
    if (country === 'US' && !/^\d{9}$/.test(cleaned)) {
      return { valid: 'warn', reason: 'US tax ID expected 9 digits (EIN), got "' + taxId + '"' };
    }
    return { valid: true, normalized: cleaned };
  }

  // ─── Audit a transmission for ID-validation issues ──────────────────────
  function auditTransmission(lines, version) {
    const issues = [];
    const stats = { iswc: { checked: 0, ok: 0 }, ipiName: { checked: 0, ok: 0 }, ipiBase: { checked: 0, ok: 0 }, isrc: { checked: 0, ok: 0 }, swn: { checked: 0, ok: 0 } };

    for (const line of lines) {
      const rt = line.slice(0, 3);

      // NWR / REV / ISW / EXC: ISWC at 95–106 (11 chars), submitterWorkNum at 81–95 (14 chars)
      if (['NWR', 'REV', 'ISW', 'EXC'].includes(rt)) {
        const swn = line.slice(81, 95).trim();
        const iswc = line.slice(95, 106).trim();
        if (swn) {
          stats.swn.checked++;
          const r = validateSubmitterWorkNum(swn);
          if (r.valid === true) stats.swn.ok++;
          else if (r.valid === false) issues.push({ severity: 'high', record: rt, field: 'submitterWorkNum', value: swn, message: r.reason });
        }
        if (iswc) {
          stats.iswc.checked++;
          const r = validateIswc(iswc);
          if (r.valid === true) stats.iswc.ok++;
          else if (r.valid === false) issues.push({ severity: 'medium', record: rt, field: 'iswc', value: iswc, message: r.reason });
        }
      }

      // SPU / OPU: IPI Name at 87–98 (11 chars), tax ID at 78–87 (9 chars)
      if (['SPU', 'OPU'].includes(rt)) {
        const ipi = line.slice(87, 98).trim();
        if (ipi && ipi !== '00000000000') {
          stats.ipiName.checked++;
          const r = validateIpiName(ipi);
          if (r.valid === true) stats.ipiName.ok++;
          else if (r.valid === false) issues.push({ severity: 'high', record: rt, field: 'ipiName', value: ipi, message: r.reason });
        }
      }

      // SWR / OWR: IPI Name at 154-165 (varies by version)
      if (['SWR', 'OWR'].includes(rt)) {
        // SWR layout: type(3)+txn(8)+rec(8)+ipNum(9)+lastName(45)+firstName(30)+unknown(1)+designation(2)+taxId(9)+ipi(11)
        // = 3+8+8+9+45+30+1+2+9 = 115; ipi starts at 115
        const ipi = line.slice(115, 126).trim();
        if (ipi && ipi !== '00000000000') {
          stats.ipiName.checked++;
          const r = validateIpiName(ipi);
          if (r.valid === true) stats.ipiName.ok++;
          else if (r.valid === false) issues.push({ severity: 'high', record: rt, field: 'ipiName', value: ipi, message: r.reason });
        }
      }

      // PER: IPI Name at 94-105
      if (rt === 'PER') {
        const ipi = line.slice(94, 105).trim();
        if (ipi && ipi !== '00000000000') {
          stats.ipiName.checked++;
          const r = validateIpiName(ipi);
          if (r.valid === true) stats.ipiName.ok++;
          else if (r.valid === false) issues.push({ severity: 'medium', record: rt, field: 'ipiName', value: ipi, message: r.reason });
        }
      }

      // REC: ISRC at offset varies; also catalog refs
      if (rt === 'REC') {
        // CWR 2.x REC: ISRC at offset around 230 in extended layout
        // Simplified: scan for ISRC-shaped tokens
      }
    }

    return {
      version,
      issues,
      stats,
      ok: issues.length === 0,
      summary: issues.length + ' ID-validation issues across ' + lines.length + ' records',
    };
  }

  // ─── Self-test against known-good and known-bad IDs ─────────────────────
  function selfTest() {
    const cases = [
      // ISWC examples — algorithmically computed valid/invalid pairs
      { fn: validateIswc, input: 'T1234567890', expectValid: false, label: 'ISWC bad-check' },
      { fn: validateIswc, input: 'T1010551172', expectValid: true,  label: 'ISWC valid mod-10' },
      { fn: validateIswc, input: 'T0000000017', expectValid: false, label: 'ISWC zeros + wrong check' },
      { fn: validateIswc, input: 'T0000000019', expectValid: false, label: 'ISWC body=1 wrong check' },
      { fn: validateIswc, input: 'T0000000010', expectValid: true,  label: 'ISWC body=1 correct check' },
      { fn: validateIswc, input: '', expectValid: true, label: 'ISWC empty (skipped)' },
      // IPI Name (11 digits)
      { fn: validateIpiName, input: '00000000000', expectValid: true, label: 'IPI all zeros' },
      { fn: validateIpiName, input: '12345', expectValid: false, label: 'IPI too short' },
      { fn: validateIpiName, input: 'abcdefghijk', expectValid: false, label: 'IPI non-numeric' },
      // ISRC
      { fn: validateIsrc, input: 'USRC11700001', expectValid: true, label: 'ISRC well-formed' },
      { fn: validateIsrc, input: 'BAD-ISRC', expectValid: false, label: 'ISRC malformed' },
      // ISNI
      { fn: validateIsni, input: '0000000121032683', expectValid: true, label: 'ISNI valid (J.K. Rowling)' },
      { fn: validateIsni, input: '0000000000000002', expectValid: false, label: 'ISNI bad check digit' },
      // Submitter work num
      { fn: validateSubmitterWorkNum, input: 'WORK001', expectValid: true, label: 'SWN simple' },
      { fn: validateSubmitterWorkNum, input: '', expectValid: false, label: 'SWN empty (required)' },
      { fn: validateSubmitterWorkNum, input: 'A'.repeat(20), expectValid: false, label: 'SWN too long' },
    ];

    let pass = 0, fail = 0;
    const results = cases.map(c => {
      const r = c.fn(c.input);
      const got = r.valid === true;
      const ok = got === c.expectValid;
      if (ok) pass++; else fail++;
      return { label: c.label, input: c.input, expected: c.expectValid, got, ok, reason: r.reason || '' };
    });
    return { pass, fail, total: cases.length, results };
  }

  // Compute a valid ISWC check digit for any 9-digit body
  function computeIswcCheck(body9) {
    const digits = String(body9).padStart(9, '0').slice(0, 9).split('').map(Number);
    let sum = 1;
    for (let i = 0; i < 9; i++) sum += digits[i] * (i + 1);
    return (10 - (sum % 10)) % 10;
  }

  // Compute a valid IPI Name check (last 2 digits)
  function computeIpiNameCheck(body9) {
    const digits = String(body9).padStart(9, '0').slice(0, 9).split('').map(Number);
    let sum = 0;
    for (let i = 0; i < 9; i++) sum += digits[i] * (10 - i);
    let r = sum % 101;
    if (r !== 0) r = 101 - r;
    return String(r).padStart(2, '0');
  }

  // Build a known-valid test ID set (so we can build clean test transmissions)
  function buildValidId(kind, body) {
    if (kind === 'iswc') return 'T' + String(body).padStart(9, '0') + computeIswcCheck(body);
    if (kind === 'ipiName') return String(body).padStart(9, '0') + computeIpiNameCheck(body);
    return null;
  }

  window.CwrIdValidate = {
    validateIswc,
    validateIpiName,
    validateIpiBase,
    validateIsrc,
    validateIsni,
    validateSubmitterWorkNum,
    validateTaxId,
    auditTransmission,
    selfTest,
    computeIswcCheck,
    computeIpiNameCheck,
    buildValidId,
  };
})();
