// Shared mock data for the Studio portal prototype.
// Mirrors the real Simply Known D1 schema closely enough to feel
// like the production app, without requiring the worker to be live.

// Stage → tab mapping:
//   pending, approved  → Ideas
//   curating           → Curate
//   preview            → A/V
//   final              → Queue
//   published          → Publish

const TABS = [
  { key: 'ideas',   num: 1, label: 'Ideas',   color: '#2563eb' },
  { key: 'curate',  num: 2, label: 'Curate',  color: '#0d9488' },
  { key: 'sound',   num: 3, label: 'A/V',     color: '#7c3aed' },
  { key: 'final',   num: 4, label: 'Queue',   color: '#d97706' },
  { key: 'publish', num: 5, label: 'Publish', color: '#16a34a' },
];

const STAGE_TO_TAB = {
  pending:   'ideas',
  approved:  'ideas',
  curating:  'curate',
  preview:   'sound',
  final:     'final',
  published: 'publish',
};

// Source labels (from real worker)
const SOURCE_LABELS = {
  wiki_otd:      'Wikipedia OTD',
  wiki_random:   'Wikipedia Random',
  wiki_history:  'Wikipedia History',
  nasa_apod:     'NASA APOD',
  smithsonian:   'Smithsonian',
  noaa_extremes: 'NOAA Weather',
  story:         'Story Mode',
  war_death:     'History (war/death)',
};

// Voice catalog (subset of real 17-voice list, grouped).
// elevenId (2026-06-09): the ElevenLabs voice id — sample mp3s in R2 are keyed
// by it (voice_samples/{elevenId}.mp3), rows store the slug. Keep in sync with
// worker VOICE_CATALOG_GROUPED + video-bot/voice_picker.py.
const VOICES = [
  { id: 'knox_dark',    elevenId: 'dPah2VEoifKnZT37774q', name: 'Knox Dark',     category: 'dramatic',    accent: 'american', blurb: 'Serious, deep, methodical. Default for weight.' },
  { id: 'james',        elevenId: '1CgVOaiK0YikcFJJHWV0', name: 'James Freeman', category: 'dramatic',    accent: 'american', blurb: 'Deep, commanding, sage.' },
  { id: 'brian',        elevenId: 'nPczCjzI2devNBz1zQrb', name: 'Brian',         category: 'dramatic',    accent: 'american', blurb: 'Resonant, documentary-grade.' },
  { id: 'zeus_epic',    elevenId: 'jB108zg64sTcu1kCbN9L', name: 'Zeus Epic',     category: 'dramatic',    accent: 'american', blurb: 'Booming, cinematic trailer feel.' },
  { id: 'adam_stone',   elevenId: 'auq43ws1oslv0tO4BDa7', name: 'Adam Stone',    category: 'warm',        accent: 'british',  blurb: 'Smooth, velvety, late-night radio.' },
  { id: 'george',       elevenId: 'JBFqnCBsd6RMkjVDRZzb', name: 'George',        category: 'warm',        accent: 'british',  blurb: 'Warm resonance, instantly captivating.' },
  { id: 'victoria',     elevenId: 'qSeXEcewz7tA0Q0qk9fH', name: 'Victoria',      category: 'warm',        accent: 'american', blurb: 'Warm, trustworthy, conversational.' },
  { id: 'liam',         elevenId: 'TX3LPaxmHKxFdv7VOQHJ', name: 'Liam',          category: 'energetic',   accent: 'american', blurb: 'Young, warm, social. Built for reels.' },
  { id: 'jessica',      elevenId: 'cgSgspJ2msm6clMCkdW9', name: 'Jessica',       category: 'energetic',   accent: 'american', blurb: 'Playful, bright, trendy.' },
  { id: 'ash',          elevenId: 'VU16byTywsWv5JpI8rbc', name: 'Ash',           category: 'mysterious',  accent: 'american', blurb: 'Calm, magnetic. Composed.' },
  { id: 'callum',       elevenId: 'N2lVS1w4EtoT3dr4eOWO', name: 'Callum',        category: 'mysterious',  accent: 'american', blurb: 'Husky, gravelly, slight edge.' },
  { id: 'jane',         elevenId: 'NFFZBoF6tNodi008z7VH', name: 'Jane',          category: 'educational', accent: 'british',  blurb: 'Articulate, neutral RP.' },
  { id: 'matilda',      elevenId: 'XrExE9yKIg1WjnnlVkGX', name: 'Matilda',       category: 'educational', accent: 'american', blurb: 'Professional, pleasing alto.' },
];

const VOICE_CATEGORIES = [
  { key: 'dramatic',    label: '🎭 Dramatic' },
  { key: 'warm',        label: '🛋 Warm' },
  { key: 'energetic',   label: '⚡ Energetic' },
  { key: 'mysterious',  label: '🌒 Mysterious' },
  { key: 'educational', label: '📚 Educational' },
];

// Keyframe engines available
const KEYFRAME_ENGINES = [
  { key: 'wikimedia', label: 'Wikimedia',    cost: 0.000, blurb: 'Free real photos via Wikimedia Commons.' },
  { key: 'photon',    label: 'Photon Flash', cost: 0.026, blurb: 'Luma Photon Flash — fast, cheap, painterly.' },
  { key: 'imagen',    label: 'Imagen Fast',  cost: 0.04,  blurb: 'Google Imagen 4 Fast — high realism.' },
  { key: 'runway_ref',label: 'Runway Ref',   cost: 0.05,  blurb: 'Runway Gen-4 Image — best at people.' },
  { key: 'veo_still', label: 'Veo Stills',   cost: 0.06,  blurb: 'Google Veo 3 Stills — cinematic.' },
];

const ANIMATION_STYLES = [
  { value: '',                   label: '(source default)' },
  { value: 'ken_burns_local',    label: 'Ken Burns (local)' },
  { value: 'runway_gen4_turbo',  label: 'Runway Gen-4 Turbo' },
  { value: 'luma_ray_flash_2',   label: 'Luma Ray Flash 2' },
  { value: 'veo_3.1',            label: 'Veo 3.1' },
];

// Helper: build a 12-scene candidate map for a curating idea — FALLBACK ONLY.
//
// This is the mock/offline generator. The REAL curate path is owned by
// sk-data.js rowToIdea, which builds idea.scenes from the worker manifest.
// makeScenes is only called when there is no real manifest (mock mode, or a
// row with no build_progress_json / keyframe_curation_json yet).
//
// Output shape MATCHES the shared idea.scenes contract so WBCurateBody can
// consume mock + real interchangeably:
//   scene = { idx:Number(0-based), key, label, text,
//             keyframePrompt, motionPrompt, prompt, note, pickedId,
//             candidates:[ { id, engine, engineIdx:0-based, idx, candIdx:1-based } ] }
// (`engine`/`idx`/`key`/`prompt`/`note` retained for current workbench.jsx
//  consumers; `engineIdx`/`candIdx`/`keyframePrompt`/`motionPrompt`/scene.idx
//  are the contract additions.)
function makeScenes(idea) {
  const engineList = idea.engines || [];
  const sceneCount = 12;
  const candPerScene = engineList[0]?.scene || 3;
  const scenes = [];

  // Build the per-engine candidate list for one scene.
  // `perEngineCount(eng)` → how many candidates that engine contributes.
  // candIdx is 1-based PER ENGINE (matches the worker's
  // {scene}_eng{engineIdx}_cand_{candIdx}.jpg numbering).
  function buildCandidates(scenePrefix, perEngineCount) {
    return engineList.flatMap((e, engineIdx) => {
      const n = perEngineCount(e);
      return Array.from({ length: n }, (_, i) => ({
        id: `${scenePrefix}_${e.key}_${i}`,
        engine: e.key,
        engineIdx,            // 0-based, contract
        idx: i,               // 0-based per-engine (legacy seed/placeholder use)
        candIdx: i + 1,       // 1-based, contract (matches still filename)
      }));
    });
  }

  // Title scene first (idx 0 in the grid, but keyed 'title').
  scenes.push({
    idx: 0,
    key: 'title',
    label: 'Title card',
    text: idea.title,
    keyframePrompt: '',
    motionPrompt: '',
    candidates: buildCandidates('title', e => e.title || 1),
    pickedId: null,
    prompt: '',
    note: '',
  });

  // Body scenes (idx 0-based across the body sequence, matching the worker's
  // numeric scene index used in still URLs).
  for (let s = 0; s < sceneCount; s++) {
    scenes.push({
      idx: s,
      key: `scene_${s}`,
      label: `Scene ${s + 1}`,
      text: idea.sceneTexts?.[s] || '',
      keyframePrompt: idea.sceneTexts?.[s] || '',
      motionPrompt: '',
      candidates: buildCandidates(`s${s}`, () => candPerScene),
      pickedId: null,
      prompt: '',
      note: '',
    });
  }
  return scenes;
}

const INITIAL_IDEAS = [
  {
    id: 'idea_seed_1777650167_c7f984',
    slug: 'c7f984',
    title: 'Caesar crosses the Rubicon',
    subtitle: '49 BC — a frozen river that started a civil war',
    transcript: 'In the dead of winter, 49 BC, one man stood at the edge of a small river in northern Italy. Crossing it meant treason. Standing still meant exile. Julius Caesar paused — then said five Latin words that would echo for two thousand years. Alea iacta est. The die is cast. With one step, Rome plunged into civil war, the Republic began to die, and the modern phrase "crossing the Rubicon" was born.',
    source: 'wiki_otd',
    createdAt: 1715500000,
    voiceId: 'knox_dark',
    durationSec: 60,
    engines: [
      { key: 'wikimedia', label: 'Wikimedia',    title: 2, scene: 3 },
      { key: 'photon',    label: 'Photon Flash', title: 1, scene: 1 },
    ],
    animationStyle: 'ken_burns_local',
    regenNotes: '',
    stage: 'pending',
    statusNote: null,
    sceneTexts: [
      'A snowy riverbank in northern Italy',
      'Roman legion banners snap in the wind',
      'Caesar on horseback, breath visible',
      'A frozen river, narrow and unassuming',
      'Senators in Rome receive the news',
      'A coin flips midair',
      'Legions wade across the water',
      'Pompey hears the rumor in Rome',
      'A map of Italy with the river highlighted',
      'A signet ring pressed into wax',
      'A bust of Caesar in shadow',
      'The Republic\'s last torch',
    ],
  },
  {
    id: 'idea_seed_1777650163_d75302',
    slug: 'd75302',
    title: 'Krakatoa, 1883',
    subtitle: 'The loudest sound ever measured by humans',
    transcript: 'On August 27, 1883, the island of Krakatoa in the Sunda Strait exploded with the force of two hundred megatons. The blast was heard 3,000 miles away. Barometers as far as Europe registered the shockwave four times as it circled the planet. Sailors fifty miles out were deafened on the spot. By any honest measure, it remains the loudest sound a human has ever survived.',
    source: 'wiki_history',
    createdAt: 1715430000,
    voiceId: 'james',
    durationSec: 60,
    engines: [
      { key: 'photon', label: 'Photon Flash', title: 3, scene: 3 },
      { key: 'runway_ref', label: 'Runway Ref', title: 1, scene: 1 },
    ],
    animationStyle: 'luma_ray_flash_2',
    regenNotes: 'lead with the decibel number',
    stage: 'curating',
    statusNote: 'keyframes ready · pick a winner',
    sceneTexts: [
      'Krakatoa island before the eruption',
      'Sunda Strait map with location pin',
      'Ship at sea, calm horizon',
      'First ash cloud rising',
      'Massive volcanic explosion',
      'Sound wave radiating out',
      'Barometer needle violently swinging',
      'Sailor covering ears, deafened',
      'Ash sky over distant city',
      'Tidal wave hitting coast',
      'Krakatoa crater left behind',
      'Map showing 3000 mile reach',
    ],
    scenes: null, // generated lazily
  },
  {
    id: 'idea_seed_1777650168_60f333',
    slug: '60f333',
    title: 'The Hope Diamond',
    subtitle: 'A blue stone, a string of dead owners',
    transcript: 'In 1668 a French jeweler returned from India with a 112-carat blue diamond. The stone was sold to Louis XIV. Then to a Dutchman who was murdered. Then to a Russian prince whose mistress was strangled. Then to Marie Antoinette, beheaded. Then to a banker whose son committed suicide. By the time the Smithsonian put it behind glass in 1958, the Hope Diamond had earned every syllable of its curse.',
    source: 'smithsonian',
    createdAt: 1715350000,
    voiceId: 'adam_stone',
    durationSec: 60,
    engines: [
      { key: 'photon', label: 'Photon Flash', title: 1, scene: 1 },
    ],
    animationStyle: 'ken_burns_local',
    regenNotes: '',
    stage: 'preview',
    statusNote: 'preview render ready',
    pickedKeyframe: 'scene_2',
    mix: {
      master: 1.10, voice: 1.15, bg: 0.50, sfx: 0.35,
      bgPrompt: 'sparse low piano with distant violin, mysterious, slow',
      bgTone: 'mysterious',
      sfxCues: [
        { i: 0, prompt: 'gentle gemstone clink, soft echo',         start: 1.4,  duration: 2.8, volume: 0.32 },
        { i: 1, prompt: 'french courtroom door slam, heavy wood',   start: 12.0, duration: 1.4, volume: 0.46 },
        { i: 2, prompt: 'distant gunshot, muffled',                 start: 24.8, duration: 0.8, volume: 0.38 },
        { i: 3, prompt: 'glass display case slide, museum',         start: 49.2, duration: 2.0, volume: 0.28 },
      ],
    },
  },
  {
    id: 'idea_seed_1777650162_e1f102',
    slug: 'e1f102',
    title: 'The Galveston Hurricane',
    subtitle: 'September 8, 1900 — America\'s deadliest disaster',
    transcript: 'On September 8, 1900, a hurricane no one in Galveston knew was coming made landfall in the dark. Within six hours the entire island was underwater. Between six and twelve thousand people died — more than every other hurricane in U.S. history combined. The Weather Bureau had ignored Cuban forecasters who saw it coming. After Galveston, they never did again.',
    source: 'noaa_extremes',
    createdAt: 1715260000,
    voiceId: 'brian',
    durationSec: 58,
    engines: [
      { key: 'runway_ref', label: 'Runway Ref', title: 1, scene: 1 },
    ],
    animationStyle: 'runway_gen4_turbo',
    regenNotes: '',
    stage: 'final',
    statusNote: 'full render complete · ready to publish',
    pickedKeyframe: 'scene_3',
    mix: { master: 1.10, voice: 1.15, bg: 0.50, sfx: 0.35 },
    hashtags: ['#history', '#hurricane', '#galveston', '#1900', '#shorts'],
    costTotal: 3.001,
  },
  {
    id: 'idea_seed_1777650160_88a431',
    slug: '88a431',
    title: 'Pompeii: the day the sky turned to ash',
    subtitle: 'August 24, AD 79 — eighteen hours',
    transcript: 'On the afternoon of August 24, AD 79, Mount Vesuvius blew the top of a mountain into the sky. Eighteen hours later, Pompeii had been buried under twenty feet of ash and pumice. The two thousand who stayed died exactly where they stood. They\'re still there. The plaster casts you see today were poured into the voids their bodies left behind.',
    source: 'wiki_history',
    createdAt: 1715170000,
    voiceId: 'zeus_epic',
    durationSec: 60,
    engines: [
      { key: 'luma_manual', label: 'Luma', title: 1, scene: 11 },
    ],
    animationStyle: 'luma_ray_flash_2',
    stage: 'published',
    statusNote: 'posted 2h ago',
    hashtags: ['#history', '#pompeii', '#vesuvius', '#79AD', '#shorts'],
    posted: {
      tiktok:  { views: 12400, likes: 1830, comments: 96, shares: 240, link: 'tiktok.com/@simplyknown/v/...' },
      youtube: { views: 4210,  likes: 412,  comments: 28, shares: 31,  link: 'youtube.com/shorts/...' },
    },
    postedAt: '2h ago',
    postedAtIso: '2026-05-12T07:11:00Z',
    costTotal: 2.881,
  },
];

const STATUS = {
  env: 'DEV',
  spendToday: 1.42,
  spendBudget: 8.00,
  deploySha: 'a858a1c',
  rendererSha: 'a858a1c',
  rendererOnline: true,
  tunnelUp: true,
  lastPoll: '14s ago',
};

function countsByTab(ideas) {
  const counts = { ideas: 0, curate: 0, sound: 0, final: 0, publish: 0 };
  ideas.forEach(i => {
    const t = STAGE_TO_TAB[i.stage];
    if (t) counts[t] = (counts[t] || 0) + 1;
  });
  return counts;
}

function voiceById(id) { return VOICES.find(v => v.id === id) || VOICES[0]; }
function sourceLabel(s) { return SOURCE_LABELS[s] || s || 'unknown'; }
function totalCost(idea) {
  return idea.engines.reduce((sum, e) => {
    const eng = KEYFRAME_ENGINES.find(k => k.key === e.key) || { cost: 0 };
    return sum + eng.cost * (e.title + e.scene);
  }, 0);
}

Object.assign(window, {
  TABS, STAGE_TO_TAB, SOURCE_LABELS, VOICES, VOICE_CATEGORIES, KEYFRAME_ENGINES,
  ANIMATION_STYLES, INITIAL_IDEAS, STATUS,
  countsByTab, voiceById, sourceLabel, totalCost, makeScenes,
});
