import SwiftUI

/// Renders one `RouteConnection` (1+ chained legs) as a single tappable card.
/// Each leg is independently tappable via the inner buttons — tapping the
/// outer card just expands/collapses the leg detail by default; we wire
/// per-leg taps via the `onLegTap` callback.
struct ConnectionRow: View {
    let connection: RouteConnection
    let appendix: RouteAppendix?
    let database: AirportDatabase
    let onLegTap: (RouteFlight) -> Void

    // MARK: - Annotation state (first-leg badges)
    @State private var loadEstimate: LoadFactorEstimate?
    @State private var onTimeStat: OnTimeStat?

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            // MARK: - Summary header
            summaryHeader

            Divider()

            // MARK: - Legs
            VStack(spacing: 10) {
                ForEach(Array(connection.flights.enumerated()), id: \.element.id) { index, leg in
                    if index > 0, let layover = layoverMinutes(at: index) {
                        layoverRow(minutes: layover, at: leg.departure.airportIata)
                    }
                    Button {
                        onLegTap(leg)
                    } label: {
                        LegSummary(leg: leg, appendix: appendix, database: database)
                    }
                    .buttonStyle(.plain)
                }
            }
        }
        .flightCard()
        .task(id: firstLegKey) {
            await loadAnnotations()
        }
    }

    // MARK: - Annotation fetch

    /// Stable identity for the first leg so SwiftUI re-runs the task only
    /// when the underlying flight key actually changes.
    private var firstLegKey: String {
        guard let first = connection.flights.first else { return "none" }
        return "\(first.carrierIata)\(first.flightNumber)-\(first.departure.airportIata)-\(first.arrival.airportIata)"
    }

    private func loadAnnotations() async {
        guard let first = connection.flights.first else { return }
        let carrier = first.carrierIata
        let flightNumber = first.flightNumber
        let origin = first.departure.airportIata
        let dest = first.arrival.airportIata
        let date = first.departure.dateTime

        // Sequential with cancellation checks between fetches. If the
        // user scrolls fast or the route list re-queries, the
        // `.task(id:)` re-fires and we abandon the prior load instead
        // of writing stale numbers into the row.
        do {
            let load = await LoadFactorService.shared.estimate(
                carrier: carrier,
                flightNumber: flightNumber,
                origin: origin,
                dest: dest,
                date: date,
                database: database,
                liveSeats: nil
            )
            try Task.checkCancellation()
            self.loadEstimate = load

            let ot = await OnTimePerformanceService.shared.stat(
                carrier: carrier,
                flightNumber: flightNumber,
                origin: origin,
                dest: dest
            )
            try Task.checkCancellation()
            self.onTimeStat = ot

            print("[ConnectionRow] \(carrier)\(flightNumber) \(origin)->\(dest) " +
                  "load=\(load.map { Int(round($0.predicted * 100)) }.map(String.init) ?? "nil")% " +
                  "ot=\(ot.map { Int(round($0.onTimePct * 100)) }.map(String.init) ?? "nil")%")
        } catch is CancellationError {
            print("[ConnectionRow] cancelled \(carrier)\(flightNumber) \(origin)->\(dest)")
        } catch {
            print("[ConnectionRow] error \(carrier)\(flightNumber): \(error)")
        }
    }

    // MARK: - Summary header

    private var summaryHeader: some View {
        HStack(alignment: .center) {
            VStack(alignment: .leading, spacing: 4) {
                Text(stopsLabel)
                    .font(.caption.weight(.semibold))
                    .foregroundStyle(FlightTheme.accent)
                    .padding(.horizontal, 8)
                    .padding(.vertical, 3)
                    .background(FlightTheme.accent.opacity(0.12), in: Capsule())

                HStack(spacing: 6) {
                    Text(carriersLabel)
                        .font(.caption)
                        .foregroundStyle(FlightTheme.textSecondary)
                        .lineLimit(1)

                    if let ot = onTimeStat {
                        onTimePill(ot)
                    }
                }
            }

            Spacer()

            VStack(alignment: .trailing, spacing: 4) {
                if let load = loadEstimate {
                    loadBadge(load)
                }
                Text(formatDuration(connection.durationMinutes))
                    .font(.subheadline.weight(.bold))
                    .foregroundStyle(FlightTheme.textPrimary)
                Text("total")
                    .font(.caption2)
                    .foregroundStyle(FlightTheme.textTertiary)
            }
        }
    }

    // MARK: - Badge views

    private func loadBadge(_ estimate: LoadFactorEstimate) -> some View {
        let pct = Int(round(estimate.predicted * 100))
        let color = loadColor(for: estimate.predicted)
        return Text("\(pct)%")
            .font(.caption.weight(.bold).monospacedDigit())
            .foregroundStyle(color)
            .padding(.horizontal, 7)
            .padding(.vertical, 2)
            .background(color.opacity(0.16), in: Capsule())
            .overlay(Capsule().strokeBorder(color.opacity(0.45), lineWidth: 0.5))
            .accessibilityLabel("Predicted load \(pct) percent")
    }

    private func onTimePill(_ stat: OnTimeStat) -> some View {
        let pct = Int(round(stat.onTimePct * 100))
        return Text("OT \(pct)%")
            .font(.caption2.weight(.semibold).monospacedDigit())
            .foregroundStyle(FlightTheme.textSecondary)
            .padding(.horizontal, 6)
            .padding(.vertical, 2)
            .background(FlightTheme.textSecondary.opacity(0.12), in: Capsule())
            .accessibilityLabel("On-time \(pct) percent")
    }

    /// Load-factor colour ramp. Lower load = better for nonrev = green.
    private func loadColor(for predicted: Double) -> Color {
        let pct = predicted * 100.0
        if pct < 70 { return .green }
        if pct <= 85 { return .yellow }
        return .red
    }

    private var stopsLabel: String {
        switch connection.stopCount {
        case 0:  return "Direct"
        case 1:  return "1 stop"
        default: return "\(connection.stopCount) stops"
        }
    }

    private var carriersLabel: String {
        let codes = connection.carrierIatas
        if codes.count == 1, let app = appendix?.airline(iata: codes[0])?.name {
            return app
        }
        return codes.joined(separator: " · ")
    }

    // MARK: - Layover

    private func layoverMinutes(at index: Int) -> Int? {
        guard index >= 1, index < connection.flights.count else { return nil }
        let arr = connection.flights[index - 1].arrival.dateTime
        let dep = connection.flights[index].departure.dateTime
        let mins = Int(dep.timeIntervalSince(arr) / 60)
        return mins > 0 ? mins : nil
    }

    private func layoverRow(minutes: Int, at iata: String) -> some View {
        HStack(spacing: 8) {
            Image(systemName: "arrow.down")
                .font(.caption2)
                .foregroundStyle(FlightTheme.textTertiary)
            Text("Layover at \(iata) · \(formatDuration(minutes))")
                .font(.caption)
                .foregroundStyle(FlightTheme.textSecondary)
            Spacer()
        }
        .padding(.leading, 8)
    }

    private func formatDuration(_ minutes: Int) -> String {
        let h = minutes / 60
        let m = minutes % 60
        if h > 0, m > 0 { return "\(h)h \(m)m" }
        if h > 0        { return "\(h)h" }
        return "\(m)m"
    }
}

// MARK: - Single leg summary

private struct LegSummary: View {
    let leg: RouteFlight
    let appendix: RouteAppendix?
    let database: AirportDatabase

    private static let timeFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "HH:mm"
        return f
    }()

    var body: some View {
        HStack(alignment: .top, spacing: 10) {
            // Airline + flight number (fixed-width left column)
            VStack(alignment: .leading, spacing: 2) {
                Text(leg.carrierIata)
                    .font(.caption.weight(.bold))
                    .foregroundStyle(FlightTheme.textPrimary)
                // verbatim: prevents SwiftUI from rendering Int as "3,189".
                Text(verbatim: "\(leg.flightNumber)")
                    .font(FlightTheme.flightNumber(11))
                    .foregroundStyle(FlightTheme.textSecondary)
            }
            .frame(width: 44, alignment: .leading)

            // Times + airports + names + aircraft
            VStack(alignment: .leading, spacing: 4) {
                // Row A — times and IATAs (compact)
                HStack(spacing: 8) {
                    timeAirport(leg.departure)
                    Image(systemName: "arrow.right")
                        .font(.caption2)
                        .foregroundStyle(FlightTheme.textTertiary)
                    timeAirport(leg.arrival)
                    Spacer(minLength: 0)
                }

                // Row B — full airport names, single line, middle-truncated
                Text("\(airportName(for: leg.departure.airportIata)) → \(airportName(for: leg.arrival.airportIata))")
                    .font(.caption2)
                    .foregroundStyle(FlightTheme.textSecondary)
                    .lineLimit(1)
                    .truncationMode(.middle)

                // Row C — aircraft (if known), single line, tail-truncated
                if let aircraft = aircraftLabel {
                    Text(aircraft)
                        .font(.caption2)
                        .foregroundStyle(FlightTheme.textTertiary)
                        .lineLimit(1)
                        .truncationMode(.tail)
                }
            }

            Spacer(minLength: 4)

            Image(systemName: "chevron.right")
                .font(.caption2)
                .foregroundStyle(FlightTheme.textTertiary)
                .padding(.top, 4)
        }
        .padding(.horizontal, 12)
        .padding(.vertical, 10)
        .background(FlightTheme.elevatedBackground)
        .clipShape(RoundedRectangle(cornerRadius: 10))
        .contentShape(Rectangle())
    }

    private func timeAirport(_ endpoint: RouteEndpoint) -> some View {
        VStack(alignment: .leading, spacing: 1) {
            Text(Self.timeFormatter.string(from: endpoint.dateTime))
                .font(.subheadline.weight(.semibold).monospaced())
                .foregroundStyle(FlightTheme.textPrimary)
            Text(endpoint.airportIata)
                .font(.caption2.weight(.semibold))
                .foregroundStyle(FlightTheme.textSecondary)
        }
    }

    private var aircraftLabel: String? {
        guard let iata = leg.equipmentIata else { return nil }
        return appendix?.equipment(iata: iata)?.name ?? iata
    }

    /// Bundled DB first (clean city names), then route-explorer appendix.
    private func airportName(for iata: String) -> String {
        if let m = database.airport(byIATA: iata) { return m.name }
        if let n = appendix?.airport(iata: iata)?.cityName, !n.isEmpty { return n }
        if let n = appendix?.airport(iata: iata)?.name, !n.isEmpty { return n }
        return iata
    }
}
