package com.onthelevel.feature.level

import android.content.Context
import android.media.AudioAttributes
import android.media.SoundPool
import android.os.SystemClock
import android.speech.tts.TextToSpeech
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
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.platform.LocalContext
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import com.onthelevel.R
import com.onthelevel.core.audio.SurfaceTickPolicy
import com.onthelevel.core.audio.VoiceGuidancePolicy
import com.onthelevel.core.settings.AudioAssistMode
import java.util.Locale

/**
 * Hands-free Surface audio assist (SOUND_DESIGN_HANDOFF.md).
 *
 * TONES — continuous proximity ticking: a tick repeats faster as you near level (axis-
 * agnostic "warmer/colder"), the rate is the message. On lock, `level.wav` plays once and
 * it goes silent while it holds. Constant tick loudness (acceleration is the signal).
 *
 * VOICE — deliberately separate: spoken corrections + "that's level", settled-change only.
 *
 * Everything plays on the media stream (so the volume slider works) and never takes audio
 * focus. Players/speakers exist only while enabled and foregrounded; released on dispose.
 */
@Composable
fun AudioLevelAssist(reading: LevelReading?, mode: AudioAssistMode) {
    val isForeground = rememberIsResumed()
    val tickPolicy = remember { SurfaceTickPolicy() }
    val voicePolicy = remember { VoiceGuidancePolicy() }
    val context = LocalContext.current.applicationContext

    val player = remember(mode, isForeground, context) {
        if (mode != AudioAssistMode.OFF && isForeground) SonarSoundPool(context) else null
    }
    val speaker = remember(mode, isForeground, context) {
        if (mode == AudioAssistMode.VOICE && isForeground) VoiceSpeaker(context) else null
    }
    DisposableEffect(player) { onDispose { player?.release() } }
    DisposableEffect(speaker) { onDispose { speaker?.shutdown() } }

    LaunchedEffect(reading, mode, isForeground, player, speaker) {
        val current = reading ?: return@LaunchedEffect

        // TONES: proximity ticks off a monotonic deadline; `level.wav` at lock.
        when (
            tickPolicy.update(
                SurfaceTickPolicy.Input(
                    isEnabled = mode == AudioAssistMode.TONES,
                    isAppForeground = isForeground,
                    placementOk = current.placementOk,
                    surfacePresentation = current.surfacePresentation,
                    errorDegrees = current.stableTiltDegrees ?: current.displayPrimaryDegrees,
                    isLocked = current.isLocked,
                    nowMillis = SystemClock.elapsedRealtime(),
                ),
            )
        ) {
            SurfaceTickPolicy.Cue.TICK -> player?.playTick()
            SurfaceTickPolicy.Cue.LEVEL -> player?.playLevel()
            null -> Unit
        }
        // Stop the 1.39 s level.wav tail the moment we're no longer holding level, so a
        // fresh tick after a quick unlock can't overlap it (Codex requirement).
        if (!(mode == AudioAssistMode.TONES && current.isLocked)) player?.stopLevel()

        val phrase = voicePolicy.update(
            VoiceGuidancePolicy.Input(
                isEnabled = mode == AudioAssistMode.VOICE,
                isAppForeground = isForeground,
                placementOk = current.placementOk,
                presentation = current.surfacePresentation,
                isSettling = current.isSettling,
                pitchDegrees = current.stableSurfacePitchDegrees ?: 0.0,
                rollDegrees = current.stableSurfaceRollDegrees ?: 0.0,
                isLocked = current.isLocked,
                nowMillis = System.currentTimeMillis(),
            ),
        )
        if (phrase != null) speaker?.speak(phrase)
    }
}

@Composable
private fun rememberIsResumed(): Boolean {
    val lifecycleOwner = LocalLifecycleOwner.current
    var isResumed by remember(lifecycleOwner) {
        mutableStateOf(lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED))
    }
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, _ ->
            isResumed = lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
    }
    return isResumed
}

private class SonarSoundPool(context: Context) {
    private val loaded = mutableSetOf<Int>()
    private val soundPool = SoundPool.Builder()
        .setMaxStreams(4)
        .setAudioAttributes(
            AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                .build(),
        )
        .build()
    private val tickSound: Int
    private val levelSound: Int
    private var levelStreamId: Int = 0

    init {
        soundPool.setOnLoadCompleteListener { _, sampleId, status -> if (status == 0) loaded += sampleId }
        tickSound = soundPool.load(context, R.raw.tick, 1)
        levelSound = soundPool.load(context, R.raw.level, 1)
    }

    /** One proximity tick — constant volume (the rate is the signal, not loudness). */
    fun playTick() {
        if (tickSound in loaded) soundPool.play(tickSound, TICK_VOLUME, TICK_VOLUME, 1, 0, 1f)
    }

    /** The "you're level" arrival; retained so it can be stopped on unlock. */
    fun playLevel() {
        stopLevel()
        if (levelSound in loaded) {
            levelStreamId = soundPool.play(levelSound, LEVEL_VOLUME, LEVEL_VOLUME, 2, 0, 1f)
        }
    }

    fun stopLevel() {
        if (levelStreamId != 0) {
            soundPool.stop(levelStreamId)
            levelStreamId = 0
        }
    }

    fun release() = soundPool.release()

    private companion object {
        const val TICK_VOLUME = 0.55f
        const val LEVEL_VOLUME = 0.85f
    }
}

/**
 * On-device text-to-speech, structured so premium pre-recorded clips can be swapped in
 * later without touching callers. Mixes as sonification and does not take audio focus.
 * TODO(voice): prefer bundled clips (res/raw) when present, fall back to TTS.
 */
private class VoiceSpeaker(context: Context) {
    private var ready = false
    private lateinit var tts: TextToSpeech

    init {
        tts = TextToSpeech(context.applicationContext) { status ->
            if (status == TextToSpeech.SUCCESS) {
                tts.language = Locale.US
                tts.setAudioAttributes(
                    AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        .build(),
                )
                ready = true
            }
        }
    }

    fun speak(phrase: VoiceGuidancePolicy.Phrase) {
        if (!ready) return
        tts.speak(textFor(phrase), TextToSpeech.QUEUE_FLUSH, null, "level-voice")
    }

    private fun textFor(phrase: VoiceGuidancePolicy.Phrase): String = when (phrase) {
        VoiceGuidancePolicy.Phrase.Level -> "that's level"
        is VoiceGuidancePolicy.Phrase.Raise -> {
            val side = when (phrase.direction) {
                VoiceGuidancePolicy.Direction.RAISE_LEFT -> "left"
                VoiceGuidancePolicy.Direction.RAISE_RIGHT -> "right"
                VoiceGuidancePolicy.Direction.RAISE_TOP -> "top"
                VoiceGuidancePolicy.Direction.RAISE_BOTTOM -> "bottom"
            }
            if (phrase.fine) "raise the $side, just a little" else "raise the $side"
        }
    }

    fun shutdown() {
        tts.stop()
        tts.shutdown()
    }
}
