package com.onthelevel.feature.level

import android.provider.Settings
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.onthelevel.core.design.LevelColors
import com.onthelevel.core.design.ReadoutFontFamily
import com.onthelevel.core.sensors.SurfaceGuidance
import kotlin.math.cos
import kotlin.math.hypot
import kotlin.math.sin

/**
 * Glass bullseye whose target and rings are both driven by SurfaceGuidance.
 *
 * The bubble is a VOID in fluorescent fluid: a perfectly round, borderless
 * translucent orb — one symmetric gradient whose edge fades out rather than
 * outlining — grounded by a contact shadow and lit by a small fixed upper-left
 * reflection. The calibration marks (and the red target dot) stay readable
 * through it. Every layer serves depth or readability (SURFACE_LEVEL_PLAN.md:
 * no decorative gloss); alphas are the tuning knobs if anything reads as chrome.
 */
@Composable
fun SurfaceBullseye(pitchDegrees: Double, rollDegrees: Double, isLocked: Boolean, reducedMotion: Boolean) {
    val guidance = SurfaceGuidance.from(pitchDegrees, rollDegrees)
    val targetX = guidance.normalizedX.toFloat()
    val targetY = guidance.normalizedY.toFloat()
    val animatedX = remember { Animatable(0f) }
    val animatedY = remember { Animatable(0f) }
    val context = LocalContext.current
    val systemMotionDisabled = remember(context) {
        Settings.Global.getFloat(context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) == 0f
    }
    val lockPulse = remember { Animatable(0f) }
    val textMeasurer = rememberTextMeasurer()
    // These must run independently. animateTo suspends until a spring settles;
    // sequencing axes here would starve Y under the sensor's ~50 Hz retargeting
    // and make diagonal movement trace an unnatural L.
    LaunchedEffect(targetX, reducedMotion, systemMotionDisabled) {
        if (reducedMotion || systemMotionDisabled) {
            animatedX.snapTo(targetX)
        } else {
            animatedX.animateTo(targetX, spring(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMediumLow))
        }
    }
    LaunchedEffect(targetY, reducedMotion, systemMotionDisabled) {
        if (reducedMotion || systemMotionDisabled) {
            animatedY.snapTo(targetY)
        } else {
            animatedY.animateTo(targetY, spring(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMediumLow))
        }
    }
    LaunchedEffect(isLocked) {
        if (isLocked) {
            lockPulse.snapTo(1f)
            lockPulse.animateTo(0f, spring(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessLow))
        } else lockPulse.snapTo(0f)
    }
    Canvas(Modifier.size(250.dp)) {
        val radius = size.minDimension / 2f
        val center = Offset(size.width / 2f, size.height / 2f)
        val vialRadius = radius * .74f

        val bubbleCenter = center + Offset(animatedX.value * vialRadius, animatedY.value * vialRadius)
        // Nested inside the 1° target ring (0.148 × radius): the level-read is the
        // classic "bubble inside the circle", never the bubble swallowing the mark.
        val bubbleRadius = radius * BUBBLE_RADIUS_RATIO

        // The target ring highlights on what the EYE sees: the drawn bubble sitting
        // inside (or tangent to) the circle. Lock truth — label, pulse, haptic —
        // stays with LockDetector; this accent must never outlive the visual.
        val targetRingRadius = vialRadius * SurfaceGuidance.ringRadiusRatio(1.0).toFloat()
        val bubbleInsideTarget =
            hypot(bubbleCenter.x - center.x, bubbleCenter.y - center.y) + bubbleRadius <=
                targetRingRadius + 1.dp.toPx()

        drawVialGlass(center, radius, vialRadius)
        drawCalibrationMarks(center, radius, vialRadius, bubbleInsideTarget, textMeasurer)
        if (lockPulse.value > 0f) {
            drawCircle(LevelColors.LimeLock.copy(alpha = .55f * lockPulse.value), vialRadius, center, style = Stroke(3.dp.toPx()))
        }

        drawBubbleBody(bubbleCenter, bubbleRadius)
        drawBubbleLight(bubbleCenter, bubbleRadius)
    }
}

private fun DrawScope.drawVialGlass(center: Offset, radius: Float, vialRadius: Float) {
    drawCircle(Brush.radialGradient(listOf(Color(0xFF22272E), Color(0xFF090A0C)), center, radius), radius, center)
    // Faint sheen from the fixed upper-left light — a depth cue, kept far below gloss.
    drawCircle(
        Brush.radialGradient(
            listOf(Color.White.copy(alpha = .05f), Color.Transparent),
            center + Offset(-radius * .38f, -radius * .42f),
            radius * .9f,
        ),
        radius,
        center,
    )
    // Fluid vignette: the dye reads darker where it meets the vial wall.
    drawCircle(
        Brush.radialGradient(
            colorStops = arrayOf(
                0f to Color.Transparent,
                .78f to Color.Transparent,
                1f to Color.Black.copy(alpha = .32f),
            ),
            center = center,
            radius = vialRadius,
        ),
        vialRadius,
        center,
    )
    // Machined bezel: a shallow metallic annulus, lit from above, instead of a
    // flat outline — housing depth without adding information noise.
    val bezelWidth = 5.dp.toPx()
    drawCircle(
        Brush.linearGradient(
            listOf(Color.White.copy(alpha = .20f), Color.White.copy(alpha = .05f)),
            start = Offset(center.x, center.y - radius),
            end = Offset(center.x, center.y + radius),
        ),
        radius - bezelWidth / 2f,
        center,
        style = Stroke(bezelWidth),
    )
    drawCircle(Color.White.copy(alpha = .14f), radius - bezelWidth, center, style = Stroke(1.dp.toPx()))
    // Etched bezel ticks: cardinals strong, 45° minors faint — orientation
    // vocabulary borrowed from real instrument bezels.
    for (degrees in 0 until 360 step 45) {
        val isCardinal = degrees % 90 == 0
        val angle = Math.toRadians(degrees.toDouble())
        val direction = Offset(cos(angle).toFloat(), sin(angle).toFloat())
        val outer = radius - 1.dp.toPx()
        val inner = outer - if (isCardinal) 8.dp.toPx() else 5.dp.toPx()
        drawLine(
            Color.White.copy(alpha = if (isCardinal) .45f else .22f),
            center + direction * inner,
            center + direction * outer,
            if (isCardinal) 1.5.dp.toPx() else 1.dp.toPx(),
        )
    }
}

private fun DrawScope.drawCalibrationMarks(
    center: Offset,
    radius: Float,
    vialRadius: Float,
    bubbleInsideTarget: Boolean,
    textMeasurer: TextMeasurer,
) {
    val labelStyle = TextStyle(
        fontSize = 9.sp,
        fontFamily = ReadoutFontFamily,
        color = Color.White.copy(alpha = .38f),
    )
    SurfaceGuidance.RING_DEGREES.forEach { mark ->
        val isTargetRing = mark == 1.0
        val ringRadius = vialRadius * SurfaceGuidance.ringRadiusRatio(mark).toFloat()
        val color = if (isTargetRing && bubbleInsideTarget) {
            // Restrained lime accent, driven by visual containment of the drawn
            // bubble. The label still carries the lock state in words.
            LevelColors.LimeLock.copy(alpha = .9f)
        } else {
            val alpha = when (mark) {
                1.0 -> .46f // The target zone needs to be the clearest calibration mark.
                2.0 -> .36f
                else -> .27f
            }
            Color.White.copy(alpha = alpha)
        }
        drawCircle(
            color,
            ringRadius,
            center,
            style = Stroke(if (isTargetRing && bubbleInsideTarget) 2.dp.toPx() else 1.5.dp.toPx()),
        )
        // Each ring says what it means: its degree value, etched small on the
        // lower-right diagonal just outside the ring.
        val layout = textMeasurer.measure(AnnotatedString("${mark.toInt()}°"), labelStyle)
        val diagonal = (ringRadius + 5.dp.toPx()) * DIAGONAL_COMPONENT
        val position = center + Offset(diagonal, diagonal)
        drawText(
            layout,
            topLeft = position - Offset(layout.size.width / 2f, layout.size.height / 2f),
        )
    }
    val crosshairStroke = 1.dp.toPx()
    drawLine(Color.White.copy(alpha = .32f), Offset(center.x - vialRadius, center.y), Offset(center.x + vialRadius, center.y), crosshairStroke)
    drawLine(Color.White.copy(alpha = .32f), Offset(center.x, center.y - vialRadius), Offset(center.x, center.y + vialRadius), crosshairStroke)
    // The exact-center target mark stays beneath the translucent bubble, seen
    // through the trapped air just as it would be in a real vial.
    drawCircle(Color.White.copy(alpha = .85f), radius * .030f, center)
    drawCircle(LevelColors.VialTarget, radius * .020f, center)
}

/** A borderless, perfectly round translucent orb: one symmetric gradient, edge fading to nothing. */
private fun DrawScope.drawBubbleBody(c: Offset, b: Float) {
    // Fluorescent halo: the dye glows brightest where the lens bends light past the rim.
    drawCircle(
        Brush.radialGradient(
            listOf(LevelColors.VialLime.copy(alpha = .16f), Color.Transparent),
            c,
            b * 1.9f,
        ),
        b * 1.9f,
        c,
    )
    // Contact shadow: a soft dark ring just outside the rim seats the bubble IN the
    // fluid instead of floating over the graphics.
    drawCircle(
        Brush.radialGradient(
            colorStops = arrayOf(
                0f to Color.Transparent,
                .68f to Color.Transparent,
                .80f to Color.Black.copy(alpha = .30f),
                1f to Color.Transparent,
            ),
            center = c,
            radius = b * 1.3f,
        ),
        b * 1.3f,
        c,
    )
    // The orb itself. Centered gradient = perfectly round; the soft bright band
    // sits evenly inside the rim, and the thin dark contact edge fades out rather
    // than drawing a border.
    drawCircle(
        Brush.radialGradient(
            colorStops = arrayOf(
                0f to LevelColors.VialHighlight.copy(alpha = .24f),
                .52f to LevelColors.VialLime.copy(alpha = .18f),
                .80f to LevelColors.VialLime.copy(alpha = .36f),
                .91f to LevelColors.VialHighlight.copy(alpha = .66f),
                .965f to LevelColors.VialDeep.copy(alpha = .32f),
                1f to Color.Transparent,
            ),
            center = c,
            radius = b * 1.06f,
        ),
        b * 1.06f,
        c,
    )
}

/**
 * Reflection layer, world-oriented: a soft window-style reflection patch at the
 * upper-left (how glass and soap bubbles actually mirror a light source), a sharp
 * glint at its heart, and a faint pass-through shimmer on the far rim.
 */
private fun DrawScope.drawBubbleLight(c: Offset, b: Float) {
    withTransform({
        translate(c.x - b * .30f, c.y - b * .32f)
        rotate(-38f, Offset.Zero)
        scale(1f, .55f, Offset.Zero)
    }) {
        drawCircle(
            Brush.radialGradient(
                listOf(Color.White.copy(alpha = .50f), Color.Transparent),
                Offset.Zero,
                b * .46f,
            ),
            b * .46f,
            Offset.Zero,
        )
    }
    drawCircle(Color.White.copy(alpha = .85f), b * .07f, c + Offset(-b * .30f, -b * .34f))
    val shimmerCenter = c + Offset(b * .42f, b * .44f)
    drawCircle(
        Brush.radialGradient(
            listOf(Color.White.copy(alpha = .12f), Color.Transparent),
            shimmerCenter,
            b * .30f,
        ),
        b * .30f,
        shimmerCenter,
    )
}

private const val BUBBLE_RADIUS_RATIO = .12f
private const val DIAGONAL_COMPONENT = .7071f
