package dev.kaizen.app.haptics

import android.content.Context
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext

/**
 * Fine-grained haptic feedback — the "premium" feel on devices with a good LRA.
 *
 * Three tiers depending on the device:
 *  - API 30+ with a capable actuator: VibrationEffect.Composition with primitives
 *    (TICK/CLICK) -> short, precise taps instead of a dull buzz.
 *  - API 29: predefined effects (EFFECT_TICK/EFFECT_CLICK).
 *  - API 26-28: createOneShot fallback.
 *
 * NOTE: the emulator has no real haptics — always evaluate on a physical device.
 * All vibrate() calls are wrapped so a missing permission / quirk never crashes.
 */
class Haptics(private val vibrator: Vibrator?) {

    private fun active(): Vibrator? = vibrator?.takeIf { it.hasVibrator() }

    /** Light tap — tile / mode selection. */
    fun tick() {
        val vib = active() ?: return
        if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_TICK)) {
            vib.composePrimitives(VibrationEffect.Composition.PRIMITIVE_TICK to 0.55f)
        } else {
            vib.predefinedOrOneShot(soft = true)
        }
    }

    /** Firmer click — send button, satisfying "message sent" confirmation. */
    fun click() {
        val vib = active() ?: return
        if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_CLICK)) {
            vib.composePrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK to 1.0f)
        } else {
            vib.predefinedOrOneShot(soft = false)
        }
    }

    /** Double-tap — the model received the prompt and starts thinking. */
    fun thinkingStart() {
        val vib = active() ?: return
        if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_TICK)) {
            vib.composePrimitives(
                Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
                Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 0.7f, 60),
            )
        } else {
            vib.predefinedOrOneShot(soft = false)
        }
    }

    /** Rising crescendo — first token arrived, answer is streaming. */
    fun responseStart() {
        val vib = active() ?: return
        if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_CLICK)) {
            vib.composePrimitives(
                Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f, 0),
                Triple(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.7f, 50),
                Triple(VibrationEffect.Composition.PRIMITIVE_CLICK, 1.0f, 50),
            )
        } else {
            vib.predefinedOrOneShot(soft = false)
        }
    }

    /** Firm settle — stream finished, answer complete. */
    fun responseEnd() {
        val vib = active() ?: return
        if (supportsPrimitive(vib, VibrationEffect.Composition.PRIMITIVE_CLICK)) {
            vib.composePrimitives(
                Triple(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.8f, 0),
                Triple(VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f, 80),
            )
        } else {
            vib.predefinedOrOneShot(soft = false)
        }
    }

    // --- internal ---

    private fun supportsPrimitive(vib: Vibrator, primitive: Int): Boolean =
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
            vib.arePrimitivesSupported(primitive)[0]

    @RequiresApi(Build.VERSION_CODES.R)
    private fun Vibrator.composePrimitives(vararg steps: Pair<Int, Float>) = runCatching {
        val comp = VibrationEffect.startComposition()
        for ((primitive, scale) in steps) comp.addPrimitive(primitive, scale)
        vibrate(comp.compose())
    }

    @RequiresApi(Build.VERSION_CODES.R)
    private fun Vibrator.composePrimitives(vararg steps: Triple<Int, Float, Int>) = runCatching {
        val comp = VibrationEffect.startComposition()
        for ((primitive, scale, delayMs) in steps) comp.addPrimitive(primitive, scale, delayMs)
        vibrate(comp.compose())
    }

    private fun Vibrator.predefinedOrOneShot(soft: Boolean) = runCatching {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            val effect = if (soft) VibrationEffect.EFFECT_TICK else VibrationEffect.EFFECT_CLICK
            vibrate(VibrationEffect.createPredefined(effect))
        } else {
            vibrate(VibrationEffect.createOneShot(if (soft) 10L else 18L, VibrationEffect.DEFAULT_AMPLITUDE))
        }
    }
}

/** Builds the right Vibrator for the API level and keeps it across recompositions. */
@Composable
fun rememberHaptics(): Haptics {
    val context = LocalContext.current
    return remember {
        val vib: Vibrator? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            (context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as? VibratorManager)?.defaultVibrator
        } else {
            @Suppress("DEPRECATION")
            context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
        }
        Haptics(vib)
    }
}
