package pl.firmatpp.kierowca.ui

import android.Manifest
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AddPhotoAlternate
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.CameraAlt
import androidx.compose.material.icons.outlined.CalendarToday
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.CloudUpload
import androidx.compose.material.icons.outlined.Factory
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.LocationOn
import androidx.compose.material.icons.outlined.LocalShipping
import androidx.compose.material.icons.outlined.Navigation
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.Phone
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.foundation.verticalScroll
import androidx.core.content.ContextCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.FileProvider
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
import java.io.File
import java.time.Instant
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.YearMonth
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import java.util.Locale
import pl.firmatpp.kierowca.R
import pl.firmatpp.kierowca.data.PhotoUploadMetadata
import pl.firmatpp.kierowca.data.model.DispatchSheetReminderDto
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.NavigationPointDto
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
import pl.firmatpp.kierowca.data.upload.DispatchSheetUploadEntity
import pl.firmatpp.kierowca.data.upload.PhotoUploadEntity
import pl.firmatpp.kierowca.data.upload.PhotoUploadStatus
import pl.firmatpp.kierowca.data.upload.RouteActionEntity
import pl.firmatpp.kierowca.domain.OtpCodeExtractor
import pl.firmatpp.kierowca.domain.RouteDisplayMapper
import pl.firmatpp.kierowca.ui.theme.AppThemeMode
import pl.firmatpp.kierowca.ui.theme.TppTheme

@Composable
fun DriverApp(
    viewModel: DriverViewModel,
    initialRouteId: String? = null,
    initialRouteTarget: String? = null,
    initialLeaveRequestId: String? = null,
) {
    val state by viewModel.state.collectAsState()
    val lifecycleOwner = LocalLifecycleOwner.current
    val context = LocalContext.current
    var appInForeground by remember { mutableStateOf(true) }
    val notificationPermissionLauncher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestPermission(),
    ) { granted ->
        if (granted) {
            viewModel.updateNewRouteNotifications(true)
        } else {
            viewModel.markNotificationPermissionDenied()
        }
    }

    BackHandler(enabled = state.screen != DriverScreen.Initializing && state.screen != DriverScreen.Phone && state.screen != DriverScreen.Routes) {
        viewModel.back()
    }
    DisposableEffect(lifecycleOwner, state.screen) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_START -> {
                    appInForeground = true
                    viewModel.onAppForegrounded()
                }
                Lifecycle.Event.ON_STOP -> appInForeground = false
                else -> Unit
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
    }
    LaunchedEffect(state.screen, state.selectedDate, state.autoRefreshSeconds, appInForeground) {
        if (state.screen != DriverScreen.Routes || !appInForeground) return@LaunchedEffect

        val intervalMillis = state.autoRefreshSeconds.coerceIn(30, 600) * 1000L
        while (true) {
            delay(intervalMillis)
            viewModel.refreshRoutesSilently()
        }
    }
    SmsUserConsentEffect(
        enabled = state.screen == DriverScreen.Phone || state.screen == DriverScreen.Otp,
        onCode = viewModel::updateOtpCode,
    )
    LaunchedEffect(initialRouteId, initialRouteTarget) {
        viewModel.openRouteFromNotification(initialRouteId, initialRouteTarget)
    }
    LaunchedEffect(initialLeaveRequestId, state.leaveRequestsConfig) {
        if (!initialLeaveRequestId.isNullOrBlank() && DriverLeaveRequestUiRules.isFeatureVisible(state.leaveRequestsConfig)) {
            viewModel.openLeaveRequest(initialLeaveRequestId)
        }
    }

    Box(Modifier.fillMaxSize().background(TppTheme.colors.surface)) {
        when (state.screen) {
            DriverScreen.Initializing -> StartupScreen()
            DriverScreen.Phone -> PhoneScreen(state, viewModel::requestOtp)
            DriverScreen.Otp -> OtpScreen(state, viewModel::updateOtpCode, viewModel::verifyOtp, viewModel::back)
            DriverScreen.Routes -> RoutesScreen(
                state = state,
                onRefresh = viewModel::refreshRoutesSilently,
                onDate = viewModel::selectRouteDate,
                onProfile = viewModel::openProfile,
                onPhotoQueue = viewModel::openPhotoQueue,
                onDispatchSheetUpload = viewModel::uploadDispatchSheetPhoto,
                onRoute = viewModel::openRoute,
                onDismissLiveUpdate = viewModel::dismissRouteDayLiveUpdate,
            )
            DriverScreen.Profile -> ProfileScreen(
                state = state,
                onRoutes = viewModel::refreshRoutes,
                onProfile = viewModel::openProfile,
                onLogout = viewModel::logout,
                onLeaveRequests = viewModel::openLeaveRequests,
                onNewRouteNotificationsChanged = { enabled ->
                    if (!enabled) {
                        viewModel.updateNewRouteNotifications(false)
                    } else if (canPostNotifications(context)) {
                        viewModel.updateNewRouteNotifications(true)
                    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasPostNotificationsRuntimePermission(context)) {
                        notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
                    } else {
                        viewModel.markNotificationPermissionDenied()
                    }
                },
                onOpenNotificationSettings = { openAppNotificationSettings(context) },
                onThemeModeChanged = viewModel::setThemeMode,
                onRouteProgressNotificationChanged = viewModel::setRouteProgressNotificationEnabled,
            )
            DriverScreen.Detail -> DetailScreen(
                state,
                viewModel::back,
                viewModel::openPhoto,
                viewModel::refreshSelectedRoute,
                viewModel::openStartRoute,
                viewModel::openFinishRoute,
            )
            DriverScreen.StartRoute -> RouteStageScreen(
                state = state,
                title = "Zdjęcia załadunku",
                stage = "loading",
                weightLabel = "Waga na załadunku",
                submitLabel = "Gotowe, jadę na wagę",
                showWeightInput = false,
                showPhotoActions = true,
                onBack = viewModel::back,
                onWeightChange = viewModel::updateRouteStageWeight,
                onUpload = { uri, source, metadata -> viewModel.uploadPhoto(uri, source, metadata, "loading") },
                onPhoto = viewModel::openPhoto,
                onDeleteUpload = viewModel::deleteConfirmedUpload,
                onRetryUpload = viewModel::retryPhotoUpload,
                onSubmit = viewModel::openLoadingWeight,
            )
            DriverScreen.LoadingWeight -> RouteStageScreen(
                state = state,
                title = "Waga załadunku",
                stage = "loading",
                weightLabel = "Waga z wagi",
                submitLabel = "Rozpocznij kurs",
                showWeightInput = true,
                showPhotoActions = false,
                showPhotoGrid = false,
                onBack = viewModel::back,
                onWeightChange = viewModel::updateRouteStageWeight,
                onUpload = { _, _, _ -> },
                onPhoto = viewModel::openPhoto,
                onDeleteUpload = viewModel::deleteConfirmedUpload,
                onRetryUpload = viewModel::retryPhotoUpload,
                onSubmit = viewModel::submitStartRoute,
            )
            DriverScreen.FinishRoute -> RouteStageScreen(
                state = state,
                title = "Zakończ kurs",
                stage = "unloading",
                weightLabel = "Waga na rozładunku",
                submitLabel = "Zakończ kurs",
                onBack = viewModel::back,
                onWeightChange = viewModel::updateRouteStageWeight,
                onUpload = { uri, source, metadata -> viewModel.uploadPhoto(uri, source, metadata, "unloading") },
                onPhoto = viewModel::openPhoto,
                onDeleteUpload = viewModel::deleteConfirmedUpload,
                onRetryUpload = viewModel::retryPhotoUpload,
                onSubmit = viewModel::submitFinishRoute,
            )
            DriverScreen.Photo -> PhotoScreen(state, viewModel::back)
            DriverScreen.PhotoQueue -> PhotoQueueScreen(
                state = state,
                onBack = viewModel::back,
                onRetryUpload = viewModel::retryPhotoUpload,
            )
            DriverScreen.LeaveRequests -> LeaveRequestsScreen(
                state = state,
                onBack = viewModel::back,
                onRefresh = viewModel::refreshLeaveRequests,
                onAdd = viewModel::openAddLeaveRequest,
                onOpen = viewModel::openLeaveRequest,
            )
            DriverScreen.LeaveRequestDetail -> LeaveRequestDetailScreen(
                state = state,
                onBack = viewModel::back,
                onCancel = viewModel::cancelSelectedLeaveRequest,
            )
            DriverScreen.LeaveCalendar -> LeaveCalendarScreen(
                state = state,
                onBack = viewModel::back,
                onDateSelected = viewModel::selectLeaveCalendarDate,
                onContinue = viewModel::continueAddLeaveRequestFromCalendar,
                onLoadMore = { viewModel.loadMoreLeaveCalendarMonths(reset = false) },
            )
            DriverScreen.AddLeaveRequest -> AddLeaveRequestScreen(
                state = state,
                onBack = viewModel::back,
                onDraft = viewModel::updateLeaveRequestDraft,
                onChangePeriod = viewModel::reopenLeaveCalendarFromDraft,
                onSubmit = viewModel::submitLeaveRequest,
            )
        }

        if (state.loading && state.screen != DriverScreen.Initializing) {
            Box(Modifier.fillMaxSize().background(Color.White.copy(alpha = 0.42f)), contentAlignment = Alignment.Center) {
                CircularProgressIndicator(color = TppTheme.colors.forest)
            }
        }
    }
}

@Composable
private fun StartupScreen() {
    BoxWithConstraints(
        Modifier
            .fillMaxSize()
            .navigationBarsPadding()
            .background(TppTheme.colors.surface),
    ) {
        val bannerHeight = authBrandBannerHeightDp(maxHeight.value.toInt()).dp
        val horizontalPadding = screenHorizontalPaddingDp(maxWidth.value.toInt()).dp
        Column(
            Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            Column(Modifier.fillMaxWidth().statusBarsPadding()) {
                BrandBannerHeader(height = bannerHeight)
            }
            Column(
                Modifier
                    .weight(1f)
                    .fillMaxWidth()
                    .padding(horizontal = horizontalPadding),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center,
            ) {
                CircularProgressIndicator(color = TppTheme.colors.forest)
                Spacer(Modifier.height(20.dp))
                Text(
                    "Sprawdzanie sesji",
                    color = TppTheme.colors.muted,
                    fontFamily = FontFamily.Monospace,
                    fontWeight = FontWeight.SemiBold,
                )
            }
        }
    }
}

@Composable
private fun PhoneScreen(state: DriverUiState, onSubmit: (String) -> Unit) {
    var phoneDigits by remember(state.phone) { mutableStateOf(polishPhoneDigits(state.phone)) }
    BoxWithConstraints(
        Modifier
            .fillMaxSize()
            .background(TppTheme.colors.surface)
            .navigationBarsPadding(),
    ) {
        val screenWidthDp = maxWidth.value.toInt()
        val screenHeightDp = maxHeight.value.toInt()
        val horizontalPadding = screenHorizontalPaddingDp(screenWidthDp).dp
        val bannerHeight = authBrandBannerHeightDp(screenHeightDp).dp
        val formTopPadding = if (screenHeightDp < 640) 18.dp else 28.dp
        val cardPadding = if (screenWidthDp < 360) 16.dp else 20.dp

        Column(
            Modifier
                .fillMaxSize(),
        ) {
            Column(Modifier.fillMaxWidth().statusBarsPadding()) {
                BrandBannerHeader(height = bannerHeight)
            }
            Column(
                Modifier
                    .fillMaxSize()
                    .verticalScroll(rememberScrollState())
                    .padding(start = horizontalPadding, top = formTopPadding, end = horizontalPadding, bottom = 28.dp),
                horizontalAlignment = Alignment.Start,
            ) {
                Text(
                    "Firma TPP - Kierowca",
                    color = TppTheme.colors.forest,
                    fontFamily = FontFamily.Monospace,
                    fontWeight = FontWeight.Bold,
                    fontSize = 14.sp,
                )
                Spacer(Modifier.height(10.dp))
                Text(
                    "Zaloguj się",
                    style = MaterialTheme.typography.headlineLarge,
                    color = TppTheme.colors.ink,
                    fontWeight = FontWeight.Bold,
                )
                Spacer(Modifier.height(8.dp))
                Text(
                    "Wpisz numer telefonu kierowcy. Kod OTP wyślemy SMS-em.",
                    color = TppTheme.colors.muted,
                    fontSize = 17.sp,
                    fontWeight = FontWeight.Medium,
                )
                Spacer(Modifier.height(if (screenHeightDp < 640) 18.dp else 28.dp))
                Card(
                    modifier = Modifier.fillMaxWidth(),
                    colors = CardDefaults.cardColors(containerColor = Color(0xFFFEFFFC)),
                    border = BorderStroke(1.dp, TppTheme.colors.outline.copy(alpha = 0.72f)),
                    shape = MaterialTheme.shapes.medium,
                ) {
                    Column(Modifier.fillMaxWidth().padding(cardPadding), verticalArrangement = Arrangement.spacedBy(18.dp)) {
                        Text(
                            "Numer telefonu",
                            color = TppTheme.colors.ink,
                            fontFamily = FontFamily.Monospace,
                            fontWeight = FontWeight.Bold,
                            fontSize = 18.sp,
                        )
                        PhoneNumberField(
                            digits = phoneDigits,
                            screenWidthDp = screenWidthDp,
                            onDigitsChange = { phoneDigits = polishPhoneDigits(it) },
                        )
                        Text(
                            "Dostęp tylko dla upoważnionych kierowców.",
                            color = TppTheme.colors.muted,
                            fontWeight = FontWeight.SemiBold,
                            fontSize = 15.sp,
                        )
                        ErrorText(state.error)
                        Button(
                            onClick = { onSubmit("+48$phoneDigits") },
                            modifier = Modifier
                                .fillMaxWidth()
                                .height(64.dp),
                            colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.containerGreen),
                            shape = MaterialTheme.shapes.small,
                            contentPadding = PaddingValues(horizontal = 20.dp),
                        ) {
                            Text(
                                "Wyślij kod OTP",
                                fontFamily = FontFamily.Monospace,
                                fontWeight = FontWeight.Bold,
                                fontSize = 20.sp,
                            )
                        }
                    }
                }
            }
        }
    }
}

@Composable
private fun BrandBannerHeader(height: Dp) {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .height(height)
            .background(Color(0xFFFAFCF1)),
    ) {
        Image(
            painter = painterResource(R.drawable.tpp_banner),
            contentDescription = "Firma TPP",
            contentScale = ContentScale.Crop,
            modifier = Modifier.matchParentSize(),
        )
        Box(
            Modifier
                .matchParentSize()
                .background(
                    Brush.horizontalGradient(
                        0f to Color(0xFFFAFCF1).copy(alpha = 0.56f),
                        0.18f to Color.Transparent,
                        0.82f to Color.Transparent,
                        1f to Color(0xFFFAFCF1).copy(alpha = 0.56f),
                    ),
                ),
        )
        Box(
            Modifier
                .matchParentSize()
                .background(
                    Brush.verticalGradient(
                        0f to Color.Transparent,
                        0.72f to Color.Transparent,
                        1f to TppTheme.colors.surface.copy(alpha = 0.88f),
                    ),
                ),
        )
    }
}

@Composable
private fun PhoneNumberField(digits: String, screenWidthDp: Int, onDigitsChange: (String) -> Unit) {
    val fieldHeight = if (screenWidthDp < 360) 68.dp else if (screenWidthDp < 420) 76.dp else 82.dp
    val prefixWidth = phonePrefixWidthDp(screenWidthDp).dp
    val prefixFontSize = if (screenWidthDp < 360) 20.sp else if (screenWidthDp < 420) 23.sp else 26.sp
    val inputFontSize = if (screenWidthDp < 360) 22.sp else if (screenWidthDp < 420) 25.sp else 28.sp
    val inputPadding = if (screenWidthDp < 360) 14.dp else if (screenWidthDp < 420) 18.dp else 24.dp

    Row(
        modifier = Modifier
            .fillMaxWidth()
            .height(fieldHeight)
            .background(Color(0xFFFEFFFC), RoundedCornerShape(2.dp))
            .border(BorderStroke(1.dp, TppTheme.colors.outline), RoundedCornerShape(2.dp)),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Box(
            Modifier
                .width(prefixWidth)
                .fillMaxHeight()
                .background(TppTheme.colors.panel),
            contentAlignment = Alignment.Center,
        ) {
            Text("+48", color = TppTheme.colors.ink, fontSize = prefixFontSize)
        }
        Box(Modifier.width(1.dp).fillMaxHeight().background(TppTheme.colors.outline))
        BasicTextField(
            value = digits,
            onValueChange = { onDigitsChange(it) },
            singleLine = true,
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
            visualTransformation = PolishPhoneNumberVisualTransformation,
            textStyle = TextStyle(
                color = TppTheme.colors.muted,
                fontSize = inputFontSize,
                fontWeight = FontWeight.Medium,
            ),
            modifier = Modifier
                .weight(1f)
                .padding(horizontal = inputPadding),
            decorationBox = { innerTextField ->
                Box(contentAlignment = Alignment.CenterStart) {
                    if (digits.isBlank()) {
                        Text(
                            "000 000 000",
                            color = TppTheme.colors.muted.copy(alpha = 0.92f),
                            fontSize = inputFontSize,
                            fontWeight = FontWeight.Medium,
                        )
                    }
                    innerTextField()
                }
            },
        )
    }
}

@Composable
private fun OtpScreen(
    state: DriverUiState,
    onCodeChange: (String) -> Unit,
    onSubmit: (String) -> Unit,
    onBack: () -> Unit,
) {
    AuthShell(title = "Kod SMS", subtitle = "Wpisz kod wyslany na ${state.maskedPhone}.", onBack = onBack) {
        OutlinedTextField(
            value = state.otpCode,
            onValueChange = { onCodeChange(it) },
            label = { Text("Kod OTP") },
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
            modifier = Modifier.fillMaxWidth(),
            singleLine = true,
        )
        ErrorText(state.error)
        Spacer(Modifier.height(if (state.error.isNullOrBlank()) 18.dp else 8.dp))
        PrimaryButton("Zaloguj") { onSubmit(state.otpCode) }
    }
}

@Composable
private fun AuthShell(title: String, subtitle: String, onBack: (() -> Unit)? = null, content: @Composable ColumnScope.() -> Unit) {
    BoxWithConstraints(
        Modifier
            .fillMaxSize()
            .background(TppTheme.colors.surface)
            .statusBarsPadding()
            .navigationBarsPadding(),
    ) {
        val horizontalPadding = screenHorizontalPaddingDp(maxWidth.value.toInt()).dp
        Column(
            Modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState())
                .padding(horizontal = horizontalPadding, vertical = 20.dp),
            verticalArrangement = Arrangement.Center,
        ) {
            if (onBack != null) {
                IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) {
                    Icon(Icons.Outlined.ArrowBack, contentDescription = "Wstecz")
                }
                Spacer(Modifier.height(8.dp))
            }
            Text(title, style = MaterialTheme.typography.headlineLarge, fontWeight = FontWeight.Bold, color = TppTheme.colors.forest)
            Text(subtitle, color = TppTheme.colors.muted, modifier = Modifier.padding(top = 8.dp, bottom = 24.dp))
            content()
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
private fun RoutesScreen(
    state: DriverUiState,
    onRefresh: () -> Unit,
    onDate: (String) -> Unit,
    onProfile: () -> Unit,
    onPhotoQueue: () -> Unit,
    onDispatchSheetUpload: (Uri, String, PhotoUploadMetadata) -> Unit,
    onRoute: (String) -> Unit,
    onDismissLiveUpdate: () -> Unit,
) {
    val context = LocalContext.current
    var cameraUri by remember { mutableStateOf<Uri?>(null) }
    var showPreciseLocationPermissionDialog by remember { mutableStateOf(false) }
    val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { ok ->
        val capturedUri = cameraUri
        if (
            ok &&
            capturedUri != null &&
            canLaunchCameraWithLocationPolicy(
                requirePreciseLocation = state.requirePreciseLocationForPhotos,
                hasFineLocation = hasPermission(context, Manifest.permission.ACCESS_FINE_LOCATION),
            )
        ) {
            onDispatchSheetUpload(capturedUri, "camera", cameraPhotoMetadata(context))
        }
        cameraUri = null
    }
    val cameraPermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { grants ->
        val pendingUri = cameraUri
        val cameraGranted = grants[Manifest.permission.CAMERA] == true || hasPermission(context, Manifest.permission.CAMERA)
        val fineLocationGranted = grants[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
            hasPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
        if (!canLaunchCameraWithLocationPolicy(state.requirePreciseLocationForPhotos, fineLocationGranted)) {
            showPreciseLocationPermissionDialog = true
            cameraUri = null
            return@rememberLauncherForActivityResult
        }
        if (cameraGranted && pendingUri != null) cameraLauncher.launch(pendingUri)
    }

    LaunchedEffect(state.routeDayLiveUpdateMessage) {
        if (!state.routeDayLiveUpdateMessage.isNullOrBlank()) {
            delay(6_000)
            onDismissLiveUpdate()
        }
    }

    BoxWithConstraints(Modifier.fillMaxSize()) {
        val screenWidthDp = maxWidth.value.toInt()
        val screenHeightDp = maxHeight.value.toInt()
        val horizontalPadding = screenHorizontalPaddingDp(screenWidthDp).dp
        val compactWidth = screenWidthDp < 360

        Scaffold(
            topBar = {
                Box(Modifier.fillMaxWidth()) {
                    StitchHeader(height = appBrandHeaderHeightDp(screenHeightDp).dp)
                    RouteDayLiveUpdateBanner(
                        message = state.routeDayLiveUpdateMessage,
                        modifier = Modifier
                            .align(Alignment.BottomCenter)
                            .padding(horizontal = horizontalPadding, vertical = 10.dp),
                    )
                }
            },
            bottomBar = {
                StitchBottomBar(
                    activeScreen = DriverScreen.Routes,
                    height = bottomNavigationHeightDp(screenHeightDp).dp,
                    onRoutes = onRefresh,
                    onProfile = onProfile,
                )
            },
            containerColor = TppTheme.colors.surface,
        ) { padding ->
            val pullRefreshState = rememberPullRefreshState(state.refreshing, onRefresh)
            Box(Modifier.fillMaxSize().padding(padding).pullRefresh(pullRefreshState)) {
                LazyColumn(
                    Modifier.fillMaxSize(),
                    contentPadding = PaddingValues(start = horizontalPadding, top = 20.dp, end = horizontalPadding, bottom = 20.dp),
                    verticalArrangement = Arrangement.spacedBy(18.dp),
                ) {
                    item {
                        if (compactWidth) {
                            Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
                                Text(
                                    routeListTitle(state.selectedDate),
                                    style = MaterialTheme.typography.headlineMedium,
                                    fontWeight = FontWeight.Bold,
                                    color = TppTheme.colors.ink,
                                )
                                DateChip(state.selectedDate)
                            }
                        } else {
                            Row(
                                Modifier.fillMaxWidth(),
                                horizontalArrangement = Arrangement.SpaceBetween,
                                verticalAlignment = Alignment.CenterVertically,
                            ) {
                                Text(
                                    routeListTitle(state.selectedDate),
                                    style = MaterialTheme.typography.headlineLarge,
                                    fontWeight = FontWeight.Bold,
                                    color = TppTheme.colors.ink,
                                )
                                DateChip(state.selectedDate)
                            }
                        }
                    }
                    item {
                        RouteDateSelector(
                            selectedDate = state.selectedDate,
                            minDate = state.minRouteDate,
                            maxDate = state.maxRouteDate,
                            onDate = onDate,
                        )
                    }
                    if (shouldShowDispatchSheetReminderCard(state.dispatchSheetReminder)) {
                        item {
                            DispatchSheetReminderCard(
                                reminder = state.dispatchSheetReminder,
                                uploads = state.dispatchSheetUploads,
                                onCamera = {
                                    val newUri = createCameraUri(context)
                                    cameraUri = newUri
                                    val missingPermissions = cameraCapturePermissions(
                                        context = context,
                                        requirePreciseLocation = state.requirePreciseLocationForPhotos,
                                    )
                                    if (missingPermissions.isEmpty()) {
                                        cameraLauncher.launch(newUri)
                                    } else {
                                        cameraPermissionLauncher.launch(missingPermissions)
                                    }
                                },
                            )
                        }
                    }
                    item { OfflineStaleBanner(state) }
                    item { PhotoQueueBanner(state.queuedPhotoUploads, onPhotoQueue) }
                    if (state.displayRoutes.isEmpty()) {
                        item { EmptyState("Brak kursow na wybrany dzien") }
                    } else {
                        items(state.displayRoutes, key = { it.id }) { route -> StitchRouteCard(route, onRoute) }
                    }
                    item { ErrorText(state.error) }
                }
                PullRefreshIndicator(
                    refreshing = state.refreshing,
                    state = pullRefreshState,
                    modifier = Modifier.align(Alignment.TopCenter),
                    backgroundColor = TppTheme.colors.card,
                    contentColor = TppTheme.colors.forest,
                )
            }
        }
    }

    if (showPreciseLocationPermissionDialog) {
        AlertDialog(
            onDismissRequest = { showPreciseLocationPermissionDialog = false },
            title = { Text("Brak dokładnej lokalizacji", color = TppTheme.colors.ink, fontWeight = FontWeight.Bold) },
            text = {
                Text(
                    "Aby zrobić zdjęcie, nadaj aplikacji uprawnienie do dokładnej lokalizacji.",
                    color = TppTheme.colors.muted,
                )
            },
            confirmButton = {
                TextButton(
                    onClick = {
                        showPreciseLocationPermissionDialog = false
                        openAppSettings(context)
                    },
                ) {
                    Text("Przejdź do ustawień", color = TppTheme.colors.forest, fontWeight = FontWeight.Bold)
                }
            },
            dismissButton = {
                TextButton(onClick = { showPreciseLocationPermissionDialog = false }) {
                    Text("Anuluj", color = TppTheme.colors.muted)
                }
            },
        )
    }
}

@Composable
private fun RouteDayLiveUpdateBanner(message: String?, modifier: Modifier = Modifier) {
    if (message.isNullOrBlank()) return

    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.successContainer),
        border = BorderStroke(1.dp, TppTheme.colors.successOutline),
        shape = MaterialTheme.shapes.medium,
        modifier = modifier.fillMaxWidth(),
    ) {
        Row(
            Modifier.fillMaxWidth().padding(14.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(12.dp),
        ) {
            Box(
                Modifier.size(40.dp).background(Color.White, RoundedCornerShape(6.dp)),
                contentAlignment = Alignment.Center,
            ) {
                Icon(Icons.Outlined.Refresh, contentDescription = null, tint = TppTheme.colors.forest)
            }
            Text(
                message,
                color = TppTheme.colors.forest,
                fontWeight = FontWeight.Bold,
                modifier = Modifier.weight(1f),
            )
        }
    }
}

@Composable
private fun LeaveRequestsEntryCard(requests: List<DriverLeaveRequestDto>, onOpen: () -> Unit) {
    val decisionCount = requests.count { it.status == "pending" || it.status == "cancel_requested" }
    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        border = BorderStroke(1.dp, TppTheme.colors.outline),
        shape = MaterialTheme.shapes.medium,
        modifier = Modifier.fillMaxWidth().clickable(onClick = onOpen),
    ) {
        Row(
            Modifier.fillMaxWidth().padding(16.dp),
            horizontalArrangement = Arrangement.spacedBy(12.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Box(Modifier.size(44.dp).background(TppTheme.colors.successContainer, MaterialTheme.shapes.medium), contentAlignment = Alignment.Center) {
                Icon(Icons.Outlined.CalendarToday, contentDescription = null, tint = TppTheme.colors.forest)
            }
            Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
                Text("Wnioski urlopowe", color = TppTheme.colors.ink, fontWeight = FontWeight.Bold, fontSize = 18.sp)
                Text(
                    if (decisionCount > 0) "$decisionCount czeka na decyzję" else "Lista, status i nowy wniosek",
                    color = TppTheme.colors.muted,
                    fontSize = 14.sp,
                )
            }
            Text("Otwórz", color = TppTheme.colors.forest, fontWeight = FontWeight.Bold)
        }
    }
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun LeaveRequestsScreen(
    state: DriverUiState,
    onBack: () -> Unit,
    onRefresh: () -> Unit,
    onAdd: () -> Unit,
    onOpen: (String) -> Unit,
) {
    val pullRefreshState = rememberPullRefreshState(state.refreshing, onRefresh)
    Scaffold(
        topBar = { SimpleTopBar("Wnioski urlopowe", onBack) },
        containerColor = TppTheme.colors.surface,
    ) { padding ->
        Box(Modifier.fillMaxSize().padding(padding).pullRefresh(pullRefreshState)) {
            LazyColumn(
                Modifier.fillMaxSize(),
                contentPadding = PaddingValues(20.dp),
                verticalArrangement = Arrangement.spacedBy(12.dp),
            ) {
                item { FeedbackAndError(state.feedback, state.error) }
                item {
                    Button(
                        onClick = onAdd,
                        modifier = Modifier.fillMaxWidth().height(56.dp),
                        colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.forest),
                        shape = MaterialTheme.shapes.medium,
                    ) {
                        Icon(Icons.Outlined.CalendarToday, contentDescription = null)
                        Spacer(Modifier.width(8.dp))
                        Text("Nowy wniosek", fontWeight = FontWeight.Bold)
                    }
                }
                if (state.leaveRequests.isEmpty()) {
                    item { EmptyState("Nie masz jeszcze wniosków urlopowych") }
                } else {
                    items(state.leaveRequests, key = { it.id }) { request ->
                        LeaveRequestCard(request, onClick = { onOpen(request.id) })
                    }
                }
            }
            PullRefreshIndicator(
                refreshing = state.refreshing,
                state = pullRefreshState,
                modifier = Modifier.align(Alignment.TopCenter),
                backgroundColor = TppTheme.colors.card,
                contentColor = TppTheme.colors.forest,
            )
        }
    }
}

@Composable
private fun LeaveRequestCard(request: DriverLeaveRequestDto, onClick: () -> Unit) {
    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        border = BorderStroke(1.dp, TppTheme.colors.outline),
        shape = MaterialTheme.shapes.medium,
        modifier = Modifier.fillMaxWidth().clickable(onClick = onClick),
    ) {
        Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Top) {
                Column(Modifier.weight(1f)) {
                    Text(
                        "${DriverLeaveRequestUiRules.typeLabel(request.type)} · ${leaveDateRange(request.dateFrom, request.dateTo)}",
                        color = TppTheme.colors.ink,
                        fontWeight = FontWeight.Bold,
                        fontSize = 17.sp,
                    )
                    if (!request.note.isNullOrBlank()) {
                        Text(request.note, color = TppTheme.colors.muted, maxLines = 2, overflow = TextOverflow.Ellipsis)
                    }
                }
                LeaveStatusPill(request.status)
            }
            Text("Szczegóły", color = TppTheme.colors.forest, fontWeight = FontWeight.Bold, fontSize = 14.sp)
        }
    }
}

@Composable
private fun LeaveRequestDetailScreen(
    state: DriverUiState,
    onBack: () -> Unit,
    onCancel: () -> Unit,
) {
    val request = state.selectedLeaveRequest
    Scaffold(topBar = { SimpleTopBar("Szczegóły wniosku", onBack) }, containerColor = TppTheme.colors.surface) { padding ->
        if (request == null) {
            Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) {
                Text("Nie znaleziono wniosku", color = TppTheme.colors.muted)
            }
            return@Scaffold
        }

        LazyColumn(
            Modifier.fillMaxSize().padding(padding),
            contentPadding = PaddingValues(20.dp),
            verticalArrangement = Arrangement.spacedBy(14.dp),
        ) {
            item { FeedbackAndError(state.feedback, state.error) }
            item {
                Card(
                    colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
                    border = BorderStroke(1.dp, TppTheme.colors.outline),
                    shape = MaterialTheme.shapes.medium,
                ) {
                    Column(Modifier.padding(18.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
                        Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Top) {
                            Text(DriverLeaveRequestUiRules.typeLabel(request.type), color = TppTheme.colors.ink, fontWeight = FontWeight.Bold, fontSize = 22.sp)
                            LeaveStatusPill(request.status)
                        }
                        DetailLine("Zakres", leaveDateRange(request.dateFrom, request.dateTo))
                        DetailLine("Złożono", request.submittedAt?.let(::shortDateTime) ?: "-")
                        DetailLine("Decyzja", request.decidedAt?.let(::shortDateTime) ?: "-")
                        DetailLine("Notatka", request.note ?: "-")
                        DetailLine("Komentarz", request.decisionComment ?: "-")
                        if (DriverLeaveRequestUiRules.canCancel(request.status, request.dateFrom.orEmpty())) {
                            Button(
                                onClick = onCancel,
                                modifier = Modifier.fillMaxWidth().height(54.dp),
                                colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFB45309)),
                                shape = MaterialTheme.shapes.medium,
                            ) {
                                Text(if (request.status == "approved") "Poproś o anulowanie" else "Anuluj wniosek")
                            }
                        }
                    }
                }
            }
            item {
                Text("Historia", color = TppTheme.colors.ink, fontWeight = FontWeight.Bold, fontSize = 18.sp)
            }
            items(request.events, key = { it.id }) { event ->
                Card(
                    colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
                    border = BorderStroke(1.dp, TppTheme.colors.outline.copy(alpha = 0.65f)),
                    shape = MaterialTheme.shapes.medium,
                ) {
                    Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
                        Text(
                            "${DriverLeaveRequestUiRules.eventActionLabel(event.action)}${event.toStatus?.let { " · ${DriverLeaveRequestUiRules.statusLabel(it)}" } ?: ""}",
                            color = TppTheme.colors.ink,
                            fontWeight = FontWeight.Bold,
                        )
                        Text(event.createdAt?.let(::shortDateTime) ?: "-", color = TppTheme.colors.muted, fontSize = 13.sp)
                        if (!event.comment.isNullOrBlank()) Text(event.comment, color = TppTheme.colors.muted)
                    }
                }
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun LeaveCalendarScreen(
    state: DriverUiState,
    onBack: () -> Unit,
    onDateSelected: (String) -> Unit,
    onContinue: () -> Unit,
    onLoadMore: () -> Unit,
) {
    val context = LocalContext.current
    val listState = rememberLazyListState()
    val months = remember(state.leaveCalendarLoadedUntil) { leaveCalendarMonths(state.leaveCalendarLoadedUntil) }
    var detailsDate by remember { mutableStateOf<LocalDate?>(null) }
    val detailsEntries = detailsDate
        ?.let { DriverLeaveRequestUiRules.calendarEntriesForDate(state.leaveCalendarEntries, it) }
        .orEmpty()
    val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)

    LaunchedEffect(months.size, state.leaveCalendarLoading) {
        snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 }
            .distinctUntilChanged()
            .collect { lastVisibleIndex ->
                if (state.leaveCalendarLoadedUntil != null && !state.leaveCalendarLoading && lastVisibleIndex >= months.size) {
                    onLoadMore()
                }
            }
    }

    Scaffold(
        topBar = { SimpleTopBar("Kalendarz urlopów", onBack) },
        bottomBar = {
            LeaveCalendarContinueBar(
                state = state,
                onContinue = onContinue,
            )
        },
        containerColor = TppTheme.colors.surface,
    ) { padding ->
        LazyColumn(
            state = listState,
            modifier = Modifier.fillMaxSize().padding(padding),
            contentPadding = PaddingValues(start = 20.dp, top = 16.dp, end = 20.dp, bottom = 120.dp),
            verticalArrangement = Arrangement.spacedBy(14.dp),
        ) {
            item { FeedbackAndError(null, state.error) }
            items(months, key = { it.toString() }) { month ->
                LeaveCalendarMonth(
                    month = month,
                    entries = state.leaveCalendarEntries,
                    selectedFrom = state.leaveRequestDateFrom,
                    selectedTo = state.leaveRequestDateTo,
                    hasSelection = state.leaveCalendarHasSelection,
                    onDayClick = { date, entries ->
                        onDateSelected(date.toString())
                        if (entries.isNotEmpty()) {
                            detailsDate = date
                        }
                    },
                )
            }
            item {
                if (state.leaveCalendarLoading) {
                    Row(
                        Modifier.fillMaxWidth().padding(vertical = 12.dp),
                        horizontalArrangement = Arrangement.Center,
                        verticalAlignment = Alignment.CenterVertically,
                    ) {
                        CircularProgressIndicator(color = TppTheme.colors.forest, modifier = Modifier.size(22.dp))
                        Spacer(Modifier.width(10.dp))
                        Text("Pobieram kolejne miesiące", color = TppTheme.colors.muted)
                    }
                } else {
                    TextButton(onClick = onLoadMore, modifier = Modifier.fillMaxWidth()) {
                        Text("Pokaż kolejne miesiące", color = TppTheme.colors.forest, fontWeight = FontWeight.Bold)
                    }
                }
            }
        }
    }

    if (detailsDate != null && detailsEntries.isNotEmpty()) {
        ModalBottomSheet(
            onDismissRequest = { detailsDate = null },
            sheetState = sheetState,
            containerColor = TppTheme.colors.card,
        ) {
            LeaveCalendarDetailsSheet(
                date = detailsDate,
                entries = detailsEntries,
                onCall = { dialUri ->
                    context.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse(dialUri)))
                },
            )
        }
    }
}

@Composable
private fun LeaveCalendarContinueBar(state: DriverUiState, onContinue: () -> Unit) {
    val selectionText = when {
        !state.leaveCalendarHasSelection -> "Wybierz pierwszy dzień urlopu"
        else -> leaveDateRange(state.leaveRequestDateFrom, state.leaveRequestDateTo)
    }

    Column(
        Modifier
            .fillMaxWidth()
            .background(Color.White)
            .border(0.5.dp, TppTheme.colors.outline.copy(alpha = 0.65f))
            .navigationBarsPadding()
            .padding(horizontal = 20.dp, vertical = 12.dp),
        verticalArrangement = Arrangement.spacedBy(10.dp),
    ) {
        Text("Wybrany termin", color = TppTheme.colors.muted, fontSize = 12.sp, fontWeight = FontWeight.Bold)
        Row(
            Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.spacedBy(12.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Text(
                selectionText,
                color = TppTheme.colors.ink,
                fontWeight = FontWeight.Bold,
                modifier = Modifier.weight(1f),
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
            )
            Button(
                onClick = onContinue,
                enabled = state.leaveCalendarHasSelection,
                colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.forest),
                shape = MaterialTheme.shapes.medium,
                modifier = Modifier.height(48.dp),
            ) {
                Text("Dalej", fontWeight = FontWeight.Bold)
            }
        }
    }
}

@Composable
private fun LeaveCalendarMonth(
    month: YearMonth,
    entries: List<DriverLeaveCalendarEntryDto>,
    selectedFrom: String,
    selectedTo: String,
    hasSelection: Boolean,
    onDayClick: (LocalDate, List<DriverLeaveCalendarEntryDto>) -> Unit,
) {
    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        border = BorderStroke(1.dp, TppTheme.colors.outline),
        shape = MaterialTheme.shapes.medium,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
            Text(
                leaveCalendarMonthTitle(month),
                color = TppTheme.colors.ink,
                fontWeight = FontWeight.Bold,
                fontSize = 20.sp,
            )
            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) {
                leaveCalendarWeekdays().forEach { day ->
                    Text(
                        leaveCalendarWeekdayLabel(day),
                        color = TppTheme.colors.muted,
                        fontWeight = FontWeight.Bold,
                        fontFamily = FontFamily.Monospace,
                        fontSize = 11.sp,
                        textAlign = androidx.compose.ui.text.style.TextAlign.Center,
                        modifier = Modifier.weight(1f),
                    )
                }
            }
            monthDaysGrid(month).chunked(7).forEach { week ->
                Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) {
                    week.forEach { date ->
                        val dayEntries = date
                            ?.let { DriverLeaveRequestUiRules.calendarEntriesForDate(entries, it) }
                            .orEmpty()
                        LeaveCalendarDayCell(
                            date = date,
                            entries = dayEntries,
                            selected = date?.let { leaveCalendarDateInRange(it, selectedFrom, selectedTo, hasSelection) } == true,
                            today = date == LocalDate.now(),
                            enabled = date != null && !date.isBefore(LocalDate.now()),
                            onClick = {
                                if (date != null) {
                                    onDayClick(date, dayEntries)
                                }
                            },
                            modifier = Modifier.weight(1f),
                        )
                    }
                }
            }
        }
    }
}

@Composable
private fun LeaveCalendarDayCell(
    date: LocalDate?,
    entries: List<DriverLeaveCalendarEntryDto>,
    selected: Boolean,
    today: Boolean,
    enabled: Boolean,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    val hasConflicts = entries.isNotEmpty()
    val background = when {
        selected -> TppTheme.colors.successContainer
        hasConflicts -> TppTheme.colors.warningContainer
        today -> Color.White
        else -> TppTheme.colors.panel
    }
    val outline = when {
        selected -> TppTheme.colors.forest
        hasConflicts -> TppTheme.colors.warningOutline
        today -> TppTheme.colors.forest.copy(alpha = 0.45f)
        else -> TppTheme.colors.outline.copy(alpha = 0.45f)
    }
    val textColor = when {
        !enabled -> TppTheme.colors.muted.copy(alpha = 0.45f)
        selected -> TppTheme.colors.forest
        hasConflicts -> Color(0xFF7A5A00)
        else -> TppTheme.colors.ink
    }

    Box(
        modifier
            .aspectRatio(1f)
            .background(background, RoundedCornerShape(4.dp))
            .border(1.dp, outline, RoundedCornerShape(4.dp))
            .clickable(enabled = enabled, onClick = onClick)
            .padding(4.dp),
    ) {
        if (date != null) {
            Text(
                date.dayOfMonth.toString(),
                color = textColor,
                fontWeight = FontWeight.Bold,
                fontSize = 14.sp,
                modifier = Modifier.align(Alignment.TopStart),
            )
            if (hasConflicts) {
                Text(
                    entries.size.toString(),
                    color = if (selected) Color.White else Color(0xFF7A5A00),
                    fontWeight = FontWeight.Bold,
                    fontFamily = FontFamily.Monospace,
                    fontSize = 11.sp,
                    modifier = Modifier
                        .align(Alignment.BottomEnd)
                        .background(if (selected) TppTheme.colors.forest else Color(0xFFFFE08A), RoundedCornerShape(999.dp))
                        .padding(horizontal = 5.dp, vertical = 1.dp),
                )
            }
        }
    }
}

@Composable
private fun LeaveCalendarDetailsSheet(
    date: LocalDate?,
    entries: List<DriverLeaveCalendarEntryDto>,
    onCall: (String) -> Unit,
) {
    Column(
        Modifier.fillMaxWidth().navigationBarsPadding().padding(start = 20.dp, end = 20.dp, bottom = 24.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp),
    ) {
        Text(
            date?.let { DateTimeFormatter.ofPattern("d MMMM yyyy", Locale("pl", "PL")).format(it) } ?: "Urlopy w tym dniu",
            color = TppTheme.colors.ink,
            fontWeight = FontWeight.Bold,
            fontSize = 22.sp,
        )
        entries.forEach { entry ->
            LeaveCalendarDetailsEntry(entry = entry, onCall = onCall)
        }
    }
}

@Composable
private fun LeaveCalendarDetailsEntry(entry: DriverLeaveCalendarEntryDto, onCall: (String) -> Unit) {
    val dialUri = DriverLeaveRequestUiRules.phoneDialUri(entry.driver.phoneNumber)
    Card(
        colors = CardDefaults.cardColors(containerColor = Color.White),
        border = BorderStroke(1.dp, TppTheme.colors.outline),
        shape = MaterialTheme.shapes.medium,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Top) {
                Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
                    Text(
                        entry.driver.displayName?.takeIf { it.isNotBlank() } ?: "Kierowca #${entry.driver.id}",
                        color = TppTheme.colors.ink,
                        fontWeight = FontWeight.Bold,
                        fontSize = 17.sp,
                    )
                    Text(
                        leaveDateRange(entry.dateFrom, entry.dateTo),
                        color = TppTheme.colors.muted,
                        fontWeight = FontWeight.SemiBold,
                    )
                }
                LeaveStatusPill(entry.status)
            }
            DetailLine("Typ", entry.typeLabel?.takeIf { it.isNotBlank() } ?: DriverLeaveRequestUiRules.typeLabel(entry.type))
            DetailLine("Telefon", entry.driver.phoneNumber?.takeIf { it.isNotBlank() } ?: "Brak numeru telefonu")
            if (dialUri != null) {
                Button(
                    onClick = { onCall(dialUri) },
                    colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.forest),
                    shape = MaterialTheme.shapes.medium,
                    modifier = Modifier.fillMaxWidth().height(50.dp),
                ) {
                    Icon(Icons.Outlined.Phone, contentDescription = null)
                    Spacer(Modifier.width(8.dp))
                    Text("Dzwoń", fontWeight = FontWeight.Bold)
                }
            }
        }
    }
}

@Composable
private fun AddLeaveRequestScreen(
    state: DriverUiState,
    onBack: () -> Unit,
    onDraft: (String?, String?, String?, String?) -> Unit,
    onChangePeriod: () -> Unit,
    onSubmit: () -> Unit,
) {
    Scaffold(topBar = { SimpleTopBar("Nowy wniosek", onBack) }, containerColor = TppTheme.colors.surface) { padding ->
        LazyColumn(
            Modifier.fillMaxSize().padding(padding),
            contentPadding = PaddingValues(20.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp),
        ) {
            item { FeedbackAndError(null, state.error) }
            item {
                Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
                    Text("Typ", color = TppTheme.colors.ink, fontWeight = FontWeight.Bold)
                    LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                        items(state.leaveRequestsConfig?.types.orEmpty()) { type ->
                            val active = type == state.leaveRequestType
                            Button(
                                onClick = { onDraft(null, null, type, null) },
                                colors = ButtonDefaults.buttonColors(
                                    containerColor = if (active) TppTheme.colors.forest else Color.White,
                                    contentColor = if (active) Color.White else TppTheme.colors.ink,
                                ),
                                border = BorderStroke(1.dp, if (active) TppTheme.colors.forest else TppTheme.colors.outline),
                                shape = MaterialTheme.shapes.medium,
                            ) {
                                Text(DriverLeaveRequestUiRules.typeLabel(type))
                            }
                        }
                    }
                }
            }
            item {
                Card(
                    colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
                    border = BorderStroke(1.dp, TppTheme.colors.outline),
                    shape = MaterialTheme.shapes.medium,
                    modifier = Modifier.fillMaxWidth().clickable { onChangePeriod() },
                ) {
                    Row(
                        Modifier.fillMaxWidth().padding(16.dp),
                        horizontalArrangement = Arrangement.spacedBy(12.dp),
                        verticalAlignment = Alignment.CenterVertically,
                    ) {
                        Box(
                            Modifier.size(44.dp).background(TppTheme.colors.successContainer, MaterialTheme.shapes.medium),
                            contentAlignment = Alignment.Center,
                        ) {
                            Icon(Icons.Outlined.CalendarToday, contentDescription = null, tint = TppTheme.colors.forest)
                        }
                        Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {
                            Text("Okres", color = TppTheme.colors.ink, fontWeight = FontWeight.Bold)
                            Text(
                                leaveDateRange(state.leaveRequestDateFrom, state.leaveRequestDateTo),
                                color = TppTheme.colors.muted,
                            )
                        }
                        Text("Zmień", color = TppTheme.colors.forest, fontWeight = FontWeight.Bold)
                    }
                }
            }
            item {
                LeaveTextField(
                    label = "Notatka (opcjonalnie)",
                    value = state.leaveRequestNote,
                    onValue = { onDraft(null, null, null, it) },
                    minLines = 4,
                )
            }
            item {
                Button(
                    onClick = onSubmit,
                    modifier = Modifier.fillMaxWidth().height(58.dp),
                    colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.forest),
                    shape = MaterialTheme.shapes.medium,
                ) {
                    Text("Wyślij wniosek", fontWeight = FontWeight.Bold)
                }
            }
        }
    }
}

@Composable
private fun SimpleTopBar(title: String, onBack: () -> Unit) {
    Row(
        Modifier
            .fillMaxWidth()
            .statusBarsPadding()
            .background(Color.White)
            .border(0.5.dp, TppTheme.colors.outline.copy(alpha = 0.65f))
            .padding(horizontal = 10.dp, vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) {
            Icon(Icons.Outlined.ArrowBack, contentDescription = "Wstecz", tint = TppTheme.colors.ink)
        }
        Text(title, color = TppTheme.colors.ink, fontWeight = FontWeight.Bold, fontSize = 20.sp)
    }
}

@Composable
private fun LeaveStatusPill(status: String) {
    val color = when (status) {
        "approved" -> TppTheme.colors.successContainer to TppTheme.colors.forest
        "rejected" -> Color(0xFFFFEBEE) to Color(0xFFB91C1C)
        "cancel_requested" -> Color(0xFFFFF7ED) to Color(0xFFB45309)
        "cancelled", "revoked" -> Color(0xFFF1F5F9) to Color(0xFF475569)
        else -> Color(0xFFFFF8DB) to Color(0xFF7A5A00)
    }
    Text(
        DriverLeaveRequestUiRules.statusLabel(status),
        color = color.second,
        fontWeight = FontWeight.Bold,
        fontSize = 12.sp,
        modifier = Modifier.background(color.first, RoundedCornerShape(999.dp)).padding(horizontal = 10.dp, vertical = 6.dp),
    )
}

@Composable
private fun DetailLine(label: String, value: String) {
    Column(verticalArrangement = Arrangement.spacedBy(3.dp)) {
        Text(label.uppercase(Locale("pl", "PL")), color = TppTheme.colors.muted, fontSize = 12.sp, fontWeight = FontWeight.Bold)
        Text(value, color = TppTheme.colors.ink, fontSize = 16.sp)
    }
}

@Composable
private fun FeedbackAndError(feedback: String?, error: String?) {
    Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
        if (!feedback.isNullOrBlank()) {
            Text(feedback, color = TppTheme.colors.forest, fontWeight = FontWeight.Bold)
        }
        ErrorText(error)
    }
}

@Composable
private fun LeaveTextField(label: String, value: String, onValue: (String) -> Unit, minLines: Int = 1) {
    OutlinedTextField(
        value = value,
        onValueChange = onValue,
        label = { Text(label) },
        minLines = minLines,
        modifier = Modifier.fillMaxWidth(),
        shape = MaterialTheme.shapes.medium,
    )
}

private fun leaveDateRange(from: String?, to: String?): String =
    if (from == to) from ?: "-" else "${from ?: "?"} - ${to ?: "?"}"

private fun leaveCalendarMonths(loadedUntil: String?): List<YearMonth> {
    val first = YearMonth.from(LocalDate.now())
    val last = loadedUntil
        ?.let { runCatching { YearMonth.from(LocalDate.parse(it)) }.getOrNull() }
        ?: first
    val months = mutableListOf<YearMonth>()
    var cursor = first
    while (!cursor.isAfter(last)) {
        months += cursor
        cursor = cursor.plusMonths(1)
    }
    return months
}

private fun leaveCalendarWeekdays(): List<DayOfWeek> =
    listOf(
        DayOfWeek.MONDAY,
        DayOfWeek.TUESDAY,
        DayOfWeek.WEDNESDAY,
        DayOfWeek.THURSDAY,
        DayOfWeek.FRIDAY,
        DayOfWeek.SATURDAY,
        DayOfWeek.SUNDAY,
    )

private fun leaveCalendarWeekdayLabel(day: DayOfWeek): String =
    when (day) {
        DayOfWeek.MONDAY -> "pon"
        DayOfWeek.TUESDAY -> "wt"
        DayOfWeek.WEDNESDAY -> "śr"
        DayOfWeek.THURSDAY -> "czw"
        DayOfWeek.FRIDAY -> "pt"
        DayOfWeek.SATURDAY -> "sob"
        DayOfWeek.SUNDAY -> "nd"
    }

private fun monthDaysGrid(month: YearMonth): List<LocalDate?> {
    val first = month.atDay(1)
    val leadingBlanks = first.dayOfWeek.value - DayOfWeek.MONDAY.value
    val days = mutableListOf<LocalDate?>()
    repeat(leadingBlanks) { days += null }
    for (day in 1..month.lengthOfMonth()) {
        days += month.atDay(day)
    }
    while (days.size % 7 != 0) {
        days += null
    }
    return days
}

private fun leaveCalendarDateInRange(date: LocalDate, selectedFrom: String, selectedTo: String, hasSelection: Boolean): Boolean {
    if (!hasSelection) return false
    val from = runCatching { LocalDate.parse(selectedFrom) }.getOrNull() ?: return false
    val to = runCatching { LocalDate.parse(selectedTo) }.getOrNull() ?: return false
    return !date.isBefore(from) && !date.isAfter(to)
}

private fun leaveCalendarMonthTitle(month: YearMonth): String =
    DateTimeFormatter.ofPattern("LLLL yyyy", Locale("pl", "PL")).format(month.atDay(1))

private fun shortDateTime(value: String): String =
    runCatching {
        DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm", Locale("pl", "PL"))
            .format(Instant.parse(value).atZone(ZoneId.systemDefault()))
    }.getOrDefault(value)

@Composable
private fun DispatchSheetReminderCard(
    reminder: DispatchSheetReminderDto?,
    uploads: List<DispatchSheetUploadEntity>,
    onCamera: () -> Unit,
) {
    if (!shouldShowDispatchSheetReminderCard(reminder)) return

    val queuedCount = queuedPhotoUploadCount(uploads.map { it.status })
    val hasQueuedUpload = queuedCount > 0
    val uploaded = reminder?.status == "uploaded" && !hasQueuedUpload
    val container = if (uploaded) TppTheme.colors.successContainer else TppTheme.colors.card
    val border = if (uploaded) TppTheme.colors.successOutline else TppTheme.colors.outline
    val actionLabel = dispatchSheetPrimaryActionLabel(reminder, hasQueuedUpload)

    Card(
        colors = CardDefaults.cardColors(containerColor = container),
        border = BorderStroke(1.dp, border),
        shape = MaterialTheme.shapes.medium,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
            Row(
                Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.spacedBy(12.dp),
                verticalAlignment = Alignment.Top,
            ) {
                Icon(
                    if (uploaded) Icons.Outlined.CheckCircle else Icons.Outlined.CameraAlt,
                    contentDescription = null,
                    tint = if (uploaded) TppTheme.colors.forest else TppTheme.colors.ink,
                    modifier = Modifier.size(28.dp),
                )
                Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {
                    Text(
                        if (uploaded) "Zdjęcie karty spedycyjnej wykonane" else "Karta spedycyjna",
                        color = TppTheme.colors.ink,
                        style = MaterialTheme.typography.titleMedium,
                        fontWeight = FontWeight.Bold,
                    )
                    Text(
                        if (uploaded) {
                            "Dokument został zapisany. W razie potrzeby możesz zrobić poprawione zdjęcie."
                        } else {
                            "Po zakończeniu dnia zrób zdjęcie karty spedycyjnej."
                        },
                        color = TppTheme.colors.muted,
                        style = MaterialTheme.typography.bodyMedium,
                    )
                }
            }

            Button(
                onClick = onCamera,
                enabled = reminder?.canUpload == true && !hasQueuedUpload,
                modifier = Modifier.fillMaxWidth().height(56.dp),
                colors = ButtonDefaults.buttonColors(
                    containerColor = if (uploaded) TppTheme.colors.containerGreen else TppTheme.colors.forest,
                    disabledContainerColor = TppTheme.colors.outline,
                ),
                shape = MaterialTheme.shapes.medium,
            ) {
                if (hasQueuedUpload) {
                    CircularProgressIndicator(
                        modifier = Modifier.size(20.dp),
                        color = Color.White,
                        strokeWidth = 2.dp,
                    )
                } else {
                    Icon(Icons.Outlined.CameraAlt, contentDescription = null, tint = Color.White)
                }
                Spacer(Modifier.width(10.dp))
                Text(actionLabel, color = Color.White, fontWeight = FontWeight.Bold)
            }
        }
    }
}

@Composable
private fun PhotoQueueBanner(uploads: List<PhotoUploadEntity>, onPhotoQueue: () -> Unit) {
    val count = queuedPhotoUploadCount(uploads.map { it.status })
    if (count <= 0) return

    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        border = BorderStroke(1.dp, TppTheme.colors.outline),
        shape = MaterialTheme.shapes.medium,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Row(
            Modifier.fillMaxWidth().padding(14.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(12.dp),
        ) {
            Box(
                Modifier.size(42.dp).background(TppTheme.colors.containerGreen, RoundedCornerShape(6.dp)),
                contentAlignment = Alignment.Center,
            ) {
                Icon(Icons.Outlined.CloudUpload, contentDescription = null, tint = TppTheme.colors.forest)
            }
            Text(
                "W kolejce do wysłania jest $count ${photoCountLabel(count)}.",
                color = TppTheme.colors.ink,
                fontWeight = FontWeight.SemiBold,
                modifier = Modifier.weight(1f),
            )
            Button(
                onClick = onPhotoQueue,
                colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.forest),
                shape = MaterialTheme.shapes.small,
                contentPadding = PaddingValues(horizontal = 14.dp, vertical = 10.dp),
            ) {
                Text("Kolejka", color = Color.White, fontWeight = FontWeight.Bold)
            }
        }
    }
}

@Composable
private fun PhotoQueueScreen(
    state: DriverUiState,
    onBack: () -> Unit,
    onRetryUpload: (PhotoUploadEntity) -> Unit,
) {
    val uploads = state.queuedPhotoUploads.sortedBy { it.createdAtEpochMillis }

    BoxWithConstraints(Modifier.fillMaxSize()) {
        val horizontalPadding = screenHorizontalPaddingDp(maxWidth.value.toInt()).dp

        Scaffold(
            topBar = { DetailHeader(onBack, title = "Kolejka zdjęć") },
            containerColor = TppTheme.colors.surface,
        ) { padding ->
            LazyColumn(
                Modifier.fillMaxSize().padding(padding),
                contentPadding = PaddingValues(horizontal = horizontalPadding, vertical = 16.dp),
                verticalArrangement = Arrangement.spacedBy(14.dp),
            ) {
                item { OfflineStaleBanner(state) }
                if (uploads.isEmpty()) {
                    item { EmptyState("Brak zdjęć oczekujących na przesłanie") }
                } else {
                    item {
                        Text(
                            "Zdjęcia do przesłania (${uploads.size})",
                            style = MaterialTheme.typography.headlineSmall,
                            fontWeight = FontWeight.Bold,
                            color = TppTheme.colors.ink,
                        )
                    }
                    items(uploads, key = { it.clientRequestId }) { upload ->
                        PhotoQueueItem(upload, onRetryUpload)
                    }
                }
            }
        }
    }
}

@Composable
private fun PhotoQueueItem(upload: PhotoUploadEntity, onRetryUpload: (PhotoUploadEntity) -> Unit) {
    val status = upload.statusType
    val hasFailureDetails = canShowPhotoUploadFailureDetails(upload.status, upload.lastError)
    var showFailureDetails by remember { mutableStateOf(false) }
    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        border = BorderStroke(1.dp, TppTheme.colors.outline),
        shape = MaterialTheme.shapes.medium,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Row(
            Modifier.fillMaxWidth().padding(12.dp),
            horizontalArrangement = Arrangement.spacedBy(12.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            AsyncImage(
                model = File(upload.localPath),
                contentDescription = "Zdjęcie z kolejki",
                contentScale = ContentScale.Crop,
                modifier = Modifier.size(76.dp).background(TppTheme.colors.panel, RoundedCornerShape(6.dp)),
            )
            Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) {
                Text("Kurs #${upload.routeId}", color = TppTheme.colors.ink, fontWeight = FontWeight.Bold)
                Text(
                    upload.lastError?.lineSequence()?.firstOrNull { it.isNotBlank() }?.takeIf { hasFailureDetails } ?: status.label,
                    color = if (hasFailureDetails) TppTheme.colors.error else TppTheme.colors.muted,
                    fontFamily = FontFamily.Monospace,
                    style = MaterialTheme.typography.labelMedium,
                )
                if (status == PhotoUploadStatus.Uploading || status == PhotoUploadStatus.Verifying) {
                    androidx.compose.material3.LinearProgressIndicator(
                        progress = { (upload.progress.coerceIn(0, 100) / 100f) },
                        modifier = Modifier.fillMaxWidth(),
                        color = TppTheme.colors.forest,
                        trackColor = TppTheme.colors.outline.copy(alpha = 0.4f),
                    )
                }
            }
            if (hasFailureDetails || canRetryPhotoUpload(upload.status)) {
                Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) {
                    if (hasFailureDetails) {
                        IconButton(
                            onClick = { showFailureDetails = true },
                            modifier = Modifier
                                .size(40.dp)
                                .background(TppTheme.colors.surface, RoundedCornerShape(20.dp))
                                .border(1.dp, TppTheme.colors.error.copy(alpha = 0.35f), RoundedCornerShape(20.dp)),
                        ) {
                            Icon(
                                Icons.Outlined.Info,
                                contentDescription = "Pokaż szczegóły błędu",
                                tint = TppTheme.colors.error,
                                modifier = Modifier.size(20.dp),
                            )
                        }
                    }
                    if (canRetryPhotoUpload(upload.status)) {
                        Button(
                            onClick = { onRetryUpload(upload) },
                            colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.forest),
                            shape = MaterialTheme.shapes.small,
                            contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp),
                        ) {
                            Icon(
                                Icons.Outlined.Refresh,
                                contentDescription = null,
                                tint = Color.White,
                                modifier = Modifier.size(18.dp),
                            )
                            Spacer(Modifier.width(6.dp))
                            Text("Ponów", color = Color.White, fontWeight = FontWeight.Bold)
                        }
                    }
                }
            }
        }
    }
    if (showFailureDetails) {
        PhotoUploadFailureDialog(
            details = upload.lastError.orEmpty(),
            onDismiss = { showFailureDetails = false },
        )
    }
}

@Composable
private fun PhotoUploadFailureDialog(details: String, onDismiss: () -> Unit) {
    AlertDialog(
        onDismissRequest = onDismiss,
        title = {
            Text("Dlaczego nie wysłano zdjęcia?", color = TppTheme.colors.ink, fontWeight = FontWeight.Bold)
        },
        text = {
            Text(
                details,
                color = TppTheme.colors.ink,
                fontFamily = FontFamily.Monospace,
                style = MaterialTheme.typography.bodySmall,
                modifier = Modifier.heightIn(max = 320.dp).verticalScroll(rememberScrollState()),
            )
        },
        confirmButton = {
            TextButton(onClick = onDismiss) {
                Text("Zamknij", color = TppTheme.colors.forest, fontWeight = FontWeight.Bold)
            }
        },
    )
}

@Composable
private fun StitchHeader(height: Dp = 124.dp) {
    Column(Modifier.fillMaxWidth().background(TppTheme.colors.surface).statusBarsPadding()) {
        BrandBannerHeader(height = height)
        HorizontalDivider(color = TppTheme.colors.outline.copy(alpha = 0.86f), thickness = 1.dp)
    }
}

@Composable
private fun DateChip(date: String) {
    val formatter = remember { DateTimeFormatter.ofPattern("d MMM", Locale("pl", "PL")) }
    val label = remember(date) { LocalDate.parse(date).format(formatter).replace(".", "").replaceFirstChar { it.uppercase() } }
    Row(
        modifier = Modifier.background(TppTheme.colors.panel, RoundedCornerShape(12.dp)).padding(horizontal = 16.dp, vertical = 12.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(10.dp),
    ) {
        Icon(Icons.Outlined.CalendarToday, contentDescription = null, tint = TppTheme.colors.forest)
        Text(label, fontFamily = FontFamily.Monospace, color = TppTheme.colors.muted, fontWeight = FontWeight.SemiBold)
    }
}

@Composable
private fun RouteDateSelector(selectedDate: String, minDate: String, maxDate: String, onDate: (String) -> Unit) {
    val dates = remember(minDate, maxDate) {
        runCatching {
            generateSequence(LocalDate.parse(minDate)) { date ->
                val next = date.plusDays(1)
                if (next <= LocalDate.parse(maxDate)) next else null
            }.toList()
        }.getOrElse { listOf(LocalDate.parse(selectedDate)) }
    }
    val listState = rememberLazyListState()

    LaunchedEffect(dates, selectedDate) {
        val selectedIndex = dates.indexOfFirst { it.toString() == selectedDate }
        if (selectedIndex >= 0) {
            listState.scrollToItem(selectedIndex)
        }
    }

    LazyRow(state = listState, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
        items(dates, key = { it.toString() }) { date ->
            DateSelectorChip(
                date = date,
                selected = date.toString() == selectedDate,
                onClick = { onDate(date.toString()) },
            )
        }
    }
}

@Composable
private fun DateSelectorChip(date: LocalDate, selected: Boolean, onClick: () -> Unit) {
    val formatter = remember { DateTimeFormatter.ofPattern("EEE d MMM", Locale("pl", "PL")) }
    val label = remember(date) { date.format(formatter).replace(".", "").replaceFirstChar { it.uppercase() } }
    val background = if (selected) TppTheme.colors.forest else TppTheme.colors.card
    val content = if (selected) Color.White else TppTheme.colors.muted

    Row(
        modifier = Modifier
            .background(background, RoundedCornerShape(8.dp))
            .clickable(onClick = onClick)
            .padding(horizontal = 14.dp, vertical = 10.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(label, color = content, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.SemiBold)
    }
}

private fun routeListTitle(selectedDate: String): String {
    val today = LocalDate.now()
    val date = runCatching { LocalDate.parse(selectedDate) }.getOrDefault(today)

    return when (date) {
        today -> "Dzisiejsze Kursy"
        today.plusDays(1) -> "Jutrzejsze Kursy"
        else -> "Kursy"
    }
}

@Composable
private fun StitchRouteCard(route: DriverRouteDto, onRoute: (String) -> Unit) {
    val display = RouteDisplayMapper.toCard(route)
    val stripColor = routeStatusColor(route.status)
    Card(
        modifier = Modifier.fillMaxWidth(),
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        shape = MaterialTheme.shapes.medium,
        border = BorderStroke(1.dp, TppTheme.colors.outline),
    ) {
        BoxWithConstraints(Modifier.fillMaxWidth()) {
            val compactWidth = maxWidth < 340.dp
            val contentPadding = if (compactWidth) 16.dp else 24.dp

            Row(Modifier.fillMaxWidth().height(IntrinsicSize.Min)) {
                Box(
                    Modifier
                        .fillMaxHeight()
                        .width(6.dp)
                        .background(stripColor, RoundedCornerShape(topStart = 8.dp, bottomStart = 8.dp)),
                )
                Column(Modifier.fillMaxWidth()) {
                    Column(Modifier.padding(start = contentPadding, top = contentPadding, end = contentPadding, bottom = 20.dp)) {
                        if (compactWidth) {
                            Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
                                StatusChip(route.status, stripColor)
                                Text(
                                    "Kontrakt: #${display.contractLabel.trimStart('#')}",
                                    fontFamily = FontFamily.Monospace,
                                    color = TppTheme.colors.ink,
                                    fontWeight = FontWeight.SemiBold,
                                )
                            }
                        } else {
                            Row(
                                Modifier.fillMaxWidth(),
                                horizontalArrangement = Arrangement.SpaceBetween,
                                verticalAlignment = Alignment.CenterVertically,
                            ) {
                                StatusChip(route.status, stripColor)
                                Text(
                                    "Kontrakt: #${display.contractLabel.trimStart('#')}",
                                    fontFamily = FontFamily.Monospace,
                                    color = TppTheme.colors.ink,
                                    fontWeight = FontWeight.SemiBold,
                                )
                            }
                        }
                        Spacer(Modifier.height(if (compactWidth) 18.dp else 26.dp))
                        RouteTimeline(display.origin, display.destination)
                    }
                    HorizontalDivider(color = TppTheme.colors.outline.copy(alpha = 0.55f))
                    Button(
                        onClick = { onRoute(route.id) },
                        modifier = Modifier.fillMaxWidth().padding(16.dp).height(56.dp),
                        colors = ButtonDefaults.buttonColors(containerColor = stripColor),
                        shape = MaterialTheme.shapes.small,
                    ) {
                        Text("Szczegóły  →", fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold)
                    }
                }
            }
        }
    }
}

@Composable
private fun StatusChip(status: String, color: Color) {
    Text(
        status.ifBlank { "NOWY" }.uppercase(Locale("pl", "PL")),
        modifier = Modifier
            .background(color.copy(alpha = 0.18f), RoundedCornerShape(3.dp))
            .padding(horizontal = 14.dp, vertical = 8.dp),
        color = color,
        fontFamily = FontFamily.Monospace,
        fontWeight = FontWeight.Bold,
    )
}

@Composable
private fun RouteTimeline(origin: String, destination: String) {
    Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(14.dp)) {
        Column(horizontalAlignment = Alignment.CenterHorizontally) {
            Box(Modifier.size(18.dp).background(Color.White, RoundedCornerShape(50)).padding(3.dp)) {
                Box(Modifier.fillMaxSize().background(TppTheme.colors.forest, RoundedCornerShape(50)))
            }
            Box(Modifier.width(3.dp).height(64.dp).background(TppTheme.colors.outline.copy(alpha = 0.8f)))
            Box(Modifier.size(18.dp).background(TppTheme.colors.navy, RoundedCornerShape(50)))
        }
        Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(30.dp)) {
            Column {
                Text("SKĄD", color = TppTheme.colors.muted, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold)
                Text(origin, style = MaterialTheme.typography.titleLarge, color = TppTheme.colors.ink, fontWeight = FontWeight.Bold)
            }
            Column {
                Text("DOKĄD", color = TppTheme.colors.muted, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold)
                Text(destination, style = MaterialTheme.typography.titleLarge, color = TppTheme.colors.ink, fontWeight = FontWeight.Bold)
            }
        }
    }
}

@Composable
private fun ProfileScreen(
    state: DriverUiState,
    onRoutes: () -> Unit,
    onProfile: () -> Unit,
    onLogout: () -> Unit,
    onLeaveRequests: () -> Unit,
    onNewRouteNotificationsChanged: (Boolean) -> Unit,
    onOpenNotificationSettings: () -> Unit,
    onThemeModeChanged: (AppThemeMode) -> Unit,
    onRouteProgressNotificationChanged: (Boolean) -> Unit,
) {
    BoxWithConstraints(Modifier.fillMaxSize()) {
        val screenWidthDp = maxWidth.value.toInt()
        val screenHeightDp = maxHeight.value.toInt()
        val horizontalPadding = screenHorizontalPaddingDp(screenWidthDp).dp

        Scaffold(
            topBar = { StitchHeader(height = appBrandHeaderHeightDp(screenHeightDp).dp) },
            bottomBar = {
                StitchBottomBar(
                    activeScreen = DriverScreen.Profile,
                    height = bottomNavigationHeightDp(screenHeightDp).dp,
                    onRoutes = onRoutes,
                    onProfile = onProfile,
                )
            },
            containerColor = TppTheme.colors.surface,
        ) { padding ->
            Column(
                Modifier
                    .fillMaxSize()
                    .padding(padding)
                    .verticalScroll(rememberScrollState())
                    .padding(horizontal = horizontalPadding, vertical = 24.dp),
                verticalArrangement = Arrangement.spacedBy(18.dp),
            ) {
                Card(
                    modifier = Modifier.fillMaxWidth(),
                    colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
                    shape = MaterialTheme.shapes.medium,
                    border = BorderStroke(1.dp, TppTheme.colors.outline),
                ) {
                    Column(Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
                        Icon(Icons.Outlined.Person, contentDescription = null, tint = TppTheme.colors.forest, modifier = Modifier.size(34.dp))
                        Text(
                            "Witaj ${state.driver?.displayName.orEmpty().ifBlank { "Kierowco" }}",
                            style = MaterialTheme.typography.headlineSmall,
                            fontWeight = FontWeight.Bold,
                            color = TppTheme.colors.ink,
                        )
                    }
                }
                if (DriverLeaveRequestUiRules.isFeatureVisible(state.leaveRequestsConfig)) {
                    LeaveRequestsEntryCard(state.leaveRequests, onLeaveRequests)
                }
                Card(
                    modifier = Modifier.fillMaxWidth(),
                    colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
                    shape = MaterialTheme.shapes.medium,
                    border = BorderStroke(1.dp, TppTheme.colors.outline),
                ) {
                    Column(Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
                        Text(
                            "Ustawienia",
                            style = MaterialTheme.typography.titleMedium,
                            fontWeight = FontWeight.Bold,
                            color = TppTheme.colors.ink,
                        )
                        Row(
                            Modifier.fillMaxWidth(),
                            horizontalArrangement = Arrangement.spacedBy(14.dp),
                            verticalAlignment = Alignment.CenterVertically,
                        ) {
                            Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) {
                                Text(
                                    "Otrzymuj powiadomienia gdy spedytor doda nowy kurs",
                                    style = MaterialTheme.typography.bodyLarge,
                                    fontWeight = FontWeight.SemiBold,
                                    color = TppTheme.colors.ink,
                                )
                                Text(
                                    "Ta opcja może wprowadzać w błąd, ponieważ część kursów może zostać później usunięta.",
                                    style = MaterialTheme.typography.bodySmall,
                                    color = TppTheme.colors.muted,
                                )
                            }
                            Switch(
                                checked = state.notifyNewRoutes,
                                onCheckedChange = onNewRouteNotificationsChanged,
                                enabled = !state.refreshing && !state.loading,
                            )
                        }
                        if (state.notificationPermissionDenied) {
                            Button(
                                onClick = onOpenNotificationSettings,
                                modifier = Modifier.fillMaxWidth().height(48.dp),
                                colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.navy),
                                shape = MaterialTheme.shapes.small,
                            ) {
                                Text("Przejdź do ustawień", fontWeight = FontWeight.Bold)
                            }
                        }
                        HorizontalDivider(color = TppTheme.colors.outline.copy(alpha = 0.55f))
                        Row(
                            Modifier.fillMaxWidth(),
                            horizontalArrangement = Arrangement.spacedBy(14.dp),
                            verticalAlignment = Alignment.CenterVertically,
                        ) {
                            Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) {
                                Text(
                                    "Pasek postępu aktywnego kursu",
                                    style = MaterialTheme.typography.bodyLarge,
                                    fontWeight = FontWeight.SemiBold,
                                    color = TppTheme.colors.ink,
                                )
                                Text(
                                    if (state.routeProgressNotificationEnabled) "Powiadomienie pokazuje dystans do celu." else "Powiadomienie zostaje minimalne: Kurs aktywny.",
                                    style = MaterialTheme.typography.bodySmall,
                                    color = TppTheme.colors.muted,
                                )
                            }
                            Switch(
                                checked = state.routeProgressNotificationEnabled,
                                onCheckedChange = onRouteProgressNotificationChanged,
                                enabled = !state.loading,
                            )
                        }
                        HorizontalDivider(color = TppTheme.colors.outline.copy(alpha = 0.55f))
                        Row(
                            Modifier.fillMaxWidth(),
                            horizontalArrangement = Arrangement.spacedBy(14.dp),
                            verticalAlignment = Alignment.CenterVertically,
                        ) {
                            Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) {
                                Text(
                                    "Theme",
                                    style = MaterialTheme.typography.bodyLarge,
                                    fontWeight = FontWeight.SemiBold,
                                    color = TppTheme.colors.ink,
                                )
                                Text(
                                    if (state.themeMode == AppThemeMode.Material3) "Material 3" else "Aktualny",
                                    style = MaterialTheme.typography.bodySmall,
                                    color = TppTheme.colors.muted,
                                )
                            }
                            Switch(
                                checked = state.themeMode == AppThemeMode.Material3,
                                onCheckedChange = { enabled ->
                                    onThemeModeChanged(if (enabled) AppThemeMode.Material3 else AppThemeMode.Current)
                                },
                                enabled = !state.loading,
                            )
                        }
                    }
                }
                Button(
                    onClick = onLogout,
                    modifier = Modifier.fillMaxWidth().height(56.dp),
                    colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.forest),
                    shape = MaterialTheme.shapes.small,
                ) {
                    Text("Wyloguj", fontWeight = FontWeight.Bold)
                }
            }
        }
    }
}

private fun canPostNotifications(context: Context): Boolean =
    hasPostNotificationsRuntimePermission(context) &&
        NotificationManagerCompat.from(context).areNotificationsEnabled()

private fun hasPostNotificationsRuntimePermission(context: Context): Boolean =
    Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
        ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED

private fun openAppNotificationSettings(context: Context) {
    val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
            .putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
    } else {
        Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
            .setData(Uri.parse("package:${context.packageName}"))
    }

    context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}

@Composable
private fun StitchBottomBar(activeScreen: DriverScreen, height: Dp = 82.dp, onRoutes: () -> Unit, onProfile: () -> Unit) {
    Column(Modifier.fillMaxWidth().background(TppTheme.colors.card).navigationBarsPadding()) {
        HorizontalDivider(color = TppTheme.colors.outline.copy(alpha = 0.9f), thickness = 2.dp)
        Row(
            Modifier.fillMaxWidth().height(height),
            horizontalArrangement = Arrangement.SpaceEvenly,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            BottomNavItem("Zlecenia", Icons.Outlined.LocalShipping, active = activeScreen == DriverScreen.Routes, onClick = onRoutes)
            BottomNavItem("Profil", Icons.Outlined.Person, active = activeScreen == DriverScreen.Profile, onClick = onProfile)
        }
    }
}

@Composable
private fun BottomNavItem(label: String, icon: ImageVector, active: Boolean, onClick: () -> Unit) {
    Column(
        modifier = Modifier.heightIn(min = 64.dp).clickable(onClick = onClick).padding(horizontal = 18.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
    ) {
        Icon(icon, contentDescription = label, tint = if (active) TppTheme.colors.forest else TppTheme.colors.muted)
        Spacer(Modifier.height(6.dp))
        Text(label, color = if (active) TppTheme.colors.forest else TppTheme.colors.muted, fontFamily = FontFamily.Monospace)
    }
}

@Composable
private fun routeStatusColor(status: String): Color {
    val normalized = status.uppercase(Locale("pl", "PL"))
    return when {
        "NOW" in normalized -> TppTheme.colors.navy
        "ZAK" in normalized || "GOT" in normalized -> TppTheme.colors.forest
        "TRAK" in normalized || "START" in normalized -> Color(0xFFF5B800)
        else -> TppTheme.colors.forest
    }
}

@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
private fun RouteStageScreen(
    state: DriverUiState,
    title: String,
    stage: String,
    weightLabel: String,
    submitLabel: String,
    showWeightInput: Boolean = true,
    showPhotoActions: Boolean = true,
    showPhotoGrid: Boolean = true,
    onBack: () -> Unit,
    onWeightChange: (String) -> Unit,
    onUpload: (Uri, String, PhotoUploadMetadata) -> Unit,
    onPhoto: (RoutePhotoDto) -> Unit,
    onDeleteUpload: (PhotoUploadEntity) -> Unit,
    onRetryUpload: (PhotoUploadEntity) -> Unit,
    onSubmit: () -> Unit,
) {
    val context = LocalContext.current
    var cameraUri by remember { mutableStateOf<Uri?>(null) }
    var showPreciseLocationPermissionDialog by remember { mutableStateOf(false) }
    val route = state.displaySelectedRoute
    val stagePhotos = route?.photos?.let { routePhotosForStage(it, stage) }.orEmpty()
    val stageUploads = state.photoUploads.filter { normalizedRoutePhotoStage(it.stage) == stage }
    val attachmentCount = visiblePhotoAttachmentCount(stagePhotos.size, stageUploads.size)
    val submitBlocker = when {
        !showWeightInput -> loadingPhotosSubmitBlocker(
            serverPhotoCount = stagePhotos.size,
            localUploadCount = stageUploads.size,
        )
        stage == "loading" -> loadingWeightSubmitBlocker(
            weightText = state.routeStageWeightText,
            serverPhotoCount = stagePhotos.size,
            localUploadCount = stageUploads.size,
        )
        else -> routeStageSubmitBlocker(
            weightText = state.routeStageWeightText,
            serverPhotoCount = stagePhotos.size,
            localUploadCount = stageUploads.size,
        )
    }
    val weightBlocker = submitBlocker?.takeIf { it.contains("wag", ignoreCase = true) || it.contains("popraw", ignoreCase = true) }

    val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { ok ->
        val capturedUri = cameraUri
        if (
            ok &&
            capturedUri != null &&
            canLaunchCameraWithLocationPolicy(
                requirePreciseLocation = state.requirePreciseLocationForPhotos,
                hasFineLocation = hasPermission(context, Manifest.permission.ACCESS_FINE_LOCATION),
            )
        ) {
            onUpload(capturedUri, "camera", cameraPhotoMetadata(context))
        }
        cameraUri = null
    }
    val cameraPermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { grants ->
        val pendingUri = cameraUri
        val cameraGranted = grants[Manifest.permission.CAMERA] == true || hasPermission(context, Manifest.permission.CAMERA)
        val fineLocationGranted = grants[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
            hasPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
        if (!canLaunchCameraWithLocationPolicy(state.requirePreciseLocationForPhotos, fineLocationGranted)) {
            showPreciseLocationPermissionDialog = true
            cameraUri = null
            return@rememberLauncherForActivityResult
        }
        if (cameraGranted && pendingUri != null) cameraLauncher.launch(pendingUri)
    }
    val pickerLauncher = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
        if (uri != null) onUpload(uri, "gallery", PhotoUploadMetadata())
    }

    BoxWithConstraints(Modifier.fillMaxSize()) {
        val horizontalPadding = screenHorizontalPaddingDp(maxWidth.value.toInt()).dp
        Scaffold(
            topBar = { DetailHeader(onBack, title = title) },
            bottomBar = {
                RouteStageSubmitBar(
                    submitLabel = submitLabel,
                    submitBlocker = submitBlocker,
                    onSubmit = onSubmit,
                )
            },
            containerColor = TppTheme.colors.surface,
        ) { padding ->
            LazyColumn(
                Modifier.fillMaxSize().padding(padding),
                contentPadding = PaddingValues(start = horizontalPadding, top = 16.dp, end = horizontalPadding, bottom = 24.dp),
                verticalArrangement = Arrangement.spacedBy(16.dp),
            ) {
                if (route != null) {
                    item { RouteStageSummaryCard(route) }
                }
                item {
                    RouteStageChecklist(
                        hasValidWeight = if (showWeightInput) {
                            routeStageSubmitBlocker(
                                weightText = state.routeStageWeightText,
                                serverPhotoCount = 1,
                                localUploadCount = 0,
                            ) == null
                        } else {
                            null
                        },
                        photoCount = attachmentCount,
                        photoLabel = if (stage == "loading") "Zdjęcie załadunku" else "Zdjęcie etapu",
                    )
                }
                if (showWeightInput) {
                    item {
                        OutlinedTextField(
                            value = state.routeStageWeightText,
                            onValueChange = onWeightChange,
                            label = { Text(weightLabel) },
                            trailingIcon = {
                                Text(
                                    "kg",
                                    color = TppTheme.colors.muted,
                                    fontFamily = FontFamily.Monospace,
                                    fontWeight = FontWeight.Bold,
                                )
                            },
                            supportingText = {
                                if (weightBlocker != null) {
                                    Text(weightBlocker)
                                }
                            },
                            isError = weightBlocker != null,
                            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
                            singleLine = true,
                            modifier = Modifier.fillMaxWidth(),
                        )
                    }
                }
                if (showPhotoActions) {
                    item {
                        Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
                            CargoActionButton("Zrób nowe zdjęcie", Icons.Outlined.CameraAlt, TppTheme.colors.forest) {
                                val newUri = createCameraUri(context)
                                cameraUri = newUri
                                val missingPermissions = cameraCapturePermissions(
                                    context = context,
                                    requirePreciseLocation = state.requirePreciseLocationForPhotos,
                                )
                                if (missingPermissions.isEmpty()) {
                                    cameraLauncher.launch(newUri)
                                } else {
                                    cameraPermissionLauncher.launch(missingPermissions)
                                }
                            }
                            if (state.allowGalleryUploads) {
                                CargoActionButton("Dodaj zdjęcie z galerii", Icons.Outlined.AddPhotoAlternate, TppTheme.colors.containerGreen) {
                                    pickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
                                }
                            }
                        }
                    }
                }
                if (showPhotoGrid) {
                    item {
                        PhotoGrid(
                            photos = stagePhotos,
                            uploads = stageUploads,
                            deletingPhotoIds = state.deletingPhotoIds,
                            imageAuthHeader = state.imageAuthHeader,
                            onPhoto = onPhoto,
                            onDeletePhoto = {},
                            onDeleteUpload = onDeleteUpload,
                            onRetryUpload = onRetryUpload,
                        )
                    }
                }
                item { ErrorText(state.error) }
            }
        }
    }

    if (showPreciseLocationPermissionDialog) {
        AlertDialog(
            onDismissRequest = { showPreciseLocationPermissionDialog = false },
            title = { Text("Brak dokładnej lokalizacji", color = TppTheme.colors.ink, fontWeight = FontWeight.Bold) },
            text = {
                Text(
                    "Aby zrobić zdjęcie, nadaj aplikacji uprawnienie do dokładnej lokalizacji.",
                    color = TppTheme.colors.muted,
                )
            },
            confirmButton = {
                TextButton(
                    onClick = {
                        showPreciseLocationPermissionDialog = false
                        openAppSettings(context)
                    },
                ) {
                    Text("Przejdź do ustawień", color = TppTheme.colors.forest, fontWeight = FontWeight.Bold)
                }
            },
            dismissButton = {
                TextButton(onClick = { showPreciseLocationPermissionDialog = false }) {
                    Text("Anuluj", color = TppTheme.colors.muted)
                }
            },
        )
    }
}

@Composable
private fun RouteStageSummaryCard(route: DriverRouteDto) {
    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        border = BorderStroke(1.dp, TppTheme.colors.outline),
        shape = MaterialTheme.shapes.medium,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
            Text(
                route.relationLabel.ifBlank { "${route.originName} → ${route.destinationName}" },
                color = TppTheme.colors.ink,
                fontWeight = FontWeight.Bold,
                style = MaterialTheme.typography.titleMedium,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
            )
            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
                RouteStageSummaryFact("Zlecenie", route.contractCode ?: route.id, Modifier.weight(1f))
                RouteStageSummaryFact("Pojazd", route.truckLabel(), Modifier.weight(1f))
            }
        }
    }
}

@Composable
private fun RouteStageSummaryFact(label: String, value: String, modifier: Modifier = Modifier) {
    Column(
        modifier
            .background(TppTheme.colors.panel, RoundedCornerShape(4.dp))
            .padding(12.dp),
        verticalArrangement = Arrangement.spacedBy(4.dp),
    ) {
        Text(
            label.uppercase(Locale("pl", "PL")),
            color = TppTheme.colors.muted,
            fontFamily = FontFamily.Monospace,
            fontWeight = FontWeight.Bold,
            style = MaterialTheme.typography.labelSmall,
        )
        Text(
            value.ifBlank { "-" },
            color = TppTheme.colors.ink,
            fontWeight = FontWeight.Bold,
            maxLines = 2,
            overflow = TextOverflow.Ellipsis,
        )
    }
}

@Composable
private fun RouteStageChecklist(hasValidWeight: Boolean?, photoCount: Int, photoLabel: String) {
    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        border = BorderStroke(1.dp, TppTheme.colors.outline),
        shape = MaterialTheme.shapes.medium,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
            Text(
                "Wymagane przed wysłaniem",
                color = TppTheme.colors.ink,
                fontWeight = FontWeight.Bold,
                style = MaterialTheme.typography.titleMedium,
            )
            if (hasValidWeight != null) {
                RouteStageRequirementRow(
                    label = "Waga",
                    done = hasValidWeight,
                    detail = if (hasValidWeight) "uzupełniona" else "wpisz wartość w kg",
                )
            }
            RouteStageRequirementRow(
                label = photoLabel,
                done = photoCount > 0,
                detail = if (photoCount > 0) "$photoCount ${photoCountLabel(photoCount)}" else "dodaj minimum jedno",
            )
        }
    }
}

@Composable
private fun RouteStageRequirementRow(label: String, done: Boolean, detail: String) {
    val color = if (done) TppTheme.colors.forest else TppTheme.colors.muted
    Row(
        Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(12.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Box(
            Modifier
                .size(34.dp)
                .background(if (done) TppTheme.colors.successContainer else TppTheme.colors.panel, RoundedCornerShape(17.dp))
                .border(1.dp, color.copy(alpha = 0.38f), RoundedCornerShape(17.dp)),
            contentAlignment = Alignment.Center,
        ) {
            Icon(
                if (done) Icons.Outlined.CheckCircle else Icons.Outlined.Info,
                contentDescription = null,
                tint = color,
                modifier = Modifier.size(19.dp),
            )
        }
        Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
            Text(label, color = TppTheme.colors.ink, fontWeight = FontWeight.Bold)
            Text(detail, color = color, fontFamily = FontFamily.Monospace, style = MaterialTheme.typography.labelMedium)
        }
    }
}

@Composable
private fun RouteStageSubmitBar(
    submitLabel: String,
    submitBlocker: String?,
    onSubmit: () -> Unit,
) {
    Column(
        Modifier
            .fillMaxWidth()
            .background(TppTheme.colors.card)
            .border(1.dp, TppTheme.colors.outline.copy(alpha = 0.72f))
            .navigationBarsPadding()
            .padding(horizontal = 20.dp, vertical = 14.dp),
        verticalArrangement = Arrangement.spacedBy(10.dp),
    ) {
        if (submitBlocker != null) {
            Text(
                submitBlocker,
                color = TppTheme.colors.error,
                fontFamily = FontFamily.Monospace,
                fontWeight = FontWeight.Bold,
                style = MaterialTheme.typography.labelMedium,
            )
        }
        Button(
            onClick = onSubmit,
            enabled = submitBlocker == null,
            modifier = Modifier.fillMaxWidth().height(58.dp),
            colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.forest),
            shape = MaterialTheme.shapes.medium,
        ) {
            Text(submitLabel, color = Color.White, fontWeight = FontWeight.Bold)
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
private fun DetailScreen(
    state: DriverUiState,
    onBack: () -> Unit,
    onPhoto: (RoutePhotoDto) -> Unit,
    onRefresh: () -> Unit,
    onStartRoute: () -> Unit,
    onFinishRoute: () -> Unit,
) {
    val context = LocalContext.current
    val pullRefreshState = rememberPullRefreshState(state.refreshing, onRefresh)
    val projection = state.selectedRouteProjection

    BoxWithConstraints(Modifier.fillMaxSize()) {
        val horizontalPadding = screenHorizontalPaddingDp(maxWidth.value.toInt()).dp

        Scaffold(
            topBar = {
                DetailHeader(onBack)
            },
            containerColor = TppTheme.colors.surface,
        ) { padding ->
            val route = projection?.route ?: return@Scaffold
            Box(Modifier.fillMaxSize().padding(padding).pullRefresh(pullRefreshState)) {
                LazyColumn(
                    Modifier.fillMaxSize(),
                    contentPadding = PaddingValues(horizontal = horizontalPadding, vertical = 16.dp),
                    verticalArrangement = Arrangement.spacedBy(24.dp),
                ) {
                    item { ManifestSection(route, selectedDate = state.selectedDate, onNavigate = { openNavigation(context, it) }) }
                    item { OfflineStaleBanner(state) }
                    item {
                        RouteLifecycleSection(
                            route = route,
                            selectedDate = state.selectedDate,
                            feedback = state.feedback,
                            routeActions = projection.visibleActions,
                            routeProgressNotificationEnabled = state.routeProgressNotificationEnabled,
                            onRefresh = onRefresh,
                            onStart = onStartRoute,
                            onFinish = onFinishRoute,
                        )
                    }
                    item {
                        CargoDocumentationSection(
                            route = route,
                            uploads = state.photoUploads,
                            deletingPhotoIds = state.deletingPhotoIds,
                            imageAuthHeader = state.imageAuthHeader,
                            loadingWeightPending = projection.loadingWeightPending,
                            unloadingWeightPending = projection.unloadingWeightPending,
                            onPhoto = onPhoto,
                        )
                    }
                    item { ErrorText(state.error) }
                }
                PullRefreshIndicator(
                    refreshing = state.refreshing,
                    state = pullRefreshState,
                    modifier = Modifier.align(Alignment.TopCenter),
                    backgroundColor = TppTheme.colors.card,
                    contentColor = TppTheme.colors.forest,
                )
            }
        }
    }
}

@Composable
private fun DetailHeader(onBack: () -> Unit, title: String = "Szczegóły Trasy") {
    Column(Modifier.fillMaxWidth().background(TppTheme.colors.surface).statusBarsPadding()) {
        Row(
            Modifier.fillMaxWidth().height(64.dp).padding(start = 8.dp, end = 16.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) {
                Icon(Icons.Outlined.ArrowBack, contentDescription = "Wstecz", tint = TppTheme.colors.ink)
            }
            Text(
                title,
                style = MaterialTheme.typography.titleLarge,
                fontWeight = FontWeight.SemiBold,
                color = TppTheme.colors.ink,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.weight(1f).padding(start = 4.dp),
            )
        }
        HorizontalDivider(color = TppTheme.colors.outline, thickness = 1.dp)
    }
}

@Composable
private fun RouteLifecycleSection(
    route: DriverRouteDto,
    selectedDate: String,
    feedback: String?,
    routeActions: List<RouteActionEntity>,
    routeProgressNotificationEnabled: Boolean,
    onRefresh: () -> Unit,
    onStart: () -> Unit,
    onFinish: () -> Unit,
) {
    val steps = routeFlowSteps(route, routeActions)
    val callout = routeSyncCallout(routeActions)
    val action = routeLifecyclePrimaryAction(route, selectedDate)

    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
        RouteFlowStepper(steps)
        if (!feedback.isNullOrBlank()) {
            RouteStateMessage(
                text = feedback,
                icon = Icons.Outlined.CheckCircle,
                container = TppTheme.colors.successContainer,
                outline = TppTheme.colors.successOutline,
                color = TppTheme.colors.forest,
            )
        }

        if (callout != null) {
            RouteStateMessage(
                text = callout.text,
                icon = if (callout.isConflict) Icons.Outlined.Info else Icons.Outlined.CloudUpload,
                container = if (callout.isConflict) TppTheme.colors.warningContainer else TppTheme.colors.panel,
                outline = if (callout.isConflict) TppTheme.colors.warningOutline else TppTheme.colors.outline,
                color = if (callout.isConflict) TppTheme.colors.error else TppTheme.colors.muted,
                actionLabel = "Odśwież dane",
                onAction = onRefresh,
            )
        }

        if (route.status == "W TRAKCIE" && !routeProgressNotificationEnabled) {
            Text(
                "Powiadomienie działa w trybie minimalnym.",
                color = TppTheme.colors.muted,
                fontFamily = FontFamily.Monospace,
                style = MaterialTheme.typography.labelMedium,
                modifier = Modifier.padding(horizontal = 4.dp),
            )
        }

        if (action == null) return@Column

        Button(
            onClick = if (action.action == RouteLifecycleAction.Start) onStart else onFinish,
            modifier = Modifier.fillMaxWidth().height(64.dp),
            colors = ButtonDefaults.buttonColors(
                containerColor = TppTheme.colors.forest,
            ),
            shape = MaterialTheme.shapes.medium,
        ) {
            Icon(
                if (action.action == RouteLifecycleAction.Start) Icons.Outlined.LocalShipping else Icons.Outlined.CheckCircle,
                contentDescription = null,
                tint = Color.White,
            )
            Spacer(Modifier.width(10.dp))
            Text(
                action.label,
                color = Color.White,
                fontWeight = FontWeight.Bold,
            )
        }
    }
}

@Composable
private fun RouteFlowStepper(steps: List<RouteFlowStepUi>) {
    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        shape = MaterialTheme.shapes.medium,
        border = BorderStroke(1.dp, TppTheme.colors.outline),
        modifier = Modifier.fillMaxWidth(),
    ) {
        Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
            Text(
                "Przebieg kursu",
                color = TppTheme.colors.ink,
                fontWeight = FontWeight.Bold,
                style = MaterialTheme.typography.titleMedium,
            )
            Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
                steps.chunked(2).forEach { rowSteps ->
                    Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                        rowSteps.forEach { step ->
                            RouteFlowStepItem(step, Modifier.weight(1f))
                        }
                        if (rowSteps.size == 1) {
                            Spacer(Modifier.weight(1f))
                        }
                    }
                }
            }
        }
    }
}

@Composable
private fun RouteFlowStepItem(step: RouteFlowStepUi, modifier: Modifier = Modifier) {
    val color = routeFlowStepColor(step.state)
    Column(
        modifier
            .heightIn(min = 96.dp)
            .background(routeFlowStepBackground(step.state), RoundedCornerShape(4.dp))
            .border(1.dp, color.copy(alpha = 0.42f), RoundedCornerShape(4.dp))
            .padding(10.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        Icon(routeFlowStepIcon(step.state), contentDescription = null, tint = color, modifier = Modifier.size(21.dp))
        Text(
            step.title,
            color = TppTheme.colors.ink,
            fontWeight = FontWeight.Bold,
            style = MaterialTheme.typography.labelLarge,
            maxLines = 2,
            overflow = TextOverflow.Ellipsis,
        )
        Text(
            step.stateLabel,
            color = color,
            fontFamily = FontFamily.Monospace,
            fontWeight = FontWeight.Bold,
            style = MaterialTheme.typography.labelSmall,
            maxLines = 2,
            overflow = TextOverflow.Ellipsis,
        )
    }
}

@Composable
private fun routeFlowStepColor(state: RouteFlowStepState): Color =
    when (state) {
        RouteFlowStepState.Todo -> TppTheme.colors.muted
        RouteFlowStepState.LocalComplete -> TppTheme.colors.navy
        RouteFlowStepState.Syncing -> TppTheme.colors.containerGreen
        RouteFlowStepState.Confirmed -> TppTheme.colors.forest
        RouteFlowStepState.NeedsAttention -> TppTheme.colors.error
    }

@Composable
private fun routeFlowStepBackground(state: RouteFlowStepState): Color =
    when (state) {
        RouteFlowStepState.Todo -> TppTheme.colors.panel.copy(alpha = 0.62f)
        RouteFlowStepState.LocalComplete -> Color(0xFFE9F2FF)
        RouteFlowStepState.Syncing -> TppTheme.colors.successContainer.copy(alpha = 0.75f)
        RouteFlowStepState.Confirmed -> TppTheme.colors.successContainer
        RouteFlowStepState.NeedsAttention -> TppTheme.colors.warningContainer
    }

private fun routeFlowStepIcon(state: RouteFlowStepState): ImageVector =
    when (state) {
        RouteFlowStepState.Todo -> Icons.Outlined.Info
        RouteFlowStepState.LocalComplete -> Icons.Outlined.LocalShipping
        RouteFlowStepState.Syncing -> Icons.Outlined.CloudUpload
        RouteFlowStepState.Confirmed -> Icons.Outlined.CheckCircle
        RouteFlowStepState.NeedsAttention -> Icons.Outlined.Info
    }

@Composable
private fun RouteStateMessage(
    text: String,
    icon: ImageVector,
    container: Color,
    outline: Color,
    color: Color,
    actionLabel: String? = null,
    onAction: (() -> Unit)? = null,
) {
    Card(
        colors = CardDefaults.cardColors(containerColor = container),
        border = BorderStroke(1.dp, outline),
        shape = MaterialTheme.shapes.medium,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Row(
            Modifier.fillMaxWidth().padding(14.dp),
            horizontalArrangement = Arrangement.spacedBy(10.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Icon(icon, contentDescription = null, tint = color)
            Text(text, color = color, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
            if (actionLabel != null && onAction != null) {
                TextButton(onClick = onAction) {
                    Text(actionLabel, color = TppTheme.colors.forest, fontWeight = FontWeight.Bold)
                }
            }
        }
    }
}

private fun photoCountLabel(count: Int): String =
    when {
        count == 1 -> "zdjęcie"
        count in 2..4 -> "zdjęcia"
        else -> "zdjęć"
    }

@Composable
private fun ManifestSection(route: DriverRouteDto, selectedDate: String, onNavigate: (NavigationPointDto) -> Unit) {
    val stripColor = routeStatusColor(route.status)
    val routeDate = route.routeDate ?: selectedDate
    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        shape = MaterialTheme.shapes.medium,
        border = BorderStroke(1.dp, TppTheme.colors.outline),
    ) {
        Column {
            Column(Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
                Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
                    Row(
                        Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.spacedBy(10.dp),
                        verticalAlignment = Alignment.Top,
                    ) {
                        StatusChip(route.status, stripColor)
                        Box(Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) {
                            RouteDateStatusChip(routeDate)
                        }
                    }
                    Text(
                        "Zlecenie #${route.contractCode ?: route.id}",
                        style = MaterialTheme.typography.headlineSmall,
                        fontWeight = FontWeight.Bold,
                        color = TppTheme.colors.ink,
                    )
                }
            }
            HorizontalDivider(color = TppTheme.colors.outline.copy(alpha = 0.65f))
            RoutePointBlock(
                label = "Załadunek",
                title = route.originName.ifBlank { "Nie podano miejsca załadunku" },
                subtitle = route.contractorName,
                icon = Icons.Outlined.Factory,
                iconBackground = TppTheme.colors.panel,
                iconTint = TppTheme.colors.containerGreen,
                navigationPoint = route.originNavigation,
                onNavigate = onNavigate,
            )
            HorizontalDivider(color = TppTheme.colors.outline.copy(alpha = 0.45f))
            RoutePointBlock(
                label = "Rozładunek",
                title = route.destinationName.ifBlank { "Nie podano miejsca rozładunku" },
                subtitle = route.notes?.takeIf { it.isNotBlank() } ?: "Uwagi do zlecenia pojawią się tutaj.",
                icon = Icons.Outlined.LocationOn,
                iconBackground = TppTheme.colors.panel.copy(alpha = 0.72f),
                iconTint = TppTheme.colors.forest,
                framed = true,
                navigationPoint = route.destinationNavigation,
                onNavigate = onNavigate,
            )
            RouteFactsFooter(route)
        }
    }
}

@Composable
private fun RouteDateStatusChip(routeDate: String, modifier: Modifier = Modifier) {
    Row(
        modifier = modifier
            .background(Color(0xFFE9F2FF), RoundedCornerShape(3.dp))
            .padding(horizontal = 12.dp, vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        Icon(
            Icons.Outlined.CalendarToday,
            contentDescription = null,
            tint = Color(0xFF1D4E89),
            modifier = Modifier.size(16.dp),
        )
        Text(
            routeDateChipLabel(routeDate),
            color = Color(0xFF1D4E89),
            fontFamily = FontFamily.Monospace,
            fontWeight = FontWeight.Bold,
            maxLines = 2,
            overflow = TextOverflow.Ellipsis,
        )
    }
}

@Composable
private fun RoutePointBlock(
    label: String,
    title: String,
    subtitle: String,
    icon: ImageVector,
    iconBackground: Color,
    iconTint: Color,
    framed: Boolean = false,
    navigationPoint: NavigationPointDto? = null,
    onNavigate: (NavigationPointDto) -> Unit = {},
) {
    Row(
        Modifier.fillMaxWidth().padding(20.dp),
        horizontalArrangement = Arrangement.spacedBy(14.dp),
        verticalAlignment = Alignment.Top,
    ) {
        Box(
            Modifier.size(40.dp).background(iconBackground, RoundedCornerShape(20.dp)),
            contentAlignment = Alignment.Center,
        ) {
            Icon(icon, contentDescription = null, tint = iconTint, modifier = Modifier.size(22.dp))
        }
        Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) {
            Text(
                label.uppercase(Locale("pl", "PL")),
                color = TppTheme.colors.muted,
                fontFamily = FontFamily.Monospace,
                fontWeight = FontWeight.Bold,
                style = MaterialTheme.typography.labelMedium,
            )
            Text(title, color = TppTheme.colors.ink, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
            if (framed) {
                Box(Modifier.fillMaxWidth().background(TppTheme.colors.panel, RoundedCornerShape(4.dp)).padding(12.dp)) {
                    Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
                        Text("UWAGI DO ZLECENIA", color = TppTheme.colors.muted, fontFamily = FontFamily.Monospace, style = MaterialTheme.typography.labelSmall)
                        Text(subtitle, color = TppTheme.colors.ink, fontWeight = FontWeight.SemiBold)
                    }
                }
            } else {
                Text(subtitle, color = TppTheme.colors.muted, style = MaterialTheme.typography.bodyLarge)
            }
        }
        if (navigationPoint?.hasCoordinates() == true) {
            IconButton(
                onClick = { onNavigate(navigationPoint) },
                modifier = Modifier.size(42.dp).background(TppTheme.colors.forest, RoundedCornerShape(21.dp)),
            ) {
                Icon(Icons.Outlined.Navigation, contentDescription = "Nawiguj", tint = Color.White, modifier = Modifier.size(23.dp))
            }
        }
    }
}

@Composable
private fun RouteFactsFooter(route: DriverRouteDto) {
    BoxWithConstraints(Modifier.fillMaxWidth().background(TppTheme.colors.panel)) {
        val columns = routeFactColumns(maxWidth.value.toInt())

        Column(Modifier.fillMaxWidth().padding(20.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
            if (columns == 1) {
                RouteFact("Typ ładunku", route.contractName ?: route.contractorName, Modifier.fillMaxWidth())
                RouteFact("Pojazd", route.truckLabel(), Modifier.fillMaxWidth())
                RouteFact("Nr kontraktu", route.contractCode ?: "#${route.id}", Modifier.fillMaxWidth())
                RouteFact("Dystans", route.distanceLabel(), Modifier.fillMaxWidth())
            } else {
                Row(horizontalArrangement = Arrangement.spacedBy(14.dp), modifier = Modifier.fillMaxWidth()) {
                    RouteFact("Typ ładunku", route.contractName ?: route.contractorName, Modifier.weight(1f))
                    RouteFact("Pojazd", route.truckLabel(), Modifier.weight(1f))
                }
                Row(horizontalArrangement = Arrangement.spacedBy(14.dp), modifier = Modifier.fillMaxWidth()) {
                    RouteFact("Nr kontraktu", route.contractCode ?: "#${route.id}", Modifier.weight(1f))
                    RouteFact("Dystans", route.distanceLabel(), Modifier.weight(1f))
                }
            }
        }
    }
}

@Composable
private fun RouteFact(label: String, value: String, modifier: Modifier = Modifier) {
    Column(modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {
        Text(label, color = TppTheme.colors.muted, fontFamily = FontFamily.Monospace, style = MaterialTheme.typography.labelSmall)
        Text(value.ifBlank { "-" }, color = TppTheme.colors.ink, fontWeight = FontWeight.Bold)
    }
}

@Composable
private fun CargoDocumentationSection(
    route: DriverRouteDto,
    uploads: List<PhotoUploadEntity>,
    deletingPhotoIds: Set<String>,
    imageAuthHeader: String?,
    loadingWeightPending: Boolean,
    unloadingWeightPending: Boolean,
    onPhoto: (RoutePhotoDto) -> Unit,
) {
    val loadingPhotos = routePhotosForStage(route.photos, "loading")
    val unloadingPhotos = routePhotosForStage(route.photos, "unloading")
    val otherPhotos = routePhotosForStage(route.photos, "other")
    val loadingUploads = uploads.filter { normalizedRoutePhotoStage(it.stage) == "loading" }
    val unloadingUploads = uploads.filter { normalizedRoutePhotoStage(it.stage) == "unloading" }
    val otherUploads = uploads.filter { normalizedRoutePhotoStage(it.stage) == "other" }

    Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
        Column {
            Text("Dokumentacja ładunku", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = TppTheme.colors.ink)
            Spacer(Modifier.height(8.dp))
            Box(Modifier.width(116.dp).height(3.dp).background(TppTheme.colors.containerGreen))
        }
        RouteStageDocumentationBlock(
            title = "Załadunek",
            weight = route.loadingWeight,
            photos = loadingPhotos,
            uploads = loadingUploads,
            deletingPhotoIds = deletingPhotoIds,
            imageAuthHeader = imageAuthHeader,
            weightPending = loadingWeightPending,
            onPhoto = onPhoto,
        )
        RouteStageDocumentationBlock(
            title = "Rozładunek",
            weight = route.unloadingWeight,
            photos = unloadingPhotos,
            uploads = unloadingUploads,
            deletingPhotoIds = deletingPhotoIds,
            imageAuthHeader = imageAuthHeader,
            weightPending = unloadingWeightPending,
            onPhoto = onPhoto,
        )
        if (otherPhotos.isNotEmpty() || otherUploads.isNotEmpty()) {
            RouteStageDocumentationBlock(
                title = "Pozostałe",
                weight = null,
                photos = otherPhotos,
                uploads = otherUploads,
                deletingPhotoIds = deletingPhotoIds,
                imageAuthHeader = imageAuthHeader,
                weightPending = false,
                onPhoto = onPhoto,
            )
        }
    }
}

@Composable
private fun RouteStageDocumentationBlock(
    title: String,
    weight: Double?,
    photos: List<RoutePhotoDto>,
    uploads: List<PhotoUploadEntity>,
    deletingPhotoIds: Set<String>,
    imageAuthHeader: String?,
    weightPending: Boolean = false,
    onPhoto: (RoutePhotoDto) -> Unit,
) {
    val count = visiblePhotoAttachmentCount(photos.size, uploads.size)
    Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
        Row(
            Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Text(title, color = TppTheme.colors.ink, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleMedium)
            Column(horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.spacedBy(2.dp)) {
                Text(
                    weight?.let { "Waga: ${formatRouteWeight(it)}" } ?: "Waga: -",
                    color = TppTheme.colors.muted,
                    fontFamily = FontFamily.Monospace,
                    fontWeight = FontWeight.Bold,
                )
                if (weightPending && weight != null) {
                    Text(
                        "czeka na wysłanie",
                        color = TppTheme.colors.containerGreen,
                        fontFamily = FontFamily.Monospace,
                        fontWeight = FontWeight.Bold,
                        style = MaterialTheme.typography.labelSmall,
                    )
                }
            }
        }
        if (count == 0) {
            Box(
                Modifier
                    .fillMaxWidth()
                    .background(TppTheme.colors.panel, RoundedCornerShape(4.dp))
                    .border(1.dp, TppTheme.colors.outline.copy(alpha = 0.55f), RoundedCornerShape(4.dp))
                    .padding(14.dp),
            ) {
                Text(
                    "Brak zdjęć ${title.lowercase(Locale("pl", "PL"))}",
                    color = TppTheme.colors.muted,
                    fontFamily = FontFamily.Monospace,
                    fontWeight = FontWeight.Bold,
                    style = MaterialTheme.typography.labelMedium,
                )
            }
        } else {
            PhotoGrid(
                photos = photos,
                uploads = uploads,
                deletingPhotoIds = deletingPhotoIds,
                imageAuthHeader = imageAuthHeader,
                onPhoto = onPhoto,
                onDeletePhoto = {},
                onDeleteUpload = {},
                onRetryUpload = {},
                readOnly = true,
            )
        }
    }
}

private fun formatRouteWeight(weight: Double): String =
    if (weight % 1.0 == 0.0) "${weight.toInt()} kg" else String.format(Locale("pl", "PL"), "%.2f kg", weight)

@Composable
private fun CargoActionButton(label: String, icon: ImageVector, color: Color, onClick: () -> Unit) {
    Button(
        onClick = onClick,
        modifier = Modifier.fillMaxWidth().height(64.dp),
        colors = ButtonDefaults.buttonColors(containerColor = color),
        shape = MaterialTheme.shapes.medium,
    ) {
        Icon(icon, contentDescription = null, tint = Color.White)
        Spacer(Modifier.width(10.dp))
        Text(label, color = Color.White, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
    }
}

@Composable
private fun PhotoGrid(
    photos: List<RoutePhotoDto>,
    uploads: List<PhotoUploadEntity>,
    deletingPhotoIds: Set<String>,
    imageAuthHeader: String?,
    onPhoto: (RoutePhotoDto) -> Unit,
    onDeletePhoto: (RoutePhotoDto) -> Unit,
    onDeleteUpload: (PhotoUploadEntity) -> Unit,
    onRetryUpload: (PhotoUploadEntity) -> Unit,
    readOnly: Boolean = false,
) {
    var pendingDelete by remember { mutableStateOf<PhotoDeleteTarget?>(null) }
    val items = (uploads.map { PhotoGridItem.Upload(it) } + photos.map { PhotoGridItem.Server(it) })
        .sortedByDescending { it.sortEpochMillis }
    val visibleAttachmentCount = visiblePhotoAttachmentCount(photos.size, uploads.size)

    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card),
        shape = MaterialTheme.shapes.medium,
        border = BorderStroke(1.dp, TppTheme.colors.outline),
    ) {
        BoxWithConstraints(Modifier.fillMaxWidth()) {
            val compactWidth = maxWidth < 340.dp
            val contentPadding = if (compactWidth) 12.dp else 20.dp
            val gridGap = if (compactWidth) 8.dp else 12.dp

            Column(Modifier.padding(contentPadding), verticalArrangement = Arrangement.spacedBy(16.dp)) {
                Text(
                    "Załączone zdjęcia ($visibleAttachmentCount)",
                    color = TppTheme.colors.muted,
                    fontFamily = FontFamily.Monospace,
                    fontWeight = FontWeight.SemiBold,
                )
                if (items.isEmpty()) {
                    EmptyPhotoState()
                } else {
                    Column(verticalArrangement = Arrangement.spacedBy(gridGap)) {
                        repeat(inlinePhotoGridRows(items.size)) { rowIndex ->
                            val startIndex = rowIndex * 2
                            val rowItems = items.subList(startIndex, minOf(startIndex + 2, items.size))
                            Row(horizontalArrangement = Arrangement.spacedBy(gridGap), modifier = Modifier.fillMaxWidth()) {
                                rowItems.forEach { item ->
                                    when (item) {
                                        is PhotoGridItem.Server -> PhotoTile(
                                            photo = item.photo,
                                            imageAuthHeader = imageAuthHeader,
                                            isDeleting = item.photo.id in deletingPhotoIds,
                                            onPhoto = onPhoto,
                                            onDeletePhoto = { pendingDelete = PhotoDeleteTarget.Server(it) },
                                            readOnly = readOnly,
                                            modifier = Modifier.weight(1f),
                                        )
                                        is PhotoGridItem.Upload -> PendingPhotoTile(
                                            upload = item.upload,
                                            isDeleting = item.upload.serverPhotoId?.let { it in deletingPhotoIds } == true,
                                            onPhoto = { upload -> localUploadPreviewPhoto(upload)?.let(onPhoto) },
                                            onDelete = { pendingDelete = PhotoDeleteTarget.Upload(it) },
                                            onRetry = onRetryUpload,
                                            readOnly = readOnly,
                                            modifier = Modifier.weight(1f),
                                        )
                                    }
                                }
                                if (rowItems.size == 1) {
                                    Spacer(Modifier.weight(1f))
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    pendingDelete?.let { target ->
        AlertDialog(
            onDismissRequest = { pendingDelete = null },
            title = { Text("Usunąć zdjęcie?", color = TppTheme.colors.ink, fontWeight = FontWeight.Bold) },
            text = { Text("Czy na pewno chcesz usunąć to zdjęcie?", color = TppTheme.colors.muted) },
            confirmButton = {
                TextButton(
                    onClick = {
                        pendingDelete = null
                        when (target) {
                            is PhotoDeleteTarget.Server -> onDeletePhoto(target.photo)
                            is PhotoDeleteTarget.Upload -> onDeleteUpload(target.upload)
                        }
                    },
                ) {
                    Text("Usuń", color = TppTheme.colors.error, fontWeight = FontWeight.Bold)
                }
            },
            dismissButton = {
                TextButton(onClick = { pendingDelete = null }) {
                    Text("Anuluj", color = TppTheme.colors.muted)
                }
            },
        )
    }
}

private sealed class PhotoGridItem(open val sortEpochMillis: Long) {
    data class Server(val photo: RoutePhotoDto) : PhotoGridItem(
        routePhotoSortEpochMillis(photo.createdAt, photo.takenAt, fallback = 0L),
    )

    data class Upload(val upload: PhotoUploadEntity) : PhotoGridItem(upload.createdAtEpochMillis)
}

private sealed class PhotoDeleteTarget {
    data class Server(val photo: RoutePhotoDto) : PhotoDeleteTarget()
    data class Upload(val upload: PhotoUploadEntity) : PhotoDeleteTarget()
}

@Composable
private fun PhotoTile(
    photo: RoutePhotoDto,
    imageAuthHeader: String?,
    isDeleting: Boolean,
    onPhoto: (RoutePhotoDto) -> Unit,
    onDeletePhoto: (RoutePhotoDto) -> Unit,
    readOnly: Boolean = false,
    modifier: Modifier = Modifier,
) {
    val hasPhoto = photo.url.isNotBlank()
    var imageLoading by remember(photo.url) { mutableStateOf(hasPhoto) }
    var imageError by remember(photo.url) { mutableStateOf(!hasPhoto) }
    Box(modifier.aspectRatio(1f).background(TppTheme.colors.panel, RoundedCornerShape(4.dp))) {
        AsyncImage(
            model = imageRequest(photo.url, imageAuthHeader),
            contentDescription = "Zdjecie ladunku",
            contentScale = ContentScale.Crop,
            onLoading = {
                imageLoading = true
                imageError = false
            },
            onSuccess = {
                imageLoading = false
                imageError = false
            },
            onError = {
                imageLoading = false
                imageError = true
            },
            modifier = Modifier.fillMaxSize().clickable { onPhoto(photo) },
        )
        AnimatedVisibility(
            visible = shouldShowPhotoTileLoadingState(hasPhoto, imageLoading, imageError),
            enter = fadeIn(),
            exit = fadeOut(),
            modifier = Modifier.align(Alignment.Center),
        ) {
            PhotoTileLoadingOverlay()
        }
        AnimatedVisibility(
            visible = shouldShowPhotoTileErrorState(hasPhoto, imageError),
            enter = fadeIn(),
            exit = fadeOut(),
            modifier = Modifier.align(Alignment.Center),
        ) {
            PhotoTileErrorOverlay()
        }
        if (isDeleting) {
            Box(
                Modifier.fillMaxSize().background(Color.White.copy(alpha = 0.72f)),
                contentAlignment = Alignment.Center,
            ) {
                Text("Usuwam", color = TppTheme.colors.muted, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold)
            }
        }
        if (!readOnly && photo.canDelete) {
            IconButton(
                onClick = { onDeletePhoto(photo) },
                enabled = !isDeleting,
                modifier = Modifier
                    .align(Alignment.TopEnd)
                    .padding(8.dp)
                    .size(40.dp)
                    .background(TppTheme.colors.surface.copy(alpha = 0.96f), RoundedCornerShape(20.dp))
                    .border(1.dp, TppTheme.colors.error.copy(alpha = 0.35f), RoundedCornerShape(20.dp)),
            ) {
                Icon(
                    Icons.Outlined.Delete,
                    contentDescription = "Usuń zdjęcie",
                    tint = TppTheme.colors.error,
                    modifier = Modifier.size(19.dp),
                )
            }
        }
    }
}

@Composable
private fun PendingPhotoTile(
    upload: PhotoUploadEntity,
    isDeleting: Boolean,
    onPhoto: (PhotoUploadEntity) -> Unit,
    onDelete: (PhotoUploadEntity) -> Unit,
    onRetry: (PhotoUploadEntity) -> Unit,
    readOnly: Boolean = false,
    modifier: Modifier = Modifier,
) {
    val status = upload.statusType
    val canDeleteServerPhoto = confirmedUploadServerPhotoId(upload.status, upload.serverPhotoId) != null
    val hasFailureDetails = canShowPhotoUploadFailureDetails(upload.status, upload.lastError)
    var showFailureDetails by remember { mutableStateOf(false) }
    if (showFailureDetails) {
        PhotoUploadFailureDialog(
            details = upload.lastError.orEmpty(),
            onDismiss = { showFailureDetails = false },
        )
    }
    val hasPhoto = upload.localPath.isNotBlank()
    var imageLoading by remember(upload.localPath) { mutableStateOf(hasPhoto) }
    var imageError by remember(upload.localPath) { mutableStateOf(!hasPhoto) }
    Box(modifier.aspectRatio(1f).background(TppTheme.colors.panel, RoundedCornerShape(4.dp))) {
        AsyncImage(
            model = File(upload.localPath),
            contentDescription = "Zdjęcie oczekujące na zapis",
            contentScale = ContentScale.Crop,
            onLoading = {
                imageLoading = true
                imageError = false
            },
            onSuccess = {
                imageLoading = false
                imageError = false
            },
            onError = {
                imageLoading = false
                imageError = true
            },
            modifier = Modifier.fillMaxSize().clickable { onPhoto(upload) },
        )
        Box(Modifier.fillMaxSize().background(Color.Black.copy(alpha = 0.34f)))
        AnimatedVisibility(
            visible = shouldShowPhotoTileLoadingState(hasPhoto, imageLoading, imageError),
            enter = fadeIn(),
            exit = fadeOut(),
            modifier = Modifier.align(Alignment.Center),
        ) {
            PhotoTileLoadingOverlay()
        }
        AnimatedVisibility(
            visible = shouldShowPhotoTileErrorState(hasPhoto, imageError),
            enter = fadeIn(),
            exit = fadeOut(),
            modifier = Modifier.align(Alignment.Center),
        ) {
            PhotoTileErrorOverlay()
        }
        if (isDeleting) {
            Box(
                Modifier.fillMaxSize().background(Color.White.copy(alpha = 0.72f)),
                contentAlignment = Alignment.Center,
            ) {
                Text("Usuwam", color = TppTheme.colors.muted, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold)
            }
        }
        if (!readOnly && canDeleteServerPhoto) {
            IconButton(
                onClick = { onDelete(upload) },
                enabled = !isDeleting,
                modifier = Modifier
                    .align(Alignment.TopEnd)
                    .padding(8.dp)
                    .size(40.dp)
                    .background(TppTheme.colors.surface.copy(alpha = 0.96f), RoundedCornerShape(20.dp))
                    .border(1.dp, TppTheme.colors.error.copy(alpha = 0.35f), RoundedCornerShape(20.dp)),
            ) {
                Icon(
                    Icons.Outlined.Delete,
                    contentDescription = "Usuń zdjęcie",
                    tint = TppTheme.colors.error,
                    modifier = Modifier.size(19.dp),
                )
            }
        }
        if (hasFailureDetails) {
            IconButton(
                onClick = { showFailureDetails = true },
                enabled = !isDeleting,
                modifier = Modifier
                    .align(Alignment.TopStart)
                    .padding(8.dp)
                    .size(40.dp)
                    .background(TppTheme.colors.surface.copy(alpha = 0.96f), RoundedCornerShape(20.dp))
                    .border(1.dp, TppTheme.colors.error.copy(alpha = 0.35f), RoundedCornerShape(20.dp)),
            ) {
                Icon(
                    Icons.Outlined.Info,
                    contentDescription = "Pokaż szczegóły błędu wysłania",
                    tint = TppTheme.colors.error,
                    modifier = Modifier.size(20.dp),
                )
            }
        }
        if (!readOnly && canRetryPhotoUpload(upload.status)) {
            IconButton(
                onClick = { onRetry(upload) },
                enabled = !isDeleting,
                modifier = Modifier
                    .align(Alignment.TopEnd)
                    .padding(8.dp)
                    .size(40.dp)
                    .background(TppTheme.colors.surface.copy(alpha = 0.96f), RoundedCornerShape(20.dp))
                    .border(1.dp, TppTheme.colors.forest.copy(alpha = 0.35f), RoundedCornerShape(20.dp)),
            ) {
                Icon(
                    Icons.Outlined.Refresh,
                    contentDescription = "Ponów wysłanie zdjęcia",
                    tint = TppTheme.colors.forest,
                    modifier = Modifier.size(20.dp),
                )
            }
        }
        Column(
            Modifier.align(Alignment.BottomStart).fillMaxWidth().background(Color.White.copy(alpha = 0.94f)).padding(10.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp),
        ) {
            Text(
                upload.lastError?.lineSequence()?.firstOrNull { it.isNotBlank() }?.takeIf { hasFailureDetails } ?: status.label,
                color = if (hasFailureDetails) TppTheme.colors.error else TppTheme.colors.ink,
                fontFamily = FontFamily.Monospace,
                fontWeight = FontWeight.Bold,
                style = MaterialTheme.typography.labelMedium,
            )
            if (status == PhotoUploadStatus.Uploading || status == PhotoUploadStatus.Verifying) {
                androidx.compose.material3.LinearProgressIndicator(
                    progress = { (upload.progress.coerceIn(0, 100) / 100f) },
                    modifier = Modifier.fillMaxWidth(),
                    color = TppTheme.colors.forest,
                    trackColor = TppTheme.colors.outline.copy(alpha = 0.4f),
                )
            }
        }
    }
}

@Composable
private fun PhotoTileLoadingOverlay() {
    Box(
        modifier = Modifier
            .size(54.dp)
            .background(TppTheme.colors.card.copy(alpha = 0.94f), RoundedCornerShape(8.dp))
            .border(1.dp, TppTheme.colors.outline.copy(alpha = 0.7f), RoundedCornerShape(8.dp)),
        contentAlignment = Alignment.Center,
    ) {
        CircularProgressIndicator(
            modifier = Modifier.size(25.dp),
            color = TppTheme.colors.forest,
            trackColor = TppTheme.colors.outline.copy(alpha = 0.32f),
            strokeWidth = 2.5.dp,
        )
    }
}

@Composable
private fun PhotoTileErrorOverlay() {
    Column(
        modifier = Modifier
            .background(TppTheme.colors.card.copy(alpha = 0.96f), RoundedCornerShape(8.dp))
            .border(1.dp, TppTheme.colors.error.copy(alpha = 0.36f), RoundedCornerShape(8.dp))
            .padding(horizontal = 10.dp, vertical = 8.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(2.dp),
    ) {
        Text(
            "Błąd",
            color = TppTheme.colors.error,
            fontFamily = FontFamily.Monospace,
            fontWeight = FontWeight.Bold,
            style = MaterialTheme.typography.labelMedium,
        )
        Text(
            "miniatury",
            color = TppTheme.colors.muted,
            fontFamily = FontFamily.Monospace,
            style = MaterialTheme.typography.labelSmall,
        )
    }
}

@Composable
private fun EmptyPhotoState() {
    Box(
        Modifier.fillMaxWidth().height(132.dp).background(TppTheme.colors.panel, RoundedCornerShape(4.dp)),
        contentAlignment = Alignment.Center,
    ) {
        Text("Brak załączonych zdjęć", color = TppTheme.colors.muted, fontFamily = FontFamily.Monospace)
    }
}

private fun DriverRouteDto.truckLabel(): String {
    val registration = truck?.registration.orEmpty()
    val trailerRegistration = truck?.trailerRegistration.orEmpty()
    val name = truck?.name.orEmpty()

    val vehicle = when {
        registration.isNotBlank() && name.isNotBlank() -> "$registration ($name)"
        registration.isNotBlank() -> registration
        name.isNotBlank() -> name
        else -> "-"
    }

    return if (trailerRegistration.isNotBlank()) {
        "$vehicle\nNaczepa: $trailerRegistration"
    } else {
        vehicle
    }
}

private fun DriverRouteDto.distanceLabel(): String =
    distanceKm?.takeIf { it > 0 }?.let { "%.1f km".format(Locale.US, it) } ?: "-"

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun PhotoScreen(state: DriverUiState, onBack: () -> Unit) {
    val photo = state.selectedPhoto
    val hasPhoto = !photo?.url.isNullOrBlank()
    var imageLoading by remember(photo?.url) { mutableStateOf(hasPhoto) }
    var imageError by remember(photo?.url) { mutableStateOf(!hasPhoto) }
    val viewerBackground = Color(0xFF050805)
    Scaffold(
        topBar = {
            TopAppBar(
                modifier = Modifier.statusBarsPadding(),
                title = { Text("Podglad zdjecia") },
                navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.Outlined.ArrowBack, contentDescription = "Wstecz") } },
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor = viewerBackground,
                    titleContentColor = Color.White,
                    navigationIconContentColor = Color.White,
                ),
            )
        },
        containerColor = viewerBackground,
    ) { padding ->
        Box(Modifier.fillMaxSize().padding(padding).background(viewerBackground)) {
            AsyncImage(
                model = imageRequest(photo?.url, state.imageAuthHeader),
                contentDescription = "Zdjecie ladunku",
                contentScale = ContentScale.Fit,
                onLoading = {
                    imageLoading = true
                    imageError = false
                },
                onSuccess = {
                    imageLoading = false
                    imageError = false
                },
                onError = {
                    imageLoading = false
                    imageError = true
                },
                modifier = Modifier.fillMaxSize(),
            )
            AnimatedVisibility(
                visible = shouldShowPhotoPreviewLoadingState(hasPhoto, imageLoading, imageError),
                enter = fadeIn(),
                exit = fadeOut(),
                modifier = Modifier.align(Alignment.Center),
            ) {
                PhotoPreviewLoadingOverlay()
            }
            AnimatedVisibility(
                visible = shouldShowPhotoPreviewErrorState(hasPhoto, imageError),
                enter = fadeIn(),
                exit = fadeOut(),
                modifier = Modifier.align(Alignment.Center),
            ) {
                PhotoPreviewErrorOverlay()
            }
        }
    }
}

@Composable
private fun PhotoPreviewLoadingOverlay() {
    Column(
        modifier = Modifier
            .background(Color(0xFF111710).copy(alpha = 0.94f), RoundedCornerShape(8.dp))
            .border(1.dp, Color(0xFF609830).copy(alpha = 0.42f), RoundedCornerShape(8.dp))
            .padding(horizontal = 24.dp, vertical = 22.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(10.dp),
    ) {
        CircularProgressIndicator(
            modifier = Modifier.size(36.dp),
            color = Color(0xFF8EC45D),
            trackColor = Color.White.copy(alpha = 0.12f),
            strokeWidth = 3.dp,
        )
        Text(
            "Ładuję zdjęcie",
            color = Color.White,
            style = MaterialTheme.typography.titleMedium,
            fontWeight = FontWeight.Bold,
        )
        Text(
            "Pobieram plik z serwera",
            color = Color.White.copy(alpha = 0.72f),
            style = MaterialTheme.typography.bodySmall,
            fontFamily = FontFamily.Monospace,
        )
    }
}

@Composable
private fun PhotoPreviewErrorOverlay() {
    Column(
        modifier = Modifier
            .background(Color(0xFF171111).copy(alpha = 0.94f), RoundedCornerShape(8.dp))
            .border(1.dp, TppTheme.colors.error.copy(alpha = 0.5f), RoundedCornerShape(8.dp))
            .padding(horizontal = 24.dp, vertical = 22.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        Text(
            "Nie udało się wczytać zdjęcia",
            color = Color.White,
            style = MaterialTheme.typography.titleMedium,
            fontWeight = FontWeight.Bold,
        )
        Text(
            "Wróć i spróbuj ponownie",
            color = Color.White.copy(alpha = 0.72f),
            style = MaterialTheme.typography.bodySmall,
            fontFamily = FontFamily.Monospace,
        )
    }
}

@Composable
private fun PrimaryButton(label: String, onClick: () -> Unit) {
    Button(
        onClick = onClick,
        modifier = Modifier.fillMaxWidth().height(56.dp),
        colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.forest),
        shape = MaterialTheme.shapes.small,
    ) { Text(label, fontWeight = FontWeight.Bold) }
}

@Composable
private fun EmptyState(text: String) {
    Card(colors = CardDefaults.cardColors(containerColor = TppTheme.colors.card), shape = MaterialTheme.shapes.medium) {
        Text(text, modifier = Modifier.padding(24.dp), color = TppTheme.colors.muted)
    }
}

@Composable
private fun ErrorText(error: String?) {
    if (!error.isNullOrBlank()) Text(error, color = TppTheme.colors.error, modifier = Modifier.padding(vertical = 8.dp))
}

@Composable
private fun OfflineStaleBanner(state: DriverUiState) {
    val syncLabel = state.lastSuccessfulSyncAtEpochMillis?.let(::formatSyncTime) ?: "brak zapisanej synchronizacji"
    val message = offlineStaleBannerMessage(
        isOnline = state.isOnline,
        isStale = state.isStale,
        syncLabel = syncLabel,
    ) ?: return

    Card(
        colors = CardDefaults.cardColors(containerColor = TppTheme.colors.warningContainer),
        border = BorderStroke(1.dp, TppTheme.colors.warningOutline),
        shape = MaterialTheme.shapes.small,
        modifier = Modifier.fillMaxWidth(),
    ) {
        Text(
            text = message,
            color = Color(0xFF5F4200),
            fontWeight = FontWeight.SemiBold,
            modifier = Modifier.padding(12.dp),
        )
    }
}

private fun formatSyncTime(epochMillis: Long): String =
    runCatching {
        DateTimeFormatter.ofPattern("HH:mm", Locale("pl", "PL"))
            .format(Instant.ofEpochMilli(epochMillis).atZone(ZoneId.systemDefault()))
    }.getOrDefault("nieznana godzina")

private fun polishPhoneDigits(value: String): String {
    val digits = value.filter(Char::isDigit)
    val withoutCountryCode = if (digits.length > 9 && digits.startsWith("48")) digits.drop(2) else digits

    return withoutCountryCode.take(9)
}

private fun createCameraUri(context: Context): Uri {
    val dir = File(context.cacheDir, "camera").apply { mkdirs() }
    val file = File.createTempFile("ladunek-", ".jpg", dir)
    return FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
}

private fun NavigationPointDto.hasCoordinates(): Boolean = latitude != null && longitude != null

private fun openNavigation(context: Context, point: NavigationPointDto) {
    val latitude = point.latitude ?: return
    val longitude = point.longitude ?: return
    val googleMapsIntent = Intent(Intent.ACTION_VIEW, Uri.parse("google.navigation:q=$latitude,$longitude"))
        .setPackage("com.google.android.apps.maps")

    if (googleMapsIntent.resolveActivity(context.packageManager) != null) {
        context.startActivity(googleMapsIntent)
        return
    }

    val label = Uri.encode(point.label?.takeIf { it.isNotBlank() } ?: "Cel")
    val geoIntent = Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q=$latitude,$longitude($label)"))
    if (geoIntent.resolveActivity(context.packageManager) != null) {
        context.startActivity(geoIntent)
    }
}

private fun cameraCapturePermissions(context: Context, requirePreciseLocation: Boolean): Array<String> = buildList {
    if (!hasPermission(context, Manifest.permission.CAMERA)) {
        add(Manifest.permission.CAMERA)
    }
    if (requirePreciseLocation) {
        if (!hasPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
            add(Manifest.permission.ACCESS_FINE_LOCATION)
        }
    } else if (!hasLocationPermission(context)) {
        add(Manifest.permission.ACCESS_FINE_LOCATION)
        add(Manifest.permission.ACCESS_COARSE_LOCATION)
    }
}.toTypedArray()

private fun cameraPhotoMetadata(context: Context): PhotoUploadMetadata {
    val location = bestLastKnownLocation(context)

    return PhotoUploadMetadata(
        takenAt = Instant.now().toString(),
        latitude = location?.latitude,
        longitude = location?.longitude,
        locationAccuracyMeters = location?.accuracy?.toDouble(),
    )
}

private fun hasPermission(context: Context, permission: String): Boolean =
    ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED

private fun hasLocationPermission(context: Context): Boolean =
    hasPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ||
        hasPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)

private fun openAppSettings(context: Context) {
    val intent = Intent(
        Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
        Uri.fromParts("package", context.packageName, null),
    )
    context.startActivity(intent)
}

private fun bestLastKnownLocation(context: Context): Location? {
    if (!hasLocationPermission(context)) return null

    val manager = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager ?: return null
    val providers = listOf(
        LocationManager.GPS_PROVIDER,
        LocationManager.NETWORK_PROVIDER,
        LocationManager.PASSIVE_PROVIDER,
    )

    return providers.mapNotNull { provider ->
        runCatching {
            if (manager.isProviderEnabled(provider) || provider == LocationManager.PASSIVE_PROVIDER) {
                manager.getLastKnownLocation(provider)
            } else {
                null
            }
        }.getOrNull()
    }.maxByOrNull { it.time }
}

@Composable
private fun SmsUserConsentEffect(enabled: Boolean, onCode: (String) -> Unit) {
    val context = LocalContext.current
    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            val message = result.data?.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE).orEmpty()
            OtpCodeExtractor.extract(message)?.let(onCode)
        }
    }

    DisposableEffect(enabled, context) {
        if (!enabled) return@DisposableEffect onDispose { }

        SmsRetriever.getClient(context).startSmsUserConsent(null)

        val receiver = object : BroadcastReceiver() {
            override fun onReceive(receiverContext: Context?, intent: Intent?) {
                if (intent?.action != SmsRetriever.SMS_RETRIEVED_ACTION) return

                val status = intent.smsRetrieverStatus() ?: return
                if (status.statusCode != CommonStatusCodes.SUCCESS) return

                intent.smsConsentIntent()?.let(launcher::launch)
            }
        }

        val filter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
        ContextCompat.registerReceiver(
            context,
            receiver,
            filter,
            SmsRetriever.SEND_PERMISSION,
            null,
            ContextCompat.RECEIVER_EXPORTED,
        )

        onDispose {
            runCatching { context.unregisterReceiver(receiver) }
        }
    }
}

private fun Intent.smsRetrieverStatus(): Status? {
    val extras = extras ?: return null
    @Suppress("DEPRECATION")
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        extras.getParcelable(SmsRetriever.EXTRA_STATUS, Status::class.java)
    } else {
        extras.getParcelable(SmsRetriever.EXTRA_STATUS)
    }
}

private fun Intent.smsConsentIntent(): Intent? {
    val extras = extras ?: return null
    @Suppress("DEPRECATION")
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        extras.getParcelable(SmsRetriever.EXTRA_CONSENT_INTENT, Intent::class.java)
    } else {
        extras.getParcelable(SmsRetriever.EXTRA_CONSENT_INTENT)
    }
}

@Composable
private fun imageRequest(url: String?, authorization: String?): ImageRequest {
    val context = LocalContext.current
    return ImageRequest.Builder(context)
        .data(url)
        .apply {
            if (!authorization.isNullOrBlank()) addHeader("Authorization", authorization)
        }
        .crossfade(true)
        .build()
}
