export type MatchStatusKind = 'live' | 'upcoming' | 'finished' | 'postponed' | 'unknown'; type MatchStatusInput = { kickoff_utc?: string | null; match_time_utc?: string | null; match_time?: string | null; status?: string | null; home_score?: number | null; away_score?: number | null; }; const LIVE_WINDOW_MS = 150 * 60 * 1000; const RESULT_BACKFILL_GRACE_MS = 240 * 60 * 1000; function kickoffTimestamp(match: MatchStatusInput): number | null { const raw = match.kickoff_utc || match.match_time_utc || match.match_time; if (!raw) { return null; } const timestamp = Date.parse(raw); return Number.isNaN(timestamp) ? null : timestamp; } function normalizedStatus(match: MatchStatusInput): string { return (match.status || '').trim().toLowerCase().replace(/_/g, '-'); } function hasScore(match: MatchStatusInput): boolean { return typeof match.home_score === 'number' && typeof match.away_score === 'number'; } export function matchStatusKind(match: MatchStatusInput, now: Date = new Date()): MatchStatusKind { const status = normalizedStatus(match); const kickoff = kickoffTimestamp(match); const nowMs = now.getTime(); if (/postponed|delayed|suspended|cancelled|canceled|abandoned/.test(status)) { return 'postponed'; } if (/finished|final|full-time|fulltime|ft|completed|closed/.test(status)) { return 'finished'; } if (/live|in-play|inplay|playing|ongoing|1st|2nd|first-half|second-half|halftime|half-time/.test(status)) { return 'live'; } if (kickoff === null) { return hasScore(match) ? 'finished' : 'unknown'; } if (kickoff > nowMs) { return 'upcoming'; } const elapsed = nowMs - kickoff; if (elapsed <= LIVE_WINDOW_MS) { return 'live'; } if (hasScore(match) || elapsed > RESULT_BACKFILL_GRACE_MS) { return hasScore(match) ? 'finished' : 'unknown'; } return 'unknown'; } export function matchStatusLabel(match: MatchStatusInput, now: Date = new Date()): string { const kind = matchStatusKind(match, now); const score = hasScore(match) ? `${match.home_score}-${match.away_score}` : null; if (kind === 'live') { return score ? `進行中 ${score}` : '進行中'; } if (kind === 'finished') { return score ? `已完賽 ${score}` : '已完賽'; } if (kind === 'postponed') { return '延期/中止'; } if (kind === 'upcoming') { return '未開賽'; } return '結果待回填'; } export function sortMatchesForProfessionalDisplay(matches: T[], now: Date = new Date()): T[] { const rank: Record = { live: 0, upcoming: 1, unknown: 2, postponed: 3, finished: 4, }; return [...matches].sort((a, b) => { const kindA = matchStatusKind(a, now); const kindB = matchStatusKind(b, now); if (kindA !== kindB) { return rank[kindA] - rank[kindB]; } const timeA = kickoffTimestamp(a) ?? 0; const timeB = kickoffTimestamp(b) ?? 0; if (kindA === 'finished') { return timeB - timeA; } return timeA - timeB; }); }