// 7play — global state store
// Single source of truth for sessions, variants, approvals, conflicts, budget, toasts, modals, chat.
// Every button in every stage dispatches into this.

const StoreContext = React.createContext(null);
const useStore = () => React.useContext(StoreContext);

// ---------- Initial data ----------
function makeInitialState() {
  // Seed richer data: per-session revisions, validations, approvals, budgets
  const games = window.GAMES;
  const sessions = {};

  // Primary deep-seeded session: Gem Quest (s1) — stage Review
  sessions['s1'] = {
    id: 's1', gameId: 'gem-quest', name: 'Gem Quest — Q2 US LAL',
    stage: 3, // 0=ref, 1=strategy, 2=build, 3=review, 4=export
    authenticity: 'true_to_game',
    status: 'in_progress',
    brief: 'Test "hook in first 3s" vs. "reward-first" for IAP-heavy audience.',
    created: '2026-04-17T09:20:00Z',
    updatedLabel: '2m ago',
    budget: { spent: 2.14, maxUsd: 5.00, elapsedMin: 6, maxMin: 20 },
    strategyApproved: true,
    strategyRevision: 1,
    strategyHistory: [
      { rev: 0, time: '14:14', label: 'initial draft', approved: false },
      { rev: 1, time: '14:31', label: 'refined CTA timing', approved: true, current: true },
    ],
    missingAssets: ['winning_animation_loop', 'localized_cta_string'],
    strategy: {
      concept: 'A two-beat hook: near-miss match-3 board resolves in 2.4s, then unlocks a character-led "save the hero" moment. Emphasizes the reward loop, not the mechanic tutorial.',
      hook: 'Board in loss-imminent state. Single tap saves the character. High-contrast VFX on match. No voiceover — copy-driven.',
      playableMoment: 'One guided match (gem → gem → 4-chain), then one free tap, then CTA. Target playtime 14–18s.',
      cta: '"PLAY FREE" · pulsing · appears at 12s if user stalls, otherwise on win-state.',
    },
    referencePack: {
      assets: [
        { id: 'a1', name: 'hero_character.png', tag: 'must-use', kind: 'Character', size: '1024×1024', hue: 330, provenance: 'user_owned', confidence: 98, addedAt: '14:22' },
        { id: 'a2', name: 'logo_primary.svg', tag: 'must-use', kind: 'Logo', size: '512×180', hue: 45, provenance: 'user_owned', confidence: 99, addedAt: '14:22' },
        { id: 'a3', name: 'screen_meta_01.png', tag: 'reference', kind: 'Screenshot', size: '2778×1284', hue: 280, provenance: 'user_owned', confidence: 95, addedAt: '14:22' },
        { id: 'a4', name: 'screen_meta_02.png', tag: 'reference', kind: 'Screenshot', size: '2778×1284', hue: 200, provenance: 'user_owned', confidence: 94, addedAt: '14:22' },
        { id: 'a5', name: 'ui_buttons.png', tag: 'reference', kind: 'UI kit', size: '512×768', hue: 150, provenance: 'user_owned', confidence: 91, addedAt: '14:22' },
        { id: 'a6', name: 'bg_level.png', tag: 'reference', kind: 'Background', size: '1920×1080', hue: 20, provenance: 'user_owned', confidence: 88, addedAt: '14:23' },
        { id: 'a7', name: 'app_icon.png', tag: 'must-use', kind: 'App icon', size: '1024×1024', hue: 330, provenance: 'user_owned', confidence: 99, addedAt: '14:23' },
        { id: 'a8', name: 'gem_sprites.png', tag: 'reference', kind: 'Sprites', size: '256×1024', hue: 280, provenance: 'licensed', confidence: 74, addedAt: '14:23' },
      ],
    },
    hypotheses: [
      { id: 'h1', key: 'A', name: 'Near-miss hook', color: 'magenta', hook: 'User feels almost-lost, then rescues the board. Tests loss-aversion pull.', ipm: 8.4 },
      { id: 'h2', key: 'B', name: 'Reward-first', color: 'cyan', hook: 'Opens mid-celebration — coin rain, 4-combo. Dopamine upfront, mechanics second.', ipm: 7.1 },
      { id: 'h3', key: 'C', name: 'Character-led', color: 'lime', hook: 'Hero speaks first ("help me match 3"). Story-led; best for narrative-affinity lookalikes.', ipm: 6.9 },
      { id: 'h4', key: 'D', name: 'Fake-difficulty ramp', color: '', hook: 'First match is trivial, second impossible without one IAP-shaped booster on screen.', ipm: 7.8 },
    ],
    variants: [
      {
        id: 'v1', key: 'A', name: 'Near-miss hook', color: 'magenta',
        state: 'approved', // ready | approved | needs_revision | building | failed | archived
        runtime: '18.2kb', played: '16s',
        hypothesis: 'Loss-aversion open; board resolves at t=2.4s. Strongest predicted IPM (8.4).',
        currentRev: 2, archived: false,
        revisions: [
          { rev: 1, time: '14:52', note: 'initial compile', approved: false },
          { rev: 2, time: '15:00', note: 'slower CTA pulse', approved: true, current: true },
        ],
        qa: { staticOk: true, runtimeOk: true, heuristicsOk: true, applovinOk: true, notes: 'One warning: background tile upscaled 512→1024 (provenance logged).' },
      },
      {
        id: 'v2', key: 'B', name: 'Reward-first', color: 'cyan',
        state: 'ready', runtime: '19.7kb', played: '15s',
        hypothesis: 'Coin-rain hook at t=0. Tests dopamine-forward open for casual lookalikes.',
        currentRev: 1, archived: false,
        revisions: [
          { rev: 1, time: '14:56', note: 'initial compile', current: true },
        ],
        qa: { staticOk: true, runtimeOk: true, heuristicsOk: true, applovinOk: true },
      },
      {
        id: 'v3', key: 'C', name: 'Character-led', color: 'lime',
        state: 'ready', runtime: '22.1kb', played: '17s',
        hypothesis: 'Hero voice opens ("help me!"). Narrative variant for story-affinity segments.',
        currentRev: 1, archived: false,
        revisions: [
          { rev: 1, time: '14:58', note: 'initial compile', current: true },
        ],
        qa: { staticOk: true, runtimeOk: true, heuristicsOk: true, applovinOk: true },
      },
      {
        id: 'v4', key: 'D', name: 'Difficulty ramp', color: '',
        state: 'needs_revision', runtime: '20.4kb', played: '19s',
        hypothesis: 'Easy → hard → booster shown. Tests IAP awareness at a creative level.',
        currentRev: 3, archived: false,
        revisions: [
          { rev: 1, time: '14:59', note: 'initial compile', current: false },
          { rev: 2, time: '15:08', note: 'rebalanced difficulty spike', current: false },
          { rev: 3, time: '15:14', note: 'audio re-master requested', current: true, failed: true },
        ],
        qa: { staticOk: true, runtimeOk: true, heuristicsOk: false, applovinOk: false, notes: 'Audio asset missing loudness mastering (−14 LUFS target).' },
      },
    ],
    approvals: [], // active gates
    chatByStage: {
      0: [
        { kind: 'system', text: '8 assets uploaded · reference pack built' },
        { kind: 'agent', time: '14:22', label: 'reference', text: 'I tagged 3 assets as must-use (logo, character, app icon) and 5 as reference-only. Two gaps: I need a winning-animation loop and a localized CTA string.' },
        { kind: 'agent', time: '14:23', label: 'reference', text: 'You can upload those, or I can generate them ($0.18 total · approval required). I won\'t silently invent branded assets.' },
        { kind: 'user', time: '14:24', text: 'Generate the CTA string locally, I\'ll upload the win loop tomorrow' },
        { kind: 'agent', time: '14:24', text: 'Queued. CTA generated — 3 candidates ("PLAY FREE", "MATCH NOW", "SAVE THE HERO"). Holding build until the win loop arrives.' },
      ],
      1: [
        { kind: 'system', text: 'strategy drafted · 4 variants proposed' },
        { kind: 'agent', time: '14:31', label: 'strategy', text: 'Built the strategy around loss-aversion as the hook ceiling. Historical winners in match-3 show a 23% IPM bump when the first 3s dramatize risk, not reward.' },
        { kind: 'agent', time: '14:31', label: 'strategy', text: 'The four variants split: A/D test loss-aversion (different intensity), B tests the inverse, C adds a narrative layer. Minimum set to read signal at 500 impressions/variant.' },
      ],
      2: [
        { kind: 'system', text: 'build started · 4 variants queued' },
        { kind: 'agent', time: '14:40', label: 'build', text: 'Scene graph compiled: 18 game objects, 6×7 match grid, 3 VFX layers. Runtime is 2D canvas — keeps bundle <25kb.' },
        { kind: 'agent', time: '14:42', label: 'var A', text: 'Variant A finished (near-miss hook). One warning: upscaled one background tile 512→1024 to meet playable spec. Provenance logged.' },
      ],
      3: [
        { kind: 'system', text: '4 variants ready · 1 approved · 1 revision pending' },
        { kind: 'agent', time: '15:02', label: 'review', text: 'All four variants passed the playable spec. Variant A is your strongest on predicted IPM — you already approved it. Variant D came back with a flagged audio master; regenerating.' },
      ],
      4: [
        { kind: 'system', text: '2 variants ready for AppLovin export' },
        { kind: 'agent', time: '15:18', label: 'export', text: 'Variants A and B passed full validation. C is waiting on your approval. D is blocked on an audio master.' },
      ],
    },
  };

  // Session s3: building — used for live Build demo
  sessions['s3'] = {
    id: 's3', gameId: 'coffee-empire', name: 'Coffee Empire — US launch',
    stage: 2, authenticity: 'true_to_game', status: 'building',
    brief: 'First-playable set for US launch, idle mechanic emphasis.',
    created: '2026-04-18T13:40:00Z', updatedLabel: 'now',
    budget: { spent: 0.82, maxUsd: 5.00, elapsedMin: 4, maxMin: 20 },
    strategyApproved: true, strategyRevision: 1,
    strategyHistory: [{ rev: 1, time: '13:52', label: 'initial', approved: true, current: true }],
    missingAssets: [],
    referencePack: { assets: [] },
    hypotheses: [],
    variants: [
      { id: 'v1', key: 'A', name: 'Fast-idle hook', color: 'magenta', state: 'ready', runtime: '17.8kb', currentRev: 1, archived: false, revisions: [{ rev: 1, current: true, time: '14:02' }], qa: { staticOk: true, runtimeOk: true, heuristicsOk: true, applovinOk: true }, hypothesis: 'First station online in 5s — coin/s climbs fast.' },
      { id: 'v2', key: 'B', name: 'Story-first', color: 'cyan', state: 'building', runtime: '—', currentRev: 0, archived: false, revisions: [], qa: {}, hypothesis: 'Open with barista narrative; first decision at t=4.' },
      { id: 'v3', key: 'C', name: 'Tap-rush', color: 'lime', state: 'queued', runtime: '—', currentRev: 0, archived: false, revisions: [], qa: {}, hypothesis: 'Tap-storm to stack income multipliers.' },
      { id: 'v4', key: 'D', name: 'Upgrade hook', color: '', state: 'queued', runtime: '—', currentRev: 0, archived: false, revisions: [], qa: {}, hypothesis: 'Shown right before an IAP-shaped upgrade.' },
    ],
    approvals: [],
    chatByStage: {
      2: [
        { kind: 'system', text: 'build started · 4 variants queued' },
        { kind: 'agent', time: '13:58', label: 'build', text: 'Scene graph locked — 4 stations, 3 upgrade tiers, idle tick = 200ms.' },
        { kind: 'agent', time: '14:00', label: 'var A', text: 'Variant A compiled. Runtime 17.8kb, within spec.' },
        { kind: 'agent', time: '14:02', label: 'var B', thinking: true, text: 'Wiring the narrative-first hook. Deferring CTA to t=12s so the story beat lands.' },
      ],
    },
  };

  // Stub other sessions minimally (list context only)
  window.SESSIONS.forEach(s => {
    if (sessions[s.id]) return;
    const g = window.GAMES.find(x => x.id === s.gameId);
    sessions[s.id] = {
      id: s.id, gameId: s.gameId, name: `${g?.name.split(':')[0] || 'Session'} — ${s.stageName}`,
      stage: s.stage === 5 ? 4 : Math.max(0, s.stage - 1),
      authenticity: 'true_to_game', status: s.status, brief: s.brief,
      updatedLabel: s.updated,
      budget: { spent: Math.random() * 4, maxUsd: 5.00, elapsedMin: 5, maxMin: 20 },
      strategyApproved: s.stage >= 2, strategyRevision: 1, strategyHistory: [],
      missingAssets: [],
      referencePack: { assets: [] },
      hypotheses: [], variants: [], approvals: [],
      chatByStage: {},
      variantsReady: s.variantsReady, variantsTotal: s.variants,
    };
  });

  return {
    sessions,
    activeSessionId: null,
    route: safeLoadRoute(),
    chatCollapsed: false,
    toasts: [], // { id, kind: 'conflict'|'budget'|'info', title, text, actions:[{label,action}] }
    modal: null, // { kind: 'approval'|'preview'|'settings'|'confirm'|'cmdk'|'share'|'history'|'qa'|'compare'|'export_bundle', data }
    sessionsListFilter: { search: '', stage: 'all', authenticity: 'all' },
    stepperStyle: 'numbered',
    budgetPanelOpen: false,
    // wizard draft
    wizardDraft: null, // { name, storeUrl, authenticity, assets, brief }
    uid: 100,
  };
}

function safeLoadRoute() {
  try {
    const saved = localStorage.getItem('7play:route');
    if (saved) return JSON.parse(saved);
  } catch (e) {}
  return { kind: 'home' };
}

// ---------- Reducer ----------
function reducer(state, action) {
  switch (action.type) {
    case 'NAV': {
      const route = action.route;
      try { localStorage.setItem('7play:route', JSON.stringify(route)); } catch (e) {}
      return { ...state, route, modal: null };
    }
    case 'SET_CHAT_COLLAPSED':
      return { ...state, chatCollapsed: action.value };
    case 'SET_STEPPER_STYLE':
      return { ...state, stepperStyle: action.value };
    case 'SET_LIST_FILTER':
      return { ...state, sessionsListFilter: { ...state.sessionsListFilter, ...action.patch } };
    case 'OPEN_MODAL':
      return { ...state, modal: action.modal };
    case 'CLOSE_MODAL':
      return { ...state, modal: null };
    case 'TOGGLE_BUDGET_PANEL':
      return { ...state, budgetPanelOpen: !state.budgetPanelOpen };
    case 'CLOSE_BUDGET_PANEL':
      return { ...state, budgetPanelOpen: false };
    case 'PUSH_TOAST':
      return { ...state, toasts: [...state.toasts, { id: state.uid + 1, ...action.toast }], uid: state.uid + 1 };
    case 'DISMISS_TOAST':
      return { ...state, toasts: state.toasts.filter(t => t.id !== action.id) };
    case 'SET_WIZARD_DRAFT':
      return { ...state, wizardDraft: { ...(state.wizardDraft || {}), ...action.patch } };
    case 'CLEAR_WIZARD':
      return { ...state, wizardDraft: null };
    case 'UPDATE_SESSION': {
      const cur = state.sessions[action.id];
      if (!cur) return state;
      return { ...state, sessions: { ...state.sessions, [action.id]: { ...cur, ...action.patch } } };
    }
    case 'UPDATE_VARIANT': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      const variants = s.variants.map(v => v.id === action.variantId ? { ...v, ...action.patch } : v);
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, variants } } };
    }
    case 'UPDATE_STRATEGY': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      const strategy = { ...(s.strategy || {}), ...action.patch };
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, strategy } } };
    }
    case 'UPDATE_ASSET': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      const assets = s.referencePack.assets.map(a => a.id === action.assetId ? { ...a, ...action.patch } : a);
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, referencePack: { ...s.referencePack, assets } } } };
    }
    case 'REMOVE_ASSETS': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      const assets = s.referencePack.assets.filter(a => !action.ids.includes(a.id));
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, referencePack: { ...s.referencePack, assets } } } };
    }
    case 'ADD_ASSETS': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      const assets = [...s.referencePack.assets, ...action.assets];
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, referencePack: { ...s.referencePack, assets } } } };
    }
    case 'UPDATE_HYPOTHESIS': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      const hypotheses = s.hypotheses.map(h => h.id === action.id ? { ...h, ...action.patch } : h);
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, hypotheses } } };
    }
    case 'ADD_HYPOTHESIS': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, hypotheses: [...s.hypotheses, action.hyp] } } };
    }
    case 'REMOVE_HYPOTHESIS': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, hypotheses: s.hypotheses.filter(h => h.id !== action.id) } } };
    }
    case 'REORDER_HYPOTHESES': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      const order = action.ids.map(id => s.hypotheses.find(h => h.id === id)).filter(Boolean);
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, hypotheses: order } } };
    }
    case 'ADD_CHAT': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      const prev = s.chatByStage[action.stage] || [];
      const chatByStage = { ...s.chatByStage, [action.stage]: [...prev, action.msg] };
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, chatByStage } } };
    }
    case 'CLEAR_CHAT': {
      const s = state.sessions[action.sessionId];
      if (!s) return state;
      const chatByStage = { ...s.chatByStage, [action.stage]: [] };
      return { ...state, sessions: { ...state.sessions, [s.id]: { ...s, chatByStage } } };
    }
    default:
      return state;
  }
}

// ---------- Provider & actions ----------
function StoreProvider({ children }) {
  const [state, dispatch] = React.useReducer(reducer, null, makeInitialState);

  const actions = React.useMemo(() => ({
    nav: (kind, sessionId) => dispatch({ type: 'NAV', route: kind === 'session' ? { kind, sessionId } : { kind } }),
    setChatCollapsed: (v) => dispatch({ type: 'SET_CHAT_COLLAPSED', value: v }),
    setStepperStyle: (v) => dispatch({ type: 'SET_STEPPER_STYLE', value: v }),
    setListFilter: (patch) => dispatch({ type: 'SET_LIST_FILTER', patch }),
    openModal: (modal) => dispatch({ type: 'OPEN_MODAL', modal }),
    closeModal: () => dispatch({ type: 'CLOSE_MODAL' }),
    toggleBudgetPanel: () => dispatch({ type: 'TOGGLE_BUDGET_PANEL' }),
    closeBudgetPanel: () => dispatch({ type: 'CLOSE_BUDGET_PANEL' }),
    pushToast: (toast) => {
      const id = Date.now() + Math.random();
      dispatch({ type: 'PUSH_TOAST', toast: { ...toast, id } });
      if (toast.autoDismiss !== false) {
        setTimeout(() => dispatch({ type: 'DISMISS_TOAST', id }), toast.duration || 5000);
      }
    },
    dismissToast: (id) => dispatch({ type: 'DISMISS_TOAST', id }),
    setWizardDraft: (patch) => dispatch({ type: 'SET_WIZARD_DRAFT', patch }),
    clearWizard: () => dispatch({ type: 'CLEAR_WIZARD' }),
    updateSession: (id, patch) => dispatch({ type: 'UPDATE_SESSION', id, patch }),
    updateStrategy: (sessionId, patch) => {
      dispatch({ type: 'UPDATE_STRATEGY', sessionId, patch });
    },
    updateVariant: (sessionId, variantId, patch) => dispatch({ type: 'UPDATE_VARIANT', sessionId, variantId, patch }),
    updateAsset: (sessionId, assetId, patch) => dispatch({ type: 'UPDATE_ASSET', sessionId, assetId, patch }),
    removeAssets: (sessionId, ids) => dispatch({ type: 'REMOVE_ASSETS', sessionId, ids }),
    addAssets: (sessionId, assets) => dispatch({ type: 'ADD_ASSETS', sessionId, assets }),
    updateHypothesis: (sessionId, id, patch) => dispatch({ type: 'UPDATE_HYPOTHESIS', sessionId, id, patch }),
    addHypothesis: (sessionId, hyp) => dispatch({ type: 'ADD_HYPOTHESIS', sessionId, hyp }),
    removeHypothesis: (sessionId, id) => dispatch({ type: 'REMOVE_HYPOTHESIS', sessionId, id }),
    reorderHypotheses: (sessionId, ids) => dispatch({ type: 'REORDER_HYPOTHESES', sessionId, ids }),
    addChat: (sessionId, stage, msg) => dispatch({ type: 'ADD_CHAT', sessionId, stage, msg }),
    clearChat: (sessionId, stage) => dispatch({ type: 'CLEAR_CHAT', sessionId, stage }),
  }), []);

  const value = React.useMemo(() => ({ state, actions }), [state, actions]);
  return <StoreContext.Provider value={value}>{children}</StoreContext.Provider>;
}

window.StoreContext = StoreContext;
window.StoreProvider = StoreProvider;
window.useStore = useStore;
