//
//  ItineraryTableViewWrapper.swift
//  SportsTime
//
//  UIViewControllerRepresentable wrapper for ItineraryTableViewController
//

import SwiftUI

struct ItineraryTableViewWrapper<HeaderContent: View>: UIViewControllerRepresentable {
    @Environment(\.colorScheme) private var colorScheme
    
    let trip: Trip
    let games: [RichGame]
    let itineraryItems: [ItineraryItem]
    let travelOverrides: [String: TravelOverride]
    let headerContent: HeaderContent
    
    // Callbacks
    var onTravelMoved: ((String, Int, Double) -> Void)?
    var onCustomItemMoved: ((UUID, Int, Double) -> Void)?  // itemId, newDay, newSortOrder
    var onCustomItemTapped: ((ItineraryItem) -> Void)?
    var onCustomItemDeleted: ((ItineraryItem) -> Void)?
    var onAddButtonTapped: ((Int) -> Void)?  // Just day number
    
    init(
        trip: Trip,
        games: [RichGame],
        itineraryItems: [ItineraryItem],
        travelOverrides: [String: TravelOverride],
        @ViewBuilder headerContent: () -> HeaderContent,
        onTravelMoved: ((String, Int, Double) -> Void)? = nil,
        onCustomItemMoved: ((UUID, Int, Double) -> Void)? = nil,
        onCustomItemTapped: ((ItineraryItem) -> Void)? = nil,
        onCustomItemDeleted: ((ItineraryItem) -> Void)? = nil,
        onAddButtonTapped: ((Int) -> Void)? = nil
    ) {
        self.trip = trip
        self.games = games
        self.itineraryItems = itineraryItems
        self.travelOverrides = travelOverrides
        self.headerContent = headerContent()
        self.onTravelMoved = onTravelMoved
        self.onCustomItemMoved = onCustomItemMoved
        self.onCustomItemTapped = onCustomItemTapped
        self.onCustomItemDeleted = onCustomItemDeleted
        self.onAddButtonTapped = onAddButtonTapped
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator {
        var headerHostingController: UIHostingController<HeaderContent>?
    }
    
    func makeUIViewController(context: Context) -> ItineraryTableViewController {
        let controller = ItineraryTableViewController(style: .plain)
        controller.colorScheme = colorScheme
        controller.onTravelMoved = onTravelMoved
        controller.onCustomItemMoved = onCustomItemMoved
        controller.onCustomItemTapped = onCustomItemTapped
        controller.onCustomItemDeleted = onCustomItemDeleted
        controller.onAddButtonTapped = onAddButtonTapped
        
        // Set header with proper sizing
        let hostingController = UIHostingController(rootView: headerContent)
        hostingController.view.backgroundColor = .clear
        
        // Store in coordinator for later updates
        context.coordinator.headerHostingController = hostingController
        
        // Pre-size the header view
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        let targetWidth = UIScreen.main.bounds.width
        let targetSize = CGSize(width: targetWidth, height: UIView.layoutFittingCompressedSize.height)
        let size = hostingController.view.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
        hostingController.view.frame = CGRect(origin: .zero, size: CGSize(width: targetWidth, height: max(size.height, 450)))
        hostingController.view.translatesAutoresizingMaskIntoConstraints = true
        
        controller.setTableHeader(hostingController.view)

        // Load initial data
        let (days, validRanges, allItemsForConstraints) = buildItineraryData()
        controller.reloadData(days: days, travelValidRanges: validRanges, itineraryItems: allItemsForConstraints)

        return controller
    }

    func updateUIViewController(_ controller: ItineraryTableViewController, context: Context) {
        controller.colorScheme = colorScheme
        controller.onTravelMoved = onTravelMoved
        controller.onCustomItemMoved = onCustomItemMoved
        controller.onCustomItemTapped = onCustomItemTapped
        controller.onCustomItemDeleted = onCustomItemDeleted
        controller.onAddButtonTapped = onAddButtonTapped

        // Update header content by updating the hosting controller's rootView
        // This avoids recreating the view hierarchy and prevents infinite loops
        context.coordinator.headerHostingController?.rootView = headerContent

        let (days, validRanges, allItemsForConstraints) = buildItineraryData()
        controller.reloadData(days: days, travelValidRanges: validRanges, itineraryItems: allItemsForConstraints)
    }

    // MARK: - Build Itinerary Data

    private func buildItineraryData() -> ([ItineraryDayData], [String: ClosedRange<Int>], [ItineraryItem]) {
        let tripDays = calculateTripDays()
        var travelValidRanges: [String: ClosedRange<Int>] = [:]

        // Build game items from RichGame data for constraint validation
        var gameItems: [ItineraryItem] = []
        for (index, dayDate) in tripDays.enumerated() {
            let dayNum = index + 1
            let gamesOnDay = gamesOn(date: dayDate)
            for (gameIndex, richGame) in gamesOnDay.enumerated() {
                let gameItem = ItineraryItem(
                    tripId: trip.id,
                    day: dayNum,
                    sortOrder: Double(gameIndex) * 0.01,  // Games have sortOrder ~0 (at the visual boundary)
                    kind: .game(gameId: richGame.game.id, city: richGame.stadium.city)
                )
                gameItems.append(gameItem)
            }
        }

        // Build travel as semantic items with (day, sortOrder)
        var travelItems: [ItineraryItem] = []
        travelItems.reserveCapacity(trip.travelSegments.count)

        func gamesIn(city: String, day: Int) -> [ItineraryItem] {
            gameItems.filter { item in
                guard item.day == day else { return false }
                guard let gameCity = item.gameCity else { return false }
                return cityMatches(gameCity, searchCity: city)
            }
        }
        
        for (segmentIndex, segment) in trip.travelSegments.enumerated() {
            let travelId = stableTravelAnchorId(segment, at: segmentIndex)
            let fromCity = segment.fromLocation.name
            let toCity = segment.toLocation.name

            // VALID RANGE:
            // - Earliest: day of last from-city game (travel can happen AFTER that game)
            // - Latest: day of first to-city game (travel can happen BEFORE that game)
            let lastFromGameDay = findLastGameDay(in: fromCity, tripDays: tripDays)
            let firstToGameDay = findFirstGameDay(in: toCity, tripDays: tripDays)

            let minDay = max(lastFromGameDay == 0 ? 1 : lastFromGameDay, 1)
            let maxDay = min(firstToGameDay == 0 ? tripDays.count : firstToGameDay, tripDays.count)

            let validRange = (minDay <= maxDay) ? (minDay...maxDay) : (maxDay...maxDay)
            travelValidRanges[travelId] = validRange

            // Placement (override if valid)
            let placement: TravelOverride
            if let override = travelOverrides[travelId], validRange.contains(override.day) {
                placement = override
            } else {
                // Default day: minDay. Default sortOrder depends on whether it's an edge game day.
                let day = minDay

                // If we're on the last-from-game day, default to AFTER those games.
                let fromGames = gamesIn(city: fromCity, day: day)
                let maxFrom = fromGames.map { $0.sortOrder }.max() ?? 0.0
                var sortOrder = maxFrom + 1.0

                // If we're on the first-to-game day (and it's the same chosen day), default to BEFORE those games.
                let toGames = gamesIn(city: toCity, day: day)
                if !toGames.isEmpty {
                    let minTo = toGames.map { $0.sortOrder }.min() ?? 0.0
                    sortOrder = minTo - 1.0
                }

                placement = TravelOverride(day: day, sortOrder: sortOrder)
            }

            let travelItem = ItineraryItem(
                tripId: trip.id,
                day: placement.day,
                sortOrder: placement.sortOrder,
                kind: .travel(
                    TravelInfo(
                        fromCity: fromCity,
                        toCity: toCity,
                        segmentIndex: segmentIndex,
                        distanceMeters: segment.distanceMeters,
                        durationSeconds: segment.durationSeconds
                    )
                )
            )
            travelItems.append(travelItem)
        }
        
        // Build day data
        var days: [ItineraryDayData] = []
        days.reserveCapacity(tripDays.count)
        
        for (index, dayDate) in tripDays.enumerated() {
            let dayNum = index + 1
            let gamesOnDay = gamesOn(date: dayDate)
            
            var rows: [ItineraryRowItem] = []
            
            // Custom items for this day
            let customItemsForDay = itineraryItems
                .filter { $0.day == dayNum && $0.isCustom }
                .sorted { $0.sortOrder < $1.sortOrder }
            for item in customItemsForDay {
                rows.append(.customItem(item))
            }
            
            // Travel items for this day (as rows). Ordering comes from sortOrder via controller lookup.
            let travelsForDay = travelItems
                .filter { $0.day == dayNum }
                .sorted { $0.sortOrder < $1.sortOrder }
            for travel in travelsForDay {
                // Find the segment matching this travel by segment index (preferred) or city pair (legacy)
                if let info = travel.travelInfo {
                    let seg: TravelSegment?
                    if let idx = info.segmentIndex, idx < trip.travelSegments.count {
                        seg = trip.travelSegments[idx]
                    } else {
                        seg = trip.travelSegments.first(where: {
                            $0.fromLocation.name.lowercased() == info.fromCity.lowercased()
                            && $0.toLocation.name.lowercased() == info.toCity.lowercased()
                        })
                    }
                    if let seg {
                        rows.append(.travel(seg, dayNumber: dayNum))
                    }
                }
            }
            
            // Sort rows by semantic sortOrder (custom uses its own; travel via travelItems)
            rows.sort { a, b in
                func so(_ r: ItineraryRowItem) -> Double {
                    switch r {
                    case .customItem(let it): return it.sortOrder
                    case .travel(let seg, _):
                        let segIdx = trip.travelSegments.firstIndex(where: { $0.id == seg.id }) ?? 0
                        let id = stableTravelAnchorId(seg, at: segIdx)
                        return (travelOverrides[id]?.sortOrder)
                        ?? (travelItems.first(where: { ti in
                            guard case .travel(let inf) = ti.kind else { return false }
                            return inf.segmentIndex == segIdx
                        })?.sortOrder ?? 0.0)
                    default:
                        return 0.0
                    }
                }
                return so(a) < so(b)
            }
            
            let dayData = ItineraryDayData(
                id: dayNum,
                dayNumber: dayNum,
                date: dayDate,
                games: gamesOnDay,
                items: rows,
                travelBefore: nil
            )
            days.append(dayData)
        }
        
        return (days, travelValidRanges, gameItems + itineraryItems + travelItems)
    }
    
    // MARK: - Helper Methods
    
    private func calculateTripDays() -> [Date] {
        let start = trip.startDate
        let end = trip.endDate
        
        var days: [Date] = []
        var current = Calendar.current.startOfDay(for: start)
        let endDay = Calendar.current.startOfDay(for: end)
        
        while current <= endDay {
            days.append(current)
            current = Calendar.current.date(byAdding: .day, value: 1, to: current) ?? current
        }
        
        return days
    }
    
    private func gamesOn(date: Date) -> [RichGame] {
        let calendar = Calendar.current
        return games.filter { calendar.isDate($0.game.dateTime, inSameDayAs: date) }
            .sorted { $0.game.dateTime < $1.game.dateTime }
    }
    
    private func stableTravelAnchorId(_ segment: TravelSegment, at index: Int) -> String {
        "travel:\(index):\(segment.fromLocation.name.lowercased())->\(segment.toLocation.name.lowercased())"
    }
    
    private func findLastGameDay(in city: String, tripDays: [Date]) -> Int {
        var lastDay = 0
        
        for (index, dayDate) in tripDays.enumerated() {
            let dayNum = index + 1
            let gamesOnDay = gamesOn(date: dayDate)
            if gamesOnDay.contains(where: { cityMatches($0.stadium.city, searchCity: city) }) {
                lastDay = dayNum
            }
        }
        return lastDay
    }
    
    private func findFirstGameDay(in city: String, tripDays: [Date]) -> Int {
        for (index, dayDate) in tripDays.enumerated() {
            let dayNum = index + 1
            let gamesOnDay = gamesOn(date: dayDate)
            if gamesOnDay.contains(where: { cityMatches($0.stadium.city, searchCity: city) }) {
                return dayNum
            }
        }
        return tripDays.count
    }
    
    /// Fuzzy city matching - handles "Salt Lake City" vs "Salt Lake" etc.
    private func cityMatches(_ stadiumCity: String, searchCity: String) -> Bool {
        let stadiumLower = stadiumCity.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
        let searchLower = searchCity.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
        
        // Exact match
        if stadiumLower == searchLower {
            return true
        }
        
        // One contains the other
        if stadiumLower.contains(searchLower) || searchLower.contains(stadiumLower) {
            return true
        }
        
        // Word-based matching (handles "Salt Lake" matching "Salt Lake City")
        let stadiumWords = Set(stadiumLower.components(separatedBy: .whitespaces).filter { !$0.isEmpty })
        let searchWords = Set(searchLower.components(separatedBy: .whitespaces).filter { !$0.isEmpty })
        
        if !searchWords.isEmpty && searchWords.isSubset(of: stadiumWords) {
            return true
        }
        if !stadiumWords.isEmpty && stadiumWords.isSubset(of: searchWords) {
            return true
        }
        
        return false
    }
}
