/* global React */
// Demo Mode for the course-partner portal.
//
// The portal doubles as the sales demo ("the portal is free until the data
// is undeniable") — but a just-signed course has no history, so every
// analytics widget can run on fabricated numbers behind one sidebar toggle.
// Everything here is DETERMINISTIC: values are seeded by course id (and
// calendar-day strings), never Date.now()/Math.random() inside a generator,
// so numbers don't jump between renders and survive a refresh identically.
// Nothing is written to the database.

// ─── Seeded PRNG ──────────────────────────────────────────────────────
function hashStr(s) {
  let h = 2166136261;
  for (const c of String(s)) { h ^= c.charCodeAt(0); h = Math.imul(h, 16777619); }
  return h >>> 0;
}
function mulberry32(seed) {
  return function () {
    seed |= 0; seed = (seed + 0x6D2B79F5) | 0;
    let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
const rngFor = (courseId, salt = '') => mulberry32(hashStr(`${courseId}|${salt}`));

// ─── The toggle store (ghost-emulation pattern) ───────────────────────
const DEMO = {
  on: (() => { try { return localStorage.getItem('spp_mgr_demo') === '1'; } catch (_) { return false; } })(),
  listeners: new Set(),
  bumps: [], // in-memory bump events recorded from the Tee Times board
};
function demoNotify() { DEMO.listeners.forEach(fn => { try { fn(); } catch (_) {} }); }
function setDemoMode(on) {
  DEMO.on = !!on;
  try { localStorage.setItem('spp_mgr_demo', on ? '1' : '0'); } catch (_) { /* noop */ }
  demoNotify();
}
function useDemoMode() {
  const [, force] = React.useReducer(x => x + 1, 0);
  React.useEffect(() => {
    DEMO.listeners.add(force);
    return () => DEMO.listeners.delete(force);
  }, []);
  return DEMO.on;
}
// Rebook clicks on the Tee Times board land here so the People panel's bump
// console moves live during a demo.
function recordDemoBump(entry) {
  DEMO.bumps = [{ at: new Date().toISOString(), ...entry }, ...DEMO.bumps].slice(0, 20);
  demoNotify();
}
// ─── Generators (all deterministic, cached per course) ────────────────
const demoCache = {};
function cached(key, make) {
  if (!(key in demoCache)) demoCache[key] = make();
  return demoCache[key];
}
const dstr = (d) => {
  const p = n => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
};

// 90 days × 4 windows of {dayStr, windowKey, seats, booked, revenue}.
// Twilight is the star (the business's core slot); weekends run hotter.
function demoWindowDays(courseId) {
  return cached(`wd|${courseId}`, () => {
    const base = { morning: 0.35, midday: 0.45, twilight: 0.78, night: 0.15 };
    const out = [];
    const today = new Date(); today.setHours(0, 0, 0, 0);
    for (let i = 90; i >= 1; i--) {
      const d = new Date(today.getTime() - i * 864e5);
      const day = dstr(d);
      const wknd = d.getDay() === 0 || d.getDay() === 6;
      for (const w of ['morning', 'midday', 'twilight', 'night']) {
        const rng = rngFor(courseId, `${day}|${w}`);
        const slots = w === 'twilight' ? 8 : 5;
        const seats = slots * 4;
        let rate = base[w] * (wknd ? 1.25 : 1) * (0.8 + rng() * 0.4);
        rate = Math.min(1, rate);
        const booked = Math.round(seats * rate);
        const price = 20 + Math.floor(rng() * 7); // $20–26
        out.push({ dayStr: day, windowKey: w, seats, booked, revenue: booked * price });
      }
    }
    return out;
  });
}

// ~2.2 years of per-day KPI rows for the Business dashboard — revenue,
// seats avail/booked, rounds, players, new/returning split, no-shows and
// an average round time. One deterministic generator backs every KPI
// tile and trend chart so the demo's numbers stay internally consistent
// (e.g. players roughly track seatsBooked minus no-shows) across
// whatever custom date range the manager picks. Depth needs to cover a
// full YTD range PLUS an equal-length "prior period" comparison window
// before it, so it reaches back further than a single year.
function demoBusinessDays(courseId) {
  return cached(`bd|${courseId}`, () => {
    const out = [];
    const today = new Date(); today.setHours(0, 0, 0, 0);
    for (let i = 800; i >= 1; i--) {
      const d = new Date(today.getTime() - i * 864e5);
      const day = dstr(d);
      const rng = rngFor(courseId, `bd|${day}`);
      const wknd = d.getDay() === 0 || d.getDay() === 6;
      const growth = 1 + (400 - i) * 0.0011; // slow multi-month climb
      const season = 1 - 0.22 * Math.abs(6.5 - (d.getMonth() + 1)) / 6.5; // Miami slows midsummer
      const seatsAvail = (wknd ? 30 : 22) * 4;
      let fillRate = (wknd ? 0.62 : 0.46) * growth * (0.75 + 0.35 * season) * (0.82 + rng() * 0.36);
      fillRate = Math.min(0.97, Math.max(0.08, fillRate));
      const seatsBooked = Math.round(seatsAvail * fillRate);
      const price = 20 + Math.floor(rng() * 7);
      const revenue = seatsBooked * price;
      const noShows = Math.round(seatsBooked * (0.02 + rng() * 0.05));
      const players = Math.max(0, seatsBooked - noShows);
      const rounds = Math.max(0, Math.round(players / 4));
      const newPlayers = Math.round(players * (0.18 + rng() * 0.14));
      out.push({
        dayStr: day, revenue, seatsAvail, seatsBooked, players, rounds,
        newPlayers, returningPlayers: players - newPlayers, noShows,
        avgRoundMin: 132 + Math.round(rng() * 26),
      });
    }
    return out;
  });
}

// 24 months of {monthKey, revenue} with an upward trend + a couple of
// genuine down months so the floor tracker shows a BELOW state too.
function demoMonthlyTakes(courseId) {
  return cached(`mt|${courseId}`, () => {
    const out = [];
    const now = new Date();
    for (let i = 23; i >= 0; i--) {
      const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
      const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
      const rng = rngFor(courseId, `m|${key}`);
      const growth = 1 + (23 - i) * 0.035;                       // steady climb
      const season = 1 - 0.3 * Math.abs(6.5 - (d.getMonth() + 1)) / 6.5; // summer dip inverse: Miami slows Jun–Sep
      const dip = rng() < 0.13 ? 0.72 : 1;                       // occasional backslide month
      out.push({ monthKey: key, revenue: Math.round(2600 * growth * (0.75 + 0.35 * season) * dip * (0.9 + rng() * 0.2)) });
    }
    return out;
  });
}

// Demand curve buckets (30-min, minutes-of-day) peaking through twilight.
function demoDemandCurve(courseId) {
  return cached(`dc|${courseId}`, () => {
    const rng = rngFor(courseId, 'demand');
    const buckets = [];
    for (let m = 360; m <= 1320; m += 30) {
      const twilight = Math.exp(-Math.pow((m - 1065) / 130, 2)) * 26; // bell around ~5:45p
      const morning = Math.exp(-Math.pow((m - 540) / 110, 2)) * 9;    // small bump ~9a
      buckets.push({ min: m, count: Math.round((twilight + morning) * (0.8 + rng() * 0.4)) });
    }
    return { buckets, total: buckets.reduce((s, b) => s + b.count, 0), horizonDays: 14 };
  });
}

// Partnership: pretend the course signed 14 months ago → Year 2 view.
function demoPartnership(courseId) {
  return cached(`pt|${courseId}`, () => {
    const d = new Date();
    d.setMonth(d.getMonth() - 14);
    return { joinedAtISO: d.toISOString(), matKitReimbursed: 7, matKitMonths: 12 };
  });
}

Object.assign(window, {
  useDemoMode, setDemoMode, recordDemoBump,
  demoWindowDays, demoMonthlyTakes, demoBusinessDays, demoDemandCurve, demoPartnership,
  rngFor,
});
