/** * Domain Entity: Achievement * * Represents an achievement that can be earned by users. * Achievements are categorized by role (driver, steward, admin) and type. */ import type { IEntity } from '@core/shared/domain'; export type AchievementCategory = 'driver' | 'steward' | 'admin' | 'community'; export type AchievementRarity = 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary'; export interface AchievementProps { id: string; name: string; description: string; category: AchievementCategory; rarity: AchievementRarity; iconUrl?: string; points: number; requirements: AchievementRequirement[]; isSecret: boolean; createdAt: Date; } export interface AchievementRequirement { type: 'races_completed' | 'wins' | 'podiums' | 'clean_races' | 'protests_handled' | 'leagues_managed' | 'seasons_completed' | 'consecutive_clean' | 'rating_threshold' | 'trust_threshold' | 'events_stewarded' | 'members_managed' | 'championships_won'; value: number; operator: '>=' | '>' | '=' | '<' | '<='; } export class Achievement implements IEntity { readonly id: string; readonly name: string; readonly description: string; readonly category: AchievementCategory; readonly rarity: AchievementRarity; readonly iconUrl: string; readonly points: number; readonly requirements: AchievementRequirement[]; readonly isSecret: boolean; readonly createdAt: Date; private constructor(props: AchievementProps) { this.id = props.id; this.name = props.name; this.description = props.description; this.category = props.category; this.rarity = props.rarity; this.iconUrl = props.iconUrl ?? ''; this.points = props.points; this.requirements = props.requirements; this.isSecret = props.isSecret; this.createdAt = props.createdAt; } static create(props: Omit & { createdAt?: Date }): Achievement { if (!props.id || props.id.trim().length === 0) { throw new Error('Achievement ID is required'); } if (!props.name || props.name.trim().length === 0) { throw new Error('Achievement name is required'); } if (props.requirements.length === 0) { throw new Error('Achievement must have at least one requirement'); } return new Achievement({ ...props, createdAt: props.createdAt ?? new Date(), }); } /** * Check if user stats meet all requirements */ checkRequirements(stats: Record): boolean { return this.requirements.every(req => { const value = stats[req.type] ?? 0; switch (req.operator) { case '>=': return value >= req.value; case '>': return value > req.value; case '=': return value === req.value; case '<': return value < req.value; case '<=': return value <= req.value; default: return false; } }); } /** * Get rarity color for display */ getRarityColor(): string { const colors: Record = { common: '#9CA3AF', uncommon: '#22C55E', rare: '#3B82F6', epic: '#A855F7', legendary: '#F59E0B', }; return colors[this.rarity]; } /** * Get display text for hidden achievements */ getDisplayName(): string { if (this.isSecret) { return '???'; } return this.name; } getDisplayDescription(): string { if (this.isSecret) { return 'This achievement is secret. Keep playing to unlock it!'; } return this.description; } }