// leaderboard-data.jsx — i18n strings + mock data for the Leaderboard module.
//
// Sprint v1.6 — "Leaderboard / Rank". Source-of-truth for everything the
// `leaderboard-pieces.jsx` primitives and `leaderboard-app.jsx` canvas
// render. Pure data + pure factories; no DOM, no React, no side effects.
//
// Conventions (locked):
//   • All strings live in LB_I18N[fa|en|ar]. No hard-coded strings in
//     pieces / app.
//   • All numbers go through `toLocaleDigits(num, locale)` at the render
//     layer (this file only ever returns raw numbers).
//   • Tier accents reuse the canonical token palette from `tokens.css`
//     (--tier-gold / --tier-platinum / --tier-silver / --tier-bronze /
//     --accent-primary) — no new CSS variables introduced.
//   • Names are bi-lingual triplets ({ fa, en, ar }) so a locale switch
//     in the Tweaks panel re-localizes every row.
//
// Shape exported on window:
//   LB_I18N             — { fa, en, ar } string bundle (master keys)
//   LB_USER             — mock current-user record (rank/score/delta/
//                         percentile/nextTierRank/history)
//   LB_LEAGUES          — 4 league chips (overall / weekly / friends / region)
//   LB_ENTRIES          — ~50 mock users with realistic score distribution,
//                         badges, fan-team, isFriend, isYou markers
//   LB_REWARD_TIERS     — top-N reward bands (podium / top10 / top100 /
//                         top1000) with coin + pack rewards
//   LB_BADGES           — badge id ↔ locale label + glyph token
//   LB_SPONSOR          — optional this-week sponsor for the leaderboard
//                         (reuses the SponsorBox primitive from shop-pieces).
//   getUserRank(entries, userId)         → number | null
//   getRankDelta(curRank, prevRank)      → number   (positive = climbed)
//   filterEntries(entries, leagueKey)    → array
//   getTierForRank(rank, tiers?)         → tier object | null
//   getProgressToNextTier(rank, tiers?)  → 0..1
//   resolveAvatarPalette(seed)           → { from, to, initial }


// ─── i18n ──────────────────────────────────────────────────────────────────
const LB_I18N = {
  fa: {
    // Chrome
    pageTitle:        'رتبه‌بندی',
    pageSub:          'موقعیتت بین همه‌ی بازیکن‌ها',
    eyebrow:          'هفته ۲۸',

    // User-rank hero
    yourRank:         'رتبه‌ی شما',
    yourScore:        'امتیاز شما',
    rankNumber:       (n) => `#${n}`,                            // “#247”
    topPercentile:    (p) => `جزو ${p}٪ برتر`,
    nextTier:         (rank) => `${rank} رتبه تا پاداش بعدی`,
    nextTierAt:       (rank) => `پاداش بعدی در رتبه ${rank}`,
    deltaSinceWeek:   'تغییر نسبت به هفته قبل',
    podiumYouOn:      'تو روی سکویی!',
    podiumYouAt:      (n) => `تو در رتبه ${n} هستی`,

    // League filter (chips)
    leagueOverall:    'کل',
    leagueWeekly:     'هفتگی',
    leagueFriends:    'دوستان',
    leagueRegion:     'منطقه',
    leagueRegionSub:  'تهران',

    // Section heads
    secHero:          'رتبه‌ی شما',
    secList:          'جدول رتبه‌بندی',
    secListSub:       (n) => `${n} بازیکن این هفته`,
    secRewards:       'پاداش‌های هفته',
    secRewardsSub:    'بسته به رتبه نهایی',
    secHistory:       'هفته‌های اخیر',
    secHistorySub:    'سه هفته آخر',
    secSponsor:       'حمایت‌کننده',

    // Row labels
    colRank:          'رتبه',
    colPlayer:        'بازیکن',
    colScore:         'امتیاز',
    youLabel:         'شما',
    fanTeamLabel:     (team) => `هوادار ${team}`,

    // Delta indicators
    deltaUp:          (n) => `+${n}`,
    deltaDown:        (n) => `−${n}`,
    deltaFlat:        'بدون تغییر',
    deltaNew:         'تازه‌وارد',

    // Reward tier strip
    rewardTier:       (range) => `رتبه ${range}`,
    rewardRange:      (a, b) => a === b ? `${a}` : `${a}-${b}`,
    rewardCoins:      (n) => `${n} سکه`,
    rewardPack:       'پک ویژه',
    rewardPackNamed:  (name) => `پک ${name}`,
    rewardYourTier:   'پاداش شما',
    rewardLockedFor:  (rank) => `قفل تا رتبه ${rank}`,

    // Tier names (top-N labels)
    tierPodium:       'سکو',
    tierTop10:        'ده برتر',
    tierTop100:       'صد برتر',
    tierTop1000:      'هزار برتر',

    // History card
    historyTitle:     'تاریخچه',
    historyWeek:      (n) => `هفته ${n}`,
    historyRankShort: (n) => `#${n}`,
    historyNoData:    'هنوز هفته‌ای ثبت نشده',

    // Entry detail sheet (tap a row)
    sheetSubLast:     'آخرین فعالیت',
    sheetSubBest:     'بهترین رتبه',
    sheetSubStreak:   'روزهای پیاپی',
    sheetCloseAria:   'بستن',
    sheetTeamLabel:   'هوادار',
    sheetBadgesTitle: 'نشان‌ها',
    sheetFollowCta:   'دنبال کردن',
    sheetUnfollowCta: 'دنبال نکن',
    // Profile stats inside the sheet (added per v1.6 feedback)
    sheetProfileTitle:'آمار کلی',
    sheetXP:          'کل XP',
    sheetXPVal:       (n) => `${n} XP`,
    sheetAccuracy:    'دقت پیش‌بینی',
    sheetAccuracyVal: (pct) => `${pct}٪`,
    sheetAccuracySub: (n) => `از ${n} پیش‌بینی`,
    sheetLineups:     'ترکیب‌ها',
    sheetLineupsVal:  (n) => `${n} ترکیب`,
    sheetBestLineup:  'بهترین ترکیب',
    // Wireframe S37 (peer-profile popup) — sub-label uses relative time
    // ("X روز پیش") rather than the matchweek. Singular vs plural ignored
    // for fa (no morphological plural for "روز").
    bestLineupDaysAgo:(n) => `${n} روز پیش`,
    // Multiplier label inside the strip chips
    multClub:         'کلوپ',
    multFestival:     'ضریب جشنواره',

    // Empty / blocked states
    emptyTitle:       'هنوز بازی نکرده‌ای',
    emptyBody:        'برای ورود به جدول رتبه‌بندی، اولین ترکیبت رو ثبت کن.',
    emptyCta:         'ساخت ترکیب',
    emptySecondary:   'یادآوری هفته بعد',

    // Badges
    badgeStreak7:     'هفت روز پیاپی',
    badgeMythicPull:  'پک اسطوره‌ای',
    badgeTopScorer:   'گل‌زن هفته',
    badgeUndefeated:  'شکست‌ناپذیر',
    badgePerfectWk:   'هفته‌ی کامل',
    badgeRising:      'صعودکننده',

    // Sponsor / promo
    sponsorEyebrow:   'حمایت‌شده',

    // Generic
    coin:             'سکه',
    weekShort:        (n) => `ه ${n}`,
  },

  en: {
    pageTitle:        'Leaderboard',
    pageSub:          'Your standing across all players',
    eyebrow:          'Matchweek 28',

    yourRank:         'Your rank',
    yourScore:        'Your score',
    rankNumber:       (n) => `#${n}`,
    topPercentile:    (p) => `Top ${p}%`,
    nextTier:         (rank) => `${rank} ranks to next reward`,
    nextTierAt:       (rank) => `Next reward at rank ${rank}`,
    deltaSinceWeek:   'Change vs last week',
    podiumYouOn:      'You\'re on the podium!',
    podiumYouAt:      (n) => `You\'re ranked ${n}`,

    leagueOverall:    'Overall',
    leagueWeekly:     'Weekly',
    leagueFriends:    'Friends',
    leagueRegion:     'Region',
    leagueRegionSub:  'Tehran',

    secHero:          'Your rank',
    secList:          'Leaderboard',
    secListSub:       (n) => `${n} players this week`,
    secRewards:       'Weekly rewards',
    secRewardsSub:    'Based on your final rank',
    secHistory:       'Recent weeks',
    secHistorySub:    'Last 3 matchweeks',
    secSponsor:       'Sponsor',

    colRank:          'Rank',
    colPlayer:        'Player',
    colScore:         'Score',
    youLabel:         'You',
    fanTeamLabel:     (team) => `${team} fan`,

    deltaUp:          (n) => `+${n}`,
    deltaDown:        (n) => `−${n}`,
    deltaFlat:        'No change',
    deltaNew:         'New entry',

    rewardTier:       (range) => `Rank ${range}`,
    rewardRange:      (a, b) => a === b ? `${a}` : `${a}–${b}`,
    rewardCoins:      (n) => `${n} coins`,
    rewardPack:       'Special pack',
    rewardPackNamed:  (name) => `${name} pack`,
    rewardYourTier:   'Your tier',
    rewardLockedFor:  (rank) => `Unlocks at rank ${rank}`,

    tierPodium:       'Podium',
    tierTop10:        'Top 10',
    tierTop100:       'Top 100',
    tierTop1000:      'Top 1,000',

    historyTitle:     'History',
    historyWeek:      (n) => `Week ${n}`,
    historyRankShort: (n) => `#${n}`,
    historyNoData:    'No history yet',

    sheetSubLast:     'Last activity',
    sheetSubBest:     'Best rank',
    sheetSubStreak:   'Day streak',
    sheetCloseAria:   'Close',
    sheetTeamLabel:   'Supports',
    sheetBadgesTitle: 'Badges',
    sheetFollowCta:   'Follow',
    sheetUnfollowCta: 'Unfollow',
    sheetProfileTitle:'Player stats',
    sheetXP:          'Total XP',
    sheetXPVal:       (n) => `${n} XP`,
    sheetAccuracy:    'Predict accuracy',
    sheetAccuracyVal: (pct) => `${pct}%`,
    sheetAccuracySub: (n) => `of ${n} picks`,
    sheetLineups:     'Lineups',
    sheetLineupsVal:  (n) => `${n} submitted`,
    sheetBestLineup:  'Best lineup',
    bestLineupDaysAgo:(n) => `${n} days ago`,
    multClub:         'Club',
    multFestival:     'Festival',

    emptyTitle:       'You haven\'t played yet',
    emptyBody:        'Submit your first lineup to land on the leaderboard.',
    emptyCta:         'Build lineup',
    emptySecondary:   'Remind me next week',

    badgeStreak7:     '7-day streak',
    badgeMythicPull:  'Mythic pull',
    badgeTopScorer:   'Top scorer',
    badgeUndefeated:  'Undefeated',
    badgePerfectWk:   'Perfect week',
    badgeRising:      'Rising',

    sponsorEyebrow:   'Sponsored',

    coin:             'coin',
    weekShort:        (n) => `W${n}`,
  },

  ar: {
    pageTitle:        'لوحة الترتيب',
    pageSub:          'موقعك بين جميع اللاعبين',
    eyebrow:          'الأسبوع ٢٨',

    yourRank:         'ترتيبك',
    yourScore:        'نقاطك',
    rankNumber:       (n) => `#${n}`,
    topPercentile:    (p) => `ضمن أفضل ٪${p}`,
    nextTier:         (rank) => `${rank} مراكز للجائزة التالية`,
    nextTierAt:       (rank) => `الجائزة التالية عند المركز ${rank}`,
    deltaSinceWeek:   'التغيير مقارنة بالأسبوع الماضي',
    podiumYouOn:      'أنت على المنصة!',
    podiumYouAt:      (n) => `أنت في المركز ${n}`,

    leagueOverall:    'الكل',
    leagueWeekly:     'الأسبوعي',
    leagueFriends:    'الأصدقاء',
    leagueRegion:     'المنطقة',
    leagueRegionSub:  'طهران',

    secHero:          'ترتيبك',
    secList:          'لوحة الترتيب',
    secListSub:       (n) => `${n} لاعب هذا الأسبوع`,
    secRewards:       'جوائز الأسبوع',
    secRewardsSub:    'حسب ترتيبك النهائي',
    secHistory:       'الأسابيع الأخيرة',
    secHistorySub:    'آخر ٣ أسابيع',
    secSponsor:       'الراعي',

    colRank:          'المركز',
    colPlayer:        'اللاعب',
    colScore:         'النقاط',
    youLabel:         'أنت',
    fanTeamLabel:     (team) => `مشجع ${team}`,

    deltaUp:          (n) => `+${n}`,
    deltaDown:        (n) => `−${n}`,
    deltaFlat:        'بدون تغيير',
    deltaNew:         'مشارك جديد',

    rewardTier:       (range) => `المركز ${range}`,
    rewardRange:      (a, b) => a === b ? `${a}` : `${a}-${b}`,
    rewardCoins:      (n) => `${n} عملة`,
    rewardPack:       'حزمة مميزة',
    rewardPackNamed:  (name) => `حزمة ${name}`,
    rewardYourTier:   'فئتك',
    rewardLockedFor:  (rank) => `يفتح في المركز ${rank}`,

    tierPodium:       'المنصة',
    tierTop10:        'أفضل ١٠',
    tierTop100:       'أفضل ١٠٠',
    tierTop1000:      'أفضل ١٬٠٠٠',

    historyTitle:     'التاريخ',
    historyWeek:      (n) => `الأسبوع ${n}`,
    historyRankShort: (n) => `#${n}`,
    historyNoData:    'لا يوجد سجل بعد',

    sheetSubLast:     'آخر نشاط',
    sheetSubBest:     'أفضل ترتيب',
    sheetSubStreak:   'سلسلة الأيام',
    sheetCloseAria:   'إغلاق',
    sheetTeamLabel:   'يشجّع',
    sheetBadgesTitle: 'الشارات',
    sheetFollowCta:   'متابعة',
    sheetUnfollowCta: 'إلغاء المتابعة',
    sheetProfileTitle:'إحصائيات اللاعب',
    sheetXP:          'إجمالي XP',
    sheetXPVal:       (n) => `${n} XP`,
    sheetAccuracy:    'دقة التوقع',
    sheetAccuracyVal: (pct) => `٪${pct}`,
    sheetAccuracySub: (n) => `من ${n} توقع`,
    sheetLineups:     'التشكيلات',
    sheetLineupsVal:  (n) => `${n} تشكيلة`,
    sheetBestLineup:  'أفضل تشكيلة',
    bestLineupDaysAgo:(n) => `قبل ${n} أيام`,
    multClub:         'النادي',
    multFestival:     'معامل المهرجان',

    emptyTitle:       'لم تلعب بعد',
    emptyBody:        'سجّل أول تشكيلة لك للظهور في لوحة الترتيب.',
    emptyCta:         'إنشاء تشكيلة',
    emptySecondary:   'ذكّرني الأسبوع القادم',

    badgeStreak7:     'سلسلة ٧ أيام',
    badgeMythicPull:  'سحب خرافي',
    badgeTopScorer:   'هدّاف الأسبوع',
    badgeUndefeated:  'بلا هزيمة',
    badgePerfectWk:   'أسبوع مثالي',
    badgeRising:      'صاعد',

    sponsorEyebrow:   'مُموَّل',

    coin:             'عملة',
    weekShort:        (n) => `أ${n}`,
  },
};


// ─── Badge dictionary ──────────────────────────────────────────────────────
// `id`     — short id used in entry.badges
// `i18nKey`— key in LB_I18N[locale]
// `glyph`  — HHIcon name (canonical icon set in homehub-pieces)
// `accent` — CSS token used for the badge tint
const LB_BADGES = {
  'streak-7':     { i18nKey: 'badgeStreak7',    glyph: 'flame',    accent: 'var(--warning-2)' },
  'mythic-pull':  { i18nKey: 'badgeMythicPull', glyph: 'sparkle',  accent: 'var(--accent-primary)' },
  'top-scorer':   { i18nKey: 'badgeTopScorer',  glyph: 'trophy',   accent: 'var(--tier-gold)' },
  'undefeated':   { i18nKey: 'badgeUndefeated', glyph: 'check',    accent: 'var(--success)' },
  'perfect-week': { i18nKey: 'badgePerfectWk',  glyph: 'check',    accent: 'var(--info)' },
  'rising':       { i18nKey: 'badgeRising',     glyph: 'sparkle',  accent: 'var(--tier-platinum)' },
};


// ─── Leagues (filter chips) ────────────────────────────────────────────────
// `id`        — chip key (passed straight into <FilterChips chips={[{key…}]}/>)
// `i18nKey`   — label in LB_I18N[locale]
// `subI18nKey`— optional second-line label (Region shows "Tehran" etc.)
// `predicate` — entry → boolean. Used by `filterEntries()` to slice the
//               canonical LB_ENTRIES dataset; the array remains the single
//               source of truth so rank numbers stay consistent.
const LB_LEAGUES = [
  { id: 'overall', i18nKey: 'leagueOverall', predicate: () => true },
  { id: 'weekly',  i18nKey: 'leagueWeekly',  predicate: () => true },           // same pool — different rank source in prod
  { id: 'friends', i18nKey: 'leagueFriends', predicate: (e) => !!e.isFriend },
  { id: 'region',  i18nKey: 'leagueRegion',  subI18nKey: 'leagueRegionSub',
    predicate: (e) => e.region === 'tehran' },
];


// ─── Avatar palette pool (asset-less; mirrors TeamCrest pattern) ───────────
// Avatars are rendered via gradient + initial. The seed is a stable short
// string (entry.avatarSeed) so the same player always gets the same colour.
// 12 distinct palettes — biased toward accent / tier / semantic tokens so
// the table stays on-brand.
const LB_AVATAR_PALETTES = [
  { from: '#7163D9', to: '#534AB7', initialColor: '#fff' },   // accent
  { from: '#FCD34D', to: '#D97706', initialColor: '#1a1208' },// gold
  { from: '#93C5FD', to: '#3B82F6', initialColor: '#0E1A2B' },// platinum
  { from: '#34D399', to: '#059669', initialColor: '#062019' },// success
  { from: '#FB7185', to: '#BE123C', initialColor: '#1A0810' },// danger
  { from: '#60A5FA', to: '#1E40AF', initialColor: '#fff'    },// info
  { from: '#F59E0B', to: '#92400E', initialColor: '#1F1305' },// amber
  { from: '#A78BFA', to: '#6D28D9', initialColor: '#fff'    },// violet
  { from: '#22D3EE', to: '#0E7490', initialColor: '#06222B' },// cyan
  { from: '#F472B6', to: '#9D174D', initialColor: '#fff'    },// pink
  { from: '#94A3B8', to: '#475569', initialColor: '#fff'    },// silver
  { from: '#EAB308', to: '#854D0E', initialColor: '#1F1305' },// olive
];

function resolveAvatarPalette(seed) {
  if (!seed) return LB_AVATAR_PALETTES[0];
  let h = 0;
  for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) | 0;
  return LB_AVATAR_PALETTES[Math.abs(h) % LB_AVATAR_PALETTES.length];
}


// ─── Current user (canonical default) ──────────────────────────────────────
// Default state matches the brief: rank 247, score 8,120, dropped 12 spots
// vs last week, ~95th percentile, 147 spots away from the top-100 reward
// tier. Three weeks of history for the HistoryCard primitive.
const LB_USER = {
  id:           'u-current',
  rank:         247,
  score:        8120,
  deltaRank:    -12,
  percentile:   95,
  nextTierRank: 100,
  name:         { fa: 'تو',     en: 'You',   ar: 'أنت' },
  team:         { fa: 'پرسپولیس', en: 'Persepolis', ar: 'برسبوليس' },
  teamCode:     'PER',
  region:       'tehran',
  avatarSeed:   'you',
  avatarInitial:{ fa: 'ت',  en: 'Y',   ar: 'أ' },
  badges:       ['streak-7', 'rising'],
  history: [
    { week: 27, rank: 235, score: 8350, deltaRank: +14 },
    { week: 26, rank: 249, score: 7910, deltaRank: -8  },
    { week: 25, rank: 241, score: 8060, deltaRank: +22 },
  ],
  // Profile stats — surfaced in the entry-detail sheet (v1.6 fb).
  totalXP:          12480,
  level:            7,
  predictAccuracy:  78,
  predictTotal:     240,
  lineupsCount:     17,
  // ────────────────────────────────────────────────────────────────────
  // bestLineup — matches Wireframes §S37 ("پروفایل کاربر دیگه") exactly:
  //   • daysAgo       — relative time label (Wireframe shows "۲ روز پیش")
  //   • positions     — 5 scrollable position tiles (rating + position)
  //                     Wireframe row: [۸۸ GK][۹۰ DEF][۹۲ MID][۹۴ MID][۹۶ FWD]
  //                                    sum = 460 (= basePoints)
  //   • bonuses       — additive "+ chips" rendered after the positions
  //                     with a leading "+" separator. Wireframe: [GER +۴۰۰]
  //   • multipliers   — multiplicative "× chips". Each has a
  //                     `showInStrip` flag — `true` chips appear as visible
  //                     pills (with a "×" separator). `false` ones are
  //                     mentioned by NAME inside the formula text only —
  //                     matches the Wireframe where "ضریب جشنواره" is
  //                     called out in the formula but not chipped.
  //   • basePoints    — sum of position scores (the "۴۶۰" in Wireframe)
  //   • total         — final number rendered on the formula row
  //                     ((basePoints + sum(bonuses)) × multipliers …)
  //
  // Worked example: (460 + 400) × 2 × 2  =  3,440  ✓ matches Wireframe.
  // ────────────────────────────────────────────────────────────────────
  bestLineup: {
    daysAgo:    2,
    positions:  [
      { pos: 'GK',  score: 88 },
      { pos: 'DEF', score: 90 },
      { pos: 'MID', score: 92 },
      { pos: 'MID', score: 94 },
      { pos: 'FWD', score: 96 },
    ],
    bonuses:    [
      { code: 'GER', value: 400 },
    ],
    multipliers: [
      { labelKey: 'multClub',     value: 2, showInStrip: true  },
      { labelKey: 'multFestival', value: 2, showInStrip: false },
    ],
    basePoints: 460,
    total:      3440,
  },
};


// ─── Reward tiers (top-N bands) ────────────────────────────────────────────
// Tier `accent` reuses the existing tier-token palette — no new variables.
// `reward.packId` references a pack defined in `shop-data.jsx`. If the Shop
// data file isn't loaded the strip still renders, just without a pack name.
const LB_REWARD_TIERS = [
  {
    id:        't-podium',
    nameKey:   'tierPodium',
    rankRange: [1, 3],
    reward:    { coins: 5000, packId: 'pk-mythic-launch' },
    accent:    'var(--tier-gold)',
    glow:      'rgba(252,211,77,0.42)',
  },
  {
    id:        't-top10',
    nameKey:   'tierTop10',
    rankRange: [4, 10],
    reward:    { coins: 2000, packId: 'pk-mw28-gold' },
    accent:    'var(--tier-platinum)',
    glow:      'rgba(147,197,253,0.36)',
  },
  {
    id:        't-top100',
    nameKey:   'tierTop100',
    rankRange: [11, 100],
    reward:    { coins: 500 },
    accent:    'var(--tier-silver)',
    glow:      'rgba(148,163,184,0.30)',
  },
  {
    id:        't-top1000',
    nameKey:   'tierTop1000',
    rankRange: [101, 1000],
    reward:    { coins: 100 },
    accent:    'var(--tier-bronze)',
    glow:      'rgba(217,119,6,0.28)',
  },
];


// ─── Sponsor (optional this-week) ──────────────────────────────────────────
// Shape matches `SHOP_SPONSORS` rows with `placement: 'box'` so the same
// `SponsorBox` primitive (shop-pieces.jsx) renders it without adaptation.
const LB_SPONSOR = {
  id:        'lb-sp-mci',
  placement: 'box',
  name:      { fa: 'بانک ملت',  en: 'Mellat Bank',  ar: 'بنك ملت' },
  tagline:   { fa: 'برترین‌های هفته، ۵۰۰ سکه‌ی هدیه از بانک ملت دریافت می‌کنن',
               en: 'This week\'s top 10 receive 500 bonus coins from Mellat Bank',
               ar: 'أفضل ١٠ هذا الأسبوع يحصلون على ٥٠٠ عملة هدية من بنك ملت' },
  cta:       { fa: 'مشاهده',  en: 'Learn more',  ar: 'اعرف المزيد' },
  bonus:     { fa: '۵۰۰ سکه برای ۱۰ نفر برتر',
               en: '500 coins for the top 10',
               ar: '٥٠٠ عملة لأفضل ١٠' },
  brand:     '#D4AF37',
  brand2:    '#A87E1F',
  mark:      'M',
  // Optional logo asset — reuses the Shop sponsor asset map when present.
  logoSrc:   'assets/packs/بانک ملت.webp',
};


// ─── Entry pool helpers ────────────────────────────────────────────────────
// Six fan-team archetypes used across the 50 entries. Codes match
// SHOP_TEAM_POOL where relevant so a future shared TeamCrest layer is
// consistent. Order is purely cosmetic.
const _LB_TEAMS = [
  { code: 'PER', name: { fa: 'پرسپولیس', en: 'Persepolis',  ar: 'برسبوليس' }, color: '#b91c1c' },
  { code: 'EST', name: { fa: 'استقلال',  en: 'Esteghlal',   ar: 'استقلال'   }, color: '#1d4ed8' },
  { code: 'SEP', name: { fa: 'سپاهان',   en: 'Sepahan',     ar: 'سباهان'    }, color: '#FCD34D' },
  { code: 'TRA', name: { fa: 'تراکتور',  en: 'Tractor',     ar: 'تراكتور'   }, color: '#dc2626' },
  { code: 'BAR', name: { fa: 'بارسلونا', en: 'Barcelona',   ar: 'برشلونة'   }, color: '#7F1D1D' },
  { code: 'REA', name: { fa: 'رئال',     en: 'Real Madrid', ar: 'ريال'      }, color: '#fff' },
];
function _team(code) { return _LB_TEAMS.find((t) => t.code === code) || _LB_TEAMS[0]; }


// ─── Mock entries (~50) ────────────────────────────────────────────────────
// Realistic score distribution: top-3 in the 14k–15k range, top-10 down to
// ~12k, top-50 down to ~6.7k. Deltas skew small (most users move ±5 spots)
// with occasional outliers (a rocket +22 here, a -15 plunge there). Roughly
// half the pool is marked `region: 'tehran'` so the Region league has a
// believable subset. Six entries carry `isFriend: true` for the Friends
// league showcase.
//
// Each entry is plain data — `LeaderboardRow` does all rendering decisions
// (top-3 podium accent, isYou highlight, badge slot, delta arrow tone).
const _RAW_ENTRIES = [
  // ─── Podium (1–3) ──────────────────────────────────────────────────────
  { rank: 1,  score: 15240, deltaRank: +2,
    name: { fa: 'آرین شفیعی',    en: 'Aryan Shafiei',    ar: 'آريان شفيعي' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'm1',
    badges: ['top-scorer', 'mythic-pull', 'streak-7'] },
  { rank: 2,  score: 14890, deltaRank: -1,
    name: { fa: 'ندا اکبری',     en: 'Neda Akbari',      ar: 'ندى أكبري' },
    teamCode: 'EST', region: 'tehran', avatarSeed: 'f1',
    badges: ['undefeated', 'streak-7'] },
  { rank: 3,  score: 14210, deltaRank: +4,
    name: { fa: 'محسن رضایی',   en: 'Mohsen Rezaei',    ar: 'محسن رضائي' },
    teamCode: 'SEP', region: 'isfahan', avatarSeed: 'm2',
    badges: ['rising', 'perfect-week'] },

  // ─── Top-10 (4–10) ─────────────────────────────────────────────────────
  { rank: 4,  score: 13780, deltaRank: +1,
    name: { fa: 'سامان جعفری',   en: 'Saman Jafari',     ar: 'سامان جعفري' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'm3',
    badges: ['streak-7'] },
  { rank: 5,  score: 13420, deltaRank: -2,
    name: { fa: 'زهرا کریمی',    en: 'Zahra Karimi',     ar: 'زهراء كريمي' },
    teamCode: 'BAR', region: 'shiraz', avatarSeed: 'f2',
    badges: ['mythic-pull'], isFriend: true },
  { rank: 6,  score: 13100, deltaRank: +5,
    name: { fa: 'امیر حسینی',    en: 'Amir Hosseini',    ar: 'أمير حسيني' },
    teamCode: 'TRA', region: 'tabriz', avatarSeed: 'm4',
    badges: ['rising'] },
  { rank: 7,  score: 12820, deltaRank: 0,
    name: { fa: 'مارکوس ولف',   en: 'Markus Wolf',      ar: 'ماركوس فولف' },
    teamCode: 'REA', region: 'munich', avatarSeed: 'm5',
    badges: ['undefeated'] },
  { rank: 8,  score: 12540, deltaRank: -3,
    name: { fa: 'لیلا مرادی',   en: 'Leila Moradi',     ar: 'ليلى مرادي' },
    teamCode: 'EST', region: 'tehran', avatarSeed: 'f3',
    badges: ['streak-7'] },
  { rank: 9,  score: 12310, deltaRank: +7,
    name: { fa: 'فرید کشاورز',  en: 'Farid Keshavarz',  ar: 'فريد كشاورز' },
    teamCode: 'SEP', region: 'isfahan', avatarSeed: 'm6',
    badges: ['rising'] },
  { rank: 10, score: 12080, deltaRank: -1,
    name: { fa: 'جوآو سیلوا',  en: 'João Silva',       ar: 'جواو سيلفا' },
    teamCode: 'BAR', region: 'lisbon', avatarSeed: 'm7',
    badges: [] },

  // ─── Top-30 (11–30) ────────────────────────────────────────────────────
  { rank: 11, score: 11890, deltaRank: +3,
    name: { fa: 'پریسا فرهنگی', en: 'Parisa Farhangi',  ar: 'بريسا فرهنگي' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'f4',
    badges: ['perfect-week'], isFriend: true },
  { rank: 12, score: 11720, deltaRank: -4,
    name: { fa: 'یاسر نظری',   en: 'Yaser Nazari',     ar: 'ياسر نظري' },
    teamCode: 'TRA', region: 'tabriz', avatarSeed: 'm8', badges: [] },
  { rank: 13, score: 11580, deltaRank: +1,
    name: { fa: 'نیلوفر کاظمی', en: 'Niloofar Kazemi',  ar: 'نيلوفر كاظمي' },
    teamCode: 'EST', region: 'tehran', avatarSeed: 'f5', badges: ['streak-7'] },
  { rank: 14, score: 11430, deltaRank: -2,
    name: { fa: 'احمد سلطانی', en: 'Ahmad Soltani',    ar: 'أحمد سلطاني' },
    teamCode: 'SEP', region: 'isfahan', avatarSeed: 'm9', badges: [] },
  { rank: 15, score: 11290, deltaRank: +9,
    name: { fa: 'سارا میرزایی', en: 'Sara Mirzaei',     ar: 'ساراء ميرزائي' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'f6',
    badges: ['rising', 'mythic-pull'], isFriend: true },
  { rank: 16, score: 11150, deltaRank: -5,
    name: { fa: 'حمید قدیمی',  en: 'Hamid Ghadimi',    ar: 'حميد قديمي' },
    teamCode: 'BAR', region: 'tehran', avatarSeed: 'm10', badges: [] },
  { rank: 17, score: 11020, deltaRank: +2,
    name: { fa: 'الناز شریفی', en: 'Elnaz Sharifi',    ar: 'الناز شريفي' },
    teamCode: 'REA', region: 'tehran', avatarSeed: 'f7', badges: [] },
  { rank: 18, score: 10900, deltaRank: 0,
    name: { fa: 'بهمن خاکی',   en: 'Bahman Khaki',     ar: 'بهمن خاكي' },
    teamCode: 'EST', region: 'mashhad', avatarSeed: 'm11', badges: ['undefeated'] },
  { rank: 19, score: 10780, deltaRank: -3,
    name: { fa: 'مریم رحیمی', en: 'Maryam Rahimi',    ar: 'مريم رحيمي' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'f8', badges: [] },
  { rank: 20, score: 10660, deltaRank: +12,
    name: { fa: 'کیان فتحی',   en: 'Kian Fathi',       ar: 'كيان فتحي' },
    teamCode: 'TRA', region: 'tabriz', avatarSeed: 'm12',
    badges: ['rising'], isFriend: true },
  { rank: 21, score: 10550, deltaRank: -1,
    name: { fa: 'شیوا تابانی', en: 'Shiva Tabani',     ar: 'شيفا تاباني' },
    teamCode: 'SEP', region: 'isfahan', avatarSeed: 'f9', badges: [] },
  { rank: 22, score: 10440, deltaRank: +6,
    name: { fa: 'پارسا اسدی', en: 'Parsa Asadi',      ar: 'بارسا أسدي' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'm13', badges: ['streak-7'] },
  { rank: 23, score: 10340, deltaRank: -7,
    name: { fa: 'حسین قنبری', en: 'Hossein Ghanbari', ar: 'حسين غنبري' },
    teamCode: 'BAR', region: 'shiraz', avatarSeed: 'm14', badges: [] },
  { rank: 24, score: 10240, deltaRank: +3,
    name: { fa: 'فاطمه نوری', en: 'Fatemeh Nouri',    ar: 'فاطمة نوري' },
    teamCode: 'EST', region: 'tehran', avatarSeed: 'f10', badges: [] },
  { rank: 25, score: 10150, deltaRank: 0,
    name: { fa: 'علی محمدی',  en: 'Ali Mohammadi',    ar: 'علي محمدي' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'm15', badges: [] },
  { rank: 26, score: 10070, deltaRank: -4,
    name: { fa: 'نگین احمدی', en: 'Negin Ahmadi',     ar: 'نجين أحمدي' },
    teamCode: 'REA', region: 'tehran', avatarSeed: 'f11', badges: [] },
  { rank: 27, score: 9990,  deltaRank: +8,
    name: { fa: 'رضا صفری',   en: 'Reza Safari',      ar: 'رضا صفري' },
    teamCode: 'SEP', region: 'isfahan', avatarSeed: 'm16',
    badges: ['rising'] },
  { rank: 28, score: 9910,  deltaRank: -2,
    name: { fa: 'هانا برزگر', en: 'Hanna Barzegar',   ar: 'هناء برزكر' },
    teamCode: 'TRA', region: 'tabriz', avatarSeed: 'f12', badges: [] },
  { rank: 29, score: 9830,  deltaRank: +1,
    name: { fa: 'ساسان قربانی', en: 'Sasan Ghorbani', ar: 'ساسان قرباني' },
    teamCode: 'EST', region: 'tehran', avatarSeed: 'm17', badges: [] },
  { rank: 30, score: 9750,  deltaRank: -6,
    name: { fa: 'سحر اوضحی', en: 'Sahar Ozhaki',     ar: 'سحر أوضحي' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'f13', badges: ['streak-7'] },

  // ─── 31–50 (long tail) ─────────────────────────────────────────────────
  { rank: 31, score: 9680,  deltaRank: +2,
    name: { fa: 'مهدی توکلی', en: 'Mehdi Tavakoli',   ar: 'مهدي توكلي' },
    teamCode: 'BAR', region: 'shiraz', avatarSeed: 'm18', badges: [] },
  { rank: 32, score: 9610,  deltaRank: 0,
    name: { fa: 'ترانه پوری', en: 'Taraneh Pouri',    ar: 'ترانه بوري' },
    teamCode: 'EST', region: 'tehran', avatarSeed: 'f14', badges: [] },
  { rank: 33, score: 9540,  deltaRank: +5,
    name: { fa: 'بابک ولوی', en: 'Babak Valavi',     ar: 'بابك ولوي' },
    teamCode: 'TRA', region: 'mashhad', avatarSeed: 'm19', badges: [] },
  { rank: 34, score: 9470,  deltaRank: -8,
    name: { fa: 'لاله سرمدی', en: 'Laleh Sarmadi',    ar: 'لاله سرمدي' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'f15', badges: [] },
  { rank: 35, score: 9400,  deltaRank: +4,
    name: { fa: 'پدرام راد',  en: 'Pedram Rad',       ar: 'بدرام راد' },
    teamCode: 'SEP', region: 'isfahan', avatarSeed: 'm20',
    badges: ['perfect-week'], isFriend: true },
  { rank: 36, score: 9335,  deltaRank: -3,
    name: { fa: 'یوسف نوین', en: 'Yousef Novin',     ar: 'يوسف نوين' },
    teamCode: 'REA', region: 'tehran', avatarSeed: 'm21', badges: [] },
  { rank: 37, score: 9270,  deltaRank: +1,
    name: { fa: 'دیانا کاوه', en: 'Diana Kaveh',      ar: 'ديانا كاوه' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'f16', badges: [] },
  { rank: 38, score: 9210,  deltaRank: +10,
    name: { fa: 'شهاب نظری', en: 'Shahab Nazari',    ar: 'شهاب نظري' },
    teamCode: 'EST', region: 'tehran', avatarSeed: 'm22',
    badges: ['rising'] },
  { rank: 39, score: 9150,  deltaRank: -5,
    name: { fa: 'صدف کاشانی', en: 'Sadaf Kashani',   ar: 'صدف كاشاني' },
    teamCode: 'BAR', region: 'tehran', avatarSeed: 'f17', badges: [] },
  { rank: 40, score: 9090,  deltaRank: -2,
    name: { fa: 'آرمین تقوی', en: 'Armin Taghavi',    ar: 'آرمين تقوي' },
    teamCode: 'TRA', region: 'tabriz', avatarSeed: 'm23', badges: [] },
  { rank: 41, score: 9035,  deltaRank: +3,
    name: { fa: 'مهسا رستمی', en: 'Mahsa Rostami',    ar: 'مهسا رستمي' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'f18', badges: ['streak-7'] },
  { rank: 42, score: 8980,  deltaRank: -1,
    name: { fa: 'سینا برومند', en: 'Sina Boroomand', ar: 'سينا برومند' },
    teamCode: 'EST', region: 'tehran', avatarSeed: 'm24', badges: [] },
  { rank: 43, score: 8925,  deltaRank: +6,
    name: { fa: 'النا رابرت', en: 'Elena Robert',    ar: 'إيلينا روبير' },
    teamCode: 'REA', region: 'madrid', avatarSeed: 'f19', badges: ['rising'] },
  { rank: 44, score: 8870,  deltaRank: -4,
    name: { fa: 'علیرضا فلاح', en: 'Alireza Fallah', ar: 'علي رضا فلاح' },
    teamCode: 'SEP', region: 'isfahan', avatarSeed: 'm25', badges: [] },
  { rank: 45, score: 8820,  deltaRank: 0,
    name: { fa: 'نیما کوثر',  en: 'Nima Kosar',       ar: 'نيما كوثر' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'm26', badges: [] },
  { rank: 46, score: 8770,  deltaRank: +2,
    name: { fa: 'رویا مهرپور', en: 'Roya Mehrpour',  ar: 'رؤيا مهر بور' },
    teamCode: 'EST', region: 'tehran', avatarSeed: 'f20', badges: [] },
  { rank: 47, score: 8720,  deltaRank: -3,
    name: { fa: 'وحید عمادی', en: 'Vahid Emadi',     ar: 'وحيد عمادي' },
    teamCode: 'TRA', region: 'mashhad', avatarSeed: 'm27', badges: [] },
  { rank: 48, score: 8675,  deltaRank: +4,
    name: { fa: 'هدی پارسا', en: 'Hoda Parsa',       ar: 'هدى بارسا' },
    teamCode: 'BAR', region: 'tehran', avatarSeed: 'f21', badges: [] },
  { rank: 49, score: 8630,  deltaRank: -2,
    name: { fa: 'بهرام دستار', en: 'Bahram Dastar', ar: 'بهرام داستار' },
    teamCode: 'PER', region: 'tehran', avatarSeed: 'm28', badges: [] },
  { rank: 50, score: 8585,  deltaRank: +1,
    name: { fa: 'مونا صدر',   en: 'Mona Sadr',        ar: 'منى صدر' },
    teamCode: 'SEP', region: 'shiraz', avatarSeed: 'f22', badges: [] },
];

// Expand each entry with `team` resolved, plus the canonical `isYou: false`
// flag. Artboards can swap a specific row's isYou=true to render the
// highlighted "you in the list" variant.
const LB_ENTRIES = _RAW_ENTRIES.map((e) => ({
  ...e,
  id:        `u-r${e.rank}`,
  team:      _team(e.teamCode).name,
  teamColor: _team(e.teamCode).color,
  isFriend:  !!e.isFriend,
  isYou:     false,
}));


// ─── Helpers ───────────────────────────────────────────────────────────────

// Find the rank for a userId. Returns null if not present.
function getUserRank(entries, userId) {
  if (!Array.isArray(entries) || !userId) return null;
  const hit = entries.find((e) => e.id === userId);
  return hit ? hit.rank : null;
}

// Compute climb delta. Positive = improved (rank decreased). Negative =
// dropped (rank increased). Returns 0 for unchanged or invalid inputs.
function getRankDelta(curRank, prevRank) {
  if (typeof curRank !== 'number' || typeof prevRank !== 'number') return 0;
  return prevRank - curRank;
}

// Slice entries by league predicate. Returns a new array — does NOT
// re-rank, because the canonical "rank" field is the global ranking
// regardless of which league chip is active (per design decision: friends
// view still shows the friend's global rank, not their friend-only rank).
function filterEntries(entries, leagueKey) {
  if (!Array.isArray(entries)) return [];
  const league = LB_LEAGUES.find((l) => l.id === leagueKey);
  if (!league) return entries.slice();
  return entries.filter(league.predicate);
}

// Find the reward tier containing a given rank. Returns null if outside
// every tier (e.g. rank 5000 is below the lowest tier).
function getTierForRank(rank, tiers = LB_REWARD_TIERS) {
  if (typeof rank !== 'number') return null;
  return tiers.find((t) => rank >= t.rankRange[0] && rank <= t.rankRange[1]) || null;
}

// Synthesize player-profile stats for any entry. The canonical LB_USER
// has explicit fields; for the 50 mock entries we derive plausible numbers
// from rank so the EntryDetailSheet never has empty cells.
//
// Distribution targets (rank → stat):
//   • totalXP        — rank 1 ≈ 18k, rank 50 ≈ 4.5k, log-shaped fall-off.
//   • predictAccuracy— rank 1 ≈ 82%, rank 50 ≈ 60%. Clamped to [55, 88].
//   • predictTotal   — rank 1 ≈ 280, rank 50 ≈ 90.
//   • lineupsCount   — rank 1 ≈ 26, rank 50 ≈ 9.
//   • bestLineup     — { matchweek, formation, points, deltaAvg } —
//                      points scale with rank, formation rotates between
//                      a small fixed set to avoid all rows looking alike.
function getEntryStats(entry) {
  if (!entry) return null;
  // Real user → real stats (no synthesis).
  if (entry.isYou && window.LB_USER) {
    const u = window.LB_USER;
    return {
      totalXP:         u.totalXP,
      level:           u.level,
      predictAccuracy: u.predictAccuracy,
      predictTotal:    u.predictTotal,
      lineupsCount:    u.lineupsCount,
      bestLineup:      u.bestLineup,
    };
  }
  const r = entry.rank || 50;
  // Smoothly decay using a log curve so the top stays believable.
  const factor = Math.max(0, 1 - Math.log10(r) / Math.log10(60));
  const totalXP         = Math.round(4500 + 13500 * factor);
  const predictAccuracy = Math.max(55, Math.min(88, Math.round(60 + 24 * factor)));
  const predictTotal    = Math.round(90 + 190 * factor);
  const lineupsCount    = Math.max(3, Math.round(9 + 17 * factor));

  // bestLineup — synthesized to match Wireframe §S37 shape.
  // Score distribution per position scales with `factor`: top-rank rows
  // have all positions in the high 80s/90s, lower-rank rows in the 60s.
  // (Deterministic per-rank so the artboard never renders different
  // numbers on re-renders for the same row.)
  const rng = (seed) => {
    let x = (r * 9301 + seed * 49297) % 233280;
    return (x / 233280);                              // 0..1
  };
  const posBase    = 60 + Math.round(35 * factor);    // average rating
  const posSpread  = 6;
  const mkScore = (i) => posBase + Math.round((rng(i) - 0.5) * 2 * posSpread);
  const positions = [
    { pos: 'GK',  score: mkScore(1) },
    { pos: 'DEF', score: mkScore(2) },
    { pos: 'MID', score: mkScore(3) },
    { pos: 'MID', score: mkScore(4) },
    { pos: 'FWD', score: mkScore(5) },
  ];
  const basePoints = positions.reduce((a, p) => a + p.score, 0);
  // Country bonuses cycle for variety — code matches the entry's region
  // when sensible, otherwise rotates through common big leagues.
  const countryCodes = ['GER', 'ESP', 'ITA', 'ENG', 'FRA', 'POR'];
  const bonusCode = countryCodes[r % countryCodes.length];
  const bonusValue = Math.round(200 + 250 * factor);
  const bonuses = [{ code: bonusCode, value: bonusValue }];
  // Multipliers — keep "club" visible in the strip, festival hidden,
  // matching the Wireframe layout. Multiplier values cycle 2 / 2 / 3 …
  const clubMult     = 2;
  const festivalMult = r <= 10 ? 2 : 1;                // festival kicks in at top-10
  const multipliers = [
    { labelKey: 'multClub',     value: clubMult,     showInStrip: true  },
    festivalMult > 1
      ? { labelKey: 'multFestival', value: festivalMult, showInStrip: false }
      : null,
  ].filter(Boolean);
  const innerSum = basePoints + bonuses.reduce((a, b) => a + b.value, 0);
  const total = multipliers.reduce((a, m) => a * m.value, innerSum);

  return {
    totalXP, level: Math.max(1, Math.round(2 + 13 * factor)),
    predictAccuracy, predictTotal, lineupsCount,
    bestLineup: {
      daysAgo:   1 + (r % 6),                         // 1..6 days ago
      positions, bonuses, multipliers,
      basePoints, total,
    },
  };
}

// 0..1 progress to the next-higher tier. If already inside the top tier
// (rank 1–3), returns 1. If beyond every tier, returns 0.
//
// "Progress" here is the climb from the bottom of the current tier toward
// the floor of the better tier. So a user at rank 247 inside Top-1000
// (101–1000) progressing toward Top-100 (floor 100) returns:
//   (1000 - 247) / (1000 - 100) = 753/900 ≈ 0.84.
function getProgressToNextTier(rank, tiers = LB_REWARD_TIERS) {
  if (typeof rank !== 'number') return 0;
  const cur = getTierForRank(rank, tiers);
  if (!cur) return 0;
  const curIdx = tiers.indexOf(cur);
  if (curIdx <= 0) return 1;          // already inside the best tier
  const better   = tiers[curIdx - 1];
  const floorBet = better.rankRange[1];  // best rank that still belongs in `cur`
  const ceilCur  = cur.rankRange[1];
  const denom    = ceilCur - floorBet;
  if (denom <= 0) return 0;
  const climbed = ceilCur - rank;
  return Math.max(0, Math.min(1, climbed / denom));
}


// ─── Export to window ──────────────────────────────────────────────────────
Object.assign(window, {
  LB_I18N, LB_USER, LB_LEAGUES, LB_ENTRIES, LB_REWARD_TIERS, LB_BADGES,
  LB_SPONSOR, LB_AVATAR_PALETTES,
  resolveAvatarPalette,
  getUserRank, getRankDelta, filterEntries,
  getTierForRank, getProgressToNextTier, getEntryStats,
});
