import type { Logger } from '@core/shared/domain/Logger'; import { Result } from '@core/shared/domain/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { Race } from '../../domain/entities/Race'; import type { Season } from '../../domain/entities/season/Season'; import { RaceRepository } from '../../domain/repositories/RaceRepository'; import { SeasonRepository } from '../../domain/repositories/SeasonRepository'; import { SeasonScheduleGenerator } from '../../domain/services/SeasonScheduleGenerator'; export type UpdateLeagueSeasonScheduleRaceInput = { leagueId: string; seasonId: string; raceId: string; track?: string; car?: string; scheduledAt?: Date; }; export type UpdateLeagueSeasonScheduleRaceResult = { success: true; }; export type UpdateLeagueSeasonScheduleRaceErrorCode = | 'SEASON_NOT_FOUND' | 'RACE_NOT_FOUND' | 'RACE_OUTSIDE_SEASON_WINDOW' | 'INVALID_INPUT' | 'REPOSITORY_ERROR'; export class UpdateLeagueSeasonScheduleRaceUseCase { constructor(private readonly seasonRepository: SeasonRepository, private readonly raceRepository: RaceRepository, private readonly logger: Logger) {} async execute( input: UpdateLeagueSeasonScheduleRaceInput, ): Promise< Result< UpdateLeagueSeasonScheduleRaceResult, ApplicationErrorCode > > { this.logger.debug('Updating league season schedule race', { leagueId: input.leagueId, seasonId: input.seasonId, raceId: input.raceId, }); try { const season = await this.seasonRepository.findById(input.seasonId); if (!season || season.leagueId !== input.leagueId) { return Result.err({ code: 'SEASON_NOT_FOUND', details: { message: 'Season not found for league' }, }); } const existing = await this.raceRepository.findById(input.raceId); if (!existing || existing.leagueId !== input.leagueId) { return Result.err({ code: 'RACE_NOT_FOUND', details: { message: 'Race not found for league' }, }); } const nextTrack = input.track ?? existing.track; const nextCar = input.car ?? existing.car; const nextScheduledAt = input.scheduledAt ?? existing.scheduledAt; if (!this.isWithinSeasonWindow(season, nextScheduledAt)) { return Result.err({ code: 'RACE_OUTSIDE_SEASON_WINDOW', details: { message: 'Race scheduledAt is outside the season schedule window' }, }); } let updated: Race; try { updated = Race.create({ id: existing.id, leagueId: existing.leagueId, track: nextTrack, car: nextCar, scheduledAt: nextScheduledAt, ...(existing.trackId !== undefined ? { trackId: existing.trackId } : {}), ...(existing.carId !== undefined ? { carId: existing.carId } : {}), sessionType: existing.sessionType, status: existing.status.toString(), ...(existing.strengthOfFieldNumber !== undefined ? { strengthOfField: existing.strengthOfFieldNumber } : {}), ...(existing.registeredCountNumber !== undefined ? { registeredCount: existing.registeredCountNumber } : {}), ...(existing.maxParticipantsNumber !== undefined ? { maxParticipants: existing.maxParticipantsNumber } : {}), }); } catch (err) { const message = err instanceof Error ? err.message : 'Invalid race update'; return Result.err({ code: 'INVALID_INPUT', details: { message } }); } await this.raceRepository.update(updated); const result: UpdateLeagueSeasonScheduleRaceResult = { success: true }; return Result.ok(result); } catch (err) { const error = err instanceof Error ? err : new Error('Unknown error'); this.logger.error('Failed to update league season schedule race', error, { leagueId: input.leagueId, seasonId: input.seasonId, raceId: input.raceId, }); return Result.err({ code: 'REPOSITORY_ERROR', details: { message: error.message }, }); } } private isWithinSeasonWindow(season: Season, scheduledAt: Date): boolean { const { start, endInclusive } = this.getSeasonDateWindow(season); if (!start && !endInclusive) return true; const t = scheduledAt.getTime(); if (start && t < start.getTime()) return false; if (endInclusive && t > endInclusive.getTime()) return false; return true; } private getSeasonDateWindow(season: Season): { start?: Date; endInclusive?: Date } { const start = season.startDate ?? season.schedule?.startDate; const window: { start?: Date; endInclusive?: Date } = {}; if (start) { window.start = start; } if (season.endDate) { window.endInclusive = season.endDate; return window; } if (season.schedule) { const slots = SeasonScheduleGenerator.generateSlotsUpTo( season.schedule, season.schedule.plannedRounds, ); const last = slots.at(-1); if (last?.scheduledAt) { window.endInclusive = last.scheduledAt; } return window; } return window; } }