/** * Dependency Injection Container * * Initializes all in-memory repositories and provides accessor functions. * Allows easy swapping to persistent repositories later. */ import { Penalty } from '@gridpilot/racing/domain/entities/Penalty'; import { Protest } from '@gridpilot/racing/domain/entities/Protest'; import { Driver } from '@gridpilot/racing/domain/entities/Driver'; import { League } from '@gridpilot/racing/domain/entities/League'; import { Race } from '@gridpilot/racing/domain/entities/Race'; import { Result } from '@gridpilot/racing/domain/entities/Result'; import { Standing } from '@gridpilot/racing/domain/entities/Standing'; import { Game } from '@gridpilot/racing/domain/entities/Game'; import { Season } from '@gridpilot/racing/domain/entities/Season'; import { Track } from '@gridpilot/racing/domain/entities/Track'; import { Car } from '@gridpilot/racing/domain/entities/Car'; import type { IDriverRepository } from '@gridpilot/racing/domain/repositories/IDriverRepository'; import type { ILeagueRepository } from '@gridpilot/racing/domain/repositories/ILeagueRepository'; import type { IRaceRepository } from '@gridpilot/racing/domain/repositories/IRaceRepository'; import type { IResultRepository } from '@gridpilot/racing/domain/repositories/IResultRepository'; import type { IStandingRepository } from '@gridpilot/racing/domain/repositories/IStandingRepository'; import type { IPenaltyRepository } from '@gridpilot/racing/domain/repositories/IPenaltyRepository'; import type { IProtestRepository } from '@gridpilot/racing/domain/repositories/IProtestRepository'; import type { IGameRepository } from '@gridpilot/racing/domain/repositories/IGameRepository'; import type { ISeasonRepository } from '@gridpilot/racing/domain/repositories/ISeasonRepository'; import type { ILeagueScoringConfigRepository } from '@gridpilot/racing/domain/repositories/ILeagueScoringConfigRepository'; import type { ITrackRepository } from '@gridpilot/racing/domain/repositories/ITrackRepository'; import type { ICarRepository } from '@gridpilot/racing/domain/repositories/ICarRepository'; import type { ITeamRepository, ITeamMembershipRepository, IRaceRegistrationRepository, } from '@gridpilot/racing'; import type { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository'; import type { LeagueMembership, JoinRequest } from '@gridpilot/racing/domain/entities/LeagueMembership'; import type { IFeedRepository } from '@gridpilot/social/domain/repositories/IFeedRepository'; import type { ISocialGraphRepository } from '@gridpilot/social/domain/repositories/ISocialGraphRepository'; import type { ImageServicePort } from '@gridpilot/media'; // Notifications package imports import type { INotificationRepository, INotificationPreferenceRepository } from '@gridpilot/notifications/application'; import { SendNotificationUseCase, MarkNotificationReadUseCase, GetUnreadNotificationsQuery } from '@gridpilot/notifications/application'; import { InMemoryNotificationRepository, InMemoryNotificationPreferenceRepository, NotificationGatewayRegistry, InAppNotificationAdapter, } from '@gridpilot/notifications/infrastructure'; import { InMemoryDriverRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryDriverRepository'; import { InMemoryLeagueRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryLeagueRepository'; import { InMemoryRaceRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryRaceRepository'; import { InMemoryResultRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryResultRepository'; import { InMemoryStandingRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryStandingRepository'; import { InMemoryPenaltyRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryPenaltyRepository'; import { InMemoryProtestRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryProtestRepository'; import { InMemoryTrackRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryTrackRepository'; import { InMemoryCarRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryCarRepository'; import { InMemoryGameRepository, InMemorySeasonRepository, InMemoryLeagueScoringConfigRepository, getLeagueScoringPresetById, } from '@gridpilot/racing/infrastructure/repositories/InMemoryScoringRepositories'; import { InMemoryLeagueScoringPresetProvider } from '@gridpilot/racing/infrastructure/repositories/InMemoryLeagueScoringPresetProvider'; import { InMemoryTeamRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryTeamRepository'; import { InMemoryTeamMembershipRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryTeamMembershipRepository'; import { InMemoryRaceRegistrationRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryRaceRegistrationRepository'; import { InMemoryLeagueMembershipRepository } from '@gridpilot/racing/infrastructure/repositories/InMemoryLeagueMembershipRepository'; import { JoinLeagueUseCase, RegisterForRaceUseCase, WithdrawFromRaceUseCase, IsDriverRegisteredForRaceQuery, GetRaceRegistrationsQuery, CreateTeamUseCase, JoinTeamUseCase, LeaveTeamUseCase, ApproveTeamJoinRequestUseCase, RejectTeamJoinRequestUseCase, UpdateTeamUseCase, GetAllTeamsQuery, GetTeamDetailsQuery, GetTeamMembersQuery, GetTeamJoinRequestsQuery, GetDriverTeamQuery, GetLeagueStandingsQuery, GetLeagueDriverSeasonStatsQuery, GetAllLeaguesWithCapacityQuery, GetAllLeaguesWithCapacityAndScoringQuery, ListLeagueScoringPresetsQuery, GetLeagueScoringConfigQuery, CreateLeagueWithSeasonAndScoringUseCase, GetLeagueFullConfigQuery, GetRaceWithSOFQuery, GetLeagueStatsQuery, FileProtestUseCase, ReviewProtestUseCase, ApplyPenaltyUseCase, GetRaceProtestsQuery, GetRacePenaltiesQuery, RequestProtestDefenseUseCase, SubmitProtestDefenseUseCase, } from '@gridpilot/racing/application'; import { TransferLeagueOwnershipUseCase } from '@gridpilot/racing/application/use-cases/TransferLeagueOwnershipUseCase'; import type { DriverRatingProvider } from '@gridpilot/racing/application'; import { createStaticRacingSeed, type RacingSeedData, getDemoLeagueArchetypeByName, } from '@gridpilot/testing-support'; import type { LeagueScheduleDTO, LeagueSchedulePreviewDTO, } from '@gridpilot/racing/application'; import { PreviewLeagueScheduleQuery } from '@gridpilot/racing/application'; import { InMemoryFeedRepository, InMemorySocialGraphRepository, } from '@gridpilot/social/infrastructure/inmemory/InMemorySocialAndFeed'; import { DemoImageServiceAdapter } from '@gridpilot/demo-infrastructure'; import type { LeagueScoringPresetProvider } from '@gridpilot/racing/application/ports/LeagueScoringPresetProvider'; /** * Seed data for development */ /** * Driver statistics and ranking data */ export interface DriverStats { driverId: string; rating: number; totalRaces: number; wins: number; podiums: number; dnfs: number; avgFinish: number; bestFinish: number; worstFinish: number; consistency: number; overallRank: number; percentile: number; } /** * Mock driver stats with calculated rankings */ const driverStats: Record = {}; function createSeedData(): RacingSeedData { const seed = createStaticRacingSeed(42); const { drivers } = seed; drivers.forEach((driver, index) => { const totalRaces = 40 + index * 5; const wins = Math.max(0, Math.floor(totalRaces * 0.2) - index); const podiums = Math.max(wins * 2, 0); const dnfs = Math.max(0, Math.floor(index / 2)); const rating = 1500 + index * 25; driverStats[driver.id] = { driverId: driver.id, rating, totalRaces, wins, podiums, dnfs, avgFinish: 4, bestFinish: 1, worstFinish: 20, consistency: 80, overallRank: index + 1, percentile: Math.max(0, 100 - index), }; }); return seed; } /** * DI Container class */ class DIContainer { private static instance: DIContainer; private _driverRepository: IDriverRepository; private _leagueRepository: ILeagueRepository; private _raceRepository: IRaceRepository; private _resultRepository: IResultRepository; private _standingRepository: IStandingRepository; private _penaltyRepository: IPenaltyRepository; private _protestRepository: IProtestRepository; private _teamRepository: ITeamRepository; private _teamMembershipRepository: ITeamMembershipRepository; private _raceRegistrationRepository: IRaceRegistrationRepository; private _leagueMembershipRepository: ILeagueMembershipRepository; private _gameRepository: IGameRepository; private _seasonRepository: ISeasonRepository; private _leagueScoringConfigRepository: ILeagueScoringConfigRepository; private _leagueScoringPresetProvider: LeagueScoringPresetProvider; private _feedRepository: IFeedRepository; private _socialRepository: ISocialGraphRepository; private _imageService: ImageServicePort; private _trackRepository: ITrackRepository; private _carRepository: ICarRepository; // Notifications private _notificationRepository: INotificationRepository; private _notificationPreferenceRepository: INotificationPreferenceRepository; private _notificationGatewayRegistry: NotificationGatewayRegistry; private _sendNotificationUseCase: SendNotificationUseCase; private _markNotificationReadUseCase: MarkNotificationReadUseCase; private _getUnreadNotificationsQuery: GetUnreadNotificationsQuery; // Racing application use-cases / queries private _joinLeagueUseCase: JoinLeagueUseCase; private _registerForRaceUseCase: RegisterForRaceUseCase; private _withdrawFromRaceUseCase: WithdrawFromRaceUseCase; private _isDriverRegisteredForRaceQuery: IsDriverRegisteredForRaceQuery; private _getRaceRegistrationsQuery: GetRaceRegistrationsQuery; private _getLeagueStandingsQuery: GetLeagueStandingsQuery; private _getLeagueDriverSeasonStatsQuery: GetLeagueDriverSeasonStatsQuery; private _getAllLeaguesWithCapacityQuery: GetAllLeaguesWithCapacityQuery; private _getAllLeaguesWithCapacityAndScoringQuery: GetAllLeaguesWithCapacityAndScoringQuery; private _listLeagueScoringPresetsQuery: ListLeagueScoringPresetsQuery; private _getLeagueScoringConfigQuery: GetLeagueScoringConfigQuery; private _createLeagueWithSeasonAndScoringUseCase: CreateLeagueWithSeasonAndScoringUseCase; private _getLeagueFullConfigQuery: GetLeagueFullConfigQuery; // Placeholder for future schedule preview wiring private _previewLeagueScheduleQuery: PreviewLeagueScheduleQuery; private _getRaceWithSOFQuery: GetRaceWithSOFQuery; private _getLeagueStatsQuery: GetLeagueStatsQuery; private _driverRatingProvider: DriverRatingProvider; private _fileProtestUseCase: FileProtestUseCase; private _reviewProtestUseCase: ReviewProtestUseCase; private _applyPenaltyUseCase: ApplyPenaltyUseCase; private _getRaceProtestsQuery: GetRaceProtestsQuery; private _getRacePenaltiesQuery: GetRacePenaltiesQuery; private _requestProtestDefenseUseCase: RequestProtestDefenseUseCase; private _submitProtestDefenseUseCase: SubmitProtestDefenseUseCase; private _createTeamUseCase: CreateTeamUseCase; private _joinTeamUseCase: JoinTeamUseCase; private _leaveTeamUseCase: LeaveTeamUseCase; private _approveTeamJoinRequestUseCase: ApproveTeamJoinRequestUseCase; private _rejectTeamJoinRequestUseCase: RejectTeamJoinRequestUseCase; private _updateTeamUseCase: UpdateTeamUseCase; private _getAllTeamsQuery: GetAllTeamsQuery; private _getTeamDetailsQuery: GetTeamDetailsQuery; private _getTeamMembersQuery: GetTeamMembersQuery; private _getTeamJoinRequestsQuery: GetTeamJoinRequestsQuery; private _getDriverTeamQuery: GetDriverTeamQuery; private _transferLeagueOwnershipUseCase: TransferLeagueOwnershipUseCase; private constructor() { // Create seed data const seedData = createSeedData(); const primaryDriverId = seedData.drivers[0]?.id ?? 'driver-1'; // Initialize repositories with seed data this._driverRepository = new InMemoryDriverRepository(seedData.drivers); this._leagueRepository = new InMemoryLeagueRepository(seedData.leagues); this._raceRepository = new InMemoryRaceRepository(seedData.races); // Result repository needs race repository for league-based queries this._resultRepository = new InMemoryResultRepository( seedData.results, this._raceRepository ); // Standing repository needs all three for recalculation this._standingRepository = new InMemoryStandingRepository( seedData.standings, this._resultRepository, this._raceRepository, this._leagueRepository ); // Race registrations - seed from results for completed races, plus some upcoming races const seedRaceRegistrations: Array<{ raceId: string; driverId: string; registeredAt: Date }> = []; // For completed races, extract driver registrations from results for (const result of seedData.results) { seedRaceRegistrations.push({ raceId: result.raceId, driverId: result.driverId, registeredAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago }); } // For some upcoming races, add random registrations const upcomingRaces = seedData.races.filter(r => r.status === 'scheduled').slice(0, 10); for (const race of upcomingRaces) { const participantCount = Math.floor(Math.random() * 12) + 8; // 8-20 participants const shuffledDrivers = [...seedData.drivers].sort(() => Math.random() - 0.5).slice(0, participantCount); for (const driver of shuffledDrivers) { seedRaceRegistrations.push({ raceId: race.id, driverId: driver.id, registeredAt: new Date(Date.now() - Math.floor(Math.random() * 5) * 24 * 60 * 60 * 1000), }); } } this._raceRegistrationRepository = new InMemoryRaceRegistrationRepository(seedRaceRegistrations); // Seed sample penalties and protests for completed races across different leagues // Group completed races by league, then take 1-2 from each league to ensure coverage const completedRaces = seedData.races.filter(r => r.status === 'completed'); const racesByLeague = new Map(); for (const race of completedRaces) { const existing = racesByLeague.get(race.leagueId) || []; existing.push(race); racesByLeague.set(race.leagueId, existing); } // Get up to 2 races per league for protest seeding const racesForProtests: Array<{ race: typeof completedRaces[0]; leagueIndex: number }> = []; let leagueIndex = 0; for (const [, leagueRaces] of racesByLeague) { // Sort by scheduled date, take earliest 2 const sorted = [...leagueRaces].sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime()); for (const race of sorted.slice(0, 2)) { racesForProtests.push({ race, leagueIndex }); } leagueIndex++; } const seededPenalties: Penalty[] = []; const seededProtests: Protest[] = []; racesForProtests.forEach(({ race, leagueIndex: leagueIdx }, raceIndex) => { // Get results for this race to find drivers involved const raceResults = seedData.results.filter(r => r.raceId === race.id); if (raceResults.length < 4) return; // Create 1-2 protests per race const protestCount = Math.min(2, raceResults.length - 2); for (let i = 0; i < protestCount; i++) { const protestingResult = raceResults[i + 2]; // Driver who finished 3rd or 4th const accusedResult = raceResults[i]; // Driver who finished 1st or 2nd if (!protestingResult || !accusedResult) continue; const protestStatuses: Array<'pending' | 'under_review' | 'upheld' | 'dismissed'> = ['pending', 'under_review', 'upheld', 'dismissed']; const status = protestStatuses[(raceIndex + i) % protestStatuses.length]; const protest = Protest.create({ id: `protest-${race.id}-${i}`, raceId: race.id, protestingDriverId: protestingResult.driverId, accusedDriverId: accusedResult.driverId, incident: { lap: 5 + i * 3, description: i === 0 ? 'Unsafe rejoining to the track after going off, causing contact' : 'Aggressive defending, pushing competitor off track', }, comment: i === 0 ? 'Driver rejoined directly into my racing line, causing contact and damaging my front wing.' : 'Driver moved under braking multiple times, forcing me off the circuit.', status, filedAt: new Date(Date.now() - (raceIndex + 1) * 24 * 60 * 60 * 1000), reviewedBy: status !== 'pending' ? primaryDriverId : undefined, decisionNotes: status === 'upheld' ? 'After reviewing the evidence, the accused driver is found at fault. Penalty applied.' : status === 'dismissed' ? 'No clear fault found. Racing incident.' : undefined, reviewedAt: status !== 'pending' ? new Date(Date.now() - raceIndex * 24 * 60 * 60 * 1000) : undefined, }); seededProtests.push(protest); // If protest was upheld, create a penalty if (status === 'upheld') { // Alternate between points deduction and time penalties for visibility const penaltyType = i % 2 === 0 ? 'points_deduction' : 'time_penalty'; const penalty = Penalty.create({ id: `penalty-${race.id}-${i}`, raceId: race.id, driverId: accusedResult.driverId, type: penaltyType, value: penaltyType === 'points_deduction' ? 3 : 5, reason: protest.incident.description, protestId: protest.id, issuedBy: primaryDriverId, status: 'applied', issuedAt: new Date(Date.now() - raceIndex * 24 * 60 * 60 * 1000), appliedAt: new Date(Date.now() - raceIndex * 24 * 60 * 60 * 1000), }); seededPenalties.push(penalty); } } // Add direct penalties (not from protest) for better visibility in standings if (raceResults.length > 5) { // Add a points deduction penalty for some drivers if (raceIndex % 3 === 0) { const penalizedResult = raceResults[4]; if (penalizedResult) { const penalty = Penalty.create({ id: `penalty-direct-${race.id}`, raceId: race.id, driverId: penalizedResult.driverId, type: 'points_deduction', value: 5, reason: 'Causing avoidable collision', issuedBy: primaryDriverId, status: 'applied', issuedAt: new Date(Date.now() - (raceIndex + 1) * 12 * 60 * 60 * 1000), appliedAt: new Date(Date.now() - (raceIndex + 1) * 12 * 60 * 60 * 1000), }); seededPenalties.push(penalty); } } // Add another points deduction for different driver if (raceIndex % 3 === 1 && raceResults.length > 6) { const penalizedResult = raceResults[5]; if (penalizedResult) { const penalty = Penalty.create({ id: `penalty-direct-2-${race.id}`, raceId: race.id, driverId: penalizedResult.driverId, type: 'points_deduction', value: 2, reason: 'Track limits violation - gained lasting advantage', issuedBy: primaryDriverId, status: 'applied', issuedAt: new Date(Date.now() - (raceIndex + 1) * 12 * 60 * 60 * 1000), appliedAt: new Date(Date.now() - (raceIndex + 1) * 12 * 60 * 60 * 1000), }); seededPenalties.push(penalty); } } } }); // Penalties and protests with seeded data this._penaltyRepository = new InMemoryPenaltyRepository(seededPenalties); this._protestRepository = new InMemoryProtestRepository(seededProtests); // Scoring preset provider and seeded game/season/scoring config repositories this._leagueScoringPresetProvider = new InMemoryLeagueScoringPresetProvider(); const game = Game.create({ id: 'iracing', name: 'iRacing' }); const seededSeasons: Season[] = []; const seededScoringConfigs = []; for (const league of seedData.leagues) { const archetype = getDemoLeagueArchetypeByName(league.name); const season = Season.create({ id: `season-${league.id}-demo`, leagueId: league.id, gameId: game.id, name: `${league.name} Demo Season`, year: new Date().getFullYear(), order: 1, status: 'active', startDate: new Date(), endDate: new Date(), }); seededSeasons.push(season); // Use archetype preset if available, otherwise fall back to default club preset const presetId = archetype?.scoringPresetId ?? 'club-default'; const infraPreset = getLeagueScoringPresetById(presetId); if (infraPreset) { const config = infraPreset.createConfig({ seasonId: season.id }); seededScoringConfigs.push(config); } } this._gameRepository = new InMemoryGameRepository([game]); this._seasonRepository = new InMemorySeasonRepository(seededSeasons); this._leagueScoringConfigRepository = new InMemoryLeagueScoringConfigRepository(seededScoringConfigs); // League memberships seeded from static memberships with guaranteed owner roles const seededMemberships: LeagueMembership[] = seedData.memberships.map((m) => ({ leagueId: m.leagueId, driverId: m.driverId, role: 'member', status: 'active', joinedAt: new Date(), })); // Ensure each league owner has an owner membership for (const league of seedData.leagues) { const existing = seededMemberships.find( (m) => m.leagueId === league.id && m.driverId === league.ownerId, ); if (!existing) { seededMemberships.push({ leagueId: league.id, driverId: league.ownerId, role: 'owner', status: 'active', joinedAt: new Date(), }); } else { existing.role = 'owner'; } } // Ensure the primary demo driver owns at least one league in memberships const hasPrimaryOwnerMembership = seededMemberships.some( (m: LeagueMembership) => m.driverId === primaryDriverId && m.role === 'owner', ); if (!hasPrimaryOwnerMembership && seedData.leagues.length > 0) { const targetLeague = seedData.leagues.find((l) => l.ownerId === primaryDriverId) ?? seedData.leagues[0]; const existingForPrimary = seededMemberships.find( (m) => m.leagueId === targetLeague.id && m.driverId === primaryDriverId, ); if (existingForPrimary) { existingForPrimary.role = 'owner'; } else { seededMemberships.push({ leagueId: targetLeague.id, driverId: primaryDriverId, role: 'owner', status: 'active', joinedAt: new Date(), }); } } // Seed sample league admins for the primary driver's league (alpha demo) const primaryLeagueForAdmins = seedData.leagues.find((l) => l.ownerId === primaryDriverId) ?? seedData.leagues[0]; if (primaryLeagueForAdmins) { const adminCandidates = seedData.drivers .filter((d) => d.id !== primaryLeagueForAdmins.ownerId) .slice(0, 2); adminCandidates.forEach((driver) => { const existing = seededMemberships.find( (m) => m.leagueId === primaryLeagueForAdmins.id && m.driverId === driver.id, ); if (existing) { if (existing.role !== 'owner') { existing.role = 'admin'; } } else { seededMemberships.push({ leagueId: primaryLeagueForAdmins.id, driverId: driver.id, role: 'admin', status: 'active', joinedAt: new Date(), }); } }); } // Seed sample league stewards for the primary driver's league (alpha demo) if (primaryLeagueForAdmins) { const stewardCandidates = seedData.drivers .filter((d) => d.id !== primaryLeagueForAdmins.ownerId) .slice(2, 5); stewardCandidates.forEach((driver) => { const existing = seededMemberships.find( (m) => m.leagueId === primaryLeagueForAdmins.id && m.driverId === driver.id, ); if (existing) { if (existing.role !== 'owner' && existing.role !== 'admin') { existing.role = 'steward'; } } else { seededMemberships.push({ leagueId: primaryLeagueForAdmins.id, driverId: driver.id, role: 'steward', status: 'active', joinedAt: new Date(), }); } }); } // Seed a few pending join requests for demo leagues (expanded to more leagues) const seededJoinRequests: JoinRequest[] = []; const demoLeagues = seedData.leagues.slice(0, 6); // Expanded from 2 to 6 leagues const extraDrivers = seedData.drivers.slice(5, 12); // More drivers for requests demoLeagues.forEach((league, leagueIndex) => { // Skip leagues where these drivers are already members const memberDriverIds = seededMemberships .filter(m => m.leagueId === league.id) .map(m => m.driverId); const availableDrivers = extraDrivers.filter(d => !memberDriverIds.includes(d.id)); const driversForThisLeague = availableDrivers.slice(0, 3 + (leagueIndex % 3)); // 3-5 requests per league driversForThisLeague.forEach((driver, index) => { const messages = [ 'Would love to race in this series!', 'Looking to join for the upcoming season.', 'Heard great things about this league. Can I join?', 'Experienced driver looking for competitive racing.', 'My friend recommended this league. Hope to race with you!', ]; seededJoinRequests.push({ id: `join-${league.id}-${driver.id}`, leagueId: league.id, driverId: driver.id, requestedAt: new Date(Date.now() - (index + 1 + leagueIndex) * 24 * 60 * 60 * 1000), message: messages[(index + leagueIndex) % messages.length], }); }); }); this._leagueMembershipRepository = new InMemoryLeagueMembershipRepository( seededMemberships, seededJoinRequests, ); // Team repositories seeded from static memberships/teams this._teamRepository = new InMemoryTeamRepository( seedData.teams.map((t) => ({ id: t.id, name: t.name, tag: t.tag, description: t.description, ownerId: seedData.drivers[0]?.id ?? 'driver-1', leagues: [t.primaryLeagueId], createdAt: new Date(), })), ); this._teamMembershipRepository = new InMemoryTeamMembershipRepository( seedData.memberships .filter((m) => m.teamId) .map((m) => ({ teamId: m.teamId!, driverId: m.driverId, role: 'driver', status: 'active', joinedAt: new Date(), })), ); // Application-layer use-cases and queries wired with repositories this._joinLeagueUseCase = new JoinLeagueUseCase(this._leagueMembershipRepository); this._registerForRaceUseCase = new RegisterForRaceUseCase( this._raceRegistrationRepository, this._leagueMembershipRepository, ); this._withdrawFromRaceUseCase = new WithdrawFromRaceUseCase( this._raceRegistrationRepository, ); this._isDriverRegisteredForRaceQuery = new IsDriverRegisteredForRaceQuery( this._raceRegistrationRepository, ); this._getRaceRegistrationsQuery = new GetRaceRegistrationsQuery( this._raceRegistrationRepository, ); this._getLeagueStandingsQuery = new GetLeagueStandingsQuery(this._standingRepository); this._getLeagueDriverSeasonStatsQuery = new GetLeagueDriverSeasonStatsQuery( this._standingRepository, this._resultRepository, this._penaltyRepository, this._raceRepository, { getRating: (driverId: string) => { const stats = driverStats[driverId]; if (!stats) { return { rating: null, ratingChange: null }; } // For alpha we expose current rating and a mock delta const baseline = 1500; const delta = stats.rating - baseline; return { rating: stats.rating, ratingChange: delta !== 0 ? delta : null, }; }, }, ); this._getAllLeaguesWithCapacityQuery = new GetAllLeaguesWithCapacityQuery( this._leagueRepository, this._leagueMembershipRepository, ); this._getAllLeaguesWithCapacityAndScoringQuery = new GetAllLeaguesWithCapacityAndScoringQuery( this._leagueRepository, this._leagueMembershipRepository, this._seasonRepository, this._leagueScoringConfigRepository, this._gameRepository, this._leagueScoringPresetProvider, ); this._listLeagueScoringPresetsQuery = new ListLeagueScoringPresetsQuery( this._leagueScoringPresetProvider, ); this._getLeagueScoringConfigQuery = new GetLeagueScoringConfigQuery( this._leagueRepository, this._seasonRepository, this._leagueScoringConfigRepository, this._gameRepository, this._leagueScoringPresetProvider, ); this._getLeagueFullConfigQuery = new GetLeagueFullConfigQuery( this._leagueRepository, this._seasonRepository, this._leagueScoringConfigRepository, this._gameRepository, ); this._createLeagueWithSeasonAndScoringUseCase = new CreateLeagueWithSeasonAndScoringUseCase( this._leagueRepository, this._seasonRepository, this._leagueScoringConfigRepository, this._leagueScoringPresetProvider, ); // Schedule preview query (used by league creation wizard step 3) this._previewLeagueScheduleQuery = new PreviewLeagueScheduleQuery(); // DriverRatingProvider adapter using driverStats this._driverRatingProvider = { getRating: (driverId: string): number | null => { const stats = driverStats[driverId]; return stats?.rating ?? null; }, getRatings: (driverIds: string[]): Map => { const result = new Map(); for (const id of driverIds) { const stats = driverStats[id]; if (stats?.rating) { result.set(id, stats.rating); } } return result; }, }; // SOF queries this._getRaceWithSOFQuery = new GetRaceWithSOFQuery( this._raceRepository, this._raceRegistrationRepository, this._resultRepository, this._driverRatingProvider, ); this._getLeagueStatsQuery = new GetLeagueStatsQuery( this._leagueRepository, this._raceRepository, this._resultRepository, this._driverRatingProvider, ); this._createTeamUseCase = new CreateTeamUseCase( this._teamRepository, this._teamMembershipRepository, ); this._joinTeamUseCase = new JoinTeamUseCase( this._teamRepository, this._teamMembershipRepository, ); this._leaveTeamUseCase = new LeaveTeamUseCase(this._teamMembershipRepository); this._approveTeamJoinRequestUseCase = new ApproveTeamJoinRequestUseCase( this._teamMembershipRepository, ); this._rejectTeamJoinRequestUseCase = new RejectTeamJoinRequestUseCase( this._teamMembershipRepository, ); this._updateTeamUseCase = new UpdateTeamUseCase( this._teamRepository, this._teamMembershipRepository, ); this._getAllTeamsQuery = new GetAllTeamsQuery(this._teamRepository); this._getTeamDetailsQuery = new GetTeamDetailsQuery( this._teamRepository, this._teamMembershipRepository, ); this._getTeamMembersQuery = new GetTeamMembersQuery(this._teamMembershipRepository); this._getTeamJoinRequestsQuery = new GetTeamJoinRequestsQuery( this._teamMembershipRepository, ); this._getDriverTeamQuery = new GetDriverTeamQuery( this._teamRepository, this._teamMembershipRepository, ); this._transferLeagueOwnershipUseCase = new TransferLeagueOwnershipUseCase( this._leagueRepository, this._leagueMembershipRepository, ); // Stewarding use cases and queries this._fileProtestUseCase = new FileProtestUseCase( this._protestRepository, this._raceRepository, this._leagueMembershipRepository, ); this._reviewProtestUseCase = new ReviewProtestUseCase( this._protestRepository, this._raceRepository, this._leagueMembershipRepository, ); this._applyPenaltyUseCase = new ApplyPenaltyUseCase( this._penaltyRepository, this._protestRepository, this._raceRepository, this._leagueMembershipRepository, ); this._getRaceProtestsQuery = new GetRaceProtestsQuery( this._protestRepository, this._driverRepository, ); this._getRacePenaltiesQuery = new GetRacePenaltiesQuery( this._penaltyRepository, this._driverRepository, ); this._requestProtestDefenseUseCase = new RequestProtestDefenseUseCase( this._protestRepository, this._raceRepository, this._leagueMembershipRepository, ); this._submitProtestDefenseUseCase = new SubmitProtestDefenseUseCase( this._protestRepository, ); // Social and feed adapters backed by static seed this._feedRepository = new InMemoryFeedRepository(seedData); this._socialRepository = new InMemorySocialGraphRepository(seedData); // Image service backed by demo adapter this._imageService = new DemoImageServiceAdapter(); // Seed Track and Car data for demo const seedTracks = [ Track.create({ id: 'track-spa', name: 'Spa-Francorchamps', shortName: 'SPA', country: 'Belgium', category: 'road', difficulty: 'advanced', lengthKm: 7.004, turns: 19, imageUrl: '/images/tracks/spa.jpg', gameId: 'iracing', }), Track.create({ id: 'track-monza', name: 'Autodromo Nazionale Monza', shortName: 'MON', country: 'Italy', category: 'road', difficulty: 'intermediate', lengthKm: 5.793, turns: 11, imageUrl: '/images/tracks/monza.jpg', gameId: 'iracing', }), Track.create({ id: 'track-nurburgring', name: 'Nürburgring Grand Prix', shortName: 'NUR', country: 'Germany', category: 'road', difficulty: 'advanced', lengthKm: 5.148, turns: 15, imageUrl: '/images/tracks/nurburgring.jpg', gameId: 'iracing', }), Track.create({ id: 'track-silverstone', name: 'Silverstone Circuit', shortName: 'SIL', country: 'United Kingdom', category: 'road', difficulty: 'intermediate', lengthKm: 5.891, turns: 18, imageUrl: '/images/tracks/silverstone.jpg', gameId: 'iracing', }), Track.create({ id: 'track-suzuka', name: 'Suzuka International Racing Course', shortName: 'SUZ', country: 'Japan', category: 'road', difficulty: 'expert', lengthKm: 5.807, turns: 18, imageUrl: '/images/tracks/suzuka.jpg', gameId: 'iracing', }), Track.create({ id: 'track-daytona', name: 'Daytona International Speedway', shortName: 'DAY', country: 'United States', category: 'oval', difficulty: 'intermediate', lengthKm: 4.023, turns: 4, imageUrl: '/images/tracks/daytona.jpg', gameId: 'iracing', }), Track.create({ id: 'track-laguna', name: 'WeatherTech Raceway Laguna Seca', shortName: 'LAG', country: 'United States', category: 'road', difficulty: 'advanced', lengthKm: 3.602, turns: 11, imageUrl: '/images/tracks/laguna.jpg', gameId: 'iracing', }), ]; const seedCars = [ Car.create({ id: 'car-porsche-992', name: '911 GT3 R', shortName: '992 GT3R', manufacturer: 'Porsche', carClass: 'gt', license: 'B', year: 2023, horsepower: 565, weight: 1300, gameId: 'iracing', }), Car.create({ id: 'car-ferrari-296', name: '296 GT3', shortName: '296 GT3', manufacturer: 'Ferrari', carClass: 'gt', license: 'B', year: 2023, horsepower: 600, weight: 1270, gameId: 'iracing', }), Car.create({ id: 'car-mclaren-720s', name: '720S GT3 Evo', shortName: '720S', manufacturer: 'McLaren', carClass: 'gt', license: 'B', year: 2023, horsepower: 552, weight: 1290, gameId: 'iracing', }), Car.create({ id: 'car-mercedes-gt3', name: 'AMG GT3 2020', shortName: 'AMG GT3', manufacturer: 'Mercedes', carClass: 'gt', license: 'B', year: 2020, horsepower: 550, weight: 1285, gameId: 'iracing', }), Car.create({ id: 'car-lmp2', name: 'Dallara P217 LMP2', shortName: 'LMP2', manufacturer: 'Dallara', carClass: 'prototype', license: 'A', year: 2021, horsepower: 600, weight: 930, gameId: 'iracing', }), Car.create({ id: 'car-f4', name: 'Formula 4', shortName: 'F4', manufacturer: 'Tatuus', carClass: 'formula', license: 'D', year: 2022, horsepower: 160, weight: 570, gameId: 'iracing', }), Car.create({ id: 'car-mx5', name: 'MX-5 Cup', shortName: 'MX5', manufacturer: 'Mazda', carClass: 'sports', license: 'D', year: 2023, horsepower: 181, weight: 1128, gameId: 'iracing', }), ]; this._trackRepository = new InMemoryTrackRepository(seedTracks); this._carRepository = new InMemoryCarRepository(seedCars); // Initialize notifications this._notificationRepository = new InMemoryNotificationRepository(); this._notificationPreferenceRepository = new InMemoryNotificationPreferenceRepository(); // Set up gateway registry with adapters this._notificationGatewayRegistry = new NotificationGatewayRegistry([ new InAppNotificationAdapter(), // Future: DiscordNotificationAdapter, EmailNotificationAdapter ]); // Notification use cases this._sendNotificationUseCase = new SendNotificationUseCase( this._notificationRepository, this._notificationPreferenceRepository, this._notificationGatewayRegistry, ); this._markNotificationReadUseCase = new MarkNotificationReadUseCase( this._notificationRepository, ); this._getUnreadNotificationsQuery = new GetUnreadNotificationsQuery( this._notificationRepository, ); } /** * Get singleton instance */ static getInstance(): DIContainer { if (!DIContainer.instance) { DIContainer.instance = new DIContainer(); } return DIContainer.instance; } /** * Reset the container (useful for testing) */ static reset(): void { DIContainer.instance = new DIContainer(); } /** * Repository getters */ get driverRepository(): IDriverRepository { return this._driverRepository; } get leagueRepository(): ILeagueRepository { return this._leagueRepository; } get raceRepository(): IRaceRepository { return this._raceRepository; } get resultRepository(): IResultRepository { return this._resultRepository; } get standingRepository(): IStandingRepository { return this._standingRepository; } get penaltyRepository(): IPenaltyRepository { return this._penaltyRepository; } get protestRepository(): IProtestRepository { return this._protestRepository; } get raceRegistrationRepository(): IRaceRegistrationRepository { return this._raceRegistrationRepository; } get leagueMembershipRepository(): ILeagueMembershipRepository { return this._leagueMembershipRepository; } get gameRepository(): IGameRepository { return this._gameRepository; } get seasonRepository(): ISeasonRepository { return this._seasonRepository; } get leagueScoringConfigRepository(): ILeagueScoringConfigRepository { return this._leagueScoringConfigRepository; } get leagueScoringPresetProvider(): LeagueScoringPresetProvider { return this._leagueScoringPresetProvider; } get joinLeagueUseCase(): JoinLeagueUseCase { return this._joinLeagueUseCase; } get registerForRaceUseCase(): RegisterForRaceUseCase { return this._registerForRaceUseCase; } get withdrawFromRaceUseCase(): WithdrawFromRaceUseCase { return this._withdrawFromRaceUseCase; } get isDriverRegisteredForRaceQuery(): IsDriverRegisteredForRaceQuery { return this._isDriverRegisteredForRaceQuery; } get getRaceRegistrationsQuery(): GetRaceRegistrationsQuery { return this._getRaceRegistrationsQuery; } get getLeagueStandingsQuery(): GetLeagueStandingsQuery { return this._getLeagueStandingsQuery; } get getLeagueDriverSeasonStatsQuery(): GetLeagueDriverSeasonStatsQuery { return this._getLeagueDriverSeasonStatsQuery; } get getAllLeaguesWithCapacityQuery(): GetAllLeaguesWithCapacityQuery { return this._getAllLeaguesWithCapacityQuery; } get getAllLeaguesWithCapacityAndScoringQuery(): GetAllLeaguesWithCapacityAndScoringQuery { return this._getAllLeaguesWithCapacityAndScoringQuery; } get listLeagueScoringPresetsQuery(): ListLeagueScoringPresetsQuery { return this._listLeagueScoringPresetsQuery; } get getLeagueScoringConfigQuery(): GetLeagueScoringConfigQuery { return this._getLeagueScoringConfigQuery; } get getLeagueFullConfigQuery(): GetLeagueFullConfigQuery { return this._getLeagueFullConfigQuery; } // Placeholder accessor for schedule preview; API route/UI can call this later. get previewLeagueScheduleQuery(): PreviewLeagueScheduleQuery { return this._previewLeagueScheduleQuery; } get getRaceWithSOFQuery(): GetRaceWithSOFQuery { return this._getRaceWithSOFQuery; } get getLeagueStatsQuery(): GetLeagueStatsQuery { return this._getLeagueStatsQuery; } get driverRatingProvider(): DriverRatingProvider { return this._driverRatingProvider; } get createLeagueWithSeasonAndScoringUseCase(): CreateLeagueWithSeasonAndScoringUseCase { return this._createLeagueWithSeasonAndScoringUseCase; } get createTeamUseCase(): CreateTeamUseCase { return this._createTeamUseCase; } get joinTeamUseCase(): JoinTeamUseCase { return this._joinTeamUseCase; } get leaveTeamUseCase(): LeaveTeamUseCase { return this._leaveTeamUseCase; } get approveTeamJoinRequestUseCase(): ApproveTeamJoinRequestUseCase { return this._approveTeamJoinRequestUseCase; } get rejectTeamJoinRequestUseCase(): RejectTeamJoinRequestUseCase { return this._rejectTeamJoinRequestUseCase; } get updateTeamUseCase(): UpdateTeamUseCase { return this._updateTeamUseCase; } get getAllTeamsQuery(): GetAllTeamsQuery { return this._getAllTeamsQuery; } get getTeamDetailsQuery(): GetTeamDetailsQuery { return this._getTeamDetailsQuery; } get getTeamMembersQuery(): GetTeamMembersQuery { return this._getTeamMembersQuery; } get getTeamJoinRequestsQuery(): GetTeamJoinRequestsQuery { return this._getTeamJoinRequestsQuery; } get getDriverTeamQuery(): GetDriverTeamQuery { return this._getDriverTeamQuery; } get teamRepository(): ITeamRepository { return this._teamRepository; } get teamMembershipRepository(): ITeamMembershipRepository { return this._teamMembershipRepository; } get feedRepository(): IFeedRepository { return this._feedRepository; } get socialRepository(): ISocialGraphRepository { return this._socialRepository; } get imageService(): ImageServicePort { return this._imageService; } get trackRepository(): ITrackRepository { return this._trackRepository; } get carRepository(): ICarRepository { return this._carRepository; } get notificationRepository(): INotificationRepository { return this._notificationRepository; } get notificationPreferenceRepository(): INotificationPreferenceRepository { return this._notificationPreferenceRepository; } get sendNotificationUseCase(): SendNotificationUseCase { return this._sendNotificationUseCase; } get markNotificationReadUseCase(): MarkNotificationReadUseCase { return this._markNotificationReadUseCase; } get getUnreadNotificationsQuery(): GetUnreadNotificationsQuery { return this._getUnreadNotificationsQuery; } get fileProtestUseCase(): FileProtestUseCase { return this._fileProtestUseCase; } get reviewProtestUseCase(): ReviewProtestUseCase { return this._reviewProtestUseCase; } get applyPenaltyUseCase(): ApplyPenaltyUseCase { return this._applyPenaltyUseCase; } get getRaceProtestsQuery(): GetRaceProtestsQuery { return this._getRaceProtestsQuery; } get getRacePenaltiesQuery(): GetRacePenaltiesQuery { return this._getRacePenaltiesQuery; } get requestProtestDefenseUseCase(): RequestProtestDefenseUseCase { return this._requestProtestDefenseUseCase; } get submitProtestDefenseUseCase(): SubmitProtestDefenseUseCase { return this._submitProtestDefenseUseCase; } get transferLeagueOwnershipUseCase(): TransferLeagueOwnershipUseCase { return this._transferLeagueOwnershipUseCase; } } /** * Exported accessor functions */ export function getDriverRepository(): IDriverRepository { return DIContainer.getInstance().driverRepository; } export function getLeagueRepository(): ILeagueRepository { return DIContainer.getInstance().leagueRepository; } export function getRaceRepository(): IRaceRepository { return DIContainer.getInstance().raceRepository; } export function getResultRepository(): IResultRepository { return DIContainer.getInstance().resultRepository; } export function getStandingRepository(): IStandingRepository { return DIContainer.getInstance().standingRepository; } export function getPenaltyRepository(): IPenaltyRepository { return DIContainer.getInstance().penaltyRepository; } export function getProtestRepository(): IProtestRepository { return DIContainer.getInstance().protestRepository; } export function getRaceRegistrationRepository(): IRaceRegistrationRepository { return DIContainer.getInstance().raceRegistrationRepository; } export function getLeagueMembershipRepository(): ILeagueMembershipRepository { return DIContainer.getInstance().leagueMembershipRepository; } export function getJoinLeagueUseCase(): JoinLeagueUseCase { return DIContainer.getInstance().joinLeagueUseCase; } export function getRegisterForRaceUseCase(): RegisterForRaceUseCase { return DIContainer.getInstance().registerForRaceUseCase; } export function getWithdrawFromRaceUseCase(): WithdrawFromRaceUseCase { return DIContainer.getInstance().withdrawFromRaceUseCase; } export function getIsDriverRegisteredForRaceQuery(): IsDriverRegisteredForRaceQuery { return DIContainer.getInstance().isDriverRegisteredForRaceQuery; } export function getGetRaceRegistrationsQuery(): GetRaceRegistrationsQuery { return DIContainer.getInstance().getRaceRegistrationsQuery; } export function getGetLeagueStandingsQuery(): GetLeagueStandingsQuery { return DIContainer.getInstance().getLeagueStandingsQuery; } export function getGetLeagueDriverSeasonStatsQuery(): GetLeagueDriverSeasonStatsQuery { return DIContainer.getInstance().getLeagueDriverSeasonStatsQuery; } export function getGetAllLeaguesWithCapacityQuery(): GetAllLeaguesWithCapacityQuery { return DIContainer.getInstance().getAllLeaguesWithCapacityQuery; } export function getGetAllLeaguesWithCapacityAndScoringQuery(): GetAllLeaguesWithCapacityAndScoringQuery { return DIContainer.getInstance().getAllLeaguesWithCapacityAndScoringQuery; } export function getGetLeagueScoringConfigQuery(): GetLeagueScoringConfigQuery { return DIContainer.getInstance().getLeagueScoringConfigQuery; } export function getGetLeagueFullConfigQuery(): GetLeagueFullConfigQuery { return DIContainer.getInstance().getLeagueFullConfigQuery; } // Placeholder export for future schedule preview API wiring. export function getPreviewLeagueScheduleQuery(): PreviewLeagueScheduleQuery { return DIContainer.getInstance().previewLeagueScheduleQuery; } export function getListLeagueScoringPresetsQuery(): ListLeagueScoringPresetsQuery { return DIContainer.getInstance().listLeagueScoringPresetsQuery; } export function getCreateLeagueWithSeasonAndScoringUseCase(): CreateLeagueWithSeasonAndScoringUseCase { return DIContainer.getInstance().createLeagueWithSeasonAndScoringUseCase; } export function getGetRaceWithSOFQuery(): GetRaceWithSOFQuery { return DIContainer.getInstance().getRaceWithSOFQuery; } export function getGetLeagueStatsQuery(): GetLeagueStatsQuery { return DIContainer.getInstance().getLeagueStatsQuery; } export function getDriverRatingProvider(): DriverRatingProvider { return DIContainer.getInstance().driverRatingProvider; } export function getTeamRepository(): ITeamRepository { return DIContainer.getInstance().teamRepository; } export function getTeamMembershipRepository(): ITeamMembershipRepository { return DIContainer.getInstance().teamMembershipRepository; } export function getCreateTeamUseCase(): CreateTeamUseCase { return DIContainer.getInstance().createTeamUseCase; } export function getJoinTeamUseCase(): JoinTeamUseCase { return DIContainer.getInstance().joinTeamUseCase; } export function getLeaveTeamUseCase(): LeaveTeamUseCase { return DIContainer.getInstance().leaveTeamUseCase; } export function getApproveTeamJoinRequestUseCase(): ApproveTeamJoinRequestUseCase { return DIContainer.getInstance().approveTeamJoinRequestUseCase; } export function getRejectTeamJoinRequestUseCase(): RejectTeamJoinRequestUseCase { return DIContainer.getInstance().rejectTeamJoinRequestUseCase; } export function getUpdateTeamUseCase(): UpdateTeamUseCase { return DIContainer.getInstance().updateTeamUseCase; } export function getGetAllTeamsQuery(): GetAllTeamsQuery { return DIContainer.getInstance().getAllTeamsQuery; } export function getGetTeamDetailsQuery(): GetTeamDetailsQuery { return DIContainer.getInstance().getTeamDetailsQuery; } export function getGetTeamMembersQuery(): GetTeamMembersQuery { return DIContainer.getInstance().getTeamMembersQuery; } export function getGetTeamJoinRequestsQuery(): GetTeamJoinRequestsQuery { return DIContainer.getInstance().getTeamJoinRequestsQuery; } export function getGetDriverTeamQuery(): GetDriverTeamQuery { return DIContainer.getInstance().getDriverTeamQuery; } export function getFeedRepository(): IFeedRepository { return DIContainer.getInstance().feedRepository; } export function getSocialRepository(): ISocialGraphRepository { return DIContainer.getInstance().socialRepository; } export function getImageService(): ImageServicePort { return DIContainer.getInstance().imageService; } export function getTrackRepository(): ITrackRepository { return DIContainer.getInstance().trackRepository; } export function getCarRepository(): ICarRepository { return DIContainer.getInstance().carRepository; } export function getNotificationRepository(): INotificationRepository { return DIContainer.getInstance().notificationRepository; } export function getNotificationPreferenceRepository(): INotificationPreferenceRepository { return DIContainer.getInstance().notificationPreferenceRepository; } export function getSendNotificationUseCase(): SendNotificationUseCase { return DIContainer.getInstance().sendNotificationUseCase; } export function getMarkNotificationReadUseCase(): MarkNotificationReadUseCase { return DIContainer.getInstance().markNotificationReadUseCase; } export function getGetUnreadNotificationsQuery(): GetUnreadNotificationsQuery { return DIContainer.getInstance().getUnreadNotificationsQuery; } export function getFileProtestUseCase(): FileProtestUseCase { return DIContainer.getInstance().fileProtestUseCase; } export function getReviewProtestUseCase(): ReviewProtestUseCase { return DIContainer.getInstance().reviewProtestUseCase; } export function getApplyPenaltyUseCase(): ApplyPenaltyUseCase { return DIContainer.getInstance().applyPenaltyUseCase; } export function getGetRaceProtestsQuery(): GetRaceProtestsQuery { return DIContainer.getInstance().getRaceProtestsQuery; } export function getGetRacePenaltiesQuery(): GetRacePenaltiesQuery { return DIContainer.getInstance().getRacePenaltiesQuery; } export function getRequestProtestDefenseUseCase(): RequestProtestDefenseUseCase { return DIContainer.getInstance().requestProtestDefenseUseCase; } export function getSubmitProtestDefenseUseCase(): SubmitProtestDefenseUseCase { return DIContainer.getInstance().submitProtestDefenseUseCase; } export function getTransferLeagueOwnershipUseCase(): TransferLeagueOwnershipUseCase { return DIContainer.getInstance().transferLeagueOwnershipUseCase; } /** * Reset function for testing */ export function resetContainer(): void { DIContainer.reset(); } /** * Get driver statistics and rankings */ export function getDriverStats(driverId: string): DriverStats | null { return driverStats[driverId] || null; } /** * Get all driver rankings sorted by rating */ export function getAllDriverRankings(): DriverStats[] { return Object.values(driverStats).sort((a, b) => b.rating - a.rating); } /** * Get league-specific rankings for a driver */ export function getLeagueRankings(driverId: string, leagueId: string): { rank: number; totalDrivers: number; percentile: number; } { // Mock league rankings (in production, calculate from actual league membership) const mockLeagueRanks: Record> = { 'league-1': { 'driver-1': { rank: 1, totalDrivers: 12, percentile: 92 }, 'driver-2': { rank: 2, totalDrivers: 12, percentile: 84 }, 'driver-3': { rank: 4, totalDrivers: 12, percentile: 67 }, 'driver-4': { rank: 5, totalDrivers: 12, percentile: 58 } } }; return mockLeagueRanks[leagueId]?.[driverId] || { rank: 0, totalDrivers: 0, percentile: 0 }; }