/* CA Automator — Audit Execution engines (JS mirror of
   server/audit_execution_engines.py). Kept numerically identical to the
   Python engine — every change here must be made there too, and vice versa. */
(function (global) {
  'use strict';

  const R2 = x => Math.round((Number(x) || 0) * 100) / 100;
  const N = (v, dflt) => (v === null || v === undefined || v === '') ? (dflt || 0) : (isFinite(Number(v)) ? Number(v) : (dflt || 0));
  const safeDiv = (a, b) => b ? (a / b) : 0;

  function systematicSample(populationSize, sampleSize, startPoint) {
    const pop = parseInt(populationSize, 10) || 0;
    const n = parseInt(sampleSize, 10) || 0;
    const start = parseInt(startPoint, 10) || 1;
    if (pop <= 0 || n <= 0) return { population_size: pop, sample_size: n, start_point: start, interval: 0, selected_positions: [] };
    const interval = Math.max(1, Math.floor(pop / n));
    const positions = [];
    let pos = start;
    while (positions.length < n && pos <= pop) { positions.push(pos); pos += interval; }
    return { population_size: pop, sample_size: n, start_point: start, interval, selected_positions: positions, actual_count: positions.length };
  }

  function testItemSummary(items) {
    const byArea = {};
    (items || []).forEach(r => {
      const area = r.area || 'Other';
      const g = byArea[area] || (byArea[area] = { count: 0, exceptions: 0, matched: 0, open: 0,
        book_total: 0, tested_total: 0, variance_total: 0, confirmations_sent: 0, confirmations_received: 0, confirmations_discrepancy: 0 });
      g.count++;
      const status = r.status || 'open';
      if (status === 'exception') g.exceptions++;
      else if (status === 'matched') g.matched++;
      else g.open++;
      const book = N(r.book_amount);
      g.book_total += book;
      if (r.tested_amount !== null && r.tested_amount !== undefined && r.tested_amount !== '') {
        const testedV = N(r.tested_amount);
        g.tested_total += testedV;
        g.variance_total += (testedV - book);
      }
      const cs = r.confirmation_status;
      if (cs === 'sent' || cs === 'received_agreed' || cs === 'received_discrepancy') g.confirmations_sent++;
      if (cs === 'received_agreed') g.confirmations_received++;
      if (cs === 'received_discrepancy') g.confirmations_discrepancy++;
    });
    const areas = {};
    Object.keys(byArea).forEach(area => {
      const g = byArea[area];
      areas[area] = { count: g.count, exceptions: g.exceptions, matched: g.matched, open: g.open,
        exception_rate_pct: R2(safeDiv(g.exceptions, g.count) * 100), book_total: R2(g.book_total),
        tested_total: R2(g.tested_total), variance_total: R2(g.variance_total),
        confirmations_sent: g.confirmations_sent, confirmations_received: g.confirmations_received,
        confirmations_discrepancy: g.confirmations_discrepancy };
    });
    const totalItems = Object.values(areas).reduce((s, a) => s + a.count, 0);
    const totalExceptions = Object.values(areas).reduce((s, a) => s + a.exceptions, 0);
    return { areas, total_items: totalItems, total_exceptions: totalExceptions,
      overall_exception_rate_pct: R2(safeDiv(totalExceptions, totalItems) * 100) };
  }

  function analyticalReview(procedures) {
    const rows = (procedures || []).map(r => {
      const current = N(r.current_period_value);
      const expected = r.expected_value;
      const threshold = N(r.threshold_pct, 10);
      let variancePct = null, flagged = false;
      if (expected !== null && expected !== undefined && expected !== '') {
        const expectedV = N(expected);
        variancePct = expectedV ? R2(safeDiv(current - expectedV, expectedV) * 100) : null;
        if (variancePct !== null) flagged = Math.abs(variancePct) > threshold;
      }
      return { id: r.id, metric: r.metric || '', prior_period_value: R2(N(r.prior_period_value)),
        current_period_value: R2(current),
        expected_value: (expected !== null && expected !== undefined && expected !== '') ? R2(N(expected)) : null,
        threshold_pct: threshold, variance_pct: variancePct, flagged, explanation: r.explanation || '' };
    });
    const flaggedRows = rows.filter(r => r.flagged);
    const unexplained = flaggedRows.filter(r => !r.explanation);
    return { rows, flagged_count: flaggedRows.length, unexplained_flagged_count: unexplained.length };
  }

  function goingConcernSummary(indicators) {
    const byCategory = {};
    let presentTotal = 0;
    (indicators || []).forEach(r => {
      const cat = r.category || 'Other';
      const g = byCategory[cat] || (byCategory[cat] = { total: 0, present: 0 });
      g.total++;
      if (r.present) { g.present++; presentTotal++; }
    });
    const out = {};
    Object.keys(byCategory).forEach(k => { out[k] = { total: byCategory[k].total, present: byCategory[k].present }; });
    return { by_category: out, present_count: presentTotal, total_count: (indicators || []).length };
  }

  function checklistSummary(items) {
    const byArea = {};
    (items || []).forEach(c => {
      const area = c.area || '';
      const g = byArea[area] || (byArea[area] = { total: 0, done: 0 });
      g.total++;
      if (c.status === 'done') g.done++;
    });
    const out = {};
    Object.keys(byArea).forEach(area => {
      const g = byArea[area];
      out[area] = { total: g.total, done: g.done, completion_pct: R2(safeDiv(g.done, g.total) * 100) };
    });
    return out;
  }

  global.AuditExecutionEngines = {
    R2, systematicSample, testItemSummary, analyticalReview, goingConcernSummary, checklistSummary,
  };
})(window);
