import SwiftUI
import SwiftData

/// History tab — redesigned as a "passport" experience.
///
/// Stacked hero cards at the top (current-year passport, all-time
/// passport, most-flown airframe), a horizontal year tab strip that
/// scopes everything, and a flight feed below. Sort + filter + search
/// + add affordances all live in the toolbar.
struct HistoryView: View {
    let database: AirportDatabase
    let routeExplorer: RouteExplorerClient
    let openSky: OpenSkyClient

    @Environment(\.modelContext) private var modelContext
    @Environment(\.colorScheme) private var scheme

    @Query(sort: \LoggedFlight.flightDate, order: .reverse)
    private var flights: [LoggedFlight]

    @State private var filters: HistoryFilters = .init()
    @State private var sort: HistorySort = .newestFirst
    @State private var selectedYear: Int? = nil   // nil = ALL
    @State private var standbyOnly: Bool = false

    /// Cached output of the standby stats pipeline. Bug F5 — we used to
    /// recompute this from SwiftData on every body invalidation, which
    /// got expensive once history grew. The cache is refreshed in a
    /// `.task` keyed on `flights.count` so it only re-fires when the
    /// flight set actually changes.
    @State private var standbyRate: StandbyRate = .empty

    /// Cached scoped + sorted flight list. Bug Q8 — we used to recompute
    /// this on every body call. The cache is refreshed in a `.task`
    /// keyed on `pipelineKey` so it only re-runs when an input that
    /// affects the pipeline actually changes.
    @State private var scopedFlights: [LoggedFlight] = []

    @State private var showingAdd = false
    @State private var showingPassport = false
    @State private var showingMap = false
    @State private var showingAircraftStats = false
    @State private var showingCalendarImport = false
    @State private var showingCSVImport = false
    @State private var showingYearInReview = false
    @State private var showingFilterSheet = false

    var body: some View {
        let store = FlightHistoryStore(context: modelContext, airportDatabase: database)
        let scoped = scopedFlights
        let stats = StatsEngine(store: store, database: database, flights: scoped)

        ScrollView {
            LazyVStack(spacing: 0, pinnedViews: []) {
                titleHeader

                standbyStatsCard
                    .padding(.horizontal, 16)
                    .padding(.top, 12)

                standbyFilterToggle
                    .padding(.horizontal, 16)
                    .padding(.top, 8)

                YearTabStrip(years: yearsList, selection: $selectedYear)
                    .padding(.vertical, 12)

                if filters.isEmpty {
                    heroDeck(store: store, stats: stats)
                        .padding(.horizontal, 16)
                        .padding(.bottom, 8)
                }

                if !filters.isEmpty {
                    activeChips
                        .padding(.horizontal, 16)
                        .padding(.bottom, 8)
                }

                if scoped.isEmpty {
                    emptyState
                } else {
                    flightFeed(scoped, store: store)
                }

                Spacer(minLength: 80)
            }
        }
        .background(HistoryStyle.background(scheme).ignoresSafeArea())
        .navigationTitle("")
        .navigationBarTitleDisplayMode(.inline)
        // History data is purely local SwiftData + CloudKit. @Query
        // auto-emits on every store mutation, and CloudKit sync runs
        // automatically when the app is foregrounded. A user-driven
        // `.refreshable` wouldn't do anything that isn't already happening
        // — we don't add the affordance to avoid the misleading "tap to
        // force a refresh" expectation.
        .searchable(text: $filters.query, prompt: "Flight #, airport, route")
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                Menu {
                    ForEach(HistorySort.allCases) { option in
                        Button {
                            sort = option
                        } label: {
                            if sort == option {
                                Label(option.rawValue, systemImage: "checkmark")
                            } else {
                                Label(option.rawValue, systemImage: option.systemImage)
                            }
                        }
                    }
                } label: {
                    Image(systemName: "arrow.up.arrow.down")
                }
            }
            ToolbarItem(placement: .topBarTrailing) {
                Button {
                    showingFilterSheet = true
                } label: {
                    Image(systemName: filters.activeCount > 0 ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle")
                }
            }
            ToolbarItem(placement: .topBarTrailing) {
                Menu {
                    Section("Add") {
                        Button { showingAdd = true } label: { Label("Add manually", systemImage: "plus") }
                        Button { showingCalendarImport = true } label: { Label("Scan Calendar", systemImage: "calendar") }
                        Button { showingCSVImport = true } label: { Label("Import CSV…", systemImage: "doc.text") }
                    }
                    Section("Explore") {
                        Button { showingPassport = true } label: { Label("Passport", systemImage: "book.closed") }
                        Button {
                            // Carry the year-strip scope into the map's
                            // filter binding so the map opens showing
                            // the same year the user was browsing.
                            if let y = selectedYear { filters.years = [y] }
                            showingMap = true
                        } label: { Label("Route map", systemImage: "map.fill") }
                        Button { showingAircraftStats = true } label: { Label("Aircraft stats", systemImage: "airplane.circle") }
                        Button { showingYearInReview = true } label: { Label("Year in Review", systemImage: "sparkles") }
                    }
                } label: {
                    Image(systemName: "plus.circle.fill")
                        .foregroundStyle(HistoryStyle.runwayOrange)
                }
            }
        }
        .sheet(isPresented: $showingAdd) {
            AddFlightView(routeExplorer: routeExplorer, database: database, store: store, prefill: nil)
        }
        .sheet(isPresented: $showingPassport) {
            NavigationStack {
                PassportView(stats: stats, allFlights: flights, database: database, store: store, selectedYear: $selectedYear)
            }
        }
        .sheet(isPresented: $showingMap) {
            NavigationStack {
                HistoryRouteMapView(
                    allFlights: flights,
                    database: database,
                    openSky: openSky,
                    store: store,
                    filters: $filters
                )
            }
        }
        .sheet(isPresented: $showingAircraftStats) {
            NavigationStack {
                AircraftStatsView(allFlights: flights, store: store, routeExplorer: routeExplorer)
            }
        }
        .sheet(isPresented: $showingCalendarImport) {
            CalendarImportView(routeExplorer: routeExplorer, database: database, store: store)
        }
        .sheet(isPresented: $showingCSVImport) {
            ImportCSVView(store: store, routeExplorer: routeExplorer)
        }
        .sheet(isPresented: $showingYearInReview) {
            YearInReviewView(stats: stats, year: selectedYear ?? Calendar.current.component(.year, from: Date()))
        }
        .sheet(isPresented: $showingFilterSheet) {
            HistoryFilterSheet(allFlights: flights, filters: $filters)
                .presentationDetents([.medium, .large])
        }
        // Bug F5 / F3 — refresh the standby stats cache when the flight
        // set OR any flight's standbyOutcome changes. Keying on
        // `flightContentSignature` covers in-place edits made through
        // HistoryDetailView (which writes to the @Model directly and
        // doesn't bump `flights.count`).
        .task(id: flightContentSignature) {
            standbyRate = StandbyStatsService().personalRate(
                carrier: nil,
                origin: nil,
                dest: nil,
                context: modelContext
            )
        }
        // Bug Q8 — refresh the scoped + sorted flight cache when any
        // pipeline input changes. The key is a string so SwiftUI's
        // Equatable comparison stays cheap.
        .task(id: pipelineKey) {
            let store = FlightHistoryStore(context: modelContext, airportDatabase: database)
            scopedFlights = computeScopedFlights(store: store)
        }
    }

    // MARK: - Pipeline

    private var yearsList: [Int] {
        let cal = Calendar.current
        let ys = Set(flights.map { cal.component(.year, from: $0.flightDate) })
        return ys.sorted(by: >)
    }

    /// Composite key for the scoped-flight pipeline. `.task(id:)` only
    /// re-fires when this string changes, so we bundle every input that
    /// affects the output: year scope, active-filter count, the
    /// standby-only toggle, the chosen sort, the SwiftData row count,
    /// AND a content signature over per-flight fields whose edits should
    /// re-invalidate the pipeline (standby outcome + the flight's id).
    /// Without the content signature, editing a flight's standbyOutcome
    /// in the detail view wouldn't change `flights.count`, the task
    /// wouldn't re-fire, and the user would see a stale list.
    private var pipelineKey: String {
        let year = selectedYear.map(String.init) ?? "ALL"
        return [
            year,
            String(filters.activeCount),
            filters.query,
            standbyOnly ? "S" : "_",
            sort.rawValue,
            String(flights.count),
            flightContentSignature
        ].joined(separator: "|")
    }

    /// Lightweight fingerprint over the per-flight fields the History
    /// pipeline cares about. Re-computed on every `body` invalidation,
    /// but the work is just an XOR/sum over ~1 hashable per flight so
    /// even a 5k-flight log finishes well under a millisecond. Includes
    /// any field that, when edited via HistoryDetailView, should
    /// trigger a UI refresh.
    private var flightContentSignature: String {
        var hasher = Hasher()
        for flight in flights {
            hasher.combine(flight.id)
            hasher.combine(flight.standbyOutcome)
        }
        return String(hasher.finalize())
    }

    private func computeScopedFlights(store: FlightHistoryStore) -> [LoggedFlight] {
        var scoped = flights
        if let y = selectedYear {
            let cal = Calendar.current
            scoped = scoped.filter { cal.component(.year, from: $0.flightDate) == y }
        }
        scoped = scoped.filter { filters.matches($0) }
        if standbyOnly {
            scoped = scoped.filter { $0.wasStandby }
        }
        let cmp = sort.comparator { store.distanceMiles(for: $0) ?? 0 }
        return scoped.sorted(by: cmp)
    }

    // MARK: - Title

    private var titleHeader: some View {
        HStack(alignment: .firstTextBaseline) {
            VStack(alignment: .leading, spacing: 2) {
                Text("PASSPORT")
                    .font(.system(size: 34, weight: .black))
                    .tracking(-0.5)
                    .foregroundStyle(HistoryStyle.ink(scheme))
                if !flights.isEmpty {
                    Text("\(flights.count) flights · \(years(of: flights)) years")
                        .font(.system(size: 13, weight: .medium))
                        .foregroundStyle(HistoryStyle.inkSecondary(scheme))
                }
            }
            Spacer()
            Image(systemName: "airplane")
                .font(.system(size: 22, weight: .heavy))
                .foregroundStyle(HistoryStyle.runwayOrange)
                .rotationEffect(.degrees(-45))
        }
        .padding(.horizontal, 16)
        .padding(.top, 8)
    }

    private func years(of list: [LoggedFlight]) -> Int {
        let yrs = Set(list.map { Calendar.current.component(.year, from: $0.flightDate) })
        return yrs.count
    }

    // MARK: - Standby stats card

    /// Compact "Standby stats" summary using StandbyStatsService over the
    /// user's full LoggedFlight history (no carrier/route narrowing). Hidden
    /// when the user has no recorded standby attempts.
    ///
    /// Bug F5 — reads from the cached `standbyRate` @State; population
    /// happens in the `.task` modifier on `body`, keyed on `flights.count`.
    @ViewBuilder
    private var standbyStatsCard: some View {
        let stats = standbyRate

        if stats.attempts > 0 {
            HStack(alignment: .center, spacing: 14) {
                VStack(alignment: .leading, spacing: 4) {
                    HStack(spacing: 6) {
                        Image(systemName: "figure.stand.line.dotted.figure.stand")
                            .font(.system(size: 11, weight: .heavy))
                            .foregroundStyle(FlightTheme.accent)
                        Text("STANDBY STATS")
                            .font(.system(size: 10, weight: .heavy))
                            .tracking(1.4)
                            .foregroundStyle(FlightTheme.textSecondary)
                    }
                    HStack(alignment: .firstTextBaseline, spacing: 4) {
                        Text("\(stats.attempts)")
                            .font(.system(size: 22, weight: .heavy).monospacedDigit())
                            .foregroundStyle(FlightTheme.textPrimary)
                        Text(stats.attempts == 1 ? "attempt" : "attempts")
                            .font(.system(size: 11, weight: .semibold))
                            .foregroundStyle(FlightTheme.textSecondary)
                    }
                }

                Spacer(minLength: 0)

                standbyStatPill(
                    value: "\(stats.made)/\(stats.attempts)",
                    label: "MADE",
                    tint: FlightTheme.onTime
                )

                standbyStatPill(
                    value: "\(stats.bumped)",
                    label: "BUMPED",
                    tint: FlightTheme.cancelled
                )

                standbyStatPill(
                    value: percentString(stats.rate),
                    label: "RATE",
                    tint: FlightTheme.accent
                )
            }
            .padding(.horizontal, 14)
            .padding(.vertical, 12)
            .background(FlightTheme.cardBackground, in: RoundedRectangle(cornerRadius: FlightTheme.cardCornerRadius))
            .overlay(
                RoundedRectangle(cornerRadius: FlightTheme.cardCornerRadius)
                    .stroke(FlightTheme.accent.opacity(0.15), lineWidth: 0.5)
            )
        }
    }

    private func standbyStatPill(value: String, label: String, tint: Color) -> some View {
        VStack(spacing: 2) {
            Text(value)
                .font(.system(size: 14, weight: .heavy).monospacedDigit())
                .foregroundStyle(tint)
                .lineLimit(1)
                .minimumScaleFactor(0.7)
            Text(label)
                .font(.system(size: 9, weight: .bold))
                .tracking(0.8)
                .foregroundStyle(FlightTheme.textTertiary)
        }
        .frame(minWidth: 48)
    }

    private func percentString(_ rate: Double) -> String {
        let pct = Int((rate * 100).rounded())
        return "\(pct)%"
    }

    // MARK: - Standby-only filter toggle

    /// Inline toggle that constrains the feed to flights where wasStandby
    /// is true. Sits between the hero stats and the year strip so it's
    /// always reachable without opening the filter sheet.
    private var standbyFilterToggle: some View {
        HStack(spacing: 8) {
            Image(systemName: standbyOnly ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle")
                .font(.system(size: 13, weight: .semibold))
                .foregroundStyle(standbyOnly ? FlightTheme.accent : FlightTheme.textTertiary)
            Toggle(isOn: $standbyOnly) {
                Text("Standby only")
                    .font(.system(size: 13, weight: .semibold))
                    .foregroundStyle(FlightTheme.textPrimary)
            }
            .tint(FlightTheme.accent)
        }
        .padding(.horizontal, 12)
        .padding(.vertical, 6)
        .background(FlightTheme.cardBackground, in: Capsule())
        .overlay(
            Capsule()
                .stroke(standbyOnly ? FlightTheme.accent.opacity(0.4) : Color.clear, lineWidth: 1)
        )
        .frame(maxWidth: .infinity, alignment: .leading)
    }

    // MARK: - Hero deck

    @ViewBuilder
    private func heroDeck(store: FlightHistoryStore, stats: StatsEngine) -> some View {
        VStack(spacing: 12) {
            // 1) Scoped passport — either current year or all-time
            let year = selectedYear ?? Calendar.current.component(.year, from: Date())
            let yearFlights = stats.flights(for: year)
            let yearStats = StatsEngine(store: store, database: database, flights: yearFlights)

            Button { showingPassport = true } label: {
                if selectedYear == nil {
                    HeroStatCard(
                        label: "ALL TIME PASSPORT",
                        value: numberString(stats.totalFlights),
                        subtitle: "\(stats.shortDistance) miles · \(stats.shortDuration)h in air",
                        variant: .orange
                    ) {
                        HStack(spacing: 14) {
                            kvp(value: "\(stats.uniqueAirports)", label: "airports")
                            kvp(value: "\(stats.uniqueAirlines)", label: "airlines")
                            kvp(value: "\(stats.uniqueCountries)", label: "countries")
                            Spacer()
                            Image(systemName: "chevron.right")
                                .font(.system(size: 13, weight: .bold))
                                .foregroundStyle(.white.opacity(0.6))
                        }
                    }
                } else {
                    HeroStatCard(
                        label: "\(year) PASSPORT",
                        value: numberString(yearStats.totalFlights),
                        subtitle: "\(yearStats.shortDistance) miles · \(yearStats.shortDuration)h aloft",
                        variant: .orange
                    ) {
                        HStack(spacing: 14) {
                            kvp(value: "\(yearStats.uniqueAirports)", label: "airports")
                            kvp(value: "\(yearStats.uniqueAirlines)", label: "airlines")
                            Spacer()
                            Image(systemName: "chevron.right")
                                .font(.system(size: 13, weight: .bold))
                                .foregroundStyle(.white.opacity(0.6))
                        }
                    }
                }
            }
            .buttonStyle(.plain)

            // 2) Most-flown aircraft, if we know it
            mostFlownCard(stats: stats)

            // 3) Quick links row
            quickLinks
        }
    }

    @ViewBuilder
    private func mostFlownCard(stats: StatsEngine) -> some View {
        let typeCounts = Dictionary(grouping: stats.flights.compactMap { $0.aircraftType }) { $0 }
            .mapValues(\.count)
        if let top = typeCounts.max(by: { $0.value < $1.value }) {
            let typeName = AircraftDatabase.shared.displayName(forTypeCode: top.key)
            Button { showingAircraftStats = true } label: {
                HeroStatCard(
                    label: "MOST FLOWN AIRCRAFT",
                    value: typeName == top.key ? top.key : typeName,
                    subtitle: "\(top.value) flights",
                    variant: .navy
                ) {
                    HStack {
                        Spacer()
                        Image(systemName: "chevron.right")
                            .font(.system(size: 13, weight: .bold))
                            .foregroundStyle(.white.opacity(0.6))
                    }
                }
            }
            .buttonStyle(.plain)
        }
    }

    @ViewBuilder
    private var quickLinks: some View {
        HStack(spacing: 10) {
            quickLink(title: "Map", icon: "map.fill") {
                if let y = selectedYear { filters.years = [y] }
                showingMap = true
            }
            quickLink(title: "Aircraft", icon: "airplane.circle.fill") { showingAircraftStats = true }
            quickLink(title: "Year", icon: "sparkles") { showingYearInReview = true }
        }
    }

    private func quickLink(title: String, icon: String, action: @escaping () -> Void) -> some View {
        Button(action: action) {
            VStack(spacing: 6) {
                Image(systemName: icon)
                    .font(.system(size: 18, weight: .semibold))
                Text(title)
                    .font(.system(size: 12, weight: .semibold))
                    .tracking(0.5)
            }
            .foregroundStyle(HistoryStyle.ink(scheme))
            .frame(maxWidth: .infinity, minHeight: 64)
            .background(HistoryStyle.card(scheme), in: RoundedRectangle(cornerRadius: 16))
        }
    }

    private func kvp(value: String, label: String) -> some View {
        VStack(alignment: .leading, spacing: 0) {
            Text(value)
                .font(.system(size: 18, weight: .heavy).monospacedDigit())
                .foregroundStyle(.white)
            Text(label.uppercased())
                .font(.system(size: 9, weight: .bold))
                .tracking(0.8)
                .foregroundStyle(.white.opacity(0.7))
        }
    }

    // MARK: - Active filter chips

    private var activeChips: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 8) {
                if !filters.query.isEmpty {
                    chip("\(filters.query)", systemImage: "magnifyingglass") { filters.query = "" }
                }
                ForEach(Array(filters.years).sorted(by: >), id: \.self) { y in
                    chip("\(y)", systemImage: "calendar") { filters.years.remove(y) }
                }
                ForEach(Array(filters.airlines).sorted(), id: \.self) { a in
                    chip(AircraftRegistry.shared.lookup(icao: a)?.name ?? a, systemImage: "building.2") {
                        filters.airlines.remove(a)
                    }
                }
                ForEach(Array(filters.airports).sorted(), id: \.self) { a in
                    chip(a, systemImage: "airplane") { filters.airports.remove(a) }
                }
                ForEach(Array(filters.aircraftTypes).sorted(), id: \.self) { t in
                    chip(t, systemImage: "airplane.departure") { filters.aircraftTypes.remove(t) }
                }
                Button { filters = HistoryFilters() } label: {
                    Text("Clear")
                        .font(.caption.weight(.semibold))
                        .foregroundStyle(HistoryStyle.runwayOrange)
                        .padding(.horizontal, 10)
                        .padding(.vertical, 6)
                }
            }
        }
    }

    private func chip(_ label: String, systemImage: String, onRemove: @escaping () -> Void) -> some View {
        HStack(spacing: 4) {
            Image(systemName: systemImage).font(.caption2)
            Text(label).font(.caption.weight(.semibold))
            Image(systemName: "xmark").font(.caption2)
        }
        .foregroundStyle(.white)
        .padding(.horizontal, 10)
        .padding(.vertical, 5)
        .background(HistoryStyle.runwayOrange, in: Capsule())
        .onTapGesture(perform: onRemove)
    }

    // MARK: - Flight feed

    @ViewBuilder
    private func flightFeed(_ scoped: [LoggedFlight], store: FlightHistoryStore) -> some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack {
                HistorySectionLabel(selectedYear == nil ? "Recent flights" : "Flights in \(selectedYear!)")
                Spacer()
                Text("\(scoped.count)")
                    .font(.system(size: 12, weight: .bold).monospacedDigit())
                    .foregroundStyle(HistoryStyle.inkTertiary(scheme))
            }
            .padding(.horizontal, 16)
            .padding(.top, 8)

            ForEach(groupedFeed(scoped), id: \.key) { group in
                if groupedFeed(scoped).count > 1 {
                    Text(group.key)
                        .font(.system(size: 12, weight: .bold).monospacedDigit())
                        .tracking(0.6)
                        .foregroundStyle(HistoryStyle.inkSecondary(scheme))
                        .padding(.horizontal, 16)
                        .padding(.top, 8)
                }
                ForEach(group.flights) { f in
                    NavigationLink {
                        HistoryDetailView(flight: f, store: store, database: database, openSky: openSky, routeExplorer: routeExplorer)
                    } label: {
                        PassportFlightRow(flight: f, database: database)
                            .padding(.horizontal, 16)
                    }
                    .buttonStyle(.plain)
                }
            }
        }
    }

    private struct FeedGroup {
        let key: String
        let flights: [LoggedFlight]
    }

    private func groupedFeed(_ list: [LoggedFlight]) -> [FeedGroup] {
        let cal = Calendar.current
        switch sort {
        case .newestFirst, .oldestFirst:
            // When scoped to a single year, sub-group by month for
            // visual rhythm; when ALL is selected, group by year.
            if selectedYear != nil {
                let grouped = Dictionary(grouping: list) { f -> String in
                    let comps = cal.dateComponents([.year, .month], from: f.flightDate)
                    let m = DateFormatter()
                    m.dateFormat = "MMMM"
                    return m.string(from: cal.date(from: comps) ?? f.flightDate).uppercased()
                }
                return grouped.map { FeedGroup(key: $0.key, flights: $0.value.sorted { sort == .newestFirst ? $0.flightDate > $1.flightDate : $0.flightDate < $1.flightDate }) }
                              .sorted { firstFlightDate($0.flights) > firstFlightDate($1.flights) }
            } else {
                let grouped = Dictionary(grouping: list) { String(cal.component(.year, from: $0.flightDate)) }
                let order: (String, String) -> Bool = sort == .newestFirst ? (>) : (<)
                return grouped.map { FeedGroup(key: $0.key, flights: $0.value) }
                              .sorted { order($0.key, $1.key) }
            }
        case .longestFirst, .shortestFirst, .airline, .flightNumber:
            return [FeedGroup(key: "", flights: list)]
        }
    }

    private func firstFlightDate(_ list: [LoggedFlight]) -> Date {
        list.first?.flightDate ?? .distantPast
    }

    // MARK: - Empty state

    private var emptyState: some View {
        VStack(spacing: 12) {
            Image(systemName: "airplane.circle")
                .font(.system(size: 48))
                .foregroundStyle(HistoryStyle.inkTertiary(scheme))
            Text(flights.isEmpty ? "No flights logged yet" : "No matches in \(selectedYear.map(String.init) ?? "this filter")")
                .font(.system(size: 15, weight: .semibold))
                .foregroundStyle(HistoryStyle.inkSecondary(scheme))
            if flights.isEmpty {
                Text("Tap + to add a flight, scan your calendar, or import a CSV.")
                    .font(.caption)
                    .foregroundStyle(HistoryStyle.inkTertiary(scheme))
                    .multilineTextAlignment(.center)
                    .padding(.horizontal, 32)
            } else {
                Button("Clear filter") {
                    selectedYear = nil
                    filters = HistoryFilters()
                    standbyOnly = false
                }
                .font(.subheadline.weight(.semibold))
                .foregroundStyle(HistoryStyle.runwayOrange)
            }
        }
        .frame(maxWidth: .infinity)
        .padding(.vertical, 60)
    }

    private func numberString(_ n: Int) -> String {
        let f = NumberFormatter()
        f.numberStyle = .decimal
        return f.string(from: NSNumber(value: n)) ?? "\(n)"
    }
}

// MARK: - Passport-styled flight row
//
// "The Classic" boarding pass — orange stub on the left, dashed
// perforation with semicircular cutouts at top and bottom, body with
// IATA route + date + meta data line. Designed in HTML mockups
// (design/boarding-pass-variants.html, variant 01) then ported here.

struct PassportFlightRow: View {
    let flight: LoggedFlight
    let database: AirportDatabase
    @Environment(\.colorScheme) private var scheme

    private let cornerRadius: CGFloat = 14
    private let stubWidth: CGFloat = 88
    private let punchRadius: CGFloat = 7
    private let rowHeight: CGFloat = 108

    var body: some View {
        HStack(spacing: 0) {
            stub
                .frame(width: stubWidth)
            body_
        }
        .frame(maxWidth: .infinity, minHeight: rowHeight)
        .clipShape(BoardingPassShape(
            cornerRadius: cornerRadius,
            perforationX: stubWidth,
            punchRadius: punchRadius
        ))
        .overlay(perforationLine)
    }

    // MARK: - Stub

    private var stub: some View {
        VStack(alignment: .leading, spacing: 0) {
            Text(flight.carrierIATA ?? flight.carrierICAO ?? "—")
                .font(.system(size: 9, weight: .heavy).monospaced())
                .tracking(2.2)
                .foregroundStyle(.white.opacity(0.85))
            Spacer(minLength: 4)
            Text(paddedFlightNumber)
                .font(.system(size: 28, weight: .heavy).monospaced())
                .foregroundStyle(.white)
                .lineLimit(1)
                .minimumScaleFactor(0.7)
                .kerning(-0.6)
            Spacer(minLength: 6)
            BarcodeStripe()
                .frame(height: 12)
                .opacity(0.95)
        }
        .padding(.horizontal, 12)
        .padding(.vertical, 14)
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
        .background(
            LinearGradient(
                colors: [HistoryStyle.runwayOrange, HistoryStyle.runwayOrangeDeep],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
        )
    }

    /// "0007" — flight number zero-padded to 4 digits when it parses
    /// as an int, else just the raw string.
    private var paddedFlightNumber: String {
        guard let num = flight.flightNumber, let i = Int(num) else {
            return flight.flightNumber ?? "—"
        }
        return String(format: "%04d", i)
    }

    // MARK: - Body

    private var body_: some View {
        VStack(alignment: .leading, spacing: 6) {
            HStack(alignment: .firstTextBaseline, spacing: 8) {
                Text(flight.departureIATA.isEmpty ? "—" : flight.departureIATA)
                    .font(.system(size: 24, weight: .heavy).monospaced())
                    .kerning(-0.5)
                    .foregroundStyle(HistoryStyle.ink(scheme))
                Text("▶")
                    .font(.system(size: 13, weight: .black))
                    .foregroundStyle(HistoryStyle.runwayOrange)
                Text(flight.arrivalIATA.isEmpty ? "—" : flight.arrivalIATA)
                    .font(.system(size: 24, weight: .heavy).monospaced())
                    .kerning(-0.5)
                    .foregroundStyle(HistoryStyle.ink(scheme))
            }
            Text(stubDate)
                .font(.system(size: 10, weight: .heavy).monospaced())
                .tracking(1.8)
                .foregroundStyle(HistoryStyle.inkTertiary(scheme))
            Spacer(minLength: 6)
            metaRow
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 14)
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
        .background(HistoryStyle.card(scheme))
    }

    private var stubDate: String {
        let f = DateFormatter()
        f.dateFormat = "dd MMM yy"
        return f.string(from: flight.flightDate).uppercased()
    }

    /// Bottom-of-card metadata line: `EQP B737 · TAIL N7747C · MI 239`
    private var metaRow: some View {
        HStack(spacing: 14) {
            metaItem(label: "EQP", value: flight.aircraftType)
            metaItem(label: "TAIL", value: flight.registration)
            metaItem(label: "MI", value: distanceValue)
            Spacer(minLength: 0)
        }
    }

    private var distanceValue: String? {
        // We don't have the store passed in here, so we recompute the
        // distance from the airport database directly.
        guard let dep = database.airport(byIATA: flight.departureIATA),
              let arr = database.airport(byIATA: flight.arrivalIATA)
        else { return nil }
        let dLat = (arr.coordinate.latitude - dep.coordinate.latitude) * .pi / 180
        let dLon = (arr.coordinate.longitude - dep.coordinate.longitude) * .pi / 180
        let lat1 = dep.coordinate.latitude * .pi / 180
        let lat2 = arr.coordinate.latitude * .pi / 180
        let a = sin(dLat / 2) * sin(dLat / 2)
            + cos(lat1) * cos(lat2) * sin(dLon / 2) * sin(dLon / 2)
        let c = 2 * atan2(sqrt(a), sqrt(1 - a))
        let km = 6371.0 * c
        let mi = km / 1.609344
        return mi >= 1 ? "\(Int(mi.rounded()))" : nil
    }

    private func metaItem(label: String, value: String?) -> some View {
        HStack(spacing: 4) {
            Text(label)
                .font(.system(size: 9, weight: .bold).monospaced())
                .tracking(1.0)
                .foregroundStyle(HistoryStyle.inkTertiary(scheme))
            Text(value ?? "—")
                .font(.system(size: 10, weight: .heavy).monospaced())
                .foregroundStyle(HistoryStyle.ink(scheme))
        }
    }

    // MARK: - Perforation

    /// Vertical dashed line drawn between stub and body, with a small
    /// inset top and bottom so it stops short of the punch cutouts.
    private var perforationLine: some View {
        GeometryReader { geo in
            Path { path in
                path.move(to: CGPoint(x: stubWidth, y: punchRadius + 2))
                path.addLine(to: CGPoint(x: stubWidth, y: geo.size.height - punchRadius - 2))
            }
            .stroke(
                HistoryStyle.inkTertiary(scheme),
                style: StrokeStyle(lineWidth: 1, dash: [3, 3])
            )
        }
        .allowsHitTesting(false)
    }
}

// MARK: - Boarding pass shape

/// Rounded rectangle with two semicircular cutouts (top + bottom) at
/// the perforation column — the visual hallmark of a boarding pass.
/// Drawn clockwise starting from the top-left corner.
struct BoardingPassShape: Shape {
    let cornerRadius: CGFloat
    let perforationX: CGFloat
    let punchRadius: CGFloat

    func path(in rect: CGRect) -> Path {
        var p = Path()
        let r = cornerRadius
        let pr = punchRadius
        let pX = perforationX
        let w = rect.width
        let h = rect.height

        // Start: top-left corner, after rounded corner
        p.move(to: CGPoint(x: r, y: 0))

        // Top edge to the perforation cutout
        p.addLine(to: CGPoint(x: pX - pr, y: 0))
        // Top semicircle cutout — sweeps DOWN into the row
        p.addArc(
            center: CGPoint(x: pX, y: 0),
            radius: pr,
            startAngle: .degrees(180),
            endAngle: .degrees(0),
            clockwise: false
        )
        // Continue along top edge
        p.addLine(to: CGPoint(x: w - r, y: 0))

        // Top-right corner
        p.addArc(
            center: CGPoint(x: w - r, y: r),
            radius: r,
            startAngle: .degrees(-90),
            endAngle: .degrees(0),
            clockwise: false
        )
        // Right edge
        p.addLine(to: CGPoint(x: w, y: h - r))
        // Bottom-right corner
        p.addArc(
            center: CGPoint(x: w - r, y: h - r),
            radius: r,
            startAngle: .degrees(0),
            endAngle: .degrees(90),
            clockwise: false
        )
        // Bottom edge to the perforation cutout
        p.addLine(to: CGPoint(x: pX + pr, y: h))
        // Bottom semicircle cutout — sweeps UP into the row
        p.addArc(
            center: CGPoint(x: pX, y: h),
            radius: pr,
            startAngle: .degrees(0),
            endAngle: .degrees(180),
            clockwise: false
        )
        // Continue along bottom edge
        p.addLine(to: CGPoint(x: r, y: h))

        // Bottom-left corner
        p.addArc(
            center: CGPoint(x: r, y: h - r),
            radius: r,
            startAngle: .degrees(90),
            endAngle: .degrees(180),
            clockwise: false
        )
        // Left edge
        p.addLine(to: CGPoint(x: 0, y: r))
        // Top-left corner
        p.addArc(
            center: CGPoint(x: r, y: r),
            radius: r,
            startAngle: .degrees(180),
            endAngle: .degrees(270),
            clockwise: false
        )
        p.closeSubpath()
        return p
    }
}

// MARK: - Faux barcode

/// Canvas-drawn faux barcode strip. Deliberately not a scannable
/// barcode — purely decorative. The bar widths cycle through a fixed
/// pattern that *looks* random enough at a glance.
struct BarcodeStripe: View {
    /// Bar widths in points: [bar, gap, bar, gap, ...]. The pattern
    /// repeats horizontally across the width of the canvas.
    private static let widths: [CGFloat] = [1, 2, 1, 3, 2, 1, 1, 2, 3, 1, 2, 1, 1, 3, 2, 2]

    var body: some View {
        Canvas { context, size in
            var x: CGFloat = 0
            var i = 0
            while x < size.width {
                let w = Self.widths[i % Self.widths.count]
                // Even indices are bars; odd are gaps.
                if i.isMultiple(of: 2) {
                    let rect = CGRect(x: x, y: 0, width: w, height: size.height)
                    context.fill(Path(rect), with: .color(.white))
                }
                x += w
                i += 1
            }
        }
    }
}
