import Foundation import Observation import OSLog private let feedLogger = Logger(subsystem: "com.treyt.mlbTVOS", category: "Feed") private func logFeed(_ message: String) { feedLogger.debug("\(message, privacy: .public)") print("[Feed] \(message)") } struct HighlightItem: Identifiable, Sendable { let id: String let headline: String let gameTitle: String let awayCode: String let homeCode: String let hlsURL: URL? let mp4URL: URL? let isCondensedGame: Bool } @Observable @MainActor final class FeedViewModel { var highlights: [HighlightItem] = [] var isLoading = false @ObservationIgnored private var refreshTask: Task? private let serverAPI = MLBServerAPI() func loadHighlights(games: [Game]) async { isLoading = true logFeed("loadHighlights start gameCount=\(games.count)") let gamesWithPk = games.filter { $0.gamePk != nil } // Fetch highlights for all games concurrently await withTaskGroup(of: [HighlightItem].self) { group in for game in gamesWithPk { group.addTask { [serverAPI] in do { let raw = try await serverAPI.fetchHighlights( gamePk: game.gamePk!, gameDate: game.gameDate ) return raw.compactMap { highlight -> HighlightItem? in guard let headline = highlight.headline, let hlsStr = highlight.hlsURL ?? highlight.mp4URL, let _ = URL(string: hlsStr) else { return nil } let isCondensed = headline.lowercased().contains("condensed") || headline.lowercased().contains("recap") return HighlightItem( id: highlight.id ?? UUID().uuidString, headline: headline, gameTitle: "\(game.awayTeam.code) @ \(game.homeTeam.code)", awayCode: game.awayTeam.code, homeCode: game.homeTeam.code, hlsURL: highlight.hlsURL.flatMap(URL.init), mp4URL: highlight.mp4URL.flatMap(URL.init), isCondensedGame: isCondensed ) } } catch { return [] } } } var allHighlights: [HighlightItem] = [] for await batch in group { allHighlights.append(contentsOf: batch) } highlights = allHighlights } isLoading = false logFeed("loadHighlights complete count=\(highlights.count)") } var condensedGames: [HighlightItem] { highlights.filter(\.isCondensedGame) } var latestHighlights: [HighlightItem] { highlights.filter { !$0.isCondensedGame } } func startAutoRefresh(games: [Game]) { stopAutoRefresh() refreshTask = Task { [weak self] in while !Task.isCancelled { try? await Task.sleep(for: .seconds(300)) guard !Task.isCancelled, let self else { break } await self.loadHighlights(games: games) } } } func stopAutoRefresh() { refreshTask?.cancel() refreshTask = nil } }