Pith - grain
package com.vgmlr.grain

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.outlined.Public
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import kotlin.math.roundToInt
import androidx.compose.ui.platform.LocalWindowInfo

private val CCard = Color(0xFF272A30).copy(alpha = 0.8f)
private val CHead = Color(0xFF33373E).copy(alpha = 0.8f)
private val CChip = Color(0xFF3A4048)
private val CHIP_RADIUS = 6.dp
private val CHIP_PAD = 12.dp
private val ROW_H = 46.dp
private val HEAD_H = 46.dp
private val FS = 15.sp
private val HEAD_INK = PAGE_INK.copy(alpha = 0.75f)
private val HEAD_ICON = 16.dp
private val HEAD_ICON_GAP = 10.dp
private val HEAD_TEXT_DY = 1.dp
private val HEAD_FS = 12.sp
private val SETTINGS_TOP_GAP = 12.dp
private const val SAVE_DEBOUNCE_MS = 1200L

@Composable
fun SettingsView(vm: GrainViewModel, onBack: () -> Unit) {
    val scope = rememberCoroutineScope()
    val aBias by vm.bias.collectAsState()
    val aPad by vm.edgePad.collectAsState()
    val aKey by vm.keySize.collectAsState()
    val aOps by vm.opsSide.collectAsState()
    val aDec by vm.decimals.collectAsState()
    val aDigits by vm.digits.collectAsState()
    val aMax by vm.historyMax.collectAsState()
    var tMax by remember(aMax) { mutableStateOf(aMax.toString()) }
    var tPad by remember(aPad) { mutableStateOf(aPad.toString()) }
    var tDec by remember(aDec) { mutableStateOf(aDec.toString()) }
    val winPx = LocalWindowInfo.current.containerSize.width
    val keyW = vm.keypadW.takeIf { it > 0f }
        ?: (with(LocalDensity.current) { winPx.toDp() }.value - BAR_EDGE.value * 2)
    val autoKey = keyAuto(keyW, aPad.toFloat(), aBias, KEY_BUMP.value).roundToInt()
    val keyLo = autoKey - KEY_RANGE
    val keyHi = autoKey + KEY_RANGE
    val keyNow = if (aKey == KEY_AUTO) autoKey else aKey.coerceIn(keyLo, keyHi)
    var tKey by remember(keyNow) { mutableStateOf(keyNow.toString()) }
    val keyOn = aBias != Bias.FULL

    LaunchedEffect(tDec) {
        val n = tDec.toIntOrNull() ?: return@LaunchedEffect
        if (n == aDec) return@LaunchedEffect
        delay(SAVE_DEBOUNCE_MS)
        vm.prefs.setDecimals(n)
    }

    LaunchedEffect(tPad) {
        val n = tPad.toIntOrNull() ?: return@LaunchedEffect
        if (n == aPad) return@LaunchedEffect
        delay(SAVE_DEBOUNCE_MS)
        vm.prefs.setEdgePad(n)
    }

    LaunchedEffect(tKey) {
        val n = tKey.toIntOrNull()?.takeIf { it in keyLo..keyHi } ?: return@LaunchedEffect
        if (n == keyNow) return@LaunchedEffect
        delay(SAVE_DEBOUNCE_MS)
        vm.prefs.setKeySize(if (n == autoKey) KEY_AUTO else n)
    }

    LaunchedEffect(tMax) {
        val n = tMax.toIntOrNull() ?: return@LaunchedEffect
        if (n == aMax) return@LaunchedEffect
        delay(SAVE_DEBOUNCE_MS)
        vm.prefs.setHistoryMax(n)
    }

    Scaffold(
        containerColor = PAGE_BG,
        contentWindowInsets = WindowInsets(0, 0, 0, 0),
        topBar = { SettingsTopBar("Settings", onBack) }
    ) { p ->
        Column(
            Modifier
                .fillMaxSize()
                .background(PAGE_BG)
                .imePadding()
                .navigationBarsPadding()
                .padding(horizontal = BAR_EDGE)
                .verticalScroll(rememberScrollState())
        ) {
            Spacer(Modifier.height(p.calculateTopPadding() + SETTINGS_TOP_GAP))

            WgSection("Global", Icons.Outlined.Public) {
                WgRow("Key Pad Bias") {
                    WgDropdown(aBias.label, Bias.entries.map { it.label }) { picked ->
                        Bias.entries.firstOrNull { it.label == picked }
                            ?.let { scope.launch { vm.prefs.setBias(it) } }
                    }
                }

                WgDivider()

                WgRow("Edge Padding", edit = true, enabled = aBias.padded) {
                    WgField(
                        value = tPad,
                        onValueChange = {
                            if (it.length <= 3 && it.all(Char::isDigit) &&
                                (it.toIntOrNull() ?: 0) <= PAD_MAX
                            ) tPad = it
                        },
                        placeholder = PAD_DEFAULT.toString(),
                        modifier = Modifier.weight(1f),
                        enabled = aBias.padded,
                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                    )
                }

                WgDivider()

                WgRow("Button Size", edit = true, enabled = keyOn) {
                    WgField(
                        value = tKey,
                        onValueChange = {
                            if (it.length <= 3 && it.all(Char::isDigit) &&
                                (it.toIntOrNull() ?: 0) <= keyHi
                            ) tKey = it
                        },
                        placeholder = autoKey.toString(),
                        modifier = Modifier.weight(1f),
                        enabled = keyOn,
                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                    )
                }

                WgDivider()

                WgRow("Operators Bias") {
                    WgRadio(aOps.label, Side.entries.map { it.label }) { picked ->
                        Side.entries.firstOrNull { it.label == picked }
                            ?.let { scope.launch { vm.prefs.setOpsSide(it) } }
                    }
                }

                WgDivider()

                WgRow("Decimal Places", edit = true) {
                    WgField(
                        value = tDec,
                        onValueChange = {
                            if (it.length <= 2 && it.all(Char::isDigit) &&
                                (it.toIntOrNull() ?: 0) <= DEC_MAX
                            ) tDec = it
                        },
                        placeholder = DEC_DEFAULT.toString(),
                        modifier = Modifier.weight(1f),
                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                    )
                }

                WgDivider()

                WgRow("Digits") {
                    WgRadio(aDigits.label, Digits.entries.map { it.label }) { picked ->
                        Digits.entries.firstOrNull { it.label == picked }
                            ?.let { scope.launch { vm.prefs.setDigits(it) } }
                    }
                }

                WgDivider()

                WgRow("History", edit = true) {
                    WgField(
                        value = tMax,
                        onValueChange = {
                            if (it.length <= 2 && it.all(Char::isDigit) &&
                                (it.toIntOrNull() ?: 0) <= HISTORY_MAX
                            ) tMax = it
                        },
                        placeholder = HISTORY_DEFAULT.toString(),
                        modifier = Modifier.weight(1f),
                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                    )
                }
            }

            Spacer(Modifier.height(8.dp))
        }
    }
}

@Composable
internal fun WgSection(
    title: String,
    icon: ImageVector? = null,
    content: @Composable ColumnScope.() -> Unit
) {
    val shape = RoundedCornerShape(BTN_RADIUS)
    Column(
        Modifier
            .fillMaxWidth()
            .clip(shape)
            .border(EDGE, shape)
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .heightIn(min = HEAD_H)
                .clip(RoundedCornerShape(topStart = BTN_RADIUS, topEnd = BTN_RADIUS))
                .background(CHead)
                .padding(horizontal = 16.dp, vertical = 8.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            if (icon != null) {
                Icon(
                    imageVector = icon,
                    contentDescription = null,
                    tint = HEAD_INK,
                    modifier = Modifier.size(HEAD_ICON)
                )
                Spacer(Modifier.width(HEAD_ICON_GAP))
            }
            Text(
                text = title,
                color = HEAD_INK,
                fontWeight = FontWeight.Medium,
                letterSpacing = 1.4.sp,
                style = TextStyle(
                    fontSize = HEAD_FS,
                    lineHeight = HEAD_FS,
                    platformStyle = PlatformTextStyle(includeFontPadding = false),
                    lineHeightStyle = LineHeightStyle(
                        alignment = LineHeightStyle.Alignment.Center,
                        trim = LineHeightStyle.Trim.Both
                    )
                ),
                modifier = Modifier.offset(y = HEAD_TEXT_DY)
            )
        }
        WgDivider()
        WgCard(
            shape = RoundedCornerShape(bottomStart = BTN_RADIUS, bottomEnd = BTN_RADIUS),
            content = content
        )
    }
    Spacer(Modifier.height(24.dp))
}

@Composable
private fun WgCard(
    shape: Shape,
    bg: Color = CCard,
    borderColor: Color = Color.Transparent,
    content: @Composable ColumnScope.() -> Unit
) {
    val radiusPx = with(LocalDensity.current) { BTN_RADIUS.toPx() }
    Column(
        Modifier
            .fillMaxWidth()
            .clip(shape)
            .background(bg)
            .drawBehind {
                val w = 1.dp.toPx()
                drawRoundRect(
                    color = borderColor,
                    topLeft = Offset(w / 2f, -radiusPx),
                    size = Size(size.width - w, size.height + radiusPx - w / 2f),
                    cornerRadius = CornerRadius(radiusPx),
                    style = Stroke(w)
                )
            },
        content = content
    )
}

@Composable
internal fun WgRow(
    label: String,
    edit: Boolean = false,
    enabled: Boolean = true,
    content: @Composable RowScope.() -> Unit
) {
    val dim = if (enabled) 1f else DISABLED_ALPHA
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .heightIn(min = ROW_H)
            .padding(horizontal = 16.dp, vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Row(
            modifier = Modifier.widthIn(min = 92.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = label,
                color = PAGE_INK.copy(alpha = 0.95f * dim),
                fontSize = FS,
                maxLines = 1
            )
            if (edit) {
                Spacer(Modifier.width(6.dp))
                Icon(
                    imageVector = Icons.Filled.Create,
                    contentDescription = null,
                    tint = PAGE_INK.copy(alpha = 0.6f * dim),
                    modifier = Modifier.size(14.dp)
                )
            }
        }
        Spacer(Modifier.width(12.dp))
        Row(
            modifier = Modifier.weight(1f),
            horizontalArrangement = Arrangement.End,
            verticalAlignment = Alignment.CenterVertically,
            content = content
        )
    }
}

@Composable
internal fun WgDivider() {
    HorizontalDivider(thickness = 1.dp, color = PAGE_BG)
}

@Composable
internal fun WgField(
    value: String,
    onValueChange: (String) -> Unit,
    placeholder: String,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default
) {
    val dim = if (enabled) 1f else DISABLED_ALPHA
    val style = TextStyle(
        color = PAGE_INK.copy(alpha = dim),
        fontSize = FS,
        textAlign = TextAlign.End,
        fontFamily = FontFamily.Monospace
    )
    CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides Dp.Unspecified) {
        BasicTextField(
            value = value,
            onValueChange = onValueChange,
            modifier = modifier,
            enabled = enabled,
            singleLine = true,
            textStyle = style,
            cursorBrush = SolidColor(PAGE_INK),
            keyboardOptions = keyboardOptions,
            decorationBox = { inner ->
                Box(contentAlignment = Alignment.CenterEnd) {
                    if (value.isEmpty() && placeholder.isNotEmpty()) {
                        Text(
                            placeholder,
                            style = style.copy(color = MUTED.copy(alpha = dim)),
                            maxLines = 1
                        )
                    }
                    inner()
                }
            }
        )
    }
}

@Composable
private fun WgChip(
    label: String,
    selected: Boolean,
    shape: Shape,
    leftLine: Color? = null,
    rightLine: Color? = null,
    onClick: () -> Unit
) {
    Box(
        modifier = Modifier
            .height(28.dp)
            .clip(shape)
            .background(if (selected) CChip else Color.Transparent)
            .then(
                if (leftLine != null || rightLine != null) Modifier.drawBehind {
                    val w = 1.dp.toPx()
                    leftLine?.let {
                        drawLine(it, Offset(w / 2f, 0f), Offset(w / 2f, size.height), w)
                    }
                    rightLine?.let {
                        val x = size.width - w / 2f
                        drawLine(it, Offset(x, 0f), Offset(x, size.height), w)
                    }
                } else Modifier
            )
            .clickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = null,
                onClick = onClick
            )
            .padding(horizontal = CHIP_PAD),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = label,
            color = if (selected) PAGE_INK else MUTED,
            fontSize = 13.sp,
            fontFamily = FontFamily.Monospace,
            maxLines = 1
        )
    }
}

@Composable
internal fun WgRadio(
    value: String,
    options: List<String>,
    onSelect: (String) -> Unit
) {
    val shape = RoundedCornerShape(CHIP_RADIUS)
    val outline = MUTED.copy(alpha = 0.30f)
    Row(
        modifier = Modifier
            .height(28.dp)
            .clip(shape)
            .border(1.dp, outline, shape),
        verticalAlignment = Alignment.CenterVertically
    ) {
        options.forEachIndexed { i, opt ->
            WgChip(
                label = opt,
                selected = opt == value,
                shape = RectangleShape,
                leftLine = if (i > 0 && options[i - 1] != value) outline else null,
                rightLine = if (i < options.lastIndex && opt == value) outline else null,
                onClick = { onSelect(opt) }
            )
        }
    }
}

@Composable
internal fun WgDropdown(
    value: String,
    options: List<String>,
    onSelect: (String) -> Unit
) {
    var open by remember { mutableStateOf(false) }
    val shape = RoundedCornerShape(CHIP_RADIUS)
    Box {
        Row(
            modifier = Modifier
                .height(28.dp)
                .clip(shape)
                .border(1.dp, MUTED.copy(alpha = 0.30f), shape)
                .clickable(
                    interactionSource = remember { MutableInteractionSource() },
                    indication = null
                ) { open = true }
                .padding(start = CHIP_PAD, end = 6.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = value,
                color = PAGE_INK,
                fontSize = 13.sp,
                fontFamily = FontFamily.Monospace,
                maxLines = 1
            )
            Spacer(Modifier.width(4.dp))
            Icon(
                Icons.Filled.KeyboardArrowDown,
                contentDescription = null,
                tint = MUTED,
                modifier = Modifier.size(18.dp)
            )
        }
        DropdownMenu(
            expanded = open,
            onDismissRequest = { open = false },
            containerColor = MENU_BG,
            shape = RoundedCornerShape(BTN_RADIUS),
            tonalElevation = 0.dp,
            shadowElevation = 0.dp
        ) {
            options.forEach { opt ->
                DropdownMenuItem(
                    text = {
                        Text(
                            opt,
                            color = if (opt == value) MUTED else PAGE_INK,
                            fontSize = FS,
                            fontFamily = FontFamily.Monospace
                        )
                    },
                    onClick = { onSelect(opt); open = false }
                )
            }
        }
    }
}