import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient"; import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient"; import { SponsorsApiClient } from "@/lib/api/sponsors/SponsorsApiClient"; import { RacesApiClient } from "@/lib/api/races/RacesApiClient"; import { CreateLeagueInputDTO } from "@/lib/types/generated/CreateLeagueInputDTO"; import { LeagueWithCapacityDTO } from "@/lib/types/generated/LeagueWithCapacityDTO"; import { CreateLeagueViewModel } from "@/lib/view-models/CreateLeagueViewModel"; import { LeagueMembershipsViewModel } from "@/lib/view-models/LeagueMembershipsViewModel"; import { LeagueScheduleViewModel } from "@/lib/view-models/LeagueScheduleViewModel"; import { LeagueStandingsViewModel } from "@/lib/view-models/LeagueStandingsViewModel"; import { LeagueStatsViewModel } from "@/lib/view-models/LeagueStatsViewModel"; import { LeagueSummaryViewModel } from "@/lib/view-models/LeagueSummaryViewModel"; import { RemoveMemberViewModel } from "@/lib/view-models/RemoveMemberViewModel"; import { LeagueDetailViewModel } from "@/lib/view-models/LeagueDetailViewModel"; import { LeagueDetailPageViewModel, SponsorInfo } from "@/lib/view-models/LeagueDetailPageViewModel"; import { RaceViewModel } from "@/lib/view-models/RaceViewModel"; import { SubmitBlocker, ThrottleBlocker } from "@/lib/blockers"; import { RaceDTO } from "@/lib/types/generated/RaceDTO"; import { LeagueScoringConfigDTO } from "@/lib/types/LeagueScoringConfigDTO"; import { LeagueStatsDTO } from "@/lib/types/generated/LeagueStatsDTO"; import { LeagueMembershipsDTO } from "@/lib/types/generated/LeagueMembershipsDTO"; /** * League Service * * Orchestrates league operations by coordinating API calls and view model creation. * All dependencies are injected via constructor. */ export class LeagueService { private readonly submitBlocker = new SubmitBlocker(); private readonly throttle = new ThrottleBlocker(500); constructor( private readonly apiClient: LeaguesApiClient, private readonly driversApiClient: DriversApiClient, private readonly sponsorsApiClient: SponsorsApiClient, private readonly racesApiClient: RacesApiClient ) {} /** * Get all leagues with view model transformation */ async getAllLeagues(): Promise { const dto = await this.apiClient.getAllWithCapacity(); return dto.leagues.map((league: LeagueWithCapacityDTO) => new LeagueSummaryViewModel(league)); } /** * Get league standings with view model transformation */ async getLeagueStandings(leagueId: string, currentUserId: string): Promise { const dto = await this.apiClient.getStandings(leagueId); // TODO: include drivers and memberships in dto const dtoWithExtras = { ...dto, drivers: [], // TODO: fetch drivers memberships: [], // TODO: fetch memberships }; return new LeagueStandingsViewModel(dtoWithExtras, currentUserId); } /** * Get league statistics */ async getLeagueStats(): Promise { const dto = await this.apiClient.getTotal(); return new LeagueStatsViewModel(dto); } /** * Get league schedule */ async getLeagueSchedule(leagueId: string): Promise { const dto = await this.apiClient.getSchedule(leagueId); return new LeagueScheduleViewModel(dto); } /** * Get league memberships */ async getLeagueMemberships(leagueId: string, currentUserId: string): Promise { const dto = await this.apiClient.getMemberships(leagueId); return new LeagueMembershipsViewModel(dto, currentUserId); } /** * Create a new league */ async createLeague(input: CreateLeagueInputDTO): Promise { if (!this.submitBlocker.canExecute() || !this.throttle.canExecute()) return; this.submitBlocker.block(); this.throttle.block(); try { await this.apiClient.create(input); } finally { this.submitBlocker.release(); } } /** * Remove a member from league */ async removeMember(leagueId: string, performerDriverId: string, targetDriverId: string): Promise { const dto = await this.apiClient.removeMember(leagueId, performerDriverId, targetDriverId); return new RemoveMemberViewModel(dto); } /** * Update a member's role in league */ async updateMemberRole(leagueId: string, performerDriverId: string, targetDriverId: string, newRole: string): Promise<{ success: boolean }> { return this.apiClient.updateMemberRole(leagueId, performerDriverId, targetDriverId, newRole); } /** * Get league detail with owner, membership, and sponsor info */ async getLeagueDetail(leagueId: string, currentDriverId: string): Promise { // For now, assume league data comes from getAllWithCapacity or a new endpoint // Since API may not have detailed league, we'll mock or assume // In real implementation, add getLeagueDetail to API const allLeagues = await this.apiClient.getAllWithCapacity(); const leagueDto = allLeagues.leagues.find(l => l.id === leagueId); if (!leagueDto) return null; // Assume league has description, ownerId - need to update DTO const league = { id: leagueDto.id, name: leagueDto.name, description: 'Description not available', // TODO: add to API ownerId: 'owner-id', // TODO: add to API }; // Get owner const owner = await this.driversApiClient.getDriver(league.ownerId); const ownerName = owner ? owner.name : `${league.ownerId.slice(0, 8)}...`; // Get membership const membershipsDto = await this.apiClient.getMemberships(leagueId); const membership = membershipsDto.members.find(m => m.driverId === currentDriverId); const isAdmin = membership ? ['admin', 'owner'].includes(membership.role) : false; // Get main sponsor let mainSponsor = null; try { const seasonsDto = await this.apiClient.getSeasons(leagueId); const activeSeason = seasonsDto.seasons.find((s: any) => s.status === 'active') ?? seasonsDto.seasons[0]; if (activeSeason) { const sponsorshipsDto = await this.apiClient.getSeasonSponsorships(activeSeason.id); const mainSponsorship = sponsorshipsDto.sponsorships.find((s: any) => s.tier === 'main' && s.status === 'active'); if (mainSponsorship) { const sponsor = await this.sponsorsApiClient.getSponsor(mainSponsorship.sponsorId); if (sponsor) { mainSponsor = { name: sponsor.name, logoUrl: sponsor.logoUrl ?? '', websiteUrl: sponsor.websiteUrl ?? '', }; } } } } catch (error) { console.warn('Failed to load main sponsor:', error); } return new LeagueDetailViewModel( league.id, league.name, league.description, league.ownerId, ownerName, mainSponsor, isAdmin ); } /** * Get comprehensive league detail page data */ async getLeagueDetailPageData(leagueId: string): Promise { try { // Get league basic info const allLeagues = await this.apiClient.getAllWithCapacity(); const league = allLeagues.leagues.find(l => l.id === leagueId); if (!league) return null; // Get owner const owner = await this.driversApiClient.getDriver(league.ownerId); // Get scoring config - TODO: implement API endpoint const scoringConfig: LeagueScoringConfigDTO | null = null; // TODO: fetch from API // Get all drivers - TODO: implement API endpoint for all drivers const drivers: DriverDTO[] = []; // TODO: fetch from API // Get memberships const memberships = await this.apiClient.getMemberships(leagueId); // Get all races for this league - TODO: implement API endpoint const allRaces: RaceViewModel[] = []; // TODO: fetch from API and map to RaceViewModel // Get league stats const leagueStats = await this.apiClient.getTotal(); // TODO: get stats for specific league // Get sponsors const sponsors = await this.getLeagueSponsors(leagueId); return new LeagueDetailPageViewModel( league, owner, scoringConfig, drivers, memberships, allRaces, leagueStats, sponsors ); } catch (error) { console.error('Failed to load league detail page data:', error); return null; } } /** * Get sponsors for a league */ private async getLeagueSponsors(leagueId: string): Promise { try { const seasons = await this.apiClient.getSeasons(leagueId); const activeSeason = seasons.seasons.find((s: any) => s.status === 'active') ?? seasons.seasons[0]; if (!activeSeason) return []; const sponsorships = await this.apiClient.getSeasonSponsorships(activeSeason.id); const activeSponsorships = sponsorships.sponsorships.filter((s: any) => s.status === 'active'); const sponsorInfos: SponsorInfo[] = []; for (const sponsorship of activeSponsorships) { const sponsor = await this.sponsorsApiClient.getSponsor(sponsorship.sponsorId); if (sponsor) { // TODO: Get tagline from testing support or API sponsorInfos.push({ id: sponsor.id, name: sponsor.name, logoUrl: sponsor.logoUrl ?? '', websiteUrl: sponsor.websiteUrl ?? '', tier: sponsorship.tier, tagline: '', // TODO: fetch tagline }); } } // Sort: main sponsors first, then secondary sponsorInfos.sort((a, b) => { if (a.tier === 'main' && b.tier !== 'main') return -1; if (a.tier !== 'main' && b.tier === 'main') return 1; return 0; }); return sponsorInfos; } catch (error) { console.warn('Failed to load sponsors:', error); return []; } } }