/* CA Automator — IPO / Capital Market engines (JS mirror of
   server/ipo_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 restatementAnalysis(lines) {
    const rows = (lines || []).map(r => {
      const prev = N(r.previously_reported), restated = N(r.restated);
      const variance = restated - prev;
      return { id: r.id, fy_label: r.fy_label || '', account: r.account || '',
        previously_reported: R2(prev), restated: R2(restated), variance: R2(variance),
        variance_pct: prev ? R2(safeDiv(variance, prev) * 100) : null, reason: r.reason || '' };
    });
    const byFy = {};
    rows.forEach(r => { byFy[r.fy_label] = (byFy[r.fy_label] || 0) + r.variance; });
    Object.keys(byFy).forEach(k => { byFy[k] = R2(byFy[k]); });
    const totalAbsoluteImpact = rows.reduce((s, r) => s + Math.abs(r.variance), 0);
    return { rows, impact_by_fy: byFy, total_absolute_impact: R2(totalAbsoluteImpact), lines_restated: rows.length };
  }

  function workingCapitalAdequacy(projections) {
    const sorted = (projections || []).slice().sort((a, b) => (a.period_label || '').localeCompare(b.period_label || ''));
    let cumulativeGap = 0;
    const rows = sorted.map(r => {
      const required = N(r.projected_requirement);
      const available = N(r.available_from_operations) + N(r.ipo_proceeds_allocated);
      const gap = required - available;
      cumulativeGap += gap;
      return { id: r.id, period_label: r.period_label || '', projected_requirement: R2(required),
        available_from_operations: R2(N(r.available_from_operations)), ipo_proceeds_allocated: R2(N(r.ipo_proceeds_allocated)),
        total_available: R2(available), gap: R2(gap), adequate: gap <= 0, cumulative_gap: R2(cumulativeGap) };
    });
    const shortfalls = rows.filter(r => !r.adequate);
    const maxCumGap = rows.length ? Math.max(...rows.map(r => r.cumulative_gap)) : 0;
    return { rows, shortfall_periods: shortfalls.length, max_cumulative_gap: R2(maxCumGap), overall_adequate: shortfalls.length === 0 };
  }

  function peerComparison(peers, subject) {
    const rows = (peers || []).map(p => {
      const price = N(p.market_price), eps = N(p.eps), bvps = N(p.book_value_per_share);
      const pe = eps ? safeDiv(price, eps) : null;
      const pbv = bvps ? safeDiv(price, bvps) : null;
      return { id: p.id, peer_name: p.peer_name || '', listed: p.listed === undefined ? true : !!p.listed,
        market_price: R2(price), eps: R2(eps), book_value_per_share: R2(bvps), roe_pct: p.roe_pct,
        pe_ratio: pe !== null ? R2(pe) : null, pbv_ratio: pbv !== null ? R2(pbv) : null };
    });
    const pes = rows.map(r => r.pe_ratio).filter(v => v !== null);
    const pbvs = rows.map(r => r.pbv_ratio).filter(v => v !== null);
    const avgPe = pes.length ? R2(pes.reduce((s, v) => s + v, 0) / pes.length) : null;
    const avgPbv = pbvs.length ? R2(pbvs.reduce((s, v) => s + v, 0) / pbvs.length) : null;
    const sortedPes = pes.slice().sort((a, b) => a - b);
    const medianPe = pes.length ? R2(sortedPes[Math.floor(pes.length / 2)]) : null;

    subject = subject || {};
    const subjectEps = N(subject.eps), subjectBvps = N(subject.book_value_per_share);
    const impliedPriceAtAvgPe = (avgPe !== null && subjectEps) ? R2(avgPe * subjectEps) : null;
    const impliedPriceAtAvgPbv = (avgPbv !== null && subjectBvps) ? R2(avgPbv * subjectBvps) : null;

    const out = { peers: rows, avg_pe: avgPe, median_pe: medianPe, avg_pbv: avgPbv,
      implied_price_at_avg_pe: impliedPriceAtAvgPe, implied_price_at_avg_pbv: impliedPriceAtAvgPbv };
    if (subject.proposed_price !== null && subject.proposed_price !== undefined && subject.proposed_price !== '') {
      const sp = N(subject.proposed_price);
      out.proposed_price = R2(sp);
      out.premium_vs_avg_pe_price_pct = impliedPriceAtAvgPe ? R2(safeDiv(sp - impliedPriceAtAvgPe, impliedPriceAtAvgPe) * 100) : null;
    }
    return out;
  }

  function pricingAnalysis(scenario) {
    scenario = scenario || {};
    const faceValue = N(scenario.face_value, 100);
    const proposedPrice = N(scenario.proposed_price);
    const out = { face_value: R2(faceValue), proposed_price: R2(proposedPrice),
      premium_to_face_value: R2(proposedPrice - faceValue),
      premium_to_face_value_pct: faceValue ? R2(safeDiv(proposedPrice - faceValue, faceValue) * 100) : null };
    if (scenario.subject_eps !== null && scenario.subject_eps !== undefined && scenario.subject_eps !== '') {
      const epsV = N(scenario.subject_eps);
      out.subject_eps = R2(epsV);
      out.implied_pe = epsV ? R2(safeDiv(proposedPrice, epsV)) : null;
    }
    if (scenario.valuation_per_share !== null && scenario.valuation_per_share !== undefined && scenario.valuation_per_share !== '') {
      const vps = N(scenario.valuation_per_share);
      out.valuation_per_share = R2(vps);
      out.gap_vs_valuation = R2(proposedPrice - vps);
      out.gap_vs_valuation_pct = vps ? R2(safeDiv(proposedPrice - vps, vps) * 100) : null;
    }
    return out;
  }

  function readinessSummary(checklistItems) {
    const byArea = {};
    (checklistItems || []).forEach(c => {
      const area = c.area || 'Readiness';
      const g = byArea[area] || (byArea[area] = { total: 0, done: 0, not_met: 0 });
      g.total++;
      if (c.status === 'done') g.done++;
      if (c.status === 'not_met') g.not_met++;
    });
    const eligibilityRows = (checklistItems || []).filter(c => c.area === 'Eligibility').map(c => {
      const threshold = c.threshold_value, actual = c.actual_value;
      let met = null;
      if (threshold !== null && threshold !== undefined && threshold !== '' &&
          actual !== null && actual !== undefined && actual !== '' &&
          isFinite(Number(threshold)) && isFinite(Number(actual))) {
        met = Number(actual) >= Number(threshold);
      }
      return { id: c.id, criterion: c.criterion || '', threshold_value: threshold, actual_value: actual, met };
    });
    const areas = {};
    Object.keys(byArea).forEach(area => {
      const g = byArea[area];
      areas[area] = { total: g.total, done: g.done, not_met: g.not_met, completion_pct: R2(safeDiv(g.done, g.total) * 100) };
    });
    const notMet = eligibilityRows.filter(r => r.met === false);
    const unconfirmed = eligibilityRows.filter(r => r.met === null);
    return { areas, eligibility: eligibilityRows, eligibility_not_met_count: notMet.length, eligibility_unconfirmed_count: unconfirmed.length };
  }

  global.IpoEngines = { R2, restatementAnalysis, workingCapitalAdequacy, peerComparison, pricingAnalysis, readinessSummary };
})(window);
