import type { Logger } from '@core/shared/domain/Logger'; import { Result } from '@core/shared/domain/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { v4 as uuidv4 } from 'uuid'; import { League } from '../../domain/entities/League'; import { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig'; import { Season } from '../../domain/entities/season/Season'; import { LeagueRepository } from '../../domain/repositories/LeagueRepository'; import { LeagueScoringConfigRepository } from '../../domain/repositories/LeagueScoringConfigRepository'; import { SeasonRepository } from '../../domain/repositories/SeasonRepository'; import type { BonusRule } from '../../domain/types/BonusRule'; import type { ChampionshipConfig } from '../../domain/types/ChampionshipConfig'; import type { SessionType } from '../../domain/types/SessionType'; import { LeagueVisibility, MIN_RANKED_LEAGUE_DRIVERS, } from '../../domain/value-objects/LeagueVisibility'; import { PointsTable } from '../../domain/value-objects/PointsTable'; export type CreateLeagueWithSeasonAndScoringCommand = { name: string; description?: string; /** * League visibility/ranking mode. * - 'ranked' (or legacy 'public'): Competitive, public, affects ratings. Requires min 10 drivers. * - 'unranked' (or legacy 'private'): Casual with friends, no rating impact. */ visibility: string; ownerId: string; gameId: string; maxDrivers?: number; maxTeams?: number; enableDriverChampionship: boolean; enableTeamChampionship: boolean; enableNationsChampionship: boolean; enableTrophyChampionship: boolean; scoringPresetId?: string; }; export type CreateLeagueWithSeasonAndScoringResult = { league: League; season: Season; scoringConfig: LeagueScoringConfig; }; type CreateLeagueWithSeasonAndScoringErrorCode = | 'VALIDATION_ERROR' | 'UNKNOWN_PRESET' | 'REPOSITORY_ERROR'; type ScoringPreset = { id: string; name: string; }; export class CreateLeagueWithSeasonAndScoringUseCase { constructor( private readonly leagueRepository: LeagueRepository, private readonly seasonRepository: SeasonRepository, private readonly leagueScoringConfigRepository: LeagueScoringConfigRepository, private readonly getLeagueScoringPresetById: (input: { presetId: string }) => Promise, private readonly logger: Logger, ) {} async execute( command: CreateLeagueWithSeasonAndScoringCommand, ): Promise< Result< CreateLeagueWithSeasonAndScoringResult, ApplicationErrorCode > > { this.logger.debug('Executing CreateLeagueWithSeasonAndScoringUseCase', { command }); const validation = this.validate(command); if (validation.isErr()) { return Result.err(validation.unwrapErr()); } this.logger.info('Command validated successfully.'); try { const leagueId = uuidv4(); this.logger.debug(`Generated leagueId: ${leagueId}`); const league = League.create({ id: leagueId, name: command.name, description: command.description ?? '', ownerId: command.ownerId, settings: { pointsSystem: 'custom', ...(command.maxDrivers !== undefined ? { maxDrivers: command.maxDrivers } : {}), }, }); await this.leagueRepository.create(league); this.logger.info(`League ${league.name} (${league.id}) created successfully.`); const seasonId = uuidv4(); this.logger.debug(`Generated seasonId: ${seasonId}`); const season = Season.create({ id: seasonId, leagueId: league.id.toString(), gameId: command.gameId, name: `${command.name} Season 1`, year: new Date().getFullYear(), order: 1, status: 'active', startDate: new Date(), endDate: new Date(), }); await this.seasonRepository.create(season); this.logger.info(`Season ${season.name} (${season.id}) created for league ${league.id}.`); const presetId = command.scoringPresetId ?? 'club-default'; this.logger.debug(`Attempting to retrieve scoring preset: ${presetId}`); const preset = await this.getLeagueScoringPresetById({ presetId }); if (!preset) { this.logger.error(`Unknown scoring preset: ${presetId}`); return Result.err({ code: 'UNKNOWN_PRESET', details: { message: `Unknown scoring preset: ${presetId}` } }); } this.logger.info(`Scoring preset ${preset.name} (${preset.id}) retrieved.`); const championships = this.createDefaultChampionshipConfigs(command); const scoringConfig = LeagueScoringConfig.create({ seasonId, scoringPresetId: preset.id, championships, }); this.logger.debug(`Scoring configuration created from preset ${preset.id}.`); await this.leagueScoringConfigRepository.save(scoringConfig); this.logger.info(`Scoring configuration saved for season ${seasonId}.`); const result: CreateLeagueWithSeasonAndScoringResult = { league, season, scoringConfig, }; this.logger.debug('CreateLeagueWithSeasonAndScoringUseCase completed successfully.', { result }); return Result.ok(result); } catch (error) { return Result.err({ code: 'REPOSITORY_ERROR', details: { message: error instanceof Error ? error.message : 'Unknown error', }, }); } } private createDefaultChampionshipConfigs( command: CreateLeagueWithSeasonAndScoringCommand, ): ChampionshipConfig[] { const sessionTypes: SessionType[] = ['main']; const defaultPoints = [25, 18, 15, 12, 10, 8, 6, 4, 2, 1]; const pointsMap: Record = {}; defaultPoints.forEach((points, index) => { pointsMap[index + 1] = points; }); const pointsTableBySessionType: Record = {} as Record; const bonusRulesBySessionType: Record = {} as Record; for (const sessionType of sessionTypes) { pointsTableBySessionType[sessionType] = new PointsTable(pointsMap); bonusRulesBySessionType[sessionType] = []; } const configs: ChampionshipConfig[] = []; if (command.enableDriverChampionship) { configs.push({ id: uuidv4(), name: 'Driver Championship', type: 'driver', sessionTypes, pointsTableBySessionType, bonusRulesBySessionType, dropScorePolicy: { strategy: 'none' }, }); } if (command.enableTeamChampionship) { configs.push({ id: uuidv4(), name: 'Team Championship', type: 'team', sessionTypes, pointsTableBySessionType, bonusRulesBySessionType, dropScorePolicy: { strategy: 'none' }, }); } if (command.enableNationsChampionship) { configs.push({ id: uuidv4(), name: 'Nations Championship', type: 'nations', sessionTypes, pointsTableBySessionType, bonusRulesBySessionType, dropScorePolicy: { strategy: 'none' }, }); } if (command.enableTrophyChampionship) { configs.push({ id: uuidv4(), name: 'Trophy Championship', type: 'trophy', sessionTypes, pointsTableBySessionType, bonusRulesBySessionType, dropScorePolicy: { strategy: 'none' }, }); } if (configs.length === 0) { configs.push({ id: uuidv4(), name: 'Driver Championship', type: 'driver', sessionTypes, pointsTableBySessionType, bonusRulesBySessionType, dropScorePolicy: { strategy: 'none' }, }); } return configs; } private validate( command: CreateLeagueWithSeasonAndScoringCommand, ): Result> { this.logger.debug('Validating CreateLeagueWithSeasonAndScoringCommand', { command }); if (!command.name || command.name.trim().length === 0) { this.logger.warn('Validation failed: League name is required', { command }); return Result.err({ code: 'VALIDATION_ERROR', details: { message: 'League name is required' } }); } if (!command.ownerId || command.ownerId.trim().length === 0) { this.logger.warn('Validation failed: League ownerId is required', { command }); return Result.err({ code: 'VALIDATION_ERROR', details: { message: 'League ownerId is required' } }); } if (!command.gameId || command.gameId.trim().length === 0) { this.logger.warn('Validation failed: gameId is required', { command }); return Result.err({ code: 'VALIDATION_ERROR', details: { message: 'gameId is required' } }); } if (!command.visibility) { this.logger.warn('Validation failed: visibility is required', { command }); return Result.err({ code: 'VALIDATION_ERROR', details: { message: 'visibility is required' } }); } if (command.maxDrivers !== undefined && command.maxDrivers <= 0) { this.logger.warn('Validation failed: maxDrivers must be greater than 0 when provided', { command }); return Result.err({ code: 'VALIDATION_ERROR', details: { message: 'maxDrivers must be greater than 0 when provided' } }); } const visibility = LeagueVisibility.fromString(command.visibility); if (visibility.isRanked()) { const driverCount = command.maxDrivers ?? 0; if (driverCount < MIN_RANKED_LEAGUE_DRIVERS) { this.logger.warn( `Validation failed: Ranked leagues require at least ${MIN_RANKED_LEAGUE_DRIVERS} drivers. Current setting: ${driverCount}.`, { command } ); return Result.err({ code: 'VALIDATION_ERROR', details: { message: `Ranked leagues require at least ${MIN_RANKED_LEAGUE_DRIVERS} drivers. ` + `Current setting: ${driverCount}. ` + `For smaller groups, consider creating an Unranked (Friends) league instead.` } }); } } this.logger.debug('Validation successful.'); return Result.ok(undefined); } }