package dev.kaizen.app.chat

import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
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.isImeVisible
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.kaizen.app.net.Attachment
import dev.kaizen.app.net.KaizenApi
import dev.kaizen.app.ui.effect.GlassSurface
import dev.kaizen.app.ui.effect.GlassTiers
import dev.kaizen.app.ui.effect.KaizenShadows
import dev.kaizen.app.ui.effect.kaizenShadow
import dev.kaizen.app.ui.motion.Durations
import dev.kaizen.app.ui.shape.KaizenShapes
import dev.kaizen.app.ui.theme.KaizenSemanticColors
import dev.kaizen.app.ui.theme.LocalKaizenAccent
import dev.kaizen.app.R
import androidx.compose.ui.res.stringResource
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext
import dev.kaizen.app.haptics.rememberHaptics
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.launch
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Request
import dev.kaizen.app.ui.icon.KaizenIcons

@Composable
fun KaizenHeader(modifier: Modifier = Modifier) {
    val cs = MaterialTheme.colorScheme
    GlassSurface(
        modifier = modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp, vertical = 10.dp),
        shape = KaizenShapes.lg,
        shadowStack = KaizenShadows.level1,
        cornerRadius = 24.dp,
    ) {
        Row(
            Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            KaizenOrb(24.dp)
            Spacer(Modifier.width(10.dp))
            Text("Kaizen", color = cs.onBackground, fontSize = 18.sp, fontWeight = FontWeight.SemiBold)
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun EmptyHero(userName: String, onPick: (String) -> Unit, modifier: Modifier = Modifier) {
    val cs = MaterialTheme.colorScheme
    val now = remember { LocalDateTime.now() }
    val dateLine = remember {
        now.format(DateTimeFormatter.ofPattern("EEE, d. MMMM · HH:mm", Locale.getDefault()))
    }

    Column(
        modifier = modifier
            .fillMaxSize()
            .padding(horizontal = 24.dp)
            .padding(top = 76.dp, bottom = 160.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
    ) {
        KaizenOrb(104.dp)
        Spacer(Modifier.height(24.dp))
        Text("${greeting(now.hour)}, $userName", color = cs.onSurfaceVariant, fontSize = 17.sp, fontWeight = FontWeight.W300)
        Spacer(Modifier.height(6.dp))
        Text(stringResource(R.string.hero_headline), color = cs.onBackground, fontSize = 32.sp, fontWeight = FontWeight.W600)
        Spacer(Modifier.height(8.dp))
        Text(dateLine, color = cs.onSurfaceVariant.copy(alpha = 0.7f), fontSize = 13.sp, fontWeight = FontWeight.W300)
    }
}

@Composable
fun ModePillsRow(
    activeMode: Int?,
    onToggle: (Int) -> Unit,
    reasoningPreset: ReasoningPreset,
    onReasoningChange: (ReasoningPreset) -> Unit,
    samplingPrefs: SamplingPrefs,
    onSamplingChange: (SamplingPrefs) -> Unit,
    currentModelId: String = "",
    webSearchProvider: String = "google",
    onWebSearchProviderChange: (String) -> Unit = {},
    modifier: Modifier = Modifier,
) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val accent = LocalKaizenAccent.current

    var showReasoningMenu by remember { mutableStateOf(false) }
    var showSamplingPopup by remember { mutableStateOf(false) }
    var showSearchProviderMenu by remember { mutableStateOf(false) }
    val isVertexModel = currentModelId.startsWith("vertex:")

    Row(
        modifier
            .fillMaxWidth()
            .horizontalScroll(rememberScrollState())
            .padding(horizontal = 14.dp),
        horizontalArrangement = Arrangement.spacedBy(6.dp),
    ) {
        // Reasoning pill
        Box {
            val isDefault = reasoningPreset == ReasoningPreset.Standard
            ModePill(
                icon = KaizenIcons.Brain,
                label = stringResource(reasoningPreset.label),
                active = !isDefault,
                showChevron = true,
                onClick = { showReasoningMenu = true },
            )
            DropdownMenu(
                expanded = showReasoningMenu,
                onDismissRequest = { showReasoningMenu = false },
                containerColor = if (isDark) KaizenSemanticColors.dropdownDark else KaizenSemanticColors.dropdownLight,
                shadowElevation = 16.dp,
                shape = KaizenShapes.md,
            ) {
                ReasoningPreset.entries.forEach { preset ->
                    Row(
                        Modifier
                            .fillMaxWidth()
                            .clickable { onReasoningChange(preset); showReasoningMenu = false }
                            .padding(horizontal = 16.dp, vertical = 10.dp),
                        verticalAlignment = Alignment.CenterVertically,
                    ) {
                        Text(
                            stringResource(preset.label),
                            fontSize = 13.5.sp,
                            color = if (preset == reasoningPreset) accent.primary else cs.onBackground.copy(alpha = 0.85f),
                            fontWeight = if (preset == reasoningPreset) FontWeight.SemiBold else FontWeight.Normal,
                            modifier = Modifier.weight(1f),
                        )
                        if (preset == reasoningPreset) {
                            Icon(KaizenIcons.Check, null, tint = accent.primary, modifier = Modifier.size(15.dp))
                        }
                    }
                }
            }
        }

        // Sampling pill
        Box {
            val anyEnabled = samplingPrefs.temperature.enabled || samplingPrefs.topP.enabled || samplingPrefs.topK.enabled
            ModePill(
                icon = KaizenIcons.SlidersHorizontal,
                label = stringResource(R.string.mode_sampling),
                active = anyEnabled,
                showChevron = true,
                onClick = { showSamplingPopup = !showSamplingPopup },
            )
            DropdownMenu(
                expanded = showSamplingPopup,
                onDismissRequest = { showSamplingPopup = false },
                containerColor = if (isDark) KaizenSemanticColors.dropdownDark else KaizenSemanticColors.dropdownLight,
                shadowElevation = 16.dp,
                shape = KaizenShapes.md,
            ) {
                Column(Modifier.padding(horizontal = 14.dp, vertical = 10.dp).width(220.dp)) {
                    SamplingRow(
                        label = stringResource(R.string.sampling_temperature),
                        param = samplingPrefs.temperature, min = 0f, max = 2f, steps = 200,
                        onUpdate = { onSamplingChange(samplingPrefs.copy(temperature = it)) },
                    )
                    Spacer(Modifier.height(6.dp))
                    SamplingRow(
                        label = stringResource(R.string.sampling_top_p),
                        param = samplingPrefs.topP, min = 0f, max = 1f, steps = 100,
                        onUpdate = { onSamplingChange(samplingPrefs.copy(topP = it)) },
                    )
                    Spacer(Modifier.height(6.dp))
                    SamplingRow(
                        label = stringResource(R.string.sampling_top_k),
                        param = samplingPrefs.topK, min = 1f, max = 100f, steps = 99,
                        onUpdate = { onSamplingChange(samplingPrefs.copy(topK = it)) },
                    )
                }
            }
        }

        // Mode pills (Bild, Suche, Video, Audio)
        chatModes.filter { it.labelRes != R.string.mode_standard && it.labelRes != R.string.mode_sampling }.forEach { mode ->
            val active = mode.labelRes == activeMode
            ModePill(
                icon = mode.icon,
                label = stringResource(mode.labelRes),
                active = active,
                onClick = { onToggle(mode.labelRes) },
            )

            // Search provider dropdown — only for Vertex models when search is active
            if (mode.labelRes == R.string.mode_search && active && isVertexModel) {
                Box {
                    val providerLabel = stringResource(
                        if (webSearchProvider == "enterprise") R.string.search_provider_enterprise
                        else R.string.search_provider_google
                    )
                    ModePill(
                        icon = KaizenIcons.Globe,
                        label = providerLabel,
                        active = true,
                        showChevron = true,
                        onClick = { showSearchProviderMenu = true },
                    )
                    DropdownMenu(
                        expanded = showSearchProviderMenu,
                        onDismissRequest = { showSearchProviderMenu = false },
                        containerColor = if (isDark) KaizenSemanticColors.dropdownDark else KaizenSemanticColors.dropdownLight,
                        shadowElevation = 16.dp,
                        shape = KaizenShapes.md,
                    ) {
                        listOf("google" to R.string.search_provider_google, "enterprise" to R.string.search_provider_enterprise).forEach { (value, labelRes) ->
                            Row(
                                Modifier
                                    .fillMaxWidth()
                                    .clickable { onWebSearchProviderChange(value); showSearchProviderMenu = false }
                                    .padding(horizontal = 16.dp, vertical = 10.dp),
                                verticalAlignment = Alignment.CenterVertically,
                            ) {
                                Text(
                                    stringResource(labelRes),
                                    fontSize = 13.5.sp,
                                    color = if (value == webSearchProvider) accent.primary else cs.onBackground.copy(alpha = 0.85f),
                                    fontWeight = if (value == webSearchProvider) FontWeight.SemiBold else FontWeight.Normal,
                                    modifier = Modifier.weight(1f),
                                )
                                if (value == webSearchProvider) {
                                    Icon(KaizenIcons.Check, null, tint = accent.primary, modifier = Modifier.size(15.dp))
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

@Composable
private fun ModePill(
    icon: ImageVector,
    label: String,
    active: Boolean,
    showChevron: Boolean = false,
    onClick: () -> Unit,
) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val accent = LocalKaizenAccent.current

    val bg = when {
        active -> accent.primary.copy(alpha = if (isDark) 0.24f else 0.14f)
        else -> if (isDark) Color.White.copy(alpha = 0.14f) else Color.Black.copy(alpha = 0.09f)
    }
    val borderBrush = if (active) {
        Brush.verticalGradient(listOf(
            accent.primary.copy(alpha = if (isDark) 0.45f else 0.35f),
            accent.primary.copy(alpha = if (isDark) 0.15f else 0.10f),
        ))
    } else {
        Brush.verticalGradient(listOf(
            if (isDark) Color.White.copy(alpha = 0.18f) else Color.Black.copy(alpha = 0.12f),
            if (isDark) Color.White.copy(alpha = 0.06f) else Color.Black.copy(alpha = 0.05f),
        ))
    }
    val iconTint = if (active) accent.primary else cs.onSurfaceVariant
    val textColor = if (active) cs.onBackground else cs.onSurfaceVariant

    Row(
        Modifier
            .clip(KaizenShapes.pill)
            .background(bg)
            .border(0.8.dp, borderBrush, KaizenShapes.pill)
            .clickable(onClick = onClick)
            .padding(horizontal = 14.dp, vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Icon(icon, null, tint = iconTint, modifier = Modifier.size(16.dp))
        Spacer(Modifier.width(6.dp))
        Text(label, color = textColor, fontSize = 13.sp, fontWeight = if (active) FontWeight.W500 else FontWeight.W400)
        if (showChevron) {
            Spacer(Modifier.width(4.dp))
            Icon(KaizenIcons.ChevronDown, null, tint = cs.onSurfaceVariant.copy(alpha = 0.50f), modifier = Modifier.size(14.dp))
        }
    }
}

@Composable
private fun SamplingRow(label: String, param: SamplingParam, min: Float, max: Float, steps: Int, onUpdate: (SamplingParam) -> Unit) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val accent = LocalKaizenAccent.current

    Row(verticalAlignment = Alignment.CenterVertically) {
        Box(
            Modifier
                .size(16.dp)
                .clip(KaizenShapes.xs)
                .border(
                    1.dp,
                    if (param.enabled) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.25f),
                    KaizenShapes.xs,
                )
                .then(if (param.enabled) Modifier.background(accent.primary.copy(alpha = 0.15f)) else Modifier)
                .clickable { onUpdate(param.copy(enabled = !param.enabled)) },
            contentAlignment = Alignment.Center,
        ) {
            if (param.enabled) {
                Icon(KaizenIcons.Check, null, tint = accent.primary, modifier = Modifier.size(11.dp))
            }
        }
        Spacer(Modifier.width(8.dp))
        Text(label, color = cs.onBackground.copy(alpha = if (param.enabled) 0.90f else 0.50f), fontSize = 12.sp, modifier = Modifier.weight(1f))
        Text(
            if (max > 2f) param.value.toInt().toString() else "%.2f".format(param.value),
            color = if (param.enabled) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.40f),
            fontSize = 11.sp, fontWeight = FontWeight.Medium,
        )
    }
    Slider(
        value = param.value, onValueChange = { onUpdate(param.copy(value = it)) },
        valueRange = min..max, steps = steps,
        enabled = param.enabled,
        colors = SliderDefaults.colors(
            thumbColor = accent.primary,
            activeTrackColor = accent.primary,
            inactiveTrackColor = if (isDark) Color.White.copy(alpha = 0.08f) else Color.Black.copy(alpha = 0.06f),
            disabledThumbColor = cs.onSurfaceVariant.copy(alpha = 0.20f),
            disabledActiveTrackColor = cs.onSurfaceVariant.copy(alpha = 0.15f),
            disabledInactiveTrackColor = if (isDark) Color.White.copy(alpha = 0.05f) else Color.Black.copy(alpha = 0.04f),
        ),
        modifier = Modifier.fillMaxWidth().height(28.dp),
    )
}

@Composable
fun TokenBadge(used: Int, contextLength: Int, modifier: Modifier = Modifier) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val accent = LocalKaizenAccent.current

    fun formatK(n: Int): String = when {
        n >= 1_000_000 -> "${n / 1_000_000}M"
        n >= 1_000 -> "${n / 1_000}k"
        else -> "$n"
    }

    GlassSurface(
        modifier = modifier,
        shape = KaizenShapes.pill,
        tintAlpha = GlassTiers.pill(isDark),
        shadowStack = KaizenShadows.level1,
        cornerRadius = 16.dp,
    ) {
        Row(
            Modifier.padding(horizontal = 10.dp, vertical = 7.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Icon(KaizenIcons.Zap, null, tint = accent.primary, modifier = Modifier.size(14.dp))
            Spacer(Modifier.width(4.dp))
            Text(
                "${formatK(used)} / ${formatK(contextLength)}",
                color = cs.onSurfaceVariant,
                fontSize = 12.sp,
                fontWeight = FontWeight.Medium,
                maxLines = 1,
                softWrap = false,
            )
        }
    }
}

@Composable
fun BranchNavigator(
    branchInfo: BranchInfo,
    onNavigate: (Int) -> Unit,
    modifier: Modifier = Modifier,
) {
    val cs = MaterialTheme.colorScheme
    Row(
        modifier = modifier,
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(2.dp),
    ) {
        Box(
            Modifier.size(28.dp).clip(KaizenShapes.circle)
                .clickable(enabled = branchInfo.index > 0) { onNavigate(-1) },
            contentAlignment = Alignment.Center,
        ) {
            Icon(
                KaizenIcons.ChevronLeft, null,
                tint = cs.onSurfaceVariant.copy(alpha = if (branchInfo.index > 0) 0.65f else 0.20f),
                modifier = Modifier.size(16.dp),
            )
        }
        Text(
            "${branchInfo.index + 1}/${branchInfo.total}",
            color = cs.onSurfaceVariant.copy(alpha = 0.55f),
            fontSize = 12.sp,
            fontWeight = FontWeight.Medium,
        )
        Box(
            Modifier.size(28.dp).clip(KaizenShapes.circle)
                .clickable(enabled = branchInfo.index < branchInfo.total - 1) { onNavigate(1) },
            contentAlignment = Alignment.Center,
        ) {
            Icon(
                KaizenIcons.ChevronRight, null,
                tint = cs.onSurfaceVariant.copy(alpha = if (branchInfo.index < branchInfo.total - 1) 0.65f else 0.20f),
                modifier = Modifier.size(16.dp),
            )
        }
    }
}

@Composable
fun MessageRow(
    message: Message,
    onCopy: (String) -> Unit = {},
    onDelete: ((String) -> Unit)? = null,
    onRegenerate: ((String) -> Unit)? = null,
    onEdit: ((String, String) -> Unit)? = null,
    onReadAloud: ((String) -> Unit)? = null,
    isPlayingTts: Boolean = false,
    branchInfo: BranchInfo? = null,
    onNavigateBranch: ((String, Int) -> Unit)? = null,
) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val accent = LocalKaizenAccent.current
    val hasAttachments = message.attachments.isNotEmpty()

    val clipboard = LocalClipboard.current
    val haptics = rememberHaptics()
    val scope = rememberCoroutineScope()
    var copied by remember { mutableStateOf(false) }

    if (message.role == Role.User) {
        Column(horizontalAlignment = Alignment.End, modifier = Modifier.fillMaxWidth()) {
        Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
            val shape = KaizenShapes.lg
            val bgBrush = remember(accent, isDark) {
                Brush.verticalGradient(
                    listOf(
                        if (isDark) accent.primary.copy(alpha = 0.28f) else accent.primary.copy(alpha = 0.16f),
                        if (isDark) accent.primaryDeep.copy(alpha = 0.22f) else accent.primaryDeep.copy(alpha = 0.10f),
                    )
                )
            }
            val borderBrush = remember(accent, isDark) {
                Brush.verticalGradient(
                    listOf(
                        accent.primary.copy(alpha = if (isDark) 0.55f else 0.40f),
                        accent.primaryDeep.copy(alpha = if (isDark) 0.15f else 0.10f),
                    )
                )
            }
            val innerHighlight = remember(accent, isDark) {
                Brush.verticalGradient(
                    listOf(
                        accent.primary.copy(alpha = if (isDark) 0.12f else 0.08f),
                        Color.Transparent,
                    )
                )
            }

            val isVoiceOnly = hasAttachments && message.content.isBlank() && message.attachments.all { it.kind == "audio" }

            Box(
                Modifier
                    .widthIn(max = 300.dp)
                    .kaizenShadow(stack = KaizenShadows.level1, cornerRadius = 20.dp)
                    .clip(shape)
                    .background(bgBrush)
                    .background(innerHighlight)
                    .border(1.2.dp, borderBrush, shape),
            ) {
                androidx.compose.foundation.text.selection.SelectionContainer {
                Column {
                    if (isVoiceOnly) {
                        Row(
                            Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
                            verticalAlignment = Alignment.CenterVertically,
                        ) {
                            Icon(KaizenIcons.Mic, null, tint = accent.primary.copy(alpha = 0.7f), modifier = Modifier.size(18.dp))
                            Spacer(Modifier.width(8.dp))
                            Text(
                                stringResource(R.string.chat_voice_message),
                                color = cs.onBackground.copy(alpha = 0.70f),
                                fontSize = 14.sp,
                                fontWeight = FontWeight.Medium,
                            )
                        }
                    } else {
                        if (hasAttachments) AttachmentGrid(message.attachments, Modifier.padding(6.dp))
                        if (message.content.isNotBlank()) {
                            Text(
                                message.content, color = cs.onBackground, fontSize = 16.sp, lineHeight = 26.sp, fontWeight = FontWeight.W300,
                                modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = if (hasAttachments) 4.dp else 13.dp, bottom = 13.dp),
                            )
                        }
                    }
                }
                }
            }
        }
        if (!message.streaming) {
            Row(
                Modifier.padding(top = 6.dp, end = 4.dp),
                horizontalArrangement = Arrangement.spacedBy(2.dp),
                verticalAlignment = Alignment.CenterVertically,
            ) {
                if (branchInfo != null && onNavigateBranch != null) {
                    BranchNavigator(branchInfo, onNavigate = { dir -> onNavigateBranch(message.wireId, dir) })
                    Spacer(Modifier.width(4.dp))
                }
                MessageActionButton(
                    icon = if (copied) KaizenIcons.Check else KaizenIcons.ClipboardCopy,
                    tint = if (copied) KaizenSemanticColors.success else cs.onSurfaceVariant.copy(alpha = 0.55f),
                ) {
                    haptics.tick()
                    copied = true
                    scope.launch {
                        clipboard.setClipEntry(androidx.compose.ui.platform.ClipEntry(android.content.ClipData.newPlainText("msg", message.content)))
                        kotlinx.coroutines.delay(1500)
                        copied = false
                    }
                }
                if (message.content.isNotBlank()) {
                    onEdit?.let { edit ->
                        MessageActionButton(KaizenIcons.SquarePen, cs.onSurfaceVariant.copy(alpha = 0.55f)) {
                            haptics.tick(); edit(message.wireId, message.content)
                        }
                    }
                }
                onDelete?.let { del ->
                    MessageActionButton(KaizenIcons.Trash, KaizenSemanticColors.error.copy(alpha = 0.70f)) {
                        haptics.tick(); del(message.wireId)
                    }
                }
            }
        }
        }
    } else {
        Column {
        Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.Top) {
            KaizenOrb(28.dp, streaming = message.streaming)
            Spacer(Modifier.width(10.dp))
            Box(Modifier.weight(1f).padding(top = 3.dp)) {
                androidx.compose.foundation.text.selection.SelectionContainer {
                Column {
                    if (message.reasoning.isNotBlank()) {
                        ReasoningBlock(message.reasoning, streaming = message.streaming)
                        Spacer(Modifier.height(8.dp))
                    }
                    if (message.tools.isNotEmpty()) {
                        ToolEventsBlock(message.tools, streaming = message.streaming)
                        Spacer(Modifier.height(8.dp))
                    }
                    if (hasAttachments) {
                        AttachmentGrid(message.attachments)
                        if (message.content.isNotBlank() || message.thinking) Spacer(Modifier.height(8.dp))
                    }
                    if (message.thinking) TypingDots()
                    else MarkdownText(text = message.content, streaming = message.streaming)
                    if (message.sources.isNotEmpty()) {
                        Spacer(Modifier.height(8.dp))
                        SourcesBlock(message.sources, query = message.query)
                    }
                }
                }
            }
        }
        if (!message.streaming && !message.thinking && message.content.isNotBlank()) {
            Row(
                Modifier.padding(start = 38.dp, top = 6.dp),
                horizontalArrangement = Arrangement.spacedBy(2.dp),
                verticalAlignment = Alignment.CenterVertically,
            ) {
                if (branchInfo != null && onNavigateBranch != null) {
                    BranchNavigator(branchInfo, onNavigate = { dir -> onNavigateBranch(message.wireId, dir) })
                    Spacer(Modifier.width(4.dp))
                }
                MessageActionButton(
                    icon = if (copied) KaizenIcons.Check else KaizenIcons.ClipboardCopy,
                    tint = if (copied) KaizenSemanticColors.success else cs.onSurfaceVariant.copy(alpha = 0.55f),
                ) {
                    haptics.tick()
                    copied = true
                    scope.launch {
                        clipboard.setClipEntry(androidx.compose.ui.platform.ClipEntry(android.content.ClipData.newPlainText("msg", message.content)))
                        kotlinx.coroutines.delay(1500)
                        copied = false
                    }
                }
                onReadAloud?.let { readAloud ->
                    MessageActionButton(
                        icon = KaizenIcons.Volume2,
                        tint = if (isPlayingTts) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.55f),
                    ) {
                        haptics.tick(); readAloud(message.content)
                    }
                }
                onRegenerate?.let { regen ->
                    MessageActionButton(KaizenIcons.RotateCcw, cs.onSurfaceVariant.copy(alpha = 0.55f)) {
                        haptics.tick(); regen(message.wireId)
                    }
                }
                onDelete?.let { del ->
                    MessageActionButton(KaizenIcons.Trash, KaizenSemanticColors.error.copy(alpha = 0.70f)) {
                        haptics.tick(); del(message.wireId)
                    }
                }
            }
        }
        }
    }
}

private const val WAVEFORM_BAR_COUNT = 32

@Composable
private fun VoiceWaveform(amplitude: Float) {
    val accent = LocalKaizenAccent.current
    val isDark = isSystemInDarkTheme()

    val barHeights = remember { Array(WAVEFORM_BAR_COUNT) { mutableStateOf(0.08f) } }

    LaunchedEffect(amplitude) {
        // Shift bars left, add new amplitude on the right
        for (i in 0 until WAVEFORM_BAR_COUNT - 1) {
            barHeights[i].value = barHeights[i + 1].value
        }
        barHeights[WAVEFORM_BAR_COUNT - 1].value = (amplitude * 0.85f + 0.08f).coerceIn(0.08f, 0.95f)
    }

    val barColor = accent.primary.copy(alpha = if (isDark) 0.75f else 0.65f)
    val mutedColor = accent.primary.copy(alpha = if (isDark) 0.25f else 0.18f)

    Canvas(
        Modifier
            .fillMaxWidth()
            .height(48.dp)
    ) {
        val totalWidth = size.width
        val barWidth = totalWidth / (WAVEFORM_BAR_COUNT * 2f)
        val gap = barWidth
        val centerY = size.height / 2f
        val maxBarHeight = size.height * 0.8f

        for (i in 0 until WAVEFORM_BAR_COUNT) {
            val h = barHeights[i].value
            val barH = maxBarHeight * h
            val x = i * (barWidth + gap) + gap / 2f
            val color = if (h > 0.12f) barColor else mutedColor

            drawRoundRect(
                color = color,
                topLeft = Offset(x, centerY - barH / 2f),
                size = androidx.compose.ui.geometry.Size(barWidth, barH),
                cornerRadius = CornerRadius(barWidth / 2f, barWidth / 2f),
            )
        }
    }
}

@Composable
private fun MessageActionButton(icon: ImageVector, tint: Color, onClick: () -> Unit) {
    Box(
        Modifier
            .size(36.dp)
            .clip(KaizenShapes.circle)
            .clickable(onClick = onClick),
        contentAlignment = Alignment.Center,
    ) {
        Icon(icon, null, tint = tint, modifier = Modifier.size(20.dp))
    }
}

@Composable
private fun TypingDots() {
    val cs = MaterialTheme.colorScheme
    val transition = rememberInfiniteTransition(label = "typing")
    Row(verticalAlignment = Alignment.CenterVertically) {
        repeat(3) { i ->
            val alpha by transition.animateFloat(
                initialValue = 0.25f, targetValue = 1f,
                animationSpec = infiniteRepeatable(
                    tween(Durations.THINKING_DOT_CYCLE, delayMillis = i * Durations.THINKING_DOT_PHASE_OFFSET, easing = FastOutSlowInEasing),
                    RepeatMode.Reverse,
                ),
                label = "dot$i",
            )
            Box(Modifier.padding(end = 5.dp).size(7.dp).clip(KaizenShapes.circle).background(cs.onSurfaceVariant.copy(alpha = alpha)))
        }
    }
}

data class PendingFile(
    val id: String = java.util.UUID.randomUUID().toString(),
    val name: String,
    val mimeType: String,
    val bytes: ByteArray,
    val uploaded: Attachment? = null,
    val error: String? = null,
    val uploading: Boolean = true,
)

@Composable
fun ChatInput(
    value: String,
    onValueChange: (String) -> Unit,
    onSend: () -> Unit,
    enabled: Boolean,
    isStreaming: Boolean = false,
    onStop: () -> Unit = {},
    isRecording: Boolean = false,
    recordingAmplitude: Float = 0f,
    onMicClick: () -> Unit = {},
    onCallClick: () -> Unit = {},
    pendingFiles: List<PendingFile> = emptyList(),
    onAddClick: () -> Unit = {},
    onRemoveFile: (String) -> Unit = {},
    pillsContent: @Composable (() -> Unit)? = null,
    modifier: Modifier = Modifier,
) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val accent = LocalKaizenAccent.current

    val hasContent = value.isNotBlank() || pendingFiles.any { it.uploaded != null }
    val canSend = enabled && hasContent && pendingFiles.none { it.uploading }

    var showPills by remember { mutableStateOf(false) }

    GlassSurface(
        modifier = modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp),
        shape = KaizenShapes.pill,
        tintAlpha = if (isDark) 0.92f else 0.97f,
        shadowStack = KaizenShadows.level4,
        cornerRadius = 34.dp,
    ) {
        Column(
            Modifier.heightIn(min = 64.dp),
            verticalArrangement = Arrangement.Center,
        ) {
            if (isRecording) {
                Column(Modifier.padding(horizontal = 14.dp)) {
                    VoiceWaveform(amplitude = recordingAmplitude)
                    Spacer(Modifier.height(10.dp))
                    Row(verticalAlignment = Alignment.CenterVertically) {
                        Spacer(Modifier.weight(1f))
                        Box(
                            Modifier.size(40.dp).clip(KaizenShapes.circle)
                                .background(KaizenSemanticColors.error)
                                .clickable(onClick = onMicClick),
                            contentAlignment = Alignment.Center,
                        ) {
                            Icon(KaizenIcons.Square, stringResource(R.string.chat_stop), tint = Color.White, modifier = Modifier.size(16.dp))
                        }
                        Spacer(Modifier.weight(1f))
                    }
                }
            } else {
                // Expandable pills row
                if (pillsContent != null) {
                    androidx.compose.animation.AnimatedVisibility(
                        visible = showPills,
                        enter = androidx.compose.animation.expandVertically() + androidx.compose.animation.fadeIn(),
                        exit = androidx.compose.animation.shrinkVertically() + androidx.compose.animation.fadeOut(),
                    ) {
                        Column {
                            pillsContent()
                            Spacer(Modifier.height(6.dp))
                            Box(
                                Modifier.fillMaxWidth().padding(horizontal = 14.dp).height(0.5.dp)
                                    .background(cs.onSurfaceVariant.copy(alpha = 0.10f))
                            )
                            Spacer(Modifier.height(8.dp))
                        }
                    }
                }

                if (pendingFiles.isNotEmpty()) {
                    Row(
                        Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()).padding(start = 14.dp, end = 14.dp, bottom = 8.dp),
                        horizontalArrangement = Arrangement.spacedBy(6.dp),
                    ) {
                        pendingFiles.forEach { pf -> PendingFileChip(pf, onRemove = { onRemoveFile(pf.id) }) }
                    }
                }

                // Single-row capsule: Plus | TextField | Tune | Mic | Send/Call
                Row(
                    Modifier
                        .fillMaxWidth()
                        .padding(start = 10.dp, end = 8.dp),
                    verticalAlignment = Alignment.CenterVertically,
                ) {
                    Box(
                        Modifier.size(40.dp).clip(KaizenShapes.circle).clickable(onClick = onAddClick),
                        contentAlignment = Alignment.Center,
                    ) {
                        Icon(KaizenIcons.Plus, stringResource(R.string.chat_attachment), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
                    }

                    Box(
                        Modifier.weight(1f).padding(horizontal = 4.dp),
                        contentAlignment = Alignment.CenterStart,
                    ) {
                        if (value.isEmpty()) Text(
                            stringResource(R.string.chat_input_placeholder),
                            color = cs.onSurfaceVariant.copy(alpha = 0.38f),
                            fontSize = 16.sp,
                            fontWeight = FontWeight.W300,
                            maxLines = 1,
                        )
                        BasicTextField(
                            value = value, onValueChange = onValueChange,
                            textStyle = TextStyle(color = cs.onBackground, fontSize = 16.sp, lineHeight = 24.sp, fontWeight = FontWeight.W300),
                            cursorBrush = SolidColor(accent.primary), maxLines = 4, modifier = Modifier.fillMaxWidth(),
                        )
                    }

                    if (pillsContent != null) {
                        Box(
                            Modifier.size(40.dp).clip(KaizenShapes.circle).clickable { showPills = !showPills },
                            contentAlignment = Alignment.Center,
                        ) {
                            Icon(
                                KaizenIcons.SlidersHorizontal, null,
                                tint = if (showPills) accent.primary else cs.onSurfaceVariant.copy(alpha = 0.45f),
                                modifier = Modifier.size(20.dp),
                            )
                        }
                    }

                    Box(
                        Modifier.size(40.dp).clip(KaizenShapes.circle).clickable(onClick = onMicClick),
                        contentAlignment = Alignment.Center,
                    ) {
                        Icon(KaizenIcons.Mic, stringResource(R.string.chat_mic_permission), tint = cs.onSurfaceVariant, modifier = Modifier.size(22.dp))
                    }

                    if (isStreaming) {
                        StopButton(onClick = onStop)
                    } else if (hasContent) {
                        SendButton(enabled = canSend, onClick = onSend)
                    } else {
                        CallButton(onClick = onCallClick)
                    }
                }
            }
        }
    }
}

@Composable
private fun PendingFileChip(pf: PendingFile, onRemove: () -> Unit) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val bg = if (isDark) Color.White.copy(alpha = 0.08f) else Color.Black.copy(alpha = 0.06f)
    val isImage = pf.mimeType.startsWith("image/")

    Box(Modifier.size(64.dp).clip(KaizenShapes.xs).background(bg), contentAlignment = Alignment.Center) {
        if (isImage && pf.uploaded != null) {
            NetworkImage(url = pf.uploaded.url, contentDescription = pf.name, modifier = Modifier.fillMaxSize().clip(KaizenShapes.xs))
        } else {
            val icon = when {
                pf.mimeType.startsWith("image/") -> KaizenIcons.Image
                pf.mimeType.startsWith("video/") -> KaizenIcons.FileVideo
                pf.mimeType.startsWith("audio/") -> KaizenIcons.FileAudio
                pf.mimeType == "application/pdf" -> KaizenIcons.FileText
                else -> KaizenIcons.File
            }
            Column(horizontalAlignment = Alignment.CenterHorizontally) {
                Icon(icon, null, tint = cs.onSurfaceVariant, modifier = Modifier.size(20.dp))
                Text(pf.name, color = cs.onSurfaceVariant, fontSize = 9.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(horizontal = 4.dp))
            }
        }
        if (pf.uploading) {
            Box(Modifier.fillMaxSize().background(Color.Black.copy(alpha = 0.4f)), contentAlignment = Alignment.Center) {
                androidx.compose.material3.CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp, color = Color.White)
            }
        }
        if (pf.error != null) {
            Box(Modifier.fillMaxSize().background(KaizenSemanticColors.errorDeep.copy(alpha = 0.3f)), contentAlignment = Alignment.Center) {
                Icon(KaizenIcons.X, stringResource(R.string.chat_error), tint = Color.White, modifier = Modifier.size(18.dp))
            }
        }
        Box(
            Modifier.align(Alignment.TopEnd).padding(2.dp).size(18.dp).clip(KaizenShapes.circle).background(Color.Black.copy(alpha = 0.5f)).clickable(onClick = onRemove),
            contentAlignment = Alignment.Center,
        ) {
            Icon(KaizenIcons.X, stringResource(R.string.chat_remove), tint = Color.White, modifier = Modifier.size(12.dp))
        }
    }
}

@Composable
private fun SendButton(enabled: Boolean, onClick: () -> Unit) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val interaction = remember { MutableInteractionSource() }
    val pressed by interaction.collectIsPressedAsState()
    val pressScale by animateFloatAsState(if (pressed) 0.88f else 1f, label = "send")
    val accent = LocalKaizenAccent.current

    val fill = if (enabled) {
        Modifier.background(
            Brush.verticalGradient(
                listOf(
                    accent.primary.copy(alpha = if (isDark) 0.85f else 0.80f),
                    accent.primaryDeep.copy(alpha = if (isDark) 0.90f else 0.85f),
                )
            )
        )
    } else {
        Modifier.background(cs.surfaceVariant.copy(alpha = 0.5f))
    }

    Box(
        Modifier.size(40.dp).scale(pressScale).clip(KaizenShapes.circle).then(fill)
            .clickable(interactionSource = interaction, indication = null, enabled = enabled, onClick = onClick),
        contentAlignment = Alignment.Center,
    ) {
        SendArrow(color = if (enabled) accent.onPrimary else cs.onSurfaceVariant.copy(alpha = 0.5f))
    }
}

@Composable
private fun CallButton(onClick: () -> Unit) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val interaction = remember { MutableInteractionSource() }
    val pressed by interaction.collectIsPressedAsState()
    val pressScale by animateFloatAsState(if (pressed) 0.88f else 1f, label = "call")
    val accent = LocalKaizenAccent.current

    Box(
        Modifier
            .size(40.dp)
            .scale(pressScale)
            .clip(KaizenShapes.circle)
            .background(
                Brush.verticalGradient(
                    listOf(
                        accent.primary.copy(alpha = if (isDark) 0.22f else 0.16f),
                        accent.primaryDeep.copy(alpha = if (isDark) 0.28f else 0.22f),
                    )
                )
            )
            .clickable(interactionSource = interaction, indication = null, onClick = onClick),
        contentAlignment = Alignment.Center,
    ) {
        Icon(KaizenIcons.AudioLines, null, tint = accent.primary, modifier = Modifier.size(22.dp))
    }
}

@Composable
private fun StopButton(onClick: () -> Unit) {
    val isDark = isSystemInDarkTheme()
    val interaction = remember { MutableInteractionSource() }
    val pressed by interaction.collectIsPressedAsState()
    val pressScale by animateFloatAsState(if (pressed) 0.88f else 1f, label = "stop")

    Box(
        Modifier
            .size(40.dp)
            .scale(pressScale)
            .clip(KaizenShapes.circle)
            .background(KaizenSemanticColors.error.copy(alpha = if (isDark) 0.85f else 0.80f))
            .clickable(interactionSource = interaction, indication = null, onClick = onClick),
        contentAlignment = Alignment.Center,
    ) {
        Icon(KaizenIcons.Square, stringResource(R.string.chat_stop), tint = Color.White, modifier = Modifier.size(16.dp))
    }
}

// ── Attachment rendering ────────────────────────────────────────────────────

private object NetworkImageCache {
    private const val MAX_MEMORY_BYTES = 32 * 1024 * 1024L // 32MB
    private var currentBytes = 0L
    private val cache = object : LinkedHashMap<String, ImageBitmap>(32, 0.75f, true) {
        override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, ImageBitmap>?) : Boolean {
            if (currentBytes <= MAX_MEMORY_BYTES) return false
            val bmp = eldest?.value ?: return false
            currentBytes -= bmp.width.toLong() * bmp.height * 4
            return true
        }
    }

    private var diskCacheDir: java.io.File? = null

    fun init(context: android.content.Context) {
        if (diskCacheDir == null) diskCacheDir = java.io.File(context.cacheDir, "img").also { it.mkdirs() }
    }

    private fun diskFile(url: String): java.io.File? {
        val dir = diskCacheDir ?: return null
        val hash = url.hashCode().toUInt().toString(16)
        return java.io.File(dir, hash)
    }

    suspend fun load(url: String): ImageBitmap? = withContext(Dispatchers.IO) {
        synchronized(cache) { cache[url] }?.let { return@withContext it }
        try {
            val bytes = diskFile(url)?.takeIf { it.exists() }?.readBytes()
                ?: run {
                    val req = Request.Builder().url(url).build()
                    KaizenApi.imageClient.newCall(req).execute().use { resp ->
                        if (!resp.isSuccessful) return@withContext null
                        resp.body!!.bytes().also { b -> diskFile(url)?.writeBytes(b) }
                    }
                }
            val probe = BitmapFactory.Options().apply { inJustDecodeBounds = true }
            BitmapFactory.decodeByteArray(bytes, 0, bytes.size, probe)
            val scale = maxOf(1, maxOf(probe.outWidth, probe.outHeight) / 1536)
            val opts = BitmapFactory.Options().apply { inSampleSize = scale }
            val bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, opts) ?: return@withContext null
            val imgBmp = bmp.asImageBitmap()
            synchronized(cache) {
                currentBytes += imgBmp.width.toLong() * imgBmp.height * 4
                cache[url] = imgBmp
            }
            imgBmp
        } catch (_: Exception) { null }
    }
}

@Composable
private fun NetworkImage(url: String, contentDescription: String?, modifier: Modifier = Modifier, contentScale: ContentScale = ContentScale.Crop) {
    val context = LocalContext.current
    LaunchedEffect(Unit) { NetworkImageCache.init(context) }
    var bitmap by remember(url) { mutableStateOf<ImageBitmap?>(null) }
    LaunchedEffect(url) { bitmap = NetworkImageCache.load(url) }
    val bmp = bitmap
    androidx.compose.animation.Crossfade(targetState = bmp, label = "img") { img ->
        if (img != null) {
            Image(bitmap = img, contentDescription = contentDescription, modifier = modifier, contentScale = contentScale)
        } else {
            Box(modifier.fillMaxWidth().height(160.dp).clip(KaizenShapes.xs).background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)))
        }
    }
}

@Composable
fun AttachmentGrid(attachments: List<Attachment>, modifier: Modifier = Modifier) {
    if (attachments.isEmpty()) return
    val images = attachments.filter { it.kind == "image" }
    val others = attachments.filter { it.kind != "image" }
    Column(modifier, verticalArrangement = Arrangement.spacedBy(6.dp)) {
        images.forEach { att ->
            NetworkImage(url = att.url, contentDescription = att.name, modifier = Modifier.fillMaxWidth().heightIn(max = 280.dp).clip(KaizenShapes.xs))
        }
        others.forEach { att -> FileChip(att) }
    }
}

@Composable
private fun FileChip(attachment: Attachment) {
    val cs = MaterialTheme.colorScheme
    val isDark = isSystemInDarkTheme()
    val bg = if (isDark) Color.White.copy(alpha = 0.08f) else Color.Black.copy(alpha = 0.06f)
    val icon = when (attachment.kind) {
        "pdf" -> KaizenIcons.FileText
        "audio" -> KaizenIcons.FileAudio
        "video" -> KaizenIcons.FileVideo
        "code" -> KaizenIcons.Code
        else -> KaizenIcons.File
    }
    Row(Modifier.clip(KaizenShapes.xs).background(bg).padding(horizontal = 10.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically) {
        Icon(icon, null, tint = cs.onSurfaceVariant, modifier = Modifier.size(16.dp))
        Spacer(Modifier.width(6.dp))
        Text(attachment.name, color = cs.onSurfaceVariant, fontSize = 13.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
    }
}

@Composable
fun AttachmentMenu(
    onCamera: () -> Unit,
    onGallery: () -> Unit,
    onFile: () -> Unit,
    modifier: Modifier = Modifier,
) {
    val accent = LocalKaizenAccent.current
    val isDark = isSystemInDarkTheme()

    Column(
        modifier = modifier.padding(horizontal = 16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        horizontalAlignment = Alignment.Start,
    ) {
        AttachPill(KaizenIcons.Camera, stringResource(R.string.attach_camera), accent.primary, isDark, onCamera)
        AttachPill(KaizenIcons.Image, stringResource(R.string.attach_gallery), KaizenSemanticColors.indigo, isDark, onGallery)
        AttachPill(KaizenIcons.File, stringResource(R.string.attach_file), KaizenSemanticColors.success, isDark, onFile)
    }
}

@Composable
private fun AttachPill(icon: ImageVector, label: String, tint: Color, isDark: Boolean, onClick: () -> Unit) {
    val cs = MaterialTheme.colorScheme
    GlassSurface(
        shape = KaizenShapes.pill,
        tintAlpha = GlassTiers.pill(isDark),
        shadowStack = KaizenShadows.level2,
        cornerRadius = 24.dp,
        modifier = Modifier.clickable(onClick = onClick),
    ) {
        Row(
            Modifier.padding(start = 6.dp, end = 16.dp, top = 6.dp, bottom = 6.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Box(
                Modifier.size(36.dp).clip(KaizenShapes.circle).background(tint.copy(alpha = if (isDark) 0.18f else 0.12f)),
                contentAlignment = Alignment.Center,
            ) {
                Icon(icon, null, tint = tint, modifier = Modifier.size(18.dp))
            }
            Spacer(Modifier.width(10.dp))
            Text(label, color = cs.onBackground, fontSize = 14.sp, fontWeight = FontWeight.Medium)
        }
    }
}

@Composable
private fun SendArrow(color: Color) {
    Canvas(Modifier.size(18.dp)) {
        val w = size.width; val h = size.height; val cx = w / 2f; val stroke = w * 0.135f
        drawLine(color, Offset(cx, h * 0.82f), Offset(cx, h * 0.20f), stroke, StrokeCap.Round)
        drawLine(color, Offset(cx, h * 0.18f), Offset(w * 0.28f, h * 0.46f), stroke, StrokeCap.Round)
        drawLine(color, Offset(cx, h * 0.18f), Offset(w * 0.72f, h * 0.46f), stroke, StrokeCap.Round)
    }
}
