import type { FetchedStandingsRecord, StandingsSyncAdapter } from "./types"; import { flattenEspnStandings, parseConferenceRank, sortByWinLoss, statsMap, type EspnStandingsResponse, } from "./espn"; const NBA_STANDINGS_URL = "https://site.api.espn.com/apis/v2/sports/basketball/nba/standings"; export class NbaStandingsAdapter implements StandingsSyncAdapter { async fetchStandings(): Promise { const response = await fetch(NBA_STANDINGS_URL); if (!response.ok) { throw new Error(`NBA standings API returned ${response.status}: ${response.statusText}`); } const json = (await response.json()) as EspnStandingsResponse; const flattened = flattenEspnStandings(json); if (flattened.length === 0) { throw new Error("NBA standings API returned no entries — response shape may have changed"); } // Pre-build statsMap once per entry so the sort doesn't rebuild it on every comparison. const withSm = flattened.map(({ entry, conference, division }) => ({ entry, conference, division, sm: statsMap(entry.stats), })); const sorted = [...withSm].toSorted(sortByWinLoss); return sorted.map(({ entry, conference, division, sm }, leagueIdx): FetchedStandingsRecord => { const wins = sm.get("wins")?.value ?? 0; const losses = sm.get("losses")?.value ?? 0; // Fall back to computing win% from wins/losses if ESPN omits the stat. const winPctStat = sm.get("winPercent")?.value ?? sm.get("winPct")?.value; const winPct = winPctStat ?? (wins + losses > 0 ? wins / (wins + losses) : 0); const gamesBehind = sm.get("gamesBehind")?.value; const streak = sm.get("streak")?.displayValue ?? sm.get("streakSummary")?.displayValue; const lastTen = sm.get("Last Ten Games")?.displayValue ?? sm.get("L10")?.displayValue ?? undefined; const homeRecord = sm.get("Home")?.displayValue ?? undefined; const awayRecord = sm.get("Road")?.displayValue ?? undefined; return { teamName: entry.team.displayName, externalTeamId: entry.team.id, wins: Math.round(wins), losses: Math.round(losses), winPct, gamesPlayed: Math.round(wins) + Math.round(losses), gamesBack: gamesBehind, conference, division: division || undefined, conferenceRank: parseConferenceRank(sm), leagueRank: leagueIdx + 1, streak, lastTen, homeRecord, awayRecord, }; }); } }