Pith - varnish
package com.vgmlr.varnish

import android.content.Intent
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.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
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.filled.KeyboardArrowDown
import androidx.compose.material.icons.outlined.Crop
import androidx.compose.material.icons.outlined.Face
import androidx.compose.material.icons.outlined.Loop
import androidx.compose.material.icons.outlined.Public
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.Offset
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.vector.ImageVector
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.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext

private val ROW_H = 46.dp
private val HEAD_H = 46.dp
private val FS = 15.sp
private val HEAD_INK = CText.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 val TF = listOf("True", "False")

@Composable
fun SettingsScreen(vm: VarnishViewModel) {
    val s by vm.settings.collectAsStateWithLifecycle()
    val scanning by vm.scanning.collectAsStateWithLifecycle()
    val editors by produceState(emptyList<AppChoice>()) {
        value = withContext(Dispatchers.IO) { vm.repo.apps(Intent.ACTION_EDIT, "image/*") }
    }
    val players by produceState(emptyList<AppChoice>()) {
        value = withContext(Dispatchers.IO) { vm.repo.apps(Intent.ACTION_VIEW, "video/*") }
    }

    var tFolderImg by remember(s.folderImg) { mutableStateOf(s.folderImg.toString()) }

    LaunchedEffect(tFolderImg) {
        val v = tFolderImg.toIntOrNull()
            ?.coerceIn(VarnishConfig.FOLDER_IMG_MIN, VarnishConfig.FOLDER_IMG_MAX)
            ?: return@LaunchedEffect
        if (v.toString() != tFolderImg || v != s.folderImg) {
            delay(VarnishConfig.DEBOUNCE_MS)
            vm.updateSettings { it.copy(folderImg = v) }
            tFolderImg = v.toString()
        }
    }

    Box(Modifier.fillMaxSize().background(PageBg)) {
        Column(
            Modifier
                .fillMaxSize()
                .imePadding()
                .navigationBarsPadding()
                .padding(horizontal = EDGE)
                .verticalScroll(rememberScrollState())
        ) {
            Spacer(Modifier.statusBarsPadding().height(TOP_BAR_H + SETTINGS_TOP_GAP))

            // global

            WgSection("Global", Icons.Outlined.Public) {
                WgRow(
                    "Scan",
                    extra = {
                        Icon(
                            Icons.Outlined.Loop,
                            contentDescription = "Rescan",
                            tint = CText.copy(alpha = if (scanning) 0.3f else 0.6f),
                            modifier = Modifier
                                .clickable(
                                    enabled = !scanning,
                                    interactionSource = remember { MutableInteractionSource() },
                                    indication = null
                                ) { vm.refresh() }
                                .padding(4.dp)
                                .size(18.dp)
                        )
                    }
                ) {
                    WgRadio(s.scan.label, ScanMode.entries.map { it.label }) { p ->
                        vm.updateSettings { st ->
                            st.copy(scan = ScanMode.entries.first { it.label == p })
                        }
                    }
                }
                WgDivider()
                WgRow("File Types") {
                    WgDropdown(s.types.label, FileTypes.entries.map { it.label }) { p ->
                        vm.updateSettings { st ->
                            st.copy(types = FileTypes.entries.first { it.label == p })
                        }
                    }
                }
                WgDivider()
                WgRow("Show Hidden") {
                    WgRadio(tf(s.showHidden), TF) { p ->
                        vm.updateSettings { it.copy(showHidden = p == "True") }
                    }
                }
                WgDivider()
                WgRow("Save Folder View") {
                    WgRadio(tf(s.saveView), TF) { p ->
                        vm.updateSettings { it.copy(saveView = p == "True") }
                    }
                }
                WgDivider()
                WgRow("Delete Confirmation") {
                    WgRadio(tf(s.confirmDelete), TF) { p ->
                        vm.updateSettings { it.copy(confirmDelete = p == "True") }
                    }
                }
                WgDivider()
                WgRow("Image Editor") {
                    AppDropdown(s.editorPkg, editors, Modifier.weight(1f, fill = false)) { pkg ->
                        vm.updateSettings { it.copy(editorPkg = pkg) }
                    }
                }
                WgDivider()
                WgRow("Video Player") {
                    AppDropdown(s.playerPkg, players, Modifier.weight(1f, fill = false)) { pkg ->
                        vm.updateSettings { it.copy(playerPkg = pkg) }
                    }
                }
            }

            // display

            WgSection("Display", Icons.Outlined.Face) {
                WgRow("Folder Image Size", edit = true) {
                    WgField(
                        value = tFolderImg,
                        onValueChange = {
                            if (it.length <= 3 && it.all(Char::isDigit)) tFolderImg = it
                        },
                        placeholder = VarnishConfig.FOLDER_IMG_DEFAULT.toString(),
                        modifier = Modifier.weight(1f),
                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                    )
                    Spacer(Modifier.width(6.dp))
                }
                WgDivider()
                WgRow("Grid Columns") {
                    WgDropdown(
                        s.gridCols.toString(),
                        VarnishConfig.GRID_OPTIONS.map(Int::toString)
                    ) { p ->
                        p.toIntOrNull()?.let { v -> vm.updateSettings { it.copy(gridCols = v) } }
                    }
                }
            }

            // output

            WgSection("Output", Icons.Outlined.Crop) {
                WgRow("Format") {
                    WgDropdown(s.outFormat.label, OutFormat.entries.map { it.label }) { p ->
                        vm.updateSettings { st ->
                            st.copy(outFormat = OutFormat.entries.first { it.label == p })
                        }
                    }
                }
                WgDivider()
                WgRow("Quality") {
                    WgDropdown(
                        s.quality.toString(),
                        VarnishConfig.QUALITY_OPTIONS.map(Int::toString)
                    ) { p ->
                        p.toIntOrNull()?.let { v -> vm.updateSettings { it.copy(quality = v) } }
                    }
                }
                WgDivider()
                WgRow("EXIF") {
                    WgRadio(
                        if (s.keepExif) "Preserve" else "Remove",
                        listOf("Remove", "Preserve")
                    ) { p -> vm.updateSettings { it.copy(keepExif = p == "Preserve") } }
                }
            }

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

        TopBar(onBack = { vm.back() }) {
            TitlePlate("Settings", Modifier.weight(1f), dim = false)
        }
    }
}

private fun tf(b: Boolean) = if (b) "True" else "False"

@Composable
private fun AppDropdown(
    pkg: String,
    apps: List<AppChoice>,
    modifier: Modifier = Modifier,
    onPick: (String) -> Unit
) {
    val chooser = VarnishConfig.CHOOSER
    val label = if (pkg.isEmpty()) chooser else apps.firstOrNull { it.pkg == pkg }?.label ?: pkg
    WgDropdown(label, listOf(chooser) + apps.map { it.label }, modifier = modifier) { picked ->
        onPick(if (picked == chooser) "" else apps.firstOrNull { it.label == picked }?.pkg ?: pkg)
    }
}

@Composable
private fun WgSection(
    title: String,
    icon: ImageVector? = null,
    content: @Composable ColumnScope.() -> Unit
) {
    val shape = RoundedCornerShape(BTN_RADIUS)
    Column(
        Modifier
            .fillMaxWidth()
            .clip(shape)
            .border(1.dp, COutline, shape)
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .heightIn(min = HEAD_H)
                .background(CHead)
                .padding(horizontal = 16.dp, vertical = 8.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            if (icon != null) {
                Icon(icon, 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(content = content)
    }
    Spacer(Modifier.height(24.dp))
}

@Composable
private fun WgCard(
    bg: Color = CCard,
    content: @Composable ColumnScope.() -> Unit
) {
    Column(
        Modifier
            .fillMaxWidth()
            .background(bg),
        content = content
    )
}

@Composable
private fun WgRow(
    label: String,
    edit: Boolean = false,
    extra: (@Composable () -> Unit)? = null,
    content: @Composable RowScope.() -> Unit
) {
    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(label, color = CText.copy(alpha = 0.95f), fontSize = FS, maxLines = 1)
            if (edit) {
                Spacer(Modifier.width(6.dp))
                Icon(
                    Icons.Filled.Create,
                    contentDescription = null,
                    tint = CText.copy(alpha = 0.6f),
                    modifier = Modifier.size(14.dp)
                )
            }
            if (extra != null) {
                Spacer(Modifier.width(6.dp))
                extra()
            }
        }
        Spacer(Modifier.width(12.dp))
        Row(
            modifier = Modifier.weight(1f),
            horizontalArrangement = Arrangement.End,
            verticalAlignment = Alignment.CenterVertically,
            content = content
        )
    }
}

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

@Composable
private fun WgValue(
    text: String,
    modifier: Modifier = Modifier,
    mono: Boolean = false,
    color: Color = CText
) {
    Text(
        text = text,
        color = color,
        fontSize = FS,
        fontFamily = if (mono) FontFamily.Monospace else FontFamily.Default,
        textAlign = TextAlign.End,
        maxLines = 1,
        overflow = TextOverflow.Ellipsis,
        modifier = modifier
    )
}

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

@Composable
private fun WgDropdown(
    value: String,
    options: List<String>,
    modifier: Modifier = Modifier,
    disabledOptions: List<String> = emptyList(),
    onSelect: (String) -> Unit
) {
    var expanded by remember { mutableStateOf(false) }

    Box(modifier) {
        Row(
            modifier = Modifier.clickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = null
            ) { expanded = true },
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = value,
                color = CText,
                fontSize = FS,
                fontFamily = FontFamily.Monospace,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.weight(1f, fill = false)
            )
            Icon(
                Icons.Default.KeyboardArrowDown,
                contentDescription = null,
                tint = CMuted,
                modifier = Modifier.size(18.dp)
            )
        }
        DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false },
            containerColor = Color(0xFF1F2226),
            shape = RoundedCornerShape(BTN_RADIUS),
            tonalElevation = 0.dp,
            shadowElevation = 0.dp
        ) {
            options.forEach { opt ->
                val disabled = opt in disabledOptions
                DropdownMenuItem(
                    enabled = !disabled,
                    text = {
                        Text(
                            text = opt,
                            color = when {
                                disabled -> CMuted.copy(alpha = 0.4f)
                                opt == value -> CMuted
                                else -> CText
                            },
                            fontSize = FS,
                            fontFamily = FontFamily.Monospace
                        )
                    },
                    onClick = {
                        onSelect(opt)
                        expanded = false
                    },
                    contentPadding = PaddingValues(horizontal = 14.dp)
                )
            }
        }
    }
}

@Composable
private fun WgChip(
    label: String,
    selected: Boolean,
    enabled: Boolean = true,
    shape: Shape = RoundedCornerShape(CHIP_RADIUS),
    border: Boolean = true,
    leftLine: Color? = null,
    rightLine: Color? = null,
    onClick: () -> Unit
) {
    val filled = selected && enabled
    Box(
        modifier = Modifier
            .height(28.dp)
            .clip(shape)
            .background(if (filled) 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
            )
            .then(
                if (border) Modifier.border(
                    width = 1.dp,
                    color = if (filled) CChip else CMuted.copy(alpha = 0.30f),
                    shape = shape
                ) else Modifier
            )
            .clickable(
                enabled = enabled,
                interactionSource = remember { MutableInteractionSource() },
                indication = null,
                onClick = onClick
            )
            .padding(horizontal = 16.dp),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = label,
            color = when {
                !enabled -> CMuted.copy(alpha = 0.4f)
                selected -> CText
                else -> CMuted
            },
            fontSize = 13.sp,
            fontFamily = FontFamily.Monospace,
            maxLines = 1
        )
    }
}

@Composable
private fun WgRadio(
    value: String,
    options: List<String>,
    disabledOptions: List<String> = emptyList(),
    onSelect: (String) -> Unit
) {
    val shape = RoundedCornerShape(CHIP_RADIUS)
    val outline = CMuted.copy(alpha = 0.30f)
    val sel = options.indexOf(value)
    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,
                enabled = opt !in disabledOptions,
                shape = RectangleShape,
                border = false,
                leftLine = if (i > 0 && i - 1 != sel) outline else null,
                rightLine = if (i < options.lastIndex && i == sel) outline else null,
                onClick = { onSelect(opt) }
            )
        }
    }
}