package pl.firmatpp.kierowca.ui

import java.io.File
import java.math.BigDecimal
import java.math.RoundingMode
import java.net.URLEncoder
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.Locale
import kotlin.math.abs
import pl.firmatpp.kierowca.data.model.DriverRouteDto
import pl.firmatpp.kierowca.data.model.DispatchSheetReminderDto
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
import pl.firmatpp.kierowca.data.model.driverLifecycleStatus
import pl.firmatpp.kierowca.data.upload.PhotoUploadEntity
import pl.firmatpp.kierowca.data.upload.PhotoUploadStatus
import pl.firmatpp.kierowca.data.upload.isWorkerCancellationFailure
import pl.firmatpp.kierowca.data.upload.RouteActionEntity
import pl.firmatpp.kierowca.data.upload.RouteActionStatus
import pl.firmatpp.kierowca.data.upload.RouteActionType

private val shortDateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM")
private val routeWeightKilogramInputThreshold = BigDecimal("1000")
private const val routeWeightMaxTons = 999.999
private const val googleMapsPackage = "com.google.android.apps.maps"

data class NavigationIntentCandidate(
    val uri: String,
    val packageName: String? = null,
)

data class AppUpdateBannerUi(
    val message: String,
    val actionLabel: String,
    val required: Boolean,
    val downloaded: Boolean,
)

fun appUpdateBannerUi(
    currentVersionCode: Int,
    latestVersionCode: Int?,
    latestVersionName: String?,
    minimumSupportedVersionCode: Int,
    playUpdateAvailable: Boolean,
    playAvailableVersionCode: Int?,
    updateDownloaded: Boolean,
): AppUpdateBannerUi? {
    val highestAvailableVersion = listOfNotNull(latestVersionCode, playAvailableVersionCode).maxOrNull()
    val required = minimumSupportedVersionCode > 0 && currentVersionCode < minimumSupportedVersionCode
    val available = updateDownloaded ||
        playUpdateAvailable ||
        highestAvailableVersion?.let { it > currentVersionCode } == true ||
        required
    if (!available) return null

    val versionLabel = latestVersionName
        ?.takeIf { it.isNotBlank() }
        ?.let { " $it" }
        .orEmpty()
    val message = when {
        updateDownloaded -> "Aktualizacja$versionLabel jest pobrana i gotowa do instalacji."
        required -> "Ta wersja aplikacji nie jest już obsługiwana. Zaktualizuj ją, aby kontynuować."
        else -> "Dostępna jest nowa wersja aplikacji$versionLabel."
    }

    return AppUpdateBannerUi(
        message = message,
        actionLabel = if (updateDownloaded) "Zainstaluj" else "Aktualizuj",
        required = required,
        downloaded = updateDownloaded,
    )
}

fun hasValidNavigationCoordinates(latitude: Double?, longitude: Double?): Boolean =
    latitude != null &&
        longitude != null &&
        latitude.isFinite() &&
        longitude.isFinite() &&
        latitude in -90.0..90.0 &&
        longitude in -180.0..180.0

fun navigationIntentCandidates(
    latitude: Double?,
    longitude: Double?,
    label: String?,
): List<NavigationIntentCandidate> {
    if (!hasValidNavigationCoordinates(latitude, longitude)) return emptyList()

    val coordinates = "$latitude,$longitude"
    val encodedLabel = URLEncoder
        .encode(label?.takeIf { it.isNotBlank() } ?: "Cel", Charsets.UTF_8.name())
        .replace("+", "%20")

    return listOf(
        NavigationIntentCandidate(
            uri = "google.navigation:q=$coordinates",
            packageName = googleMapsPackage,
        ),
        NavigationIntentCandidate(uri = "geo:0,0?q=$coordinates($encodedLabel)"),
        NavigationIntentCandidate(uri = "https://www.google.com/maps/dir/?api=1&destination=$coordinates"),
    )
}

enum class RouteFlowStepState {
    Todo,
    ServerSaved,
    InProgress,
    Syncing,
    Confirmed,
    NeedsAttention,
}

enum class RouteLifecycleAction {
    Start,
    Finish,
}

data class RouteFlowStepUi(
    val key: String,
    val title: String,
    val state: RouteFlowStepState,
    val stateLabel: String,
    val dateLabel: String? = null,
)

data class RouteLifecycleActionUi(
    val label: String,
    val action: RouteLifecycleAction,
)

data class RouteSyncCalloutUi(
    val text: String,
    val isConflict: Boolean,
    val canCorrect: Boolean,
)

fun canManageRoutePhotos(selectedDate: String, today: LocalDate = LocalDate.now()): Boolean =
    runCatching { LocalDate.parse(selectedDate).isEqual(today) }.getOrDefault(false)

fun routeIsActiveToday(
    route: DriverRouteDto,
    selectedDate: String,
    today: LocalDate = LocalDate.now(),
): Boolean {
    val viewedDate = runCatching { LocalDate.parse(selectedDate) }.getOrNull() ?: return false
    val loadingDate = route.routeDate?.let { runCatching { LocalDate.parse(it) }.getOrNull() } ?: return false
    val unloadingDate = route.unloadingDate
        ?.let { runCatching { LocalDate.parse(it) }.getOrNull() }
        ?: loadingDate

    return viewedDate == today && !today.isBefore(loadingDate) && !today.isAfter(unloadingDate)
}

fun routeUnloadingDate(route: DriverRouteDto): String? = route.unloadingDate ?: route.routeDate

fun routeDayOrdering(route: DriverRouteDto, date: String): Int =
    route.dayOrderings?.get(date) ?: route.ordering ?: Int.MAX_VALUE

fun routeScheduleDateLabel(date: String?): String =
    date
        ?.let { runCatching { LocalDate.parse(it).format(shortDateFormatter) }.getOrNull() }
        ?: "—"

fun routeScheduleSummary(route: DriverRouteDto): String {
    val loading = routeScheduleDateLabel(route.routeDate)
    val unloading = routeScheduleDateLabel(routeUnloadingDate(route))

    return if (route.unloadingDate == null) {
        "Załadunek: $loading · Rozładunek: ten sam dzień"
    } else {
        "Załadunek: $loading · Rozładunek: $unloading"
    }
}

fun routeTodayInstruction(
    route: DriverRouteDto,
    selectedDate: String,
    today: LocalDate = LocalDate.now(),
): String? {
    if (!routeIsActiveToday(route, selectedDate, today)) return null

    val loadingDate = route.routeDate?.let { runCatching { LocalDate.parse(it) }.getOrNull() } ?: return null
    val unloadingDate = routeUnloadingDate(route)?.let { runCatching { LocalDate.parse(it) }.getOrNull() } ?: loadingDate

    return when {
        route.driverLifecycleStatus() == "ZAKOŃCZONA" -> "Kurs zakończony."
        loadingDate == unloadingDate -> "Dziś: załadunek i rozładunek."
        today == loadingDate -> "Dziś: załadunek."
        today == unloadingDate && route.driverLifecycleStatus() == "ZAPLANOWANA" -> "Dziś: rozładunek. Załadunek możesz potwierdzić dzisiaj."
        today == unloadingDate -> "Dziś: rozładunek."
        else -> "Kurs jest w trasie. Rozładunek: ${routeScheduleDateLabel(routeUnloadingDate(route))}."
    }
}

fun canCompleteRouteFromDriverApp(
    route: DriverRouteDto,
    selectedDate: String,
    today: LocalDate = LocalDate.now(),
): Boolean =
    runCatching { LocalDate.parse(selectedDate) }.getOrNull() == today
        && routeUnloadingDate(route)?.let { runCatching { LocalDate.parse(it) }.getOrNull() } == today
        && route.driverLifecycleStatus() in setOf("ZAPLANOWANA", "W TRAKCIE")

fun canStartRouteFromDriverApp(
    route: DriverRouteDto,
    selectedDate: String,
    today: LocalDate = LocalDate.now(),
): Boolean =
    routeIsActiveToday(route, selectedDate, today)
        && route.driverLifecycleStatus() == "ZAPLANOWANA"

fun canFinishRouteFromDriverApp(
    route: DriverRouteDto,
    selectedDate: String,
    today: LocalDate = LocalDate.now(),
): Boolean =
    route.driverLifecycleStatus() == "W TRAKCIE"
        && runCatching { LocalDate.parse(selectedDate) }.getOrNull() == today
        && routeUnloadingDate(route)?.let { runCatching { LocalDate.parse(it) }.getOrNull() } == today

object RouteStageRequirement {
    const val Required = "required"
    const val Optional = "optional"
    const val Disabled = "disabled"
}

fun normalizeRouteStageRequirement(value: String?): String = when (value) {
    RouteStageRequirement.Optional -> RouteStageRequirement.Optional
    RouteStageRequirement.Disabled -> RouteStageRequirement.Disabled
    else -> RouteStageRequirement.Required
}

fun routeStageRequirementIsVisible(requirement: String): Boolean =
    normalizeRouteStageRequirement(requirement) != RouteStageRequirement.Disabled

fun routeStageRequirementIsRequired(requirement: String): Boolean =
    normalizeRouteStageRequirement(requirement) == RouteStageRequirement.Required

fun parseRouteWeightTons(weightText: String): Double? {
    val enteredWeight = weightText.trim().replace(',', '.').toBigDecimalOrNull() ?: return null
    val tons = if (enteredWeight.abs() >= routeWeightKilogramInputThreshold) {
        enteredWeight.movePointLeft(3)
    } else {
        enteredWeight
    }

    return tons.setScale(3, RoundingMode.HALF_UP).toDouble()
}

fun routeWeightInputHelpText(weightText: String): String {
    val enteredWeight = weightText.trim().replace(',', '.').toDoubleOrNull()
    val tons = parseRouteWeightTons(weightText)
    val enteredAsKilograms = enteredWeight != null &&
        abs(enteredWeight) >= routeWeightKilogramInputThreshold.toDouble() &&
        tons != null

    return if (enteredAsKilograms) {
        "Wpisano kilogramy — zapiszemy ${String.format(Locale("pl", "PL"), "%.3f", tons)} t."
    } else {
        "Wpisz tony (np. 23,4). Wartości od 1000 rozpoznajemy jako kilogramy (np. 23400)."
    }
}

fun canSubmitRouteStageForm(
    weightText: String,
    serverPhotoCount: Int,
    localUploadCount: Int,
    weightRequirement: String = RouteStageRequirement.Required,
    photoRequirement: String = RouteStageRequirement.Required,
): Boolean {
    return routeStageSubmitBlocker(
        weightText,
        serverPhotoCount,
        localUploadCount,
        weightRequirement,
        photoRequirement,
    ) == null
}

fun loadingPhotosSubmitBlocker(
    serverPhotoCount: Int,
    localUploadCount: Int,
    photoRequirement: String = RouteStageRequirement.Required,
): String? =
    if (
        routeStageRequirementIsRequired(photoRequirement) &&
        visiblePhotoAttachmentCount(serverPhotoCount, localUploadCount) <= 0
    ) {
        "Dodaj co najmniej jedno zdjęcie załadunku."
    } else {
        null
    }

fun loadingWeightSubmitBlocker(
    weightText: String,
    serverPhotoCount: Int,
    localUploadCount: Int,
    weightRequirement: String = RouteStageRequirement.Required,
    photoRequirement: String = RouteStageRequirement.Required,
): String? {
    val trimmed = weightText.trim()
    val normalizedWeight = parseRouteWeightTons(trimmed)
    val weightVisible = routeStageRequirementIsVisible(weightRequirement)

    return when {
        weightVisible && routeStageRequirementIsRequired(weightRequirement) && trimmed.isBlank() -> "Podaj tonaż."
        weightVisible && trimmed.isNotBlank() && normalizedWeight == null -> "Podaj poprawny tonaż."
        weightVisible && normalizedWeight != null && normalizedWeight <= 0.0 -> "Tonaż musi być większy od zera."
        weightVisible && normalizedWeight != null && normalizedWeight > routeWeightMaxTons -> "Tonaż nie może przekraczać 999,999 t."
        loadingPhotosSubmitBlocker(serverPhotoCount, localUploadCount, photoRequirement) != null -> "Dodaj co najmniej jedno zdjęcie załadunku."
        else -> null
    }
}

fun routeStageSubmitBlocker(
    weightText: String,
    serverPhotoCount: Int,
    localUploadCount: Int,
    weightRequirement: String = RouteStageRequirement.Required,
    photoRequirement: String = RouteStageRequirement.Required,
): String? {
    val trimmed = weightText.trim()
    val normalizedWeight = parseRouteWeightTons(trimmed)
    val weightVisible = routeStageRequirementIsVisible(weightRequirement)

    return when {
        weightVisible && routeStageRequirementIsRequired(weightRequirement) && trimmed.isBlank() -> "Podaj tonaż."
        weightVisible && trimmed.isNotBlank() && normalizedWeight == null -> "Podaj poprawny tonaż."
        weightVisible && normalizedWeight != null && normalizedWeight <= 0.0 -> "Tonaż musi być większy od zera."
        weightVisible && normalizedWeight != null && normalizedWeight > routeWeightMaxTons -> "Tonaż nie może przekraczać 999,999 t."
        routeStageRequirementIsRequired(photoRequirement) && visiblePhotoAttachmentCount(serverPhotoCount, localUploadCount) <= 0 -> "Dodaj co najmniej jedno zdjęcie etapu."
        else -> null
    }
}

fun routeLifecyclePrimaryAction(
    route: DriverRouteDto,
    selectedDate: String,
    today: LocalDate = LocalDate.now(),
): RouteLifecycleActionUi? =
    when {
        canStartRouteFromDriverApp(route, selectedDate, today) -> RouteLifecycleActionUi("Załaduj", RouteLifecycleAction.Start)
        canFinishRouteFromDriverApp(route, selectedDate, today) -> RouteLifecycleActionUi("Rozładuj", RouteLifecycleAction.Finish)
        else -> null
    }

fun routeSyncCallout(actions: List<RouteActionEntity>): RouteSyncCalloutUi? {
    val unresolved = actions
        .filter { it.status in routeActionVisibleStatuses }
        .maxByOrNull { it.createdAtEpochMillis }
        ?: return null
    val isConflict = routeActionNeedsAttention(unresolved.status)

    return RouteSyncCalloutUi(
        text = if (isConflict) {
            "Kurs wymaga obsługi. Dane wpisane w telefonie zostały zachowane."
        } else {
            when (unresolved.status) {
                RouteActionStatus.WaitingForPhotos -> "Czekam na wysłanie zdjęć etapu, potem wyślę zmianę statusu."
                RouteActionStatus.Pending,
                RouteActionStatus.Syncing -> "Trwa wysyłanie zmiany statusu kursu."
                RouteActionStatus.FailedRetryable -> "Nie udało się wysłać. Aplikacja spróbuje ponownie."
                else -> "Zmiana zapisana w telefonie. Aplikacja wyśle ją automatycznie."
            }
        },
        isConflict = isConflict,
        canCorrect = unresolved.status == RouteActionStatus.FailedPermanent,
    )
}

fun routeFlowSteps(
    route: DriverRouteDto,
    actions: List<RouteActionEntity>,
    loadingPhotoRequirement: String = RouteStageRequirement.Required,
    loadingWeightRequirement: String = RouteStageRequirement.Required,
): List<RouteFlowStepUi> {
    val startOverride = routeActionStepState(actions, RouteActionType.Start)
    val finishOverride = routeActionStepState(actions, RouteActionType.Finish)
    val loadingPhotoCount = routePhotosForStage(route.photos, "loading").size
    val driverStatus = route.driverLifecycleStatus()
    val loadingPhotosState = when {
        startOverride != null -> startOverride
        driverStatus == "ZAKOŃCZONA" -> RouteFlowStepState.Confirmed
        driverStatus == "W TRAKCIE" -> RouteFlowStepState.Confirmed
        loadingPhotoCount > 0 -> RouteFlowStepState.ServerSaved
        else -> RouteFlowStepState.Todo
    }
    val loadingWeightState = startOverride ?: when {
        driverStatus == "ZAKOŃCZONA" -> RouteFlowStepState.Confirmed
        driverStatus == "W TRAKCIE" -> RouteFlowStepState.Confirmed
        route.loadingWeight != null -> RouteFlowStepState.ServerSaved
        else -> RouteFlowStepState.Todo
    }
    val transitState = when {
        finishOverride != null -> RouteFlowStepState.InProgress
        driverStatus == "ZAKOŃCZONA" -> RouteFlowStepState.Confirmed
        startOverride != null -> startOverride
        driverStatus == "W TRAKCIE" -> RouteFlowStepState.InProgress
        else -> RouteFlowStepState.Todo
    }
    val unloadingState = finishOverride ?: when {
        driverStatus == "ZAKOŃCZONA" -> RouteFlowStepState.Confirmed
        route.unloadingWeight != null -> RouteFlowStepState.ServerSaved
        else -> RouteFlowStepState.Todo
    }

    return buildList {
        if (routeStageRequirementIsVisible(loadingPhotoRequirement)) {
            add(RouteFlowStepUi("loading_photos", "Zdjęcia załadunku", loadingPhotosState, routeFlowStateLabel(loadingPhotosState), routeScheduleDateLabel(route.routeDate)))
        }
        if (routeStageRequirementIsVisible(loadingWeightRequirement)) {
            add(RouteFlowStepUi("loading_weight", "Tonaż załadunku", loadingWeightState, routeFlowStateLabel(loadingWeightState), routeScheduleDateLabel(route.routeDate)))
        }
        add(RouteFlowStepUi("transit", "W trasie", transitState, routeFlowStateLabel(transitState)))
        add(RouteFlowStepUi("unloading", "Rozładunek", unloadingState, routeFlowStateLabel(unloadingState), routeScheduleDateLabel(routeUnloadingDate(route))))
    }
}

fun routePhotosForStage(photos: List<RoutePhotoDto>?, stage: String): List<RoutePhotoDto> {
    val normalized = normalizedRoutePhotoStage(stage)
    return photos.orEmpty().filter { normalizedRoutePhotoStage(it.stage) == normalized }
}

fun normalizedRoutePhotoStage(stage: String?): String =
    when (stage) {
        "loading", "unloading" -> stage
        else -> "other"
    }

fun activeRouteUploadIntervalSeconds(
    isMoving: Boolean,
    isUrbanOrTurning: Boolean = false,
    isNearDestination: Boolean = false,
    isHighwayStable: Boolean = false,
    isLikelyOffRoute: Boolean = false,
): Int =
    when {
        isLikelyOffRoute -> 3
        !isMoving -> 45
        isUrbanOrTurning || isNearDestination -> 5
        isHighwayStable -> 20
        else -> 20
    }

fun activeRouteNotificationTarget(arrived: Boolean): DriverScreen =
    if (arrived) DriverScreen.FinishRoute else DriverScreen.Detail

private fun routeActionStepState(actions: List<RouteActionEntity>, actionType: String): RouteFlowStepState? {
    val action = actions
        .filter { it.action == actionType && it.status in routeActionVisibleStatuses }
        .maxByOrNull { it.createdAtEpochMillis }
        ?: return null

    return when (action.status) {
        RouteActionStatus.FailedConflict,
        RouteActionStatus.FailedPermanent -> RouteFlowStepState.NeedsAttention
        else -> RouteFlowStepState.Syncing
    }
}

private fun routeFlowStateLabel(state: RouteFlowStepState): String =
    when (state) {
        RouteFlowStepState.Todo -> "do zrobienia"
        RouteFlowStepState.ServerSaved -> "zapisane na serwerze"
        RouteFlowStepState.InProgress -> "w trakcie"
        RouteFlowStepState.Syncing -> "synchronizacja"
        RouteFlowStepState.Confirmed -> "potwierdzone"
        RouteFlowStepState.NeedsAttention -> "wymaga uwagi"
    }

fun shouldShowRouteDayLiveUpdate(viewedDate: String, hintDate: String?): Boolean =
    viewedDate.isNotBlank() && (hintDate == null || hintDate == viewedDate)

fun routeDateChipLabel(routeDate: String?, today: LocalDate = LocalDate.now()): String {
    val date = routeDate
        ?.takeIf { it.isNotBlank() }
        ?.let { runCatching { LocalDate.parse(it) }.getOrNull() }
        ?: return "—"

    val fallback = date.format(shortDateFormatter)
    val days = ChronoUnit.DAYS.between(today, date)
    val human = when {
        days == 0L -> "dzisiaj"
        days == 1L -> "jutro"
        days in 2L..7L -> polishWeekdayPhrase(date.dayOfWeek)
        else -> null
    }

    return human?.let { "$it · $fallback" } ?: fallback
}

private fun polishWeekdayPhrase(day: DayOfWeek): String =
    when (day) {
        DayOfWeek.MONDAY -> "w poniedziałek"
        DayOfWeek.TUESDAY -> "we wtorek"
        DayOfWeek.WEDNESDAY -> "w środę"
        DayOfWeek.THURSDAY -> "w czwartek"
        DayOfWeek.FRIDAY -> "w piątek"
        DayOfWeek.SATURDAY -> "w sobotę"
        DayOfWeek.SUNDAY -> "w niedzielę"
    }

fun inlinePhotoGridRows(photoCount: Int): Int =
    if (photoCount <= 0) 0 else (photoCount + 1) / 2

fun shouldShowPhotoPreviewLoadingState(hasPhoto: Boolean, isLoading: Boolean, isError: Boolean): Boolean =
    hasPhoto && isLoading && !isError

fun shouldShowPhotoPreviewErrorState(hasPhoto: Boolean, isError: Boolean): Boolean =
    hasPhoto && isError

fun shouldShowPhotoTileLoadingState(hasPhoto: Boolean, isLoading: Boolean, isError: Boolean): Boolean =
    hasPhoto && isLoading && !isError

fun shouldShowPhotoTileErrorState(hasPhoto: Boolean, isError: Boolean): Boolean =
    hasPhoto && isError

fun authBrandBannerHeightDp(screenHeightDp: Int): Int =
    when {
        screenHeightDp < 640 -> 118
        screenHeightDp < 740 -> 150
        else -> 190
    }

fun appBrandHeaderHeightDp(screenHeightDp: Int): Int =
    when {
        screenHeightDp < 640 -> 92
        screenHeightDp < 740 -> 108
        else -> 124
    }

fun bottomNavigationHeightDp(screenHeightDp: Int): Int =
    when {
        screenHeightDp < 640 -> 72
        screenHeightDp < 740 -> 78
        else -> 82
    }

fun screenHorizontalPaddingDp(screenWidthDp: Int): Int =
    when {
        screenWidthDp < 360 -> 14
        screenWidthDp < 420 -> 16
        else -> 20
    }

fun phonePrefixWidthDp(screenWidthDp: Int): Int =
    when {
        screenWidthDp < 360 -> 70
        screenWidthDp < 420 -> 82
        else -> 94
    }

fun routeFactColumns(screenWidthDp: Int): Int =
    if (screenWidthDp < 360) 1 else 2

fun visiblePhotoAttachmentCount(serverPhotoCount: Int, localUploadCount: Int): Int =
    serverPhotoCount.coerceAtLeast(0) + localUploadCount.coerceAtLeast(0)

fun photoUploadCanSatisfyStage(status: String): Boolean =
    status in setOf(
        PhotoUploadStatus.Pending.storageValue,
        PhotoUploadStatus.Uploading.storageValue,
        PhotoUploadStatus.Verifying.storageValue,
        PhotoUploadStatus.Confirmed.storageValue,
        PhotoUploadStatus.FailedRetryable.storageValue,
    )

fun routeStageUsableUploads(uploads: List<PhotoUploadEntity>): List<PhotoUploadEntity> =
    uploads.filter { photoUploadCanSatisfyStage(it.status) }

fun queuedPhotoUploadCount(statuses: List<String>): Int =
    statuses.count { status -> status != "CONFIRMED" && status != "CANCELLED" }

fun routePhotoSortEpochMillis(createdAt: String?, takenAt: String?, fallback: Long): Long =
    parseIsoOffsetEpochMillis(createdAt)
        ?: parseIsoOffsetEpochMillis(takenAt)
        ?: fallback

fun confirmedUploadServerPhotoId(status: String, serverPhotoId: String?): String? =
    serverPhotoId?.takeIf { status == "CONFIRMED" && it.isNotBlank() }

fun canRetryPhotoUpload(status: String, lastError: String? = null): Boolean =
    status == PhotoUploadStatus.FailedRetryable.storageValue ||
        (status == PhotoUploadStatus.FailedPermanent.storageValue && isWorkerCancellationFailure(lastError))

fun canDiscardLocalPhotoUpload(status: String, serverPhotoId: String?): Boolean =
    serverPhotoId.isNullOrBlank() && status in setOf(
        PhotoUploadStatus.FailedRetryable.storageValue,
        PhotoUploadStatus.FailedPermanent.storageValue,
    )

fun canShowPhotoUploadFailureDetails(status: String, lastError: String?): Boolean =
    status in setOf("FAILED_RETRYABLE", "FAILED_PERMANENT") && !lastError.isNullOrBlank()

fun localUploadPreviewPhoto(upload: PhotoUploadEntity): RoutePhotoDto? {
    if (upload.localPath.isBlank()) return null

    return RoutePhotoDto(
        id = upload.clientRequestId,
        routeId = upload.routeId,
        clientRequestId = upload.clientRequestId,
        contentSha256 = upload.contentSha256,
        source = upload.source,
        stage = normalizedRoutePhotoStage(upload.stage),
        mimeType = upload.mimeType,
        size = upload.size,
        url = File(upload.localPath).toURI().toString(),
        takenAt = upload.takenAt,
        latitude = upload.latitude,
        longitude = upload.longitude,
        locationAccuracyMeters = upload.locationAccuracyMeters,
        createdAt = null,
        canDelete = confirmedUploadServerPhotoId(upload.status, upload.serverPhotoId) != null,
        deleteUntil = null,
    )
}

fun routeScreenAfterServerRefresh(currentScreen: DriverScreen, route: DriverRouteDto): DriverScreen =
    when {
        currentScreen in setOf(DriverScreen.StartRoute, DriverScreen.LoadingWeight) &&
            route.driverLifecycleStatus() in setOf("W TRAKCIE", "ZAKOŃCZONA") -> DriverScreen.Detail
        currentScreen == DriverScreen.FinishRoute && route.driverLifecycleStatus() == "ZAKOŃCZONA" -> DriverScreen.Detail
        else -> currentScreen
    }

fun canLaunchCameraWithLocationPolicy(requirePreciseLocation: Boolean, hasFineLocation: Boolean): Boolean =
    !requirePreciseLocation || hasFineLocation

fun shouldShowDispatchSheetReminderCard(reminder: DispatchSheetReminderDto?): Boolean =
    reminder?.dueToday == true && reminder.status in setOf("missing", "uploaded")

fun dispatchSheetPrimaryActionLabel(reminder: DispatchSheetReminderDto?, hasQueuedUpload: Boolean): String =
    when {
        hasQueuedUpload -> "Wysyłam zdjęcie..."
        reminder?.status == "uploaded" -> "Popraw"
        else -> "Zrób zdjęcie"
    }

fun offlineStaleBannerMessage(isOnline: Boolean, isStale: Boolean, syncLabel: String): String? =
    when {
        isOnline && !isStale -> null
        !isOnline && syncLabel == "brak zapisanej synchronizacji" ->
            "Brak połączenia z serwerem. Sprawdź internet i spróbuj ponownie."
        !isOnline -> "Brak połączenia z serwerem. Dane z $syncLabel mogą być nieaktualne."
        else -> "Dane mogą być nieaktualne. Ostatnia synchronizacja: $syncLabel."
    }

fun connectionStatusBannerMessage(
    isOnline: Boolean,
    isStale: Boolean,
    serverConnectionState: ServerConnectionState,
    showRealtimeReconnecting: Boolean,
    syncLabel: String,
): String? = when {
    !isOnline -> offlineStaleBannerMessage(false, isStale, syncLabel)
    serverConnectionState == ServerConnectionState.Degraded ->
        "Internet działa, ale serwer jest chwilowo niedostępny. Pokazujemy dane z $syncLabel."
    showRealtimeReconnecting -> "Ponowne łączenie. Dane są uzgadniane z serwerem."
    isStale -> offlineStaleBannerMessage(true, true, syncLabel)
    else -> null
}

private fun parseIsoOffsetEpochMillis(value: String?): Long? =
    value?.takeIf { it.isNotBlank() }?.let {
        runCatching { OffsetDateTime.parse(it).toInstant().toEpochMilli() }.getOrNull()
    }
