// invodiff-data.jsx — sample invoices, vendor presets, comparison engine.

// ── helpers ──────────────────────────────────────────────────────────────
const fmtUSD = (n) => {
  const sign = n < 0 ? '-' : '';
  return sign + '$' + Math.abs(n).toLocaleString('en-US', {
    minimumFractionDigits: 2, maximumFractionDigits: 2,
  });
};
const fmtDelta = (n) => (n >= 0 ? '+' : '') + fmtUSD(n).replace('+-', '-');
const pct = (n, d = 1) => (d === 0 ? '—' : (n * 100).toFixed(1) + '%');

// ── sample data ──────────────────────────────────────────────────────────
const SAMPLE_VENDORS = [
  'Acme Industrial Supply',
  'Northbridge Logistics',
  'Pacific Materials Co.',
  'Vertex Fabrication',
  'Helios Components Inc.',
];

// Acme — has historical context
const INVOICE_A = {
  id: 'PO-29481',
  label: 'Purchase Order',
  vendor: 'Acme Industrial Supply',
  date: '2026-04-22',
  poRef: 'PO-29481',
  currency: 'USD',
  terms: 'Net 30',
  items: [
    { sku: 'BR-204-S',   desc: 'Steel L-bracket, 4"x4", galvanized',  qty: 100,  unit: 12.50, },
    { sku: 'HX-M8-50',   desc: 'Hex bolts, M8 x 50mm, A2 stainless',  qty: 500,  unit: 0.45,  },
    { sku: 'WS-M8-FL',   desc: 'Flat washers, M8, zinc plated',       qty: 1000, unit: 0.12,  },
    { sku: 'AL-SH-3MM',  desc: 'Aluminum sheet, 3mm, 1m x 2m',         qty: 20,   unit: 85.00, },
    { sku: 'GR-SOLD',    desc: 'Solder wire, 1lb spool, lead-free',    qty: 6,    unit: 28.00, },
  ],
  shipping: 45.00,
  taxRate: 0.08,
};

const INVOICE_B = {
  id: 'INV-77214',
  label: 'Vendor Invoice',
  vendor: 'Acme Industrial Supply',
  date: '2026-05-14',
  poRef: 'PO-29481',
  currency: 'USD',
  terms: 'Net 30',
  items: [
    { sku: 'BR-204-S',   desc: 'Steel L-bracket, 4"x4", galvanized',  qty: 100,  unit: 12.75, },   // price up
    { sku: 'HX-M8-50',   desc: 'Hex bolts, M8 x 50mm, A2 stainless',  qty: 500,  unit: 0.45,  },   // match
    { sku: 'WS-M8-FL',   desc: 'Flat washers, M8, zinc plated',       qty: 950,  unit: 0.12,  },   // qty short
    { sku: 'AL-SH-3MM',  desc: 'Aluminum sheet, 3mm, 1m x 2m',         qty: 20,   unit: 85.00, },   // match
    // GR-SOLD missing
    { sku: 'FS-SURCHG',  desc: 'Fuel surcharge (5%)',                 qty: 1,    unit: 165.70, _new: true },
    { sku: 'EXP-HNDL',   desc: 'Expedited handling fee',              qty: 1,    unit: 35.00, _new: true },
  ],
  shipping: 50.00, // shipping up
  taxRate: 0.08,
};

// Historical rules saved for "Acme Industrial Supply"
const SEED_VENDOR_RULES = {
  'Acme Industrial Supply': [
    {
      id: 'r1',
      kind: 'tolerance',
      pattern: 'BR-204-S',
      note: 'Steel L-brackets allowed ±3% unit-price drift (market index).',
      added: '2026-02-11',
    },
    {
      id: 'r2',
      kind: 'ignore',
      pattern: 'FS-SURCHG',
      note: 'Disregard 5% fuel surcharge variance — pre-approved per vendor agreement v3.',
      added: '2025-11-04',
    },
    {
      id: 'r3',
      kind: 'note',
      pattern: '*',
      note: 'Standard shipping rate updated to $50 effective Q2 2026.',
      added: '2026-03-30',
    },
  ],
};

// ── line-item totals ─────────────────────────────────────────────────────
const lineTotal = (it) => +(it.qty * it.unit).toFixed(2);
const subtotal = (inv) => inv.items.reduce((s, it) => s + lineTotal(it), 0);
const tax = (inv) => +(subtotal(inv) * inv.taxRate).toFixed(2);
const grandTotal = (inv) => +(subtotal(inv) + (inv.shipping || 0) + tax(inv)).toFixed(2);

// ── comparison engine ────────────────────────────────────────────────────
// Returns rows aligned by SKU, with a status per row + per-field flags.
// status: 'match' | 'price' | 'qty' | 'missing' | 'added' | 'multi'
// Each row also has .resolved = true if applied vendor rules forgive it.
function compareInvoices(a, b, rules = []) {
  const skus = [];
  const seen = new Set();
  [...a.items, ...b.items].forEach((it) => {
    if (!seen.has(it.sku)) { seen.add(it.sku); skus.push(it.sku); }
  });

  const ruleMatchesSku = (rule, sku) =>
    rule.pattern === '*' || rule.pattern === sku || sku.startsWith(rule.pattern);

  const rows = skus.map((sku) => {
    const left = a.items.find((it) => it.sku === sku) || null;
    const right = b.items.find((it) => it.sku === sku) || null;
    const flags = { price: false, qty: false, missing: false, added: false };
    let resolved = false;
    let resolvedBy = null;

    if (!right && left) flags.missing = true;
    else if (!left && right) flags.added = true;
    else {
      if (left.unit !== right.unit) flags.price = true;
      if (left.qty !== right.qty) flags.qty = true;
    }

    // Apply rules
    const applicable = rules.filter((r) => ruleMatchesSku(r, sku));
    for (const r of applicable) {
      if (r.kind === 'ignore' && (flags.added || flags.price || flags.qty)) {
        resolved = true; resolvedBy = r; break;
      }
      if (r.kind === 'tolerance' && flags.price && !flags.qty && !flags.added && !flags.missing) {
        const drift = Math.abs((right.unit - left.unit) / left.unit);
        if (drift <= 0.03) { resolved = true; resolvedBy = r; break; }
      }
    }

    let status = 'match';
    const flagCount = ['price','qty','missing','added'].filter((k) => flags[k]).length;
    if (flagCount === 0) status = 'match';
    else if (flags.missing) status = 'missing';
    else if (flags.added) status = 'added';
    else if (flagCount === 1) status = flags.price ? 'price' : 'qty';
    else status = 'multi';

    const lineA = left ? lineTotal(left) : 0;
    const lineB = right ? lineTotal(right) : 0;
    return {
      sku, left, right, flags, status, resolved, resolvedBy,
      lineA, lineB, lineDelta: +(lineB - lineA).toFixed(2),
    };
  });

  // Shipping & tax synthetic rows
  const shipFlag = (a.shipping || 0) !== (b.shipping || 0);
  rows.push({
    synthetic: 'shipping',
    sku: '—',
    left: { desc: 'Shipping', qty: 1, unit: a.shipping || 0 },
    right: { desc: 'Shipping', qty: 1, unit: b.shipping || 0 },
    flags: { price: shipFlag, qty: false, missing: false, added: false },
    status: shipFlag ? 'price' : 'match',
    resolved: shipFlag && rules.some((r) => r.kind === 'note' && /shipping/i.test(r.note)),
    resolvedBy: rules.find((r) => r.kind === 'note' && /shipping/i.test(r.note)) || null,
    lineA: a.shipping || 0, lineB: b.shipping || 0,
    lineDelta: +((b.shipping || 0) - (a.shipping || 0)).toFixed(2),
  });

  const taxA = tax(a), taxB = tax(b);
  rows.push({
    synthetic: 'tax',
    sku: '—',
    left: { desc: `Tax (${(a.taxRate*100).toFixed(2)}%)`, qty: 1, unit: taxA },
    right: { desc: `Tax (${(b.taxRate*100).toFixed(2)}%)`, qty: 1, unit: taxB },
    flags: { price: taxA !== taxB, qty: false, missing: false, added: false },
    status: taxA !== taxB ? 'price' : 'match',
    resolved: false, resolvedBy: null,
    lineA: taxA, lineB: taxB, lineDelta: +(taxB - taxA).toFixed(2),
  });

  // Summary
  const flagged = rows.filter((r) => r.status !== 'match' && !r.resolved);
  const matched = rows.filter((r) => r.status === 'match' || r.resolved);
  const totalA = grandTotal(a), totalB = grandTotal(b);
  const totalDelta = +(totalB - totalA).toFixed(2);
  const matchPct = rows.length ? matched.length / rows.length : 1;

  return {
    rows,
    summary: {
      totalA, totalB, totalDelta,
      subA: subtotal(a), subB: subtotal(b),
      taxA, taxB,
      shipA: a.shipping || 0, shipB: b.shipping || 0,
      matchPct,
      flaggedCount: flagged.length,
      matchCount: matched.length,
      rowCount: rows.length,
      resolvedCount: rows.filter((r) => r.resolved).length,
    },
  };
}

Object.assign(window, {
  fmtUSD, fmtDelta, pct,
  SAMPLE_VENDORS, INVOICE_A, INVOICE_B, SEED_VENDOR_RULES,
  compareInvoices, lineTotal, subtotal, tax, grandTotal,
});
