//
//  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
    }
}
