package club.clubk.ktag.apps.konfigurator

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.core.content.ContextCompat
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import club.clubk.ktag.apps.konfigurator.ui.theme.KonfiguratorTheme
import kotlinx.coroutines.delay
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.draw.clip
import club.clubk.ktag.apps.core.ui.theme.KTagBlue
import club.clubk.ktag.apps.core.ui.theme.KTagDarkGray
import club.clubk.ktag.apps.core.ui.theme.KTagGreen
import club.clubk.ktag.apps.core.ui.theme.KTagPurple
import club.clubk.ktag.apps.core.ui.theme.KTagRed
import club.clubk.ktag.apps.core.ui.theme.KTagYellow

private const val TAG = "Konfigurator"

/**
 * Entry point for the Konfigurator subapp. Hosts the Compose UI and manages BLE permissions.
 *
 * On launch, requests all required Bluetooth and location permissions. If granted, inflates
 * the [StateMachine] composable rooted at [StateMachineViewModel]. If any permission is denied,
 * shows [PermissionDeniedScreen] with a retry button.
 */
class KonfiguratorActivity : ComponentActivity() {
    private val requiredPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        arrayOf(
            Manifest.permission.BLUETOOTH_ADVERTISE,
            Manifest.permission.BLUETOOTH_CONNECT,
            Manifest.permission.BLUETOOTH_SCAN,
            Manifest.permission.ACCESS_FINE_LOCATION,  // Required for BLE operations
            Manifest.permission.ACCESS_COARSE_LOCATION  // Required for BLE operations
        )
    } else {
        arrayOf(
            Manifest.permission.BLUETOOTH,
            Manifest.permission.BLUETOOTH_ADMIN,
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
        )
    }

    /** Handles the result of the system permission dialog. */
    private val permissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        val allGranted = permissions.entries.all { it.value }

        if (allGranted) {
            Log.i(TAG, "All permissions granted")
            mainScreen()
        } else {
            val deniedPermissions = permissions.filter { !it.value }.keys
            Log.w(TAG, "Some permissions denied: $deniedPermissions")
            setContent {
                MaterialTheme {
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        color = MaterialTheme.colorScheme.background
                    ) {
                        PermissionDeniedScreen(
                            deniedPermissions = deniedPermissions.toList()
                        ) { requestPermissions() }
                    }
                }
            }
        }
    }

    /** Proceeds to [mainScreen] if all permissions are already granted, otherwise requests them. */
    private fun checkAndRequestPermissions() {
        if (hasRequiredPermissions()) {
            Log.i(TAG, "All required permissions already granted")
            mainScreen()
        } else {
            Log.i(TAG, "Requesting permissions")
            requestPermissions()
        }
    }

    /** Returns true if every permission in [requiredPermissions] has been granted. */
    private fun hasRequiredPermissions(): Boolean {
        return requiredPermissions.all { permission ->
            ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
        }
    }

    /** Launches the system permission request dialog for all [requiredPermissions]. */
    private fun requestPermissions() {
        permissionLauncher.launch(requiredPermissions)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        checkAndRequestPermissions()
    }

    /** Sets the Compose content to the main [StateMachine] screen. */
    private fun mainScreen() {
        setContent {
            KonfiguratorTheme {
                StateMachine(StateMachineViewModel(applicationContext))
            }
        }
    }
}

/**
 * Shown when one or more permissions were denied. Lists the denied permissions by short name
 * and offers a button to re-trigger the system permission dialog.
 */
@Composable
fun PermissionDeniedScreen(
    deniedPermissions: List<String>,
    onRequestPermissions: () -> Unit
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "The following permissions are required:",
            style = MaterialTheme.typography.bodyLarge,
            textAlign = TextAlign.Center,
            modifier = Modifier.padding(bottom = 8.dp)
        )
        deniedPermissions.forEach { permission ->
            Text(
                text = "• ${permission.split(".").last()}",
                style = MaterialTheme.typography.bodyMedium,
                textAlign = TextAlign.Center,
                modifier = Modifier.padding(vertical = 4.dp)
            )
        }
        Button(
            onClick = { onRequestPermissions() },
            modifier = Modifier.padding(top = 16.dp)
        ) {
            Text("Grant Permissions")
        }
    }
}

/**
 * Root composable. Observes [StateMachineViewModel.appState] and renders the corresponding
 * screen composable for each [AppState].
 */
@Composable
fun StateMachine(stateMachine: StateMachineViewModel) {
    val appState by stateMachine.appState.collectAsState()

    Surface(
        modifier = Modifier.fillMaxSize()
    ) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            when (appState) {
                AppState.GameSettings -> GameSettingsScreen(stateMachine)
                AppState.TeamSettings -> TeamSettingsScreen(stateMachine)
                AppState.PregameTimer -> PregameTimerScreen(stateMachine)
                AppState.Countdown -> CountdownScreen(stateMachine)
                AppState.GameTimer -> GameTimerScreen(stateMachine)
                AppState.WrapUp -> WrapUpScreen(stateMachine)
            }
        }
    }
}

// -------------
// GAME SETTINGS
// -------------

/**
 * First screen in the flow. Displays [GameConfigEditor] pre-populated with the current config.
 * Saving the form updates the ViewModel and advances to [AppState.TeamSettings].
 */
@Composable
fun GameSettingsScreen(stateMachine: StateMachineViewModel) {
    val currentGameConfig by stateMachine.currentGameConfig.collectAsState()

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Game Settings",
            style = MaterialTheme.typography.headlineMedium,
            modifier = Modifier.padding(16.dp)
        )
        GameConfigEditor(oldGameConfig = currentGameConfig,
            onSave = {
                stateMachine.updateGameConfig(it)
                stateMachine.nextState()
            })
    }
}

/**
 * Editable form for all [GameConfig] parameters. Each field is validated on submit;
 * invalid fields are highlighted and the save is blocked until all are corrected.
 *
 * @param oldGameConfig Current config values used to pre-populate fields.
 * @param onSave Called with the new [GameConfig] when the form passes validation.
 */
@Composable
fun GameConfigEditor(oldGameConfig: GameConfig,
                     onSave: (GameConfig) -> Unit,
                     modifier: Modifier = Modifier) {
    key(oldGameConfig) {
        var gameDurationMin by rememberSaveable(oldGameConfig.gameDurationMin) { mutableStateOf(oldGameConfig.gameDurationMin.toString()) }
        var timeUntilCountdownS by rememberSaveable(oldGameConfig.timeUntilCountdownS) { mutableStateOf(oldGameConfig.timeUntilCountdownS.toString()) }
        var numRounds by rememberSaveable(oldGameConfig.numRounds) { mutableStateOf(oldGameConfig.numRounds.toString()) }
        var maxHealth by rememberSaveable(oldGameConfig.maxHealth) { mutableStateOf(oldGameConfig.maxHealth.toString()) }
        var specialWeaponsOnReentry by rememberSaveable(oldGameConfig.specialWeaponsOnReentry) { mutableStateOf(oldGameConfig.specialWeaponsOnReentry.toString()) }

        // For tracking validation errors
        var gameDurationMinError by rememberSaveable { mutableStateOf(false) }
        var timeUntilCountdownSError by rememberSaveable { mutableStateOf(false) }
        var numRoundsError by rememberSaveable { mutableStateOf(false) }
        var maxHealthError by rememberSaveable { mutableStateOf(false) }
        var specialWeaponsOnReentryError by rememberSaveable { mutableStateOf(false) }

        // Sets error flags on all fields; returns true only if all are valid.
        fun validateForm(): Boolean {
            gameDurationMinError = !isValidInteger(gameDurationMin)
            timeUntilCountdownSError = !isValidInteger(timeUntilCountdownS)
            numRoundsError = !isValidInteger(numRounds)
            maxHealthError = !isValidInteger(maxHealth)
            specialWeaponsOnReentryError = !isValidInteger(specialWeaponsOnReentry)

            return !gameDurationMinError && !timeUntilCountdownSError && !numRoundsError && !maxHealthError && !specialWeaponsOnReentryError
        }

        // Validates and, if successful, constructs and emits the new GameConfig.
        fun saveGameConfig() {
            if (validateForm()) {
                val newGameConfig = GameConfig(
                    gameDurationMin = gameDurationMin.toIntOrNull() ?: oldGameConfig.gameDurationMin,
                    timeUntilCountdownS = timeUntilCountdownS.toIntOrNull() ?: oldGameConfig.timeUntilCountdownS,
                    numRounds = numRounds.toIntOrNull() ?: oldGameConfig.numRounds,
                    maxHealth = maxHealth.toIntOrNull() ?: oldGameConfig.maxHealth,
                    specialWeaponsOnReentry = specialWeaponsOnReentry.toIntOrNull() ?: oldGameConfig.specialWeaponsOnReentry
                )
                onSave(newGameConfig)
            }
        }

        Column(
            modifier = modifier
                .fillMaxWidth()
                .padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            IntegerTextField(
                value = timeUntilCountdownS,
                onValueChange = { timeUntilCountdownS = it },
                label = "Time Until Countdown (seconds)",
                isError = timeUntilCountdownSError
            )

            IntegerTextField(
                value = gameDurationMin,
                onValueChange = { gameDurationMin = it },
                label = "Game Duration (minutes)",
                isError = gameDurationMinError
            )

            IntegerTextField(
                value = numRounds,
                onValueChange = { numRounds = it },
                label = "Number Of Rounds",
                isError = numRoundsError
            )

            IntegerTextField(
                value = maxHealth,
                onValueChange = { maxHealth = it },
                label = "Max Health",
                isError = maxHealthError
            )

            IntegerTextField(
                value = specialWeaponsOnReentry,
                onValueChange = { specialWeaponsOnReentry = it },
                label = "Special Weapons Received on Game Reentry",
                isError = specialWeaponsOnReentryError
            )
        }

        Spacer(modifier = Modifier.height(8.dp))
        Button(
            onClick = { saveGameConfig() },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Confirm")
        }
    }
}

/** Returns true if [input] is empty or parses as a valid integer. */
private fun isValidInteger(input: String): Boolean {
    return input.toIntOrNull() != null || input.isEmpty()
}

/**
 * Numeric-only [OutlinedTextField] that shows an error message when [isError] is true.
 *
 * @param value Current field text.
 * @param onValueChange Called on every keystroke.
 * @param label Field label shown inside the outline.
 * @param isError Whether to display the field in its error state.
 */
@Composable
private fun IntegerTextField(
    value: String,
    onValueChange: (String) -> Unit,
    label: String,
    isError: Boolean
) {
    OutlinedTextField(
        value = value,
        onValueChange = onValueChange,
        label = { Text(label) },
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
        isError = isError,
        supportingText = if (isError) {
            { Text("Please enter a valid integer") }
        } else null,
        modifier = Modifier.fillMaxWidth()
    )
}

// -------------
// TEAM SETTINGS
// -------------

/**
 * A centered row with a [CircularProgressIndicator] and a status [text] label.
 * Used in the Team Settings bottom bar while the app is busy (scanning, configuring, waiting).
 */
@Composable
private fun StatusSpinner(text: String) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.Center,
        verticalAlignment = Alignment.CenterVertically
    ) {
        CircularProgressIndicator(
            modifier = Modifier.size(24.dp),
            strokeWidth = 2.dp,
            color = KTagYellow
        )
        Spacer(modifier = Modifier.width(12.dp))
        Text(
            text = text,
            style = MaterialTheme.typography.bodyMedium
        )
    }
}

/**
 * Second screen in the flow. Displays discovered BLE devices, lets the operator assign
 * teams, and drives the configuration handshake before starting the game.
 *
 * Bottom bar transitions through several states:
 * - **Scanning** – no devices found yet
 * - **Configuring** – BLE Parameters packets being sent
 * - **Apply Settings** – team assignments changed and need to be pushed to devices
 * - **Make Ready** – config applied; periodically re-broadcasts EVENT_CONFIGURED until all ACK
 * - **Start Game** – all devices report [DeviceState.Ready]
 * - **Waiting** – config sent, awaiting STATUS acknowledgements
 */
@Composable
fun TeamSettingsScreen(stateMachine: StateMachineViewModel) {
    val devices by stateMachine.devices.collectAsState()
    val allDevicesReady by stateMachine.allDevicesReady.collectAsState()
    val pendingApply by stateMachine.pendingApply.collectAsState()
    val isConfiguring by stateMachine.isConfiguring.collectAsState()
    val allTeamsConfirmed = devices.isNotEmpty() && devices.all {
        val bt = it.broadcastedTeam; bt != null && it.team == bt
    }
    Scaffold(
        bottomBar = {
            BottomAppBar(
                containerColor = MaterialTheme.colorScheme.background,
                tonalElevation = 0.dp
            ) {
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(horizontal = 16.dp),
                    contentAlignment = Alignment.Center
                ) {
                    when {
                        devices.isEmpty()  -> StatusSpinner("Scanning for devices\u2026")
                        isConfiguring      -> StatusSpinner("Configuring\u2026")
                        pendingApply       -> Button(
                            onClick = { stateMachine.configureDevices() },
                            modifier = Modifier.fillMaxWidth()
                        ) {
                            Text("Apply Settings")
                        }
                        allDevicesReady    -> Button(
                            onClick = { stateMachine.nextState() },
                            modifier = Modifier.fillMaxWidth()
                        ) {
                            Text("Start Game")
                        }
                        allTeamsConfirmed  -> Button(
                            onClick = { stateMachine.readyAllDevices() },
                            modifier = Modifier.fillMaxWidth()
                        ) {
                            Text("Make Ready")
                        }
                        else               -> StatusSpinner("Waiting for confirmations\u2026")
                    }
                }
            }
        }
    ) { paddingValues ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues)
        ) {
            Text(
                text = "Team Settings",
                style = MaterialTheme.typography.headlineMedium,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(vertical = 16.dp),
                textAlign = TextAlign.Center
            )
            TeamOverview(devices)
            DeviceList(stateMachine, devices, Modifier.weight(1f))
        }
    }
}

/**
 * Scrollable list of discovered [Device]s, each rendered as a [DeviceCard].
 * Shows a "No Devices Found" placeholder when [devices] is empty.
 */
@Composable
fun DeviceList(stateMachine: StateMachineViewModel, devices: List<Device>, modifier: Modifier) {
    if (devices.isEmpty()) {
        Box(
            modifier = modifier.fillMaxWidth(),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "No Devices Found",
                style = MaterialTheme.typography.bodyLarge,
                color = Color.Gray
            )
        }
    } else {
        LazyColumn(modifier = modifier) {
            items(devices) { device ->
                DeviceCard(stateMachine, device)
            }
        }
    }
}

/**
 * Summary card showing how many devices are assigned to each team (Purple / Red / Blue / None).
 * Updated reactively as [devices] changes.
 */
@Composable
fun TeamOverview(devices: List<Device>) {
    val purpleCount  = devices.count { it.team == 0 }
    val redCount     = devices.count { it.team == 1 }
    val blueCount    = devices.count { it.team == 2 }
    val noneCount    = devices.count { val t = it.team; t == null || t < 0 || t > 2 }

    val teams = listOf(
        Triple(KTagPurple, "Purple", purpleCount),
        Triple(KTagRed,    "Red",    redCount),
        Triple(KTagBlue,   "Blue",   blueCount),
        Triple(Color.Gray, "None",   noneCount),
    )

    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 8.dp, vertical = 4.dp),
        shape = RoundedCornerShape(10.dp),
        colors = CardDefaults.cardColors(containerColor = KTagDarkGray)
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(12.dp),
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {
            teams.forEach { (color, label, count) ->
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text(
                        text = count.toString(),
                        style = MaterialTheme.typography.titleMedium,
                        color = color,
                        fontWeight = FontWeight.Bold
                    )
                    Text(
                        text = label,
                        style = MaterialTheme.typography.labelSmall,
                        color = color
                    )
                }
            }
        }
    }
}

/** Maps a team index (0=Purple, 1=Red, 2=Blue) to its brand color. Returns gray for unassigned. */
private fun teamColor(team: Int?) = when (team) {
    0    -> KTagPurple
    1    -> KTagRed
    2    -> KTagBlue
    else -> Color.Gray
}

/** Maps a team index (0=Purple, 1=Red, 2=Blue) to its display name. Returns "None" for unassigned. */
private fun teamLabel(team: Int?) = when (team) {
    0    -> "Purple"
    1    -> "Red"
    2    -> "Blue"
    else -> "None"
}

/**
 * Card representing a single discovered device. Tapping cycles the device's team assignment.
 * Long-pressing opens a detail dialog with full device information.
 *
 * The left accent bar is team-colored when the device has acknowledged its assignment via BLE
 * STATUS packet, and amber while the assignment is pending. A checkmark appears when the
 * broadcasted team matches the assigned team. A yellow warning icon appears when the device
 * reports a SystemK version below 1.02. READY/NOT READY reflects [DeviceState].
 */
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DeviceCard(stateMachine: StateMachineViewModel, device: Device) {
    val color = teamColor(device.team)
    val label = teamLabel(device.team)
    val isReady = device.deviceState == DeviceState.Ready
    val allSettingsMatch = device.allSettingsMatch()
    val accentBarColor = if (allSettingsMatch) color else Color(0xFFFFA000)
    val isOutdated = device.isOutdatedSystemK()
    var showDetailDialog by remember { mutableStateOf(false) }

    if (showDetailDialog) {
        DeviceDetailDialog(
            device = device,
            currentUserDefinedName = stateMachine.getDeviceUserDefinedName(device.address),
            onDismiss = { showDetailDialog = false },
            onSave = { newName, maxHealth, specialWeapons ->
                stateMachine.renameDevice(device.address, newName)
                stateMachine.updateDeviceMaxHealth(device.address, maxHealth)
                stateMachine.updateDeviceSpecialWeapons(device.address, specialWeapons)
                showDetailDialog = false
            }
        )
    }

    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 8.dp, vertical = 3.dp)
            .border(2.dp, KTagYellow, RoundedCornerShape(10.dp))
            .clip(RoundedCornerShape(10.dp))
            .combinedClickable(
                onClick = { stateMachine.cycleDeviceTeam(device) },
                onLongClick = { showDetailDialog = true }
            ),
        shape = RoundedCornerShape(10.dp),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        colors = CardDefaults.cardColors(containerColor = KTagDarkGray)
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .height(IntrinsicSize.Min)
                .padding(8.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(10.dp)
        ) {
            Box(
                modifier = Modifier
                    .width(6.dp)
                    .fillMaxHeight()
                    .clip(RoundedCornerShape(3.dp))
                    .background(accentBarColor)
            )
            Column(modifier = Modifier.weight(1f)) {
                Text(
                    text = device.name,
                    style = MaterialTheme.typography.bodyMedium,
                    color = KTagYellow,
                    fontWeight = FontWeight.Bold
                )
                Text(
                    text = device.deviceTypeName(),
                    style = MaterialTheme.typography.bodySmall,
                    color = KTagYellow.copy(alpha = 0.7f)
                )
            }
            Text(
                text = label,
                style = MaterialTheme.typography.labelMedium,
                color = color,
                fontWeight = FontWeight.Bold
            )
            Box(modifier = Modifier.size(16.dp), contentAlignment = Alignment.Center) {
                if (allSettingsMatch) {
                    Icon(
                        imageVector = Icons.Default.Check,
                        contentDescription = "All settings confirmed",
                        tint = KTagGreen,
                        modifier = Modifier.size(16.dp)
                    )
                }
            }
            Box(modifier = Modifier.size(16.dp), contentAlignment = Alignment.Center) {
                if (isOutdated) {
                    Icon(
                        imageVector = Icons.Default.Warning,
                        contentDescription = "Outdated SystemK version",
                        tint = KTagYellow,
                        modifier = Modifier.size(16.dp)
                    )
                }
            }
            Text(
                text = if (isReady) "READY" else "NOT READY",
                style = MaterialTheme.typography.labelSmall,
                color = if (isReady) KTagGreen else Color.Gray,
                fontWeight = FontWeight.Bold
            )
        }
    }
}

/**
 * Dialog shown on long-press of a [DeviceCard]. Displays full device details and allows
 * the device to be given a custom name. The custom name is saved to [DeviceInfoRepository]
 * and synced to MQTT. Highlights an outdated SystemK version with a warning icon.
 */
@Composable
private fun DeviceDetailDialog(
    device: Device,
    currentUserDefinedName: String,
    onDismiss: () -> Unit,
    onSave: (name: String, maxHealth: Int, specialWeapons: Int) -> Unit
) {
    var nameText by remember { mutableStateOf(currentUserDefinedName.ifEmpty { device.name }) }
    var maxHealthText by remember { mutableStateOf(device.maxHealth?.toString() ?: "") }
    var specialWeaponsText by remember { mutableStateOf(device.specialWeaponsOnReentry?.toString() ?: "") }
    var maxHealthError by remember { mutableStateOf(false) }
    var specialWeaponsError by remember { mutableStateOf(false) }

    AlertDialog(
        onDismissRequest = onDismiss,
        text = {
            Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
                OutlinedTextField(
                    value = nameText,
                    onValueChange = { nameText = it },
                    label = { Text("Device Name") },
                    singleLine = true,
                    modifier = Modifier.fillMaxWidth()
                )
                IntegerTextField(
                    value = maxHealthText,
                    onValueChange = { maxHealthText = it; maxHealthError = false },
                    label = "Max Health",
                    isError = maxHealthError
                )
                IntegerTextField(
                    value = specialWeaponsText,
                    onValueChange = { specialWeaponsText = it; specialWeaponsError = false },
                    label = "Special Weapons on Reentry",
                    isError = specialWeaponsError
                )
                DeviceDetailRow("Address", device.address)
                DeviceDetailRow("Type", device.deviceTypeName())
                DeviceDetailRow("Team", teamLabel(device.team), teamColor(device.team))
                DeviceDetailRow("Status", when (device.deviceState) {
                    DeviceState.Ready        -> "Ready"
                    DeviceState.Playing      -> "Playing"
                    DeviceState.WrapUp       -> "Wrap Up"
                    DeviceState.Configurable -> "Configurable"
                    null                     -> "Unknown"
                })
                if (device.isOutdatedSystemK()) {
                    Row(
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.spacedBy(4.dp)
                    ) {
                        Text(
                            text = "SystemK:",
                            style = MaterialTheme.typography.bodySmall,
                            color = Color.Gray
                        )
                        Icon(
                            imageVector = Icons.Default.Warning,
                            contentDescription = null,
                            tint = KTagYellow,
                            modifier = Modifier.size(14.dp)
                        )
                        Text(
                            text = "${device.SystemKVersionString()} — expects ${MinSystemKVersion.display} or later",
                            style = MaterialTheme.typography.bodySmall,
                            color = KTagYellow
                        )
                    }
                } else {
                    DeviceDetailRow("SystemK", device.SystemKVersionString())
                }
            }
        },
        confirmButton = {
            TextButton(
                onClick = {
                    val parsedHealth = maxHealthText.toIntOrNull()
                    val parsedWeapons = specialWeaponsText.toIntOrNull()
                    maxHealthError = parsedHealth == null
                    specialWeaponsError = parsedWeapons == null
                    if (nameText.isNotBlank() && parsedHealth != null && parsedWeapons != null) {
                        onSave(nameText.trim(), parsedHealth, parsedWeapons)
                    }
                },
                enabled = nameText.isNotBlank()
                    && maxHealthText.toIntOrNull() != null
                    && specialWeaponsText.toIntOrNull() != null
            ) { Text("Save") }
        },
        dismissButton = {
            TextButton(onClick = onDismiss) { Text("Cancel") }
        },
        containerColor = KTagDarkGray
    )
}

@Composable
private fun DeviceDetailRow(label: String, value: String, valueColor: Color = Color.White) {
    Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
        Text(
            text = "$label:",
            style = MaterialTheme.typography.bodySmall,
            color = Color.Gray
        )
        Text(
            text = value,
            style = MaterialTheme.typography.bodySmall,
            color = valueColor
        )
    }
}

// -------------
// PREGAME TIMER
// -------------

/** Formats [remainingSeconds] as a zero-padded "MM:SS" string (e.g. 90 → "01:30"). */
private fun formatTimeToMinutesSeconds(remainingSeconds: Int): String {
    return "%02d:%02d".format(remainingSeconds / 60, remainingSeconds % 60)
}

/**
 * Composable helper that drives a countdown from [timeInMillis] to zero, ticking every second.
 * Calls [onComplete] when the countdown reaches zero.
 *
 * @return A [Pair] of (remainingTimeMs, initialTimeMs) for use in progress calculations.
 */
@Composable
private fun rememberCountdown(timeInMillis: Long, onComplete: () -> Unit): Pair<Long, Long> {
    var remainingTime by remember { mutableLongStateOf(timeInMillis) }
    val initialTime = remember { timeInMillis }
    LaunchedEffect(key1 = timeInMillis) {
        remainingTime = timeInMillis
        while (remainingTime > 0) {
            delay(1000)
            remainingTime -= 1000
        }
        onComplete()
    }
    return Pair(remainingTime, initialTime)
}

/**
 * Displays a monospace MM:SS countdown with a [LinearProgressIndicator] below it.
 * Calls [onCountdownComplete] when the timer reaches zero.
 *
 * @param timeInMillis Total duration of the countdown in milliseconds.
 * @param onCountdownComplete Invoked on the main thread when the timer expires.
 */
@Composable
fun CountdownTimer(timeInMillis: Long, onCountdownComplete: () -> Unit) {
    val (remainingTime, initialTime) = rememberCountdown(timeInMillis, onCountdownComplete)

    val remainingSeconds = (remainingTime / 1000).toInt()
    val progress = if (initialTime > 0) remainingTime.toFloat() / initialTime.toFloat() else 0f

    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = formatTimeToMinutesSeconds(remainingSeconds),
            fontSize = 36.sp,
            fontWeight = FontWeight.Bold,
            fontFamily = FontFamily.Monospace,
            modifier = Modifier.padding(bottom = 8.dp)
        )
        LinearProgressIndicator(
            progress = { progress },
            modifier = Modifier.fillMaxWidth(),
        )
    }
}

/**
 * Third screen in the flow. Displays a [CountdownTimer] for the pregame period.
 * The ViewModel is already broadcasting the InstigatingGame BLE packet while this screen is shown.
 * Automatically advances to [AppState.Countdown] when the timer expires.
 */
@Composable
fun PregameTimerScreen(stateMachine: StateMachineViewModel) {
    Column(
        modifier = Modifier.fillMaxWidth(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Countdown In",
            style = MaterialTheme.typography.headlineMedium
        )
        CountdownTimer(stateMachine.pregameLengthMs, onCountdownComplete = {
            stateMachine.nextState()
        })
    }
}

// ---------
// COUNTDOWN
// ---------

/**
 * Fourth screen in the flow. Delegates entirely to [CountdownLights] and advances to
 * [AppState.GameTimer] when the lights-out animation completes.
 */
@Composable
fun CountdownScreen(stateMachine: StateMachineViewModel) {
    CountdownLights(onComplete = { stateMachine.nextState() })
}

/**
 * F1-style race start lights animation. Five red circles illuminate one per second,
 * then all fade out simultaneously after a one-second pause. Calls [onComplete] 400 ms
 * after lights-out to allow the fade animation to finish.
 */
@Composable
fun CountdownLights(onComplete: () -> Unit) {
    var litCount by remember { mutableIntStateOf(0) }
    var lightsOut by remember { mutableStateOf(false) }

    LaunchedEffect(Unit) {
        for (i in 1..5) {
            delay(1000)
            litCount = i
        }
        delay(1000)
        lightsOut = true
        delay(400)
        onComplete()
    }

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
            for (i in 1..5) {
                val isLit = !lightsOut && litCount >= i
                val alpha by animateFloatAsState(
                    targetValue = if (isLit) 1f else 0f,
                    animationSpec = tween(durationMillis = if (lightsOut) 400 else 200),
                    label = "light_$i"
                )
                Box(
                    modifier = Modifier
                        .size(56.dp)
                        .background(Color.Red.copy(alpha = alpha), CircleShape)
                        .border(3.dp, Color(0xFF555555), CircleShape)
                )
            }
        }
    }
}

// ----------
// GAME TIMER
// ----------

/**
 * Fifth screen in the flow. Shows the [RingCountdownTimer] for the active game duration.
 * Provides an "End Game" button for early termination. Both expiry and the button advance
 * to [AppState.WrapUp] via [StateMachineViewModel.nextState].
 */
@Composable
fun GameTimerScreen(stateMachine: StateMachineViewModel) {
    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Game In Progress",
            style = MaterialTheme.typography.headlineMedium
        )
        RingCountdownTimer(
            timeInMillis = stateMachine.gameLengthMs,
            modifier = Modifier.weight(1f),
            onCountdownComplete = { stateMachine.nextState() }
        )
        Button(
            onClick = { stateMachine.nextState() },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("End Game")
        }
    }
}

/**
 * Circular ring countdown timer with a monospace MM:SS time overlay.
 *
 * The ring starts full and sweeps counter-clockwise as time elapses. The ring color
 * transitions from KTag yellow to red when 10% or less time remains. Calls
 * [onCountdownComplete] when the timer reaches zero.
 *
 * @param timeInMillis Total game duration in milliseconds.
 * @param modifier Applied to the outermost [Box]; use to constrain size (e.g. [Modifier.weight]).
 * @param onCountdownComplete Invoked on the main thread when the timer expires.
 */
@Composable
fun RingCountdownTimer(timeInMillis: Long, modifier: Modifier = Modifier, onCountdownComplete: () -> Unit) {
    val (remainingTime, initialTime) = rememberCountdown(timeInMillis, onCountdownComplete)

    val progress = if (initialTime > 0) remainingTime.toFloat() / initialTime.toFloat() else 0f

    val fillColor by animateColorAsState(
        targetValue = if (progress <= 0.1f) KTagRed else KTagYellow,
        animationSpec = tween(durationMillis = 600),
        label = "ring_color"
    )

    Box(
        modifier = modifier.fillMaxWidth().aspectRatio(1f).padding(24.dp),
        contentAlignment = Alignment.Center
    ) {
        Canvas(modifier = Modifier.fillMaxSize()) {
            val strokeWidth = size.minDimension * 0.09f
            val radius = (size.minDimension - strokeWidth) / 2f
            val cx = size.width / 2f
            val cy = size.height / 2f
            val topLeft = Offset(cx - radius, cy - radius)
            val arcSize = Size(radius * 2f, radius * 2f)
            val stroke = Stroke(width = strokeWidth, cap = StrokeCap.Round)

            // Background track ring
            drawArc(
                color = Color(0xFF333333),
                startAngle = -90f,
                sweepAngle = 360f,
                useCenter = false,
                topLeft = topLeft,
                size = arcSize,
                style = stroke
            )
            // Progress ring
            drawArc(
                color = fillColor,
                startAngle = -90f,
                sweepAngle = progress * 360f,
                useCenter = false,
                topLeft = topLeft,
                size = arcSize,
                style = stroke
            )
        }
        Text(
            text = formatTimeToMinutesSeconds((remainingTime / 1000).toInt()),
            fontSize = 48.sp,
            fontWeight = FontWeight.Bold,
            fontFamily = FontFamily.Monospace,
            color = KTagYellow
        )
    }
}

// --------
// WRAP UP
// --------

/**
 * Final screen in the flow. Shown after the game timer expires or the operator ends the game early.
 * The ViewModel broadcasts EVENT_GAME_OVER on entry and EVENT_WRAPUP_COMPLETE on exit.
 * Pressing "Play Again" returns to [AppState.GameSettings].
 */
@Composable
fun WrapUpScreen(stateMachine: StateMachineViewModel) {
    Column(
        modifier = Modifier.fillMaxWidth(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Wrapping Up",
            style = MaterialTheme.typography.headlineMedium
        )

        Spacer(modifier = Modifier.height(16.dp))

        Button(
            onClick = { stateMachine.nextState() },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Play Again")
        }
    }
}
