package com.onthelevel.core.sensors

import kotlin.math.abs

/**
 * Level-lock state machine with hysteresis, dwell, and haptic debounce (BRIEF.md
 * §Sensor and measurement architecture). Operates ONLY on the stable calibrated
 * measurement — the same value the numeric readout shows. The lock must never
 * disagree with the number on screen.
 *
 * Time is injected (callers pass `nowMillis`) so transitions are unit-testable.
 */
class LockDetector(
    private val enterThresholdDegrees: Double = DEFAULT_ENTER_DEGREES,
    private val exitThresholdDegrees: Double = DEFAULT_EXIT_DEGREES,
    private val dwellMillis: Long = DEFAULT_DWELL_MILLIS,
    private val feedbackDebounceMillis: Long = DEFAULT_FEEDBACK_DEBOUNCE_MILLIS,
) {
    companion object {
        // BRIEF.md: enter at no more than 0.2°, exit at at least 0.35°. The exit
        // threshold doubles as the tolerance stated next to the locked label, so the
        // rounded readout and "level" claim can never contradict (Codex review).
        const val DEFAULT_ENTER_DEGREES = 0.2
        const val DEFAULT_EXIT_DEGREES = 0.35
        const val DEFAULT_DWELL_MILLIS = 400L
        const val DEFAULT_FEEDBACK_DEBOUNCE_MILLIS = 3_000L
    }

    init {
        require(exitThresholdDegrees > enterThresholdDegrees) {
            "Hysteresis requires exit > enter threshold"
        }
    }

    data class Result(
        val isLocked: Boolean,
        /** True exactly once per lock acquisition, and only outside the debounce window. */
        val fireFeedback: Boolean,
    )

    private var locked = false
    private var withinEnterSinceMillis: Long? = null
    private var lastFeedbackAtMillis: Long? = null

    fun update(stableTiltDegrees: Double, nowMillis: Long): Result {
        val magnitude = abs(stableTiltDegrees)
        var fire = false

        if (!locked) {
            if (magnitude <= enterThresholdDegrees) {
                val since = withinEnterSinceMillis ?: nowMillis.also { withinEnterSinceMillis = it }
                if (nowMillis - since >= dwellMillis) {
                    locked = true
                    val last = lastFeedbackAtMillis
                    if (last == null || nowMillis - last >= feedbackDebounceMillis) {
                        fire = true
                        lastFeedbackAtMillis = nowMillis
                    }
                }
            } else {
                withinEnterSinceMillis = null
            }
        } else if (magnitude >= exitThresholdDegrees) {
            locked = false
            withinEnterSinceMillis = null
        }

        return Result(isLocked = locked, fireFeedback = fire)
    }

    fun reset() {
        locked = false
        withinEnterSinceMillis = null
        // lastFeedbackAtMillis survives reset on purpose: switching modes must not
        // defeat the haptic debounce.
    }
}
