// Copyright (C) 2026 Jason M. Schwefel. All Rights Reserved.
package com.coldboreballisticsllc.scopedope.ui.screens.conditions

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.hardware.GeomagneticField
import android.location.LocationManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import kotlin.math.roundToInt
import com.coldboreballisticsllc.scopedope.domain.model.RangeConditions
import com.coldboreballisticsllc.scopedope.domain.model.WindConditions
import com.coldboreballisticsllc.scopedope.data.sensor.CompassWindStatus
import com.coldboreballisticsllc.scopedope.domain.sensor.SensorState
import com.coldboreballisticsllc.scopedope.ui.components.*
import com.coldboreballisticsllc.scopedope.ui.session.AltitudeUnit
import com.coldboreballisticsllc.scopedope.ui.session.ConditionUnits
import com.coldboreballisticsllc.scopedope.ui.session.PressureUnit
import com.coldboreballisticsllc.scopedope.ui.session.SensorEvent as ScopeSensorEvent
import com.coldboreballisticsllc.scopedope.ui.session.SensorViewModel
import com.coldboreballisticsllc.scopedope.ui.session.SessionViewModel
import com.coldboreballisticsllc.scopedope.ui.session.TempUnit
import com.coldboreballisticsllc.scopedope.ui.session.WindSpeedUnit
import com.coldboreballisticsllc.scopedope.ui.theme.*

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConditionsScreen(
    sessionVm: SessionViewModel = hiltViewModel(),
    sensorVm:  SensorViewModel  = hiltViewModel(),
) {
    val state            by sessionVm.state.collectAsState()
    val sensorUiState    by sensorVm.uiState.collectAsState()
    val sensorState      by sensorVm.sensorState.collectAsState()
    val sensorDriverId   by sensorVm.sensorDriverId.collectAsState()
    val captureResult    by sessionVm.captureResult.collectAsState()
    val compassStatus    by sensorVm.compassStatus.collectAsState()
    val compassBearing   by sensorVm.compassBearingDeg.collectAsState()

    // Bridge SensorViewModel events to SessionViewModel: freeze conditions when
    // the sensor's shared-mode FREEZE_DISCONNECT or TIMED_WINDOW sub-mode fires.
    LaunchedEffect(Unit) {
        sensorVm.events.collect { event ->
            if (event is ScopeSensorEvent.FreezeConditions) sessionVm.setConditionsFrozen(true)
        }
    }

    // GPS altitude requires FINE location (coarse strips altitude — SPEC §17.1).
    // Request it if missing, then capture; if denied, capture still runs (it just
    // won't populate altitude — the rest of the capture is unaffected).
    val screenContext = LocalContext.current
    val fineLocationLauncher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { _ -> sessionVm.captureConditions() }

    fun requestAndCapture() {
        val hasFine = ContextCompat.checkSelfPermission(
            screenContext, Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
        if (hasFine) sessionVm.captureConditions()
        else fineLocationLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
    }

    // Auto-populate altitude from GPS last-known location when the screen opens,
    // if altitude is unset (0) and no weather sensor is connected (sensor provides
    // barometric pressure which gives a more accurate ICAO-derived altitude).
    val autoCaptureDone = remember { mutableStateOf(false) }
    LaunchedEffect(Unit) {
        if (!autoCaptureDone.value &&
            state.conditions.altitudeFt == 0 &&
            sensorState is SensorState.Disconnected &&
            !state.conditionsFrozen
        ) {
            requestAndCapture()
            autoCaptureDone.value = true
        }
    }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Range Conditions", style = MaterialTheme.typography.titleLarge) },
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor    = ColorBackground,
                    titleContentColor = ColorTextPrimary,
                ),
            )
        },
        containerColor = ColorBackground,
    ) { padding ->
        val imeBottom = WindowInsets.ime.asPaddingValues().calculateBottomPadding()
        Column(
            modifier = Modifier
                .padding(padding)
                .fillMaxSize()
                .verticalScroll(rememberScrollState())
                .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 16.dp + imeBottom),
            verticalArrangement = Arrangement.spacedBy(12.dp),
        ) {
            // ── Capture ───────────────────────────────────────────────────────
            CapturePanel(
                captureResult = captureResult,
                onCapture     = ::requestAndCapture,
            )

            // ── Atmosphere ────────────────────────────────────────────────────
            AtmospherePanel(
                conditions     = state.conditions,
                conditionUnits = state.conditionUnits,
                onUpdate       = sessionVm::setConditions,
            )

            // ── Wind ──────────────────────────────────────────────────────────
            WindPanel(
                wind            = state.wind,
                conditionUnits  = state.conditionUnits,
                compassStatus   = compassStatus,
                compassBearing  = compassBearing,
                onUpdate        = sessionVm::setWind,
                onCompassOverride = sensorVm::overrideCompassWithManual,
            )

            // ── Firing angle (inclined fire) ──────────────────────────────────
            FiringAnglePanel(
                angleDeg = state.firingAngleDeg,
                onUpdate = sessionVm::setFiringAngle,
            )

            // ── Firing azimuth (Coriolis) — only shown when Coriolis is enabled ──
            if (state.useCoriolis) {
                FiringAzimuthPanel(
                    azimuthDeg   = state.firingAzimuthDeg,
                    latitudeDeg  = state.coriolisLatitudeDeg,
                    onUpdate     = sessionVm::setFiringAzimuth,
                )
            }

            // ── Weather sensor ────────────────────────────────────────────────
            SensorConnectionPanel(
                sensorState       = sensorState,
                driverId          = sensorDriverId,
                friendlyName      = sensorUiState.sensorFriendlyName,
                conditionsFrozen  = state.conditionsFrozen,
                onConnect         = sensorVm::connect,
                onDisconnect      = sensorVm::disconnect,
                onToggleFreeze    = { sessionVm.setConditionsFrozen(!state.conditionsFrozen) },
            )
        }
    }
}

// ── Atmosphere panel ──────────────────────────────────────────────────────────

@Composable
private fun AtmospherePanel(
    conditions:    RangeConditions,
    conditionUnits: ConditionUnits,
    onUpdate:      (RangeConditions) -> Unit,
) {
    var altStr  by remember(conditions.altitudeFt, conditionUnits.altitude) {
        mutableStateOf(when (conditionUnits.altitude) {
            AltitudeUnit.FT -> conditions.altitudeFt.toString()
            AltitudeUnit.M  -> (conditions.altitudeFt / 3.28084).roundToInt().toString()
        })
    }
    var tempStr by remember(conditions.temperatureF, conditionUnits.temp) {
        mutableStateOf(when (conditionUnits.temp) {
            TempUnit.F -> "%.1f".format(conditions.temperatureF)
            TempUnit.C -> "%.1f".format((conditions.temperatureF - 32.0) * 5.0 / 9.0)
            TempUnit.K -> "%.1f".format((conditions.temperatureF - 32.0) * 5.0 / 9.0 + 273.15)
        })
    }
    var presStr by remember(conditions.pressureHpa, conditionUnits.pressure) {
        mutableStateOf(when (conditionUnits.pressure) {
            PressureUnit.INHG -> "%.2f".format(conditions.pressureHpa / 33.8639)
            PressureUnit.HPA  -> "%.2f".format(conditions.pressureHpa)
            PressureUnit.MBAR -> "%.2f".format(conditions.pressureHpa)
            PressureUnit.MMHG -> "%.2f".format(conditions.pressureHpa / 1.33322)
            PressureUnit.KPA  -> "%.3f".format(conditions.pressureHpa / 10.0)
        })
    }
    var humStr  by remember(conditions.humidityPct) { mutableStateOf("%.0f".format(conditions.humidityPct)) }

    fun commit() {
        val altFt = when (conditionUnits.altitude) {
            AltitudeUnit.FT -> altStr.toIntOrNull() ?: conditions.altitudeFt
            AltitudeUnit.M  -> ((altStr.toDoubleOrNull() ?: (conditions.altitudeFt / 3.28084)) * 3.28084).roundToInt()
        }
        val tempF = when (conditionUnits.temp) {
            TempUnit.F -> tempStr.toDoubleOrNull() ?: conditions.temperatureF
            TempUnit.C -> tempStr.toDoubleOrNull()?.let { it * 9.0 / 5.0 + 32.0 } ?: conditions.temperatureF
            TempUnit.K -> tempStr.toDoubleOrNull()?.let { (it - 273.15) * 9.0 / 5.0 + 32.0 } ?: conditions.temperatureF
        }
        val presHpa = when (conditionUnits.pressure) {
            PressureUnit.INHG -> presStr.toDoubleOrNull()?.let { it * 33.8639 } ?: conditions.pressureHpa
            PressureUnit.HPA  -> presStr.toDoubleOrNull() ?: conditions.pressureHpa
            PressureUnit.MBAR -> presStr.toDoubleOrNull() ?: conditions.pressureHpa
            PressureUnit.MMHG -> presStr.toDoubleOrNull()?.let { it * 1.33322 } ?: conditions.pressureHpa
            PressureUnit.KPA  -> presStr.toDoubleOrNull()?.let { it * 10.0 } ?: conditions.pressureHpa
        }
        onUpdate(conditions.copy(
            altitudeFt   = altFt,
            temperatureF = tempF,
            pressureHpa  = presHpa,
            humidityPct  = humStr.toDoubleOrNull() ?: conditions.humidityPct,
        ))
    }

    InstrumentPanel(label = "Atmosphere") {
        Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            NumericField(
                value = altStr, onValueChange = { altStr = it },
                label = "Altitude", suffix = conditionUnits.altitude.suffix,
                modifier = Modifier.weight(1f), onDone = ::commit, onFocusLost = ::commit,
            )
            NumericField(
                value = tempStr, onValueChange = { tempStr = it },
                label = "Temperature", suffix = conditionUnits.temp.suffix,
                modifier = Modifier.weight(1f), onDone = ::commit, onFocusLost = ::commit,
            )
        }
        Spacer(Modifier.height(8.dp))
        Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            NumericField(
                value = presStr, onValueChange = { presStr = it },
                label = "Pressure", suffix = conditionUnits.pressure.suffix,
                modifier = Modifier.weight(1f), onDone = ::commit, onFocusLost = ::commit,
            )
            NumericField(
                value = humStr, onValueChange = { humStr = it },
                label = "Humidity", suffix = "%",
                modifier = Modifier.weight(1f),
                onDone = ::commit, onFocusLost = ::commit,
            )
        }
    }
}

// ── Wind panel ────────────────────────────────────────────────────────────────

@Composable
private fun WindPanel(
    wind:              WindConditions,
    conditionUnits:    ConditionUnits,
    compassStatus:     CompassWindStatus,
    compassBearing:    Double?,
    onUpdate:          (WindConditions) -> Unit,
    onCompassOverride: () -> Unit,
) {
    var speedStr by remember(wind.speedMph, conditionUnits.windSpeed) {
        mutableStateOf(when (conditionUnits.windSpeed) {
            WindSpeedUnit.MPH -> "%.1f".format(wind.speedMph)
            WindSpeedUnit.KPH -> "%.1f".format(wind.speedMph * 1.60934)
            WindSpeedUnit.MPS -> "%.2f".format(wind.speedMph / 2.23694)
            WindSpeedUnit.KTS -> "%.1f".format(wind.speedMph / 1.15078)
        })
    }

    var showOverrideDialog by remember { mutableStateOf(false) }

    if (showOverrideDialog) {
        AlertDialog(
            onDismissRequest = { showOverrideDialog = false },
            title   = { Text("Stop using phone compass?", color = ColorTextPrimary) },
            text    = {
                Text(
                    "Wind direction will revert to manual entry. You can re-enable the phone compass in Advanced Settings → Weather Sensor.",
                    color = ColorTextMuted,
                )
            },
            confirmButton = {
                TextButton(onClick = {
                    onCompassOverride()
                    showOverrideDialog = false
                }) { Text("Enter Manually", color = ColorAmber) }
            },
            dismissButton = {
                TextButton(onClick = { showOverrideDialog = false }) {
                    Text("Cancel", color = ColorTextMuted)
                }
            },
            containerColor = ColorBackground,
        )
    }

    fun commitSpeed() {
        val mph = when (conditionUnits.windSpeed) {
            WindSpeedUnit.MPH -> speedStr.toDoubleOrNull() ?: wind.speedMph
            WindSpeedUnit.KPH -> speedStr.toDoubleOrNull()?.let { it / 1.60934 } ?: wind.speedMph
            WindSpeedUnit.MPS -> speedStr.toDoubleOrNull()?.let { it * 2.23694 } ?: wind.speedMph
            WindSpeedUnit.KTS -> speedStr.toDoubleOrNull()?.let { it * 1.15078 } ?: wind.speedMph
        }
        onUpdate(wind.copy(speedMph = mph))
    }

    val compassActive = compassStatus == CompassWindStatus.OK ||
                        compassStatus == CompassWindStatus.LOW_ACCURACY ||
                        compassStatus == CompassWindStatus.READING

    InstrumentPanel(label = "Wind") {
        Row(
            modifier              = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.spacedBy(8.dp),
            verticalAlignment     = Alignment.CenterVertically,
        ) {
            NumericField(
                value         = speedStr,
                onValueChange = { speedStr = it },
                label         = "Speed",
                suffix        = conditionUnits.windSpeed.suffix,
                modifier      = Modifier.weight(1f),
                onDone        = ::commitSpeed,
                onFocusLost   = ::commitSpeed,
            )
            WindDirectionDial(
                angleDeg      = wind.angleDeg,
                onAngleChange = { angle ->
                    if (compassActive) {
                        showOverrideDialog = true
                    } else {
                        onUpdate(wind.copy(angleDeg = angle))
                    }
                },
                modifier      = Modifier.weight(1f),
            )
        }
        if (compassActive) {
            Spacer(Modifier.height(4.dp))
            val (compassLabel, compassColor) = when (compassStatus) {
                CompassWindStatus.READING      -> "Compass reading…" to ColorAmber
                CompassWindStatus.LOW_ACCURACY -> "Compass: low accuracy" to ColorAmber
                else                           -> "Compass: ${"%.0f".format(compassBearing ?: 0.0)}°" to ColorDataPositive
            }
            Text(
                text  = compassLabel,
                style = MaterialTheme.typography.labelMedium,
                color = compassColor,
            )
        }
    }
}

// ── Weather sensor connection panel ──────────────────────────────────────────

private const val DRIVER_TACTICAL = "weatherflow_tactical"

@Composable
private fun SensorConnectionPanel(
    sensorState:      SensorState,
    driverId:         String,
    friendlyName:     String?,
    conditionsFrozen: Boolean,
    onConnect:        () -> Unit,
    onDisconnect:     () -> Unit,
    onToggleFreeze:   () -> Unit,
) {
    val isConnected = sensorState is SensorState.Connected
    val (statusText, statusColor) = when {
        conditionsFrozen && isConnected -> "Connected · Frozen" to ColorAmber
        else -> when (sensorState) {
            is SensorState.Disconnected -> "Disconnected"      to ColorTextMuted
            is SensorState.Scanning     -> "Scanning…"         to ColorAmber
            is SensorState.Connecting   -> "Connecting…"       to ColorAmber
            is SensorState.Connected    -> "Connected"         to ColorDataPositive
            is SensorState.Error        -> sensorState.message to ColorError
        }
    }

    val panelLabel = friendlyName?.takeIf { it.isNotBlank() }
        ?: if (driverId == DRIVER_TACTICAL) "WeatherFlow Tactical" else "BTMETER BT-100 Anemometer"

    InstrumentPanel(label = panelLabel) {
        Row(
            modifier              = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment     = Alignment.CenterVertically,
        ) {
            Text(
                text  = statusText,
                style = MaterialTheme.typography.bodyMedium,
                color = statusColor,
            )
            val isActive = sensorState is SensorState.Scanning ||
                           sensorState is SensorState.Connecting ||
                           sensorState is SensorState.Connected
            OutlinedButton(
                onClick = if (isActive) onDisconnect else onConnect,
                border  = BorderStroke(1.dp, if (isActive) ColorBorder else ColorAmber),
                colors  = ButtonDefaults.outlinedButtonColors(
                    contentColor = if (isActive) ColorTextMuted else ColorAmber,
                ),
            ) {
                Text(
                    text  = if (isActive) "Disconnect" else "Connect",
                    style = MaterialTheme.typography.labelMedium,
                )
            }
        }

        if (isConnected && !conditionsFrozen) {
            Spacer(Modifier.height(4.dp))
            Text(
                text  = if (driverId == DRIVER_TACTICAL)
                    "Temperature, humidity, pressure, and wind speed are being auto-populated. " +
                    "Wind direction is supplied by the phone compass."
                else
                    "Wind speed and temperature are being auto-populated from the sensor.",
                style = MaterialTheme.typography.labelMedium,
                color = ColorTextMuted,
            )
        }

        // Freeze toggle — visible when connected or when already frozen
        if (isConnected || conditionsFrozen) {
            Spacer(Modifier.height(8.dp))
            Row(
                modifier              = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment     = Alignment.CenterVertically,
            ) {
                Column(Modifier.weight(1f)) {
                    Text(
                        text  = if (conditionsFrozen) "Conditions Frozen" else "Freeze Conditions",
                        style = MaterialTheme.typography.bodyMedium,
                        color = if (conditionsFrozen) ColorAmber else ColorTextPrimary,
                    )
                    Text(
                        text  = if (conditionsFrozen)
                            "Live auto-populate paused — conditions locked in place"
                        else
                            "Pause live updates and lock current conditions",
                        style = MaterialTheme.typography.labelMedium,
                        color = ColorTextMuted,
                    )
                }
                Switch(
                    checked         = conditionsFrozen,
                    onCheckedChange = { onToggleFreeze() },
                    colors          = SwitchDefaults.colors(
                        checkedThumbColor   = ColorBackground,
                        checkedTrackColor   = ColorAmber,
                        uncheckedThumbColor = ColorTextMuted,
                        uncheckedTrackColor = ColorBorder,
                    ),
                )
            }
        }
    }
}

// ── Firing angle panel (inclined fire) ───────────────────────────────────────

@Composable
private fun FiringAnglePanel(
    angleDeg: Double,
    onUpdate: (Double) -> Unit,
) {
    var angleStr by remember(angleDeg) { mutableStateOf("%.1f".format(angleDeg)) }

    fun commit() {
        val v = angleStr.toDoubleOrNull()?.coerceIn(-90.0, 90.0) ?: angleDeg
        onUpdate(v)
    }

    InstrumentPanel(label = "Firing Angle") {
        NumericField(
            value         = angleStr,
            onValueChange = { angleStr = it },
            label         = "Angle",
            suffix        = "°",
            modifier      = Modifier.fillMaxWidth(),
            onDone        = ::commit,
            onFocusLost   = ::commit,
        )
        Spacer(Modifier.height(4.dp))
        Text(
            text  = "Uphill (+) or downhill (−) firing angle in degrees. 0° = flat range.",
            style = MaterialTheme.typography.labelMedium,
            color = ColorTextMuted,
        )
    }
}

// ── Firing azimuth panel (Coriolis) ───────────────────────────────────────────

private enum class CompassStatus { READING, OK, LOW_ACCURACY, UNAVAILABLE }

@SuppressLint("MissingPermission")
@Composable
private fun FiringAzimuthPanel(
    azimuthDeg:  Double,
    latitudeDeg: Double,
    onUpdate:    (Double) -> Unit,
) {
    val context = LocalContext.current
    val sm      = remember(context) { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager }

    var compassStatus  by remember { mutableStateOf<CompassStatus?>(null) }
    var activeListener by remember { mutableStateOf<SensorEventListener?>(null) }

    // Unregister the sensor listener if the composable exits before a reading arrives.
    DisposableEffect(Unit) {
        onDispose { activeListener?.let { sm.unregisterListener(it) } }
    }

    InstrumentPanel(label = "Firing Azimuth  (Coriolis)") {
        Row(
            modifier              = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.Center,
        ) {
            FiringAzimuthDial(
                angleDeg      = azimuthDeg,
                onAngleChange = onUpdate,
            )
        }
        Spacer(Modifier.height(8.dp))
        Row(
            modifier              = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment     = Alignment.CenterVertically,
        ) {
            OutlinedButton(
                onClick = {
                    compassStatus = CompassStatus.READING
                    val sensor = sm.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
                    if (sensor == null) {
                        compassStatus = CompassStatus.UNAVAILABLE
                        return@OutlinedButton
                    }
                    val listener = object : SensorEventListener {
                        override fun onSensorChanged(event: SensorEvent) {
                            val lowAccuracy = event.accuracy < SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM
                            val rotMatrix = FloatArray(9)
                            SensorManager.getRotationMatrixFromVector(rotMatrix, event.values)
                            val orientation = FloatArray(3)
                            SensorManager.getOrientation(rotMatrix, orientation)
                            var azimuthRad = orientation[0].toDouble()

                            // Apply magnetic declination using the best available location.
                            // Falls back to Coriolis latitude + longitude=0 when GPS is absent.
                            val hasPerm = ContextCompat.checkSelfPermission(
                                context, android.Manifest.permission.ACCESS_COARSE_LOCATION
                            ) == android.content.pm.PackageManager.PERMISSION_GRANTED
                            val loc = if (hasPerm) runCatching {
                                val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
                                listOf(LocationManager.NETWORK_PROVIDER,
                                       LocationManager.GPS_PROVIDER,
                                       LocationManager.PASSIVE_PROVIDER)
                                    .firstNotNullOfOrNull { lm.getLastKnownLocation(it) }
                            }.getOrNull() else null
                            val geoLat = loc?.latitude?.toFloat() ?: latitudeDeg.toFloat()
                            val geoLon = loc?.longitude?.toFloat() ?: 0f
                            val geoAlt = loc?.altitude?.toFloat()  ?: 0f
                            if (geoLat != 0.0f) {
                                val decl = GeomagneticField(geoLat, geoLon, geoAlt, System.currentTimeMillis()).declination
                                azimuthRad += Math.toRadians(decl.toDouble())
                            }

                            val azDeg = ((Math.toDegrees(azimuthRad) + 360.0) % 360.0)
                            onUpdate(azDeg)
                            compassStatus  = if (lowAccuracy) CompassStatus.LOW_ACCURACY else CompassStatus.OK
                            sm.unregisterListener(this)
                            activeListener = null
                        }
                        override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
                    }
                    activeListener = listener
                    sm.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI)
                },
                border = BorderStroke(1.dp, ColorAmber.copy(alpha = 0.6f)),
                colors = ButtonDefaults.outlinedButtonColors(contentColor = ColorAmber),
            ) {
                Text(
                    text  = if (compassStatus == CompassStatus.READING) "Reading…" else "Use Compass",
                    style = MaterialTheme.typography.labelSmall,
                )
            }
            when (compassStatus) {
                CompassStatus.LOW_ACCURACY ->
                    Text("Low accuracy", style = MaterialTheme.typography.labelMedium, color = ColorTextMuted)
                CompassStatus.UNAVAILABLE ->
                    Text("No compass sensor", style = MaterialTheme.typography.labelMedium, color = ColorTextMuted)
                CompassStatus.OK ->
                    Text("${"%.1f".format(azimuthDeg)}°", style = MaterialTheme.typography.labelMedium, color = ColorAmber)
                else -> {}
            }
        }
        Spacer(Modifier.height(4.dp))
        Text(
            text  = "Direction you are firing  (0° = North). Used for Coriolis calculation.",
            style = MaterialTheme.typography.labelMedium,
            color = ColorTextMuted,
        )
    }
}


// ── Capture panel ─────────────────────────────────────────────────────────────

@Composable
private fun CapturePanel(
    captureResult: Set<String>?,
    onCapture:     () -> Unit,
) {
    InstrumentPanel(label = "Capture from Sensors") {
        OutlinedButton(
            onClick  = onCapture,
            modifier = Modifier.fillMaxWidth(),
            border   = BorderStroke(1.dp, ColorAmber),
            colors   = ButtonDefaults.outlinedButtonColors(contentColor = ColorAmber),
        ) {
            Text("Capture All Available", style = MaterialTheme.typography.labelMedium)
        }

        if (captureResult != null) {
            Spacer(Modifier.height(8.dp))
            val allFields = listOf("temperature", "windSpeed", "altitude")
            val captured  = captureResult
            val missed    = allFields.filter { it !in captured }

            fun fieldLabel(key: String) = when (key) {
                "temperature" -> "Temp"
                "windSpeed"   -> "Wind Speed"
                "altitude"    -> "Altitude"
                else          -> key
            }

            if (captured.isNotEmpty()) {
                Text(
                    text  = "✓  ${captured.joinToString("  •  ") { fieldLabel(it) }}",
                    style = MaterialTheme.typography.labelSmall,
                    color = ColorSuccess,
                )
            }
            if (missed.isNotEmpty()) {
                Text(
                    text  = "○  Not available: ${missed.joinToString(", ") { fieldLabel(it) }}",
                    style = MaterialTheme.typography.labelSmall,
                    color = ColorTextMuted,
                )
            }
            if (captured.isEmpty()) {
                Text(
                    text  = "No sensor data available — connect BT sensor or allow location",
                    style = MaterialTheme.typography.labelSmall,
                    color = ColorAmberDim,
                )
            }
            Spacer(Modifier.height(2.dp))
            Text(
                text  = "Wind direction updates live via dial",
                style = MaterialTheme.typography.labelSmall,
                color = ColorAmberDim,
            )
        }
    }
}
