//
//  SuggestedTripsGenerator.swift
//  SportsTime
//
//  Generates suggested trip options for the home screen.
//

import Foundation
import SwiftUI
import CoreLocation

// MARK: - Suggested Trip Model

struct SuggestedTrip: Identifiable {
    let id: UUID
    let region: Region
    let isSingleSport: Bool
    let trip: Trip
    let richGames: [String: RichGame]
    let sports: Set<Sport>

    var displaySports: [Sport] {
        Array(sports).sorted { $0.rawValue < $1.rawValue }
    }

    var sportLabel: String {
        if sports.count == 1 {
            return sports.first?.rawValue ?? ""
        } else {
            return "Multi-Sport"
        }
    }
}

// MARK: - Generator

@MainActor
@Observable
final class SuggestedTripsGenerator {

    // MARK: - Published State

    var isLoading = false
    var suggestedTrips: [SuggestedTrip] = []
    var loadingMessage: String = ""
    var error: String?

    // MARK: - Dependencies

    private let dataProvider = AppDataProvider.shared

    // MARK: - Grouped Trips

    var tripsByRegion: [(region: Region, trips: [SuggestedTrip])] {
        let grouped = Dictionary(grouping: suggestedTrips) { $0.region }
        return Region.allCases.compactMap { region in
            guard var trips = grouped[region], !trips.isEmpty else { return nil }

            // For cross-country trips, sort by most stops (descending) and limit to top 2
            if region == .crossCountry {
                trips = trips
                    .sorted { $0.trip.stops.count > $1.trip.stops.count }
                    .prefix(2)
                    .map { $0 }
            }

            return (region: region, trips: trips)
        }
    }

    // MARK: - Generation

    func generateTrips() async {
        guard !isLoading else { return }

        isLoading = true
        error = nil
        suggestedTrips = []

        // Set loading message
        loadingMessage = "Finding the best routes..."

        // Ensure data is loaded
        if dataProvider.teams.isEmpty {
            await dataProvider.loadInitialData()
        }

        guard !dataProvider.stadiums.isEmpty else {
            error = "Unable to load stadium data"
            isLoading = false
            return
        }

        // Calculate date window: 4-8 weeks from now
        let calendar = Calendar.current
        let today = Date()
        guard let startDate = calendar.date(byAdding: .weekOfYear, value: 4, to: today),
              let endDate = calendar.date(byAdding: .weekOfYear, value: 8, to: today) else {
            error = "Failed to calculate date range"
            isLoading = false
            return
        }

        do {
            // Filter all games in the window
            let allSports = Set(Sport.supported)
            let games = try await dataProvider.filterGames(
                sports: allSports,
                startDate: startDate,
                endDate: endDate
            )

            guard !games.isEmpty else {
                error = "No games found in the next 4-8 weeks"
                isLoading = false
                return
            }

            // Build lookups (use reduce to handle potential duplicate UUIDs gracefully)
            let stadiums = dataProvider.stadiums
            let teams = dataProvider.teams

            // Move heavy computation to background task
            let result = await Task.detached(priority: .userInitiated) {
                Self.generateTripsInBackground(
                    games: games,
                    stadiums: stadiums,
                    teams: teams,
                    startDate: startDate,
                    endDate: endDate
                )
            }.value

            suggestedTrips = result
        } catch {
            self.error = "Failed to generate trips: \(error.localizedDescription)"
        }

        isLoading = false
    }

    func refreshTrips() async {
        await generateTrips()
    }

    // MARK: - Background Trip Generation

    /// Performs heavy trip generation computation off the main actor
    private nonisolated static func generateTripsInBackground(
        games: [Game],
        stadiums: [Stadium],
        teams: [Team],
        startDate: Date,
        endDate: Date
    ) -> [SuggestedTrip] {
        // Build lookups (use reduce to handle potential duplicate UUIDs gracefully)
        let stadiumsById = stadiums.reduce(into: [String: Stadium]()) { $0[$1.id] = $1 }
        let teamsById = teams.reduce(into: [String: Team]()) { $0[$1.id] = $1 }

        // Create a local planning engine for this background task
        let planningEngine = TripPlanningEngine()

        var generatedTrips: [SuggestedTrip] = []

        // Generate regional trips (East, Central, West)
        for region in [Region.east, Region.central, Region.west] {
            let regionStadiumIds = Set(
                stadiums
                    .filter { $0.region == region }
                    .map { $0.id }
            )

            let regionGames = games.filter { regionStadiumIds.contains($0.stadiumId) }

            guard !regionGames.isEmpty else { continue }

            // Single sport trip
            if let singleSportTrip = generateRegionalTrip(
                games: regionGames,
                region: region,
                singleSport: true,
                stadiums: stadiumsById,
                teams: teamsById,
                startDate: startDate,
                endDate: endDate,
                planningEngine: planningEngine
            ) {
                generatedTrips.append(singleSportTrip)
            }

            // Multi-sport trip
            if let multiSportTrip = generateRegionalTrip(
                games: regionGames,
                region: region,
                singleSport: false,
                stadiums: stadiumsById,
                teams: teamsById,
                startDate: startDate,
                endDate: endDate,
                planningEngine: planningEngine
            ) {
                generatedTrips.append(multiSportTrip)
            } else if let fallbackTrip = generateRegionalTrip(
                // Fallback: if multi-sport fails, try another single-sport
                games: regionGames,
                region: region,
                singleSport: true,
                stadiums: stadiumsById,
                teams: teamsById,
                startDate: startDate,
                endDate: endDate,
                excludingSport: generatedTrips.last?.sports.first,
                planningEngine: planningEngine
            ) {
                generatedTrips.append(fallbackTrip)
            }
        }

        // Cross-country trips (2)
        for i in 0..<2 {
            if let crossCountryTrip = generateCrossCountryTrip(
                games: games,
                stadiums: stadiumsById,
                teams: teamsById,
                startDate: startDate,
                endDate: endDate,
                excludeGames: i > 0 ? generatedTrips.last?.richGames.values.map { $0.game } ?? [] : []
            ) {
                generatedTrips.append(crossCountryTrip)
            }
        }

        return generatedTrips
    }

    // MARK: - Trip Generation Helpers

    private nonisolated static func generateRegionalTrip(
        games: [Game],
        region: Region,
        singleSport: Bool,
        stadiums: [String: Stadium],
        teams: [String: Team],
        startDate: Date,
        endDate: Date,
        excludingSport: Sport? = nil,
        planningEngine: TripPlanningEngine
    ) -> SuggestedTrip? {

        var filteredGames = games

        if singleSport {
            // Pick a random sport that has games in this region
            let sportsWithGames = Set(games.map { $0.sport })
            var availableSports = Array(sportsWithGames)

            if let exclude = excludingSport {
                availableSports.removeAll { $0 == exclude }
            }

            guard let selectedSport = availableSports.randomElement() else { return nil }
            filteredGames = games.filter { $0.sport == selectedSport }
        }

        guard !filteredGames.isEmpty else { return nil }

        // Randomly select a subset of games (3-6 games for a trip)
        let gameCount = min(filteredGames.count, Int.random(in: 3...6))
        let selectedGames = Array(filteredGames.shuffled().prefix(gameCount))
            .sorted { $0.dateTime < $1.dateTime }

        // Calculate trip dates based on selected games
        guard let firstGame = selectedGames.first,
              let lastGame = selectedGames.last else { return nil }

        let tripStartDate = Calendar.current.date(byAdding: .day, value: -1, to: firstGame.dateTime) ?? firstGame.dateTime
        let tripEndDate = Calendar.current.date(byAdding: .day, value: 1, to: lastGame.dateTime) ?? lastGame.dateTime

        let sports = Set(selectedGames.map { $0.sport })

        // Build planning request
        let preferences = TripPreferences(
            planningMode: .dateRange,
            sports: sports,
            startDate: tripStartDate,
            endDate: tripEndDate,
            leisureLevel: .moderate
        )

        let request = PlanningRequest(
            preferences: preferences,
            availableGames: selectedGames,
            teams: teams,
            stadiums: stadiums
        )

        // Run planning engine
        let result = planningEngine.planItineraries(request: request)

        switch result {
        case .success(let options):
            guard let option = options.first else { return nil }

            let trip = convertToTrip(option: option, preferences: preferences)

            // Build richGames dictionary
            let richGames = buildRichGames(from: selectedGames, teams: teams, stadiums: stadiums)

            // Compute sports from games actually in the trip (not all selectedGames)
            let gameIdsInTrip = Set(trip.stops.flatMap { $0.games })
            let actualSports = Set(gameIdsInTrip.compactMap { richGames[$0]?.game.sport })

            return SuggestedTrip(
                id: UUID(),
                region: region,
                isSingleSport: actualSports.count == 1,
                trip: trip,
                richGames: richGames,
                sports: actualSports.isEmpty ? sports : actualSports
            )

        case .failure:
            return nil
        }
    }

    private nonisolated static func buildRichGames(from games: [Game], teams: [String: Team], stadiums: [String: Stadium]) -> [String: RichGame] {
        var result: [String: RichGame] = [:]
        for game in games {
            guard let homeTeam = teams[game.homeTeamId],
                  let awayTeam = teams[game.awayTeamId],
                  let stadium = stadiums[game.stadiumId] else { continue }
            result[game.id] = RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
        }
        return result
    }

    private nonisolated static func generateCrossCountryTrip(
        games: [Game],
        stadiums: [String: Stadium],
        teams: [String: Team],
        startDate: Date,
        endDate: Date,
        excludeGames: [Game]
    ) -> SuggestedTrip? {

        let excludeIds = Set(excludeGames.map { $0.id })
        let availableGames = games.filter { !excludeIds.contains($0.id) }

        guard !availableGames.isEmpty else { return nil }

        // Group games by region
        var gamesByRegion: [Region: [Game]] = [:]
        for game in availableGames {
            guard let stadium = stadiums[game.stadiumId] else { continue }
            gamesByRegion[stadium.region, default: []].append(game)
        }

        print("📍 CrossCountry: Games by region - East: \(gamesByRegion[.east]?.count ?? 0), Central: \(gamesByRegion[.central]?.count ?? 0), West: \(gamesByRegion[.west]?.count ?? 0)")

        // REQUIREMENT: Must have games in BOTH East AND West for a true coast-to-coast
        guard let eastGames = gamesByRegion[.east], !eastGames.isEmpty,
              let westGames = gamesByRegion[.west], !westGames.isEmpty else {
            print("❌ CrossCountry: Need games in BOTH East and West regions")
            return nil
        }

        let centralGames = gamesByRegion[.central] ?? []
        let calendar = Calendar.current

        // Try both directions: East→West and West→East
        let eastToWest = buildCoastToCoastRoute(
            startGames: eastGames,
            middleGames: centralGames,
            endGames: westGames,
            stadiums: stadiums,
            calendar: calendar
        )

        let westToEast = buildCoastToCoastRoute(
            startGames: westGames,
            middleGames: centralGames,
            endGames: eastGames,
            stadiums: stadiums,
            calendar: calendar
        )

        // Pick the better route (more games)
        var selectedGames: [Game]
        if eastToWest.count >= westToEast.count && eastToWest.count >= 3 {
            selectedGames = eastToWest
            print("🧭 CrossCountry: Using East→West route")
        } else if westToEast.count >= 3 {
            selectedGames = westToEast
            print("🧭 CrossCountry: Using West→East route")
        } else {
            print("❌ CrossCountry: No valid coast-to-coast route found (E→W: \(eastToWest.count), W→E: \(westToEast.count))")
            return nil
        }

        // Validate the route spans both coasts
        let routeRegions = Set(selectedGames.compactMap { stadiums[$0.stadiumId]?.region })
        guard routeRegions.contains(.east) && routeRegions.contains(.west) else {
            print("❌ CrossCountry: Route doesn't span both coasts: \(routeRegions)")
            return nil
        }

        // Debug: show selected cities and their regions
        let cityDetails = selectedGames.compactMap { game -> String? in
            guard let stadium = stadiums[game.stadiumId] else { return nil }
            let dateStr = game.dateTime.formatted(date: .abbreviated, time: .omitted)
            return "\(stadium.city)(\(stadium.region.rawValue.prefix(1)),\(dateStr))"
        }
        print("✅ CrossCountry: Selected \(selectedGames.count) games: \(cityDetails.joined(separator: " → "))")

        // Build trip directly from selected games (bypass planning engine)
        guard let firstGame = selectedGames.first,
              let lastGame = selectedGames.last else { return nil }

        let tripStartDate = calendar.date(byAdding: .day, value: -1, to: firstGame.dateTime) ?? firstGame.dateTime
        let tripEndDate = calendar.date(byAdding: .day, value: 1, to: lastGame.dateTime) ?? lastGame.dateTime

        let sports = Set(selectedGames.map { $0.sport })

        // Build stops by grouping consecutive games at the same stadium
        var tripStops: [TripStop] = []
        var currentStadiumId: String? = nil
        var currentGames: [Game] = []

        for game in selectedGames {
            if game.stadiumId == currentStadiumId {
                currentGames.append(game)
            } else {
                // Finalize previous stop
                if let stadiumId = currentStadiumId, !currentGames.isEmpty {
                    if let stop = buildTripStop(from: currentGames, stadiumId: stadiumId, stadiums: stadiums, stopNumber: tripStops.count + 1) {
                        tripStops.append(stop)
                    }
                }
                currentStadiumId = game.stadiumId
                currentGames = [game]
            }
        }
        // Don't forget the last group
        if let stadiumId = currentStadiumId, !currentGames.isEmpty {
            if let stop = buildTripStop(from: currentGames, stadiumId: stadiumId, stadiums: stadiums, stopNumber: tripStops.count + 1) {
                tripStops.append(stop)
            }
        }

        guard tripStops.count >= 3 else {
            print("❌ CrossCountry: Only \(tripStops.count) stops built (need at least 3)")
            return nil
        }

        print("✅ CrossCountry: Built trip with \(tripStops.count) stops")

        // Build the trip
        let preferences = TripPreferences(
            planningMode: .dateRange,
            sports: sports,
            startDate: tripStartDate,
            endDate: tripEndDate,
            leisureLevel: .moderate
        )

        // Generate travel segments between stops
        let travelSegments = buildTravelSegments(from: tripStops, stadiums: stadiums)

        // Calculate totals from travel segments
        let totalDistanceMeters = travelSegments.reduce(0) { $0 + $1.distanceMeters }
        let totalDrivingSeconds = travelSegments.reduce(0) { $0 + $1.durationSeconds }

        let trip = Trip(
            name: generateTripName(from: tripStops),
            preferences: preferences,
            stops: tripStops,
            travelSegments: travelSegments,
            totalGames: selectedGames.count,
            totalDistanceMeters: totalDistanceMeters,
            totalDrivingSeconds: totalDrivingSeconds
        )

        // Build richGames dictionary
        let richGames = buildRichGames(from: selectedGames, teams: teams, stadiums: stadiums)

        return SuggestedTrip(
            id: UUID(),
            region: .crossCountry,
            isSingleSport: sports.count == 1,
            trip: trip,
            richGames: richGames,
            sports: sports
        )
    }

    /// Builds a TripStop from a group of games at the same stadium
    private nonisolated static func buildTripStop(from games: [Game], stadiumId: String, stadiums: [String: Stadium], stopNumber: Int) -> TripStop? {
        guard !games.isEmpty else { return nil }

        let sortedGames = games.sorted { $0.dateTime < $1.dateTime }
        let stadium = stadiums[stadiumId]
        let city = stadium?.city ?? "Unknown"
        let state = stadium?.state ?? ""
        let coordinate = stadium?.coordinate

        let lastGameDate = sortedGames.last?.gameDate ?? Date()
        let departureDate = Calendar.current.date(byAdding: .day, value: 1, to: lastGameDate) ?? lastGameDate

        return TripStop(
            stopNumber: stopNumber,
            city: city,
            state: state,
            coordinate: coordinate,
            arrivalDate: sortedGames.first?.gameDate ?? Date(),
            departureDate: departureDate,
            games: sortedGames.map { $0.id },
            isRestDay: false
        )
    }

    /// Builds travel segments between consecutive stops using TravelEstimator
    private nonisolated static func buildTravelSegments(from stops: [TripStop], stadiums: [String: Stadium]) -> [TravelSegment] {
        guard stops.count >= 2 else { return [] }

        var segments: [TravelSegment] = []
        let constraints = DrivingConstraints.default

        for i in 0..<(stops.count - 1) {
            let fromStop = stops[i]
            let toStop = stops[i + 1]

            // Get coordinates from stops or from stadium lookup
            let fromCoord = fromStop.coordinate ?? stadiums.values.first { $0.city == fromStop.city }?.coordinate
            let toCoord = toStop.coordinate ?? stadiums.values.first { $0.city == toStop.city }?.coordinate

            let fromLocation = LocationInput(name: fromStop.city, coordinate: fromCoord)
            let toLocation = LocationInput(name: toStop.city, coordinate: toCoord)

            // Use TravelEstimator for consistent distance/time calculations
            if let segment = TravelEstimator.estimate(from: fromLocation, to: toLocation, constraints: constraints) {
                segments.append(segment)
            }
        }

        return segments
    }

    private nonisolated static func convertToTrip(option: ItineraryOption, preferences: TripPreferences) -> Trip {
        print("🔍 convertToTrip: option.stops.count = \(option.stops.count)")

        let tripStops = option.stops.enumerated().map { index, stop in
            TripStop(
                stopNumber: index + 1,
                city: stop.city,
                state: stop.state,
                coordinate: stop.coordinate,
                arrivalDate: stop.arrivalDate,
                departureDate: stop.departureDate,
                games: stop.games,
                isRestDay: stop.games.isEmpty
            )
        }

        print("🔍 convertToTrip: tripStops.count = \(tripStops.count)")

        return Trip(
            name: generateTripName(from: tripStops),
            preferences: preferences,
            stops: tripStops,
            travelSegments: option.travelSegments,
            totalGames: option.totalGames,
            totalDistanceMeters: option.totalDistanceMiles * 1609.34,
            totalDrivingSeconds: option.totalDrivingHours * 3600
        )
    }

    private nonisolated static func generateTripName(from stops: [TripStop]) -> String {
        let cities = stops.compactMap { $0.city }.prefix(3)
        if cities.count <= 1 {
            return cities.first ?? "Road Trip"
        }
        return cities.joined(separator: " - ")
    }

    // MARK: - Corridor Trip Building

    private enum Direction {
        case east, west
    }

    /// Builds a trip following a geographic corridor (moving consistently east or west)
    private nonisolated static func buildCorridorTrip(
        games: [(game: Game, lon: Double)],
        stadiums: [String: Stadium],
        direction: Direction,
        calendar: Calendar
    ) -> [Game] {
        guard !games.isEmpty else { return [] }

        var selectedGames: [Game] = []
        var lastLongitude: Double?
        var lastDate: Date?

        // Max driving distance per day (roughly 500 miles = ~8 hours at 60mph)
        let maxDailyMiles = 500.0

        for (game, longitude) in games {
            let gameDay = calendar.startOfDay(for: game.dateTime)

            // Skip if same day as last game
            if let last = lastDate, calendar.isDate(gameDay, inSameDayAs: last) {
                continue
            }

            // Check direction constraint
            if let lastLon = lastLongitude {
                let isCorrectDirection: Bool
                switch direction {
                case .west: isCorrectDirection = longitude < lastLon + 2 // Allow small eastward drift
                case .east: isCorrectDirection = longitude > lastLon - 2 // Allow small westward drift
                }

                if !isCorrectDirection {
                    continue
                }

                // Check driving distance is reasonable
                if let lastGame = selectedGames.last,
                   let lastStadium = stadiums[lastGame.stadiumId],
                   let currentStadium = stadiums[game.stadiumId] {
                    let daysBetween = calendar.dateComponents([.day], from: lastDate!, to: gameDay).day ?? 0
                    let maxMiles = Double(max(1, daysBetween)) * maxDailyMiles

                    let distance = haversineDistance(
                        lat1: lastStadium.coordinate.latitude,
                        lon1: lastStadium.coordinate.longitude,
                        lat2: currentStadium.coordinate.latitude,
                        lon2: currentStadium.coordinate.longitude
                    )

                    if distance > maxMiles {
                        continue // Too far to drive
                    }
                }
            }

            selectedGames.append(game)
            lastLongitude = longitude
            lastDate = gameDay

            if selectedGames.count >= 6 { break }
        }

        return selectedGames
    }

    /// Builds a coast-to-coast route: start region → middle games → end region
    /// Returns games in calendar order that are drivable between each stop
    /// INVARIANT: No two games on the same calendar day can be at different stadiums
    /// PREFERS: Shorter trips (fewer days) over more games
    private nonisolated static func buildCoastToCoastRoute(
        startGames: [Game],
        middleGames: [Game],
        endGames: [Game],
        stadiums: [String: Stadium],
        calendar: Calendar
    ) -> [Game] {
        // Sort all games by date
        let sortedStart = startGames.sorted { $0.dateTime < $1.dateTime }
        let sortedMiddle = middleGames.sorted { $0.dateTime < $1.dateTime }
        let sortedEnd = endGames.sorted { $0.dateTime < $1.dateTime }

        // Maximum driving per day (roughly 500 miles)
        let maxDailyMiles = 500.0

        // Collect all valid routes to pick the shortest
        var validRoutes: [(games: [Game], days: Int, distance: Double)] = []

        for startGame in sortedStart {
            guard let startStadium = stadiums[startGame.stadiumId] else { continue }

            // Find ending games that are AFTER the start game with enough time to drive cross-country
            // Cross-country is roughly 2500-3000 miles, so need at least 5-6 days
            let minDaysForCrossCountry = 5
            let maxDaysForTrip = 14 // Cap trips at 2 weeks for reasonable length

            for endGame in sortedEnd {
                guard let endStadium = stadiums[endGame.stadiumId] else { continue }

                let daysBetween = calendar.dateComponents([.day], from: startGame.dateTime, to: endGame.dateTime).day ?? 0

                // Need enough time to drive cross-country but not TOO long
                guard daysBetween >= minDaysForCrossCountry && daysBetween <= maxDaysForTrip else { continue }

                // Build route: start → middle games → end
                var route: [Game] = [startGame]
                var lastGame = startGame
                var lastStadium = startStadium
                var totalDistance: Double = 0

                // Track all days with games and their stadiums for conflict detection
                var gamesByDay: [Date: String] = [:]
                gamesByDay[calendar.startOfDay(for: startGame.dateTime)] = startGame.stadiumId

                // Find middle games that fit between start and end (limit to 2-3 middle stops)
                var middleStopsAdded = 0
                let maxMiddleStops = 3

                for middleGame in sortedMiddle {
                    guard middleStopsAdded < maxMiddleStops else { break }
                    guard let middleStadium = stadiums[middleGame.stadiumId] else { continue }

                    // Must be after last game
                    guard middleGame.dateTime > lastGame.dateTime else { continue }

                    // Must be before end game
                    guard middleGame.dateTime < endGame.dateTime else { continue }

                    // CRITICAL: Check ALL games in route for same-day conflict, not just last game
                    let middleGameDay = calendar.startOfDay(for: middleGame.dateTime)
                    if let existingStadiumId = gamesByDay[middleGameDay] {
                        if middleGame.stadiumId != existingStadiumId {
                            continue // Can't be in two different cities on the same day
                        }
                    }

                    // Check if drivable from last stop
                    let daysFromLast = calendar.dateComponents([.day], from: lastGame.dateTime, to: middleGame.dateTime).day ?? 0
                    let maxMilesFromLast = Double(max(1, daysFromLast)) * maxDailyMiles
                    let distanceFromLast = haversineDistance(
                        lat1: lastStadium.coordinate.latitude,
                        lon1: lastStadium.coordinate.longitude,
                        lat2: middleStadium.coordinate.latitude,
                        lon2: middleStadium.coordinate.longitude
                    )

                    guard distanceFromLast <= maxMilesFromLast else { continue }

                    // Check if we can still reach the end from this middle game
                    let daysToEnd = calendar.dateComponents([.day], from: middleGame.dateTime, to: endGame.dateTime).day ?? 0
                    let maxMilesToEnd = Double(max(1, daysToEnd)) * maxDailyMiles
                    let distanceToEnd = haversineDistance(
                        lat1: middleStadium.coordinate.latitude,
                        lon1: middleStadium.coordinate.longitude,
                        lat2: endStadium.coordinate.latitude,
                        lon2: endStadium.coordinate.longitude
                    )

                    guard distanceToEnd <= maxMilesToEnd else { continue }

                    // This middle game works!
                    route.append(middleGame)
                    totalDistance += distanceFromLast
                    gamesByDay[middleGameDay] = middleGame.stadiumId
                    lastGame = middleGame
                    lastStadium = middleStadium
                    middleStopsAdded += 1
                }

                // CRITICAL: Check end game against ALL days in route
                let endGameDay = calendar.startOfDay(for: endGame.dateTime)
                if let existingStadiumId = gamesByDay[endGameDay] {
                    if endGame.stadiumId != existingStadiumId {
                        continue // Can't be in two different cities on the same day
                    }
                }

                let daysToEnd = calendar.dateComponents([.day], from: lastGame.dateTime, to: endGame.dateTime).day ?? 0
                let maxMilesToEnd = Double(max(1, daysToEnd)) * maxDailyMiles
                let distanceToEnd = haversineDistance(
                    lat1: lastStadium.coordinate.latitude,
                    lon1: lastStadium.coordinate.longitude,
                    lat2: endStadium.coordinate.latitude,
                    lon2: endStadium.coordinate.longitude
                )

                if distanceToEnd <= maxMilesToEnd {
                    route.append(endGame)
                    totalDistance += distanceToEnd

                    // Final validation: ensure no same-day conflicts in entire route
                    if validateNoSameDayConflicts(route, calendar: calendar) && route.count >= 3 {
                        validRoutes.append((games: route, days: daysBetween, distance: totalDistance))
                    }
                }
            }
        }

        // Sort routes: prefer fewer days, then less distance
        let sortedRoutes = validRoutes.sorted { a, b in
            if a.days != b.days {
                return a.days < b.days
            }
            return a.distance < b.distance
        }

        // Return the shortest valid route
        if let best = sortedRoutes.first {
            print("✅ C2C route: \(best.days) days, \(Int(best.distance)) miles, \(best.games.count) games")
            return best.games
        }

        return []
    }

    /// Validates that no two games in the route are on the same calendar day at different stadiums
    private nonisolated static func validateNoSameDayConflicts(_ games: [Game], calendar: Calendar) -> Bool {
        var gamesByDay: [Date: String] = [:]
        for game in games {
            let day = calendar.startOfDay(for: game.dateTime)
            if let existingStadiumId = gamesByDay[day] {
                if game.stadiumId != existingStadiumId {
                    print("⚠️ Same-day conflict: \(day) has games at different stadiums")
                    return false
                }
            }
            gamesByDay[day] = game.stadiumId
        }
        return true
    }

    /// Haversine distance in miles between two coordinates
    private nonisolated static func haversineDistance(lat1: Double, lon1: Double, lat2: Double, lon2: Double) -> Double {
        let R = 3959.0 // Earth radius in miles
        let dLat = (lat2 - lat1) * .pi / 180
        let dLon = (lon2 - lon1) * .pi / 180
        let a = sin(dLat/2) * sin(dLat/2) +
                cos(lat1 * .pi / 180) * cos(lat2 * .pi / 180) *
                sin(dLon/2) * sin(dLon/2)
        let c = 2 * atan2(sqrt(a), sqrt(1-a))
        return R * c
    }
}
