// data.jsx — RGS — Rook's Game Shop catalog + collection / community data (v2.0.0)
// NOTE: set names, card names + art are original/fictional, not real TCG trademarks.

/* ───────────── Brand ─────────────
   One spelling, one place. "Rook's Game Shop" is the trading name; "RGS" is the
   short mark for tight spaces (order IDs, ops tables). See rgs/specs/SPEC-003.
*/
const BRAND = {
  name:  "Rook's Game Shop",
  short: 'RGS',
  possessive: "Rook's",
  tagline: 'Fair packs, no scalping',
  place: 'Fort Leavenworth',
  orderPrefix: 'RGS',
};

/* ───────────── Categories ─────────────
   The shop is a game shop. Pokémon is the first shelf — it is the only one
   trading today, and everything below it is sequencing, not decoration. Order
   in this array IS the order on the storefront, so Pokémon stays index 0.

   Names of other games are used descriptively, to say what the shop intends to
   stock. Nothing in the demo catalog is licensed product: SETS below are
   original fictional boosters (see the note at the top of this file), and the
   real-card index in data-cards.jsx is Pokémon only.
*/
const CATEGORIES = [
  { id: 'pokemon',  name: 'Pokémon TCG',  short: 'Pokémon',  icon: 'spark',  status: 'live',
    blurb: 'Packs, singles, and collection buys. The shelf the shop opened on.' },
  { id: 'lorcana',  name: 'Lorcana',      short: 'Lorcana',  icon: 'star',   status: 'next',
    blurb: 'Second shelf. Same fair-markup pricing, same delivery days.' },
  { id: 'onepiece', name: 'One Piece TCG',short: 'One Piece',icon: 'wave',   status: 'planned',
    blurb: 'Demand on post is there; sourcing is not settled yet.' },
  { id: 'magic',    name: 'Magic',        short: 'Magic',    icon: 'bolt',   status: 'planned',
    blurb: 'Singles-first — the collection-buy pipeline handles it well.' },
  { id: 'supplies', name: 'Sleeves & supplies', short: 'Supplies', icon: 'shield', status: 'planned',
    blurb: 'Sleeves, top-loaders, binders. Attaches to every other shelf.' },
  { id: 'board',    name: 'Board games',  short: 'Board games', icon: 'layers', status: 'planned',
    blurb: 'Family night on post. Furthest out, biggest shelf.' },
];
const LEAD_CATEGORY = CATEGORIES[0];
const categoryById = (id) => CATEGORIES.find(c => c.id === id);
// Everything sellable today is Pokémon; the rest is roadmap the storefront shows
// honestly rather than hiding.
const liveCategories = () => CATEGORIES.filter(c => c.status === 'live');

const SETS = [
  {
    id: 'ember',  name: 'Ember Vanguard', era: 'Current', icon: 'flame',
    c1: '#E0552F', c2: '#7A1E12', accent: '#FFD24A',
    msrp: 4.49, stock: 38, hype: 'chase',
    blurb: 'The flagship fire set. Big pulls, bigger demand on post.',
    chase: 'Scorchwing ex',
  },
  {
    id: 'tide',   name: 'Tidecaller', era: 'Current', icon: 'wave',
    c1: '#2E8DB0', c2: '#123B57', accent: '#9BE3D6',
    msrp: 4.49, stock: 52, hype: 'hot',
    blurb: 'Deep-blue holos and a friendly entry price.',
    chase: 'Leviath Prime',
  },
  {
    id: 'verdant',name: 'Verdant Reign', era: 'Current', icon: 'leaf',
    c1: '#3E9B53', c2: '#1C4A26', accent: '#E4F08A',
    msrp: 4.49, stock: 61, hype: null,
    blurb: 'Steady seller. Great for first-time openers.',
    chase: 'Thornmother',
  },
  {
    id: 'astral', name: 'Astral Circuit', era: 'Current', icon: 'bolt',
    c1: '#7A5CD0', c2: '#2C1C57', accent: '#C9B8FF',
    msrp: 4.99, stock: 14, hype: 'low',
    blurb: 'Premium electric set — runs hot, restocks slow.',
    chase: 'Voltcrown ex',
  },
  {
    id: 'frost',  name: 'Frostforge', era: 'Last gen', icon: 'snow',
    c1: '#4F7DC4', c2: '#1B2E52', accent: '#CFE6FF',
    msrp: 3.99, stock: 44, hype: null,
    blurb: 'Prior-gen favorite. Lower price, still pulls great.',
    chase: 'Glacewyrm',
  },
  {
    id: 'dust',   name: 'Dustline Relics', era: 'Throwback', icon: 'star',
    c1: '#B98A3E', c2: '#5A3D18', accent: '#F2D69B',
    msrp: 5.49, stock: 9, hype: 'low',
    blurb: 'Reprint of vintage commons. Nostalgia, limited run.',
    chase: 'Old-Frame Holo',
  },
];
const setById = (id) => SETS.find(s => s.id === id);

// Every booster in the catalog sits on the Pokémon shelf — the only one trading
// today. Tagging them means the storefront filters by category for real rather
// than assuming, so the second shelf is a data change, not a rewrite.
SETS.forEach(s => { s.cat = s.cat || LEAD_CATEGORY.id; });
const setsInCategory = (catId) => SETS.filter(s => s.cat === catId);

/* ───────────── Rarity system ───────────── */
const RARITY = {
  common:   { id: 'common',   label: 'Common',     stars: 1, color: '#8C8475', tint: '#CFC7B6' },
  uncommon: { id: 'uncommon', label: 'Uncommon',   stars: 2, color: '#3E9B53', tint: '#9FD8AA' },
  rare:     { id: 'rare',     label: 'Rare',       stars: 3, color: '#3A5BD9', tint: '#9DB2FF' },
  holo:     { id: 'holo',     label: 'Holo rare',  stars: 4, color: '#8A3FD1', tint: '#D2B4FF' },
  chase:    { id: 'chase',    label: 'Chase',      stars: 5, color: '#C2912F', tint: '#FFDD8A' },
};
const RARITY_ORDER = ['common', 'uncommon', 'rare', 'holo', 'chase'];

// 12 collectible cards per set. Index → rarity tier is fixed; index 11 is the chase.
const CARDS_PER_SET = 12;
const SPECIES_RARITY = ['common','common','common','common','common','common','uncommon','uncommon','uncommon','rare','holo','chase'];

const SPECIES = {
  ember:   ['Emberling','Cindermaw','Sootpaw','Coalback','Ashfin','Flintling','Flarewing','Pyrelisk','Ductdrake','Magmite','Vulcandle','Scorchwing ex'],
  tide:    ['Wavekin','Brineback','Mistgill','Driplet','Coralisk','Snapfin','Tidehorn','Frothel','Deepscale','Abyssel','Lumifin','Leviath Prime'],
  verdant: ['Sproutling','Mossback','Petalkin','Seedpip','Vinewhisk','Thornlet','Barkhorn','Fernox','Bloomare','Canopod','Lumibloom','Thornmother'],
  astral:  ['Staticat','Sparkfin','Coilbug','Fizzle','Arcwing','Joltite','Ozonelle','Plasmoth','Voltkit','Dynamo','Aurion','Voltcrown ex'],
  frost:   ['Frostkit','Rimehorn','Chillet','Sleetpip','Snowback','Icelisk','Sleetwing','Glazel','Hailpup','Permafox','Aurorae','Glacewyrm'],
  dust:    ['Relickin','Dustmite','Claypaw','Pebblet','Sandwyrm','Fossilback','Oldbloom','Runeshell','Gravelle','Antiqua','Mirage','Old-Frame Holo'],
};

function cardRarity(idx) { return SPECIES_RARITY[idx]; }
function cardKey(setId, idx) { return setId + '-' + idx; }
function makeCard(setId, idx) {
  const set = setById(setId);
  const r = cardRarity(idx);
  // deterministic HP-style stat for flavor
  const hp = 40 + idx * 8 + RARITY[r].stars * 20;
  return { setId, idx, name: SPECIES[setId][idx], rarity: r, hp, set };
}
// all 12 cards of a set
function setCards(setId) { return Array.from({ length: CARDS_PER_SET }, (_, i) => makeCard(setId, i)); }

/* ───────────── Bundle / pricing ───────────── */
const BUNDLES = [
  { packs: 1,  off: 0.00, label: 'Single' },
  { packs: 3,  off: 0.00, label: '3-pack' },
  { packs: 5,  off: 0.04, label: '5-pack',  tag: 'Save 4%' },
  { packs: 10, off: 0.08, label: '10-pack', tag: 'Best value · Save 8%' },
];

/* ───────────── Delivery ───────────── */
const DELIVERY_DAYS = [
  { id: 'tue', day: 'Tue', date: 'Jun 2',  full: 'Tuesday, June 2' },
  { id: 'thu', day: 'Thu', date: 'Jun 4',  full: 'Thursday, June 4' },
  { id: 'sat', day: 'Sat', date: 'Jun 6',  full: 'Saturday, June 6' },
];
const WINDOWS = ['4:00 – 4:40 PM', '4:40 – 5:20 PM', '5:20 – 6:00 PM'];

const NEIGHBORHOODS = [
  { id: 'normandy', name: 'Normandy Village', days: ['tue', 'sat'] },
  { id: 'hancock',  name: 'Hancock Heights',  days: ['thu', 'sat'] },
  { id: 'pope',     name: 'Pope Avenue',      days: ['tue', 'thu'] },
  { id: 'sherman',  name: 'Sherman Heights',  days: ['tue', 'sat'] },
  { id: 'otis',     name: 'Otis & Eisenhower',days: ['thu', 'sat'] },
  { id: 'mainpost', name: 'Main Post (pickup)',days: ['tue', 'thu', 'sat'] },
];

// A live delivery run for the "on the run" tracker (delivery day)
const ACTIVE_DELIVERY = {
  orderId: 'RPS-2406-39',
  day: 'Today · Sat, Jun 6',
  window: '4:00 – 4:40 PM',
  you: 'Normandy Village',
  packs: 5,
  stops: [
    { zone: 'Main Post', state: 'done' },
    { zone: 'Sherman Heights', state: 'done' },
    { zone: 'Normandy Village', state: 'active', eta: '4:14 PM', you: true },
    { zone: 'Otis & Eisenhower', state: 'next' },
    { zone: 'Hancock Heights', state: 'next' },
  ],
};

/* ───────────── Binder: Jordan's existing collection (unique idxs owned) ───────────── */
const SEED_OWNED = {
  ember:   [0, 1, 2, 3, 5, 6, 9, 10],
  tide:    [0, 1, 4, 6, 8],
  verdant: [0, 2, 3, 5, 6, 7, 9],
  astral:  [0, 4],
  frost:   [0, 1, 2, 3, 5, 6, 8, 9, 10, 11],
  dust:    [],
};

/* ───────────── Community: Traders' Wall ───────────── */
// Recent neighbor pulls (the feed)
const SEED_PULLS = [
  { id: 'p1', who: 'Maya R.',   zone: 'Hancock Heights',    setId: 'ember', idx: 11, when: '2h',  likes: 18, fire: true },
  { id: 'p2', who: 'Tariq B.',  zone: 'Pope Avenue',        setId: 'astral',idx: 10, when: '5h',  likes: 11 },
  { id: 'p3', who: 'The Okwus', zone: 'Normandy Village',   setId: 'tide',  idx: 11, when: '8h',  likes: 24, fire: true },
  { id: 'p4', who: 'S. Delgado',zone: 'Otis & Eisenhower',  setId: 'verdant',idx: 9, when: '1d',  likes: 6 },
  { id: 'p5', who: 'Briggs Fam',zone: 'Sherman Heights',    setId: 'frost', idx: 10, when: '1d',  likes: 9 },
];

// Local trade board — have/want
const SEED_TRADES = [
  { id: 't1', who: 'Dee K.',     zone: 'Normandy Village',  have: ['frost', 10], want: ['ember', 10],  when: '1h' },
  { id: 't2', who: 'PFC Alvarez',zone: 'Main Post',         have: ['verdant', 10], want: ['tide', 9],  when: '3h' },
  { id: 't3', who: 'Riley & Co', zone: 'Hancock Heights',   have: ['ember', 7],  want: ['astral', 10], when: '6h' },
];

// This week's leaderboard (packs ripped on post)
const LEADERBOARD = [
  { who: 'Maya R.',    zone: 'Hancock Heights',  packs: 24, hits: 5 },
  { who: 'The Okwus',  zone: 'Normandy Village',  packs: 19, hits: 4 },
  { who: 'Tariq B.',   zone: 'Pope Avenue',       packs: 16, hits: 3 },
  { who: 'You',        zone: 'Normandy Village',  packs: 11, hits: 2, you: true },
  { who: 'Briggs Fam', zone: 'Sherman Heights',   packs: 9,  hits: 2 },
];

/* ═══════════════════════════════════════════════════════════
   Ops — Rook's side of the counter. Desktop-only admin view.
   Order book for the open drop, plus the collection-buying
   pipeline from business-supply-chain.md (buy local, sell national).
═══════════════════════════════════════════════════════════ */

// Order book for the drop currently taking orders.
const OPS_ORDERS = [
  { id: 'RPS-2406-42', who: 'Maya R.',      zone: 'hancock',  day: 'thu', lines: { ember: 4, tide: 2 },            paid: true,  when: '18m' },
  { id: 'RPS-2406-41', who: 'Jordan P.',    zone: 'normandy', day: 'sat', lines: { tide: 5 },                      paid: true,  when: '1h'  },
  { id: 'RPS-2406-40', who: 'The Okwus',    zone: 'normandy', day: 'sat', lines: { ember: 3, verdant: 3, dust: 1 }, paid: true,  when: '3h'  },
  { id: 'RPS-2406-39', who: 'PFC Alvarez',  zone: 'mainpost', day: 'tue', lines: { frost: 10 },                    paid: false, when: '5h'  },
  { id: 'RPS-2406-38', who: 'Tariq B.',     zone: 'pope',     day: 'tue', lines: { astral: 2, ember: 2, dust: 5 }, paid: true,  when: '8h'  },
  { id: 'RPS-2406-37', who: 'Briggs Fam',   zone: 'sherman',  day: 'sat', lines: { verdant: 5, frost: 3, dust: 6 },paid: true,  when: '1d'  },
  { id: 'RPS-2406-36', who: 'Dee K.',       zone: 'normandy', day: 'sat', lines: { ember: 1, astral: 1 },          paid: false, when: '1d'  },
  { id: 'RPS-2406-35', who: 'S. Delgado',   zone: 'otis',     day: 'thu', lines: { tide: 3, verdant: 2 },          paid: true,  when: '2d'  },
];

function opsOrderPacks(o) { return Object.values(o.lines).reduce((a, b) => a + b, 0); }
function opsOrderTotal(o, markup) {
  return Object.entries(o.lines).reduce((s, [id, packs]) => s + packLine(setById(id), packs, markup).total, 0);
}

// Packs committed per set across the open order book — what Rook has to have in hand.
function opsPickList() {
  const by = {};
  OPS_ORDERS.forEach(o => Object.entries(o.lines).forEach(([id, n]) => { by[id] = (by[id] || 0) + n; }));
  return SETS.map(s => ({ set: s, need: by[s.id] || 0, stock: s.stock }))
             .filter(r => r.need > 0)
             .sort((a, b) => (b.need - b.stock) - (a.need - a.stock));
}

// Collection-buying pipeline. Values in dollars; hrs is estimated triage+listing time.
// Stages: lead → appraised → offered → bought
const OPS_PIPELINE = [
  { id: 'c1', who: 'Henderson',  zone: 'Normandy Village', stage: 'bought',    market: 365,  offer: 160, hrs: 5,  note: 'PCS Jul · vintage run + 2 sealed', pcs: 'Jul' },
  // c2 and c7 are the two lots the indexed singles came out of, so their market
  // value is the sum of those cards at NM-adjusted-for-condition, and the offer
  // is 44% of it — the buy discipline in business-supply-chain.md. Keep these
  // three numbers in step with data-cards.jsx if you edit either side.
  { id: 'c2', who: 'Vasquez',    zone: 'Pope Avenue',      stage: 'bought',    market: 2300, offer: 1012, hrs: 9, note: 'Binder of modern chase, no bulk', pcs: 'Jun' },
  { id: 'c7', who: 'Okonkwo',    zone: 'Main Post',        stage: 'bought',    market: 1110, offer: 488, hrs: 6,  note: 'Base Set run + Moonbreon', pcs: 'Jun' },
  { id: 'c3', who: 'Kowalski',   zone: 'Hancock Heights',  stage: 'appraised', market: 240,  offer: 105, hrs: 6,  note: 'Bulk-heavy — low priority', pcs: 'Aug' },
  { id: 'c4', who: 'Reyes',      zone: 'Sherman Heights',  stage: 'appraised', market: 1450, offer: 640, hrs: 12, note: 'Sealed boxes + slabs · best lead', pcs: 'Jun' },
  { id: 'c5', who: 'Ahn',        zone: 'Otis & Eisenhower',stage: 'lead',      market: 300,  offer: 130, hrs: 5,  note: 'Facebook reply, not yet seen', pcs: 'Jul' },
  { id: 'c6', who: 'Whitfield',  zone: 'Main Post',        stage: 'lead',      market: 520,  offer: 230, hrs: 7,  note: 'Retiring, wants one buyer', pcs: 'Sep' },
];
const PIPELINE_STAGES = [
  { id: 'lead',      label: 'Lead' },
  { id: 'appraised', label: 'Appraised' },
  { id: 'offered',   label: 'Offer out' },
  { id: 'bought',    label: 'Bought' },
];

// Working capital position — the constraint that actually binds (business-scenarios.md).
const OPS_CAPITAL = { float: 5000, committed: 1200, packsOnOrder: 1850, turnsPerYear: 2.5, targetRoc: 0.80 };

/* ───────────── Pricing helpers ───────────── */
function fairPrice(msrp, markup) { return msrp * (1 + markup); }
function bundlePackPrice(set, bundle, markup) { return fairPrice(set.msrp, markup) * (1 - bundle.off); }
function money(n) { return '$' + n.toFixed(2); }
// Whole dollars for ops figures — cents are noise on a $1,450 collection.
function money0(n) { return '$' + Math.round(n).toLocaleString('en-US'); }

Object.assign(window, {
  BRAND, CATEGORIES, LEAD_CATEGORY, categoryById, liveCategories, setsInCategory,
  SETS, setById, BUNDLES, DELIVERY_DAYS, WINDOWS, NEIGHBORHOODS,
  RARITY, RARITY_ORDER, CARDS_PER_SET, SPECIES_RARITY, SPECIES,
  cardRarity, cardKey, makeCard, setCards,
  ACTIVE_DELIVERY, SEED_OWNED, SEED_PULLS, SEED_TRADES, LEADERBOARD,
  fairPrice, bundlePackPrice, money, money0,
  OPS_ORDERS, OPS_PIPELINE, PIPELINE_STAGES, OPS_CAPITAL,
  opsOrderPacks, opsOrderTotal, opsPickList,
});
