import { v4 as uuidv4 } from 'uuid'; import { League } from '../../domain/entities/League'; import { Season } from '../../domain/entities/season/Season'; import { LeagueScoringConfig } from '../../domain/entities/LeagueScoringConfig'; import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository'; import type { Logger, UseCaseOutputPort } from '@core/shared/application'; import { LeagueVisibility, MIN_RANKED_LEAGUE_DRIVERS, } from '../../domain/value-objects/LeagueVisibility'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; 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: ILeagueRepository, private readonly seasonRepository: ISeasonRepository, private readonly leagueScoringConfigRepository: ILeagueScoringConfigRepository, private readonly getLeagueScoringPresetById: (input: { presetId: string }) => Promise, private readonly logger: Logger, private readonly output: UseCaseOutputPort, ) {} async execute( command: CreateLeagueWithSeasonAndScoringCommand, ): Promise>> { 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 scoringConfig = LeagueScoringConfig.create({ seasonId, scoringPresetId: preset.id, championships: [], // Empty array - will be populated by preset }); 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 }); this.output.present(result); return Result.ok(undefined); } catch (error) { return Result.err({ code: 'REPOSITORY_ERROR', details: { message: error instanceof Error ? error.message : 'Unknown error', }, }); } } 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); } }