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

package space.rcmd.android.sizzle.ui.mixer

import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
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.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
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.model.Mixer
import space.rcmd.android.sizzle.model.MixerChannel
import space.rcmd.android.sizzle.model.ToolboxKind
import space.rcmd.android.sizzle.ui.AppViewModel
import space.rcmd.android.sizzle.ui.isLandscape
import space.rcmd.android.sizzle.ui.components.RepeatButton
import space.rcmd.android.sizzle.ui.components.RetroButton
import space.rcmd.android.sizzle.ui.components.RetroDropdown
import space.rcmd.android.sizzle.ui.components.SectionLabel
import space.rcmd.android.sizzle.ui.theme.LocalRetro

/**
 * The second tab: a four-channel mixer, one strip per tracker track. Each strip
 * chooses an instrument + up to four effects (all drawn from the toolbox), sets a
 * MIDI channel, and has a volume fader plus mute/solo. Everything writes straight
 * into the shared [Mixer] model that the audio engine reads.
 */
@Composable
fun MixerScreen(vm: AppViewModel) {
    val c = LocalRetro.current
    @Suppress("UNUSED_VARIABLE") val redraw = vm.revision
    Column(Modifier.fillMaxSize().background(c.background)) {
        RecordToolbar(vm)
        Row(
            Modifier.weight(1f).fillMaxWidth().padding(6.dp),
            horizontalArrangement = Arrangement.spacedBy(6.dp),
        ) {
            vm.project.mixer.channels.forEach { channel ->
                ChannelStrip(vm, channel, Modifier.weight(1f).fillMaxHeight())
            }
        }
    }
}

/**
 * Master-bus recorder controls at the top of the mixer: arm, FX-tail length, and
 * the output folder. Recording begins when playback starts and stops automatically
 * a tail (in bars) after the sequencer stops — a pause/stop, or the arrangement end
 * with looping off. WAV files are written to the chosen folder with a date-time name.
 */
@Composable
private fun RecordToolbar(vm: AppViewModel) {
    val c = LocalRetro.current
    val dirPicker = rememberLauncherForActivityResult(
        ActivityResultContracts.OpenDocumentTree(),
    ) { uri -> uri?.let { vm.setRecordDir(it) } }

    Row(
        Modifier
            .fillMaxWidth()
            .background(c.surface)
            .padding(6.dp)
            .horizontalScroll(rememberScrollState()),
        horizontalArrangement = Arrangement.spacedBy(6.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        val recording = vm.masterRecording
        // Arm/record. With no folder yet, the first tap opens the picker instead.
        RetroButton(
            if (recording) "● REC" else "● ARM",
            active = vm.recordArmed && !recording,
            danger = recording,
            onClick = { if (vm.recordDirUri == null) dirPicker.launch(null) else vm.toggleRecordArm() },
        )
        RetroDropdown(
            label = "TAIL",
            options = listOf(0, 1, 2, 3, 4),
            selected = vm.recordTailBars,
            optionLabel = { "$it bar" + if (it == 1) "" else "s" },
            onSelect = { vm.updateRecordTailBars(it) },
        )
        RetroButton(
            "DIR:" + (vm.recordDirName ?: "—"),
            onClick = { dirPicker.launch(null) },
        )
        val status = when {
            recording -> "recording…"
            vm.recordDirUri == null -> "pick a folder to record"
            vm.recordArmed -> "armed — starts on play"
            vm.lastRecordingName != null -> "saved ${vm.lastRecordingName}"
            else -> "master → WAV"
        }
        Text(
            status, color = c.textDim, fontFamily = FontFamily.Monospace,
            fontSize = 11.sp, maxLines = 1,
        )
    }
}

@Composable
private fun ChannelStrip(vm: AppViewModel, channel: MixerChannel, modifier: Modifier) {
    val c = LocalRetro.current
    @Suppress("UNUSED_VARIABLE") val rev = vm.revision // subscribe so faders/mute/solo/routing refresh

    Column(
        modifier.background(c.surface).padding(6.dp),
        verticalArrangement = Arrangement.spacedBy(4.dp),
    ) {
        Text("CH ${channel.index + 1}", color = c.accent,
            fontFamily = FontFamily.Monospace, fontSize = 13.sp)

        if (isLandscape()) {
            // Landscape: two columns per bus — routing on the left, level controls on
            // the right — so the fader + LIM/mute/solo are visible without scrolling
            // in the short height. The routing column scrolls if a device is extra
            // short; the level column never does.
            Row(Modifier.weight(1f).fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) {
                Column(
                    Modifier.weight(1f).fillMaxHeight().verticalScroll(rememberScrollState()),
                    verticalArrangement = Arrangement.spacedBy(4.dp),
                ) { ChannelRouting(vm, channel) }
                Column(
                    Modifier.weight(1f).fillMaxHeight(),
                    verticalArrangement = Arrangement.spacedBy(4.dp),
                ) { ChannelLevels(vm, channel) }
            }
        } else {
            // Portrait: one column, the fader filling the leftover height.
            ChannelRouting(vm, channel)
            ChannelLevels(vm, channel)
        }
    }
}

/** Instrument + FX + MIDI-channel selectors for a mixer [channel]. */
@Composable
private fun ColumnScope.ChannelRouting(vm: AppViewModel, channel: MixerChannel) {
    val c = LocalRetro.current
    @Suppress("UNUSED_VARIABLE") val rev = vm.revision // defeat strong-skipping so routing refreshes
    val toolbox = vm.project.toolbox
    val instrumentSlots = toolbox.filter { it.type?.kind == ToolboxKind.INSTRUMENT }
    val effectSlots = toolbox.filter { it.type?.kind == ToolboxKind.EFFECT }

    SectionLabel("Instrument")
    RetroDropdown(
        label = "",
        options = listOf(-1) + instrumentSlots.map { it.index },
        selected = channel.instrumentSlot,
        optionLabel = { idx -> if (idx < 0) "—" else "${idx + 1}:${toolbox[idx].name}" },
        onSelect = { channel.instrumentSlot = it; vm.bumpForToolbar() },
        modifier = Modifier.fillMaxWidth(),
    )

    SectionLabel("FX 1-4")
    for (fx in 0 until Mixer.FX_SLOTS) {
        RetroDropdown(
            label = "",
            options = listOf(-1) + effectSlots.map { it.index },
            selected = channel.fxSlots[fx],
            optionLabel = { idx -> if (idx < 0) "—" else "${idx + 1}:${toolbox[idx].name}" },
            onSelect = { channel.fxSlots[fx] = it; vm.rebuildAudioRouting() },
            modifier = Modifier.fillMaxWidth(),
        )
    }

    SectionLabel("MIDI Ch")
    Row(verticalAlignment = Alignment.CenterVertically) {
        RepeatButton("-") { channel.midiChannel = (channel.midiChannel - 1).coerceAtLeast(0); vm.bumpForToolbar() }
        Text(
            "${channel.midiChannel}", color = c.text,
            fontFamily = FontFamily.Monospace, fontSize = 12.sp,
            textAlign = TextAlign.Center, maxLines = 1,
            modifier = Modifier.width(28.dp),
        )
        RepeatButton("+") { channel.midiChannel = (channel.midiChannel + 1).coerceAtMost(16); vm.bumpForToolbar() }
    }
}

/** Volume fader + limiter + mute/solo for a mixer [channel]. The fader fills the
 *  leftover height of its column. */
@Composable
private fun ColumnScope.ChannelLevels(vm: AppViewModel, channel: MixerChannel) {
    @Suppress("UNUSED_VARIABLE") val rev = vm.revision // defeat strong-skipping so LIM/mute/solo refresh
    SectionLabel("Volume")
    VerticalFader(
        value = channel.volume,
        default = Mixer.DEFAULT_VOLUME,
        onValueChange = { channel.volume = it; vm.bumpForToolbar() },
        modifier = Modifier.weight(1f).fillMaxWidth(),
    )
    // Per-bus soft-clip limiter, sitting above mute/solo.
    RetroButton("LIM", active = channel.limiter, modifier = Modifier.fillMaxWidth()) {
        channel.limiter = !channel.limiter; vm.bumpForToolbar()
    }
    Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(4.dp)) {
        RetroButton("M", active = channel.mute, modifier = Modifier.weight(1f)) {
            channel.mute = !channel.mute; vm.bumpForToolbar()
        }
        RetroButton("S", active = channel.solo, modifier = Modifier.weight(1f)) {
            channel.solo = !channel.solo; vm.bumpForToolbar()
        }
    }
}

/** A vertical volume fader: fill grows from the bottom; tap or drag to set.
 *  Snaps to the [default] level within a small band so returning to the standard
 *  volume is easy to hit. */
@Composable
private fun VerticalFader(value: Float, default: Float, onValueChange: (Float) -> Unit, modifier: Modifier) {
    val c = LocalRetro.current
    Box(
        modifier
            .border(1.dp, c.grid, RectangleShape)
            .background(c.background)
            .pointerInput(default) {
                detectTapGestures { off -> onValueChange(snapDefault(1f - off.y / size.height, default)) }
            }
            .pointerInput(default) {
                detectDragGestures { change, _ ->
                    change.consume()
                    onValueChange(snapDefault(1f - change.position.y / size.height, default))
                }
            },
        contentAlignment = Alignment.BottomCenter,
    ) {
        Box(
            Modifier
                .fillMaxWidth()
                .fillMaxHeight(value.coerceIn(0.001f, 1f))
                .background(c.accent.copy(alpha = 0.30f)),
            contentAlignment = Alignment.TopCenter,
        ) {
            Box(Modifier.fillMaxWidth().height(3.dp).background(c.accent)) // thumb
        }
    }
}

/** Snap the fader to the [default] level when the raw value lands within a small
 *  band around it, so returning to the standard volume is easy. */
private fun snapDefault(raw: Float, default: Float): Float {
    val v = raw.coerceIn(0f, 1f)
    return if (kotlin.math.abs(v - default) < SNAP_BAND) default else v
}

private const val SNAP_BAND = 0.03f
