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

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

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.withTimeoutOrNull
import space.rcmd.android.sizzle.ui.theme.LocalRetro

/**
 * A tiny library of "pixel" UI building blocks. They are intentionally plain —
 * hard rectangles, monospace text, 1px borders — so they are cheap to draw
 * (helping the "lightweight UI" requirement) and match the retro aesthetic.
 * Reusing these keeps every screen visually consistent.
 */

/**
 * A flat, square-cornered button. [active] paints it in the accent colour.
 * Pass [width] to pin a fixed width (e.g. a button whose label toggles, like
 * PLAY/PAUSE) so it never resizes; the single-line label clips with an ellipsis
 * if it can't fit.
 */
@Composable
fun RetroButton(
    label: String,
    modifier: Modifier = Modifier,
    active: Boolean = false,
    width: Dp? = null,
    danger: Boolean = false,
    onClick: () -> Unit,
) {
    RetroButtonSurface(label, modifier, active, width, danger, Modifier.clickable(onClick = onClick))
}

/**
 * A stepper button (e.g. "+" / "-") that fires once on press and then **auto-repeats
 * while held**, with the repeat interval shrinking exponentially so a long hold spins
 * the value ever faster. Lifting the finger — or dragging off, e.g. to scroll a
 * toolbar — stops the repeat.
 */
@Composable
fun RepeatButton(
    label: String,
    modifier: Modifier = Modifier,
    active: Boolean = false,
    width: Dp? = null,
    danger: Boolean = false,
    onStep: () -> Unit,
) {
    val step by rememberUpdatedState(onStep)
    RetroButtonSurface(
        label, modifier, active, width, danger,
        Modifier.pointerInput(Unit) {
            awaitEachGesture {
                awaitFirstDown(requireUnconsumed = false)
                step() // immediate first step on press
                var delayMs = REPEAT_START_MS
                while (true) {
                    // Wait for finger-up/cancel, but no longer than the current delay.
                    var ended = false
                    withTimeoutOrNull(delayMs) {
                        waitForUpOrCancellation() // completes on up OR cancel (scroll)
                        ended = true
                    }
                    if (ended) break            // released or cancelled → stop
                    step()                      // still held → repeat …
                    delayMs = (delayMs * REPEAT_DECAY).toLong().coerceAtLeast(REPEAT_MIN_MS)
                }
            }
        },
    )
}

/** Shared visual for [RetroButton]/[RepeatButton]; [interaction] carries the tap or
 *  hold-repeat gesture so the look stays identical across both. */
@Composable
private fun RetroButtonSurface(
    label: String,
    modifier: Modifier,
    active: Boolean,
    width: Dp?,
    danger: Boolean,
    interaction: Modifier,
) {
    val c = LocalRetro.current
    // danger (red) wins over active (accent); otherwise the neutral surface look.
    val hue = when { danger -> c.danger; active -> c.accent; else -> null }
    Box(
        modifier
            .then(if (width != null) Modifier.width(width) else Modifier)
            .border(1.dp, hue ?: c.grid, RectangleShape)
            .background(hue?.copy(alpha = 0.18f) ?: c.surface)
            .then(interaction)
            .padding(horizontal = 10.dp, vertical = 6.dp),
        contentAlignment = Alignment.Center,
    ) {
        Text(
            label,
            color = hue ?: c.text,
            fontFamily = FontFamily.Monospace,
            fontSize = 12.sp,
            maxLines = 1,
            softWrap = false,
            overflow = TextOverflow.Ellipsis,
        )
    }
}

// Hold-to-repeat timing: first repeat after REPEAT_START_MS, then each interval
// multiplied by REPEAT_DECAY (exponential speed-up) down to REPEAT_MIN_MS.
private const val REPEAT_START_MS = 320L
private const val REPEAT_MIN_MS = 24L
private const val REPEAT_DECAY = 0.80

/** A labelled dropdown selector rendered as `LABEL:value ▾`. */
@Composable
fun <T> RetroDropdown(
    label: String,
    options: List<T>,
    selected: T,
    modifier: Modifier = Modifier,
    optionLabel: (T) -> String = { it.toString() },
    onSelect: (T) -> Unit,
) {
    val c = LocalRetro.current
    var open by remember { mutableStateOf(false) }
    // Reserve the width of the longest option so the field never resizes as the
    // selection changes — a common source of toolbar "jitter".
    val longest = options.maxByOrNull { optionLabel(it).length }?.let(optionLabel).orEmpty()
    // Drop the "label:" prefix entirely when no label is given (e.g. mixer selectors
    // that already have a section header above them).
    val prefix = if (label.isEmpty()) "" else "$label:"
    Box(modifier) {
        Box(
            Modifier
                .border(1.dp, c.grid, RectangleShape)
                .background(c.surface)
                .clickable { open = true }
                .padding(horizontal = 8.dp, vertical = 6.dp),
        ) {
            // Invisible sizer (longest option) fixes the width; the visible value
            // draws on top and clips with an ellipsis if it ever overflows.
            Text(
                "$prefix$longest ▾",
                color = Color.Transparent,
                fontFamily = FontFamily.Monospace, fontSize = 12.sp,
                maxLines = 1, softWrap = false,
            )
            Text(
                "$prefix${optionLabel(selected)} ▾",
                color = c.text, fontFamily = FontFamily.Monospace, fontSize = 12.sp,
                maxLines = 1, softWrap = false, overflow = TextOverflow.Ellipsis,
            )
        }
        DropdownMenu(expanded = open, onDismissRequest = { open = false }) {
            options.forEach { opt ->
                DropdownMenuItem(
                    text = { Text(optionLabel(opt), fontFamily = FontFamily.Monospace) },
                    onClick = { onSelect(opt); open = false },
                )
            }
        }
    }
}

/** A dim, spaced section header, e.g. "── TRANSPORT ──". */
@Composable
fun SectionLabel(text: String, modifier: Modifier = Modifier) {
    Text(
        text.uppercase(),
        modifier = modifier.padding(vertical = 4.dp),
        color = LocalRetro.current.textDim,
        fontFamily = FontFamily.Monospace,
        fontSize = 11.sp,
    )
}

