// ============================================================================
// CWR AGREEMENT VALIDATION
// ============================================================================
// Business-logic validation for AGR transactions (AGR + TER + IPA + NPA).
// CISAC's AGR has a complex set of cross-record invariants that go beyond
// per-record syntax. This module enforces them.
//
// Rules implemented:
//
//   AGR.1  Agreement type ∈ {OS, OG, PS, PG}
//   AGR.2  agreementStartDate ≤ agreementEndDate ≤ retentionEndDate
//   AGR.3  priorRoyaltyStartDate, if set, must be < agreementStartDate
//   AGR.4  postTermCollectionEndDate, if set, must be > agreementEndDate
//   AGR.5  dateOfSignatureOfAgreement ≤ agreementStartDate
//   AGR.6  numberOfWorks ≥ 1 for OS/PS (specific); = 0 ok for OG/PG (general)
//
//   TER.1  At least one TER per AGR
//   TER.2  Inclusion flags must be consistent (no I+E for same TIS)
//   TER.3  No duplicate TIS codes within same agreement
//   TER.4  If 2WL/2136 (worldwide) is present, no other includes allowed
//
//   IPA.1  Exactly one assignor (AS) and ≥ one acquirer (AC)
//   IPA.2  All IPA shares must sum to ≤ 100% per share-class
//   IPA.3  Every IPA must have a valid IPI Name # (mod-10/1)
//   IPA.4  Acquirer total share cannot exceed assignor's share for that class
//
//   NPA.1  NPA records can only follow IPA (not AGR or TER)
//   NPA.2  v3.1: writer-name variant required for non-Roman publisher names
//
// EXPORT: window.CwrAgrValidate = { validateAgreement, validateAgreementBatch }
// ============================================================================

(function () {
  'use strict';

  function parseDate(s) {
    if (!s || !/^\d{8}$/.test(s)) return null;
    const y = +s.slice(0, 4), m = +s.slice(4, 6), d = +s.slice(6, 8);
    if (m < 1 || m > 12 || d < 1 || d > 31) return null;
    return new Date(Date.UTC(y, m - 1, d));
  }

  // IPI Name # check-digit (mod-10/1, ITU-T spec)
  function validIpiName(ipi) {
    if (!ipi) return false;
    const s = String(ipi).replace(/\s+/g, '').padStart(11, '0');
    if (!/^\d{11}$/.test(s)) return false;
    const digits = s.split('').map(Number);
    const check = digits.pop();
    let sum = 0;
    for (let i = 0; i < digits.length; i++) sum += digits[i] * (10 - i);
    const computed = (10 - (sum % 10)) % 10;
    return computed === check;
  }

  // Validate a single agreement object (matches builder input shape)
  function validateAgreement(agr, opts) {
    opts = opts || {};
    const version = opts.version || '2.2';
    const errors = [];
    const warnings = [];

    // ─── AGR rules ───
    const validTypes = ['OS', 'OG', 'PS', 'PG'];
    if (!validTypes.includes(agr.agreementType)) {
      errors.push({ rule: 'AGR.1', code: 'INVALID_AGR_TYPE', msg: `Agreement type must be one of ${validTypes.join('/')}, got "${agr.agreementType}"` });
    }

    const startDate = parseDate(agr.agreementStartDate);
    const endDate = parseDate(agr.agreementEndDate);
    const retentionEnd = parseDate(agr.retentionEndDate);
    const priorRoyStart = parseDate(agr.priorRoyaltyStartDate);
    const postTermEnd = parseDate(agr.postTermCollectionEndDate);
    const sigDate = parseDate(agr.dateOfSignatureOfAgreement);

    if (startDate && endDate && startDate > endDate) {
      errors.push({ rule: 'AGR.2', code: 'INVALID_DATE_RANGE', msg: `Agreement start (${agr.agreementStartDate}) > end (${agr.agreementEndDate})` });
    }
    if (endDate && retentionEnd && endDate > retentionEnd) {
      errors.push({ rule: 'AGR.2', code: 'INVALID_RETENTION', msg: `Agreement end (${agr.agreementEndDate}) > retention end (${agr.retentionEndDate})` });
    }
    if (priorRoyStart && startDate && priorRoyStart >= startDate) {
      errors.push({ rule: 'AGR.3', code: 'PRIOR_ROY_AFTER_START', msg: `Prior-royalty start must be < agreement start` });
    }
    if (postTermEnd && endDate && postTermEnd <= endDate) {
      errors.push({ rule: 'AGR.4', code: 'POST_TERM_BEFORE_END', msg: `Post-term collection end must be > agreement end` });
    }
    if (sigDate && startDate && sigDate > startDate) {
      warnings.push({ rule: 'AGR.5', code: 'SIG_AFTER_START', msg: `Signature date is after agreement start (uncommon but allowed)` });
    }

    if (agr.agreementType === 'OS' || agr.agreementType === 'PS') {
      if (!agr.numberOfWorks || agr.numberOfWorks < 1) {
        errors.push({ rule: 'AGR.6', code: 'SPECIFIC_NEEDS_WORKS', msg: `${agr.agreementType} (specific) requires numberOfWorks ≥ 1` });
      }
    }

    // ─── TER rules ───
    const territories = agr.territories || [];
    if (territories.length === 0) {
      errors.push({ rule: 'TER.1', code: 'NO_TER', msg: `Agreement must have at least one TER record` });
    }

    const tisSeen = {};
    let hasWorldwide = false;
    let hasInclude = false;
    territories.forEach((t, i) => {
      const tis = String(t.tisNum || t.tis || '').trim();
      const flag = t.inclusionFlag || t.include || 'I';
      if (tis === '2136' || tis === '2WL') hasWorldwide = true;
      if (flag === 'I') hasInclude = true;
      if (tisSeen[tis]) {
        if (tisSeen[tis] !== flag) {
          errors.push({ rule: 'TER.2', code: 'CONFLICTING_INCLUSION', msg: `TIS ${tis} has both include and exclude` });
        } else {
          errors.push({ rule: 'TER.3', code: 'DUPLICATE_TIS', msg: `Duplicate TIS ${tis} in agreement` });
        }
      }
      tisSeen[tis] = flag;
    });
    if (hasWorldwide && Object.keys(tisSeen).length > 1) {
      const otherIncludes = Object.entries(tisSeen).filter(([t, f]) => t !== '2136' && t !== '2WL' && f === 'I');
      if (otherIncludes.length > 0) {
        warnings.push({ rule: 'TER.4', code: 'WORLDWIDE_WITH_INCLUDES', msg: `Worldwide TIS with additional includes is redundant` });
      }
    }

    // ─── IPA rules ───
    const parties = agr.parties || [];
    const assignors = parties.filter(p => p.agreementRoleCode === 'AS');
    const acquirers = parties.filter(p => p.agreementRoleCode === 'AC');

    if (assignors.length !== 1) {
      errors.push({ rule: 'IPA.1', code: 'WRONG_ASSIGNOR_COUNT', msg: `Agreement must have exactly 1 assignor (AS), found ${assignors.length}` });
    }
    if (acquirers.length < 1) {
      errors.push({ rule: 'IPA.1', code: 'NO_ACQUIRER', msg: `Agreement must have at least 1 acquirer (AC)` });
    }

    parties.forEach((p, i) => {
      if (!validIpiName(p.ipiName)) {
        errors.push({ rule: 'IPA.3', code: 'INVALID_IPI', msg: `Party ${i} (${p.lastName || '?'}) has invalid IPI Name # "${p.ipiName}"` });
      }
    });

    // Share sum check (IPA.2)
    ['prShare', 'mrShare', 'srShare'].forEach((cls) => {
      const acquirerSum = acquirers.reduce((acc, p) => acc + (+p[cls] || 0), 0);
      const assignorShare = assignors[0]?.[cls] || 0;
      if (acquirerSum > assignorShare + 0.01) {
        errors.push({
          rule: 'IPA.4',
          code: 'ACQUIRER_OVER_ASSIGNOR',
          msg: `${cls}: acquirers sum to ${acquirerSum} but assignor only has ${assignorShare}`,
        });
      }
      if (acquirerSum > 100.01) {
        errors.push({ rule: 'IPA.2', code: 'SHARE_OVER_100', msg: `${cls} acquirer total ${acquirerSum} > 100%` });
      }
    });

    // ─── NPA rules (v3.1) ───
    if (version === '3.1') {
      parties.forEach((p, i) => {
        const isNonRoman = /[^\x00-\x7F]/.test((p.lastName || '') + (p.firstName || ''));
        if (isNonRoman && !p.nonRomanName) {
          errors.push({ rule: 'NPA.2', code: 'NPA_REQUIRED_V31', msg: `v3.1: party ${i} has non-Roman name but no NPA variant supplied` });
        }
      });
    }

    return {
      ok: errors.length === 0,
      errors,
      warnings,
      summary: {
        agreementType: agr.agreementType,
        territoryCount: territories.length,
        partyCount: parties.length,
        assignors: assignors.length,
        acquirers: acquirers.length,
      },
    };
  }

  function validateAgreementBatch(agreements, opts) {
    return (agreements || []).map((a, i) => ({
      index: i,
      submitterAgreementNumber: a.submitterAgreementNumber || `[unnamed-${i}]`,
      ...validateAgreement(a, opts),
    }));
  }

  window.CwrAgrValidate = { validateAgreement, validateAgreementBatch };
})();
