import SwiftUI

/// Routes `RideFlowState` to exactly one screen.
///
/// The whole ride lifecycle lives behind this single `switch`, so there is no way to
/// render two ride states at once, and adding a case to the machine forces a screen.
struct RideFlowHost<Idle: View>: View {
    @StateObject private var vm: RideViewModel
    private let onTopUp: () -> Void
    private let idle: (@escaping (String) -> Void) -> Idle

    init(
        container: AppContainer,
        onTopUp: @escaping () -> Void,
        @ViewBuilder idle: @escaping (@escaping (String) -> Void) -> Idle
    ) {
        _vm = StateObject(
            wrappedValue: RideViewModel(
                rides: container.rideRepository,
                wallet: container.walletRepository,
                photos: container.photoUploader,
                zones: container.zoneRepository,
                location: container.location,
                legal: container.legalRepository
            )
        )
        self.onTopUp = onTopUp
        self.idle = idle
    }

    var body: some View {
        content
            .task {
                vm.refreshBalance()
                // A ride the rider left running is theirs to finish, so look for one before
                // settling on the idle screen.
                vm.restoreInFlight()
            }
    }

    @ViewBuilder
    private var content: some View {
        switch vm.state {
        case .idle:
            ZStack(alignment: .bottom) {
                idle { vm.start(qrCode: $0) }
                // Reading vm.tick here is what makes the bar's timers move: it changes once a
                // second while any ride is running, and nothing else would redraw this.
                let _ = vm.tick
                RunningRidesBar(
                    rides: vm.running.map {
                        RunningRide(ride: $0, elapsedSeconds: vm.elapsed(of: $0), fare: vm.fare(of: $0))
                    },
                    onOpen: vm.focus
                )
                // Clears the scan control, which owns the bottom of the map.
                .padding(.bottom, 96)
            }

        // Allocation in flight  the scan surface shows its own busy state.
        case .validating:
            idle { _ in }

        case let .consent(documents, _, submitting, failed):
            ConsentView(
                documents: documents,
                submitting: submitting,
                failed: failed,
                onAccept: vm.acceptConsent,
                onDecline: vm.declineConsent
            )

        case let .unlocking(ride, elapsed):
            UnlockingView(ride: ride, elapsedSeconds: elapsed, onCancel: vm.cancelUnlock)

        case let .active(ride, elapsed, fare, battery):
            ActiveRideView(
                ride: ride,
                elapsedSeconds: elapsed,
                runningFare: fare,
                batteryPercent: battery,
                onEnd: vm.requestEnd,
                onMinimise: vm.minimise
            )

        case let .confirmEnd(ride, elapsed, fare, balanceAfter):
            // The live ride stays on screen behind the dialog  ending is not a
            // destination, it's a decision taken while riding.
            ActiveRideView(
                ride: ride,
                elapsedSeconds: elapsed,
                runningFare: fare,
                batteryPercent: nil,
                onEnd: {},
                onMinimise: {}
            )
            .confirmEndAlert(
                elapsedSeconds: elapsed,
                fare: fare,
                balanceAfter: balanceAfter,
                currency: ride.currency,
                onKeepRiding: vm.dismissEnd,
                onConfirm: vm.proceedToPhoto
            )

        case let .photo(_, captured, uploading):
            ParkingPhotoView(
                captured: captured,
                uploading: uploading,
                zoneRefused: vm.zoneRefusal,
                parkingGuidance: vm.parkingGuidance,
                onCaptured: { data in
                    vm.clearZoneRefusal()
                    vm.onPhotoCaptured(data)
                },
                onRetake: vm.retakePhoto,
                onConfirm: vm.confirmPhotoAndEnd,
                onCancel: vm.cancelPhoto
            )

        case .ending:
            EndingView()

        case let .completed(ride, invoice, rating, submitted):
            RideCompletedView(
                ride: ride,
                invoice: invoice,
                rating: rating,
                ratingSubmitted: submitted,
                balance: vm.balance,
                onRate: vm.rate,
                onDone: vm.finish
            )

        case let .blocked(reason):
            BlockedView(
                reason: reason,
                onDismiss: vm.dismissBlocked,
                onTopUp: {
                    vm.dismissBlocked()
                    onTopUp()
                }
            )
        }
    }
}

private extension View {
    /// Pre-charge summary. The rider sees the number before anything is committed.
    func confirmEndAlert(
        elapsedSeconds: Int,
        fare: Double,
        balanceAfter: Double?,
        currency: String,
        onKeepRiding: @escaping () -> Void,
        onConfirm: @escaping () -> Void
    ) -> some View {
        var message = "confirm_end_body".localized
        message += "\n\n\("label_duration".localized): \(RideFormat.duration(elapsedSeconds))"
        message += "\n\("label_cost".localized): \(RideFormat.money(fare, currency))"
        if let balanceAfter {
            message += "\n\("label_balance_after".localized): \(RideFormat.money(balanceAfter, currency))"
        }
        return alert("confirm_end_title".localized, isPresented: .constant(true)) {
            Button("keep_riding".localized, role: .cancel, action: onKeepRiding)
            Button("yes_end_ride".localized, role: .destructive, action: onConfirm)
        } message: {
            Text(message)
        }
    }
}
