// viewbot.info — seed data v3 (preview / mock)
// ─────────────────────────────────────────────────────────────────────────────
// All channel handles below are PLACEHOLDERS in the form `case-NNN`.
// The product is in a public-preview state. Real telemetry, real handles, and
// real verdicts ship with the production launch — the mock data here is for
// design and methodology preview only. No statement is being made about any
// real streamer.
// ─────────────────────────────────────────────────────────────────────────────

const PLATFORMS = ['twitch', 'kick', 'rumble'];

const CATEGORIES = [
  'Just Chatting','IRL','Slots & Casino','Counter-Strike 2','League of Legends',
  'Grand Theft Auto V (GTA)','Dota 2','VALORANT','Fortnite','Music',
  'World of Warcraft','Special Events','Marbles on Stream','Pools, Hot Tubs & Beaches',
  'ASMR','Art','Travel & Outdoors','Sports','Apex Legends','Minecraft',
];

// ─── the 8 scores ───
// Every score is 0-100. Higher = healthier audience. Lower = more suspicious.
const SCORE_DEFS = [
  { idx: '01', key: 'velocity',  name: 'Viewer Velocity',     short: 'VEL',
    desc: 'How viewers arrive. Sudden geometric jumps with no raid, embed, or external push are unnatural — organic discovery is curve-shaped.',
    viz: 'velocity' },
  { idx: '02', key: 'rigidity',  name: 'Audience Rigidity',   short: 'RGD',
    desc: 'Real viewer counts breathe. They drift, dip, and respond. A hard floor that returns to the same band across every stream is mechanical.',
    viz: 'rigidity' },
  { idx: '03', key: 'category',  name: 'Category Elasticity', short: 'CAT',
    desc: 'When a category surges or dies, the streamers inside move with it. Channels that ignore the tide are detached from the ecosystem.',
    viz: 'category' },
  { idx: '04', key: 'game',      name: 'Game Elasticity',     short: 'GAM',
    desc: 'Audiences shift when the game shifts. Identical CCV across a main game, a dead category, and a sponsored title is a fingerprint.',
    viz: 'game' },
  { idx: '05', key: 'engagement',name: 'Engagement Coupling', short: 'ENG',
    desc: 'Chat velocity, unique chatters, follows, clips, subs — they should move with viewers. Decoupling is the most legible synthetic signal.',
    viz: 'engagement' },
  { idx: '06', key: 'rank',      name: 'Rank Targeting',      short: 'RNK',
    desc: 'Buying just-enough viewers to hold a category placement. Look for boosts that arrive when rank is about to slip and vanish at stream end.',
    viz: 'rank' },
  { idx: '07', key: 'deviation', name: 'Historical Deviation',short: 'HST',
    desc: 'Compared against this creator’s own past streams — by game, time, day, title, and length. A break from a personal baseline is harder to fake.',
    viz: 'deviation' },
  { idx: '08', key: 'repetition',name: 'Repetition',          short: 'REP',
    desc: 'One anomaly is a story. Twenty is a pattern. The same fingerprint reappearing across weeks is the difference between a bad night and a paid floor.',
    viz: 'repetition' },
];

// ─── live feed: mock streams currently failing the model ───
const LIVE_FEED = [
  { rank: 1,  user: 'case-001', plat: 'kick',   cat: 'Just Chatting',           viewers: 52627, chatters: 3389, eng: 6.4, score: 28 },
  { rank: 2,  user: 'case-002', plat: 'kick',   cat: 'IRL',                     viewers: 39240, chatters: 750,  eng: 1.9, score: 12 },
  { rank: 3,  user: 'case-003', plat: 'twitch', cat: 'Dota 2',                  viewers: 18351, chatters: 865,  eng: 4.7, score: 31 },
  { rank: 4,  user: 'case-004', plat: 'twitch', cat: 'IRL',                     viewers: 14460, chatters: 495,  eng: 3.4, score: 22 },
  { rank: 5,  user: 'case-005', plat: 'twitch', cat: 'IRL',                     viewers: 10458, chatters: 877,  eng: 8.4, score: 41 },
  { rank: 6,  user: 'case-006', plat: 'kick',   cat: 'Slots & Casino',          viewers: 5203,  chatters: 283,  eng: 5.4, score: 33 },
  { rank: 7,  user: 'case-007', plat: 'kick',   cat: 'League of Legends',       viewers: 4196,  chatters: 401,  eng: 9.6, score: 47 },
  { rank: 8,  user: 'case-008', plat: 'kick',   cat: 'Grand Theft Auto V (GTA)',viewers: 4071,  chatters: 110,  eng: 2.7, score: 18 },
  { rank: 9,  user: 'case-009', plat: 'kick',   cat: 'Counter-Strike 2',        viewers: 4039,  chatters: 125,  eng: 3.1, score: 21 },
  { rank: 10, user: 'case-010', plat: 'kick',   cat: 'Counter-Strike 2',        viewers: 3735,  chatters: 120,  eng: 3.2, score: 23 },
  { rank: 11, user: 'case-011', plat: 'twitch', cat: 'Just Chatting',           viewers: 3601,  chatters: 220,  eng: 6.1, score: 35 },
  { rank: 12, user: 'case-012', plat: 'kick',   cat: 'Slots & Casino',          viewers: 3290,  chatters: 170,  eng: 5.2, score: 29 },
  { rank: 13, user: 'case-013', plat: 'twitch', cat: 'VALORANT',                viewers: 2980,  chatters: 88,   eng: 2.9, score: 17 },
  { rank: 14, user: 'case-014', plat: 'kick',   cat: 'IRL',                     viewers: 2740,  chatters: 95,   eng: 3.5, score: 24 },
  { rank: 15, user: 'case-015', plat: 'rumble', cat: 'Just Chatting',           viewers: 2614,  chatters: 60,   eng: 2.3, score: 14 },
  { rank: 16, user: 'case-016', plat: 'twitch', cat: 'Fortnite',                viewers: 2402,  chatters: 132,  eng: 5.5, score: 32 },
  { rank: 17, user: 'case-017', plat: 'kick',   cat: 'Slots & Casino',          viewers: 2335,  chatters: 70,   eng: 3.0, score: 19 },
  { rank: 18, user: 'case-018', plat: 'twitch', cat: 'Pools, Hot Tubs & Beaches',viewers: 2210, chatters: 192,  eng: 8.7, score: 44 },
];

// ─── streamer dossiers (mock) ───
const STATUSES = ['botted','legit','review','reformed'];

function avatarColor(seed) {
  const palettes = [
    ['#ff7a59','#ffb259'], ['#9aef4b','#4ab06b'], ['#a070ff','#5a4aff'],
    ['#5aa3ff','#3c6dff'], ['#ffd84d','#ff8c4a'], ['#ff5a8a','#ff5a5f'],
    ['#4adfd0','#3c8eaf'], ['#e0e3e6','#8c9498'], ['#ff5a5f','#7a2c2e'],
    ['#85c742','#3a6e1f'],
  ];
  return palettes[seed % palettes.length];
}

const STREAMER_NAMES = [
  'case-019','case-020','case-021','case-022','case-023',
  'case-024','case-025','case-026','case-027','case-028',
  'case-029','case-030','case-031','case-032','case-033',
  'case-034','case-035','case-036','case-037','case-038',
  'case-039','case-040','case-041','case-042','case-043',
  'case-044','case-045','case-046','case-047','case-048',
  'case-049','case-050',
];

const STREAMER_STATUS = {
  'case-019': 'botted',  'case-020': 'botted',  'case-021': 'legit',   'case-022': 'review',  'case-023': 'botted',
  'case-024': 'legit',   'case-025': 'review',  'case-026': 'botted',  'case-027': 'botted',  'case-028': 'review',
  'case-029': 'legit',   'case-030': 'botted',  'case-031': 'legit',   'case-032': 'legit',   'case-033': 'legit',
  'case-034': 'review',  'case-035': 'botted',  'case-036': 'legit',   'case-037': 'botted',  'case-038': 'legit',
  'case-039': 'legit',   'case-040': 'legit',   'case-041': 'botted',  'case-042': 'botted',  'case-043': 'botted',
  'case-044': 'botted',  'case-045': 'legit',   'case-046': 'botted',  'case-047': 'legit',   'case-048': 'legit',
  'case-049': 'legit',   'case-050': 'legit',
};

const STREAMER_PLAT = {
  'case-019':'kick',  'case-020':'kick',  'case-021':'twitch','case-022':'kick',  'case-023':'kick',
  'case-024':'twitch','case-025':'twitch','case-026':'kick',  'case-027':'kick',  'case-028':'kick',
  'case-029':'twitch','case-030':'twitch','case-031':'twitch','case-032':'twitch','case-033':'twitch',
  'case-034':'twitch','case-035':'kick',  'case-036':'twitch','case-037':'kick',  'case-038':'twitch',
  'case-039':'twitch','case-040':'twitch','case-041':'kick',  'case-042':'twitch','case-043':'kick',
  'case-044':'kick',  'case-045':'twitch','case-046':'kick',  'case-047':'twitch','case-048':'twitch',
  'case-049':'twitch','case-050':'twitch',
};

function rng(seed){ let s = seed; return ()=>{ s = (s*1103515245 + 12345) & 0x7fffffff; return s/0x7fffffff; }; }

// generate 8 scores for one streamer
function genScores(name, status) {
  const r = rng(name.charCodeAt(0)*31 + name.charCodeAt(name.length-1));
  const base = status === 'botted'   ? 16 :
               status === 'review'   ? 46 :
               status === 'reformed' ? 64 : 82;
  const out = {};
  for (const def of SCORE_DEFS) {
    const swing = (r() - 0.5) * 22;
    let v = Math.max(2, Math.min(99, Math.round(base + swing)));
    out[def.key] = v;
  }
  return out;
}

function streamerStats(name, i) {
  const r = rng(name.charCodeAt(0)*31 + name.charCodeAt(name.length-1) + i*7);
  const status = STREAMER_STATUS[name];
  const plat = STREAMER_PLAT[name];
  let viewers, eng, score;
  if (status === 'botted') {
    viewers = Math.floor(800 + r()*9000);
    eng = +(0.6 + r()*4.2).toFixed(1);
    score = Math.floor(8 + r()*28);
  } else if (status === 'review') {
    viewers = Math.floor(400 + r()*4500);
    eng = +(4.0 + r()*4.0).toFixed(1);
    score = Math.floor(40 + r()*18);
  } else {
    viewers = Math.floor(200 + r()*5500);
    eng = +(8.0 + r()*22).toFixed(1);
    score = Math.floor(72 + r()*22);
  }
  const cat = CATEGORIES[Math.floor(r()*CATEGORIES.length)];
  const scores = genScores(name, status);
  return { viewers, eng, score, cat, plat, status, scores };
}

const DOSSIERS = STREAMER_NAMES.map((name, i) => ({
  name,
  avatar: avatarColor(name.length + i*3),
  ...streamerStats(name, i),
}));

// ─── ticker payload ───
const TICKER_ITEMS = [
  { l: 'preview build',    v: 'v0 · mock data', k: 'purple' },
  { l: 'scanned',          v: '14,238,901', k: '' },
  { l: 'flagged today',    v: '1,402',      k: 'red' },
  { l: 'cleared today',    v: '893',        k: 'green' },
  { l: 'under review',     v: '210',        k: 'yellow' },
  { l: 'platforms',        v: 'twitch · kick · rumble', k: '' },
  { l: 'sample interval',  v: '30s',        k: '' },
  { l: 'last poll',        v: '8s ago',     k: 'green' },
  { l: 'model',            v: 'v3.2.1-forensic', k: 'purple' },
  { l: 'avg eng (legit)',  v: '14.2%',      k: 'green' },
  { l: 'avg eng (botted)', v: '3.1%',       k: 'red' },
  { l: 'rigidity threshold', v: '0.92',     k: '' },
  { l: 'tip line',         v: 'tips@viewbot.info', k: '' },
];

// helpers
function engClass(e) {
  if (e < 4) return 'low';
  if (e < 7) return 'mid';
  return 'high';
}
function scoreClass(s) {
  if (s < 35) return 'low';
  if (s < 65) return 'mid';
  return 'high';
}
function scoreColor(s) {
  if (s < 35) return 'var(--red)';
  if (s < 65) return 'var(--yellow)';
  return 'var(--green)';
}

// expose
Object.assign(window, {
  PLATFORMS, CATEGORIES, LIVE_FEED, DOSSIERS, SCORE_DEFS, TICKER_ITEMS,
  STREAMER_NAMES, STREAMER_STATUS, STREAMER_PLAT,
  avatarColor, engClass, scoreClass, scoreColor, rng,
});
