//
//  PDFGenerator.swift
//  SportsTime
//
//  Generates magazine-style PDF trip itineraries with clean typography and generous whitespace.
//

import Foundation
import PDFKit
import UIKit

@MainActor
final class PDFGenerator {

    // MARK: - Constants

    private let pageWidth: CGFloat = 612   // Letter size
    private let pageHeight: CGFloat = 792
    private let margin: CGFloat = 54       // Print-safe margins
    private let contentWidth: CGFloat = 504  // 612 - 54*2

    // Header/footer heights
    private let headerHeight: CGFloat = 0   // No top header (title in cover only)
    private let footerHeight: CGFloat = 40  // Room for trip info + page number

    // Magazine-style spacing
    private let sectionSpacing: CGFloat = 28
    private let itemSpacing: CGFloat = 16
    private let daySpacing: CGFloat = 36

    // MARK: - Colors

    private let primaryColor = UIColor(red: 0.12, green: 0.23, blue: 0.54, alpha: 1.0)  // Dark blue
    private let accentColor = UIColor(red: 0.97, green: 0.45, blue: 0.09, alpha: 1.0)   // SportsTime orange
    private let textPrimary = UIColor.black
    private let textSecondary = UIColor.darkGray
    private let textMuted = UIColor.gray

    // Sport-specific accent colors
    private func sportAccentColor(for sport: Sport) -> UIColor {
        switch sport {
        case .mlb: return UIColor(red: 0.76, green: 0.09, blue: 0.16, alpha: 1.0)  // Baseball red
        case .nba: return UIColor(red: 0.91, green: 0.33, blue: 0.11, alpha: 1.0)  // Basketball orange
        case .nhl: return UIColor(red: 0.0, green: 0.32, blue: 0.58, alpha: 1.0)   // Hockey blue
        case .nfl: return UIColor(red: 0.0, green: 0.21, blue: 0.38, alpha: 1.0)   // Football navy
        case .mls: return UIColor(red: 0.0, green: 0.5, blue: 0.35, alpha: 1.0)    // Soccer green
        case .wnba: return UIColor(red: 0.98, green: 0.33, blue: 0.14, alpha: 1.0) // WNBA orange
        case .nwsl: return UIColor(red: 0.0, green: 0.45, blue: 0.65, alpha: 1.0)  // NWSL blue
        }
    }

    // MARK: - Trip Info (for footer)

    private var currentTrip: Trip?
    private var totalPages: Int = 1

    // MARK: - Generate PDF

    func generatePDF(
        for trip: Trip,
        games: [String: RichGame],
        assets: PDFAssetPrefetcher.PrefetchedAssets? = nil,
        itineraryItems: [ItineraryItem]? = nil
    ) async throws -> Data {
        // Store trip for footer access
        currentTrip = trip

        // Calculate total pages for footer
        totalPages = calculateTotalPages(trip: trip, games: games, itineraryItems: itineraryItems)

        let pdfRenderer = UIGraphicsPDFRenderer(
            bounds: CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
        )

        let data = pdfRenderer.pdfData { context in
            // Page 1: Cover with hero map
            context.beginPage()
            drawCoverPage(context: context, trip: trip, assets: assets)

            // Pages 2+: Day-by-Day Itinerary (magazine-style continuous flow)
            drawItineraryPages(context: context, trip: trip, games: games, assets: assets, itineraryItems: itineraryItems)
        }

        currentTrip = nil
        return data
    }

    /// Estimate total pages for footer display
    private func calculateTotalPages(trip: Trip, games: [String: RichGame], itineraryItems: [ItineraryItem]?) -> Int {
        var pages = 1  // Cover page

        let usableHeight = pageHeight - margin - footerHeight - 50  // Content area per page
        var currentPageUsed: CGFloat = 0
        let allTripDays = trip.itineraryDays()

        if let items = itineraryItems, !items.isEmpty {
            let groupedItems = Dictionary(grouping: items) { $0.day }
            // Iterate over ALL trip days, not just days with items
            for day in allTripDays {
                let dayItems = groupedItems[day.dayNumber] ?? []
                let dayHeight = estimateItemsDayHeight(items: dayItems, games: games)

                if currentPageUsed + dayHeight > usableHeight {
                    pages += 1
                    currentPageUsed = dayHeight
                } else {
                    currentPageUsed += dayHeight + daySpacing
                }
            }
        } else {
            for day in trip.itineraryDays() {
                let dayHeight = estimateDayHeight(day: day, games: games)

                if currentPageUsed + dayHeight > usableHeight {
                    pages += 1
                    currentPageUsed = dayHeight
                } else {
                    currentPageUsed += dayHeight + daySpacing
                }
            }
        }

        // Add one more page for any remaining content
        if currentPageUsed > 0 {
            pages += 1
        }

        return max(2, pages)  // At least cover + 1 itinerary page
    }

    // MARK: - Cover Page

    private func drawCoverPage(
        context: UIGraphicsPDFRendererContext,
        trip: Trip,
        assets: PDFAssetPrefetcher.PrefetchedAssets?
    ) {
        var y: CGFloat = margin

        // Hero route map (full width, large)
        if let routeMap = assets?.routeMap {
            let mapHeight: CGFloat = 320
            let mapRect = CGRect(x: margin, y: y, width: contentWidth, height: mapHeight)

            // Draw map with rounded corners
            let path = UIBezierPath(roundedRect: mapRect, cornerRadius: 16)
            context.cgContext.saveGState()
            path.addClip()
            routeMap.draw(in: mapRect)
            context.cgContext.restoreGState()

            // Subtle border
            UIColor.lightGray.withAlphaComponent(0.5).setStroke()
            path.lineWidth = 1
            path.stroke()

            y += mapHeight + sectionSpacing
        } else {
            y = 140
        }

        // Accent bar
        let barRect = CGRect(x: margin, y: y, width: 80, height: 4)
        let barPath = UIBezierPath(roundedRect: barRect, cornerRadius: 2)
        accentColor.setFill()
        barPath.fill()
        y += 20

        // Trip name (large, bold)
        let titleFont = UIFont.systemFont(ofSize: 34, weight: .bold)
        let titleAttributes: [NSAttributedString.Key: Any] = [
            .font: titleFont,
            .foregroundColor: textPrimary
        ]
        let titleRect = CGRect(x: margin, y: y, width: contentWidth, height: 45)
        (trip.displayName as NSString).draw(in: titleRect, withAttributes: titleAttributes)
        y += 50

        // Date range (formatted nicely)
        let dateRangeText = formatCoverDateRange(trip: trip)
        let dateAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 18, weight: .regular),
            .foregroundColor: textSecondary
        ]
        (dateRangeText as NSString).draw(at: CGPoint(x: margin, y: y), withAttributes: dateAttributes)
        y += 36

        // Quick stats with bullet separators
        let statsText = "\(trip.stops.count) Cities  •  \(trip.totalGames) Games  •  \(formatMiles(trip.totalDistanceMiles))"
        let statsAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 15, weight: .medium),
            .foregroundColor: primaryColor
        ]
        (statsText as NSString).draw(at: CGPoint(x: margin, y: y), withAttributes: statsAttributes)
        y += 28

        // Driving summary
        let drivingText = "\(trip.tripDuration) Days  •  \(trip.formattedTotalDriving) driving"
        let drivingAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 14),
            .foregroundColor: textMuted
        ]
        (drivingText as NSString).draw(at: CGPoint(x: margin, y: y), withAttributes: drivingAttributes)
        y += sectionSpacing + 12

        // Route overview (compact city list)
        drawCoverSectionHeader("Route", at: &y)

        let cityFont = UIFont.systemFont(ofSize: 13)
        let cityAttributes: [NSAttributedString.Key: Any] = [
            .font: cityFont,
            .foregroundColor: textSecondary
        ]

        // Draw cities in a flowing layout
        var cityLine = ""
        for (index, city) in trip.cities.enumerated() {
            let separator = index == 0 ? "" : "  →  "
            let testLine = cityLine + separator + city

            // Check if we need to wrap
            let testSize = (testLine as NSString).size(withAttributes: cityAttributes)
            if testSize.width > contentWidth - 20 && !cityLine.isEmpty {
                (cityLine as NSString).draw(at: CGPoint(x: margin + 12, y: y), withAttributes: cityAttributes)
                y += 22
                cityLine = city
            } else {
                cityLine = testLine
            }
        }
        // Draw remaining
        if !cityLine.isEmpty {
            (cityLine as NSString).draw(at: CGPoint(x: margin + 12, y: y), withAttributes: cityAttributes)
            y += 22
        }

        // Footer
        drawFooter(context: context, pageNumber: 1)
    }

    /// Format date range for cover: "Jan 18 – Jan 25, 2026"
    private func formatCoverDateRange(trip: Trip) -> String {
        let formatter = DateFormatter()

        // Get first and last dates
        guard let firstStop = trip.stops.first,
              let lastStop = trip.stops.last else {
            return trip.formattedDateRange
        }

        let startDate = firstStop.arrivalDate
        let endDate = lastStop.departureDate

        // Same year?
        let startYear = Calendar.current.component(.year, from: startDate)
        let endYear = Calendar.current.component(.year, from: endDate)

        if startYear == endYear {
            formatter.dateFormat = "MMM d"
            let startStr = formatter.string(from: startDate)
            formatter.dateFormat = "MMM d, yyyy"
            let endStr = formatter.string(from: endDate)
            return "\(startStr) – \(endStr)"
        } else {
            formatter.dateFormat = "MMM d, yyyy"
            return "\(formatter.string(from: startDate)) – \(formatter.string(from: endDate))"
        }
    }

    /// Format miles: "1,234 mi"
    private func formatMiles(_ miles: Double) -> String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.maximumFractionDigits = 0
        let formatted = formatter.string(from: NSNumber(value: miles)) ?? "\(Int(miles))"
        return "\(formatted) mi"
    }

    private func drawCoverSectionHeader(_ title: String, at y: inout CGFloat) {
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 12, weight: .semibold),
            .foregroundColor: textMuted
        ]
        (title.uppercased() as NSString).draw(at: CGPoint(x: margin, y: y), withAttributes: attributes)
        y += 20
    }

    // MARK: - Enhanced Footer

    private func drawFooter(context: UIGraphicsPDFRendererContext, pageNumber: Int) {
        let footerY = pageHeight - footerHeight

        // Separator line
        let linePath = UIBezierPath()
        linePath.move(to: CGPoint(x: margin, y: footerY))
        linePath.addLine(to: CGPoint(x: pageWidth - margin, y: footerY))
        UIColor.lightGray.withAlphaComponent(0.3).setStroke()
        linePath.lineWidth = 0.5
        linePath.stroke()

        let textY = footerY + 12

        // Left: SportsTime branding
        let brandAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 9, weight: .semibold),
            .foregroundColor: accentColor
        ]
        ("SportsTime" as NSString).draw(at: CGPoint(x: margin, y: textY), withAttributes: brandAttributes)

        // Center: Trip name + dates
        if let trip = currentTrip {
            let centerText = "\(trip.displayName)  •  \(formatCoverDateRange(trip: trip))"
            let centerAttributes: [NSAttributedString.Key: Any] = [
                .font: UIFont.systemFont(ofSize: 9),
                .foregroundColor: textMuted
            ]
            let centerSize = (centerText as NSString).size(withAttributes: centerAttributes)
            let centerX = (pageWidth - centerSize.width) / 2
            (centerText as NSString).draw(at: CGPoint(x: centerX, y: textY), withAttributes: centerAttributes)
        }

        // Right: Page number
        let pageText = "Page \(pageNumber)"
        let pageAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 9),
            .foregroundColor: textMuted
        ]
        let pageSize = (pageText as NSString).size(withAttributes: pageAttributes)
        (pageText as NSString).draw(
            at: CGPoint(x: pageWidth - margin - pageSize.width, y: textY),
            withAttributes: pageAttributes
        )
    }

    // MARK: - Itinerary Pages

    private func drawItineraryPages(
        context: UIGraphicsPDFRendererContext,
        trip: Trip,
        games: [String: RichGame],
        assets: PDFAssetPrefetcher.PrefetchedAssets?,
        itineraryItems: [ItineraryItem]?
    ) {
        var pageNumber = 2
        var y: CGFloat = margin
        var isFirstDayOnPage = true

        context.beginPage()
        drawItineraryPageHeader(at: &y)

        // Usable content height (accounting for footer)
        let usableHeight = pageHeight - footerHeight - 20

        // Get all trip days to ensure we render every day
        let allTripDays = trip.itineraryDays()

        // If we have custom itinerary items, use their order; otherwise fall back to derived order
        if let items = itineraryItems, !items.isEmpty {
            let groupedItems = Dictionary(grouping: items) { $0.day }

            // Iterate over ALL trip days, not just days with items
            for day in allTripDays {
                let dayNumber = day.dayNumber
                let dayItems = groupedItems[dayNumber]?.sorted(by: { $0.sortOrder < $1.sortOrder }) ?? []

                let estimatedHeight = estimateItemsDayHeight(items: dayItems, games: games)
                if y + estimatedHeight > usableHeight {
                    drawFooter(context: context, pageNumber: pageNumber)
                    pageNumber += 1
                    context.beginPage()
                    y = margin
                    isFirstDayOnPage = true
                }

                if !isFirstDayOnPage {
                    y += daySpacing
                }

                y = drawDayWithItems(
                    context: context,
                    dayNumber: dayNumber,
                    date: day.date,
                    items: dayItems,
                    games: games,
                    assets: assets,
                    y: y
                )

                isFirstDayOnPage = false
            }
        } else {
            for day in trip.itineraryDays() {
                let estimatedHeight = estimateDayHeight(day: day, games: games)
                if y + estimatedHeight > usableHeight {
                    drawFooter(context: context, pageNumber: pageNumber)
                    pageNumber += 1
                    context.beginPage()
                    y = margin
                    isFirstDayOnPage = true
                }

                if !isFirstDayOnPage {
                    y += daySpacing
                }

                y = drawDay(
                    context: context,
                    day: day,
                    games: games,
                    assets: assets,
                    y: y
                )

                isFirstDayOnPage = false
            }
        }

        drawFooter(context: context, pageNumber: pageNumber)
    }

    private func drawItineraryPageHeader(at y: inout CGFloat) {
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 11, weight: .semibold),
            .foregroundColor: textMuted
        ]
        ("ITINERARY" as NSString).draw(at: CGPoint(x: margin, y: y), withAttributes: attributes)
        y += sectionSpacing
    }

    /// Estimate height for a day rendered from ItineraryItems
    private func estimateItemsDayHeight(items: [ItineraryItem], games: [String: RichGame]) -> CGFloat {
        var height: CGFloat = 50  // Day header

        for item in items {
            switch item.kind {
            case .game:
                height += 44 + itemSpacing  // Simplified game entry
            case .travel:
                height += 36 + itemSpacing  // Travel segment
            case .custom:
                height += 40 + itemSpacing  // Custom item
            }
        }

        return height
    }

    /// Draw a day using ItineraryItems for order (magazine-style)
    private func drawDayWithItems(
        context: UIGraphicsPDFRendererContext,
        dayNumber: Int,
        date: Date?,
        items: [ItineraryItem],
        games: [String: RichGame],
        assets: PDFAssetPrefetcher.PrefetchedAssets?,
        y: CGFloat
    ) -> CGFloat {
        var currentY = y

        // Day header: "Day 1  •  Sat, Jan 18"
        let dayHeaderAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 16, weight: .bold),
            .foregroundColor: textPrimary
        ]

        var dayHeaderText = "Day \(dayNumber)"
        if let date = date {
            let formatter = DateFormatter()
            formatter.dateFormat = "EEE, MMM d"  // "Sat, Jan 18"
            dayHeaderText += "  •  \(formatter.string(from: date))"
        }

        (dayHeaderText as NSString).draw(at: CGPoint(x: margin, y: currentY), withAttributes: dayHeaderAttributes)
        currentY += 28

        // Location subtitle (if we can determine it)
        let primaryCity = determinePrimaryCity(from: items, games: games)
        if let city = primaryCity {
            let locationAttributes: [NSAttributedString.Key: Any] = [
                .font: UIFont.systemFont(ofSize: 12),
                .foregroundColor: textMuted
            ]
            (city as NSString).draw(at: CGPoint(x: margin, y: currentY), withAttributes: locationAttributes)
            currentY += 22
        }

        currentY += 8  // Space before items

        // Draw items in sortOrder
        var hasContent = false
        for item in items {
            switch item.kind {
            case .game(let gameId, _):
                if let richGame = games[gameId] {
                    currentY = drawGameEntry(richGame: richGame, y: currentY)
                    currentY += itemSpacing
                    hasContent = true
                }
            case .travel(let travelInfo):
                currentY = drawTravelEntry(info: travelInfo, y: currentY)
                currentY += itemSpacing
                hasContent = true
            case .custom(let customInfo):
                currentY = drawCustomEntry(info: customInfo, y: currentY)
                currentY += itemSpacing
                hasContent = true
            }
        }

        // Rest day message if no content
        if !hasContent {
            let restAttributes: [NSAttributedString.Key: Any] = [
                .font: UIFont.italicSystemFont(ofSize: 13),
                .foregroundColor: textMuted
            ]
            ("Rest Day – Explore the city!" as NSString).draw(
                at: CGPoint(x: margin + 12, y: currentY),
                withAttributes: restAttributes
            )
            currentY += 28
        }

        return currentY
    }

    /// Determine primary city from items
    private func determinePrimaryCity(from items: [ItineraryItem], games: [String: RichGame]) -> String? {
        for item in items {
            switch item.kind {
            case .game(let gameId, let city):
                // Prefer stadium city if available
                if let richGame = games[gameId] {
                    return "\(richGame.stadium.city), \(richGame.stadium.state)"
                }
                return city
            case .travel(let info):
                return info.toCity
            case .custom:
                continue  // Skip custom items for city detection
            }
        }
        return nil
    }

    // MARK: - Magazine-Style Item Drawing

    /// Draw a simplified game entry: "Lakers vs. Celtics  •  7:00 PM"
    private func drawGameEntry(richGame: RichGame, y: CGFloat) -> CGFloat {
        let currentY = y

        // Sport color accent (left border)
        let accentBarRect = CGRect(x: margin, y: currentY, width: 3, height: 36)
        let sportColor = sportAccentColor(for: richGame.game.sport)
        sportColor.setFill()
        UIBezierPath(roundedRect: accentBarRect, cornerRadius: 1.5).fill()

        // Teams: "Lakers vs. Celtics"
        let teamsText = "\(richGame.awayTeam.name) vs. \(richGame.homeTeam.name)"
        let teamsAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 14, weight: .semibold),
            .foregroundColor: textPrimary
        ]
        (teamsText as NSString).draw(at: CGPoint(x: margin + 14, y: currentY), withAttributes: teamsAttributes)

        // Time: "7:00 PM EDT" (stadium local time with timezone indicator)
        let timeText = richGame.localGameTime
        let timeAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 13),
            .foregroundColor: textSecondary
        ]
        (timeText as NSString).draw(at: CGPoint(x: margin + 14, y: currentY + 20), withAttributes: timeAttributes)

        // Venue name (subtle, after time)
        let venueText = "  •  \(richGame.stadium.name)"
        let venueAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 13),
            .foregroundColor: textMuted
        ]
        let timeSize = (timeText as NSString).size(withAttributes: timeAttributes)
        (venueText as NSString).draw(
            at: CGPoint(x: margin + 14 + timeSize.width, y: currentY + 20),
            withAttributes: venueAttributes
        )

        return currentY + 40
    }

    /// Draw a travel entry: "→  3h 45m  •  220 mi to Phoenix"
    private func drawTravelEntry(info: TravelInfo, y: CGFloat) -> CGFloat {
        let currentY = y

        // Arrow indicator
        let arrowAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 12, weight: .medium),
            .foregroundColor: textMuted
        ]
        ("→" as NSString).draw(at: CGPoint(x: margin + 4, y: currentY + 2), withAttributes: arrowAttributes)

        // Build travel text: "3h 45m  •  220 mi to Phoenix"
        var travelParts: [String] = []
        if !info.formattedDuration.isEmpty {
            travelParts.append(info.formattedDuration)
        }
        if !info.formattedDistance.isEmpty {
            // Convert to miles format
            travelParts.append(info.formattedDistance)
        }
        travelParts.append("to \(info.toCity)")

        let travelText = travelParts.joined(separator: "  •  ")
        let travelAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 12),
            .foregroundColor: textSecondary
        ]
        (travelText as NSString).draw(at: CGPoint(x: margin + 24, y: currentY + 2), withAttributes: travelAttributes)

        return currentY + 28
    }

    /// Draw a custom item entry: "🍽️  Lunch at Joe's Diner  •  123 Main St"
    private func drawCustomEntry(info: CustomInfo, y: CGFloat) -> CGFloat {
        let currentY = y

        // Icon
        let iconAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 14)
        ]
        (info.icon as NSString).draw(at: CGPoint(x: margin + 2, y: currentY), withAttributes: iconAttributes)

        // Title
        let titleAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 13, weight: .medium),
            .foregroundColor: textPrimary
        ]
        (info.title as NSString).draw(at: CGPoint(x: margin + 24, y: currentY), withAttributes: titleAttributes)

        // Address (if present)
        if let address = info.address {
            let addressAttributes: [NSAttributedString.Key: Any] = [
                .font: UIFont.systemFont(ofSize: 11),
                .foregroundColor: textMuted
            ]
            (address as NSString).draw(at: CGPoint(x: margin + 24, y: currentY + 18), withAttributes: addressAttributes)
            return currentY + 36
        }

        return currentY + 22
    }

    /// Draw a lodging entry: "🏨  Marriott Downtown  •  123 Main St"
    private func drawLodgingEntry(name: String, address: String?, y: CGFloat) -> CGFloat {
        let currentY = y

        // Hotel icon
        let iconAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 14)
        ]
        ("🏨" as NSString).draw(at: CGPoint(x: margin + 2, y: currentY), withAttributes: iconAttributes)

        // Name
        let nameAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 13, weight: .medium),
            .foregroundColor: textPrimary
        ]
        (name as NSString).draw(at: CGPoint(x: margin + 24, y: currentY), withAttributes: nameAttributes)

        // Address
        if let address = address {
            let addressAttributes: [NSAttributedString.Key: Any] = [
                .font: UIFont.systemFont(ofSize: 11),
                .foregroundColor: textMuted
            ]
            (address as NSString).draw(at: CGPoint(x: margin + 24, y: currentY + 18), withAttributes: addressAttributes)
            return currentY + 36
        }

        return currentY + 22
    }

    private func estimateDayHeight(day: ItineraryDay, games: [String: RichGame]) -> CGFloat {
        var height: CGFloat = 50  // Day header + city

        // Games (simplified)
        height += CGFloat(day.gameIds.count) * (44 + itemSpacing)

        // Travel
        if day.hasTravelSegment {
            height += 36 + itemSpacing
        }

        // Rest day
        if day.isRestDay && day.gameIds.isEmpty {
            height += 28
        }

        return height
    }

    /// Draw a day using derived order (magazine-style)
    private func drawDay(
        context: UIGraphicsPDFRendererContext,
        day: ItineraryDay,
        games: [String: RichGame],
        assets: PDFAssetPrefetcher.PrefetchedAssets?,
        y: CGFloat
    ) -> CGFloat {
        var currentY = y

        // Day header: "Day 1  •  Sat, Jan 18"
        let dayHeaderAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 16, weight: .bold),
            .foregroundColor: textPrimary
        ]

        let formatter = DateFormatter()
        formatter.dateFormat = "EEE, MMM d"  // "Sat, Jan 18"
        let dayHeaderText = "Day \(day.dayNumber)  •  \(formatter.string(from: day.date))"
        (dayHeaderText as NSString).draw(at: CGPoint(x: margin, y: currentY), withAttributes: dayHeaderAttributes)
        currentY += 28

        // Location subtitle
        if let city = day.primaryCity {
            let locationAttributes: [NSAttributedString.Key: Any] = [
                .font: UIFont.systemFont(ofSize: 12),
                .foregroundColor: textMuted
            ]
            (city as NSString).draw(at: CGPoint(x: margin, y: currentY), withAttributes: locationAttributes)
            currentY += 22
        }

        currentY += 8  // Space before items

        // Games
        for gameId in day.gameIds {
            if let richGame = games[gameId] {
                currentY = drawGameEntry(richGame: richGame, y: currentY)
                currentY += itemSpacing
            }
        }

        // Travel segments
        for segment in day.travelSegments {
            currentY = drawTravelSegmentEntry(segment: segment, y: currentY)
            currentY += itemSpacing
        }

        // Rest day
        if day.isRestDay && day.gameIds.isEmpty {
            let restAttributes: [NSAttributedString.Key: Any] = [
                .font: UIFont.italicSystemFont(ofSize: 13),
                .foregroundColor: textMuted
            ]
            ("Rest Day – Explore the city!" as NSString).draw(
                at: CGPoint(x: margin + 12, y: currentY),
                withAttributes: restAttributes
            )
            currentY += 28
        }

        return currentY
    }

    /// Draw a travel segment entry (from TravelSegment, not TravelInfo)
    private func drawTravelSegmentEntry(segment: TravelSegment, y: CGFloat) -> CGFloat {
        let currentY = y

        // Arrow indicator
        let arrowAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 12, weight: .medium),
            .foregroundColor: textMuted
        ]
        ("→" as NSString).draw(at: CGPoint(x: margin + 4, y: currentY + 2), withAttributes: arrowAttributes)

        // Build travel text
        let travelText = "\(segment.formattedDuration)  •  \(segment.formattedDistance) to \(segment.toLocation.name)"
        let travelAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 12),
            .foregroundColor: textSecondary
        ]
        (travelText as NSString).draw(at: CGPoint(x: margin + 24, y: currentY + 2), withAttributes: travelAttributes)

        return currentY + 28
    }

}

// MARK: - UIColor Extension

extension UIColor {
    convenience init?(hex: String) {
        var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
        hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")

        guard hexSanitized.count == 6 else { return nil }

        var rgb: UInt64 = 0
        Scanner(string: hexSanitized).scanHexInt64(&rgb)

        let red = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
        let green = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
        let blue = CGFloat(rgb & 0x0000FF) / 255.0

        self.init(red: red, green: green, blue: blue, alpha: 1.0)
    }
}

// MARK: - Export Service

@MainActor
final class ExportService {
    private let pdfGenerator = PDFGenerator()
    private let assetPrefetcher = PDFAssetPrefetcher()

    /// Export trip to PDF with full prefetched assets
    /// - Parameters:
    ///   - trip: The trip to export
    ///   - games: Dictionary of game IDs to RichGame objects
    ///   - itineraryItems: Optional custom itinerary items for user-specified ordering
    ///   - progressCallback: Optional callback for prefetch progress
    func exportToPDF(
        trip: Trip,
        games: [String: RichGame],
        itineraryItems: [ItineraryItem]? = nil,
        progressCallback: ((PDFAssetPrefetcher.PrefetchProgress) async -> Void)? = nil
    ) async throws -> URL {
        // Prefetch all assets
        let assets = await assetPrefetcher.prefetchAssets(
            for: trip,
            games: games,
            progressCallback: progressCallback
        )

        // Generate PDF with assets and custom itinerary order
        let data = try await pdfGenerator.generatePDF(
            for: trip,
            games: games,
            assets: assets,
            itineraryItems: itineraryItems
        )

        // Save to temp file
        let fileName = "\(trip.name.replacingOccurrences(of: " ", with: "_"))_\(Date().timeIntervalSince1970).pdf"
        let url = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)

        try data.write(to: url)
        return url
    }

    /// Quick export without prefetching (basic PDF)
    func exportToPDFBasic(
        trip: Trip,
        games: [String: RichGame],
        itineraryItems: [ItineraryItem]? = nil
    ) async throws -> URL {
        let data = try await pdfGenerator.generatePDF(
            for: trip,
            games: games,
            assets: nil,
            itineraryItems: itineraryItems
        )

        let fileName = "\(trip.name.replacingOccurrences(of: " ", with: "_"))_\(Date().timeIntervalSince1970).pdf"
        let url = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)

        try data.write(to: url)
        return url
    }

    func shareTrip(_ trip: Trip) -> URL? {
        let baseURL = "sportstime://trip/"
        return URL(string: baseURL + trip.id.uuidString)
    }
}
