// // RouteCandidateBuilder.swift // SportsTime // import Foundation import CoreLocation /// Builds route candidates for different planning scenarios enum RouteCandidateBuilder { // MARK: - Scenario A: Linear Candidates (Date Range) /// Builds linear route candidates from games sorted chronologically /// - Parameters: /// - games: Available games sorted by start time /// - mustStop: Optional must-stop location /// - Returns: Array of route candidates static func buildLinearCandidates( games: [Game], stadiums: [UUID: Stadium], mustStop: LocationInput? ) -> [RouteCandidate] { guard !games.isEmpty else { return [] } // Group games by stadium var stadiumGames: [UUID: [Game]] = [:] for game in games { stadiumGames[game.stadiumId, default: []].append(game) } // Build stops from chronological game order var stops: [ItineraryStop] = [] var processedStadiums: Set = [] for game in games { guard !processedStadiums.contains(game.stadiumId) else { continue } processedStadiums.insert(game.stadiumId) let gamesAtStop = stadiumGames[game.stadiumId] ?? [game] let sortedGames = gamesAtStop.sorted { $0.startTime < $1.startTime } // Look up stadium for coordinates and city info let stadium = stadiums[game.stadiumId] let city = stadium?.city ?? "Unknown" let state = stadium?.state ?? "" let coordinate = stadium?.coordinate let location = LocationInput( name: city, coordinate: coordinate, address: stadium?.fullAddress ) let stop = ItineraryStop( city: city, state: state, coordinate: coordinate, games: sortedGames.map { $0.id }, arrivalDate: sortedGames.first?.gameDate ?? Date(), departureDate: sortedGames.last?.gameDate ?? Date(), location: location, firstGameStart: sortedGames.first?.startTime ) stops.append(stop) } guard !stops.isEmpty else { return [] } return [RouteCandidate( stops: stops, rationale: "Linear route through \(stops.count) cities" )] } // MARK: - Scenario B: Expand Around Anchors (Selected Games) /// Expands route around user-selected anchor games /// - Parameters: /// - anchors: User-selected games (must-see) /// - allGames: All available games /// - dateRange: Trip date range /// - mustStop: Optional must-stop location /// - Returns: Array of route candidates static func expandAroundAnchors( anchors: [Game], allGames: [Game], stadiums: [UUID: Stadium], dateRange: DateInterval, mustStop: LocationInput? ) -> [RouteCandidate] { guard !anchors.isEmpty else { return [] } // Start with anchor games as the core route let sortedAnchors = anchors.sorted { $0.startTime < $1.startTime } // Build stops from anchor games var stops: [ItineraryStop] = [] for game in sortedAnchors { let stadium = stadiums[game.stadiumId] let city = stadium?.city ?? "Unknown" let state = stadium?.state ?? "" let coordinate = stadium?.coordinate let location = LocationInput( name: city, coordinate: coordinate, address: stadium?.fullAddress ) let stop = ItineraryStop( city: city, state: state, coordinate: coordinate, games: [game.id], arrivalDate: game.gameDate, departureDate: game.gameDate, location: location, firstGameStart: game.startTime ) stops.append(stop) } guard !stops.isEmpty else { return [] } return [RouteCandidate( stops: stops, rationale: "Route connecting \(anchors.count) selected games" )] } // MARK: - Scenario C: Directional Routes (Start + End) /// Builds directional routes from start to end location /// - Parameters: /// - start: Start location /// - end: End location /// - games: Available games /// - dateRange: Optional trip date range /// - Returns: Array of route candidates static func buildDirectionalRoutes( start: LocationInput, end: LocationInput, games: [Game], stadiums: [UUID: Stadium], dateRange: DateInterval? ) -> [RouteCandidate] { // Filter games by date range if provided let filteredGames: [Game] if let range = dateRange { filteredGames = games.filter { range.contains($0.startTime) } } else { filteredGames = games } guard !filteredGames.isEmpty else { return [] } // Sort games chronologically let sortedGames = filteredGames.sorted { $0.startTime < $1.startTime } // Build stops: start -> games -> end var stops: [ItineraryStop] = [] // Start stop (no games) let startStop = ItineraryStop( city: start.name, state: "", coordinate: start.coordinate, games: [], arrivalDate: sortedGames.first?.gameDate.addingTimeInterval(-86400) ?? Date(), departureDate: sortedGames.first?.gameDate.addingTimeInterval(-86400) ?? Date(), location: start, firstGameStart: nil ) stops.append(startStop) // Game stops for game in sortedGames { let stadium = stadiums[game.stadiumId] let city = stadium?.city ?? "Unknown" let state = stadium?.state ?? "" let coordinate = stadium?.coordinate let location = LocationInput( name: city, coordinate: coordinate, address: stadium?.fullAddress ) let stop = ItineraryStop( city: city, state: state, coordinate: coordinate, games: [game.id], arrivalDate: game.gameDate, departureDate: game.gameDate, location: location, firstGameStart: game.startTime ) stops.append(stop) } // End stop (no games) let endStop = ItineraryStop( city: end.name, state: "", coordinate: end.coordinate, games: [], arrivalDate: sortedGames.last?.gameDate.addingTimeInterval(86400) ?? Date(), departureDate: sortedGames.last?.gameDate.addingTimeInterval(86400) ?? Date(), location: end, firstGameStart: nil ) stops.append(endStop) return [RouteCandidate( stops: stops, rationale: "Directional route from \(start.name) to \(end.name)" )] } }