import { MediaReference } from '@core/domain/media/MediaReference'; import { Driver } from '@core/racing/domain/entities/Driver'; import { League } from '@core/racing/domain/entities/League'; import { Team } from '@core/racing/domain/entities/Team'; import type { TeamJoinRequest, TeamMembership } from '@core/racing/domain/types/TeamMembership'; import { faker } from '@faker-js/faker'; import { seedId } from './SeedIdHelper'; export interface TeamStats { performanceLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro'; specialization: 'endurance' | 'sprint' | 'mixed'; region: string; languages: string[]; totalWins: number; totalRaces: number; rating: number; category?: string; } export class RacingTeamFactory { constructor( private readonly baseDate: Date, private readonly persistence: 'postgres' | 'inmemory' = 'inmemory', ) {} createTeams(drivers: Driver[], leagues: League[]): Team[] { const teamCount = 50; // Increased from 15 to 50 return Array.from({ length: teamCount }, (_, idx) => { const i = idx + 1; const owner = faker.helpers.arrayElement(drivers); const teamLeagues = faker.helpers.arrayElements( leagues.map((l) => l.id.toString()), { min: 0, max: 3 }, ); // 30-50% of teams are recruiting const isRecruiting = faker.datatype.boolean({ probability: 0.4 }); const teamId = seedId(`team-${i}`, this.persistence); const racingNames = [ 'Apex Performance', 'Velocity Racing', 'Zenith Motorsport', 'Quantum Racing', 'Ignition Racing', 'Precision Dynamics', 'Overdrive Motorsport', 'Apex Predators', 'Gridline Racing', 'Shift Point Motorsport', 'Redline Performance', 'Apex Legends', 'Circuit Breakers', 'Full Throttle Racing', 'Gearhead Motorsport', 'Piston Cup Racing', 'Turbo Titans', 'Nitro Knights', 'Velocity Vanguards', 'Mach One Racing', 'Apex Alliance', 'Elite Endurance', 'Sprint Specialists', 'Grand Prix Group', 'Podium Pursuit', 'Victory Vibe', 'Championship Chase', 'Racing Renegades', 'Track Titans', 'Asphalt Assassins', 'Speed Syndicate', 'Fast Lane Force', 'Apex Architects', 'Velocity Visionaries', 'Zenith Zephyrs', 'Quantum Quicksilver', 'Ignition Iron', 'Precision Pilots', 'Overdrive Outlaws', 'Apex Aces', 'Gridline Guardians', 'Shift Point Sentinels', 'Redline Rebels', 'Apex Avengers', 'Circuit Crusaders', 'Full Throttle Falcons', 'Gearhead Giants', 'Piston Cup Pros', 'Turbo Tigers', 'Nitro Ninjas' ]; const name = racingNames[(i - 1) % racingNames.length]!; return Team.create({ id: teamId, name: name, tag: name.split(' ').map(w => w[0]).join('').toUpperCase().substring(0, 4), description: faker.lorem.sentences(2), ownerId: owner.id, leagues: teamLeagues, isRecruiting, createdAt: faker.date.past({ years: 2, refDate: this.baseDate }), logoRef: MediaReference.createGenerated(`team-${teamId}`), }); }); } createTeamMemberships(drivers: Driver[], teams: Team[]): TeamMembership[] { const memberships: TeamMembership[] = []; const usedDrivers = new Set(); teams.forEach((team, teamIndex) => { const availableDrivers = drivers.filter(d => !usedDrivers.has(d.id.toString()) && d.id.toString() !== team.ownerId.toString()); // Create varied team compositions let memberCount: number; let hasManager: boolean; if (teamIndex % 5 === 0) { // Solo teams (just owner) memberCount = 0; hasManager = false; } else if (teamIndex % 5 === 1) { // Small teams (2-3 members) memberCount = faker.number.int({ min: 1, max: 2 }); hasManager = faker.datatype.boolean(); } else if (teamIndex % 5 === 2) { // Medium teams (3-5 members) memberCount = faker.number.int({ min: 2, max: 4 }); hasManager = true; } else if (teamIndex % 5 === 3) { // Large teams (5-7 members) memberCount = faker.number.int({ min: 4, max: 6 }); hasManager = true; } else { // Mixed - sometimes with manager, sometimes without memberCount = faker.number.int({ min: 1, max: 5 }); hasManager = faker.datatype.boolean(); } const members = faker.helpers.arrayElements(availableDrivers, memberCount); // Add owner memberships.push({ teamId: team.id.toString(), driverId: team.ownerId.toString(), role: 'owner', status: 'active', joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }), }); usedDrivers.add(team.ownerId.toString()); // Add manager if needed if (hasManager && members.length > 0) { const managerIndex = faker.number.int({ min: 0, max: members.length - 1 }); const manager = members[managerIndex]!; memberships.push({ teamId: team.id.toString(), driverId: manager.id.toString(), role: 'manager', status: 'active', joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }), }); usedDrivers.add(manager.id.toString()); } // Add remaining members as drivers members.forEach((driver) => { if (!usedDrivers.has(driver.id.toString())) { memberships.push({ teamId: team.id.toString(), driverId: driver.id.toString(), role: 'driver', status: 'active', joinedAt: faker.date.past({ years: 1, refDate: team.createdAt.toDate() }), }); usedDrivers.add(driver.id.toString()); } }); }); return memberships; } createTeamJoinRequests( drivers: Driver[], teams: Team[], teamMemberships: TeamMembership[], ): TeamJoinRequest[] { const membershipIds = new Set(teamMemberships.map(m => `${m.teamId}:${m.driverId}`)); const requests: TeamJoinRequest[] = []; const addRequest = (request: TeamJoinRequest): void => { requests.push(request); }; const team1 = teams[0]; if (team1) { const candidateDriverIds = drivers .map(d => d.id.toString()) .filter(driverId => !membershipIds.has(`${team1.id.toString()}:${driverId}`)) .slice(0, 8); candidateDriverIds.forEach((driverId, idx) => { addRequest({ id: seedId(`team-join-${team1.id.toString()}-${driverId}`, this.persistence), teamId: team1.id.toString(), driverId, requestedAt: this.addDays(this.baseDate, -(5 + idx)), ...(idx % 3 === 0 ? { message: 'Can I join as a substitute driver for endurance events?' } : idx % 3 === 2 ? { message: '' } : {}), }); }); // Conflict edge case: owner submits a join request to own team. addRequest({ id: seedId(`team-join-${team1.id.toString()}-${team1.ownerId.toString()}-conflict`, this.persistence), teamId: team1.id.toString(), driverId: team1.ownerId.toString(), requestedAt: this.addDays(this.baseDate, -1), message: 'Testing edge case: owner submitted join request.', }); } const team3 = teams[2]; if (team3 && drivers[0]) { const driverId = drivers[0].id.toString(); addRequest({ id: seedId('dup-team-join-req-1', this.persistence), teamId: team3.id.toString(), driverId, requestedAt: this.addDays(this.baseDate, -10), message: 'First request message (will be overwritten).', }); addRequest({ id: seedId('dup-team-join-req-1', this.persistence), teamId: team3.id.toString(), driverId, requestedAt: this.addDays(this.baseDate, -9), message: 'Updated request message (duplicate id).', }); } return requests; } /** * Generate team statistics and metadata for display * This would be stored in a separate stats table/service in production */ generateTeamStats(teams: Team[]): Map { const statsMap = new Map(); // Available regions (using country codes for flags) const regions = ['DE', 'GB', 'US', 'FR', 'IT', 'ES', 'BR', 'JP', 'AU', 'NL', 'BE', 'AT', 'CH', 'SE', 'NO', 'FI', 'DK', 'PL', 'CZ', 'HU']; // Available languages const allLanguages = ['English', 'German', 'French', 'Spanish', 'Italian', 'Portuguese', 'Japanese', 'Korean', 'Russian', 'Chinese']; teams.forEach((team, idx) => { const i = idx + 1; // Determine performance level based on index let performanceLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro'; let totalRaces: number; let rating: number; let totalWins: number; if (i % 8 === 0) { // Pro teams performanceLevel = 'pro'; totalRaces = faker.number.int({ min: 80, max: 150 }); rating = faker.number.int({ min: 1700, max: 2000 }); totalWins = faker.number.int({ min: 15, max: 40 }); } else if (i % 5 === 0) { // Advanced teams performanceLevel = 'advanced'; totalRaces = faker.number.int({ min: 40, max: 100 }); rating = faker.number.int({ min: 1500, max: 1800 }); totalWins = faker.number.int({ min: 8, max: 20 }); } else if (i % 3 === 0) { // Intermediate teams performanceLevel = 'intermediate'; totalRaces = faker.number.int({ min: 20, max: 60 }); rating = faker.number.int({ min: 1200, max: 1600 }); totalWins = faker.number.int({ min: 3, max: 12 }); } else { // Beginner teams performanceLevel = 'beginner'; totalRaces = faker.number.int({ min: 5, max: 25 }); rating = faker.number.int({ min: 900, max: 1300 }); totalWins = faker.number.int({ min: 0, max: 5 }); } // Determine specialization let specialization: 'endurance' | 'sprint' | 'mixed'; if (i % 7 === 0) { specialization = 'endurance'; } else if (i % 4 === 0) { specialization = 'sprint'; } else { specialization = 'mixed'; } // Determine category - use all available categories const categories = ['beginner', 'intermediate', 'advanced', 'pro', 'endurance', 'sprint']; const category = faker.helpers.arrayElement(categories); // Generate region and languages const region = faker.helpers.arrayElement(regions); const languageCount = faker.number.int({ min: 1, max: 3 }); const languages = faker.helpers.arrayElements(allLanguages, languageCount); statsMap.set(team.id.toString(), { performanceLevel, specialization, region, languages, totalWins, totalRaces, rating, category, }); }); return statsMap; } private addDays(date: Date, days: number): Date { return new Date(date.getTime() + days * 24 * 60 * 60 * 1000); } }