// // TravelPlacement.swift // SportsTime // // Computes which day number each travel segment should be displayed on. // Extracted from TripDetailView for testability. // import Foundation enum TravelPlacement { /// Result of computing travel placement for a trip. struct Placement { let day: Int let segmentIndex: Int } /// Computes which day each travel segment belongs to. /// /// Uses stop indices (not city name matching) so repeat cities work correctly. /// `trip.travelSegments[i]` connects `trip.stops[i]` to `trip.stops[i+1]`. /// /// - Parameters: /// - trip: The trip containing stops and travel segments. /// - tripDays: Array of dates (one per trip day, start-of-day normalized). /// - Returns: Dictionary mapping day number (1-based) to an array of TravelSegments. /// Multiple segments can land on the same day (e.g. back-to-back single-game stops). static func computeTravelByDay( trip: Trip, tripDays: [Date] ) -> [Int: [TravelSegment]] { var travelByDay: [Int: [TravelSegment]] = [:] for (segmentIndex, segment) in trip.travelSegments.enumerated() { let minDay: Int let maxDay: Int let defaultDay: Int if segmentIndex < trip.stops.count - 1 { let fromStop = trip.stops[segmentIndex] let toStop = trip.stops[segmentIndex + 1] let fromDayNum = dayNumber(for: fromStop.departureDate, in: tripDays) let toDayNum = dayNumber(for: toStop.arrivalDate, in: tripDays) minDay = max(fromDayNum + 1, 1) maxDay = min(toDayNum, tripDays.count) // Cap default at the arrival day to prevent overshoot when // departure is already the next morning (fromDayNum+1 > toDayNum). defaultDay = min(fromDayNum + 1, toDayNum) } else { minDay = 1 maxDay = tripDays.count defaultDay = 1 } let clampedDefault: Int if minDay <= maxDay { clampedDefault = max(minDay, min(defaultDay, maxDay)) } else { // Invalid range (departure day is at or past arrival day). // Fall back to arrival day, clamped within trip bounds. clampedDefault = max(1, min(defaultDay, tripDays.count)) } travelByDay[clampedDefault, default: []].append(segment) } return travelByDay } /// Convert a date to a 1-based day number within the trip days array. /// Returns 0 if before trip start, tripDays.count + 1 if after trip end. static func dayNumber(for date: Date, in tripDays: [Date]) -> Int { let calendar = Calendar.current let target = calendar.startOfDay(for: date) for (index, tripDay) in tripDays.enumerated() { if calendar.startOfDay(for: tripDay) == target { return index + 1 } } if let firstDay = tripDays.first, target < firstDay { return 0 } return tripDays.count + 1 } }