{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mrdoge-stats-list-adapter",
  "title": "Stats List Adapter (Mr. Doge SDK)",
  "description": "Maps a real match.stats (any of the 8 sports) onto Stats List's entries prop.",
  "dependencies": [
    "@mrdoge/protocol"
  ],
  "registryDependencies": [
    "stats-list"
  ],
  "files": [
    {
      "path": "lib/mrdoge-adapters/stats-list.ts",
      "content": "import type { MatchDetail, MatchStats } from \"@mrdoge/protocol\"\nimport type { StatsListEntry } from \"@/registry/mrdoge-ui/stats-list/stats-list\"\n\nfunction pair(label: string, home: number | undefined, away: number | undefined, format?: (n: number) => string): StatsListEntry | null {\n  if (home == null || away == null) return null\n  const toText = format ?? String\n  return { label, home: toText(home), away: toText(away), homeValue: home, awayValue: away }\n}\n\nfunction percent(value: number) {\n  return `${Math.round(value * 100)}%`\n}\n\n// Booleans (who's serving, who's in the bonus) still render as a bar (a\n// full bar on whichever side is true, empty on the other) rather than\n// being dropped, since every row here is a comparison bar.\nfunction booleanPair(label: string, home: boolean | undefined, away: boolean | undefined): StatsListEntry | null {\n  if (home == null && away == null) return null\n  return {\n    label,\n    home: home ? \"Yes\" : \"No\",\n    away: away ? \"Yes\" : \"No\",\n    homeValue: home ? 1 : 0,\n    awayValue: away ? 1 : 0,\n  }\n}\n\n/**\n * Maps a real `match.stats` (from `matches.get()`/`matches.subscribe()`)\n * to Stats List's entries: a plain array, one row per available stat. If\n * the SDK's shape changes, this fails to compile.\n *\n * MatchStats is a discriminated union per sport, so this switches on\n * `stats.sport`. Every branch is covered, but four sports\n * (american football, baseball, ice hockey, handball) currently have no\n * comparable home/away stats in the protocol beyond score and clock\n * (already shown elsewhere, e.g. Match Highlight), so those return an\n * empty array rather than padding the list with fields that don't exist.\n * Tennis's current-game points (\"15\"/\"40\"/\"AD\") aren't included: text,\n * not a number, so there's no meaningful bar for it.\n */\nexport function statsToStatsListEntries(stats: MatchStats | null | undefined): StatsListEntry[] {\n  if (!stats) return []\n\n  switch (stats.sport) {\n    case \"soccer\":\n      return [\n        pair(\"Possession\", stats.homePossession, stats.awayPossession, percent),\n        pair(\"Expected Goals\", stats.homeExpectedGoals, stats.awayExpectedGoals, (n) => n.toFixed(2)),\n        pair(\"Shots\", stats.homeShots, stats.awayShots),\n        pair(\"Shots on Target\", stats.homeShotsOnTarget, stats.awayShotsOnTarget),\n        pair(\"Corners\", stats.homeCorners, stats.awayCorners),\n        pair(\"Fouls\", stats.homeFouls, stats.awayFouls),\n        pair(\"Offsides\", stats.homeOffsides, stats.awayOffsides),\n        pair(\"Yellow Cards\", stats.homeYellowCards, stats.awayYellowCards),\n        pair(\"Red Cards\", stats.homeRedCards, stats.awayRedCards),\n        pair(\"Tackles\", stats.homeTackles, stats.awayTackles),\n        pair(\"Throw-ins\", stats.homeThrowIns, stats.awayThrowIns),\n        pair(\"Goal Kicks\", stats.homeGoalKicks, stats.awayGoalKicks),\n        pair(\"Woodwork Hits\", stats.homeWoodworkHits, stats.awayWoodworkHits),\n        pair(\"Penalty Kicks\", stats.homePenaltyKicks, stats.awayPenaltyKicks),\n      ].filter((entry): entry is StatsListEntry => entry !== null)\n\n    case \"tennis\":\n      return [\n        pair(\"Games (current set)\", stats.homeGamesInCurrentSet, stats.awayGamesInCurrentSet),\n        booleanPair(\"Serving\", stats.homeServes, stats.awayServes),\n      ].filter((entry): entry is StatsListEntry => entry !== null)\n\n    case \"basketball\":\n      return [\n        pair(\"Fouls\", stats.homeFouls, stats.awayFouls),\n        booleanPair(\"In Bonus\", stats.homeIsBonus, stats.awayIsBonus),\n        booleanPair(\"Possession\", stats.homeHasPossession, stats.awayHasPossession),\n      ].filter((entry): entry is StatsListEntry => entry !== null)\n\n    case \"volleyball\":\n      return [booleanPair(\"Serving\", stats.homeServes, stats.awayServes)].filter(\n        (entry): entry is StatsListEntry => entry !== null\n      )\n\n    // Baseball's stats (outs, balls, strikes, bases) describe the current\n    // at-bat, not a per-team count. They don't fit a home/away comparison\n    // row. American football, ice hockey, and handball have no\n    // sport-specific fields in the protocol at all yet.\n    case \"american_football\":\n    case \"baseball\":\n    case \"ice_hockey\":\n    case \"handball\":\n      return []\n\n    default:\n      return []\n  }\n}\n\nfunction topPlayer(players: { name: string; value: number }[] | undefined): { name: string; value: number } | null {\n  if (!players || players.length === 0) return null\n  return players.reduce((top, player) => (player.value > top.value ? player : top))\n}\n\nfunction playerPair(\n  label: string,\n  homePlayers: { name: string; value: number }[] | undefined,\n  awayPlayers: { name: string; value: number }[] | undefined\n): StatsListEntry | null {\n  const home = topPlayer(homePlayers)\n  const away = topPlayer(awayPlayers)\n  if (!home && !away) return null\n  return {\n    label,\n    home: home ? `${home.name} (${home.value})` : \"—\",\n    away: away ? `${away.name} (${away.value})` : \"—\",\n    homeValue: home?.value ?? 0,\n    awayValue: away?.value ?? 0,\n  }\n}\n\n/**\n * Maps a real match's per-player stat breakdowns (soccer only, the only\n * sport with per-player arrays in the protocol today) to Stats List's\n * entries: the top player per side for shots, shots on target, and\n * woodwork hits. Returns an empty array when `match.timeline` has no\n * player-attributed events (captions with a player name), a proxy for\n * \"this match doesn't have player-level tracking,\" since the same\n * data-provider gap that skips player names in the timeline also tends\n * to leave the per-player stat arrays empty.\n */\nexport function matchToPlayerStatsListEntries(match: MatchDetail): StatsListEntry[] {\n  const hasPlayerInfo = (match.timeline ?? []).some((event) => event.captions.length >= 3)\n  if (!hasPlayerInfo) return []\n\n  const stats = match.stats?.sport === \"soccer\" ? match.stats : undefined\n  if (!stats) return []\n\n  return [\n    playerPair(\"Shots\", stats.homePlayersShots, stats.awayPlayersShots),\n    playerPair(\"Shots on Target\", stats.homePlayersShotsOnTarget, stats.awayPlayersShotsOnTarget),\n    playerPair(\"Woodwork Hits\", stats.homePlayersWoodworkHits, stats.awayPlayersWoodworkHits),\n  ].filter((entry): entry is StatsListEntry => entry !== null)\n}\n",
      "type": "registry:lib",
      "target": "lib/mrdoge-adapters/stats-list.ts"
    }
  ],
  "type": "registry:lib"
}