import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository'; import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository'; import type { IGameRepository } from '../../domain/repositories/IGameRepository'; import type { LeagueScoringPresetProvider, LeagueScoringPresetDTO, } from '../ports/LeagueScoringPresetProvider'; import type { LeagueSummaryDTO, LeagueSummaryScoringDTO, } from '../dto/LeagueSummaryDTO'; /** * Combined capacity + scoring summary query for leagues. * * Extends the behavior of GetAllLeaguesWithCapacityQuery by including * scoring preset and game summaries when an active season and * LeagueScoringConfig are available. */ export class GetAllLeaguesWithCapacityAndScoringQuery { constructor( private readonly leagueRepository: ILeagueRepository, private readonly leagueMembershipRepository: ILeagueMembershipRepository, private readonly seasonRepository: ISeasonRepository, private readonly leagueScoringConfigRepository: ILeagueScoringConfigRepository, private readonly gameRepository: IGameRepository, private readonly presetProvider: LeagueScoringPresetProvider, ) {} async execute(): Promise { const leagues = await this.leagueRepository.findAll(); const results: LeagueSummaryDTO[] = []; for (const league of leagues) { const members = await this.leagueMembershipRepository.getLeagueMembers( league.id, ); const usedDriverSlots = members.filter( (m) => m.status === 'active' && (m.role === 'owner' || m.role === 'admin' || m.role === 'steward' || m.role === 'member'), ).length; const configuredMaxDrivers = league.settings.maxDrivers ?? usedDriverSlots; const safeMaxDrivers = Math.max(configuredMaxDrivers, usedDriverSlots); const scoringSummary = await this.buildScoringSummary(league.id); const structureSummary = `Solo • ${safeMaxDrivers} drivers`; const qualifyingMinutes = 30; const mainRaceMinutes = typeof league.settings.sessionDuration === 'number' ? league.settings.sessionDuration : 40; const timingSummary = `${qualifyingMinutes} min Quali • ${mainRaceMinutes} min Race`; const dto: LeagueSummaryDTO = { id: league.id, name: league.name, description: league.description, ownerId: league.ownerId, createdAt: league.createdAt, maxDrivers: safeMaxDrivers, usedDriverSlots, maxTeams: undefined, usedTeamSlots: undefined, structureSummary, scoringPatternSummary: scoringSummary?.scoringPatternSummary, timingSummary, scoring: scoringSummary, }; results.push(dto); } return results; } private async buildScoringSummary( leagueId: string, ): Promise { const seasons = await this.seasonRepository.findByLeagueId(leagueId); if (!seasons || seasons.length === 0) { return undefined; } const activeSeason = seasons.find((s) => s.status === 'active') ?? seasons[0]; const scoringConfig = await this.leagueScoringConfigRepository.findBySeasonId(activeSeason.id); if (!scoringConfig) { return undefined; } const game = await this.gameRepository.findById(activeSeason.gameId); if (!game) { return undefined; } const presetId = scoringConfig.scoringPresetId; let preset: LeagueScoringPresetDTO | undefined; if (presetId) { preset = this.presetProvider.getPresetById(presetId); } const dropPolicySummary = preset?.dropPolicySummary ?? this.deriveDropPolicySummary(scoringConfig); const primaryChampionshipType = preset?.primaryChampionshipType ?? (scoringConfig.championships[0]?.type ?? 'driver'); const scoringPresetName = preset?.name ?? 'Custom'; const scoringPatternSummary = `${scoringPresetName} • ${dropPolicySummary}`; return { gameId: game.id, gameName: game.name, primaryChampionshipType, scoringPresetId: presetId ?? 'custom', scoringPresetName, dropPolicySummary, scoringPatternSummary, }; } private deriveDropPolicySummary(config: { championships: Array<{ dropScorePolicy: { strategy: string; count?: number; dropCount?: number }; }>; }): string { const championship = config.championships[0]; if (!championship) { return 'All results count'; } const policy = championship.dropScorePolicy; if (!policy || policy.strategy === 'none') { return 'All results count'; } if (policy.strategy === 'bestNResults' && typeof policy.count === 'number') { return `Best ${policy.count} results count`; } if ( policy.strategy === 'dropWorstN' && typeof policy.dropCount === 'number' ) { return `Worst ${policy.dropCount} results are dropped`; } return 'Custom drop score rules'; } }