package pl.firmatpp.kierowca.ui

import android.app.Application
import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.google.firebase.messaging.FirebaseMessaging
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.time.LocalDate
import java.time.Instant
import java.io.IOException
import pl.firmatpp.kierowca.data.AppPreferencesStore
import pl.firmatpp.kierowca.data.ApiErrorMapper
import pl.firmatpp.kierowca.data.ApiErrorKind
import pl.firmatpp.kierowca.data.DriverRepository
import pl.firmatpp.kierowca.data.PhotoUploadMetadata
import pl.firmatpp.kierowca.data.sync.DriverSyncRepository
import pl.firmatpp.kierowca.data.sync.NetworkMonitor
import pl.firmatpp.kierowca.data.sync.StartupOfflineFallbackLoader
import pl.firmatpp.kierowca.data.model.DispatchSheetReminderDto
import pl.firmatpp.kierowca.data.model.AppUpdateDto
import pl.firmatpp.kierowca.data.model.DriverDto
import pl.firmatpp.kierowca.data.model.DriverLeaveCalendarEntryDto
import pl.firmatpp.kierowca.data.model.DriverLeaveRequestDto
import pl.firmatpp.kierowca.data.model.DriverRouteDto
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
import pl.firmatpp.kierowca.data.model.driverLifecycleStatus
import pl.firmatpp.kierowca.data.upload.DispatchSheetUploadEntity
import pl.firmatpp.kierowca.data.upload.DispatchSheetUploadOutbox
import pl.firmatpp.kierowca.data.upload.PhotoUploadEntity
import pl.firmatpp.kierowca.data.upload.PhotoUploadOutbox
import pl.firmatpp.kierowca.data.upload.PhotoUploadStatus
import pl.firmatpp.kierowca.data.upload.DriverDatabase
import pl.firmatpp.kierowca.data.upload.OfflineOutboxManager
import pl.firmatpp.kierowca.data.upload.RouteActionEntity
import pl.firmatpp.kierowca.data.upload.RouteActionOutbox
import pl.firmatpp.kierowca.data.upload.RouteActionStatus
import pl.firmatpp.kierowca.data.upload.RouteActionType
import pl.firmatpp.kierowca.data.upload.isWorkerCancellationFailure
import pl.firmatpp.kierowca.diagnostics.AppDiagnostics
import pl.firmatpp.kierowca.diagnostics.DiagnosticEntry
import pl.firmatpp.kierowca.diagnostics.DiagnosticSection
import pl.firmatpp.kierowca.diagnostics.DiagnosticSeverity
import pl.firmatpp.kierowca.diagnostics.DiagnosticSnapshot
import pl.firmatpp.kierowca.diagnostics.DiagnosticSnapshotProvider
import pl.firmatpp.kierowca.sync.DriverLiveSyncClient
import pl.firmatpp.kierowca.sync.LiveSyncConnectionState
import pl.firmatpp.kierowca.sync.DriverSyncHint
import pl.firmatpp.kierowca.sync.DriverSyncWorker
import pl.firmatpp.kierowca.sync.DriverRuntimeSyncState
import pl.firmatpp.kierowca.tracking.ActiveRouteTrackingService
import pl.firmatpp.kierowca.ui.theme.AppThemeMode

enum class DriverScreen { Initializing, Phone, Otp, Routes, Profile, Diagnostics, Detail, StartRoute, LoadingWeight, FinishRoute, Photo, PhotoQueue, LeaveRequests, LeaveRequestDetail, LeaveCalendar, AddLeaveRequest }

enum class ServerConnectionState { Unknown, Reachable, Degraded }

data class DriverUiState(
    val screen: DriverScreen = DriverScreen.Initializing,
    val loading: Boolean = true,
    val refreshing: Boolean = false,
    val startupOfflineAvailable: Boolean = false,
    val phone: String = "",
    val otpCode: String = "",
    val maskedPhone: String = "",
    val driver: DriverDto? = null,
    val routes: List<DriverRouteDto> = emptyList(),
    val selectedDate: String = LocalDate.now().toString(),
    val minRouteDate: String = LocalDate.now().minusDays(7).toString(),
    val maxRouteDate: String = LocalDate.now().plusDays(1).toString(),
    val autoRefreshSeconds: Int = 60,
    val allowGalleryUploads: Boolean = true,
    val allowRouteCompletion: Boolean = false,
    val requirePreciseLocationForPhotos: Boolean = false,
    val loadingPhotoRequirement: String = RouteStageRequirement.Required,
    val loadingWeightRequirement: String = RouteStageRequirement.Required,
    val unloadingPhotoRequirement: String = RouteStageRequirement.Required,
    val unloadingWeightRequirement: String = RouteStageRequirement.Required,
    val notifyNewRoutes: Boolean = false,
    val routeProgressNotificationEnabled: Boolean = true,
    val notificationPermissionDenied: Boolean = false,
    val selectedRoute: DriverRouteDto? = null,
    val selectedPhoto: RoutePhotoDto? = null,
    val routeStageWeightText: String = "",
    val routeActions: List<RouteActionEntity> = emptyList(),
    val visibleRouteActions: List<RouteActionEntity> = emptyList(),
    val photoUploads: List<PhotoUploadEntity> = emptyList(),
    val queuedPhotoUploads: List<PhotoUploadEntity> = emptyList(),
    val dispatchSheetReminder: DispatchSheetReminderDto? = null,
    val appUpdate: AppUpdateDto? = null,
    val dispatchSheetUploads: List<DispatchSheetUploadEntity> = emptyList(),
    val leaveRequestsConfig: LeaveRequestsConfig? = null,
    val leaveRequests: List<DriverLeaveRequestDto> = emptyList(),
    val selectedLeaveRequest: DriverLeaveRequestDto? = null,
    val leaveRequestDateFrom: String = LocalDate.now().toString(),
    val leaveRequestDateTo: String = LocalDate.now().toString(),
    val leaveRequestType: String = "URLOP",
    val leaveRequestNote: String = "",
    val leaveCalendarEntries: List<DriverLeaveCalendarEntryDto> = emptyList(),
    val leaveCalendarLoading: Boolean = false,
    val leaveCalendarLoadedUntil: String? = null,
    val leaveCalendarHasSelection: Boolean = false,
    val leaveCalendarSelectionAnchor: String? = null,
    val deletingPhotoIds: Set<String> = emptySet(),
    val completingRoute: Boolean = false,
    val imageAuthHeader: String? = null,
    val isOnline: Boolean = true,
    val serverConnectionState: ServerConnectionState = ServerConnectionState.Unknown,
    val realtimeConnectionState: LiveSyncConnectionState = LiveSyncConnectionState.Stopped,
    val showRealtimeReconnecting: Boolean = false,
    val isStale: Boolean = false,
    val lastServerSyncAtEpochMillis: Long? = null,
    val routeDayLiveUpdateMessage: String? = null,
    val themeMode: AppThemeMode = AppThemeMode.Default,
    val feedback: String? = null,
    val error: String? = null,
    val showLogoutWarning: Boolean = false,
    val pendingLogoutItems: Int = 0,
    val pendingDispatchUploads: Int = 0,
    val pendingGpsPoints: Int = 0,
    val oldestPendingGpsAtEpochMillis: Long? = null,
    val diagnostics: DiagnosticSnapshot? = null,
    val diagnosticsLoading: Boolean = false,
    val diagnosticsError: String? = null,
) {
    private val projectionActions: List<RouteActionEntity>
        get() = (visibleRouteActions + routeActions).distinctBy { it.clientActionId }

    val displayRoutes: List<DriverRouteDto>
        get() = projectDriverRoutes(routes, projectionActions)
            .sortedWith(
                compareBy<DriverRouteDto> { route -> if (route.driverLifecycleStatus() == "ZAKOŃCZONA") 1 else 0 }
                    .thenBy { route -> routeDayOrdering(route, selectedDate) },
            )

    val selectedRouteProjection: ProjectedDriverRoute?
        get() = selectedRoute?.let { projectDriverRoute(it, projectionActions) }

    val displaySelectedRoute: DriverRouteDto?
        get() = selectedRouteProjection?.route

    private val pendingUserOperationalItems: Int
        get() = queuedPhotoUploads.count { it.status != PhotoUploadStatus.Confirmed.storageValue && it.status != PhotoUploadStatus.Cancelled.storageValue } +
            visibleRouteActions.count { it.status != RouteActionStatus.Confirmed } + pendingDispatchUploads

    val gpsQueueNeedsAttention: Boolean
        get() = isOnline && oldestPendingGpsAtEpochMillis?.let { System.currentTimeMillis() - it >= 2 * 60 * 1000L } == true

    val pendingOperationalItems: Int
        get() = pendingUserOperationalItems + if (!isOnline || serverConnectionState == ServerConnectionState.Degraded || gpsQueueNeedsAttention) pendingGpsPoints else 0

    val operationalQueueNeedsAttention: Boolean
        get() = queuedPhotoUploads.any { it.status == PhotoUploadStatus.FailedPermanent.storageValue } ||
            visibleRouteActions.any { it.status == RouteActionStatus.FailedPermanent || it.status == RouteActionStatus.FailedConflict } ||
            gpsQueueNeedsAttention
}

internal fun DriverUiState.withApiError(throwable: Throwable): DriverUiState {
    val apiError = ApiErrorMapper.map(throwable)
    return copy(
        serverConnectionState = if (apiError.kind == ApiErrorKind.Network || apiError.kind == ApiErrorKind.Server) ServerConnectionState.Degraded else serverConnectionState,
        isStale = if ((apiError.kind == ApiErrorKind.Network || apiError.kind == ApiErrorKind.Server) && lastServerSyncAtEpochMillis != null) true else isStale,
        error = apiError.message,
    )
}

internal fun DriverUiState.withLoadedLeaveRequests(
    requests: List<DriverLeaveRequestDto>,
    navigateToList: Boolean,
): DriverUiState {
    val currentById = leaveRequests.associateBy { it.id }
    val merged = requests.map { incoming ->
        currentById[incoming.id]?.takeIf { current -> current.version > incoming.version } ?: incoming
    }.toMutableList()
    leaveRequests.filter { current -> requests.none { it.id == current.id } }.forEach(merged::add)
    val refreshedSelectedRequest = selectedLeaveRequest?.let { selected ->
        merged.firstOrNull { request -> request.id == selected.id }?.takeIf { it.version >= selected.version } ?: selected
    }
    return copy(
        screen = if (navigateToList) DriverScreen.LeaveRequests else screen,
        leaveRequests = merged,
        selectedLeaveRequest = refreshedSelectedRequest,
        error = null,
    )
}

class DriverViewModel(application: Application) : AndroidViewModel(application) {
    private val appPreferencesStore = AppPreferencesStore(application)
    private val repository = DriverRepository(application)
    private val syncRepository = DriverSyncRepository(application, repository)
    private val startupOfflineFallbackLoader = StartupOfflineFallbackLoader()
    private val networkMonitor = NetworkMonitor(application)
    private val photoUploadOutbox = PhotoUploadOutbox(application)
    private val routeActionOutbox = RouteActionOutbox(application)
    private val dispatchSheetUploadOutbox = DispatchSheetUploadOutbox(application)
    private val offlineOutboxManager = OfflineOutboxManager(application)
    private val diagnosticSnapshotProvider = DiagnosticSnapshotProvider(application)
    private val _state = MutableStateFlow(DriverUiState(loading = true))
    private val otpAutoSubmitPolicy = OtpAutoSubmitPolicy()
    private val liveSyncClient = DriverLiveSyncClient(
        repository = repository,
        onConnected = { viewModelScope.launch { checkRemoteSyncState() } },
        onHint = { hint -> viewModelScope.launch { handleSyncHint(hint) } },
        onAuthenticationFailed = { viewModelScope.launch { handleExpiredSession() } },
    )
    private var photoUploadsJob: Job? = null
    private var routeActionsJob: Job? = null
    private var dispatchSheetUploadsJob: Job? = null
    private var realtimeBannerJob: Job? = null
    private var pushTokenRegisteredForDriverId: String? = null
    private val leaveMutationMutex = Mutex()
    private val syncCheckMutex = Mutex()
    private val syncHintMutex = Mutex()
    private val highestHintVersions = mutableMapOf<String, Long>()
    private var pendingLeaveHint: DriverSyncHint? = null
    private var leaveCalendarRequestGeneration: Long = 0
    private var routesRequestGeneration: Long = 0
    private var routeDetailRequestGeneration: Long = 0
    private var routesLoadJob: Job? = null
    val state: StateFlow<DriverUiState> = _state

    init {
        viewModelScope.launch {
            appPreferencesStore.themeMode.collect { themeMode ->
                _state.update { it.copy(themeMode = themeMode) }
            }
        }
        viewModelScope.launch {
            appPreferencesStore.routeProgressNotificationEnabled.collect { enabled ->
                _state.update { it.copy(routeProgressNotificationEnabled = enabled) }
            }
        }
        viewModelScope.launch {
            photoUploadOutbox.observeQueuedUploads().collect { uploads ->
                _state.update { it.copy(queuedPhotoUploads = uploads) }
            }
        }
        viewModelScope.launch {
            routeActionOutbox.observeVisibleActions().collect { actions ->
                _state.update { it.copy(visibleRouteActions = actions) }
            }
        }
        viewModelScope.launch {
            DriverDatabase.get(application).dispatchSheetUploadDao().observeUnsentCount().collect { count ->
                _state.update { it.copy(pendingDispatchUploads = count) }
            }
        }
        viewModelScope.launch {
            DriverDatabase.get(application).routePointDao().observeUnsentCount().collect { count ->
                _state.update { it.copy(pendingGpsPoints = count) }
            }
        }
        viewModelScope.launch {
            DriverDatabase.get(application).routePointDao().observeOldestUnsentAt().collect { oldest ->
                _state.update { it.copy(oldestPendingGpsAtEpochMillis = oldest) }
            }
        }
        viewModelScope.launch {
            liveSyncClient.connectionState.collect { connectionState ->
                DriverRuntimeSyncState.realtimeConnected = connectionState == LiveSyncConnectionState.Connected
                realtimeBannerJob?.cancel()
                _state.update {
                    it.copy(
                        realtimeConnectionState = connectionState,
                        showRealtimeReconnecting = false,
                    )
                }
                if (connectionState in setOf(
                        LiveSyncConnectionState.Connecting,
                        LiveSyncConnectionState.Subscribing,
                        LiveSyncConnectionState.Disconnected,
                    )) {
                    realtimeBannerJob = viewModelScope.launch {
                        kotlinx.coroutines.delay(3_000L)
                        _state.update {
                            it.copy(showRealtimeReconnecting = it.isOnline && it.realtimeConnectionState == connectionState)
                        }
                    }
                }
            }
        }
        viewModelScope.launch {
            networkMonitor.isOnline.collect { online ->
                liveSyncClient.onNetworkAvailable(online)
                _state.update {
                    it.copy(
                        isOnline = online,
                        isStale = if (!online && it.lastServerSyncAtEpochMillis != null) true else it.isStale,
                        serverConnectionState = if (!online) ServerConnectionState.Unknown else it.serverConnectionState,
                    )
                }
                if (online && repository.hasToken()) {
                    liveSyncClient.ensureConnected()
                    checkRemoteSyncState()
                }
            }
        }
        viewModelScope.launch {
            if (repository.hasToken()) {
                val offlineAvailable = syncRepository.hasBootstrapCache(_state.value.selectedDate)
                _state.update { it.copy(startupOfflineAvailable = offlineAvailable) }
                refreshRoutes()
            } else {
                _state.update { it.copy(screen = DriverScreen.Phone, loading = false) }
            }
        }
    }

    fun requestOtp(phone: String) = runLoading("request_otp") {
        if (!_state.value.isOnline) throw IOException("Logowanie wymaga połączenia z internetem.")
        val response = repository.requestOtp(phone)
        otpAutoSubmitPolicy.reset()
        _state.update {
            it.copy(
                screen = DriverScreen.Otp,
                phone = phone,
                otpCode = "",
                maskedPhone = response.phoneMasked.orEmpty(),
                error = null,
            )
        }
    }

    fun verifyOtp(code: String) = runLoading("verify_otp") {
        if (!_state.value.isOnline) throw IOException("Weryfikacja kodu wymaga połączenia z internetem.")
        val driver = repository.verifyOtp(_state.value.phone, code, android.os.Build.MODEL ?: "Android")
        AppDiagnostics.setDriverId(driver.id)
        _state.update { it.copy(driver = driver, imageAuthHeader = repository.imageAuthHeader()) }
        refreshRoutes()
    }

    fun updateOtpCode(code: String) {
        val sanitized = code.filter(Char::isDigit).take(6)
        _state.update { it.copy(otpCode = sanitized) }

        if (otpAutoSubmitPolicy.shouldSubmit(sanitized, _state.value.loading)) {
            verifyOtp(sanitized)
        }
    }

    fun refreshRoutes() = loadRoutes(date = _state.value.selectedDate, showLoading = true, navigateToRoutes = true)

    fun refreshRoutesSilently() = loadRoutes(date = _state.value.selectedDate, showLoading = false, navigateToRoutes = false)

    fun useOfflineStartupDataNow() {
        val snapshot = _state.value
        if (snapshot.screen != DriverScreen.Initializing || !snapshot.startupOfflineAvailable) {
            return
        }

        loadRoutes(
            date = snapshot.selectedDate,
            showLoading = true,
            navigateToRoutes = true,
            startupOfflineOnly = true,
        )
    }

    fun onAppForegrounded() {
        DriverRuntimeSyncState.foreground = true
        liveSyncClient.setForeground(true)
        if (!_state.value.isOnline || _state.value.driver == null) return
        liveSyncClient.ensureConnected()
        refreshCurrentVisibleScope()
    }

    fun onAppBackgrounded() {
        DriverRuntimeSyncState.foreground = false
        liveSyncClient.setForeground(false)
    }

    fun periodicSyncCheck() {
        if (!_state.value.isOnline) return
        viewModelScope.launch { checkRemoteSyncState() }
    }

    fun selectRouteDate(date: String) {
        _state.update { it.copy(routeDayLiveUpdateMessage = null) }
        loadRoutes(date = date, showLoading = true, navigateToRoutes = true)
    }

    fun dismissRouteDayLiveUpdate() {
        _state.update { it.copy(routeDayLiveUpdateMessage = null) }
    }

    private fun loadRoutes(
        date: String?,
        showLoading: Boolean,
        navigateToRoutes: Boolean,
        startupOfflineOnly: Boolean = false,
    ) {
        val generation = ++routesRequestGeneration
        routesLoadJob?.cancel()
        routesLoadJob = viewModelScope.launch {
            val isStartup = _state.value.screen == DriverScreen.Initializing
            _state.update { it.copy(loading = showLoading, refreshing = !showLoading, feedback = null, error = null) }

            runCatching {
                val cached = when {
                    startupOfflineOnly -> syncRepository.bootstrapFromCache(date)
                        ?: throw IOException("Brak zapisanych danych dla wybranego dnia.")
                    !_state.value.isOnline -> syncRepository.bootstrapFromCache(date)
                        ?: throw IOException("Brak zapisanych danych dla wybranego dnia.")
                    isStartup -> {
                        startupOfflineFallbackLoader.load(
                            onlineLoad = { syncRepository.bootstrap(date) },
                            offlineLoad = { syncRepository.bootstrapFromCache(date) },
                        )
                    }
                    else -> syncRepository.bootstrap(date)
                }
                val response = cached.value
                val settings = response.driverAppSettings
                AppDiagnostics.setDriverId(response.session.driver.id)
                if (generation != routesRequestGeneration) return@runCatching
                _state.update {
                    it.copy(
                        driver = response.session.driver,
                        routes = response.routes.today,
                        dispatchSheetReminder = response.dispatchSheetReminder,
                        appUpdate = response.appUpdate ?: it.appUpdate,
                        selectedDate = settings?.selectedDate ?: date ?: it.selectedDate,
                        minRouteDate = settings?.minDate ?: it.minRouteDate,
                        maxRouteDate = settings?.maxDate ?: it.maxRouteDate,
                        autoRefreshSeconds = settings?.autoRefreshSeconds ?: it.autoRefreshSeconds,
                        allowGalleryUploads = settings?.allowGalleryUploads ?: it.allowGalleryUploads,
                        allowRouteCompletion = settings?.allowRouteCompletion ?: it.allowRouteCompletion,
                        requirePreciseLocationForPhotos = settings?.requirePreciseLocationForPhotos
                            ?: it.requirePreciseLocationForPhotos,
                        loadingPhotoRequirement = normalizeRouteStageRequirement(settings?.loadingPhotoRequirement),
                        loadingWeightRequirement = normalizeRouteStageRequirement(settings?.loadingWeightRequirement),
                        unloadingPhotoRequirement = normalizeRouteStageRequirement(settings?.unloadingPhotoRequirement),
                        unloadingWeightRequirement = normalizeRouteStageRequirement(settings?.unloadingWeightRequirement),
                        leaveRequestsConfig = settings?.leaveRequests?.let { config ->
                            LeaveRequestsConfig(enabled = config.enabled, types = config.types)
                        },
                        screen = if (
                            settings?.leaveRequests?.enabled != true &&
                            it.screen in setOf(DriverScreen.LeaveRequests, DriverScreen.LeaveRequestDetail, DriverScreen.AddLeaveRequest)
                        ) DriverScreen.Routes else if (navigateToRoutes) DriverScreen.Routes else it.screen,
                        notifyNewRoutes = response.notificationPreferences?.notifyNewRoutes ?: it.notifyNewRoutes,
                        imageAuthHeader = repository.imageAuthHeader(),
                        isStale = cached.stale || !it.isOnline,
                        lastServerSyncAtEpochMillis = cached.syncedAtEpochMillis ?: it.lastServerSyncAtEpochMillis,
                        serverConnectionState = if (cached.stale) ServerConnectionState.Degraded else ServerConnectionState.Reachable,
                        error = null,
                    )
                }
                response.session.driver.id.let { driverId ->
                    liveSyncClient.start(driverId, response.realtime)
                    if (_state.value.isOnline && !cached.stale) registerPushTokenIfAvailable(driverId)
                }
                observeDispatchSheetUploads(response.dispatchSheetReminder?.workDate)
                if (_state.value.isOnline && !cached.stale && settings?.selectedDate == settings?.today) {
                    reconcileTrackingService(response.routes.today, response.session.driver)
                }
            }.onFailure { throwable ->
                if (generation != routesRequestGeneration) return@onFailure
                reportHandledException("load_routes", throwable, mapOf("date" to date))
                if (ApiErrorMapper.map(throwable).kind == ApiErrorKind.Auth) {
                    handleExpiredSession()
                } else {
                    _state.update {
                        it.withApiError(throwable).copy(
                            screen = if (it.screen == DriverScreen.Initializing) DriverScreen.Routes else it.screen,
                        )
                    }
                }
            }

            if (generation == routesRequestGeneration) _state.update {
                it.copy(loading = false, refreshing = false)
            }
        }
    }

    fun openRoute(routeId: String) = openRouteWithTarget(routeId, DriverScreen.Detail)

    private fun openRouteWithTarget(routeId: String, targetScreen: DriverScreen) = runLoading("open_route", mapOf("route_id" to routeId)) {
        val generation = ++routeDetailRequestGeneration
        val cached = if (_state.value.isOnline) {
            syncRepository.route(routeId)
        } else {
            syncRepository.routeFromCache(routeId)
                ?: throw IOException("Szczegóły tej trasy nie zostały zapisane w telefonie.")
        }
        if (generation != routeDetailRequestGeneration) return@runLoading
        val response = cached.value
        photoUploadOutbox.discardConfirmedServerPhotos(response.route)
        _state.update {
            it.copy(
                screen = targetScreen,
                selectedRoute = response.route,
                routeStageWeightText = when (targetScreen) {
                    DriverScreen.LoadingWeight -> response.route.loadingWeight?.toString().orEmpty()
                    DriverScreen.FinishRoute -> response.route.unloadingWeight?.toString().orEmpty()
                    else -> it.routeStageWeightText
                },
                feedback = null,
                isStale = cached.stale || !it.isOnline,
                lastServerSyncAtEpochMillis = cached.syncedAtEpochMillis ?: it.lastServerSyncAtEpochMillis,
                serverConnectionState = if (cached.stale) ServerConnectionState.Degraded else ServerConnectionState.Reachable,
                error = null,
            )
        }
        observePhotoUploads(routeId)
        observeRouteActions(routeId)
        if (_state.value.isOnline && !cached.stale) {
            reconcileTrackingServiceForRoute(response.route, _state.value.driver)
        }
    }

    fun refreshSelectedRoute() {
        val routeId = _state.value.selectedRoute?.id ?: return
        val generation = ++routeDetailRequestGeneration

        viewModelScope.launch {
            _state.update { it.copy(refreshing = true, feedback = null, error = null) }

            runCatching {
                if (_state.value.isOnline) syncRepository.route(routeId)
                else syncRepository.routeFromCache(routeId) ?: throw IOException("Szczegóły tej trasy nie zostały zapisane w telefonie.")
            }
                .onSuccess { cached ->
                    if (generation != routeDetailRequestGeneration) return@onSuccess
                    val response = cached.value
                    photoUploadOutbox.discardConfirmedServerPhotos(response.route)
                    _state.update {
                        it.copy(
                            screen = routeScreenAfterServerRefresh(it.screen, response.route),
                            selectedRoute = response.route,
                            routes = it.routes.map { route -> if (route.id == routeId) response.route else route },
                            isStale = cached.stale || !it.isOnline,
                            lastServerSyncAtEpochMillis = cached.syncedAtEpochMillis ?: it.lastServerSyncAtEpochMillis,
                            serverConnectionState = if (cached.stale) ServerConnectionState.Degraded else ServerConnectionState.Reachable,
                            error = null,
                        )
                    }
                    if (_state.value.isOnline && !cached.stale) {
                        reconcileTrackingServiceForRoute(response.route, _state.value.driver)
                    }
                }
                .onFailure { throwable ->
                    if (generation != routeDetailRequestGeneration) return@onFailure
                    if (ApiErrorMapper.map(throwable).kind == ApiErrorKind.Auth) {
                        handleExpiredSession()
                    } else {
                        _state.update { it.withApiError(throwable) }
                    }
                }

            if (generation == routeDetailRequestGeneration) _state.update { it.copy(refreshing = false) }
        }
    }

    fun openProfile() {
        _state.update { it.copy(screen = DriverScreen.Profile, error = null) }
    }

    fun openDiagnostics() {
        _state.update { it.copy(screen = DriverScreen.Diagnostics, diagnosticsError = null) }
        refreshDiagnostics()
    }

    fun refreshDiagnostics() {
        viewModelScope.launch {
            _state.update { it.copy(diagnosticsLoading = true, diagnosticsError = null) }
            val runtime = _state.value
            val liveDiagnostics = liveSyncClient.diagnostics()
            val hintVersions = syncHintMutex.withLock { highestHintVersions.toMap() }
            val runtimeSections = listOf(
                DiagnosticSection(
                    "Połączenie i synchronizacja",
                    listOf(
                        DiagnosticEntry("Sieć według aplikacji", if (runtime.isOnline) "online" else "offline", if (runtime.isOnline) DiagnosticSeverity.Good else DiagnosticSeverity.Error),
                        DiagnosticEntry("Serwer", runtime.serverConnectionState.name, if (runtime.serverConnectionState == ServerConnectionState.Reachable) DiagnosticSeverity.Good else DiagnosticSeverity.Warning),
                        DiagnosticEntry("Realtime", runtime.realtimeConnectionState.name, if (runtime.realtimeConnectionState == LiveSyncConnectionState.Connected) DiagnosticSeverity.Good else DiagnosticSeverity.Warning),
                        DiagnosticEntry("Realtime skonfigurowany", if (liveDiagnostics.configured) "tak" else "nie"),
                        DiagnosticEntry("Realtime aktywny docelowo", if (liveDiagnostics.desiredActive) "tak" else "nie"),
                        DiagnosticEntry("Sieć widziana przez realtime", if (liveDiagnostics.networkAvailable) "dostępna" else "niedostępna"),
                        DiagnosticEntry("Aplikacja na pierwszym planie", if (DriverRuntimeSyncState.foreground) "tak" else "nie"),
                        DiagnosticEntry("Foreground klienta realtime", if (liveDiagnostics.foregroundActive) "tak" else "nie"),
                        DiagnosticEntry("WebSocket obsługuje hinty", if (DriverRuntimeSyncState.websocketOwnsForegroundHints()) "tak" else "nie"),
                        DiagnosticEntry("Socket ID", liveDiagnostics.socketId ?: "brak"),
                        DiagnosticEntry("Próba reconnect", liveDiagnostics.reconnectAttempt.toString()),
                        DiagnosticEntry("Licznik wiadomości", liveDiagnostics.messageVersion.toString()),
                        DiagnosticEntry("Ostatnia wiadomość realtime", liveDiagnostics.lastMessageAtEpochMillis?.let { Instant.ofEpochMilli(it).toString() } ?: "brak"),
                        DiagnosticEntry("Ostatnie rozłączenie", liveDiagnostics.lastDisconnectReason ?: "brak"),
                        DiagnosticEntry("Dane nieaktualne", if (runtime.isStale) "tak" else "nie", if (runtime.isStale) DiagnosticSeverity.Warning else DiagnosticSeverity.Good),
                        DiagnosticEntry("Ostatnia synchronizacja serwera", runtime.lastServerSyncAtEpochMillis?.let { Instant.ofEpochMilli(it).toString() } ?: "brak"),
                        DiagnosticEntry("Fallback kontroli", "co ${runtime.autoRefreshSeconds} s bez zdrowego realtime; co 5 min z realtime"),
                        DiagnosticEntry("Najwyższe odebrane hinty", hintVersions.entries.sortedBy { it.key }.joinToString { "${it.key}=v${it.value}" }.ifBlank { "brak" }),
                        DiagnosticEntry("Oczekujący hint urlopowy", pendingLeaveHint?.let { "${it.scope}, v${it.version}" } ?: "brak"),
                    ),
                ),
                DiagnosticSection(
                    "Bieżąca sesja",
                    listOf(
                        DiagnosticEntry("Kierowca", runtime.driver?.displayName.orEmpty().ifBlank { "brak" }),
                        DiagnosticEntry("ID kierowcy", runtime.driver?.id ?: "brak"),
                        DiagnosticEntry("Ekran", runtime.screen.name),
                        DiagnosticEntry("Wybrany dzień", runtime.selectedDate),
                        DiagnosticEntry("Wybrana trasa", runtime.selectedRoute?.id ?: "brak"),
                        DiagnosticEntry("Trasy w pamięci", runtime.routes.size.toString()),
                        DiagnosticEntry("Wnioski w pamięci", runtime.leaveRequests.size.toString()),
                        DiagnosticEntry("Push zarejestrowany dla sesji", if (pushTokenRegisteredForDriverId == runtime.driver?.id) "tak" else "nie", if (pushTokenRegisteredForDriverId == runtime.driver?.id) DiagnosticSeverity.Good else DiagnosticSeverity.Warning),
                        DiagnosticEntry("Ostatni komunikat", runtime.feedback ?: "brak"),
                        DiagnosticEntry("Ostatni błąd UI", runtime.error ?: "brak", if (runtime.error == null) DiagnosticSeverity.Good else DiagnosticSeverity.Warning),
                    ),
                ),
                DiagnosticSection(
                    "Konfiguracja aplikacji",
                    listOf(
                        DiagnosticEntry("Zakres dni", "${runtime.minRouteDate} – ${runtime.maxRouteDate}"),
                        DiagnosticEntry("Galeria", if (runtime.allowGalleryUploads) "włączona" else "wyłączona"),
                        DiagnosticEntry("Kończenie kursu", if (runtime.allowRouteCompletion) "włączone" else "wyłączone"),
                        DiagnosticEntry("Dokładna lokalizacja zdjęć", if (runtime.requirePreciseLocationForPhotos) "wymagana" else "niewymagana"),
                        DiagnosticEntry("Załadunek, zdjęcie", runtime.loadingPhotoRequirement),
                        DiagnosticEntry("Załadunek, tonaż", runtime.loadingWeightRequirement),
                        DiagnosticEntry("Rozładunek, zdjęcie", runtime.unloadingPhotoRequirement),
                        DiagnosticEntry("Rozładunek, tonaż", runtime.unloadingWeightRequirement),
                        DiagnosticEntry("Powiadomienia o trasach", if (runtime.notifyNewRoutes) "włączone" else "wyłączone"),
                        DiagnosticEntry("Pasek aktywnego kursu", if (runtime.routeProgressNotificationEnabled) "włączony" else "wyłączony"),
                        DiagnosticEntry("Motyw", runtime.themeMode.name),
                        DiagnosticEntry("Wnioski urlopowe", if (runtime.leaveRequestsConfig?.enabled == true) "włączone" else "wyłączone"),
                    ),
                ),
            )

            runCatching { diagnosticSnapshotProvider.capture(runtimeSections) }
                .onSuccess { snapshot ->
                    _state.update { it.copy(diagnostics = snapshot, diagnosticsLoading = false, diagnosticsError = null) }
                }
                .onFailure { throwable ->
                    reportHandledException("diagnostics_snapshot", throwable)
                    _state.update {
                        it.copy(
                            diagnosticsLoading = false,
                            diagnosticsError = "Nie udało się zebrać wszystkich danych: ${throwable.message ?: throwable::class.java.simpleName}",
                        )
                    }
                }
        }
    }

    fun updateNewRouteNotifications(enabled: Boolean) {
        if (!_state.value.isOnline) {
            _state.update { it.copy(error = "Zmiana powiadomień wymaga połączenia z internetem.") }
            return
        }
        viewModelScope.launch {
            _state.update { it.copy(refreshing = true, error = null, notificationPermissionDenied = false) }
            runCatching { repository.updateNotificationPreferences(enabled) }
                .onSuccess { preferences ->
                    _state.update {
                        it.copy(
                            notifyNewRoutes = preferences.notifyNewRoutes,
                            feedback = if (preferences.notifyNewRoutes) "Powiadomienia o nowych kursach są włączone." else "Powiadomienia o nowych kursach są wyłączone.",
                            error = null,
                        )
                    }
                }
                .onFailure { throwable ->
                    _state.update { it.withApiError(throwable) }
                }
            _state.update { it.copy(refreshing = false) }
        }
    }

    fun markNotificationPermissionDenied() {
        _state.update {
            it.copy(
                notifyNewRoutes = false,
                notificationPermissionDenied = true,
                error = "Aby otrzymywać powiadomienia o nowych kursach, włącz powiadomienia w ustawieniach aplikacji.",
            )
        }
    }

    fun markNotificationPermissionBlocked() {
        _state.update {
            it.copy(
                notificationPermissionDenied = true,
                error = "Powiadomienia są włączone w aplikacji, ale Android blokuje ich wyświetlanie. Włącz powiadomienia w ustawieniach aplikacji.",
            )
        }
    }

    fun clearNotificationPermissionWarning() {
        _state.update {
            it.copy(
                notificationPermissionDenied = false,
                error = if (it.error?.contains("powiadom", ignoreCase = true) == true) null else it.error,
            )
        }
    }

    fun setThemeMode(themeMode: AppThemeMode) {
        _state.update { it.copy(themeMode = themeMode) }
        viewModelScope.launch {
            runCatching { appPreferencesStore.setThemeMode(themeMode) }
                .onFailure { throwable -> _state.update { it.withApiError(throwable) } }
        }
    }

    fun setRouteProgressNotificationEnabled(enabled: Boolean) {
        _state.update { it.copy(routeProgressNotificationEnabled = enabled) }
        viewModelScope.launch {
            runCatching { appPreferencesStore.setRouteProgressNotificationEnabled(enabled) }
                .onFailure { throwable -> _state.update { it.withApiError(throwable) } }
        }
    }

    fun openPhotoQueue() {
        _state.update { it.copy(screen = DriverScreen.PhotoQueue, error = null) }
    }

    fun openLeaveRequests() {
        if (!DriverLeaveRequestUiRules.isFeatureVisible(_state.value.leaveRequestsConfig)) return
        if (!_state.value.isOnline) {
            _state.update {
                it.copy(
                    screen = DriverScreen.LeaveRequests,
                    error = "Wnioski urlopowe są dostępne tylko online. Pokazujemy dane zapisane w tej sesji.",
                )
            }
            return
        }
        viewModelScope.launch { loadLeaveRequests(showLoading = true, navigateToList = true) }
    }

    fun refreshLeaveRequests() {
        if (!DriverLeaveRequestUiRules.isFeatureVisible(_state.value.leaveRequestsConfig)) return
        if (!_state.value.isOnline) {
            _state.update { it.copy(error = "Odświeżenie wniosków wymaga połączenia z internetem.") }
            return
        }
        viewModelScope.launch { loadLeaveRequests(showLoading = false, navigateToList = false) }
    }

    private suspend fun loadLeaveRequests(showLoading: Boolean, navigateToList: Boolean): Boolean {
        _state.update { it.copy(loading = showLoading, refreshing = !showLoading, error = null, feedback = null) }
        val result = runCatching { repository.leaveRequests() }
        result.onSuccess { requests ->
            _state.update { it.withLoadedLeaveRequests(requests, navigateToList) }
        }.onFailure { throwable -> _state.update { it.withApiError(throwable) } }
        _state.update { it.copy(loading = false, refreshing = false) }
        return result.isSuccess
    }

    fun openLeaveRequest(id: String) {
        if (!DriverLeaveRequestUiRules.isFeatureVisible(_state.value.leaveRequestsConfig)) return
        if (!_state.value.isOnline) {
            val cached = _state.value.leaveRequests.firstOrNull { it.id == id }
            _state.update {
                it.copy(
                    screen = if (cached != null) DriverScreen.LeaveRequestDetail else it.screen,
                    selectedLeaveRequest = cached ?: it.selectedLeaveRequest,
                    error = "Szczegóły wniosku wymagają połączenia z internetem.",
                )
            }
            return
        }
        viewModelScope.launch {
            _state.update { it.copy(loading = true, error = null, feedback = null) }
            runCatching { repository.leaveRequest(id) }
                .onSuccess { request ->
                    _state.update { it.copy(screen = DriverScreen.LeaveRequestDetail, selectedLeaveRequest = request, error = null) }
                }
                .onFailure { throwable -> _state.update { it.withApiError(throwable) } }
            _state.update { it.copy(loading = false) }
        }
    }

    fun openAddLeaveRequest() {
        val config = _state.value.leaveRequestsConfig
        if (!DriverLeaveRequestUiRules.isFeatureVisible(config)) return
        if (!_state.value.isOnline) {
            _state.update { it.copy(error = "Złożenie wniosku urlopowego wymaga połączenia z internetem.") }
            return
        }
        val today = LocalDate.now().toString()
        _state.update {
            it.copy(
                screen = DriverScreen.LeaveCalendar,
                leaveRequestDateFrom = today,
                leaveRequestDateTo = today,
                leaveRequestType = config?.types?.firstOrNull() ?: "URLOP",
                leaveRequestNote = "",
                leaveCalendarEntries = emptyList(),
                leaveCalendarLoadedUntil = null,
                leaveCalendarHasSelection = false,
                leaveCalendarSelectionAnchor = null,
                error = null,
                feedback = null,
            )
        }
        loadMoreLeaveCalendarMonths(reset = true)
    }

    fun updateLeaveRequestDraft(dateFrom: String? = null, dateTo: String? = null, type: String? = null, note: String? = null) {
        _state.update {
            val nextFrom = dateFrom ?: it.leaveRequestDateFrom
            val nextTo = dateTo ?: it.leaveRequestDateTo
            it.copy(
                leaveRequestDateFrom = nextFrom,
                leaveRequestDateTo = if (nextTo < nextFrom) nextFrom else nextTo,
                leaveRequestType = type ?: it.leaveRequestType,
                leaveRequestNote = note ?: it.leaveRequestNote,
            )
        }
    }

    fun selectLeaveCalendarDate(date: String) {
        val selected = runCatching { LocalDate.parse(date) }.getOrNull() ?: return
        if (selected.isBefore(LocalDate.now())) return

        _state.update {
            val anchor = it.leaveCalendarSelectionAnchor
            if (!it.leaveCalendarHasSelection || anchor == null) {
                it.copy(
                    leaveRequestDateFrom = date,
                    leaveRequestDateTo = date,
                    leaveCalendarHasSelection = true,
                    leaveCalendarSelectionAnchor = date,
                    error = null,
                )
            } else {
                val start = LocalDate.parse(anchor)
                val from = minOf(start, selected).toString()
                val to = maxOf(start, selected).toString()
                it.copy(
                    leaveRequestDateFrom = from,
                    leaveRequestDateTo = to,
                    leaveCalendarHasSelection = true,
                    leaveCalendarSelectionAnchor = null,
                    error = null,
                )
            }
        }
    }

    fun continueAddLeaveRequestFromCalendar() {
        if (!_state.value.leaveCalendarHasSelection) {
            _state.update { it.copy(error = "Wybierz termin urlopu w kalendarzu.") }
            return
        }

        _state.update { it.copy(screen = DriverScreen.AddLeaveRequest, error = null, feedback = null) }
    }

    fun reopenLeaveCalendarFromDraft() {
        if (!DriverLeaveRequestUiRules.isFeatureVisible(_state.value.leaveRequestsConfig)) return

        _state.update {
            it.copy(
                screen = DriverScreen.LeaveCalendar,
                error = null,
                feedback = null,
            )
        }
        if (_state.value.leaveCalendarLoadedUntil == null) {
            loadMoreLeaveCalendarMonths(reset = true)
        }
    }

    fun loadMoreLeaveCalendarMonths(reset: Boolean = false) {
        val snapshot = _state.value
        if (!DriverLeaveRequestUiRules.isFeatureVisible(snapshot.leaveRequestsConfig) || snapshot.leaveCalendarLoading) return
        if (!snapshot.isOnline) {
            _state.update { it.copy(error = "Kalendarz urlopów wymaga połączenia z internetem.") }
            return
        }

        val from = if (reset || snapshot.leaveCalendarLoadedUntil == null) {
            LocalDate.now()
        } else {
            runCatching { LocalDate.parse(snapshot.leaveCalendarLoadedUntil).plusDays(1) }.getOrDefault(LocalDate.now())
        }
        val to = from.plusMonths(6).minusDays(1)
        val generation = ++leaveCalendarRequestGeneration

        viewModelScope.launch {
            _state.update {
                it.copy(
                    leaveCalendarLoading = true,
                    error = null,
                    leaveCalendarEntries = if (reset) emptyList() else it.leaveCalendarEntries,
                    leaveCalendarLoadedUntil = if (reset) null else it.leaveCalendarLoadedUntil,
                )
            }
            runCatching { repository.leaveCalendar(from.toString(), to.toString()) }
                .onSuccess { entries ->
                    if (generation != leaveCalendarRequestGeneration) return@onSuccess
                    _state.update {
                        val merged = (it.leaveCalendarEntries + entries)
                            .distinctBy { entry -> entry.id }
                            .sortedWith(compareBy<DriverLeaveCalendarEntryDto> { entry -> entry.dateFrom.orEmpty() }.thenBy { entry -> entry.id })
                        it.copy(
                            leaveCalendarEntries = merged,
                            leaveCalendarLoadedUntil = to.toString(),
                            leaveCalendarLoading = false,
                            error = null,
                        )
                    }
                }
                .onFailure { throwable ->
                    if (generation != leaveCalendarRequestGeneration) return@onFailure
                    _state.update { it.withApiError(throwable).copy(leaveCalendarLoading = false) }
                }
        }
    }

    fun submitLeaveRequest() {
        val snapshot = _state.value
        if (!DriverLeaveRequestUiRules.isFeatureVisible(snapshot.leaveRequestsConfig)) return
        if (!snapshot.isOnline) {
            _state.update { it.copy(error = "Wysłanie wniosku wymaga połączenia z internetem.") }
            return
        }
        if (!leaveMutationMutex.tryLock()) return
        if (snapshot.leaveRequestDateFrom < LocalDate.now().toString()) {
            _state.update { it.copy(error = "Data od nie może być z przeszłości.") }
            leaveMutationMutex.unlock()
            return
        }

        viewModelScope.launch {
            _state.update { it.copy(loading = true, error = null, feedback = null) }
            try {
            runCatching {
                repository.createLeaveRequest(
                    dateFrom = snapshot.leaveRequestDateFrom,
                    dateTo = snapshot.leaveRequestDateTo,
                    type = snapshot.leaveRequestType,
                    note = snapshot.leaveRequestNote.takeIf { it.isNotBlank() },
                )
            }.onSuccess { created ->
                val requests = runCatching { repository.leaveRequests() }.getOrElse { listOf(created) }
                _state.update {
                    it.copy(
                        screen = DriverScreen.LeaveRequestDetail,
                        selectedLeaveRequest = created,
                        leaveRequests = requests,
                        feedback = "Wniosek został wysłany do decyzji.",
                        error = null,
                    )
                }
            }.onFailure { throwable ->
                _state.update { it.withApiError(throwable) }
            }
            _state.update { it.copy(loading = false) }
            } finally {
                leaveMutationMutex.unlock()
                consumePendingLeaveHint()
            }
        }
    }

    fun cancelSelectedLeaveRequest() {
        val request = _state.value.selectedLeaveRequest ?: return
        if (!_state.value.isOnline) {
            _state.update { it.copy(error = "Anulowanie wniosku wymaga połączenia z internetem.") }
            return
        }
        if (!DriverLeaveRequestUiRules.canCancel(request.status, request.dateFrom.orEmpty())) return
        if (!leaveMutationMutex.tryLock()) return

        viewModelScope.launch {
            _state.update { it.copy(loading = true, error = null, feedback = null) }
            try {
            runCatching { repository.cancelLeaveRequest(request.id, request.version) }
                .onSuccess { updated ->
                    _state.update {
                        it.copy(
                            selectedLeaveRequest = updated,
                            leaveRequests = it.leaveRequests.map { existing -> if (existing.id == updated.id) updated else existing },
                            feedback = if (updated.status == "cancel_requested") "Anulowanie wysłane do decyzji." else "Wniosek został anulowany.",
                            error = null,
                        )
                    }
                }
                .onFailure { throwable ->
                    val apiError = ApiErrorMapper.map(throwable)
                    if (apiError.kind == ApiErrorKind.Conflict && apiError.code == "leave_request_stale") {
                        runCatching { repository.leaveRequest(request.id) }
                            .onSuccess { current ->
                                _state.update {
                                    it.withLoadedLeaveRequests(listOf(current), navigateToList = false)
                                        .copy(error = "Wniosek został wcześniej zmieniony. Pokazujemy aktualny stan.")
                                }
                            }
                            .onFailure { refreshError -> _state.update { it.withApiError(refreshError) } }
                    } else {
                        _state.update { it.withApiError(throwable) }
                    }
                }
            _state.update { it.copy(loading = false) }
            } finally {
                leaveMutationMutex.unlock()
                consumePendingLeaveHint()
            }
        }
    }

    fun uploadPhoto(uri: Uri, source: String, metadata: PhotoUploadMetadata = PhotoUploadMetadata(), stage: String = "other") {
        val snapshot = _state.value
        val route = snapshot.selectedRoute ?: return
        if (!routeIsActiveToday(route, snapshot.selectedDate)) {
            _state.update { it.copy(error = "Zdjęcia możesz dodać tylko w zaplanowanym zakresie kursu.") }
            return
        }
        viewModelScope.launch {
            _state.update { it.copy(error = null) }
            runCatching { photoUploadOutbox.enqueue(route.id, uri, source, metadata, stage) }
                .onFailure { throwable ->
                    _state.update { it.withApiError(throwable) }
                }
        }
    }

    fun openStartRoute() {
        val snapshot = _state.value
        val route = snapshot.selectedRoute ?: return
        if (!canStartRouteFromDriverApp(route, snapshot.selectedDate)) {
            _state.update { it.copy(error = "Załadunek nie jest dostępny poza zaplanowanym zakresem kursu.") }
            return
        }
        val targetScreen = if (
            !routeStageRequirementIsVisible(snapshot.loadingPhotoRequirement) &&
            routeStageRequirementIsVisible(snapshot.loadingWeightRequirement)
        ) DriverScreen.LoadingWeight else DriverScreen.StartRoute
        _state.update {
            it.copy(
                screen = targetScreen,
                routeStageWeightText = "",
                feedback = null,
                error = null,
            )
        }
    }

    fun openLoadingWeight() {
        val snapshot = _state.value
        val route = snapshot.selectedRoute ?: return
        val stagePhotos = routePhotosForStage(route.photos, "loading")
        val stageUploads = routeStageUsableUploads(
            snapshot.photoUploads.filter { normalizedRoutePhotoStage(it.stage) == "loading" },
        )
        val blocker = loadingPhotosSubmitBlocker(
            serverPhotoCount = stagePhotos.size,
            localUploadCount = stageUploads.size,
            photoRequirement = snapshot.loadingPhotoRequirement,
        )
        if (blocker != null) {
            _state.update { it.copy(error = blocker) }
            return
        }

        _state.update {
            it.copy(
                screen = DriverScreen.LoadingWeight,
                routeStageWeightText = route.loadingWeight?.toString().orEmpty(),
                feedback = null,
                error = null,
            )
        }
    }

    fun openFinishRoute() {
        val snapshot = _state.value
        val route = snapshot.selectedRoute ?: return
        if (!canFinishRouteFromDriverApp(route, snapshot.selectedDate)) {
            _state.update { it.copy(error = "Rozładunek jest dostępny w dniu zaplanowanego rozładunku.") }
            return
        }
        _state.update {
            it.copy(
                screen = DriverScreen.FinishRoute,
                routeStageWeightText = route.unloadingWeight?.toString().orEmpty(),
                feedback = null,
                error = null,
            )
        }
    }

    fun updateRouteStageWeight(value: String) {
        val normalized = value.filter { it.isDigit() || it == ',' || it == '.' }.take(8)
        _state.update { it.copy(routeStageWeightText = normalized) }
    }

    fun correctFailedRouteAction() {
        val snapshot = _state.value
        val action = (snapshot.visibleRouteActions + snapshot.routeActions)
            .distinctBy { it.clientActionId }
            .filter { it.status == RouteActionStatus.FailedPermanent }
            .maxByOrNull { it.createdAtEpochMillis }
            ?: return

        viewModelScope.launch {
            val interrupted = isWorkerCancellationFailure(action.lastError)
            runCatching {
                if (interrupted) routeActionOutbox.retry(action.clientActionId)
                else routeActionOutbox.discard(action.clientActionId)
            }
                .onSuccess {
                    _state.update {
                        it.copy(
                            routeActions = if (interrupted) it.routeActions else it.routeActions.filterNot { item -> item.clientActionId == action.clientActionId },
                            visibleRouteActions = if (interrupted) it.visibleRouteActions else it.visibleRouteActions.filterNot { item -> item.clientActionId == action.clientActionId },
                            feedback = if (interrupted) "Ponawiam potwierdzenie z tym samym identyfikatorem operacji." else it.feedback,
                            error = null,
                        )
                    }
                    if (!interrupted) {
                        if (action.action == RouteActionType.Start) openStartRoute() else openFinishRoute()
                    }
                }
                .onFailure { throwable -> _state.update { it.withApiError(throwable) } }
        }
    }

    fun submitStartRoute() {
        submitRouteStage(stage = "loading")
    }

    fun submitFinishRoute() {
        submitRouteStage(stage = "unloading")
    }

    private fun submitRouteStage(stage: String) {
        val snapshot = _state.value
        val route = snapshot.selectedRoute ?: return
        val canSubmitStage = if (stage == "loading") {
            canStartRouteFromDriverApp(route, snapshot.selectedDate)
        } else {
            canFinishRouteFromDriverApp(route, snapshot.selectedDate)
        }
        if (!canSubmitStage) {
            _state.update {
                it.copy(error = if (stage == "loading") {
                    "Załadunek nie jest dostępny poza zaplanowanym zakresem kursu."
                } else {
                    "Rozładunek jest dostępny w dniu zaplanowanego rozładunku."
                })
            }
            return
        }
        val driver = snapshot.driver
        val photoRequirement = if (stage == "loading") snapshot.loadingPhotoRequirement else snapshot.unloadingPhotoRequirement
        val weightRequirement = if (stage == "loading") snapshot.loadingWeightRequirement else snapshot.unloadingWeightRequirement
        val weight = if (routeStageRequirementIsVisible(weightRequirement)) {
            parseRouteWeightTons(snapshot.routeStageWeightText)
        } else {
            null
        }
        val stagePhotos = routePhotosForStage(route.photos, stage)
        val stageUploads = routeStageUsableUploads(
            snapshot.photoUploads.filter { normalizedRoutePhotoStage(it.stage) == stage },
        )
        val submitBlocker = if (stage == "loading") {
            loadingWeightSubmitBlocker(
                weightText = snapshot.routeStageWeightText,
                serverPhotoCount = stagePhotos.size,
                localUploadCount = stageUploads.size,
                weightRequirement = weightRequirement,
                photoRequirement = photoRequirement,
            )
        } else {
            routeStageSubmitBlocker(
                weightText = snapshot.routeStageWeightText,
                serverPhotoCount = stagePhotos.size,
                localUploadCount = stageUploads.size,
                weightRequirement = weightRequirement,
                photoRequirement = photoRequirement,
            )
        }

        if (submitBlocker != null) {
            _state.update { it.copy(error = submitBlocker) }
            return
        }

        val photoClientRequestIds = if (routeStageRequirementIsVisible(photoRequirement)) {
            (stagePhotos.mapNotNull { it.clientRequestId } + stageUploads.map { it.clientRequestId })
            .map { it.trim() }
            .filter { it.isNotBlank() }
            .distinct()
        } else {
            emptyList()
        }

        if (routeStageRequirementIsRequired(photoRequirement) && photoClientRequestIds.isEmpty()) {
            _state.update { it.copy(error = "Zdjęcie musi mieć lokalny identyfikator synchronizacji.") }
            return
        }

        viewModelScope.launch {
            _state.update { it.copy(loading = true, error = null, feedback = null) }
            runCatching {
                val updatedRoute = when (stage) {
                    "loading" -> {
                        routeActionOutbox.enqueueStart(route.id, weight, photoClientRequestIds)
                        route.copy(
                            status = "W TRAKCIE",
                            driverStatus = "W TRAKCIE",
                            loadingWeight = weight,
                            trackingStatus = "active",
                            photos = route.safePhotos(),
                        )
                    }
                    "unloading" -> {
                        routeActionOutbox.enqueueFinish(route.id, weight, photoClientRequestIds)
                        route.copy(
                            status = "ZAKOŃCZONA",
                            driverStatus = "ZAKOŃCZONA",
                            unloadingWeight = weight,
                            trackingStatus = "finished",
                            completedAt = Instant.now().toString(),
                            photos = route.safePhotos(),
                        )
                    }
                    else -> route
                }

                _state.update {
                    it.copy(
                        screen = DriverScreen.Detail,
                        selectedRoute = updatedRoute,
                        routes = it.routes.map { item -> if (item.id == route.id) updatedRoute else item },
                        routeStageWeightText = "",
                        feedback = if (stage == "loading") {
                            if (snapshot.isOnline) "Potwierdzam załadunek z serwerem." else "Załadunek zapisano w telefonie. Wyślemy go po odzyskaniu internetu."
                        } else {
                            if (snapshot.isOnline) "Potwierdzam rozładunek z serwerem." else "Rozładunek zapisano w telefonie. Wyślemy go po odzyskaniu internetu."
                        },
                        error = null,
                    )
                }

                if (stage == "loading" && driver != null) {
                    ActiveRouteTrackingService.start(
                        context = getApplication(),
                        route = updatedRoute,
                        driver = driver,
                        progressEnabled = snapshot.routeProgressNotificationEnabled,
                    )
                }
                if (stage == "unloading") {
                    ActiveRouteTrackingService.stop(getApplication())
                }
            }.onFailure { throwable ->
                reportHandledException("submit_route_stage", throwable, mapOf("route_id" to route.id, "stage" to stage))
                _state.update { it.withApiError(throwable) }
            }
            _state.update { it.copy(loading = false) }
        }
    }

    fun uploadDispatchSheetPhoto(uri: Uri, source: String, metadata: PhotoUploadMetadata = PhotoUploadMetadata()) {
        val reminder = _state.value.dispatchSheetReminder ?: return
        val workDate = reminder.workDate ?: return
        if (!reminder.canUpload) return

        viewModelScope.launch {
            _state.update { it.copy(error = null) }
            runCatching {
                dispatchSheetUploadOutbox.enqueue(
                    workDate = workDate,
                    replacePhotoId = reminder.photo?.id,
                    uri = uri,
                    source = source,
                    metadata = metadata,
                )
            }.onFailure { throwable ->
                _state.update { it.withApiError(throwable) }
            }
        }
    }

    fun retryPhotoUpload(upload: PhotoUploadEntity) {
        viewModelScope.launch {
            runCatching { photoUploadOutbox.retry(upload.clientRequestId) }
                .onFailure { throwable ->
                    _state.update { it.withApiError(throwable) }
                }
        }
    }

    fun deletePhoto(photo: RoutePhotoDto) {
        val route = _state.value.selectedRoute ?: return
        if (!_state.value.isOnline) {
            _state.update { it.copy(error = "Usunięcie zdjęcia wymaga połączenia z internetem.") }
            return
        }
        viewModelScope.launch {
            _state.update { it.copy(deletingPhotoIds = it.deletingPhotoIds + photo.id, error = null) }
            runCatching {
                repository.deletePhoto(photo.id)
                val response = syncRepository.route(route.id).value
                photoUploadOutbox.discardConfirmedServerPhotos(response.route)
                _state.update {
                    it.copy(
                        selectedRoute = response.route,
                        routes = it.routes.map { item -> if (item.id == route.id) response.route else item },
                        error = null,
                    )
                }
            }.onFailure { throwable ->
                _state.update { it.withApiError(throwable) }
            }
            _state.update { it.copy(deletingPhotoIds = it.deletingPhotoIds - photo.id) }
        }
    }

    fun deletePhotoUpload(upload: PhotoUploadEntity) {
        val route = _state.value.selectedRoute ?: return
        val serverPhotoId = confirmedUploadServerPhotoId(upload.status, upload.serverPhotoId)
        if (serverPhotoId == null) {
            if (!canDiscardLocalPhotoUpload(upload.status, upload.serverPhotoId)) return
            viewModelScope.launch {
                _state.update { it.copy(deletingPhotoIds = it.deletingPhotoIds + upload.clientRequestId, error = null) }
                runCatching { photoUploadOutbox.discard(upload.clientRequestId) }
                    .onFailure { throwable -> _state.update { it.withApiError(throwable) } }
                _state.update { it.copy(deletingPhotoIds = it.deletingPhotoIds - upload.clientRequestId) }
            }
            return
        }
        if (!_state.value.isOnline) {
            _state.update { it.copy(error = "Usunięcie wysłanego zdjęcia wymaga połączenia z internetem.") }
            return
        }

        viewModelScope.launch {
            _state.update { it.copy(deletingPhotoIds = it.deletingPhotoIds + serverPhotoId, error = null) }
            runCatching {
                repository.deletePhoto(serverPhotoId)
                photoUploadOutbox.discard(upload.clientRequestId)
                val response = syncRepository.route(route.id).value
                photoUploadOutbox.discardConfirmedServerPhotos(response.route)
                _state.update {
                    it.copy(
                        selectedRoute = response.route,
                        routes = it.routes.map { item -> if (item.id == route.id) response.route else item },
                        error = null,
                    )
                }
            }.onFailure { throwable ->
                _state.update { it.withApiError(throwable) }
            }
            _state.update { it.copy(deletingPhotoIds = it.deletingPhotoIds - serverPhotoId) }
        }
    }

    fun completeSelectedRoute() {
        val snapshot = _state.value
        val route = snapshot.selectedRoute ?: return
        if (snapshot.completingRoute) return
        if (!snapshot.isOnline) {
            _state.update { it.copy(error = "Ta operacja zakończenia kursu wymaga połączenia z internetem.") }
            return
        }
        if (!canCompleteRouteFromDriverApp(route, snapshot.selectedDate)) {
            _state.update { it.copy(error = "Zakończenie kursu jest dostępne w dniu zaplanowanego rozładunku.") }
            return
        }

        viewModelScope.launch {
            _state.update { it.copy(completingRoute = true, feedback = null, error = null) }
            runCatching {
                val completed = repository.completeRoute(route.id).route
                syncRepository.cacheConfirmedRoute(completed, _state.value.selectedDate)
                val refreshed = runCatching { syncRepository.route(route.id) }
                    .getOrNull()
                    ?.takeUnless { it.stale }
                    ?.value
                    ?.route
                    ?: completed
                photoUploadOutbox.discardConfirmedServerPhotos(refreshed)
                _state.update {
                    it.copy(
                        selectedRoute = refreshed,
                        routes = it.routes.map { item -> if (item.id == route.id) refreshed else item },
                        feedback = "Kurs zakończony",
                        error = null,
                    )
                }
            }.onFailure { throwable ->
                _state.update { it.withApiError(throwable).copy(feedback = null) }
            }
            _state.update { it.copy(completingRoute = false) }
        }
    }

    fun openPhoto(photo: RoutePhotoDto) {
        _state.update { it.copy(screen = DriverScreen.Photo, selectedPhoto = photo) }
    }

    fun openRouteFromNotification(routeId: String?, target: String? = null) {
        if (routeId.isNullOrBlank()) return
        val targetScreen = if (target == "finish") DriverScreen.FinishRoute else DriverScreen.Detail
        openRouteWithTarget(routeId, targetScreen)
    }

    fun back() {
        _state.update {
            when (it.screen) {
                DriverScreen.Photo -> it.copy(screen = DriverScreen.Detail, selectedPhoto = null)
                DriverScreen.PhotoQueue -> it.copy(screen = DriverScreen.Routes)
                DriverScreen.StartRoute -> it.copy(screen = DriverScreen.Detail, routeStageWeightText = "", feedback = null)
                DriverScreen.LoadingWeight -> it.copy(screen = DriverScreen.StartRoute, feedback = null)
                DriverScreen.FinishRoute -> it.copy(screen = DriverScreen.Detail, routeStageWeightText = "", feedback = null)
                DriverScreen.LeaveRequests -> it.copy(screen = DriverScreen.Routes)
                DriverScreen.LeaveRequestDetail -> it.copy(screen = DriverScreen.LeaveRequests, selectedLeaveRequest = null, feedback = null)
                DriverScreen.LeaveCalendar -> it.copy(screen = DriverScreen.LeaveRequests, feedback = null)
                DriverScreen.AddLeaveRequest -> it.copy(screen = DriverScreen.LeaveRequests, feedback = null)
                DriverScreen.Detail -> {
                    photoUploadsJob?.cancel()
                    routeActionsJob?.cancel()
                    it.copy(screen = DriverScreen.Routes, selectedRoute = null, routeActions = emptyList(), photoUploads = emptyList(), feedback = null)
                }
                DriverScreen.Profile -> it.copy(screen = DriverScreen.Routes)
                DriverScreen.Diagnostics -> it.copy(screen = DriverScreen.Profile)
                DriverScreen.Otp -> it.copy(screen = DriverScreen.Phone)
                else -> it
            }
        }
    }

    fun requestLogout() {
        viewModelScope.launch {
            val pending = offlineOutboxManager.pendingCount()
            if (pending > 0) {
                _state.update { it.copy(showLogoutWarning = true, pendingLogoutItems = pending) }
            } else {
                performLogout()
            }
        }
    }

    fun dismissLogoutWarning() {
        _state.update { it.copy(showLogoutWarning = false) }
    }

    fun confirmLogoutAndDeletePending() {
        _state.update { it.copy(showLogoutWarning = false) }
        performLogout()
    }

    private fun performLogout() = runLoading("logout") {
        photoUploadsJob?.cancel()
        routeActionsJob?.cancel()
        dispatchSheetUploadsJob?.cancel()
        realtimeBannerJob?.cancel()
        liveSyncClient.stop()
        ActiveRouteTrackingService.stop(getApplication())
        offlineOutboxManager.clearAll()
        repository.logout(notifyServer = _state.value.isOnline)
        syncRepository.clearCache()
        pushTokenRegisteredForDriverId = null
        AppDiagnostics.clearDriverId()
        _state.update { DriverUiState(screen = DriverScreen.Phone, loading = false) }
    }

    private fun runLoading(
        operation: String,
        keys: Map<String, Any?> = emptyMap(),
        block: suspend () -> Unit,
    ) {
        viewModelScope.launch {
            _state.update { it.copy(loading = true, error = null) }
            val result = runCatching { block() }
            result.exceptionOrNull()?.let { throwable ->
                reportHandledException(operation, throwable, keys)
                if (ApiErrorMapper.map(throwable).kind == ApiErrorKind.Auth && _state.value.driver != null) {
                    handleExpiredSession()
                } else {
                    _state.update { it.withApiError(throwable) }
                }
            }
            _state.update { it.copy(loading = false) }
        }
    }

    private fun observePhotoUploads(routeId: String) {
        photoUploadsJob?.cancel()
        photoUploadsJob = viewModelScope.launch {
            photoUploadOutbox.observeVisibleForRoute(routeId).collect { uploads ->
                _state.update { it.copy(photoUploads = uploads) }
            }
        }
    }

    private fun reconcileTrackingService(routes: List<DriverRouteDto>, driver: DriverDto) {
        val activeRoute = routes.firstOrNull { it.driverLifecycleStatus() == "W TRAKCIE" }
        if (activeRoute == null) {
            ActiveRouteTrackingService.stop(getApplication())
        } else {
            ActiveRouteTrackingService.start(
                context = getApplication(),
                route = activeRoute,
                driver = driver,
                progressEnabled = _state.value.routeProgressNotificationEnabled,
            )
        }
    }

    private fun reconcileTrackingServiceForRoute(route: DriverRouteDto, driver: DriverDto?) {
        if (route.driverLifecycleStatus() == "W TRAKCIE") {
            if (driver != null) {
                ActiveRouteTrackingService.start(
                    context = getApplication(),
                    route = route,
                    driver = driver,
                    progressEnabled = _state.value.routeProgressNotificationEnabled,
                )
            }
        } else {
            ActiveRouteTrackingService.stop(getApplication(), route.id)
        }
    }

    private fun observeRouteActions(routeId: String) {
        routeActionsJob?.cancel()
        routeActionsJob = viewModelScope.launch {
            routeActionOutbox.observeUnresolvedForRoute(routeId).collect { actions ->
                _state.update { it.copy(routeActions = actions) }
            }
        }
    }

    private fun observeDispatchSheetUploads(workDate: String?) {
        dispatchSheetUploadsJob?.cancel()
        if (workDate.isNullOrBlank()) {
            _state.update { it.copy(dispatchSheetUploads = emptyList()) }
            return
        }

        dispatchSheetUploadsJob = viewModelScope.launch {
            dispatchSheetUploadOutbox.observeVisibleForWorkDate(workDate).collect { uploads ->
                _state.update { it.copy(dispatchSheetUploads = uploads) }
            }
        }
    }

    private fun refreshCurrentVisibleScope() {
        when (_state.value.screen) {
            DriverScreen.Routes -> refreshRoutesSilently()
            DriverScreen.Detail -> refreshSelectedRoute()
            DriverScreen.LeaveRequests, DriverScreen.LeaveRequestDetail -> refreshLeaveRequests()
            else -> viewModelScope.launch { checkRemoteSyncState() }
        }
    }

    private suspend fun checkRemoteSyncState() {
        if (!_state.value.isOnline) return
        syncCheckMutex.withLock {
            syncHintMutex.withLock { highestHintVersions.clear() }
            val snapshot = _state.value
            val routeId = snapshot.selectedRoute?.id
            val result = runCatching { syncRepository.fetchSyncState(snapshot.selectedDate, routeId) }
            val remote = result.getOrElse { throwable ->
                val apiError = ApiErrorMapper.map(throwable)
                if (apiError.kind == ApiErrorKind.Auth) {
                    handleExpiredSession()
                } else {
                    _state.update {
                        it.copy(
                            serverConnectionState = ServerConnectionState.Degraded,
                            isStale = it.lastServerSyncAtEpochMillis != null,
                        )
                    }
                }
                return@withLock
            }
            _state.update { it.copy(serverConnectionState = ServerConnectionState.Reachable) }
            val staleScopes = remote.scopes.filter { syncRepository.shouldRefresh(it) }

            if (staleScopes.isEmpty()) {
                syncRepository.saveSyncStates(remote)
                return@withLock
            }

            staleScopes.forEach { scope ->
                if (scope.scope == DriverSyncRepository.SCOPE_SESSION) {
                    syncRepository.saveSyncScope(scope)
                    return@forEach
                }
                handleSyncHint(
                    DriverSyncHint(
                        scope = scope.scope,
                        date = scope.date,
                        routeId = scope.routeId,
                        version = scope.version,
                        checksum = scope.checksum,
                    ),
                )
            }
        }
    }

    private suspend fun handleSyncHint(hint: DriverSyncHint) {
        syncHintMutex.withLock {
        val hintKey = listOf(hint.scope, hint.date ?: "-", hint.routeId ?: "-").joinToString(":")
        val previousVersion = highestHintVersions[hintKey]
        if (previousVersion != null && hint.version <= previousVersion) return
        highestHintVersions[hintKey] = hint.version
        if (!syncRepository.shouldRefresh(hint.asScope())) return

        val snapshot = _state.value
        when (hint.scope) {
            DriverSyncRepository.SCOPE_ROUTES -> {
                if (shouldShowRouteDayLiveUpdate(snapshot.selectedDate, hint.date)) {
                    refreshRoutesSilently()
                    if (snapshot.screen == DriverScreen.Routes) {
                        _state.update {
                            it.copy(routeDayLiveUpdateMessage = "Spedytor zaktualizował zlecenia dla tego dnia.")
                        }
                    }
                } else {
                    DriverSyncWorker.enqueue(getApplication(), hint.date, null, hint.scope, hint.version, hint.checksum)
                }
            }
            DriverSyncRepository.SCOPE_ROUTE_DETAIL -> {
                if (hint.routeId == snapshot.selectedRoute?.id) {
                    refreshSelectedRoute()
                } else {
                    DriverSyncWorker.enqueue(getApplication(), hint.date, hint.routeId, hint.scope, hint.version, hint.checksum)
                }
            }
            DriverSyncRepository.SCOPE_SETTINGS -> refreshRoutesSilently()
            DriverSyncRepository.SCOPE_DISPATCH_SHEET -> refreshRoutesSilently()
            DriverSyncRepository.SCOPE_LEAVE_REQUESTS -> {
                if (leaveMutationMutex.isLocked) {
                    val pending = pendingLeaveHint
                    if (pending == null || hint.version > pending.version) pendingLeaveHint = hint
                    return
                }
                if (snapshot.screen == DriverScreen.LeaveRequests || snapshot.screen == DriverScreen.LeaveRequestDetail) {
                    if (loadLeaveRequests(showLoading = false, navigateToList = false)) {
                        syncRepository.saveSyncScope(hint.asScope())
                    }
                } else {
                    syncRepository.saveSyncScope(hint.asScope())
                }
            }
            DriverSyncRepository.SCOPE_LEAVE_CALENDAR -> {
                if (snapshot.screen == DriverScreen.LeaveCalendar) {
                    refreshLoadedLeaveCalendar(hint)
                } else {
                    syncRepository.saveSyncScope(hint.asScope())
                }
            }
            DriverSyncRepository.SCOPE_SESSION -> {
                runCatching { syncRepository.fetchSyncState(snapshot.selectedDate, snapshot.selectedRoute?.id) }
                    .onSuccess { syncRepository.saveSyncStates(it) }
                    .onFailure { throwable ->
                        if (ApiErrorMapper.map(throwable).kind == ApiErrorKind.Auth) handleExpiredSession()
                    }
            }
        }
        }
    }

    private suspend fun handleExpiredSession() {
        liveSyncClient.stop()
        ActiveRouteTrackingService.stop(getApplication())
        offlineOutboxManager.clearAll()
        repository.logout(notifyServer = false)
        syncRepository.clearCache()
        pushTokenRegisteredForDriverId = null
        AppDiagnostics.clearDriverId()
        _state.update {
            DriverUiState(
                screen = DriverScreen.Phone,
                loading = false,
                error = "Sesja wygasła lub została cofnięta. Zaloguj się ponownie.",
            )
        }
    }

    private suspend fun consumePendingLeaveHint() {
        val hint = pendingLeaveHint ?: return
        pendingLeaveHint = null
        if (loadLeaveRequests(showLoading = false, navigateToList = false)) {
            syncRepository.saveSyncScope(hint.asScope())
        }
    }

    private suspend fun refreshLoadedLeaveCalendar(hint: DriverSyncHint) {
        val generation = ++leaveCalendarRequestGeneration
        val snapshot = _state.value
        val from = LocalDate.now()
        val loadedUntil = snapshot.leaveCalendarLoadedUntil
            ?.let { runCatching { LocalDate.parse(it) }.getOrNull() }
            ?.takeUnless { it.isBefore(from) }
            ?: from.plusMonths(6).minusDays(1)

        runCatching { repository.leaveCalendar(from.toString(), loadedUntil.toString()) }
            .onSuccess { entries ->
                if (generation != leaveCalendarRequestGeneration) return@onSuccess
                _state.update { it.copy(leaveCalendarEntries = entries, leaveCalendarLoadedUntil = loadedUntil.toString(), leaveCalendarLoading = false) }
                syncRepository.saveSyncScope(hint.asScope())
            }
            .onFailure { throwable ->
                if (generation != leaveCalendarRequestGeneration) return@onFailure
                _state.update { it.withApiError(throwable).copy(leaveCalendarLoading = false) }
            }
    }

    private fun registerPushTokenIfAvailable(driverId: String) {
        if (pushTokenRegisteredForDriverId == driverId) return

        runCatching {
            FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
                viewModelScope.launch {
                    runCatching {
                        repository.storePushToken(token)
                        pushTokenRegisteredForDriverId = driverId
                    }.onFailure { throwable ->
                        reportHandledException("store_push_token", throwable, mapOf("driver_id" to driverId))
                    }
                }
            }.addOnFailureListener { throwable ->
                reportHandledException("get_push_token", throwable, mapOf("driver_id" to driverId))
            }
        }.onFailure { throwable ->
            reportHandledException("register_push_token", throwable, mapOf("driver_id" to driverId))
        }
    }

    private fun reportHandledException(
        operation: String,
        throwable: Throwable,
        keys: Map<String, Any?> = emptyMap(),
    ) {
        val apiError = ApiErrorMapper.map(throwable)
        if (!ApiErrorMapper.shouldReportNonFatal(throwable)) {
            AppDiagnostics.log("network_failure: $operation: ${throwable.message ?: throwable::class.java.simpleName}")
            return
        }
        AppDiagnostics.reportNonFatal(
            throwable = throwable,
            operation = operation,
            keys = mapOf(
                "screen" to _state.value.screen.name,
                "api_error_kind" to apiError.kind.name,
                "api_error_code" to apiError.code,
                "api_status_code" to apiError.statusCode,
                "retryable" to apiError.retryable,
            ) + keys,
        )
    }

    override fun onCleared() {
        DriverRuntimeSyncState.foreground = false
        DriverRuntimeSyncState.realtimeConnected = false
        liveSyncClient.close()
        photoUploadsJob?.cancel()
        routeActionsJob?.cancel()
        dispatchSheetUploadsJob?.cancel()
        super.onCleared()
    }
}
