import type { IDriverRepository } from '../../domain/repositories/IDriverRepository'; import type { ITeamRepository } from '../../domain/repositories/ITeamRepository'; import type { ITeamMembershipRepository } from '../../domain/repositories/ITeamMembershipRepository'; import type { IImageServicePort } from '../ports/IImageServicePort'; import type { ISocialGraphRepository } from '@core/social/domain/repositories/ISocialGraphRepository'; import type { IProfileOverviewPresenter, ProfileOverviewViewModel, ProfileOverviewDriverSummaryViewModel, ProfileOverviewStatsViewModel, ProfileOverviewFinishDistributionViewModel, ProfileOverviewTeamMembershipViewModel, ProfileOverviewSocialSummaryViewModel, ProfileOverviewExtendedProfileViewModel, } from '../presenters/IProfileOverviewPresenter'; interface ProfileDriverStatsAdapter { rating: number | null; wins: number; podiums: number; dnfs: number; totalRaces: number; avgFinish: number | null; bestFinish: number | null; worstFinish: number | null; overallRank: number | null; consistency: number | null; percentile: number | null; } interface DriverRankingEntry { driverId: string; rating: number; overallRank: number | null; } export interface GetProfileOverviewParams { driverId: string; } export class GetProfileOverviewUseCase { constructor( private readonly driverRepository: IDriverRepository, private readonly teamRepository: ITeamRepository, private readonly teamMembershipRepository: ITeamMembershipRepository, private readonly socialRepository: ISocialGraphRepository, private readonly imageService: IImageServicePort, private readonly getDriverStats: (driverId: string) => ProfileDriverStatsAdapter | null, private readonly getAllDriverRankings: () => DriverRankingEntry[], public readonly presenter: IProfileOverviewPresenter, ) {} async execute(params: GetProfileOverviewParams): Promise { const { driverId } = params; const driver = await this.driverRepository.findById(driverId); if (!driver) { const emptyViewModel: ProfileOverviewViewModel = { currentDriver: null, stats: null, finishDistribution: null, teamMemberships: [], socialSummary: { friendsCount: 0, friends: [], }, extendedProfile: null, }; this.presenter.present(emptyViewModel); return emptyViewModel; } const [statsAdapter, teams, friends] = await Promise.all([ Promise.resolve(this.getDriverStats(driverId)), this.teamRepository.findAll(), this.socialRepository.getFriends(driverId), ]); const driverSummary = this.buildDriverSummary(driver, statsAdapter); const stats = this.buildStats(statsAdapter); const finishDistribution = this.buildFinishDistribution(statsAdapter); const teamMemberships = await this.buildTeamMemberships(driver.id, teams); const socialSummary = this.buildSocialSummary(friends); const extendedProfile = this.buildExtendedProfile(driver.id); const viewModel: ProfileOverviewViewModel = { currentDriver: driverSummary, stats, finishDistribution, teamMemberships, socialSummary, extendedProfile, }; this.presenter.present(viewModel); return viewModel; } private buildDriverSummary( driver: any, stats: ProfileDriverStatsAdapter | null, ): ProfileOverviewDriverSummaryViewModel { const rankings = this.getAllDriverRankings(); const fallbackRank = this.computeFallbackRank(driver.id, rankings); const totalDrivers = rankings.length; return { id: driver.id, name: driver.name, country: driver.country, avatarUrl: this.imageService.getDriverAvatar(driver.id), iracingId: driver.iracingId ?? null, joinedAt: driver.joinedAt instanceof Date ? driver.joinedAt.toISOString() : new Date(driver.joinedAt).toISOString(), rating: stats?.rating ?? null, globalRank: stats?.overallRank ?? fallbackRank, consistency: stats?.consistency ?? null, bio: driver.bio ?? null, totalDrivers, }; } private computeFallbackRank( driverId: string, rankings: DriverRankingEntry[], ): number | null { const index = rankings.findIndex(entry => entry.driverId === driverId); if (index === -1) { return null; } return index + 1; } private buildStats( stats: ProfileDriverStatsAdapter | null, ): ProfileOverviewStatsViewModel | null { if (!stats) { return null; } const totalRaces = stats.totalRaces; const dnfs = stats.dnfs; const finishedRaces = Math.max(totalRaces - dnfs, 0); const finishRate = totalRaces > 0 ? (finishedRaces / totalRaces) * 100 : null; const winRate = totalRaces > 0 ? (stats.wins / totalRaces) * 100 : null; const podiumRate = totalRaces > 0 ? (stats.podiums / totalRaces) * 100 : null; return { totalRaces, wins: stats.wins, podiums: stats.podiums, dnfs, avgFinish: stats.avgFinish, bestFinish: stats.bestFinish, worstFinish: stats.worstFinish, finishRate, winRate, podiumRate, percentile: stats.percentile, rating: stats.rating, consistency: stats.consistency, overallRank: stats.overallRank, }; } private buildFinishDistribution( stats: ProfileDriverStatsAdapter | null, ): ProfileOverviewFinishDistributionViewModel | null { if (!stats || stats.totalRaces <= 0) { return null; } const totalRaces = stats.totalRaces; const dnfs = stats.dnfs; const finishedRaces = Math.max(totalRaces - dnfs, 0); const estimatedTopTen = Math.min( finishedRaces, Math.round(totalRaces * 0.7), ); const topTen = Math.max(estimatedTopTen, stats.podiums); const other = Math.max(totalRaces - topTen, 0); return { totalRaces, wins: stats.wins, podiums: stats.podiums, topTen, dnfs, other, }; } private async buildTeamMemberships( driverId: string, teams: any[], ): Promise { const memberships: ProfileOverviewTeamMembershipViewModel[] = []; for (const team of teams) { const membership = await this.teamMembershipRepository.getMembership( team.id, driverId, ); if (!membership) continue; memberships.push({ teamId: team.id, teamName: team.name, teamTag: team.tag ?? null, role: membership.role, joinedAt: membership.joinedAt instanceof Date ? membership.joinedAt.toISOString() : new Date(membership.joinedAt).toISOString(), isCurrent: membership.status === 'active', }); } memberships.sort((a, b) => a.joinedAt.localeCompare(b.joinedAt)); return memberships; } private buildSocialSummary(friends: any[]): ProfileOverviewSocialSummaryViewModel { return { friendsCount: friends.length, friends: friends.map(friend => ({ id: friend.id, name: friend.name, country: friend.country, avatarUrl: this.imageService.getDriverAvatar(friend.id), })), }; } private buildExtendedProfile(driverId: string): ProfileOverviewExtendedProfileViewModel { const hash = driverId .split('') .reduce((acc: number, char: string) => acc + char.charCodeAt(0), 0); const socialOptions: Array< Array<{ platform: 'twitter' | 'youtube' | 'twitch' | 'discord'; handle: string; url: string; }> > = [ [ { platform: 'twitter', handle: '@speedracer', url: 'https://twitter.com/speedracer', }, { platform: 'youtube', handle: 'SpeedRacer Racing', url: 'https://youtube.com/@speedracer', }, { platform: 'twitch', handle: 'speedracer_live', url: 'https://twitch.tv/speedracer_live', }, ], [ { platform: 'twitter', handle: '@racingpro', url: 'https://twitter.com/racingpro', }, { platform: 'discord', handle: 'RacingPro#1234', url: '#', }, ], [ { platform: 'twitch', handle: 'simracer_elite', url: 'https://twitch.tv/simracer_elite', }, { platform: 'youtube', handle: 'SimRacer Elite', url: 'https://youtube.com/@simracerelite', }, ], ]; const achievementSets: Array< Array<{ id: string; title: string; description: string; icon: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap'; rarity: 'common' | 'rare' | 'epic' | 'legendary'; earnedAt: Date; }> > = [ [ { id: '1', title: 'First Victory', description: 'Win your first race', icon: 'trophy', rarity: 'common', earnedAt: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000), }, { id: '2', title: 'Clean Racer', description: '10 races without incidents', icon: 'star', rarity: 'rare', earnedAt: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000), }, { id: '3', title: 'Podium Streak', description: '5 consecutive podium finishes', icon: 'medal', rarity: 'epic', earnedAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), }, { id: '4', title: 'Championship Glory', description: 'Win a league championship', icon: 'crown', rarity: 'legendary', earnedAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), }, ], [ { id: '1', title: 'Rookie No More', description: 'Complete 25 races', icon: 'target', rarity: 'common', earnedAt: new Date(Date.now() - 120 * 24 * 60 * 60 * 1000), }, { id: '2', title: 'Consistent Performer', description: 'Maintain 80%+ consistency rating', icon: 'zap', rarity: 'rare', earnedAt: new Date(Date.now() - 45 * 24 * 60 * 60 * 1000), }, { id: '3', title: 'Endurance Master', description: 'Complete a 24-hour race', icon: 'star', rarity: 'epic', earnedAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000), }, ], [ { id: '1', title: 'Welcome Racer', description: 'Join GridPilot', icon: 'star', rarity: 'common', earnedAt: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000), }, { id: '2', title: 'Team Player', description: 'Join a racing team', icon: 'medal', rarity: 'rare', earnedAt: new Date(Date.now() - 80 * 24 * 60 * 60 * 1000), }, ], ]; const tracks = [ 'Spa-Francorchamps', 'Nürburgring Nordschleife', 'Suzuka', 'Monza', 'Interlagos', 'Silverstone', ]; const cars = [ 'Porsche 911 GT3 R', 'Ferrari 488 GT3', 'Mercedes-AMG GT3', 'BMW M4 GT3', 'Audi R8 LMS', ]; const styles = [ 'Aggressive Overtaker', 'Consistent Pacer', 'Strategic Calculator', 'Late Braker', 'Smooth Operator', ]; const timezones = [ 'EST (UTC-5)', 'CET (UTC+1)', 'PST (UTC-8)', 'GMT (UTC+0)', 'JST (UTC+9)', ]; const hours = [ 'Evenings (18:00-23:00)', 'Weekends only', 'Late nights (22:00-02:00)', 'Flexible schedule', ]; const socialHandles = socialOptions[hash % socialOptions.length] ?? []; const achievementsSource = achievementSets[hash % achievementSets.length] ?? []; return { socialHandles, achievements: achievementsSource.map(achievement => ({ id: achievement.id, title: achievement.title, description: achievement.description, icon: achievement.icon, rarity: achievement.rarity, earnedAt: achievement.earnedAt.toISOString(), })), racingStyle: styles[hash % styles.length] ?? 'Consistent Pacer', favoriteTrack: tracks[hash % tracks.length] ?? 'Unknown Track', favoriteCar: cars[hash % cars.length] ?? 'Unknown Car', timezone: timezones[hash % timezones.length] ?? 'UTC', availableHours: hours[hash % hours.length] ?? 'Flexible schedule', lookingForTeam: hash % 3 === 0, openToRequests: hash % 2 === 0, }; } }