package com.onthelevel.feature.level

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.CapturedSurfaceSample
import com.onthelevel.core.sensors.Ema
import com.onthelevel.core.sensors.OrientationMath
import com.onthelevel.core.sensors.SettlingDetector
import com.onthelevel.core.sensors.StableSurfaceCapture
import com.onthelevel.core.sensors.SurfaceCalibration
import com.onthelevel.core.sensors.SurfaceCalibrationSample
import com.onthelevel.core.sensors.SurfaceCalibrationValidation
import com.onthelevel.core.sensors.TwoSampleCalibration
import com.onthelevel.core.settings.CalibrationMode
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.Locale

private enum class Stage { OVERVIEW, CAPTURING }
private enum class CapturePhase { READY, RUNNING, ERROR }

/**
 * Surface calibration. OVERVIEW shows the current correction and lets the user start,
 * reset, or leave. CAPTURING runs the guided flow and shows ONLY calibration controls;
 * Cancel returns to OVERVIEW without touching the saved calibration, so exploring or
 * backing out never changes the current setting.
 *
 * Captures read SensorSource.gravity directly and never apply the stored calibration.
 */
@Composable
fun SurfaceCalibrationScreen(container: AppContainer, onBack: () -> Unit) {
    KeepScreenOn()

    val sensorSource = container.sensorSource
    val mode by container.settings.calibrationMode
        .collectAsStateWithLifecycle(initialValue = CalibrationMode.SIMPLE)
    val totalSteps = mode.captureCount
    val existingCalibration by container.settings.surfaceCalibration
        .collectAsStateWithLifecycle(initialValue = SurfaceCalibration.NONE)
    val isCalibrated = existingCalibration != SurfaceCalibration.NONE

    var stage by remember { mutableStateOf(Stage.OVERVIEW) }
    var phase by remember { mutableStateOf(CapturePhase.READY) }
    var captures by remember { mutableStateOf<List<CapturedSurfaceSample>>(emptyList()) }
    var capture by remember { mutableStateOf<StableSurfaceCapture?>(null) }
    var error by remember { mutableStateOf<String?>(null) }
    var justSaved by remember { mutableStateOf(false) }

    val rawReadingFlow = remember(sensorSource) {
        val pitchEma = Ema(0.15)
        val rollEma = Ema(0.15)
        val tiltEma = Ema(0.15)
        val settling = SettlingDetector()
        var lastNanos: Long? = null
        sensorSource.gravity.map { raw ->
            val dtSeconds = lastNanos?.let { (raw.timestampNanos - it) / 1e9 } ?: 0.0
            lastNanos = raw.timestampNanos
            val pitch = pitchEma.update(OrientationMath.surfacePitchDegrees(raw), dtSeconds)
            val roll = rollEma.update(OrientationMath.surfaceRollDegrees(raw), dtSeconds)
            val tilt = tiltEma.update(OrientationMath.surfaceTiltMagnitudeDegrees(raw), dtSeconds)
            val settlingResult = settling.update(pitch, roll, raw.timestampNanos)
            SurfaceCalibrationSample(pitch, roll, tilt, raw.timestampNanos, settlingResult.isSettling)
        }
    }
    val reading by rawReadingFlow.collectAsStateWithLifecycle(initialValue = null)

    // Fully synchronous body: persistence goes to `scope`, never awaited here. Writing a
    // key (capture/phase/stage) and then suspending inside would let Compose cancel the
    // effect mid-write and strand the transition.
    LaunchedEffect(reading, stage, phase, capture) {
        if (stage != Stage.CAPTURING || phase != CapturePhase.RUNNING) return@LaunchedEffect
        val activeCapture = capture ?: return@LaunchedEffect
        val current = reading ?: return@LaunchedEffect
        val captured = activeCapture.add(current) ?: return@LaunchedEffect

        val newCaptures = captures + captured
        capture = null
        if (newCaptures.size < totalSteps) {
            captures = newCaptures
            phase = CapturePhase.READY
            return@LaunchedEffect
        }
        val calibration = TwoSampleCalibration.deriveSurfaceFromSamples(newCaptures)
        val consistent = SurfaceCalibrationValidation.isRotationConsistent(newCaptures)
        if (!SurfaceCalibrationValidation.isAcceptable(calibration) || !consistent) {
            captures = emptyList()
            error = "Those positions didn't agree. Keep the phone on the same spot, turn it in place, and don't flip it over."
            phase = CapturePhase.ERROR
        } else {
            captures = emptyList()
            phase = CapturePhase.READY
            stage = Stage.OVERVIEW
            justSaved = true
            // App-owned scope: the write must survive an immediate Done/back.
            container.applicationScope.launch { container.settings.setSurfaceCalibration(calibration) }
        }
    }

    fun startCalibrating() {
        captures = emptyList()
        error = null
        justSaved = false
        phase = CapturePhase.READY
        stage = Stage.CAPTURING
    }

    fun cancelCalibrating() {
        // Saved calibration is never touched mid-flow, so this simply restores it.
        captures = emptyList()
        capture = null
        error = null
        phase = CapturePhase.READY
        stage = Stage.OVERVIEW
    }

    Column(modifier = Modifier.fillMaxSize().padding(horizontal = 24.dp, vertical = 16.dp)) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Text("SURFACE CALIBRATION", style = MaterialTheme.typography.labelSmall, color = LevelColors.TextDim)
            if (stage == Stage.CAPTURING) {
                Text(
                    text = if (mode == CalibrationMode.THOROUGH) "DETAILED" else "STANDARD",
                    style = MaterialTheme.typography.labelSmall,
                    color = LevelColors.Amber,
                )
            }
        }

        if (stage == Stage.OVERVIEW) {
            OverviewContent(
                modifier = Modifier.weight(1f).fillMaxWidth(),
                isCalibrated = isCalibrated,
                calibration = existingCalibration,
                justSaved = justSaved,
                mode = mode,
                onModeChange = { container.applicationScope.launch { container.settings.setCalibrationMode(it) } },
            )
            Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
                Button(onClick = ::startCalibrating, modifier = Modifier.fillMaxWidth()) {
                    Text(if (isCalibrated) "Recalibrate" else "Calibrate now")
                }
                if (isCalibrated) {
                    CalibrationOutlinedButton(
                        text = "Reset to phone defaults",
                        onClick = {
                            justSaved = false
                            container.applicationScope.launch { container.settings.clearSurfaceCalibration() }
                        },
                        modifier = Modifier.fillMaxWidth(),
                    )
                }
                CalibrationOutlinedButton(text = "Done", onClick = onBack, modifier = Modifier.fillMaxWidth())
            }
        } else {
            CapturingContent(
                modifier = Modifier.weight(1f).fillMaxWidth(),
                phase = phase,
                stepIndex = captures.size,
                totalSteps = totalSteps,
                mode = mode,
                error = error,
                tiltText = reading?.let { formatDegrees(it.tiltDegrees) } ?: "—",
                status = captureStatus(reading?.isSettling, reading?.tiltDegrees, reading != null),
            )
            Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
                when (phase) {
                    CapturePhase.READY -> {
                        val canStart = reading?.let {
                            !it.isSettling && it.tiltDegrees <= StableSurfaceCapture.MAXIMUM_CALIBRATION_TILT_DEGREES
                        } ?: false
                        Button(
                            onClick = {
                                capture = StableSurfaceCapture()
                                phase = CapturePhase.RUNNING
                            },
                            enabled = canStart,
                            modifier = Modifier.fillMaxWidth(),
                        ) {
                            Text(if (captures.isEmpty()) "Capture first position" else "Capture position ${captures.size + 1}")
                        }
                    }
                    CapturePhase.RUNNING -> Text(
                        text = "Hold still — capturing 1.5 seconds…",
                        modifier = Modifier.fillMaxWidth(),
                        textAlign = TextAlign.Center,
                        style = MaterialTheme.typography.bodyLarge,
                        color = LevelColors.TextDim,
                    )
                    CapturePhase.ERROR -> Button(
                        onClick = {
                            captures = emptyList()
                            error = null
                            phase = CapturePhase.READY
                        },
                        modifier = Modifier.fillMaxWidth(),
                    ) { Text("Start over") }
                }
                CalibrationOutlinedButton(text = "Cancel", onClick = ::cancelCalibrating, modifier = Modifier.fillMaxWidth())
            }
        }
    }
}

@Composable
private fun OverviewContent(
    modifier: Modifier,
    isCalibrated: Boolean,
    calibration: SurfaceCalibration,
    justSaved: Boolean,
    mode: CalibrationMode,
    onModeChange: (CalibrationMode) -> Unit,
) {
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
    ) {
        Text(
            text = if (justSaved) "CALIBRATION SAVED" else "CURRENT CORRECTION",
            style = MaterialTheme.typography.labelSmall,
            // Lime marks the confirmed/active state: just-saved, or an active calibration.
            color = when {
                justSaved -> LevelColors.LimeLockText
                isCalibrated -> LevelColors.LimeLock
                else -> LevelColors.TextDim
            },
        )
        Spacer(Modifier.height(10.dp))
        if (isCalibrated) {
            Text(
                "Pitch ${signedDegrees(calibration.pitchBiasDegrees)}",
                style = MaterialTheme.typography.headlineMedium,
                color = LevelColors.TextPrimary,
            )
            Text(
                "Roll ${signedDegrees(calibration.rollBiasDegrees)}",
                style = MaterialTheme.typography.headlineMedium,
                color = LevelColors.TextPrimary,
            )
        } else {
            Text(
                "Using phone defaults",
                style = MaterialTheme.typography.headlineMedium,
                color = LevelColors.TextPrimary,
                textAlign = TextAlign.Center,
            )
        }
        Spacer(Modifier.height(10.dp))
        Text(
            text = "Calibration corrects your phone-and-case setup. It does not make a surface level.",
            style = MaterialTheme.typography.bodyMedium,
            color = LevelColors.TextDim,
            textAlign = TextAlign.Center,
        )
        Spacer(Modifier.height(28.dp))
        Text("METHOD", style = MaterialTheme.typography.labelSmall, color = LevelColors.TextDim)
        Spacer(Modifier.height(8.dp))
        SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
            CalibrationMode.entries.forEachIndexed { index, entry ->
                SegmentedButton(
                    selected = mode == entry,
                    onClick = { onModeChange(entry) },
                    shape = SegmentedButtonDefaults.itemShape(index, CalibrationMode.entries.size),
                ) {
                    Text(if (entry == CalibrationMode.SIMPLE) "Standard" else "Detailed")
                }
            }
        }
        Text(
            text = if (mode == CalibrationMode.THOROUGH) {
                "Four positions, a quarter-turn apart. Averages more sensor noise and cross-checks the turn."
            } else {
                "Two positions, a half-turn apart. Quick and dependable."
            },
            style = MaterialTheme.typography.labelSmall,
            color = LevelColors.TextDim,
            textAlign = TextAlign.Center,
            modifier = Modifier.padding(top = 8.dp),
        )
    }
}

@Composable
private fun CapturingContent(
    modifier: Modifier,
    phase: CapturePhase,
    stepIndex: Int,
    totalSteps: Int,
    mode: CalibrationMode,
    error: String?,
    tiltText: String,
    status: String,
) {
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
    ) {
        if (phase != CapturePhase.ERROR) {
            Text(
                "POSITION ${stepIndex + 1} OF $totalSteps",
                style = MaterialTheme.typography.labelSmall,
                color = LevelColors.TextDim,
            )
            Spacer(Modifier.height(14.dp))
        }
        Text(
            text = when {
                phase == CapturePhase.ERROR -> error ?: "Let's try that again"
                stepIndex == 0 -> "Lay the phone screen-up on a firm, roughly level surface. Keep it still."
                mode == CalibrationMode.SIMPLE ->
                    "Spin the phone a half-turn (180°) in the same spot. Don't lift or flip it over."
                else ->
                    "Spin the phone a quarter-turn (90°) the same direction, same spot. Don't lift or flip it over."
            },
            style = MaterialTheme.typography.headlineMedium,
            color = if (phase == CapturePhase.ERROR) LevelColors.Amber else LevelColors.TextPrimary,
            textAlign = TextAlign.Center,
        )
        if (phase != CapturePhase.ERROR) {
            Spacer(Modifier.height(32.dp))
            Text(tiltText, style = MaterialTheme.typography.displaySmall, color = LevelColors.TextPrimary)
            Text(status, style = MaterialTheme.typography.bodyLarge, color = LevelColors.Amber, textAlign = TextAlign.Center)
        }
    }
}

private fun captureStatus(isSettling: Boolean?, tiltDegrees: Double?, hasReading: Boolean): String = when {
    !hasReading -> "Waiting for a sensor reading"
    isSettling == true -> "Settling — keep the phone still"
    tiltDegrees != null && tiltDegrees > StableSurfaceCapture.MAXIMUM_CALIBRATION_TILT_DEGREES ->
        "Find a flatter surface (under 5°)"
    else -> "Ready to capture"
}

/** Outlined buttons default to a near-invisible outline on graphite; give them a real border. */
@Composable
private fun CalibrationOutlinedButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
    OutlinedButton(
        onClick = onClick,
        modifier = modifier,
        border = BorderStroke(1.5.dp, LevelColors.Amber.copy(alpha = .65f)),
        colors = ButtonDefaults.outlinedButtonColors(contentColor = LevelColors.Amber),
    ) { Text(text) }
}

private fun signedDegrees(value: Double): String {
    val rounded = Math.round(value * 10.0) / 10.0
    return when {
        rounded == 0.0 -> "0.0°" // never render a signed negative zero
        rounded > 0.0 -> String.format(Locale.US, "+%.1f°", rounded)
        else -> String.format(Locale.US, "%.1f°", rounded)
    }
}
