Pith - wedge
package com.vgmlr.wedge

import android.content.Intent
import android.net.Uri
import android.provider.OpenableColumns
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.detectVerticalDragGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer
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.Alarm
import androidx.compose.material.icons.outlined.CalendarToday
import androidx.compose.material.icons.outlined.Create
import androidx.compose.material.icons.outlined.Inventory2
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material.icons.outlined.Widgets
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.getValue
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.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.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
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.input.VisualTransformation
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.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import java.io.File
import kotlin.math.roundToInt
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import androidx.compose.material.icons.outlined.WarningAmber

internal val CBg = Color(0xFF0E0F11)
internal val CCard = Color(0xFF272A30).copy(alpha = 0.8f)
internal val CCardSync = Color(0xFF272A30)
internal val CText = Color(0xFFE8EAED)
internal val CMuted = Color(0xFF868D97)
internal val CChip = Color(0xFF3A4048)
internal val CHead = Color(0xFF33373E).copy(alpha = 0.8f)
internal val CHeadSync = Color(0xFF33373E)
internal val EDGE = BorderStroke(1.dp, CMuted.copy(alpha = 0.30f))

internal val ROW_H = 46.dp
internal val HEAD_H = 46.dp
internal val FS = 15.sp
internal val FSend = 16.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

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsView(vm: MainViewModel, prefs: PreferenceManager, onBack: () -> Unit) {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()

    val aTxt by prefs.textColor.collectAsState(WedgeConfig.TEXT_COLOR_DEFAULT)
    val aBg by prefs.bgColor.collectAsState(WedgeConfig.BG_COLOR_DEFAULT)
    val aWTxt by prefs.widgetTextColor.collectAsState(WedgeConfig.WIDGET_TEXT_COLOR_DEFAULT)
    val aWBg by prefs.widgetBgColor.collectAsState(WedgeConfig.WIDGET_BG_COLOR_DEFAULT)
    val aESize by prefs.editorFontSize.collectAsState(WedgeConfig.FONT_SIZE_DEFAULT)
    val aWSize by prefs.widgetFontSize.collectAsState(WedgeConfig.WIDGET_FONT_SIZE_DEFAULT)
    val aLHeight by prefs.lineHeight.collectAsState(WedgeConfig.LINE_HEIGHT_DEFAULT)
    val aBColor by prefs.boldColor.collectAsState(WedgeConfig.BOLD_COLOR_DEFAULT)
    val aLink by prefs.linkColor.collectAsState(WedgeConfig.LINK_COLOR_DEFAULT)
    val aLinkOn by prefs.linkEnabled.collectAsState(false)
    val aWBCol by prefs.widgetBoldColor.collectAsState(WedgeConfig.WIDGET_BOLD_COLOR_DEFAULT)
    val aNextBg by prefs.nextTimeBg.collectAsState(WedgeConfig.CALENDAR_COLOR_DEFAULT)
    val aWNextBg by prefs.widgetNextTimeBg.collectAsState(WedgeConfig.WIDGET_CALENDAR_COLOR_DEFAULT)
    val aIncognito by prefs.incognitoMode.collectAsState(false)
    val aGuide by prefs.guideEnabled.collectAsState(WedgeConfig.GUIDE_ENABLED_DEFAULT)
    val aGuideChars by prefs.guideChars.collectAsState(WedgeConfig.GUIDE_CHARS_DEFAULT)
    val aGuideOpacity by prefs.guideOpacity.collectAsState(WedgeConfig.GUIDE_OPACITY_DEFAULT)
    val aPadRight by prefs.btnPadRight.collectAsState(WedgeConfig.BTN_PAD_RIGHT_DEFAULT)
    val aPadBottom by prefs.btnPadBottom.collectAsState(WedgeConfig.BTN_PAD_BOTTOM_DEFAULT)
    val aPadLeft by prefs.editorPadLeft.collectAsState(WedgeConfig.EDITOR_PAD_LEFT_DEFAULT)

    val aHeaderFmt by prefs.headerFormat.collectAsState(WedgeConfig.HEADER_FORMAT_DEFAULT)
    val aPrune by prefs.autoPrune.collectAsState(WedgeConfig.PRUNE_DAYS_DEFAULT)

    val aAlarmUri by prefs.alarmSoundUri.collectAsState("")
    val aAlarmDuration by prefs.alarmDurationMinutes.collectAsState(5)
    val aVibration by prefs.vibrationEnabled.collectAsState(false)

    val aBkInterval by prefs.backupInterval.collectAsState(WedgeConfig.BACKUP_INTERVAL_DEFAULT)
    val aBkLocation by prefs.backupLocation.collectAsState("")
    val aBkStamp by prefs.backupStamp.collectAsState(0L)
    val aSpec by prefs.phraseSpec.collectAsState(PhraseSpec())

    var tTxt by remember(aTxt) { mutableStateOf(aTxt) }
    var tBg by remember(aBg) { mutableStateOf(aBg) }
    var tWTxt by remember(aWTxt) { mutableStateOf(aWTxt) }
    var tWBg by remember(aWBg) { mutableStateOf(aWBg) }
    var tBCol by remember(aBColor) { mutableStateOf(aBColor) }
    var tLink by remember(aLink) { mutableStateOf(aLink) }
    var tWBCol by remember(aWBCol) { mutableStateOf(aWBCol) }
    var tNextBg by remember(aNextBg) { mutableStateOf(aNextBg) }
    var tWNextBg by remember(aWNextBg) { mutableStateOf(aWNextBg) }
    var tIncognito by remember(aIncognito) { mutableStateOf(aIncognito) }
    var tGuide by remember(aGuide) { mutableStateOf(aGuide) }
    var tGuideChars by remember(aGuideChars) { mutableStateOf(aGuideChars.toString()) }
    var tGuideOpacity by remember(aGuideOpacity) { mutableIntStateOf(aGuideOpacity) }
    var tPadRight by remember(aPadRight) { mutableStateOf(aPadRight.toString()) }
    var tPadBottom by remember(aPadBottom) { mutableStateOf(aPadBottom.toString()) }
    var tPadLeft by remember(aPadLeft) { mutableStateOf(aPadLeft.toString()) }

    var tHeaderFmt by remember(aHeaderFmt) { mutableStateOf(aHeaderFmt) }
    var tPrune by remember(aPrune) { mutableStateOf(aPrune) }

    var tAlarmUri by remember(aAlarmUri) { mutableStateOf(aAlarmUri) }
    var tAlarmDuration by remember(aAlarmDuration) { mutableIntStateOf(aAlarmDuration) }
    var tVibration by remember(aVibration) { mutableStateOf(aVibration) }

    var tBkInterval by remember(aBkInterval) { mutableIntStateOf(aBkInterval) }
    var tBkLocation by remember(aBkLocation) { mutableStateOf(aBkLocation) }
    var tSpec by remember(aSpec) { mutableStateOf(aSpec) }

    var armRestore by remember { mutableStateOf(false) }

    var tLinkOn by remember(aLinkOn) { mutableStateOf(aLinkOn) }
    
    var tESize by remember(aESize) { mutableStateOf(aESize.toString()) }
    var tWSize by remember(aWSize) { mutableStateOf(aWSize.toString()) }
    var tLHeight by remember(aLHeight) { mutableStateOf(aLHeight.toString()) }

    LaunchedEffect(tTxt, tBg, tWTxt, tWBg, tBCol, tWBCol, tNextBg, tWNextBg) {
        val hasColorChanges = tTxt != aTxt || tBg != aBg || tWTxt != aWTxt || tWBg != aWBg ||
                tBCol != aBColor || tWBCol != aWBCol || tNextBg != aNextBg || tWNextBg != aWNextBg
        if (hasColorChanges) {
            delay(1200)
            prefs.setColors(tTxt, tBg, tWTxt, tWBg, tBCol, tWBCol, tNextBg, tWNextBg)
            NoteWidgetProvider.triggerUpdate(context)
        }
    }

    LaunchedEffect(tPrune) {
        if (tPrune != aPrune) prefs.setAutoPrune(tPrune)
    }

    LaunchedEffect(tLink) {
        if (tLink != aLink) {
            delay(1200)
            prefs.setLinkColor(tLink)
        }
    }

    LaunchedEffect(tLinkOn) {
        if (tLinkOn != aLinkOn) prefs.setLinkEnabled(tLinkOn)
    }

    LaunchedEffect(tESize, tWSize, tLHeight) {
        val finalE =
            tESize.toFloatOrNull()?.coerceIn(WedgeConfig.FONT_SIZE_MIN, WedgeConfig.FONT_SIZE_MAX)
                ?: WedgeConfig.FONT_SIZE_DEFAULT
        val finalW =
            tWSize.toFloatOrNull()?.coerceIn(WedgeConfig.FONT_SIZE_MIN, WedgeConfig.FONT_SIZE_MAX)
                ?: WedgeConfig.WIDGET_FONT_SIZE_DEFAULT
        val finalLH = tLHeight.toFloatOrNull() ?: WedgeConfig.LINE_HEIGHT_DEFAULT
        if (finalE != aESize || finalW != aWSize || finalLH != aLHeight) {
            prefs.setFontSettings(finalE, finalW, finalLH)
            NoteWidgetProvider.triggerUpdate(context)
        }
    }

    LaunchedEffect(tIncognito) {
        if (tIncognito != aIncognito) {
            prefs.setIncognito(tIncognito)
            NoteWidgetProvider.triggerUpdate(context)
        }
    }

    LaunchedEffect(tGuide, tGuideOpacity) {
        if (tGuide != aGuide || tGuideOpacity != aGuideOpacity) {
            prefs.setGuide(tGuide, aGuideChars, tGuideOpacity)
        }
    }

    LaunchedEffect(tGuideChars) {
        val n = tGuideChars.toIntOrNull()?.coerceIn(1, 999) ?: WedgeConfig.GUIDE_CHARS_DEFAULT
        if (n != aGuideChars) {
            delay(1200)
            prefs.setGuide(tGuide, n, tGuideOpacity)
        }
    }

    LaunchedEffect(tPadRight, tPadBottom) {
        val r = tPadRight.toIntOrNull()?.coerceIn(0, 999) ?: WedgeConfig.BTN_PAD_RIGHT_DEFAULT
        val b = tPadBottom.toIntOrNull()?.coerceIn(0, 999) ?: WedgeConfig.BTN_PAD_BOTTOM_DEFAULT
        if (r != aPadRight || b != aPadBottom) {
            delay(1200)
            prefs.setBtnPad(r, b)
        }
    }

    LaunchedEffect(tPadLeft) {
        val v = tPadLeft.toIntOrNull()?.coerceIn(0, 999)
            ?: WedgeConfig.EDITOR_PAD_LEFT_DEFAULT
        if (v != aPadLeft) {
            delay(1200)
            prefs.setEditorPadLeft(v)
        }
    }

    LaunchedEffect(tHeaderFmt) {
        if (tHeaderFmt != aHeaderFmt) {
            delay(1500)
            prefs.setHeaderFormat(tHeaderFmt.ifBlank { WedgeConfig.HEADER_FORMAT_DEFAULT })
            NoteWidgetProvider.triggerUpdate(context)
        }
    }

    LaunchedEffect(tAlarmUri, tAlarmDuration, tVibration) {
        if (tAlarmUri != aAlarmUri || tAlarmDuration != aAlarmDuration || tVibration != aVibration) {
            prefs.setAlarmSettings(tAlarmUri, tAlarmDuration, tVibration)
            NoteWidgetProvider.triggerUpdate(context)
        }
    }

    LaunchedEffect(tBkInterval, tBkLocation) {
        if (tBkInterval != aBkInterval || tBkLocation != aBkLocation) {
            delay(1200)
            prefs.setBackupSettings(tBkInterval, tBkLocation)
            if (tBkInterval != aBkInterval) WedgeBackup.schedule(context, tBkInterval)
        }
    }

    LaunchedEffect(tSpec) {
        if (tSpec != aSpec) prefs.setPhraseSpec(tSpec)
    }

    LaunchedEffect(armRestore) {
        if (!armRestore) return@LaunchedEffect
        delay(3_000L)
        armRestore = false
    }

    val folderPickerLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.OpenDocumentTree()
    ) { uri: Uri? ->
        uri?.let {
            try {
                context.contentResolver.takePersistableUriPermission(
                    it,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                )
                tBkLocation = it.toString()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    val audioPickerLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.GetContent()
    ) { uri: Uri? ->
        uri?.let { pickedUri ->
            try {
                var displayName = "alarm_sound"
                if (pickedUri.scheme == "content") {
                    context.contentResolver.query(pickedUri, null, null, null, null)
                        ?.use { cursor ->
                            val nameIdx =
                                cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                            if (nameIdx != -1 && cursor.moveToFirst()) {
                                displayName = cursor.getString(nameIdx)
                            }
                        }
                } else {
                    pickedUri.path?.substringAfterLast('/')?.let { displayName = it }
                }

                context.filesDir.listFiles()?.forEach { file ->
                    if (file.name.startsWith("custom_alarm_")) {
                        file.delete()
                    }
                }

                val localFile = File(context.filesDir, "custom_alarm_$displayName")
                context.contentResolver.openInputStream(pickedUri)?.use { input ->
                    localFile.outputStream().use { output ->
                        input.copyTo(output)
                    }
                }

                tAlarmUri = Uri.fromFile(localFile).toString()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    Scaffold(
        contentWindowInsets = WindowInsets(0, 0, 0, 0),
        topBar = { SettingsTopBar("Settings", onBack) }
    ) { p ->

        Column(
            Modifier
                .fillMaxSize()
                .padding(bottom = p.calculateBottomPadding())
                .background(CBg)
                .imePadding()
                .navigationBarsPadding()
                .padding(horizontal = BAR_EDGE)
                .verticalScroll(rememberScrollState())
        ) {

            Spacer(Modifier.height(p.calculateTopPadding() + SETTINGS_TOP_GAP))

            // alarm

            WgSection("Alarm", Icons.Outlined.Alarm) {
                WgRow("Audio") {
                    val alarmDisplayName = remember(tAlarmUri) {
                        if (tAlarmUri.isEmpty()) {
                            ""
                        } else {
                            val uri = tAlarmUri.toUri()
                            var resolvedName = ""
                            if (uri.scheme == "content") {
                                try {
                                    context.contentResolver.query(uri, null, null, null, null)
                                        ?.use { cursor ->
                                            val nameIndex =
                                                cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                                            if (nameIndex != -1 && cursor.moveToFirst()) {
                                                resolvedName = cursor.getString(nameIndex)
                                            }
                                        }
                                } catch (_: Exception) {
                                }
                            }
                            val baseName = resolvedName.ifEmpty {
                                uri.path?.substringAfterLast('/') ?: tAlarmUri
                            }

                            if (baseName.startsWith("custom_alarm_")) {
                                baseName.removePrefix("custom_alarm_")
                            } else {
                                baseName
                            }
                        }
                    }
                    val truncatedName =
                        if (alarmDisplayName.length > 25) alarmDisplayName.take(22) + "..." else alarmDisplayName
                    WgPick(truncatedName, "default.wav") {
                        audioPickerLauncher.launch("audio/*")
                    }
                }

                WgDivider()

                WgRow("Duration") {
                    WgDropdown(
                        value = durationLabel(tAlarmDuration),
                        options = (1..10).map { durationLabel(it) }
                    ) { picked ->
                        tAlarmDuration = picked.substringBefore(' ').toIntOrNull() ?: tAlarmDuration
                    }
                }

                WgDivider()

                WgRow("Vibration") {
                    WgRadio(
                        value = if (tVibration) "True" else "False",
                        options = listOf("True", "False")
                    ) { tVibration = it == "True" }
                }
            }

            // archive

            WgSection("Archive", Icons.Outlined.Inventory2) {
                WgRow("Interval") {
                    WgDropdown(
                        value = WedgeConfig.intervalLabel(tBkInterval),
                        options = WedgeConfig.BACKUP_INTERVALS.map { WedgeConfig.intervalLabel(it) }
                    ) { picked ->
                        tBkInterval = WedgeConfig.BACKUP_INTERVALS
                            .firstOrNull { WedgeConfig.intervalLabel(it) == picked } ?: tBkInterval
                    }
                }

                WgDivider()

                WgRow("Location") {
                    val locationName = remember(tBkLocation) {
                        if (tBkLocation.isEmpty()) "" else try {
                            DocumentFile.fromTreeUri(context, tBkLocation.toUri())?.name
                                ?: tBkLocation
                        } catch (_: Exception) {
                            tBkLocation
                        }
                    }
                    val truncatedLocation =
                        if (locationName.length > 25) locationName.take(22) + "..." else locationName
                    WgPick(truncatedLocation, "app/") {
                        folderPickerLauncher.launch(null)
                    }
                }

                WgDivider()

                WgRow("Archived") {
                    WgValue(
                        if (aBkStamp == 0L) "[none]" else WedgeDate.getOtcMetadata(aBkStamp),
                        mono = true
                    )
                }

                WgDivider()

                WgRow("Archive") {
                    WgAction(
                        if (armRestore) "Confirm" else "Restore",
                        enabled = true,
                        icon = if (armRestore) Icons.Outlined.WarningAmber else null
                    ) {
                        if (armRestore) {
                            armRestore = false
                            scope.launch { WedgeBackup.restore(context) }
                        } else armRestore = true
                    }
                }
            }

            // encryption

            WgSection("Encryption", Icons.Outlined.Lock) {
                WgRow("Words") {
                    WgRadio(
                        value = if (tSpec.words) "True" else "False",
                        options = listOf("True", "False"),
                        disabledOptions = if (tSpec.numbers) emptyList() else listOf("False")
                    ) { tSpec = tSpec.copy(words = it == "True") }
                }

                WgDivider()

                WgRow("Words") {
                    WgDropdown(tSpec.wordCount.toString(), WORD_COUNT_OPTIONS) { picked ->
                        tSpec = tSpec.copy(wordCount = picked.toIntOrNull() ?: tSpec.wordCount)
                    }
                }

                WgDivider()

                WgRow("Letters") {
                    WgDropdown(tSpec.letters.toString(), LETTER_OPTIONS) { picked ->
                        tSpec = tSpec.copy(letters = picked.toIntOrNull() ?: tSpec.letters)
                    }
                }

                WgDivider()

                WgRow("Numbers") {
                    WgRadio(
                        value = if (tSpec.numbers) "True" else "False",
                        options = listOf("True", "False"),
                        disabledOptions = if (tSpec.words) emptyList() else listOf("False")
                    ) { tSpec = tSpec.copy(numbers = it == "True") }
                }

                WgDivider()

                WgRow("Numbers") {
                    WgDropdown(tSpec.numberCount.toString(), NUMBER_COUNT_OPTIONS) { picked ->
                        tSpec = tSpec.copy(numberCount = picked.toIntOrNull() ?: tSpec.numberCount)
                    }
                }
            }

            // calendar

            WgSection("Calendar", Icons.Outlined.CalendarToday) {
                WgRow("Header", edit = true) {
                    WgField(
                        value = tHeaderFmt,
                        onValueChange = { tHeaderFmt = it },
                        placeholder = WedgeConfig.HEADER_FORMAT_DEFAULT,
                        modifier = Modifier.weight(1f)
                    )
                }

                WgDivider()

                WgRow("Output") {
                    val headerPreview = remember(tHeaderFmt) {
                        try {
                            WedgeHeader.render(
                                tHeaderFmt.ifBlank { WedgeConfig.HEADER_FORMAT_DEFAULT },
                                System.currentTimeMillis()
                            )
                        } catch (_: Exception) {
                            ""
                        }
                    }
                    WgValue(headerPreview, mono = true)
                }
                WgDivider()

                WgRow("Remove Past") {
                    WgRadio(
                        value = if (tPrune) "True" else "False",
                        options = listOf("True", "False")
                    ) { tPrune = it == "True" }
                }
            }

            // editor

            WgSection("Editor", Icons.Outlined.Create) {
                WgColorRow("Text", tTxt, WedgeConfig.TEXT_COLOR_DEFAULT) { tTxt = it }
                WgDivider()
                WgColorRow("Focus", tBCol, WedgeConfig.BOLD_COLOR_DEFAULT) { tBCol = it }
                WgDivider()
                WgRow("Link URLs") {
                    WgRadio(
                        value = if (tLinkOn) "True" else "False",
                        options = listOf("True", "False")
                    ) { tLinkOn = it == "True" }
                }
                WgDivider()
                WgColorRow("Link", tLink, WedgeConfig.LINK_COLOR_DEFAULT) { tLink = it }
                WgDivider()
                WgColorRow("Next", tNextBg, WedgeConfig.CALENDAR_COLOR_DEFAULT) { tNextBg = it }
                WgDivider()
                WgColorRow("Ground", tBg, WedgeConfig.BG_COLOR_DEFAULT) { tBg = it }
                WgDivider()

                WgRow("Font Size") {
                    WgDropdown(tESize, FONT_SIZE_OPTIONS) { tESize = it }
                }

                WgDivider()

                WgRow("Line Height") {
                    WgDropdown(tLHeight, LINE_HEIGHT_OPTIONS) { tLHeight = it }
                }

                WgDivider()

                WgRow("Incognito") {
                    WgRadio(
                        value = if (tIncognito) "True" else "False",
                        options = listOf("True", "False")
                    ) { tIncognito = it == "True" }
                }

                WgDivider()

                WgRow("Left Padding", edit = true) {
                    WgField(
                        value = tPadLeft,
                        onValueChange = {
                            if (it.length <= 3 && it.all(Char::isDigit)) tPadLeft = it
                        },
                        placeholder = WedgeConfig.EDITOR_PAD_LEFT_DEFAULT.toString(),
                        modifier = Modifier.weight(1f),
                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                    )
                }

                WgDivider()

                WgRow("Guide") {
                    WgRadio(
                        value = if (tGuide) "True" else "False",
                        options = listOf("True", "False")
                    ) { tGuide = it == "True" }
                }

                WgDivider()

                WgRow("Guide Characters", edit = true) {
                    WgField(
                        value = tGuideChars,
                        onValueChange = {
                            if (it.length <= 3 && it.all(Char::isDigit)) tGuideChars = it
                        },
                        placeholder = WedgeConfig.GUIDE_CHARS_DEFAULT.toString(),
                        modifier = Modifier.weight(1f),
                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                    )
                }

                WgDivider()

                WgRow("Guide Opacity") {
                    WgDropdown(
                        value = "$tGuideOpacity%",
                        options = GUIDE_OPACITY_OPTIONS
                    ) { picked ->
                        tGuideOpacity = picked.dropLast(1).toIntOrNull() ?: tGuideOpacity
                    }
                }

                WgDivider()

                WgRow("Button Position", edit = true) {
                    WgField(
                        value = tPadRight,
                        onValueChange = {
                            if (it.length <= 3 && it.all(Char::isDigit)) tPadRight = it
                        },
                        placeholder = WedgeConfig.BTN_PAD_RIGHT_DEFAULT.toString(),
                        modifier = Modifier.weight(1f),
                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                    )
                    Spacer(Modifier.width(6.dp))
                    WgValue("right", color = CMuted)
                    Spacer(Modifier.width(8.dp))
                    WgField(
                        value = tPadBottom,
                        onValueChange = {
                            if (it.length <= 3 && it.all(Char::isDigit)) tPadBottom = it
                        },
                        placeholder = WedgeConfig.BTN_PAD_BOTTOM_DEFAULT.toString(),
                        modifier = Modifier.weight(1f),
                        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
                    )
                    Spacer(Modifier.width(6.dp))
                    WgValue("bottom", color = CMuted)
                }
            }

            // widget

            WgSection("Widget", Icons.Outlined.Widgets) {
                WgColorRow("Text", tWTxt, WedgeConfig.WIDGET_TEXT_COLOR_DEFAULT) { tWTxt = it }
                WgDivider()
                WgColorRow("Focus", tWBCol, WedgeConfig.WIDGET_BOLD_COLOR_DEFAULT) { tWBCol = it }
                WgDivider()
                WgColorRow(
                    "Next",
                    tWNextBg,
                    WedgeConfig.WIDGET_CALENDAR_COLOR_DEFAULT
                ) { tWNextBg = it }
                WgDivider()
                WgColorRow("Ground", tWBg, WedgeConfig.WIDGET_BG_COLOR_DEFAULT) { tWBg = it }
                WgDivider()

                WgRow("Font Size") {
                    WgDropdown(tWSize, FONT_SIZE_OPTIONS) { tWSize = it }
                }
            }

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

@Composable
internal fun WgSection(
    title: String,
    icon: ImageVector? = null,
    headBg: Color = CHead,
    cardBg: Color = CCard,
    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(headBg)
                .padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 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),
            bg = cardBg,
            borderColor = Color.Transparent,
            content = content
        )
    }
    Spacer(Modifier.height(24.dp))
}
@Composable
internal fun WgCard(
    shape: Shape = RoundedCornerShape(BTN_RADIUS),
    bg: Color = CCard,
    borderColor: Color = CHead,
    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,
    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(
                text = label,
                color = CText.copy(alpha = 0.95f),
                fontSize = FS,
                maxLines = 1
            )
            if (edit) {
                Spacer(Modifier.width(6.dp))
                Icon(
                    imageVector = Icons.Filled.Create,
                    contentDescription = null,
                    tint = CText.copy(alpha = 0.6f),
                    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 = CBg)
}

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

@Composable
private fun WgPick(value: String, placeholder: String, onClick: () -> Unit) {
    Text(
        text = value.ifBlank { placeholder },
        color = if (value.isBlank()) CMuted else CText,
        fontSize = FS,
        fontFamily = FontFamily.Monospace,
        maxLines = 1,
        overflow = TextOverflow.Ellipsis,
        modifier = Modifier.clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null,
            onClick = onClick
        )
    )
}

@Suppress("SameParameterValue")
@Composable
private fun WgAction(
    text: String,
    enabled: Boolean,
    icon: ImageVector? = null,
    onClick: () -> Unit
) {
    val shape = RoundedCornerShape(CHIP_RADIUS)
    Box(
        Modifier
            .clip(shape)
            .border(1.dp, CMuted.copy(alpha = 0.30f), shape)
    ) {
        WgChip(
            label = text,
            selected = true,
            enabled = enabled,
            border = false,
            icon = icon,
            onClick = onClick
        )
    }
}

@Composable
internal fun WgField(
    value: String,
    onValueChange: (String) -> Unit,
    placeholder: String,
    modifier: Modifier = Modifier,
    visualTransformation: VisualTransformation = VisualTransformation.None,
    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),
            visualTransformation = visualTransformation,
            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 WgColorRow(
    label: String,
    value: String,
    default: String,
    onValueChange: (String) -> Unit
) {
    var showPicker by remember { mutableStateOf(false) }

    WgRow(label) {
        WgField(
            value = value,
            onValueChange = onValueChange,
            placeholder = default,
            modifier = Modifier.weight(1f)
        )
        Spacer(Modifier.width(12.dp))
        Box(
            Modifier
                .size(20.dp)
                .clip(CircleShape)
                .background(parseColor(value.ifBlank { default }))
                .clickable(
                    interactionSource = remember { MutableInteractionSource() },
                    indication = null
                ) { showPicker = true }
        )
    }

    if (showPicker) {
        ColorPickerDialog(
            value.ifBlank { default },
            { onValueChange(it); showPicker = false },
            { showPicker = false }
        )
    }
}

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

    Box {
        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
            )
            Icon(
                imageVector = 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,
    icon: ImageVector? = null,
    onClick: () -> Unit
) {
    val filled = selected && enabled
    val ink = when {
        !enabled -> CMuted.copy(alpha = 0.4f)
        selected -> CText
        else -> CMuted
    }

    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
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            if (icon != null) {
                Icon(icon, contentDescription = null, tint = ink, modifier = Modifier.size(16.dp))
                Spacer(Modifier.width(6.dp))
            }
            Text(
                text = label,
                color = ink,
                fontSize = 13.sp,
                fontFamily = FontFamily.Monospace,
                maxLines = 1
            )
        }
    }
}

@Composable
internal 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)

    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 && options[i - 1] != value) outline else null,
                rightLine = if (i < options.lastIndex && opt == value) outline else null,
                onClick = { onSelect(opt) }
            )
        }
    }
}

private val FONT_SIZE_OPTIONS = (22..32).map { (it / 2.0).toString() }
private val LINE_HEIGHT_OPTIONS = (10..20).map { (it / 10.0).toString() }
private val GUIDE_OPACITY_OPTIONS = (0..100 step 10).map { "$it%" }
private val WORD_COUNT_OPTIONS = (1..5).map { it.toString() }
private val LETTER_OPTIONS = (3..9).map { it.toString() }
private val NUMBER_COUNT_OPTIONS = (1..10).map { it.toString() }

private fun durationLabel(m: Int): String = if (m == 1) "1 minute" else "$m minutes"

@Composable
fun ConfirmDialog(
    text: String,
    onConfirm: () -> Unit,
    onDismiss: () -> Unit,
    confirmText: String = "Yep",
    dismissText: String = "Nope",
    mono: Boolean = true
) {
    val btnShape = RoundedCornerShape(BTN_RADIUS)
    val btnColors = ButtonDefaults.buttonColors(
        containerColor = CChip,
        contentColor = CText
    )
    AlertDialog(
        onDismissRequest = onDismiss,
        modifier = Modifier.border(EDGE, RoundedCornerShape(BTN_RADIUS)),
        shape = RoundedCornerShape(BTN_RADIUS),
        containerColor = CCardSync,
        confirmButton = {
            Button(
                onClick = onConfirm,
                modifier = Modifier.height(44.dp),
                colors = btnColors,
                shape = btnShape
            ) { Text(confirmText, color = CText, fontSize = 15.sp) }
        },
        dismissButton = {
            Button(
                onClick = onDismiss,
                modifier = Modifier.height(44.dp),
                colors = btnColors,
                shape = btnShape
            ) { Text(dismissText, color = CText, fontSize = 15.sp) }
        },
        text = { Text(text, fontFamily = if (mono) FontFamily.Monospace else FontFamily.Default, fontSize = 17.sp) }
    )
}

@Composable
fun OlderDataDialog(onForce: () -> Unit, onDismiss: () -> Unit) = ConfirmDialog(
    text = "Local data is older than destination",
    onConfirm = onForce,
    onDismiss = onDismiss,
    confirmText = "Force",
    dismissText = "Oops",
    mono = false
)

@Composable
fun PhraseDialog(phrase: String, onEncrypt: () -> Unit, onDismiss: () -> Unit) {
    Dialog(
        onDismissRequest = onDismiss,
        properties = DialogProperties(usePlatformDefaultWidth = false)
    ) {
        Column(
            modifier = Modifier
                .padding(horizontal = BAR_EDGE)
                .clip(RoundedCornerShape(BTN_RADIUS))
                .background(CCardSync)
                .border(EDGE, RoundedCornerShape(BTN_RADIUS))
                .padding(horizontal = 40.dp, vertical = 24.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            SelectionContainer {
                Text(
                    text = phrase,
                    color = CText,
                    fontFamily = FontFamily.Monospace,
                    fontSize = 17.sp,
                    textAlign = TextAlign.Center
                )
            }
            Spacer(Modifier.height(20.dp))
            Button(
                onClick = onEncrypt,
                modifier = Modifier.height(44.dp),
                colors = ButtonDefaults.buttonColors(containerColor = CRYPT_BG, contentColor = CText),
                shape = RoundedCornerShape(BTN_RADIUS)
            ) { Text("Encrypt", color = CText, fontSize = 15.sp) }
        }
    }
}

@Composable
fun ColorPickerDialog(initial: String, onPick: (String) -> Unit, onDismiss: () -> Unit) {
    val start = parseColor(initial)
    var r by remember { mutableFloatStateOf(start.red) }
    var g by remember { mutableFloatStateOf(start.green) }
    var b by remember { mutableFloatStateOf(start.blue) }
    val hex = "#%02X%02X%02X".format((r * 255).toInt(), (g * 255).toInt(), (b * 255).toInt())

    val pickerBtnShape = RoundedCornerShape(BTN_RADIUS)
    val pickerBtnColors = ButtonDefaults.buttonColors(
        containerColor = CChip,
        contentColor = CText
    )
    val sliderColor = Color(0xFF9AA1AA)
    val sliderBg = Color(0xFF23262A)

    AlertDialog(
        onDismissRequest = onDismiss,
        modifier = Modifier.border(EDGE, RoundedCornerShape(BTN_RADIUS)),
        shape = RoundedCornerShape(BTN_RADIUS),
        containerColor = CCardSync,
        confirmButton = {
            Button(
                onClick = { onPick(hex) },
                modifier = Modifier.height(44.dp),
                colors = pickerBtnColors,
                shape = pickerBtnShape
            ) { Text("This", color = CText, fontSize = 15.sp) }
        },
        dismissButton = {
            Button(
                onClick = onDismiss,
                modifier = Modifier.height(44.dp),
                colors = pickerBtnColors,
                shape = pickerBtnShape
            ) { Text("Abort", color = CText, fontSize = 15.sp) }
        },
        text = {
            Column {
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.Center,
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Box(
                        Modifier
                            .size(52.dp)
                            .background(Color(r, g, b), RoundedCornerShape(BTN_RADIUS))
                    )
                    Spacer(Modifier.width(15.dp))
                    Text(hex, color = CText, fontFamily = FontFamily.Monospace, fontSize = 17.sp)
                }
                Spacer(Modifier.height(22.dp))
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.spacedBy(62.dp, Alignment.CenterHorizontally)
                ) {
                    VerticalRgbSlider(value = r, onValueChange = { r = it }, color = sliderColor, trackColor = sliderBg)
                    VerticalRgbSlider(value = g, onValueChange = { g = it }, color = sliderColor, trackColor = sliderBg)
                    VerticalRgbSlider(value = b, onValueChange = { b = it }, color = sliderColor, trackColor = sliderBg)
                }
            }
        }
    )
}

@Composable
private fun VerticalRgbSlider(
    value: Float,
    onValueChange: (Float) -> Unit,
    color: Color,
    modifier: Modifier = Modifier,
    trackColor: Color = color,
    height: Dp = 130.dp,
    trackWidth: Dp = 10.dp,
    thumbSize: Dp = 22.dp
) {
    val thumbPx = with(LocalDensity.current) { thumbSize.toPx() }
    var heightPx by remember { mutableFloatStateOf(0f) }
    val travel = (heightPx - thumbPx).coerceAtLeast(1f)

    val setFromY: (Float) -> Unit = { y ->
        val pos = (y - thumbPx / 2f).coerceIn(0f, travel)
        onValueChange(1f - pos / travel)
    }

    Box(
        modifier = modifier
            .height(height)
            .width(thumbSize)
            .onGloballyPositioned { heightPx = it.size.height.toFloat() }
            .pointerInput(Unit) { detectTapGestures { setFromY(it.y) } }
            .pointerInput(Unit) {
                detectVerticalDragGestures { change, _ -> setFromY(change.position.y) }
            }
    ) {
        Box(
            Modifier
                .width(trackWidth)
                .fillMaxHeight()
                .align(Alignment.Center)
                .background(trackColor, CircleShape)
                .border(EDGE, CircleShape)
        )
        Box(
            Modifier
                .size(thumbSize)
                .offset { IntOffset(0, ((1f - value) * travel).roundToInt()) }
                .background(color, CircleShape)
        )
    }
}