/** * Domain Entity: LiveryTemplate * * Represents an admin-defined livery template for a specific car. * Contains base image and sponsor decal placements. */ import type { IEntity } from '@core/shared/domain'; import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError'; import type { LiveryDecal } from '../value-objects/LiveryDecal'; import { LiveryTemplateId } from './LiveryTemplateId'; import { LeagueId } from './LeagueId'; import { SeasonId } from './season/SeasonId'; import { CarId } from '../value-objects/CarId'; import { ImageUrl } from './ImageUrl'; import { LiveryTemplateCreatedAt } from './LiveryTemplateCreatedAt'; import { LiveryTemplateUpdatedAt } from './LiveryTemplateUpdatedAt'; export class LiveryTemplate implements IEntity { readonly id: LiveryTemplateId; readonly leagueId: LeagueId; readonly seasonId: SeasonId; readonly carId: CarId; readonly baseImageUrl: ImageUrl; readonly adminDecals: LiveryDecal[]; readonly createdAt: LiveryTemplateCreatedAt; readonly updatedAt: LiveryTemplateUpdatedAt | undefined; private constructor(props: { id: LiveryTemplateId; leagueId: LeagueId; seasonId: SeasonId; carId: CarId; baseImageUrl: ImageUrl; adminDecals: LiveryDecal[]; createdAt: LiveryTemplateCreatedAt; updatedAt?: LiveryTemplateUpdatedAt; }) { this.id = props.id; this.leagueId = props.leagueId; this.seasonId = props.seasonId; this.carId = props.carId; this.baseImageUrl = props.baseImageUrl; this.adminDecals = props.adminDecals; this.createdAt = props.createdAt; this.updatedAt = props.updatedAt; } static create(props: { id: string; leagueId: string; seasonId: string; carId: string; baseImageUrl: string; createdAt?: Date; adminDecals?: LiveryDecal[]; }): LiveryTemplate { this.validate(props); const id = LiveryTemplateId.create(props.id); const leagueId = LeagueId.create(props.leagueId); const seasonId = SeasonId.create(props.seasonId); const carId = CarId.create(props.carId); const baseImageUrl = ImageUrl.create(props.baseImageUrl); const createdAt = LiveryTemplateCreatedAt.create(props.createdAt ?? new Date()); return new LiveryTemplate({ id, leagueId, seasonId, carId, baseImageUrl, adminDecals: props.adminDecals ?? [], createdAt, }); } private static validate(props: { id: string; leagueId: string; seasonId: string; carId: string; baseImageUrl: string; }): void { if (!props.id || props.id.trim().length === 0) { throw new RacingDomainValidationError('LiveryTemplate ID is required'); } if (!props.leagueId || props.leagueId.trim().length === 0) { throw new RacingDomainValidationError('LiveryTemplate leagueId is required'); } if (!props.seasonId || props.seasonId.trim().length === 0) { throw new RacingDomainValidationError('LiveryTemplate seasonId is required'); } if (!props.carId || props.carId.trim().length === 0) { throw new RacingDomainValidationError('LiveryTemplate carId is required'); } if (!props.baseImageUrl || props.baseImageUrl.trim().length === 0) { throw new RacingDomainValidationError('LiveryTemplate baseImageUrl is required'); } } /** * Add a decal to the template */ addDecal(decal: LiveryDecal): LiveryTemplate { if (decal.type !== 'sponsor') { throw new RacingDomainInvariantError('Only sponsor decals can be added to admin template'); } return new LiveryTemplate({ ...this, adminDecals: [...this.adminDecals, decal], updatedAt: LiveryTemplateUpdatedAt.create(new Date()), }); } /** * Remove a decal from the template */ removeDecal(decalId: string): LiveryTemplate { const updatedDecals = this.adminDecals.filter(d => d.id !== decalId); if (updatedDecals.length === this.adminDecals.length) { throw new RacingDomainValidationError('Decal not found in template'); } return new LiveryTemplate({ ...this, adminDecals: updatedDecals, updatedAt: LiveryTemplateUpdatedAt.create(new Date()), }); } /** * Update a decal position */ updateDecal(decalId: string, updatedDecal: LiveryDecal): LiveryTemplate { const index = this.adminDecals.findIndex(d => d.id === decalId); if (index === -1) { throw new RacingDomainValidationError('Decal not found in template'); } const updatedDecals = [...this.adminDecals]; updatedDecals[index] = updatedDecal; return new LiveryTemplate({ ...this, adminDecals: updatedDecals, updatedAt: LiveryTemplateUpdatedAt.create(new Date()), }); } /** * Get all sponsor decals */ getSponsorDecals(): LiveryDecal[] { return this.adminDecals.filter(d => d.type === 'sponsor'); } /** * Check if template has sponsor decals */ hasSponsorDecals(): boolean { return this.adminDecals.some(d => d.type === 'sponsor'); } }