/** * Shared helpers for "shared major" simulators (CS2, tennis). * * A major (a real tournament) can be linked to several sports_season windows. * The bracket/stage structure is built and scored once on the PRIMARY window; * sibling windows hold no structure of their own. When simulating a sibling, the * simulator must read the structure from the primary and translate the primary * window's season_participant ids into the local window's ids via the shared * canonical participant. These helpers centralise that resolution + translation. */ import { getPrimaryEventForTournament } from "~/models/scoring-event"; import { findParticipantsBySportsSeasonId } from "~/models/season-participant"; /** Maps a participant id from one window to another (or passes it through). */ export type IdTranslator = (id: string | null) => string | null; /** Identity translation — used when structure lives in the event's own window. */ export const IDENTITY_TR: IdTranslator = (id) => id; /** * Build a translator that maps a PRIMARY window's season_participant id to the * equivalent id in a LOCAL (sibling) window, bridging via the shared canonical * participant. Ids that can't be bridged (no canonical link, or no local * counterpart) pass through unchanged so structure is never silently dropped. */ export function buildSpTranslator( primaryParticipants: Array<{ id: string; participantId: string | null }>, localParticipants: Array<{ id: string; participantId: string | null }> ): IdTranslator { const primaryToCanonical = new Map( primaryParticipants .filter((p) => p.participantId) .map((p) => [p.id, p.participantId as string]) ); const canonicalToLocal = new Map( localParticipants .filter((p) => p.participantId) .map((p) => [p.participantId as string, p.id]) ); return (id: string | null): string | null => { if (!id) return id; const canonical = primaryToCanonical.get(id); if (!canonical) return id; return canonicalToLocal.get(canonical) ?? id; }; } export interface StructureSource { /** Event id whose bracket/stage rows hold this event's structure. */ sourceId: string; /** Translate the source window's participant ids into the local window's. */ tr: IdTranslator; } /** * Resolve where an event's bracket/stage structure lives and how to map its * participant ids into the local window. For a primary/standalone/non-linked * event this is the event itself with identity translation. For a sibling * (tournament-linked, non-primary) event, the structure is the primary event and * ids are translated primary→local. * * `translatorCache` is keyed by primary sports_season id so a window's majors * (whose primaries may live in different windows) each build their translator * once. */ export async function resolveStructureSource( event: { id: string; tournamentId: string | null; isPrimary: boolean }, localParticipants: Array<{ id: string; participantId: string | null }>, translatorCache: Map ): Promise { if (!event.tournamentId || event.isPrimary) { return { sourceId: event.id, tr: IDENTITY_TR }; } const primary = await getPrimaryEventForTournament(event.tournamentId); if (!primary || primary.id === event.id) { return { sourceId: event.id, tr: IDENTITY_TR }; } let tr = translatorCache.get(primary.sportsSeasonId); if (!tr) { const primaryParticipants = await findParticipantsBySportsSeasonId( primary.sportsSeasonId ); tr = buildSpTranslator(primaryParticipants, localParticipants); translatorCache.set(primary.sportsSeasonId, tr); } return { sourceId: primary.id, tr }; }