import { GROUP_IDS, GroupId, Match, Team, StandingRow, GroupTable, ThirdPlaceRow, } from "./types"; // Berechnet eine leere Tabellenzeile. function emptyRow(teamId: string): StandingRow { return { teamId, played: 0, won: 0, drawn: 0, lost: 0, goalsFor: 0, goalsAgainst: 0, goalDiff: 0, points: 0, rank: 0, }; } // Trägt ein abgeschlossenes Spiel in zwei Tabellenzeilen ein. function applyMatch(rows: Map, m: Match) { if (m.status !== "FINISHED") return; if (m.homeTeamId == null || m.awayTeamId == null) return; if (m.homeScore == null || m.awayScore == null) return; const h = rows.get(m.homeTeamId); const a = rows.get(m.awayTeamId); if (!h || !a) return; h.played++; a.played++; h.goalsFor += m.homeScore; h.goalsAgainst += m.awayScore; a.goalsFor += m.awayScore; a.goalsAgainst += m.homeScore; if (m.homeScore > m.awayScore) { h.won++; a.lost++; h.points += 3; } else if (m.homeScore < m.awayScore) { a.won++; h.lost++; a.points += 3; } else { h.drawn++; a.drawn++; h.points += 1; a.points += 1; } } function finalizeRow(r: StandingRow) { r.goalDiff = r.goalsFor - r.goalsAgainst; } // Vergleicht zwei Teams nach FIFA-Kriterien innerhalb derselben Gruppe. // Reihenfolge: Punkte, dann Direktvergleich (Punkte/TD/Tore in den Spielen // der punktgleichen Teams), dann Gesamt-TD, Gesamt-Tore. Fair-Play/Ranking // werden hier nicht abgebildet (kein Feed dafür) -> bleibt bei Gleichstand stabil. function compareInGroup( a: StandingRow, b: StandingRow, matches: Match[], tiedIds: Set, ): number { if (b.points !== a.points) return b.points - a.points; // Direktvergleich nur unter den punktgleichen Teams if (tiedIds.size >= 2 && tiedIds.has(a.teamId) && tiedIds.has(b.teamId)) { const mini = miniTable(matches, tiedIds); const ma = mini.get(a.teamId); const mb = mini.get(b.teamId); if (ma && mb) { if (mb.points !== ma.points) return mb.points - ma.points; const mdA = ma.goalsFor - ma.goalsAgainst; const mdB = mb.goalsFor - mb.goalsAgainst; if (mdB !== mdA) return mdB - mdA; if (mb.goalsFor !== ma.goalsFor) return mb.goalsFor - ma.goalsFor; } } if (b.goalDiff !== a.goalDiff) return b.goalDiff - a.goalDiff; if (b.goalsFor !== a.goalsFor) return b.goalsFor - a.goalsFor; return 0; } // Baut eine Minitabelle aus den Spielen, in denen NUR die tiedIds gegeneinander spielten. function miniTable(matches: Match[], tiedIds: Set): Map { const rows = new Map(); tiedIds.forEach((id) => rows.set(id, emptyRow(id))); for (const m of matches) { if (m.status !== "FINISHED") continue; if (m.homeTeamId == null || m.awayTeamId == null) continue; if (!tiedIds.has(m.homeTeamId) || !tiedIds.has(m.awayTeamId)) continue; applyMatch(rows, m); } rows.forEach(finalizeRow); return rows; } // Berechnet alle 12 Gruppentabellen aus Teams + Spielen. export function computeGroupTables(teams: Team[], matches: Match[]): GroupTable[] { const tables: GroupTable[] = []; for (const g of GROUP_IDS) { const groupTeams = teams.filter((t) => t.group === g); const rows = new Map(); groupTeams.forEach((t) => rows.set(t.id, emptyRow(t.id))); const groupMatches = matches.filter((m) => m.group === g); for (const m of groupMatches) applyMatch(rows, m); rows.forEach(finalizeRow); const arr = [...rows.values()]; // Gruppen punktgleicher Teams für den Direktvergleich bestimmen const byPoints = new Map(); arr.forEach((r) => { const list = byPoints.get(r.points) ?? []; list.push(r.teamId); byPoints.set(r.points, list); }); arr.sort((a, b) => { const tied = byPoints.get(a.points) ?? []; const tiedIds = new Set(a.points === b.points && tied.length >= 2 ? tied : []); return compareInGroup(a, b, groupMatches, tiedIds); }); arr.forEach((r, i) => (r.rank = i + 1)); tables.push({ group: g, rows: arr }); } return tables; } // Rankt die 12 Gruppendritten gruppenübergreifend (ohne Direktvergleich). // Kriterien: Punkte, TD, Tore. Beste 8 -> qualifies = true. export function computeThirdPlaceTable(tables: GroupTable[]): ThirdPlaceRow[] { const thirds: ThirdPlaceRow[] = tables .map((t) => { const r = t.rows.find((x) => x.rank === 3); if (!r) return null; return { ...r, group: t.group, qualifies: false, overallRank: 0 }; }) .filter((x): x is ThirdPlaceRow => x !== null); thirds.sort((a, b) => { if (b.points !== a.points) return b.points - a.points; if (b.goalDiff !== a.goalDiff) return b.goalDiff - a.goalDiff; if (b.goalsFor !== a.goalsFor) return b.goalsFor - a.goalsFor; return a.group.localeCompare(b.group); // stabil }); thirds.forEach((r, i) => { r.overallRank = i + 1; r.qualifies = i < 8; }); return thirds; }