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

package space.rcmd.android.sizzle.ui.toolbox

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import space.rcmd.android.sizzle.model.DelayDivisions
import space.rcmd.android.sizzle.model.ParamSpec
import space.rcmd.android.sizzle.model.ToolboxSlot
import space.rcmd.android.sizzle.ui.AppViewModel
import space.rcmd.android.sizzle.ui.theme.LocalRetro

/**
 * The Tape Delay's custom editor: four tape heads side by side, each a vertical
 * tempo-synced division fader (hard-snapping to the [DelayDivisions] steps —
 * 1/1 … 1/16 plus dotted and triplet values) with an on/off toggle above it, then
 * the shared Feedback / Dry-Wet / Tape-Strength sliders below. Feedback is capped
 * below 1 in both the UI and the DSP, so it can never build a runaway loop.
 */
@Composable
fun DelayEditor(vm: AppViewModel, slot: ToolboxSlot) {
    val c = LocalRetro.current
    @Suppress("UNUSED_VARIABLE") val rev = vm.revision // subscribe so heads/params refresh

    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) {
        Text(
            "Tape heads — drag a fader to set its time; tap the number to switch it off/on:",
            color = c.textDim, fontFamily = FontFamily.Monospace, fontSize = 11.sp,
        )
        Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            for (h in 0 until DelayDivisions.HEADS) {
                DelayHead(vm, slot, h, rev, Modifier.weight(1f))
            }
        }
        // Common controls (left as plain sliders with sane defaults). Feedback's max
        // is the DSP's hard ceiling, so a positive-feedback runaway is impossible.
        ParamControl(vm, slot, ParamSpec("feedback", "Feedback", 0.4f, 0f, DelayDivisions.MAX_FEEDBACK))
        ParamControl(vm, slot, ParamSpec("drywet", "Dry/Wet", 0.35f, 0f, 1f))
        ParamControl(vm, slot, ParamSpec("tape", "Tape Strength", 0.3f, 0f, 1f))
    }
}

/** One tape head: an on/off toggle, a vertical hard-snapping division fader, and
 *  the current division label. Longer values sit at the top (taller fill). */
@Composable
private fun DelayHead(vm: AppViewModel, slot: ToolboxSlot, head: Int, rev: Int, modifier: Modifier) {
    @Suppress("UNUSED_PARAMETER") val ignored = rev // param only used to bust skipping
    val c = LocalRetro.current
    val on = slot.float("head${head}On", if (head == 0) 1f else 0f) >= 0.5f
    val steps = DelayDivisions.size
    val idx = slot.float("head${head}Div", DelayDivisions.DEFAULT.toFloat()).toInt().coerceIn(0, steps - 1)
    val border = if (on) c.accent else c.grid

    Column(modifier, horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(4.dp)) {
        // On/off toggle (the head number).
        Box(
            Modifier
                .fillMaxWidth()
                .height(26.dp)
                .border(1.dp, border, RectangleShape)
                .background(if (on) c.accent.copy(alpha = 0.22f) else c.surface)
                .pointerInput(head, slot.index) {
                    detectTapGestures { slot.set("head${head}On", if (on) 0f else 1f); vm.bumpForToolbar() }
                },
            contentAlignment = Alignment.Center,
        ) {
            Text("${head + 1}", color = if (on) c.accent else c.textDim,
                fontFamily = FontFamily.Monospace, fontSize = 13.sp)
        }

        // Vertical fader: top = longest (1/1), bottom = shortest (1/16T). Tap or drag
        // to snap to a step.
        Canvas(
            Modifier
                .fillMaxWidth()
                .height(150.dp)
                .background(c.surface)
                .border(1.dp, border, RectangleShape)
                .pointerInput(head, slot.index) {
                    detectTapGestures { off ->
                        val i = (off.y / size.height * steps).toInt().coerceIn(0, steps - 1)
                        slot.set("head${head}Div", i.toFloat()); vm.bumpForToolbar()
                    }
                }
                .pointerInput(head, slot.index) {
                    detectDragGestures { change, _ ->
                        change.consume()
                        val i = (change.position.y / size.height * steps).toInt().coerceIn(0, steps - 1)
                        slot.set("head${head}Div", i.toFloat()); vm.bumpForToolbar()
                    }
                },
        ) {
            val w = size.width
            val hgt = size.height
            val stepH = hgt / steps
            // Fill from the bottom up to the selected step: a longer division (higher
            // up) shows a taller fader.
            val fillTop = idx * stepH
            drawRect(
                (if (on) c.accent else c.grid).copy(alpha = 0.45f),
                Offset(0f, fillTop), Size(w, hgt - fillTop),
            )
            // Step dividers.
            for (s in 1 until steps) {
                drawLine(c.grid.copy(alpha = 0.4f), Offset(0f, s * stepH), Offset(w, s * stepH), strokeWidth = 1f)
            }
            // Highlight the selected step band.
            drawRect(
                (if (on) c.playhead else c.textDim).copy(alpha = 0.5f),
                Offset(0f, fillTop), Size(w, stepH),
            )
        }

        Text(
            DelayDivisions.label(idx),
            color = if (on) c.text else c.textDim,
            fontFamily = FontFamily.Monospace, fontSize = 10.sp,
        )
    }
}
