// Traction Forecasting Template — Pure math + helpers (no JSX)

// Format number with optional decimals, comma separators
function fmt(n, dec=0) {
  if (n === null || n === undefined || !isFinite(n)) return '—';
  return n.toLocaleString('en-US', { minimumFractionDigits: dec, maximumFractionDigits: dec });
}
function fmtCurrency(n, dec=0) {
  if (n === null || n === undefined || !isFinite(n)) return '—';
  if (Math.abs(n) >= 1e6) return '$' + (n/1e6).toLocaleString('en-US', {minimumFractionDigits:1,maximumFractionDigits:1}) + 'M';
  if (Math.abs(n) >= 1e3) return '$' + (n/1e3).toLocaleString('en-US', {minimumFractionDigits:0,maximumFractionDigits:1}) + 'K';
  return '$' + n.toLocaleString('en-US', { minimumFractionDigits: dec, maximumFractionDigits: dec });
}
function fmtPct(n, dec=1) {
  if (n === null || n === undefined || !isFinite(n)) return '—';
  return n.toLocaleString('en-US', { minimumFractionDigits: dec, maximumFractionDigits: dec }) + '%';
}

// Cohort retention curve. Power-law-ish: m1=initial, m12=long-tail
// retentionCurve(initial, m6, m12, monthsOut)
function retentionCurve(m1, m6, m12, n) {
  // smooth interpolation between anchor months
  // m1 at month 1, m6 at month 6, m12 at month 12, m12 holds after
  if (n <= 0) return 1;
  if (n === 1) return m1;
  if (n <= 6) {
    const t = (n - 1) / 5; // 0..1
    return m1 * Math.pow(m6 / m1, t);
  }
  if (n <= 12) {
    const t = (n - 6) / 6;
    return m6 * Math.pow(m12 / m6, t);
  }
  return m12 * Math.pow(0.98, n - 12); // slow decay after month 12
}

// Build cohort projection over horizon months
// cohorts: { startSize, growthMoM, retention: {m1,m6,m12}, activationRate }
// returns array per month: { newCohort, activeUsers, transactionsPerActive ... }
function projectCohorts(cohorts, horizonMonths, txPerActive) {
  const months = [];
  const cohortSizes = []; // index by cohort-start-month
  for (let m = 1; m <= horizonMonths; m++) {
    const newSize = m === 1 ? cohorts.startSize : cohortSizes[cohortSizes.length - 1] * (1 + cohorts.growthMoM/100);
    cohortSizes.push(newSize);
    // for each prior cohort started at month c (1..m), survivors this month
    let active = 0;
    for (let c = 1; c <= m; c++) {
      const ageMonths = m - c + 1;
      const cohortStart = cohortSizes[c - 1] * cohorts.activationRate / 100;
      const surv = cohortStart * retentionCurve(cohorts.retention.m1/100, cohorts.retention.m6/100, cohorts.retention.m12/100, ageMonths);
      active += surv;
    }
    months.push({
      month: m,
      newCohort: Math.round(newSize),
      activated: Math.round(newSize * cohorts.activationRate / 100),
      active: Math.round(active),
      transactions: Math.round(active * txPerActive),
    });
  }
  return months;
}

// Apply scenario multiplier to a config (pessimistic/base/optimistic)
function scenarioMultiply(input, mult) {
  return {
    ...input,
    supply: { ...input.supply, growthMoM: input.supply.growthMoM * mult.growth, activationRate: Math.min(95, input.supply.activationRate * mult.activation) },
    demand: { ...input.demand, growthMoM: input.demand.growthMoM * mult.growth, activationRate: Math.min(95, input.demand.activationRate * mult.activation) },
    matchRate: Math.min(95, input.matchRate * mult.match),
  };
}

Object.assign(window, { fmt, fmtCurrency, fmtPct, retentionCurve, projectCohorts, scenarioMultiply });
