/* CA Automator — Corporate Advisory engines (JS mirror of
   server/corporate_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));

  function rightsIssue(inputs) {
    inputs = inputs || {};
    const existingShares = N(inputs.existing_shares);
    const ratioNew = N(inputs.ratio_new, 1), ratioHeld = N(inputs.ratio_held, 1);
    const subscriptionPrice = N(inputs.subscription_price);
    const cumRightsPrice = N(inputs.cum_rights_price);

    const newShares = ratioHeld ? existingShares * (ratioNew / ratioHeld) : 0;
    const totalSharesAfter = existingShares + newShares;
    const proceeds = newShares * subscriptionPrice;
    const terp = totalSharesAfter ? ((existingShares * cumRightsPrice) + (newShares * subscriptionPrice)) / totalSharesAfter : 0;
    const valueOfRight = cumRightsPrice - terp;

    return { existing_shares: R2(existingShares), ratio_new: ratioNew, ratio_held: ratioHeld,
      new_shares: R2(newShares), total_shares_after: R2(totalSharesAfter),
      subscription_price: R2(subscriptionPrice), cum_rights_price: R2(cumRightsPrice),
      proceeds: R2(proceeds), theoretical_ex_rights_price: R2(terp), value_of_right: R2(valueOfRight) };
  }

  function bonusIssue(inputs) {
    inputs = inputs || {};
    const existingShares = N(inputs.existing_shares);
    const ratioNew = N(inputs.ratio_new, 1), ratioHeld = N(inputs.ratio_held, 1);
    const faceValue = N(inputs.face_value, 100);
    const reservesAvailable = N(inputs.reserves_available);

    const bonusShares = ratioHeld ? existingShares * (ratioNew / ratioHeld) : 0;
    const capitalizedAmount = bonusShares * faceValue;
    const postBonusShares = existingShares + bonusShares;
    const reservesSufficient = capitalizedAmount <= reservesAvailable;
    const reservesShortfall = Math.max(0, capitalizedAmount - reservesAvailable);

    return { existing_shares: R2(existingShares), ratio_new: ratioNew, ratio_held: ratioHeld,
      face_value: R2(faceValue), bonus_shares: R2(bonusShares), post_bonus_shares: R2(postBonusShares),
      capitalized_amount: R2(capitalizedAmount), reserves_available: R2(reservesAvailable),
      reserves_sufficient: reservesSufficient, reserves_shortfall: R2(reservesShortfall) };
  }

  function capitalReduction(inputs) {
    inputs = inputs || {};
    const existingCapital = N(inputs.existing_capital);
    const existingShares = N(inputs.existing_shares);
    const faceValue = N(inputs.face_value, 100);
    const mode = inputs.mode || 'amount';

    let reductionPct, reductionAmount;
    if (mode === 'ratio') {
      reductionPct = N(inputs.reduction_pct);
      reductionAmount = existingCapital * reductionPct / 100;
    } else {
      reductionAmount = N(inputs.reduction_amount);
      reductionPct = existingCapital ? (reductionAmount / existingCapital * 100) : 0;
    }
    const newCapital = Math.max(0, existingCapital - reductionAmount);
    const newShares = faceValue ? (newCapital / faceValue) : 0;
    const sharesCancelled = Math.max(0, existingShares - newShares);

    return { existing_capital: R2(existingCapital), existing_shares: R2(existingShares), face_value: R2(faceValue),
      reduction_pct: R2(reductionPct), reduction_amount: R2(reductionAmount), new_capital: R2(newCapital),
      new_shares: R2(newShares), shares_cancelled: R2(sharesCancelled) };
  }

  function dividendDistributionCheck(inputs) {
    inputs = inputs || {};
    const netProfit = N(inputs.net_profit_current_year);
    const accumulatedLossesBf = N(inputs.accumulated_losses_bf);
    const freeReserves = N(inputs.free_reserves);
    const mandatoryReservePct = N(inputs.mandatory_reserve_pct);
    const proposedDividend = N(inputs.proposed_dividend_amount);

    const profitAfterLosses = netProfit - accumulatedLossesBf;
    const mandatoryReserveAmount = Math.max(0, profitAfterLosses) * mandatoryReservePct / 100;
    const distributableProfit = Math.max(0, profitAfterLosses - mandatoryReserveAmount) + freeReserves;
    const shortfall = Math.max(0, proposedDividend - distributableProfit);
    const sufficient = proposedDividend <= distributableProfit;

    const flags = [];
    if (accumulatedLossesBf > 0 && netProfit <= accumulatedLossesBf) {
      flags.push({ severity: 'High', message: 'Current year profit does not even cover brought-forward accumulated losses — no profit is available for distribution before those losses are made good.' });
    }
    if (!sufficient) {
      flags.push({ severity: 'High', message: 'Proposed dividend exceeds distributable profit by ' + shortfall.toLocaleString() + '.' });
    }
    if (mandatoryReservePct <= 0) {
      flags.push({ severity: 'Low', message: "No mandatory reserve percentage has been entered — confirm the entity's own AOA / statutory reserve requirement before relying on this figure." });
    }

    return { net_profit_current_year: R2(netProfit), accumulated_losses_bf: R2(accumulatedLossesBf),
      profit_after_losses: R2(profitAfterLosses), mandatory_reserve_pct: mandatoryReservePct,
      mandatory_reserve_amount: R2(mandatoryReserveAmount), free_reserves: R2(freeReserves),
      distributable_profit: R2(distributableProfit), proposed_dividend_amount: R2(proposedDividend),
      sufficient, shortfall: R2(shortfall), flags };
  }

  function shareholdingAnalysis(shareholders) {
    const rows = shareholders || [];
    const totalShares = rows.reduce((s, x) => s + N(x.shares_held), 0);
    let out = rows.map(s => {
      const shares = N(s.shares_held);
      const pct = totalShares ? (shares / totalShares * 100) : 0;
      return { id: s.id, name: s.name || '', share_class: s.share_class || 'equity', shares_held: R2(shares),
        ownership_pct: R2(pct), is_related_party: !!s.is_related_party, is_director: !!s.is_director };
    });
    out = out.sort((a, b) => b.ownership_pct - a.ownership_pct);

    const top5Pct = R2(out.slice(0, 5).reduce((s, r) => s + r.ownership_pct, 0));
    const relatedPartyPct = R2(out.filter(r => r.is_related_party).reduce((s, r) => s + r.ownership_pct, 0));
    const directorPct = R2(out.filter(r => r.is_director).reduce((s, r) => s + r.ownership_pct, 0));
    const byClass = {};
    out.forEach(r => { byClass[r.share_class] = (byClass[r.share_class] || 0) + r.shares_held; });
    Object.keys(byClass).forEach(k => { byClass[k] = R2(byClass[k]); });

    return { holders: out, total_shares: R2(totalShares), holder_count: out.length,
      top5_concentration_pct: top5Pct, related_party_pct: relatedPartyPct,
      director_holding_pct: directorPct, shares_by_class: byClass };
  }

  function boardCompliance(meetings, maxGapDays) {
    const boardMeetings = (meetings || [])
      .filter(m => (m.meeting_type || 'board') === 'board' && m.status !== 'cancelled')
      .slice().sort((a, b) => (a.meeting_date || '').localeCompare(b.meeting_date || ''));
    const gaps = [];
    for (let i = 1; i < boardMeetings.length; i++) {
      let gapDays = null;
      const d0 = new Date(boardMeetings[i - 1].meeting_date), d1 = new Date(boardMeetings[i].meeting_date);
      if (!isNaN(d0) && !isNaN(d1)) gapDays = Math.round((d1 - d0) / 86400000);
      gaps.push({ from: boardMeetings[i - 1].meeting_date, to: boardMeetings[i].meeting_date, gap_days: gapDays,
        exceeds_max: !!(maxGapDays && gapDays !== null && gapDays > maxGapDays) });
    }
    const total = (meetings || []).length;
    const minutesFiled = (meetings || []).filter(m => m.minutes_recorded).length;
    const minutesFiledPct = total ? R2(minutesFiled / total * 100) : 0;
    const breaches = gaps.filter(g => g.exceeds_max);

    return { meetings_count: total, board_meetings_count: boardMeetings.length,
      minutes_filed_pct: minutesFiledPct, gaps, gap_breaches_count: breaches.length, max_gap_days: maxGapDays || null };
  }

  const REQUIRES_AGM = { rights_issue: 1, bonus_issue: 1, private_placement: 1, capital_reduction: 1, buyback: 1 };

  function shareTransactionCompliance(transactions) {
    const out = (transactions || []).map(t => {
      const eventType = t.event_type || '';
      const needsAgm = !!REQUIRES_AGM[eventType];
      const missing = [];
      if (!t.board_approval_date) missing.push('board approval date');
      if (needsAgm && !t.agm_approval_date) missing.push('AGM/EGM (shareholder) approval date');
      if (!t.ocr_filed_date && (t.status === 'approved' || t.status === 'completed')) missing.push('OCR filing date');
      return { id: t.id, event_type: eventType, status: t.status, needs_agm_approval: needsAgm,
        missing, complete: missing.length === 0 };
    });
    const total = out.length;
    const complete = out.filter(r => r.complete).length;
    return { rows: out, total, complete_count: complete, complete_pct: total ? R2(complete / total * 100) : 0 };
  }

  global.CorporateEngines = {
    R2, rightsIssue, bonusIssue, capitalReduction, dividendDistributionCheck,
    shareholdingAnalysis, boardCompliance, shareTransactionCompliance,
  };
})(window);
