// ml-forecast.jsx — Per-work 12-month line-item royalty forecast
// ────────────────────────────────────────────────────────────────────
// For any work or recording, project monthly earnings 12 months out, broken down by:
//   - DSP (Spotify / Apple / YT / Amazon / Tidal / Deezer / Pandora / SC)
//   - Royalty type (mechanical / performance / sync / neighboring)
//   - Territory (top 12 markets)
//
// Math: exponential decay model with seasonal multiplier + growth-stage adjustment.
// Each line is a { month, dsp, type, territory, low, mid, high } cell.
// ────────────────────────────────────────────────────────────────────
(function () {
  if (typeof window === 'undefined') return;

  function fseed(s) {
    s = String(s || 'x'); let h = 0;
    for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;
    let a = h ^ 0x9e3779b9, b = h ^ 0xdeadbeef, c = h ^ 0x41c6ce57, d = h ^ 0x6b79f5d3;
    return function () {
      a |= 0; b |= 0; c |= 0; d |= 0;
      const t = (((a + b) | 0) + d) | 0; d = (d + 1) | 0;
      a = b ^ (b >>> 9); b = (c + (c << 3)) | 0; c = (c << 21 | c >>> 11);
      c = (c + t) | 0;
      return (t >>> 0) / 4294967296;
    };
  }

  // Decay rate by track age (years since release)
  // Younger = more volatility, older = stable annuity
  function decayProfile(ageYears) {
    if (ageYears < 1) return { monthlyDecay: 0.94, vol: 0.18 }; // fast initial dropoff
    if (ageYears < 3) return { monthlyDecay: 0.97, vol: 0.12 };
    if (ageYears < 7) return { monthlyDecay: 0.992, vol: 0.07 };
    return { monthlyDecay: 0.998, vol: 0.04 }; // catalog annuity
  }

  // Seasonal multipliers (1.0 = avg)
  const SEASONAL = [0.94, 0.92, 1.02, 1.05, 1.04, 0.98, 0.96, 1.00, 1.03, 1.06, 1.10, 1.18];
  // ^ Jan-Feb soft, summer mid, Q4 spike (Christmas-driven)

  const DSP_SHARES = { 'Spotify': 0.32, 'Apple Music': 0.18, 'YouTube Music': 0.16, 'Amazon Music': 0.13, 'Tidal': 0.04, 'Deezer': 0.05, 'Pandora': 0.08, 'SoundCloud': 0.04 };
  const TYPE_SHARES = { 'Mechanical': 0.42, 'Performance': 0.41, 'Sync': 0.06, 'Neighboring': 0.11 };
  const TERR_SHARES = { US: 0.36, GB: 0.09, DE: 0.07, JP: 0.10, BR: 0.08, MX: 0.06, IN: 0.07, AU: 0.04, KR: 0.05, NL: 0.03, ES: 0.025, FR: 0.025 };

  function forecastWork(work, opts) {
    opts = opts || {};
    const horizonMonths = opts.horizon || 12;
    const startMonth = opts.startMonth || 1; // 1=Jan
    const startYear = opts.startYear || 2026;
    const rng = fseed('fc·' + (work.id || work.title || 'x'));

    const releaseYear = parseInt((work.releaseDate || work.year || 2022).toString().slice(0, 4), 10);
    const ageYears = Math.max(0, 2026 - releaseYear);
    const profile = decayProfile(ageYears);

    // Anchor: monthly earnings today
    const annualBase = (work.annualEarnings || (work.plays || 1) * 1_000_000 * 0.0028 || 12000);
    let monthlyBase = annualBase / 12;

    const months = [];
    for (let m = 0; m < horizonMonths; m++) {
      const monthIdx = ((startMonth - 1 + m) % 12);
      const yearIdx = startYear + Math.floor((startMonth - 1 + m) / 12);
      const seasonalMult = SEASONAL[monthIdx];
      const noise = 1 + (rng() - 0.5) * profile.vol;
      const monthValue = monthlyBase * seasonalMult * noise;

      const lines = [];
      Object.entries(DSP_SHARES).forEach(([dsp, dShare]) => {
        Object.entries(TYPE_SHARES).forEach(([type, tShare]) => {
          // Skip non-applicable combos
          if ((dsp === 'Pandora' || dsp === 'SoundCloud') && type === 'Sync') return;
          if (type === 'Sync' && rng() > 0.18) return; // sync is sparse
          Object.entries(TERR_SHARES).forEach(([terr, terShare]) => {
            const v = monthValue * dShare * tShare * terShare;
            if (v < 0.5) return;
            const conf = 0.10 + ageYears * 0.015 + (m / horizonMonths) * 0.18; // CI widens with age & distance
            lines.push({
              month: monthIdx + 1,
              year: yearIdx,
              dsp,
              type,
              territory: terr,
              mid: Math.round(v * 100) / 100,
              low: Math.round(v * (1 - conf) * 100) / 100,
              high: Math.round(v * (1 + conf) * 100) / 100,
            });
          });
        });
      });

      const monthTotal = lines.reduce((s, l) => s + l.mid, 0);
      const lowTotal   = lines.reduce((s, l) => s + l.low, 0);
      const highTotal  = lines.reduce((s, l) => s + l.high, 0);

      months.push({
        idx: m,
        label: `${['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][monthIdx]} ${String(yearIdx).slice(2)}`,
        year: yearIdx,
        month: monthIdx + 1,
        seasonalMult,
        total: Math.round(monthTotal * 100) / 100,
        low: Math.round(lowTotal * 100) / 100,
        high: Math.round(highTotal * 100) / 100,
        lineCount: lines.length,
        // Lines kept lazy (only return on demand to keep payloads light)
        lines,
      });

      // Decay base for next month
      monthlyBase *= profile.monthlyDecay;
    }

    const total = months.reduce((s, m) => s + m.total, 0);
    const totalLow = months.reduce((s, m) => s + m.low, 0);
    const totalHigh = months.reduce((s, m) => s + m.high, 0);

    return {
      workId: work.id,
      title: work.title,
      ageYears,
      profile,
      annualBase,
      months,
      total: Math.round(total * 100) / 100,
      totalLow: Math.round(totalLow * 100) / 100,
      totalHigh: Math.round(totalHigh * 100) / 100,
      // Aggregations for charts
      byDsp: aggregateBy(months, 'dsp'),
      byType: aggregateBy(months, 'type'),
      byTerritory: aggregateBy(months, 'territory'),
    };
  }

  function aggregateBy(months, key) {
    const out = {};
    for (const m of months) for (const l of m.lines) {
      out[l[key]] = (out[l[key]] || 0) + l.mid;
    }
    return Object.entries(out).map(([k, v]) => ({ key: k, total: Math.round(v * 100) / 100 }))
      .sort((a, b) => b.total - a.total);
  }

  // Catalog-wide rollup: sum N forecasts
  function forecastCatalog(works, opts) {
    works = (works || []).slice(0, opts?.limit || 100);
    const fcs = works.map(w => forecastWork(w, opts));
    const months = (opts?.horizon || 12);
    const rollup = [];
    for (let m = 0; m < months; m++) {
      const total = fcs.reduce((s, f) => s + (f.months[m]?.total || 0), 0);
      const low = fcs.reduce((s, f) => s + (f.months[m]?.low || 0), 0);
      const high = fcs.reduce((s, f) => s + (f.months[m]?.high || 0), 0);
      rollup.push({ idx: m, label: fcs[0]?.months[m]?.label, total, low, high });
    }
    return { works: fcs, rollup, total: rollup.reduce((s, r) => s + r.total, 0) };
  }

  window.ForecastEngine = { forecastWork, forecastCatalog, decayProfile };
  console.log('[ForecastEngine] loaded · 12mo line-item');
})();
