/** * Domain Value Object: LeagueVisibility * * Represents the visibility and ranking status of a league. * * - 'ranked' (public): Competitive leagues visible to everyone, affects driver ratings. * Requires minimum 10 players to ensure competitive integrity. * - 'unranked' (private): Casual leagues for friends/private groups, no rating impact. * Can have any number of players. */ import type { IValueObject } from '@core/shared/domain'; import { RacingDomainValidationError } from '../errors/RacingDomainError'; export type LeagueVisibilityType = 'ranked' | 'unranked'; export interface LeagueVisibilityConstraints { readonly minDrivers: number; readonly isPubliclyVisible: boolean; readonly affectsRatings: boolean; readonly requiresApproval: boolean; } const VISIBILITY_CONSTRAINTS: Record = { ranked: { minDrivers: 10, isPubliclyVisible: true, affectsRatings: true, requiresApproval: false, // Anyone can join public leagues }, unranked: { minDrivers: 2, isPubliclyVisible: false, affectsRatings: false, requiresApproval: true, // Private leagues require invite/approval }, }; export interface LeagueVisibilityProps { type: LeagueVisibilityType; } export class LeagueVisibility implements IValueObject { readonly type: LeagueVisibilityType; readonly constraints: LeagueVisibilityConstraints; private constructor(type: LeagueVisibilityType) { this.type = type; this.constraints = VISIBILITY_CONSTRAINTS[type]; } static ranked(): LeagueVisibility { return new LeagueVisibility('ranked'); } static unranked(): LeagueVisibility { return new LeagueVisibility('unranked'); } static fromString(value: string): LeagueVisibility { // Support both old ('public'/'private') and new ('ranked'/'unranked') terminology if (value === 'ranked' || value === 'public') { return LeagueVisibility.ranked(); } if (value === 'unranked' || value === 'private') { return LeagueVisibility.unranked(); } throw new RacingDomainValidationError(`Invalid league visibility: ${value}`); } /** * Validates that the given driver count meets the minimum requirement * for this visibility type. */ validateDriverCount(driverCount: number): { valid: boolean; error?: string } { if (driverCount < this.constraints.minDrivers) { return { valid: false, error: `${this.type === 'ranked' ? 'Ranked' : 'Unranked'} leagues require at least ${this.constraints.minDrivers} drivers`, }; } return { valid: true }; } /** * Returns true if this is a ranked/public league */ isRanked(): boolean { return this.type === 'ranked'; } /** * Returns true if this is an unranked/private league */ isUnranked(): boolean { return this.type === 'unranked'; } /** * Convert to string for serialization */ toString(): LeagueVisibilityType { return this.type; } get props(): LeagueVisibilityProps { return { type: this.type }; } /** * For backward compatibility with existing 'public'/'private' terminology */ toLegacyString(): 'public' | 'private' { return this.type === 'ranked' ? 'public' : 'private'; } equals(other: IValueObject): boolean { return this.props.type === other.props.type; } } // Export constants for validation export const MIN_RANKED_LEAGUE_DRIVERS = VISIBILITY_CONSTRAINTS.ranked.minDrivers; export const MIN_UNRANKED_LEAGUE_DRIVERS = VISIBILITY_CONSTRAINTS.unranked.minDrivers;