import type { LeagueTimingsFormDTO } from './LeagueConfigFormDTO'; import type { Weekday } from '../../domain/types/Weekday'; import { RaceTimeOfDay } from '../../domain/value-objects/RaceTimeOfDay'; import { LeagueTimezone } from '../../domain/value-objects/LeagueTimezone'; import { WeekdaySet } from '../../domain/value-objects/WeekdaySet'; import { MonthlyRecurrencePattern } from '../../domain/value-objects/MonthlyRecurrencePattern'; import type { RecurrenceStrategy } from '../../domain/value-objects/RecurrenceStrategy'; import { RecurrenceStrategyFactory } from '../../domain/value-objects/RecurrenceStrategy'; import { SeasonSchedule } from '../../domain/value-objects/SeasonSchedule'; import { BusinessRuleViolationError } from '../errors/RacingApplicationError'; export interface LeagueScheduleDTO { seasonStartDate: string; raceStartTime: string; timezoneId: string; recurrenceStrategy: 'weekly' | 'everyNWeeks' | 'monthlyNthWeekday'; intervalWeeks?: number; weekdays?: Weekday[]; monthlyOrdinal?: 1 | 2 | 3 | 4; monthlyWeekday?: Weekday; plannedRounds: number; } export interface LeagueSchedulePreviewDTO { rounds: Array<{ roundNumber: number; scheduledAt: string; timezoneId: string }>; summary: string; } export function leagueTimingsToScheduleDTO( timings: LeagueTimingsFormDTO, ): LeagueScheduleDTO | null { if ( !timings.seasonStartDate || !timings.raceStartTime || !timings.timezoneId || !timings.recurrenceStrategy || !timings.roundsPlanned ) { return null; } return { seasonStartDate: timings.seasonStartDate, raceStartTime: timings.raceStartTime, timezoneId: timings.timezoneId, recurrenceStrategy: timings.recurrenceStrategy, intervalWeeks: timings.intervalWeeks, weekdays: timings.weekdays, monthlyOrdinal: timings.monthlyOrdinal, monthlyWeekday: timings.monthlyWeekday, plannedRounds: timings.roundsPlanned, }; } export function scheduleDTOToSeasonSchedule(dto: LeagueScheduleDTO): SeasonSchedule { if (!dto.seasonStartDate) { throw new RacingApplicationError('seasonStartDate is required'); } if (!dto.raceStartTime) { throw new RacingApplicationError('raceStartTime is required'); } if (!dto.timezoneId) { throw new RacingApplicationError('timezoneId is required'); } if (!dto.recurrenceStrategy) { throw new RacingApplicationError('recurrenceStrategy is required'); } if (!Number.isInteger(dto.plannedRounds) || dto.plannedRounds <= 0) { throw new RacingApplicationError('plannedRounds must be a positive integer'); } const startDate = new Date(dto.seasonStartDate); if (Number.isNaN(startDate.getTime())) { throw new RacingApplicationError(`seasonStartDate must be a valid date, got "${dto.seasonStartDate}"`); } const timeOfDay = RaceTimeOfDay.fromString(dto.raceStartTime); const timezone = new LeagueTimezone(dto.timezoneId); let recurrence: RecurrenceStrategy; if (dto.recurrenceStrategy === 'weekly') { if (!dto.weekdays || dto.weekdays.length === 0) { throw new RacingApplicationError('weekdays are required for weekly recurrence'); } recurrence = RecurrenceStrategyFactory.weekly(new WeekdaySet(dto.weekdays)); } else if (dto.recurrenceStrategy === 'everyNWeeks') { if (!dto.weekdays || dto.weekdays.length === 0) { throw new RacingApplicationError('weekdays are required for everyNWeeks recurrence'); } if (dto.intervalWeeks == null) { throw new RacingApplicationError('intervalWeeks is required for everyNWeeks recurrence'); } recurrence = RecurrenceStrategyFactory.everyNWeeks( dto.intervalWeeks, new WeekdaySet(dto.weekdays), ); } else if (dto.recurrenceStrategy === 'monthlyNthWeekday') { if (!dto.monthlyOrdinal || !dto.monthlyWeekday) { throw new RacingApplicationError('monthlyOrdinal and monthlyWeekday are required for monthlyNthWeekday'); } const pattern = new MonthlyRecurrencePattern(dto.monthlyOrdinal, dto.monthlyWeekday); recurrence = RecurrenceStrategyFactory.monthlyNthWeekday(pattern); } else { throw new RacingApplicationError(`Unknown recurrenceStrategy "${dto.recurrenceStrategy}"`); } return new SeasonSchedule({ startDate, timeOfDay, timezone, recurrence, plannedRounds: dto.plannedRounds, }); }