// // ScheduleViewModel.swift // SportsTime // import Foundation import SwiftUI @MainActor @Observable final class ScheduleViewModel { // MARK: - Filter State var selectedSports: Set = Set(Sport.supported) var startDate: Date = Date() var endDate: Date = Calendar.current.date(byAdding: .day, value: 14, to: Date()) ?? Date() var searchText: String = "" // MARK: - Data State private(set) var games: [RichGame] = [] private(set) var isLoading = false private(set) var error: Error? private(set) var errorMessage: String? private let dataProvider = AppDataProvider.shared // MARK: - Pagination private let pageSize = 50 private(set) var displayedGames: [RichGame] = [] private var currentPage = 0 private var allFilteredGames: [RichGame] = [] var hasMoreGames: Bool { displayedGames.count < allFilteredGames.count } /// The last game in the displayed list, used for infinite scroll detection var lastDisplayedGame: RichGame? { displayedGames.last } // MARK: - Computed Properties var filteredGames: [RichGame] { guard !searchText.isEmpty else { return games } let query = searchText.lowercased() return games.filter { game in game.homeTeam.name.lowercased().contains(query) || game.homeTeam.city.lowercased().contains(query) || game.awayTeam.name.lowercased().contains(query) || game.awayTeam.city.lowercased().contains(query) || game.stadium.name.lowercased().contains(query) || game.stadium.city.lowercased().contains(query) } } var gamesByDate: [(date: Date, games: [RichGame])] { let calendar = Calendar.current let grouped = Dictionary(grouping: filteredGames) { game in calendar.startOfDay(for: game.game.dateTime) } return grouped.sorted { $0.key < $1.key }.map { ($0.key, $0.value) } } var gamesBySport: [(sport: Sport, games: [RichGame])] { let grouped = Dictionary(grouping: displayedGames) { game in game.game.sport } // Sort by sport order (use allCases index for consistent ordering) // Within each sport, games are sorted by date return grouped .sorted { lhs, rhs in let lhsIndex = Sport.allCases.firstIndex(of: lhs.key) ?? 0 let rhsIndex = Sport.allCases.firstIndex(of: rhs.key) ?? 0 return lhsIndex < rhsIndex } .map { (sport: $0.key, games: $0.value.sorted { $0.game.dateTime < $1.game.dateTime }) } } var hasFilters: Bool { selectedSports.count < Sport.supported.count || !searchText.isEmpty } // MARK: - Pagination Actions /// Updates filtered games list based on current search text and resets pagination func updateFilteredGames() { allFilteredGames = filteredGames loadInitialGames() } /// Loads the first page of games func loadInitialGames() { currentPage = 0 displayedGames = Array(allFilteredGames.prefix(pageSize)) } /// Loads more games when scrolling to the bottom func loadMoreGames() { guard hasMoreGames else { return } currentPage += 1 let start = currentPage * pageSize let end = min(start + pageSize, allFilteredGames.count) displayedGames.append(contentsOf: allFilteredGames[start..