/** * Domain Entity: Car * * Represents a racing car/vehicle in the GridPilot platform. * Immutable entity with factory methods and domain validation. */ import type { IEntity } from '@gridpilot/shared/domain'; import { RacingDomainValidationError } from '../errors/RacingDomainError'; export type CarClass = 'formula' | 'gt' | 'prototype' | 'touring' | 'sports' | 'oval' | 'dirt'; export type CarLicense = 'R' | 'D' | 'C' | 'B' | 'A' | 'Pro'; export class Car implements IEntity { readonly id: string; readonly name: string; readonly shortName: string; readonly manufacturer: string; readonly carClass: CarClass; readonly license: CarLicense; readonly year: number; readonly horsepower: number | undefined; readonly weight: number | undefined; readonly imageUrl: string | undefined; readonly gameId: string; private constructor(props: { id: string; name: string; shortName: string; manufacturer: string; carClass: CarClass; license: CarLicense; year: number; horsepower?: number; weight?: number; imageUrl?: string; gameId: string; }) { this.id = props.id; this.name = props.name; this.shortName = props.shortName; this.manufacturer = props.manufacturer; this.carClass = props.carClass; this.license = props.license; this.year = props.year; this.horsepower = props.horsepower; this.weight = props.weight; this.imageUrl = props.imageUrl; this.gameId = props.gameId; } /** * Factory method to create a new Car entity */ static create(props: { id: string; name: string; shortName?: string; manufacturer: string; carClass?: CarClass; license?: CarLicense; year?: number; horsepower?: number; weight?: number; imageUrl?: string; gameId: string; }): Car { this.validate(props); return new Car({ id: props.id, name: props.name, shortName: props.shortName ?? props.name.slice(0, 10), manufacturer: props.manufacturer, carClass: props.carClass ?? 'gt', license: props.license ?? 'D', year: props.year ?? new Date().getFullYear(), ...(props.horsepower !== undefined ? { horsepower: props.horsepower } : {}), ...(props.weight !== undefined ? { weight: props.weight } : {}), ...(props.imageUrl !== undefined ? { imageUrl: props.imageUrl } : {}), gameId: props.gameId, }); } /** * Domain validation logic */ private static validate(props: { id: string; name: string; manufacturer: string; gameId: string; }): void { if (!props.id || props.id.trim().length === 0) { throw new RacingDomainValidationError('Car ID is required'); } if (!props.name || props.name.trim().length === 0) { throw new RacingDomainValidationError('Car name is required'); } if (!props.manufacturer || props.manufacturer.trim().length === 0) { throw new RacingDomainValidationError('Car manufacturer is required'); } if (!props.gameId || props.gameId.trim().length === 0) { throw new RacingDomainValidationError('Game ID is required'); } } /** * Get formatted car display name */ getDisplayName(): string { return `${this.manufacturer} ${this.name}`; } /** * Get license badge color */ getLicenseColor(): string { const colors: Record = { 'R': '#FF6B6B', 'D': '#FFB347', 'C': '#FFD700', 'B': '#7FFF00', 'A': '#00BFFF', 'Pro': '#9370DB', }; return colors[this.license]; } }