// SPDX-FileCopyrightText: 2026 Reactorcoremeltdown
// SPDX-License-Identifier: GPL-3.0-or-later

package space.rcmd.android.sizzle.ui.components

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import space.rcmd.android.sizzle.ui.theme.LocalRetro
import kotlin.math.cos
import kotlin.math.sin

/** Vertical finger travel (dp) for a full min→max knob sweep. Larger = less sensitive. */
private const val KNOB_TRAVEL_DP = 280f

/**
 * A rotary gauge (knob). Shows a 270° arc from bottom-left to bottom-right with a
 * filled portion + pointer for the current value, the formatted value above and a
 * short label below. Control is RELATIVE and fader-like: sliding the finger up raises
 * the value and down lowers it, with a full [min]..[max] sweep spanning [KNOB_TRAVEL_DP]
 * of travel — decoupled from the knob's small size, so fine adjustments are easy and
 * the control isn't touchy. Double-tap resets to [default]. Values are reported in the
 * caller's [min]..[max] range.
 *
 * Rotary controls are the tracker's preferred way to show a continuous parameter
 * (see the synth editor); pair with [RetroDropdown] for enumerated params.
 */
@Composable
fun Knob(
    label: String,
    value: Float,
    min: Float,
    max: Float,
    valueText: String,
    modifier: Modifier = Modifier,
    default: Float = min,
    onChange: (Float) -> Unit,
) {
    val c = LocalRetro.current
    val span = if (max > min) max - min else 1f
    val frac = ((value - min) / span).coerceIn(0f, 1f)
    // Latest value seen without restarting the drag gesture, so a relative drag starts
    // from wherever the knob currently sits.
    val currentValue by rememberUpdatedState(value)

    Column(
        modifier,
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(2.dp),
    ) {
        Text(
            valueText, color = c.text, fontFamily = FontFamily.Monospace, fontSize = 9.sp,
            maxLines = 1, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth(),
        )
        Canvas(
            Modifier
                .size(44.dp)
                .pointerInput(min, max) {
                    // Relative: slide up to raise, down to lower. A full min..max sweep
                    // takes KNOB_TRAVEL_DP of vertical travel (not the 44dp knob height),
                    // so the control is calm and fine tweaks are easy. Accumulate from
                    // the current value at drag start (via [currentValue]).
                    val travelPx = KNOB_TRAVEL_DP.dp.toPx()
                    var acc = 0f
                    detectDragGestures(onDragStart = { acc = currentValue }) { change, dragAmount ->
                        change.consume()
                        acc = (acc - dragAmount.y / travelPx * span).coerceIn(min, max)
                        onChange(acc)
                    }
                }
                .pointerInput(min, max) {
                    detectTapGestures(onDoubleTap = { onChange(default) })
                },
        ) {
            val stroke = 4f
            val r = (size.minDimension - stroke) / 2f
            val cx = size.width / 2f
            val cy = size.height / 2f
            val start = 135f
            val sweep = 270f
            // Track, then the filled value arc.
            drawArc(
                color = c.grid, startAngle = start, sweepAngle = sweep, useCenter = false,
                topLeft = Offset(cx - r, cy - r), size = androidx.compose.ui.geometry.Size(r * 2, r * 2),
                style = androidx.compose.ui.graphics.drawscope.Stroke(width = stroke),
            )
            drawArc(
                color = c.accent, startAngle = start, sweepAngle = sweep * frac, useCenter = false,
                topLeft = Offset(cx - r, cy - r), size = androidx.compose.ui.geometry.Size(r * 2, r * 2),
                style = androidx.compose.ui.graphics.drawscope.Stroke(width = stroke),
            )
            // Pointer to the current position.
            val ang = Math.toRadians((start + sweep * frac).toDouble())
            drawLine(
                color = c.text,
                start = Offset(cx, cy),
                end = Offset(cx + (r * 0.85f) * cos(ang).toFloat(), cy + (r * 0.85f) * sin(ang).toFloat()),
                strokeWidth = 2f,
            )
        }
        Text(
            label, color = c.textDim, fontFamily = FontFamily.Monospace, fontSize = 9.sp,
            maxLines = 1, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().height(12.dp),
        )
    }
}
