// Traction Forecasting . App shell. Wires state, computes projections, renders all sections.

const STORAGE_KEY = 'ms_traction_v1';

function useTractionState() {
  const [data, setData] = React.useState(() => {
    try {
      const saved = localStorage.getItem(STORAGE_KEY);
      if (saved) {
        const p = JSON.parse(saved);
        // Migrate old shape: meta.takeRate -> meta.monetization
        if (p.meta && !p.meta.monetization) {
          p.meta.monetization = {
            takeRateOn: true,
            takeRate: p.meta.takeRate ?? 12,
            supplySubOn: false, supplySubMonthly: 0,
            demandSubOn: false, demandSubMonthly: 0,
          };
          delete p.meta.takeRate;
        }
        return p;
      }
    } catch(e) {}
    return JSON.parse(JSON.stringify(TRACTION_EXAMPLE));
  });
  React.useEffect(() => {
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch(e) {}
  }, [data]);

  // setPath(['supply','retention','m1'], 88)
  const setPath = React.useCallback((path, value) => {
    setData(prev => {
      const next = JSON.parse(JSON.stringify(prev));
      let obj = next;
      for (let i = 0; i < path.length - 1; i++) obj = obj[path[i]];
      obj[path[path.length - 1]] = value;
      return next;
    });
  }, []);

  return [data, setPath, setData];
}

// Build full projection from inputs
function buildProjection(data) {
  const horizon = data.meta.horizonMonths || 24;
  const supply = projectCohorts(
    {
      startSize: data.supply.startSize,
      growthMoM: data.supply.growthMoM,
      activationRate: data.supply.activationRate,
      retention: data.supply.retention,
    }, horizon, 0
  );
  const demand = projectCohorts(
    {
      startSize: data.demand.startSize,
      growthMoM: data.demand.growthMoM,
      activationRate: data.demand.activationRate,
      retention: data.demand.retention,
    }, horizon, data.demand.transactionsPerActive
  );
  // matched transactions: gated by liquidity match rate AND supply availability
  // Heuristic: each active supplier can fulfill listingsPerActive bookings/mo;
  // matched = min(demand.transactions * matchRate, supply.active * listingsPerActive)
  const matched = [];
  const gmv = [];
  const takeRevenue = [];
  const supplySubRevenue = [];
  const demandSubRevenue = [];
  const grossRevenue = [];
  const netTake = [];
  const m11n = data.meta.monetization || { takeRateOn: true, takeRate: 12, supplySubOn: false, supplySubMonthly: 0, demandSubOn: false, demandSubMonthly: 0 };
  const effTake = m11n.takeRateOn ? (m11n.takeRate || 0) : 0;
  for (let i = 0; i < horizon; i++) {
    const buyerTx = demand[i].transactions * (data.liquidity.matchRate / 100);
    const supplyCap = supply[i].active * data.supply.listingsPerActive;
    const m = Math.round(Math.min(buyerTx, supplyCap));
    matched.push(m);
    const g = m * data.liquidity.avgBookingValue;
    gmv.push(g);
    // Three revenue streams
    const grossTake = g * (effTake / 100);
    const supplySub = m11n.supplySubOn ? supply[i].active * (m11n.supplySubMonthly || 0) : 0;
    const demandSub = m11n.demandSubOn ? demand[i].active * (m11n.demandSubMonthly || 0) : 0;
    takeRevenue.push(grossTake);
    supplySubRevenue.push(supplySub);
    demandSubRevenue.push(demandSub);
    const gross = grossTake + supplySub + demandSub;
    grossRevenue.push(gross);
    // Deductions only apply to GMV-driven economics
    const netDeductions = (g * data.unitEconomics.paymentProcessing/100) + (m * data.unitEconomics.supportCostPerTx) + (g * data.unitEconomics.refundRate/100);
    netTake.push(Math.max(0, gross - netDeductions));
  }
  const cumGMV = gmv.reduce((a,b)=>a+b,0);
  const cumNetTake = netTake.reduce((a,b)=>a+b,0);
  const cumTx = matched.reduce((a,b)=>a+b,0);
  const cumTake = takeRevenue.reduce((a,b)=>a+b,0);
  const cumSupplySub = supplySubRevenue.reduce((a,b)=>a+b,0);
  const cumDemandSub = demandSubRevenue.reduce((a,b)=>a+b,0);
  return { supply, demand, matched, gmv, takeRevenue, supplySubRevenue, demandSubRevenue, grossRevenue, netTake, cumGMV, cumNetTake, cumTx, cumTake, cumSupplySub, cumDemandSub };
}

function buildComputed(data, projection) {
  // contribution per tx (dollar) . Uses unitEconomics.avgMargin (not the meta take rate; this is the *net* per-tx margin assumption)
  const avgGMV = data.liquidity.avgBookingValue;
  const grossTake = avgGMV * data.unitEconomics.avgMargin / 100;
  const deductions = (avgGMV * data.unitEconomics.paymentProcessing/100) + data.unitEconomics.supportCostPerTx + (avgGMV * data.unitEconomics.refundRate/100);
  const contributionPerTx = grossTake - deductions;

  // LTV: contribution * txPerActiveOrListing * avg lifetime months + subscription contribution over lifetime
  const lifetimeMonths = (r) => {
    let s = 0;
    for (let m = 1; m <= 24; m++) s += retentionCurve(r.m1/100, r.m6/100, r.m12/100, m);
    return s;
  };
  const supplyLifetime = lifetimeMonths(data.supply.retention);
  const demandLifetime = lifetimeMonths(data.demand.retention);

  const m11n = data.meta.monetization || {};
  const supplySubLTV = (m11n.supplySubOn ? (m11n.supplySubMonthly||0) : 0) * supplyLifetime;
  const demandSubLTV = (m11n.demandSubOn ? (m11n.demandSubMonthly||0) : 0) * demandLifetime;

  // demand transactional LTV: txPerMonth * lifetime * contribution * matchRate
  const demandTxLTV = data.demand.transactionsPerActive * demandLifetime * contributionPerTx * (data.liquidity.matchRate/100);
  // supply transactional LTV: listings * lifetime * fillProbability * contribution; fillProb modeled as matchRate/2 for conservatism
  const supplyTxLTV = data.supply.listingsPerActive * supplyLifetime * contributionPerTx * (data.liquidity.matchRate/100) * 0.5;

  const supplyLTV = supplyTxLTV + supplySubLTV;
  const demandLTV = demandTxLTV + demandSubLTV;

  const blendedLTV = demandLTV + supplyLTV;
  const blendedCAC = data.demand.cac + data.supply.cac;
  const ltvCac = blendedCAC > 0 ? blendedLTV / blendedCAC : 0;

  return { contributionPerTx, supplyLTV, demandLTV, supplyTxLTV, demandTxLTV, supplySubLTV, demandSubLTV, ltvCac };
}

function buildScenarios(data) {
  const horizon = data.meta.horizonMonths;
  const variants = {
    pessimistic: { growth: 0.7, activation: 0.8, match: 0.85 },
    base: { growth: 1, activation: 1, match: 1 },
    optimistic: { growth: 1.15, activation: 1.1, match: 1.05 },
  };
  const out = {};
  for (const [k, mult] of Object.entries(variants)) {
    const adj = scenarioMultiply(data, mult);
    const proj = buildProjection({ ...adj, liquidity: { ...adj.liquidity, matchRate: adj.matchRate } });
    out[k] = {
      cumGMV: proj.cumGMV,
      cumNetTake: proj.cumNetTake,
      exitActiveBuyers: proj.demand[horizon-1]?.active || 0,
      exitActiveSuppliers: proj.supply[horizon-1]?.active || 0,
      exitMRR: proj.netTake[horizon-1] || 0,
      exitARR: (proj.netTake[horizon-1] || 0) * 12,
    };
  }
  return out;
}

function TractionApp() {
  const [data, setPath, setData] = useTractionState();
  const projection = React.useMemo(() => buildProjection(data), [data]);
  const computed = React.useMemo(() => buildComputed(data, projection), [data, projection]);
  const scenarios = React.useMemo(() => buildScenarios(data), [data]);

  const onPrint = () => window.print();
  const onReset = () => { if(confirm('Reset all inputs to the RigShare example?')) setData(JSON.parse(JSON.stringify(TRACTION_EXAMPLE))); };
  const onClear = () => {
    if(!confirm('Clear all example data? You will start with blanks.')) return;
    const blank = JSON.parse(JSON.stringify(TRACTION_EXAMPLE));
    blank.meta = { name:'', category:'', geography:'', stage:'', horizonMonths:24, monetization: { takeRateOn:true, takeRate:10, supplySubOn:false, supplySubMonthly:0, demandSubOn:false, demandSubMonthly:0 } };
    blank.supply = { label:'Supply', startSize:0, growthMoM:0, activationRate:0, retention:{m1:0,m6:0,m12:0}, listingsPerActive:0, cac:0 };
    blank.demand = { label:'Demand', startSize:0, growthMoM:0, activationRate:0, retention:{m1:0,m6:0,m12:0}, transactionsPerActive:0, cac:0 };
    blank.liquidity = { matchRate:0, avgBookingValue:0, timeToMatchHours:0 };
    blank.unitEconomics = { avgMargin:0, paymentProcessing:0, supportCostPerTx:0, refundRate:0 };
    setData(blank);
  };
  const onBook = () => {
    window.open('Marketplace Studio.html', '_blank');
  };

  return (
    <>
      <TractionTopbar onPrint={onPrint} onReset={onReset} onClear={onClear}/>
      <main className="lm-doc">
        <TractionCover data={data} onChange={setPath}/>
        <TractionCohorts data={data} onChange={setPath}/>
        <TractionLiquidity data={data} onChange={setPath}/>
        <TractionUnitEcon data={data} onChange={setPath} computed={computed}/>
        <TractionProjection data={data} projection={projection}/>
        <TractionScenarios scenarios={scenarios}/>
        <TractionSummary data={data} projection={projection} computed={computed} onBook={onBook}/>
      </main>
    </>
  );
}

ReactDOM.createRoot(document.getElementById('app')).render(<TractionApp/>);
