import SwiftUI

/// Shared formatting for the ride flow. Latin digits, LTR-isolated  money and timers
/// must not reorder under Arabic (design-system.md §8).
enum RideFormat {
    /// MM:SS, or H:MM:SS past an hour.
    static func duration(_ totalSeconds: Int) -> String {
        let s = max(0, totalSeconds)
        let (h, m, sec) = (s / 3600, (s % 3600) / 60, s % 60)
        return h > 0
            ? String(format: "%d:%02d:%02d", h, m, sec)
            : String(format: "%02d:%02d", m, sec)
    }

    static func money(_ amount: Double, _ currency: String) -> String {
        String(format: "%.2f %@", amount, currency)
    }

    static func time(_ iso: String?) -> String {
        guard let iso else { return "" }
        let parser = ISO8601DateFormatter()
        parser.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        guard let date = parser.date(from: iso) ?? ISO8601DateFormatter().date(from: iso) else {
            return ""
        }
        let out = DateFormatter()
        out.dateFormat = "HH:mm"
        return out.string(from: date)
    }
}

// MARK: - Unlocking

/// PENDING_UNLOCK. The rider is standing next to a scooter waiting for a physical lock,
/// so this screen's only job is to say "we're working on it" honestly  including that
/// they aren't charged if it never opens.
struct UnlockingView: View {
    let ride: RideResponse
    let elapsedSeconds: Int
    let onCancel: () -> Void

    @State private var pulsing = false

    var body: some View {
        VStack(spacing: 0) {
            Spacer()
            Image(systemName: "lock.open.fill")
                .font(.system(size: 34, weight: .bold))
                .foregroundStyle(Color.brandInk)
                .frame(width: 88, height: 88)
                .background(Color.brand, in: Circle())
                .opacity(pulsing ? 1 : 0.45)
                .animation(
                    .easeInOut(duration: 0.9).repeatForever(autoreverses: true),
                    value: pulsing
                )
                .onAppear { pulsing = true }

            Text("unlocking_title".localized)
                .font(.beebTitle)
                .multilineTextAlignment(.center)
                .padding(.top, 28)

            // Past ~20s the reassuring copy stops being reassuring; switch to the honest one.
            Text((elapsedSeconds >= 20 ? "unlocking_slow" : "unlocking_body").localized)
                .font(.beebBody)
                .foregroundStyle(Color.muted)
                .multilineTextAlignment(.center)
                .padding(.top, 10)

            StatusBadge(
                text: String(format: "unlocking_scooter".localized, String(ride.scooterId.prefix(8))),
                tone: .warning
            )
            .padding(.top, 20)

            Spacer()
            SecondaryButton(title: "cancel".localized, action: onCancel)
        }
        .padding(24)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.appBackground)
        .sheet(item: $sharing) { url in ShareSheet(items: [url]) }
    }
}

/// `sheet(item:)` needs identity, and a file URL is its own.
extension URL: Identifiable {
    public var id: String { absoluteString }
}

// MARK: - Active

/// ACTIVE. The timer is the one display-size number; everything else is a tinted tile.
/// Nothing animates  it has to be readable at a glance while moving.
struct ActiveRideView: View {
    let ride: RideResponse
    let elapsedSeconds: Int
    let runningFare: Double
    let batteryPercent: Int?
    let onEnd: () -> Void
    let onMinimise: () -> Void

    var body: some View {
        ScrollView {
            VStack(spacing: 12) {
                StatusBadge(text: "ride_in_progress".localized, tone: .brand)

                // Which scooter this is. Only matters once a rider can hold more than one, but
                // then it matters a lot: two identical timers with no vehicle name are
                // indistinguishable.
                // Same reasoning as the balance warning below: the rider is about to lose the
                // vehicle, so it belongs on the screen they are looking at.
                if ride.lowBatteryWarnedAt != nil {
                    HStack(spacing: 12) {
                        IconChip(glyph: "🔋", tone: .warning)
                        VStack(alignment: .leading, spacing: 2) {
                            Text("battery_low_title".localized)
                                .font(.beebBody)
                                .foregroundStyle(Color.warning)
                            Text("battery_low_body".localized)
                                .font(.beebCaption)
                                .foregroundStyle(Color.muted)
                        }
                        Spacer()
                    }
                    .padding(16)
                    .beebCard()
                }

                // The grace-period warning, in the app rather than only in a push: a rider
                // whose ride is about to be ended for them must be told on the screen they are
                // actually looking at.
                if ride.lowBalanceWarnedAt != nil {
                    HStack(spacing: 12) {
                        IconChip(glyph: "⚠", tone: .warning)
                        VStack(alignment: .leading, spacing: 2) {
                            Text("grace_title".localized)
                                .font(.beebBody)
                                .foregroundStyle(Color.warning)
                            Text("grace_body".localized)
                                .font(.beebCaption)
                                .foregroundStyle(Color.muted)
                        }
                        Spacer()
                    }
                    .padding(16)
                    .beebCard()
                }

                if let code = ride.qrCode {
                    Text(code).font(.beebCaption).foregroundStyle(Color.muted)
                }

                DisplayNumber(
                    value: RideFormat.duration(elapsedSeconds),
                    caption: "ride_duration".localized
                )
                .padding(.vertical, 28)
                .beebCard()

                HStack(spacing: 12) {
                    StatTile(
                        glyph: "◉",
                        value: String(format: "%.2f", runningFare),
                        label: "metric_fare".localized,
                        tone: .brand
                    )
                    // TODO(location): distance and speed need CoreLocation; the backend
                    // does not stream them. Placeholders rather than fabricated numbers.
                    StatTile(glyph: "◈", value: "", label: "metric_distance".localized, tone: .info)
                    StatTile(glyph: "◐", value: "", label: "metric_speed".localized, tone: .accentPurple)
                }

                BatteryBar(
                    percent: batteryPercent,
                    label: "scooter_battery".localized,
                    hint: batteryPercent.map {
                        String(format: "battery_range".localized, Int(Double($0) * 0.4))
                    } ?? "battery_unknown".localized
                )

                DangerButton(title: "end_ride".localized, action: onEnd)
                // Leaving this screen is not the same as ending the ride. A rider taking a
                // second scooter needs the map back while this one keeps running, and without a
                // way out the only exit from a live ride would be to end it.
                SecondaryButton(title: "back_to_map".localized, action: onMinimise)
                    .padding(.top, 4)
            }
            .padding(16)
        }
        .background(Color.appBackground)
    }
}

// MARK: - Ending

/// Zone check → lock close → fare → wallet debit. Any of them can still refuse.
struct EndingView: View {
    var body: some View {
        VStack(spacing: 24) {
            ProgressView().tint(Color.brand)
            VStack(spacing: 8) {
                Text("ending_title".localized).font(.beebSection)
                Text("ending_body".localized)
                    .font(.beebBody)
                    .foregroundStyle(Color.muted)
                    .multilineTextAlignment(.center)
            }
        }
        .padding(24)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.appBackground)
    }
}

// MARK: - Completed

struct RideCompletedView: View {
    let ride: RideResponse
    let invoice: InvoiceResponse
    let rating: Int
    let ratingSubmitted: Bool
    let balance: Double?
    let onRate: (Int) -> Void
    let onDone: () -> Void

    @State private var showReceipt = false

    var body: some View {
        ScrollView {
            VStack(spacing: 14) {
                StatusMark(systemName: "checkmark", tone: .success)
                    .padding(.top, 12)
                Text("completed_title".localized).font(.beebTitle)
                Text("completed_body".localized)
                    .font(.beebBody)
                    .foregroundStyle(Color.muted)

                VStack(spacing: 0) {
                    HStack {
                        Text("ride_summary".localized)
                            .font(.beebLabel)
                            .foregroundStyle(Color.muted)
                        Spacer()
                        StatusBadge(text: String(ride.scooterId.prefix(8)), tone: .brand)
                    }
                    .padding(16)

                    MetricRow(
                        glyph: "◔",
                        label: "label_duration".localized,
                        value: RideFormat.duration(invoice.durationSeconds),
                        tone: .accentPurple
                    )
                    MetricRow(
                        glyph: "◉",
                        label: "total_cost".localized,
                        value: RideFormat.money(invoice.amount, invoice.currency),
                        tone: .danger
                    )
                    if let balance {
                        MetricRow(
                            glyph: "▤",
                            label: "remaining_balance".localized,
                            value: RideFormat.money(balance, invoice.currency),
                            tone: .brand
                        )
                    }
                }
                .beebCard()

                VStack(spacing: 14) {
                    Text((ratingSubmitted ? "rate_thanks" : "rate_prompt").localized)
                        .font(.beebBody)
                        .foregroundStyle(Color.muted)
                        .multilineTextAlignment(.center)
                    HStack(spacing: 10) {
                        ForEach(1...5, id: \.self) { star in
                            Image(systemName: star <= rating ? "star.fill" : "star")
                                .font(.system(size: 26))
                                .foregroundStyle(star <= rating ? Color.brand : Color.lineStrong)
                                .onTapGesture { onRate(star) }
                                .accessibilityLabel("\(star)")
                        }
                    }
                }
                .padding(20)
                .beebCard()

                SecondaryButton(title: "view_receipt".localized) { showReceipt = true }
                BrandButton(title: "done".localized, action: onDone)
            }
            .padding(20)
        }
        .background(Color.appBackground)
        .sheet(isPresented: $showReceipt) {
            ReceiptSheet(ride: ride, invoice: invoice)
                .presentationDetents([.medium, .large])
        }
    }
}

/// The itemised receipt  what a rider would hand to an employer or dispute against.
private struct ReceiptSheet: View {
    let ride: RideResponse
    let invoice: InvoiceResponse

    @Environment(\.layoutDirection) private var layoutDirection
    @State private var sharing: URL?

    /// The same numbers as the sheet, rendered to a PDF the rider owns. Built on demand rather
    /// than up front: most riders never open this sheet.
    private func document() -> URL {
        Receipt.render(
            ride: ride,
            invoice: invoice,
            text: Receipt.Text(
                title: "receipt_title".localized,
                rideLabel: "label_ride_id".localized,
                vehicleLabel: "label_vehicle".localized,
                startedLabel: "label_started".localized,
                endedLabel: "label_ended".localized,
                durationLabel: "label_duration".localized,
                unlockLabel: "label_unlock_fee".localized,
                perMinuteLabel: "label_per_minute".localized,
                totalLabel: "total_cost".localized,
                footer: "receipt_footer".localized,
                rtl: layoutDirection == .rightToLeft
            )
        )
    }

    var body: some View {
        ScrollView {
            VStack(spacing: 14) {
                Text("receipt_title".localized)
                    .font(.beebSection)
                    .padding(.top, 20)

                VStack(spacing: 0) {
                    MetricRow(
                        glyph: "#",
                        label: "label_ride_id".localized,
                        value: ride.id.prefix(8).uppercased(),
                        tone: .muted
                    )
                    MetricRow(
                        glyph: "▶",
                        label: "label_started".localized,
                        value: RideFormat.time(ride.startedAt),
                        tone: .success
                    )
                    MetricRow(
                        glyph: "■",
                        label: "label_ended".localized,
                        // endedAt is not yet set on the ride we hold; the invoice is written at end.
                        value: RideFormat.time(ride.endedAt ?? invoice.createdAt),
                        tone: .danger
                    )
                    MetricRow(
                        glyph: "◔",
                        label: "label_duration".localized,
                        value: RideFormat.duration(invoice.durationSeconds),
                        tone: .accentPurple
                    )
                    MetricRow(
                        glyph: "⚿",
                        label: "label_unlock_fee".localized,
                        value: RideFormat.money(ride.unlockFee, ride.currency),
                        tone: .muted
                    )
                    MetricRow(
                        glyph: "◷",
                        label: "label_per_minute".localized,
                        value: RideFormat.money(ride.perMinuteRate, ride.currency),
                        tone: .muted
                    )
                }
                .beebCard()

                HStack {
                    Text("total_cost".localized).font(.beebSection)
                    Spacer()
                    Text(RideFormat.money(invoice.amount, invoice.currency))
                        .font(.beebSection)
                        .foregroundStyle(Color.brand)
                        .numeric()
                }

                BrandButton(title: "receipt_share".localized) { sharing = document() }
                    .padding(.top, 6)
                // On iOS the share sheet is also how a document is saved: "Save to Files" is one
                // of its actions. A separate download button would be the same sheet twice.
                SecondaryButton(title: "receipt_download".localized) { sharing = document() }
            }
            .padding(20)
        }
        .background(Color.appBackground)
    }
}

// MARK: - Blocked

/// A refusal the rider can act on. Every reason gets its own copy and its own primary
/// action  a generic "error, retry" here is a support ticket.
struct BlockedView: View {
    let reason: BlockedReason
    let onDismiss: () -> Void
    let onTopUp: () -> Void

    private var needsTopUp: Bool { reason == .insufficientBalance || reason == .balanceToEnd }
    private var tone: Color { needsTopUp ? .warning : .danger }

    var body: some View {
        VStack(spacing: 0) {
            Spacer()
            IconChip(glyph: needsTopUp ? "!" : "✕", tone: tone, size: 56)
            Text(reason.copy.title.localized)
                .font(.beebSection)
                .multilineTextAlignment(.center)
                .padding(.top, 22)
            Text(reason.copy.body.localized)
                .font(.beebBody)
                .foregroundStyle(Color.muted)
                .multilineTextAlignment(.center)
                .padding(.top, 10)
            Spacer()

            if needsTopUp {
                BrandButton(title: reason.actionKey.localized, action: onTopUp)
                SecondaryButton(title: "cancel".localized, action: onDismiss)
                    .padding(.top, 10)
            } else {
                BrandButton(title: reason.actionKey.localized, action: onDismiss)
            }
        }
        .padding(24)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.appBackground)
    }
}
