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 { DriverExtendedProfileProvider } from '../ports/DriverExtendedProfileProvider'; import type { Driver } from '../../domain/entities/Driver'; import type { Team } from '../../domain/entities/Team'; import type { ProfileOverviewOutputPort } from '../ports/output/ProfileOverviewOutputPort'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; 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 driverExtendedProfileProvider: DriverExtendedProfileProvider, private readonly getDriverStats: (driverId: string) => ProfileDriverStatsAdapter | null, private readonly getAllDriverRankings: () => DriverRankingEntry[], ) {} async execute(params: GetProfileOverviewParams): Promise>> { try { const { driverId } = params; const driver = await this.driverRepository.findById(driverId); if (!driver) { return Result.err({ code: 'DRIVER_NOT_FOUND', message: 'Driver not found' }); } 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 as Team[]); const socialSummary = this.buildSocialSummary(friends as Driver[]); const extendedProfile = this.driverExtendedProfileProvider.getExtendedProfile(driverId); const outputPort: ProfileOverviewOutputPort = { driver: driverSummary, stats, finishDistribution, teamMemberships, socialSummary, extendedProfile, }; return Result.ok(outputPort); } catch { return Result.err({ code: 'REPOSITORY_ERROR', message: 'Failed to fetch profile overview' }); } } private buildDriverSummary( driver: Driver, stats: ProfileDriverStatsAdapter | null, ): ProfileOverviewOutputPort['driver'] { const rankings = this.getAllDriverRankings(); const fallbackRank = this.computeFallbackRank(driver.id, rankings); const totalDrivers = rankings.length; return { id: driver.id, name: driver.name.value, country: driver.country.value, avatarUrl: this.imageService.getDriverAvatar(driver.id), iracingId: driver.iracingId?.value ?? null, joinedAt: driver.joinedAt instanceof Date ? driver.joinedAt : new Date(driver.joinedAt.value), rating: stats?.rating ?? null, globalRank: stats?.overallRank ?? fallbackRank, consistency: stats?.consistency ?? null, bio: driver.bio?.value ?? 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: Team[], ): Promise { const memberships: ProfileOverviewOutputPort['teamMemberships'] = []; 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.value, teamTag: team.tag?.value ?? null, role: membership.role, joinedAt: membership.joinedAt instanceof Date ? membership.joinedAt : new Date(membership.joinedAt), isCurrent: membership.status === 'active', }); } memberships.sort((a, b) => a.joinedAt.getTime() - b.joinedAt.getTime()); return memberships; } private buildSocialSummary(friends: Driver[]): ProfileOverviewOutputPort['socialSummary'] { return { friendsCount: friends.length, friends: friends.map(friend => ({ id: friend.id, name: friend.name.value, country: friend.country.value, avatarUrl: this.imageService.getDriverAvatar(friend.id), })), }; } }