import type { ChampionshipConfig } from '@gridpilot/racing/domain/types/ChampionshipConfig'; import type { BonusRule } from '@gridpilot/racing/domain/types/BonusRule'; import type { ILeagueScoringConfigPresenter, LeagueScoringConfigData, LeagueScoringConfigViewModel, LeagueScoringChampionshipViewModel, } from '@gridpilot/racing/application/presenters/ILeagueScoringConfigPresenter'; export class LeagueScoringConfigPresenter implements ILeagueScoringConfigPresenter { private viewModel: LeagueScoringConfigViewModel | null = null; reset(): void { this.viewModel = null; } present(data: LeagueScoringConfigData): LeagueScoringConfigViewModel { const championships: LeagueScoringChampionshipViewModel[] = data.championships.map((champ) => this.mapChampionship(champ)); const dropPolicySummary = data.preset?.dropPolicySummary ?? this.deriveDropPolicyDescriptionFromChampionships(data.championships); this.viewModel = { leagueId: data.leagueId, seasonId: data.seasonId, gameId: data.gameId, gameName: data.gameName, scoringPresetId: data.scoringPresetId ?? 'custom', scoringPresetName: data.preset?.name ?? 'Custom', dropPolicySummary, championships, }; return this.viewModel; } getViewModel(): LeagueScoringConfigViewModel { if (!this.viewModel) { throw new Error('Presenter has not been called yet'); } return this.viewModel; } private mapChampionship(championship: ChampionshipConfig): LeagueScoringChampionshipViewModel { const sessionTypes = championship.sessionTypes.map((s) => s.toString()); const pointsPreview = this.buildPointsPreview(championship.pointsTableBySessionType); const bonusSummary = this.buildBonusSummary( championship.bonusRulesBySessionType ?? {}, ); const dropPolicyDescription = this.deriveDropPolicyDescription( championship.dropScorePolicy, ); return { id: championship.id, name: championship.name, type: championship.type, sessionTypes, pointsPreview, bonusSummary, dropPolicyDescription, }; } private buildPointsPreview( tables: Record number }>, ): Array<{ sessionType: string; position: number; points: number }> { const preview: Array<{ sessionType: string; position: number; points: number; }> = []; const maxPositions = 10; for (const [sessionType, table] of Object.entries(tables)) { for (let pos = 1; pos <= maxPositions; pos++) { const points = table.getPointsForPosition(pos); if (points && points !== 0) { preview.push({ sessionType, position: pos, points, }); } } } return preview; } private buildBonusSummary( bonusRulesBySessionType: Record, ): string[] { const summaries: string[] = []; for (const [sessionType, rules] of Object.entries(bonusRulesBySessionType)) { for (const rule of rules) { if (rule.type === 'fastestLap') { const base = `Fastest lap in ${sessionType}`; if (rule.requiresFinishInTopN) { summaries.push( `${base} +${rule.points} points if finishing P${rule.requiresFinishInTopN} or better`, ); } else { summaries.push(`${base} +${rule.points} points`); } } else { summaries.push( `${rule.type} bonus in ${sessionType} worth ${rule.points} points`, ); } } } return summaries; } private deriveDropPolicyDescriptionFromChampionships( championships: ChampionshipConfig[], ): string { const first = championships[0]; if (!first) { return 'All results count'; } return this.deriveDropPolicyDescription(first.dropScorePolicy); } private deriveDropPolicyDescription(policy: { strategy: string; count?: number; dropCount?: number; }): string { if (!policy || policy.strategy === 'none') { return 'All results count'; } if (policy.strategy === 'bestNResults' && typeof policy.count === 'number') { return `Best ${policy.count} results count towards the championship`; } if ( policy.strategy === 'dropWorstN' && typeof policy.dropCount === 'number' ) { return `Worst ${policy.dropCount} results are dropped from the championship total`; } return 'Custom drop score rules apply'; } }