/* Folclore Latin Fund I — fund-level returns projection engine */

const FUND_MODEL_FILE = "dataroom/10-financial-models/fund-returns-model.xlsx";

const FUND_DEFAULTS = {
  fundSize: 150,
  committed: 150,
  called: 90,
  invested: 75,
  currentYear: 3,
  fundLife: 10,
  deploymentYears: 5,
  mgmtFee: 0.02,
  carry: 0.20,
  preferredReturn: 0.08,
  currentTvpi: 1.42,
  currentDpi: 0.073,
};

const FUND_SCENARIOS = {
  bear: { label: "Bear", grossMoic: 1.75, growthRate: 0.07, exitStart: 6 },
  base: { label: "Base", grossMoic: 2.35, growthRate: 0.11, exitStart: 6 },
  bull: { label: "Bull", grossMoic: 3.10, growthRate: 0.15, exitStart: 5 },
};

function fundIrr(cashFlows) {
  let rate = 0.12;
  for (let i = 0; i < 40; i++) {
    let npv = 0;
    let dnpv = 0;
    cashFlows.forEach((cf, t) => {
      const factor = Math.pow(1 + rate, t);
      npv += cf / factor;
      if (t > 0) dnpv -= (t * cf) / Math.pow(1 + rate, t + 1);
    });
    if (Math.abs(dnpv) < 1e-12) break;
    const next = rate - npv / dnpv;
    if (!Number.isFinite(next) || next <= -0.99) break;
    if (Math.abs(next - rate) < 1e-7) { rate = next; break; }
    rate = next;
  }
  return rate;
}

function computeFundReturns(assumptions, scenarioKey) {
  const scenario = FUND_SCENARIOS[scenarioKey];
  const life = assumptions.fundLife;
  const rows = [];
  const cashFlows = [];

  let called = 0;
  let nav = 0;
  let distributed = 0;
  let grossRealized = 0;

  for (let y = 0; y <= life; y++) {
    const prevCalled = called;
    const targetCalled = y === 0
      ? 0
      : Math.min(assumptions.committed, assumptions.committed * Math.min(y, assumptions.deploymentYears) / assumptions.deploymentYears);
    const newCall = Math.max(0, targetCalled - prevCalled);
    called = targetCalled;

    if (y > 0) {
      nav = (nav + newCall) * (1 + scenario.growthRate);
      const feeBase = y <= assumptions.deploymentYears ? assumptions.committed : called;
      nav -= feeBase * assumptions.mgmtFee;
    }

    if (y >= scenario.exitStart && called > 0) {
      const harvestYears = Math.max(1, life - scenario.exitStart + 1);
      const progress = (y - scenario.exitStart + 1) / harvestYears;
      const targetGrossValue = called * scenario.grossMoic;
      const targetRealized = targetGrossValue * progress;
      const grossExit = Math.max(0, targetRealized - grossRealized);
      grossRealized += grossExit;

      const profitShare = Math.max(0, grossExit - grossExit / scenario.grossMoic);
      const carryDrag = profitShare * assumptions.carry;
      const netExit = grossExit - carryDrag;

      distributed += netExit;
      nav = Math.max(0, nav - grossExit * 0.55);
    }

    if (y === assumptions.currentYear) {
      called = assumptions.called;
      distributed = assumptions.called * assumptions.currentDpi;
      nav = assumptions.called * (assumptions.currentTvpi - assumptions.currentDpi);
      grossRealized = distributed / Math.max(0.01, 1 - assumptions.carry * 0.35);
    }

    const tvpi = called > 0 ? (nav + distributed) / called : 0;
    const dpi = called > 0 ? distributed / called : 0;
    const rvpi = called > 0 ? nav / called : 0;

    rows.push({
      year: y,
      vintage: 2023 + y,
      called,
      nav,
      distributed,
      tvpi,
      dpi,
      rvpi,
    });
  }

  const terminal = rows[rows.length - 1];
  const netMoic = terminal.tvpi;
  const annualFlows = rows.slice(1).map((r, i) => {
    const prev = rows[i];
    const call = r.called - prev.called;
    const dist = r.distributed - prev.distributed;
    return -call + dist;
  });
  if (terminal.nav > 0) annualFlows[annualFlows.length - 1] += terminal.nav;
  const netIrr = fundIrr([0, ...annualFlows]);

  return {
    scenario: scenario.label,
    rows,
    netMoic,
    netIrr,
    terminalDpi: terminal.dpi,
    terminalRvpi: terminal.rvpi,
    grossMoic: scenario.grossMoic,
  };
}

Object.assign(window, {
  FUND_MODEL_FILE,
  FUND_DEFAULTS,
  FUND_SCENARIOS,
  computeFundReturns,
});
