import type { MatchRecord, MatchSyncAdapter, MatchStatusValue } from "./types"; interface EspnCompetitor { id: string; team: { id: string; displayName: string; abbreviation?: string }; score?: string; homeAway?: "home" | "away"; winner?: boolean; } interface EspnEvent { id: string; date: string; competitions?: Array<{ competitors?: EspnCompetitor[]; status?: { type?: { name?: string; completed?: boolean; description?: string } }; matchday?: number; }>; } interface EspnScheduleResponse { events?: EspnEvent[]; } function mapStatus(name: string | undefined, completed: boolean | undefined): MatchStatusValue { if (completed) return "complete"; if (name === "STATUS_IN_PROGRESS" || name === "in_progress") return "in_progress"; if (name === "STATUS_CANCELED" || name === "canceled") return "canceled"; if (name === "STATUS_POSTPONED" || name === "postponed") return "postponed"; return "scheduled"; } function parseScore(s: string | undefined): number | null { if (s === undefined || s === "") return null; const n = parseInt(s, 10); return Number.isFinite(n) ? n : null; } export class EspnScheduleAdapter implements MatchSyncAdapter { readonly supportsLiveScores = false; // sport: e.g. "baseball/mlb", "basketball/nba" // seasonType: ESPN season type (1=preseason, 2=regular, 3=postseason). Pass 3 for bracket sports // to avoid the scoreboard limit silently truncating regular-season games. constructor(private readonly sport: string, private readonly seasonType?: number) {} // externalSeasonId is "YYYY" year string for ESPN sports async fetchMatches(externalSeasonId: string): Promise { const seasonTypeParam = this.seasonType !== undefined ? `&seasontype=${this.seasonType}` : ""; const url = `https://site.api.espn.com/apis/site/v2/sports/${this.sport}/scoreboard?limit=1000&dates=${externalSeasonId}${seasonTypeParam}`; const resp = await fetch(url); if (!resp.ok) { const body = await resp.text(); throw new Error(`ESPN API error ${resp.status}: ${body}`); } const data = (await resp.json()) as EspnScheduleResponse; const matches: MatchRecord[] = []; for (const event of data.events ?? []) { const comp = event.competitions?.[0]; if (!comp) continue; const competitors = comp.competitors ?? []; const home = competitors.find((c) => c.homeAway === "home") ?? competitors[0]; const away = competitors.find((c) => c.homeAway === "away") ?? competitors[1]; if (!home || !away) continue; const statusType = comp.status?.type; const status = mapStatus(statusType?.name, statusType?.completed); const homeScore = parseScore(home.score); const awayScore = parseScore(away.score); const winnerExternalId = home.winner ? home.team.id : away.winner ? away.team.id : null; matches.push({ externalMatchId: event.id, team1ExternalId: home.team.id, team1Name: home.team.displayName, team2ExternalId: away.team.id, team2Name: away.team.displayName, team1Score: homeScore, team2Score: awayScore, winnerExternalId, status, scheduledAt: new Date(event.date), startedAt: status !== "scheduled" ? new Date(event.date) : null, completedAt: status === "complete" ? new Date(event.date) : null, matchStage: null, matchRound: null, matchday: comp.matchday ?? null, isSeries: false, }); } return matches; } }