package com.onthelevel.feature.angle

import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.onthelevel.AppContainer
import com.onthelevel.core.design.KeepScreenOn
import com.onthelevel.core.design.LevelColors
import com.onthelevel.core.sensors.Ema
import com.onthelevel.core.sensors.GravitySample
import com.onthelevel.core.sensors.OrientationMath
import com.onthelevel.feature.level.formatDegrees
import com.onthelevel.feature.level.performConfirmHaptic
import kotlinx.coroutines.flow.map

/**
 * Scaffold Angle screen: live absolute/relative angle with hold-to-zero (always free).
 *
 * Relative zero stores the full gravity DIRECTION at the moment of zeroing, and the
 * relative reading is the angle between the current and stored directions. Subtracting
 * tilt magnitudes would lose the axis: zeroed at 10° pitch and moved to 10° roll, the
 * device has genuinely rotated ~14°, and that is what this reports (Codex review).
 * The relative reading is therefore unsigned.
 *
 * TODO(pro): target-angle alerts and saved named references behind the entitlement.
 */
@Composable
fun AngleScreen(container: AppContainer) {
    KeepScreenOn()

    val sensorSource = container.sensorSource
    if (!sensorSource.isAvailable) {
        Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Text("No usable tilt sensor on this device.", color = LevelColors.TextDim)
        }
        return
    }

    val readingFlow = remember {
        // Smooth the vector components, then derive angles — keeps the smoothed
        // gravity direction available for the vector-based zero reference.
        val xEma = Ema(0.15)
        val yEma = Ema(0.15)
        val zEma = Ema(0.15)
        var lastNanos: Long? = null
        sensorSource.gravity.map { g ->
            val dt = lastNanos?.let { (g.timestampNanos - it) / 1e9 } ?: 0.0
            lastNanos = g.timestampNanos
            GravitySample(
                x = xEma.update(g.x, dt),
                y = yEma.update(g.y, dt),
                z = zEma.update(g.z, dt),
                timestampNanos = g.timestampNanos,
            )
        }
    }
    val smoothed by readingFlow.collectAsStateWithLifecycle(initialValue = null)

    // Zero reference: the smoothed gravity direction captured on long-press.
    // Empty array = no reference set. DoubleArray keeps rememberSaveable happy.
    var zeroReference by rememberSaveable { mutableStateOf(doubleArrayOf()) }
    val view = LocalView.current

    val primaryDegrees = smoothed?.let { s ->
        if (zeroReference.size == 3) {
            OrientationMath.angleBetweenDegrees(
                s,
                GravitySample(zeroReference[0], zeroReference[1], zeroReference[2], 0),
            )
        } else {
            OrientationMath.surfaceTiltMagnitudeDegrees(s)
        }
    }
    val isRelative = zeroReference.size == 3

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(horizontal = 24.dp, vertical = 16.dp),
    ) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween,
        ) {
            Text("ANGLE", style = MaterialTheme.typography.labelSmall, color = LevelColors.TextDim)
            Text(
                text = if (isRelative) "RELATIVE · HOLD TO RE-ZERO" else "HOLD TO ZERO",
                style = MaterialTheme.typography.labelSmall,
                color = LevelColors.TextFaint,
            )
        }

        Box(
            modifier = Modifier
                .weight(1f)
                .fillMaxWidth()
                .pointerInput(Unit) {
                    detectTapGestures(
                        onLongPress = {
                            smoothed?.let {
                                zeroReference = doubleArrayOf(it.x, it.y, it.z)
                                view.performConfirmHaptic()
                            }
                        },
                        onDoubleTap = { zeroReference = doubleArrayOf() },
                    )
                },
            contentAlignment = Alignment.Center,
        ) {
            Column(horizontalAlignment = Alignment.CenterHorizontally) {
                Text(
                    text = primaryDegrees?.let(::formatDegrees) ?: "—",
                    style = MaterialTheme.typography.displayLarge,
                    color = LevelColors.TextPrimary,
                )
                if (isRelative) {
                    Text(
                        text = "from saved orientation · double-tap to clear",
                        style = MaterialTheme.typography.labelSmall,
                        color = LevelColors.Amber,
                        textAlign = TextAlign.Center,
                    )
                }
            }
        }

        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.spacedBy(14.dp),
        ) {
            val pitch = smoothed?.let(OrientationMath::surfacePitchDegrees)
            SecondaryValue("PITCH", pitch?.let(::formatDegrees), Modifier.weight(1f))
            SecondaryValue(
                "ROLL",
                smoothed?.let(OrientationMath::surfaceRollDegrees)?.let(::formatDegrees),
                Modifier.weight(1f),
            )
            SecondaryValue(
                "GRADE",
                pitch?.let { formatGrade(OrientationMath.percentGrade(it)) },
                Modifier.weight(1f),
            )
        }
    }
}

@Composable
private fun SecondaryValue(label: String, value: String?, modifier: Modifier = Modifier) {
    Column(modifier = modifier) {
        Text(label, style = MaterialTheme.typography.labelSmall, color = LevelColors.TextDim)
        Text(value ?: "—", style = MaterialTheme.typography.headlineMedium, color = LevelColors.TextPrimary)
    }
}

private fun formatGrade(grade: Double): String = when {
    grade.isInfinite() -> "∞"
    else -> String.format(java.util.Locale.US, "%.1f%%", grade)
}
