// // DataProvider.swift // SportsTime // import Foundation import Combine /// Protocol defining data operations for teams, stadiums, and games protocol DataProvider: Sendable { func fetchTeams(for sport: Sport) async throws -> [Team] func fetchAllTeams() async throws -> [Team] func fetchStadiums() async throws -> [Stadium] func fetchGames(sports: Set, startDate: Date, endDate: Date) async throws -> [Game] func fetchGame(by id: UUID) async throws -> Game? // Resolved data (with team/stadium references) func fetchRichGames(sports: Set, startDate: Date, endDate: Date) async throws -> [RichGame] } /// Environment-aware data provider that switches between stub and CloudKit @MainActor final class AppDataProvider: ObservableObject { static let shared = AppDataProvider() private let provider: any DataProvider @Published private(set) var teams: [Team] = [] @Published private(set) var stadiums: [Stadium] = [] @Published private(set) var isLoading = false @Published private(set) var error: Error? @Published private(set) var errorMessage: String? @Published private(set) var isUsingStubData: Bool private var teamsById: [UUID: Team] = [:] private var stadiumsById: [UUID: Stadium] = [:] private init() { #if targetEnvironment(simulator) self.provider = StubDataProvider() self.isUsingStubData = true #else self.provider = CloudKitDataProvider() self.isUsingStubData = false #endif } // MARK: - Data Loading func loadInitialData() async { isLoading = true error = nil errorMessage = nil do { async let teamsTask = provider.fetchAllTeams() async let stadiumsTask = provider.fetchStadiums() let (loadedTeams, loadedStadiums) = try await (teamsTask, stadiumsTask) self.teams = loadedTeams self.stadiums = loadedStadiums // Build lookup dictionaries self.teamsById = Dictionary(uniqueKeysWithValues: loadedTeams.map { ($0.id, $0) }) self.stadiumsById = Dictionary(uniqueKeysWithValues: loadedStadiums.map { ($0.id, $0) }) } catch let cloudKitError as CloudKitError { self.error = cloudKitError self.errorMessage = cloudKitError.errorDescription } catch { self.error = error self.errorMessage = error.localizedDescription } isLoading = false } func clearError() { error = nil errorMessage = nil } func retry() async { await loadInitialData() } // MARK: - Data Access func team(for id: UUID) -> Team? { teamsById[id] } func stadium(for id: UUID) -> Stadium? { stadiumsById[id] } func teams(for sport: Sport) -> [Team] { teams.filter { $0.sport == sport } } func fetchGames(sports: Set, startDate: Date, endDate: Date) async throws -> [Game] { try await provider.fetchGames(sports: sports, startDate: startDate, endDate: endDate) } func fetchRichGames(sports: Set, startDate: Date, endDate: Date) async throws -> [RichGame] { let games = try await provider.fetchGames(sports: sports, startDate: startDate, endDate: endDate) return games.compactMap { game in guard let homeTeam = teamsById[game.homeTeamId], let awayTeam = teamsById[game.awayTeamId], let stadium = stadiumsById[game.stadiumId] else { return nil } return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium) } } func richGame(from game: Game) -> RichGame? { guard let homeTeam = teamsById[game.homeTeamId], let awayTeam = teamsById[game.awayTeamId], let stadium = stadiumsById[game.stadiumId] else { return nil } return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium) } }