package com.optix.android.ui.screens.lanmap

import android.app.Activity
import android.content.Intent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.*
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.shape.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.draw.*
import androidx.compose.ui.focus.*
import androidx.compose.ui.input.key.*
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.*
import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.*
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.*
import androidx.hilt.navigation.compose.hiltViewModel
import com.optix.android.domain.model.HostResult
import com.optix.android.ui.components.ToolPickerSheet
import com.optix.android.ui.navigation.Screen
import com.optix.android.ui.screens.settings.NetworkSettingsSheet
import com.optix.android.ui.theme.*
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.*

private data class Vec3(val x: Float, val y: Float, val z: Float)
private fun Vec3.rotate(yaw: Float, pitch: Float): Vec3 {
    val cy = cos(yaw); val sy = sin(yaw)
    val x1 = x * cy + z * sy; val z1 = -x * sy + z * cy
    val cp = cos(pitch); val sp = sin(pitch)
    return Vec3(x1, y * cp - z1 * sp, y * sp + z1 * cp)
}
private fun Vec3.project(cx: Float, cy: Float, r: Float): Offset {
    val perspective = (1.18f + (z + 1f) * 0.42f).coerceIn(0.6f, 2.5f)
    val ox = cx + x * r * perspective
    val oy = cy - y * r * perspective
    return Offset(
        if (ox.isNaN() || ox.isInfinite()) cx else ox,
        if (oy.isNaN() || oy.isInfinite()) cy else oy,
    )
}
private fun fibSphere(n: Int): List<Vec3> {
    if (n == 0) return emptyList()
    val g = (1f + sqrt(5f)) / 2f
    return List(n) { i ->
        val t = acos(1f - 2f * (i + 0.5f) / n)
        val p = (2f * PI * i / g).toFloat()
        Vec3(sin(t) * cos(p), cos(t), sin(t) * sin(p))
    }
}

@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
fun LanMapScreen(
    viewModel: LanMapViewModel = hiltViewModel(),
    currentScreen: Screen = Screen.Network,
    runningByRoute: Map<String, Boolean> = emptyMap(),
    onNavigate: (Screen) -> Unit = {},
) {
    val state by viewModel.uiState.collectAsState()
    val context = LocalContext.current
    var showToolPicker by remember { mutableStateOf(false) }
    var showNetSettings by remember { mutableStateOf(false) }
    var showHistory by remember { mutableStateOf(false) }
    var showViewOptions by remember { mutableStateOf(false) }
    var showWorkflows by remember { mutableStateOf(false) }
    var showSetup by remember { mutableStateOf(false) }
    val toolPickerSheet = rememberModalBottomSheetState(skipPartiallyExpanded = true)
    val netSettingsSheet = rememberModalBottomSheetState(skipPartiallyExpanded = true)
    val historySheet = rememberModalBottomSheetState(skipPartiallyExpanded = true)
    val workflowSheet = rememberModalBottomSheetState(skipPartiallyExpanded = true)

    // Layout Toggles
    var showGlobe by remember { mutableStateOf(true) }
    var showReadiness by remember { mutableStateOf(false) }

    // Globe rotation state
    var yaw   by remember { mutableFloatStateOf(0.4f) }
    var pitch by remember { mutableFloatStateOf(-0.3f) }
    var velYaw   by remember { mutableFloatStateOf(0.0015f) }
    var velPitch by remember { mutableFloatStateOf(0f) }
    var zoom by remember { mutableFloatStateOf(1f) }
    var isDragging by remember { mutableStateOf(false) }

    val tBackground = T.background
    val tSurface = T.surface
    val tAccent = T.accent

    val tick by rememberInfiniteTransition(label = "tick").animateFloat(
        0f, 1f, infiniteRepeatable(tween(16, easing = LinearEasing)), label = "t"
    )
    LaunchedEffect(tick) {
        if (!isDragging) {
            yaw += velYaw; pitch += velPitch
            pitch = pitch.coerceIn(-PI.toFloat() / 2f + 0.05f, PI.toFloat() / 2f - 0.05f)
            velYaw *= 0.985f; velPitch *= 0.985f
            if (abs(velYaw) < 0.0015f) velYaw = 0.0015f
        }
    }

    // Overlay minimize state
    var isPanelMinimized by remember { mutableStateOf(false) }

    // Focus requester for keyboard navigation
    val focusRequester = remember { FocusRequester() }

    // Auto-scan on startup/active if nodes are empty
    LaunchedEffect(state.nodes) {
        if (state.nodes.isEmpty() && !state.scanning) {
            viewModel.scanLan()
        }
    }

    // Request focus for keyboard navigation
    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }

    // Arrow key navigation calculations
    val spherePositions = remember(state.nodes.size) { fibSphere(state.nodes.size.coerceAtLeast(1)) }
    val rotatedNodes = remember(state.nodes, yaw, pitch) {
        state.nodes.mapIndexed { index, node ->
            val base = spherePositions.getOrElse(index) { Vec3(0f, 1f, 0f) }
            val rot = base.rotate(yaw, pitch)
            node to rot
        }
    }

    val navigateNode = { left: Boolean ->
        val visibleNodes = rotatedNodes.filter { it.first.ip != state.gatewayIp }
        if (visibleNodes.isNotEmpty()) {
            val sorted = visibleNodes.sortedBy { it.second.x }
            val currentIndex = sorted.indexOfFirst { it.first == state.selectedNode }
            val nextIndex = if (currentIndex == -1) {
                0
            } else {
                if (left) {
                    (currentIndex - 1 + sorted.size) % sorted.size
                } else {
                    (currentIndex + 1) % sorted.size
                }
            }
            val targetNode = sorted[nextIndex].first
            viewModel.selectNode(targetNode)
        }
    }

    Column(modifier = Modifier.fillMaxSize().background(tBackground)) {

        // â”€â”€ TOP BAR â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        Row(
            modifier = Modifier.fillMaxWidth().background(tSurface)
                .drawBehind { drawLine(tAccent, Offset(0f, size.height), Offset(size.width, size.height), 1f) }
                .padding(horizontal = 16.dp, vertical = 6.dp),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Column {
                Text("NETWORK WEB", color = T.accent, style = MaterialTheme.typography.titleSmall)
                Text(
                    when {
                        state.scanning -> "SCANNINGâ€¦"
                        state.activeTargetIp != null -> "TARGET: ${state.activeTargetIp}"
                        state.discoveryMode == "WIFI" -> "${state.nodes.size} WIFI NODES  Â·  TAP TO INSPECT"
                        else -> "${state.nodes.size} LAN NODES  Â·  DRAG TO ROTATE"
                    },
                    color = if (state.activeTargetIp != null && !state.scanning) T.accentGlow else T.onSurfaceMid,
                    style = MaterialTheme.typography.labelSmall,
                )
            }
            // Insert Tabs in the middle of Top Bar
            Box(modifier = Modifier.weight(1f).padding(horizontal = 16.dp)) {
                TopScreenTabs(
                    currentScreen = currentScreen,
                    runningByRoute = runningByRoute,
                    onNavigate = onNavigate,
                    modifier = Modifier.fillMaxWidth(),
                )
            }

            Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
                Box(Modifier.size(30.dp).background(T.surfaceVar, RoundedCornerShape(3.dp))
                    .border(1.dp, T.border, RoundedCornerShape(3.dp))
                    .clickable { showHistory = true }, Alignment.Center) {
                    Icon(Icons.Default.History, null, tint = T.onSurfaceMid, modifier = Modifier.size(15.dp))
                }
                Box(Modifier.size(30.dp).background(T.surfaceVar, RoundedCornerShape(3.dp))
                    .border(1.dp, T.border, RoundedCornerShape(3.dp))
                    .clickable { showWorkflows = true }, Alignment.Center) {
                    Icon(Icons.Default.AccountTree, null, tint = T.onSurfaceMid, modifier = Modifier.size(15.dp))
                }
                Box(Modifier.size(30.dp).background(T.surfaceVar, RoundedCornerShape(3.dp))
                    .border(1.dp, T.border, RoundedCornerShape(3.dp))
                    .clickable {
                        context.startActivity(Intent.createChooser(
                            Intent(Intent.ACTION_SEND).apply {
                                type = "application/json"
                                putExtra(Intent.EXTRA_TEXT, viewModel.exportNodeMap())
                            },
                            "Export network map"
                        ))
                    }, Alignment.Center) {
                    Icon(Icons.Default.UploadFile, null, tint = T.onSurfaceMid, modifier = Modifier.size(15.dp))
                }
                if (state.scanning) {
                    val p by rememberInfiniteTransition(label = "sp").animateFloat(
                        0.4f, 1f, infiniteRepeatable(tween(700), RepeatMode.Reverse), label = "sp"
                    )
                    Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(5.dp)) {
                        CircularProgressIndicator(Modifier.size(9.dp), T.success, 1.5.dp)
                        Text("LIVE", color = T.success.copy(alpha = p), style = MaterialTheme.typography.labelSmall)
                    }
                } else {
                    Box(Modifier.background(T.accent.copy(alpha = 0.1f), RoundedCornerShape(3.dp))
                        .border(1.dp, T.accent.copy(alpha = 0.4f), RoundedCornerShape(3.dp))
                        .clickable(onClick = viewModel::scanLan).padding(horizontal = 10.dp, vertical = 5.dp)) {
                        Text("â†º RESCAN", color = T.accent, style = MaterialTheme.typography.labelSmall)
                    }
                }
                Box(Modifier.size(30.dp).background(T.surfaceVar, RoundedCornerShape(3.dp))
                    .border(1.dp, T.border, RoundedCornerShape(3.dp))
                    .clickable { showViewOptions = true }, Alignment.Center) {
                    Icon(Icons.Default.Visibility, null, tint = T.onSurfaceMid, modifier = Modifier.size(15.dp))
                    DropdownMenu(
                        expanded = showViewOptions,
                        onDismissRequest = { showViewOptions = false },
                        modifier = Modifier.background(T.surface).border(1.dp, T.border.copy(alpha = 0.5f))
                    ) {
                        DropdownMenuItem(
                            text = { Text("Node Map", color = if (showGlobe) T.accent else T.onSurface, style = MaterialTheme.typography.labelMedium) },
                            onClick = { showGlobe = !showGlobe }
                        )
                        DropdownMenuItem(
                            text = { Text("Tool Readiness", color = if (showReadiness) T.accent else T.onSurface, style = MaterialTheme.typography.labelMedium) },
                            onClick = { showReadiness = !showReadiness }
                        )
                        DropdownMenuItem(
                            text = { Text("Setup / Permissions", color = if (showSetup) T.accent else T.onSurface, style = MaterialTheme.typography.labelMedium) },
                            onClick = { showSetup = true; showViewOptions = false }
                        )
                    }
                }
                Box(Modifier.size(30.dp).background(T.surfaceVar, RoundedCornerShape(3.dp))
                    .border(1.dp, T.border, RoundedCornerShape(3.dp))
                    .clickable { showNetSettings = true }, Alignment.Center) {
                    Icon(Icons.Default.Settings, null, tint = T.onSurfaceMid, modifier = Modifier.size(15.dp))
                }
            }
        }
        if (showReadiness) {
            ToolReadinessStrip(
                readiness = state.toolReadiness,
                onRefresh = viewModel::refreshToolReadiness,
                modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 2.dp),
            )
        }

        Row(
            modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 2.dp),
            horizontalArrangement = Arrangement.End,
        ) {
            ResultsDropdown(
                nodes = state.nodes,
                gatewayIp = state.gatewayIp,
                onSelectNode = { node -> viewModel.selectNode(node) },
                onSetTarget = { node -> viewModel.setActiveTarget(node); viewModel.selectNode(null) },
                onSelectTool = { toolName, ip -> viewModel.setActiveTarget(ip); viewModel.selectTool(toolName) },
                modifier = Modifier,
            )
        }

        AnimatedVisibility(visible = state.activeChain != null) {
            state.activeChain?.let { chain ->
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .background(T.accent.copy(alpha = 0.12f))
                        .border(1.dp, T.accent.copy(alpha = 0.4f), RoundedCornerShape(8.dp))
                        .padding(horizontal = 12.dp, vertical = 8.dp),
                    verticalAlignment = Alignment.CenterVertically,
                    horizontalArrangement = Arrangement.spacedBy(10.dp),
                ) {
                    CircularProgressIndicator(
                        modifier = Modifier.size(16.dp),
                        color = T.accent,
                        strokeWidth = 2.dp,
                    )
                    Column(modifier = Modifier.weight(1f)) {
                        Text(
                            "CHAIN ${chain.name}  -  ${state.chainStatus ?: ""}",
                            color = T.accent,
                            style = MaterialTheme.typography.labelSmall,
                        )
                        Text(
                            "Step ${state.chainStepIndex.coerceAtMost(chain.steps.size)}/${chain.steps.size}",
                            color = T.onSurfaceMid,
                            style = MaterialTheme.typography.labelSmall,
                        )
                    }
                    Text(
                        "CANCEL",
                        color = T.warning,
                        style = MaterialTheme.typography.labelSmall,
                        modifier = Modifier.clickable { viewModel.cancelChain() },
                    )
                }
            }
        }

        // GLOBE & FLOATING OVERLAY
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
                .padding(horizontal = 10.dp, vertical = 6.dp)
                .background(T.surface, RoundedCornerShape(18.dp))
                .border(1.dp, T.border.copy(alpha = 0.5f), RoundedCornerShape(18.dp))
                .focusRequester(focusRequester)
                .focusable()
                .onKeyEvent { keyEvent ->
                    if (keyEvent.type == KeyEventType.KeyDown) {
                        when (keyEvent.key) {
                            Key.DirectionLeft -> {
                                navigateNode(true)
                                true
                            }
                            Key.DirectionRight -> {
                                navigateNode(false)
                                true
                            }
                            else -> false
                        }
                    } else false
                },
        ) {
            GlobeCanvas(
                nodes = state.nodes, scanning = state.scanning,
                selectedNode = state.selectedNode,
                portScanning = state.portScanning,
                portScanProgress = state.portScanProgress,
                selfIp = state.selfIp, gatewayIp = state.gatewayIp,
                activeTarget = state.activeTargetIp,
                yaw = yaw, pitch = pitch, zoom = zoom,
                onNodeTap = { node -> viewModel.selectNode(if (node == state.selectedNode) null else node) },
                onBackgroundTap = {
                    viewModel.selectNode(null)
                    isPanelMinimized = true
                },
                onDrag = { dYaw, dPitch ->
                    isDragging = true; velYaw = dYaw; velPitch = dPitch
                    yaw += dYaw * 3f
                    pitch = (pitch + dPitch * 3f).coerceIn(-PI.toFloat() / 2f + 0.05f, PI.toFloat() / 2f - 0.05f)
                },
                onDragEnd = { isDragging = false },
                onZoomChange = { zoom = it },
                modifier = Modifier.fillMaxSize(),
            )
            if (state.scanning) {
                val p by rememberInfiniteTransition(label = "sb").animateFloat(
                    0.3f, 1f, infiniteRepeatable(tween(600), RepeatMode.Reverse), label = "sbp"
                )
                Row(
                    modifier = Modifier.align(Alignment.TopEnd).padding(8.dp)
                        .background(T.background.copy(alpha = 0.8f), RoundedCornerShape(4.dp))
                        .border(1.dp, T.success.copy(alpha = 0.3f), RoundedCornerShape(4.dp))
                        .padding(horizontal = 8.dp, vertical = 4.dp),
                    verticalAlignment = Alignment.CenterVertically,
                    horizontalArrangement = Arrangement.spacedBy(5.dp),
                ) {
                    CircularProgressIndicator(Modifier.size(8.dp), T.success, 1.5.dp)
                    Text("SCANNING", color = T.success.copy(alpha = p), style = MaterialTheme.typography.labelSmall)
                }
            }
            CornerZoomButton(
                label = "-",
                onClick = { zoom = (zoom - 0.16f).coerceIn(0.45f, 2.8f) },
                modifier = Modifier.align(Alignment.BottomStart).padding(10.dp),
            )
            CornerZoomButton(
                label = "+",
                onClick = { zoom = (zoom + 0.16f).coerceIn(0.45f, 2.8f) },
                modifier = Modifier.align(Alignment.BottomEnd).padding(10.dp),
            )

        } // End of Globe Box

        // â”€â”€ STATIC SPLIT WINDOW (always visible at the bottom) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        BottomControlPanel(
            state = state,
            onPickTool = { showToolPicker = true },
            onParamChange = viewModel::updateParam,
            onRun = viewModel::runTool,
            onCancel = viewModel::cancelTool,
            onClearTarget = viewModel::clearActiveTarget,
            onClearLogs = viewModel::clearGlobalLogs,
            onShareResult = { text ->
                context.startActivity(Intent.createChooser(
                    Intent(Intent.ACTION_SEND).apply { type = "text/plain"; putExtra(Intent.EXTRA_TEXT, text) }, "Share"
                ))
            },
            onResultAction = { action -> viewModel.runAction(action.toolSlug, action.params) },
            onRunChain = viewModel::runSuggestedChain,
            onDismissChain = viewModel::clearSuggestedChain,
        )
    } // End of Screen Column

    if (showToolPicker) {
        ModalBottomSheet(onDismissRequest = { showToolPicker = false }, sheetState = toolPickerSheet, containerColor = T.surface) {
            ToolPickerSheet(
                toolsByCategory = viewModel.toolsByCategory,
                selectedTool = state.selectedTool,
                favorites = state.favorites,
                onToolSelected = { tool: com.optix.android.domain.model.ToolMetadata -> viewModel.selectTool(tool.name); showToolPicker = false },
                onToggleFavorite = viewModel::toggleFavorite,
            )
        }
    }
    if (showNetSettings) {
        ModalBottomSheet(onDismissRequest = { showNetSettings = false }, sheetState = netSettingsSheet, containerColor = T.surface) {
            NetworkSettingsSheet(onDismiss = { showNetSettings = false })
        }
    }
    if (showSetup) {
        SetupSheet(
            bootstrapStatus = state.bootstrapStatus,
            readiness = state.toolReadiness,
            onBootstrap = viewModel::bootstrapCapabilities,
            onDismiss = viewModel::dismissBootstrap,
            onClose = { showSetup = false },
        )
    }
    if (showHistory) {
        ModalBottomSheet(onDismissRequest = { showHistory = false }, sheetState = historySheet, containerColor = T.surface) {
            ScanHistorySheet(
                sessions = state.recentSessions,
                onShare = { session ->
                    context.startActivity(Intent.createChooser(
                        Intent(Intent.ACTION_SEND).apply {
                            type = "application/json"
                            putExtra(Intent.EXTRA_TEXT, session.results.joinToString("\n"))
                        },
                        "Share session"
                    ))
                },
                onExport = { format ->
                    viewModel.exportSessions(format)
                    showHistory = false
                },
            )
        }
    }
    if (showWorkflows) {
        ModalBottomSheet(onDismissRequest = { showWorkflows = false }, sheetState = workflowSheet, containerColor = T.surface) {
            WorkflowSheet(
                state = state,
                onSaveCurrent = { name ->
                    val step = viewModel.currentStepFromSelection()
                    viewModel.saveWorkflow(name, if (step != null) listOf(step) else emptyList())
                },
                onSaveSuggested = { name ->
                    val steps = viewModel.stepsFromSuggestedChain()
                    viewModel.saveWorkflow(name, steps ?: emptyList())
                },
                onRun = { id -> viewModel.runWorkflow(id); showWorkflows = false },
                onDelete = viewModel::deleteWorkflow,
                onDismiss = { showWorkflows = false },
            )
        }
    }
    state.exportResult?.let { result ->
        LaunchedEffect(result) {
            context.startActivity(Intent.createChooser(
                Intent(Intent.ACTION_SEND).apply {
                    type = "text/plain"
                    putExtra(Intent.EXTRA_TEXT, result)
                },
                "Export sessions"
            ))
            viewModel.clearExport()
        }
    }
    state.pendingInstall?.let { (toolName, aptPackage) ->
        AlertDialog(
            onDismissRequest = viewModel::dismissInstall, containerColor = T.surface,
            title = { Text("Install Required", color = T.accent, style = MaterialTheme.typography.titleMedium) },
            text = {
                Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
                    Text("'$toolName' not found in Termux.", color = T.onSurface, style = MaterialTheme.typography.bodyMedium)
                    Text("pkg install $aptPackage", color = T.warning, style = MaterialTheme.typography.bodySmall)
                }
            },
            confirmButton = {
                Button(onClick = viewModel::confirmInstall,
                    colors = ButtonDefaults.buttonColors(containerColor = T.accent, contentColor = if (T.isDark) Color.Black else Color.White),
                    shape = RoundedCornerShape(3.dp)) { Text("Install & Run") }
            },
            dismissButton = {
                Button(onClick = viewModel::dismissInstall,
                    colors = ButtonDefaults.buttonColors(containerColor = T.surfaceVar, contentColor = T.onSurface),
                    shape = RoundedCornerShape(3.dp)) { Text("Cancel") }
            },
        )
    }
    state.pendingEthicalAck?.let { tool ->
        AlertDialog(
            onDismissRequest = viewModel::dismissEthicalAck, containerColor = T.surface,
            title = { Text("Ethical-Use Confirmation", color = T.warning, style = MaterialTheme.typography.titleMedium) },
            text = {
                Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
                    Text(
                        "You are about to run '${tool.displayName}' against a live target.",
                        color = T.onSurface, style = MaterialTheme.typography.bodyMedium,
                    )
                    Text(
                        "I confirm that I have explicit, authorized permission to test the selected target(s). Unauthorized scanning or testing is illegal.",
                        color = T.onSurfaceMid, style = MaterialTheme.typography.bodySmall,
                    )
                }
            },
            confirmButton = {
                Button(onClick = viewModel::confirmEthicalAck,
                    colors = ButtonDefaults.buttonColors(containerColor = T.accent, contentColor = if (T.isDark) Color.Black else Color.White),
                    shape = RoundedCornerShape(3.dp)) { Text("I Have Authorization") }
            },
            dismissButton = {
                Button(onClick = viewModel::dismissEthicalAck,
                    colors = ButtonDefaults.buttonColors(containerColor = T.surfaceVar, contentColor = T.onSurface),
                    shape = RoundedCornerShape(3.dp)) { Text("Cancel") }
            },
        )
    }
}

// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
// 3D Globe Canvas
// â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

@Composable
private fun GlobeCanvas(
    nodes: List<HostResult>, scanning: Boolean, selectedNode: HostResult?,
    portScanning: Set<String>, portScanProgress: Map<String, Pair<Int, Int>>,
    selfIp: String?, gatewayIp: String?, activeTarget: String?,
    yaw: Float, pitch: Float, zoom: Float,
    onNodeTap: (HostResult) -> Unit,
    onBackgroundTap: () -> Unit,
    onDrag: (Float, Float) -> Unit, onDragEnd: () -> Unit,
    onZoomChange: (Float) -> Unit,
    modifier: Modifier = Modifier,
) {
    val pulse by rememberInfiniteTransition(label = "pu").animateFloat(
        0f, 1f, infiniteRepeatable(tween(2600, easing = FastOutSlowInEasing)), label = "pu"
    )
    val scanSpin by rememberInfiniteTransition(label = "sc").animateFloat(
        0f, 1f, infiniteRepeatable(tween(1200, easing = LinearEasing)), label = "sc"
    )
    val time by rememberInfiniteTransition(label = "tm").animateFloat(
        0f, 6.2832f, infiniteRepeatable(tween(14000, easing = LinearEasing)), label = "tm"
    )

    val spherePositions = remember(nodes.size) { fibSphere(nodes.size.coerceAtLeast(1)) }
    val screenPositions = remember { mutableStateListOf<Pair<Offset, HostResult>>() }
    val tapRipples = remember { mutableStateListOf<Pair<Offset, Long>>() }
    val textMeasurer = rememberTextMeasurer()
    val ipColor = T.textAccent
    val ipStyle = TextStyle(fontFamily = FontFamily.Monospace, fontSize = 8.5.sp, color = ipColor.copy(alpha = 0.9f))
    val ipStyleSel = ipStyle.copy(color = T.accentGlow, fontWeight = FontWeight.SemiBold)
    // Capture theme colors before entering DrawScope
    val cSurface = if (T.isDark) T.surface else Color(0xFFF4F7FB)
    val cBackground = if (T.isDark) T.background else Color(0xFFEAF1F8)
    val cGraphics = if (T.isDark) T.graphics else Color(0xFF5B7FA6)
    val cAccent = T.accent
    val cAccentGlow = T.accentGlow
    val cSuccess = T.success
    val cWarning = T.warning
    val cOnSurfaceMid = T.onSurfaceMid
    val cTextAccent = T.textAccent

    Canvas(
        modifier = modifier
            .pointerInput(nodes, zoom) {
                detectTransformGestures { _, pan, gestureZoom, _ ->
                    if (gestureZoom != 1f) onZoomChange((zoom * gestureZoom).coerceIn(0.45f, 2.8f))
                    if (pan != Offset.Zero && size.width > 0 && size.height > 0) {
                        onDrag(pan.x / size.width * 2.5f, -pan.y / size.height * 2.5f)
                    }
                }
            }
            .pointerInput(nodes, zoom) {
                val tapThreshold = 18.dp.toPx()
                val pickRadius = 40.dp.toPx()
                awaitEachGesture {
                    val down = awaitFirstDown(requireUnconsumed = false)
                    var last = down.position
                    var draggedDistance = 0f
                    var released = false

                    while (true) {
                        val event = awaitPointerEvent()
                        val change = event.changes.firstOrNull { it.id == down.id } ?: break
                        if (change.changedToUpIgnoreConsumed()) {
                            released = true
                            break
                        }
                        val delta = change.position - last
                        if (delta != Offset.Zero) {
                            draggedDistance += delta.getDistance()
                            if (size.width > 0 && size.height > 0) {
                                onDrag(delta.x / size.width * 2.5f, -delta.y / size.height * 2.5f)
                            }
                            last = change.position
                            change.consume()
                        }
                    }
                    onDragEnd()
                    if (released && draggedDistance < tapThreshold) {
                        // Clean up old tap ripples to avoid list growing indefinitely
                        val current = System.currentTimeMillis()
                        tapRipples.removeAll { current - it.second > 1000 }

                        screenPositions.minByOrNull { (p, _) -> (down.position - p).getDistance() }
                            ?.takeIf { (p, _) -> (down.position - p).getDistance() < pickRadius }
                            ?.let {
                                tapRipples.add(down.position to current)
                                onNodeTap(it.second)
                            }
                            ?: run {
                                tapRipples.add(down.position to current)
                                onBackgroundTap()
                            }
                    }
                }
            }
    ) {
        val W = size.width; val H = size.height
        if (W <= 0f || H <= 0f) return@Canvas
        val cx = W / 2f; val cy = H / 2f
        val safeZoom = zoom.coerceIn(0.45f, 2.8f).let { if (it.isNaN() || it.isInfinite()) 1f else it }
        val globeR = (minOf(W, H) * 0.22f * safeZoom).coerceIn(10f, minOf(W, H) * 0.62f)
        val globeRx = globeR
        val globeRy = globeR

        drawRect(Brush.radialGradient(listOf(cSurface.copy(alpha = 0.8f), cBackground),
            Offset(cx, cy * 0.7f), maxOf(W, H) * 0.9f))

        drawCircle(
            Brush.radialGradient(
                0f to cGraphics.copy(alpha = 0.05f),
                0.72f to cGraphics.copy(alpha = 0.09f),
                1f to Color.Transparent,
                center = Offset(cx, cy),
                radius = globeR * 1.55f,
            ),
            globeR * 1.55f,
            Offset(cx, cy),
        )
        drawCircle(Brush.radialGradient(0f to Color.Transparent, 0.78f to Color.Transparent,
            0.90f to cGraphics.copy(alpha = 0.07f), 1f to cGraphics.copy(alpha = 0.22f),
            center = Offset(cx, cy), radius = globeR), globeR, Offset(cx, cy))

        for (latIdx in -8..8) {
            val la = latIdx * PI.toFloat() / 18f
            val cosL = cos(la); val sinL = sin(la)
            val path = Path(); var first = true
            for (step in 0..96) {
                val lon = step * 2f * PI.toFloat() / 96f
                val v = Vec3(cosL * cos(lon), sinL, cosL * sin(lon)).rotate(yaw, pitch)
                val sp = v.project(cx, cy, globeR)
                if (v.z < -0.24f) { first = true; continue }
                if (first) { path.moveTo(sp.x, sp.y); first = false } else path.lineTo(sp.x, sp.y)
            }
            drawPath(path, cGraphics.copy(alpha = if (latIdx == 0) 0.12f else 0.05f),
                style = Stroke(if (latIdx == 0) 1f else 0.4f))
        }
        for (lonIdx in 0 until 24) {
            val lon = lonIdx * PI.toFloat() / 12f
            val path = Path(); var first = true
            for (step in 0..96) {
                val lat = step * PI.toFloat() / 96f - PI.toFloat() / 2f
                val v = Vec3(cos(lat) * cos(lon), sin(lat), cos(lat) * sin(lon)).rotate(yaw, pitch)
                val sp = v.project(cx, cy, globeR)
                if (v.z < -0.24f) { first = true; continue }
                if (first) { path.moveTo(sp.x, sp.y); first = false } else path.lineTo(sp.x, sp.y)
            }
            drawPath(path, cGraphics.copy(alpha = 0.04f), style = Stroke(0.4f))
        }
        drawCircle(cGraphics.copy(alpha = 0.28f), globeR, Offset(cx, cy), style = Stroke(1.3f))

        if (scanning) {
            val sweepAngle = time / 6.2832f * 360f
            drawArc(Brush.sweepGradient(0f to Color.Transparent, 0.78f to cGraphics.copy(alpha = 0f),
                1f to cGraphics.copy(alpha = 0.20f), center = Offset(cx, cy)),
                sweepAngle - 90f, 75f, true,
                Offset(cx - globeR, cy - globeR * 0.38f),
                androidx.compose.ui.geometry.Size(globeR * 2f, globeR * 0.76f))
            drawArc(Brush.sweepGradient(0f to Color.Transparent, 0.82f to cSuccess.copy(alpha = 0f),
                1f to cSuccess.copy(alpha = 0.28f), center = Offset(cx, cy)),
                scanSpin * 360f - 90f, 55f, true,
                Offset(cx - globeR, cy - globeR * 0.38f),
                androidx.compose.ui.geometry.Size(globeR * 2f, globeR * 0.76f))
        }
        drawCircle(cGraphics.copy(alpha = (1f - pulse) * 0.14f),
            globeR * (0.92f + pulse * 0.22f), Offset(cx, cy), style = Stroke(1.5f))

        val gwColor = cWarning
        drawCircle(gwColor.copy(alpha = 0.10f), 26.dp.toPx(), Offset(cx, cy))
        drawCircle(gwColor.copy(alpha = 0.30f), 16.dp.toPx(), Offset(cx, cy))
        drawCircle(gwColor.copy(alpha = 0.70f), 9.dp.toPx(), Offset(cx, cy))
        drawCircle(gwColor, 5.dp.toPx(), Offset(cx, cy))
        val centerLabel = if (gatewayIp != null) "GW  $gatewayIp" else if (selfIp != null) "YOU  $selfIp" else null
        centerLabel?.let {
            val m = textMeasurer.measure(it, ipStyle.copy(color = gwColor, fontSize = 7.5.sp))
            val lx = cx - m.size.width / 2f; val ly = cy + 30.dp.toPx()
            drawRoundRect(cBackground.copy(alpha = 0.9f), Offset(lx - 4.dp.toPx(), ly - 2.dp.toPx()),
                androidx.compose.ui.geometry.Size(m.size.width + 8.dp.toPx(), m.size.height + 4.dp.toPx()),
                androidx.compose.ui.geometry.CornerRadius(3.dp.toPx()))
            drawText(textMeasurer, it, Offset(lx, ly), ipStyle.copy(color = gwColor, fontSize = 7.5.sp))
        }

        if (!scanning && nodes.isEmpty()) {
            val emptyText = "NO HOSTS FOUND  Â·  TAP RESCAN"
            val emptyStyle = ipStyle.copy(color = cAccentGlow.copy(alpha = 0.9f), fontSize = 10.sp, fontWeight = FontWeight.Bold)
            val emptyMeasure = textMeasurer.measure(emptyText, emptyStyle)
            drawRoundRect(
                cBackground.copy(alpha = 0.86f),
                Offset(cx - emptyMeasure.size.width / 2f - 10.dp.toPx(), cy - emptyMeasure.size.height / 2f - 6.dp.toPx()),
                androidx.compose.ui.geometry.Size(emptyMeasure.size.width + 20.dp.toPx(), emptyMeasure.size.height + 12.dp.toPx()),
                androidx.compose.ui.geometry.CornerRadius(8.dp.toPx()),
            )
            drawText(textMeasurer, emptyText, Offset(cx - emptyMeasure.size.width / 2f, cy - emptyMeasure.size.height / 2f), emptyStyle)
        }

        screenPositions.clear()
        val now = System.currentTimeMillis()
        tapRipples.forEach { (pos, time) ->
            val age = ((now - time) / 500f).coerceIn(0f, 1f)
            if (age < 1f) {
                drawCircle(cAccentGlow.copy(alpha = (1f - age) * 0.18f), 12.dp.toPx() + age * 42.dp.toPx(), pos, style = Stroke(1.6f))
            }
        }
        val display = nodes.filter { it.ip != gatewayIp }.take(24)
        val subnetPalette = listOf(Color(0xFF4FC3F7), Color(0xFF81C784), Color(0xFFFFB74D), Color(0xFFBA68C8), Color(0xFFE57373), Color(0xFF4DD0E1))
        fun subnetKeyOf(ip: String): String = ip.substringBeforeLast('.', "")
        fun subnetColorOf(idx: Int): Color = subnetPalette[idx % subnetPalette.size]
        fun blendCol(a: Color, b: Color, r: Float = 0.15f): Color = Color(a.red * (1f - r) + b.red * r, a.green * (1f - r) + b.green * r, a.blue * (1f - r) + b.blue * r, a.alpha)

        val subnetOrder = display.map { subnetKeyOf(it.ip) }.distinct().filter { it.isNotBlank() }

        data class Entry(val host: HostResult, val rotated: Vec3, val screen: Offset, val depth: Float, val subnetIndex: Int)
        val entries = display.mapIndexed { i, host ->
            val base = spherePositions.getOrElse(i) { Vec3(0f, 1f, 0f) }
            val rot = base.rotate(yaw, pitch)
            val si = subnetOrder.indexOf(subnetKeyOf(host.ip)).coerceAtLeast(0)
            val orbitR = (globeR * (1.02f + si * 0.09f + (safeZoom - 1f).coerceAtLeast(0f) * 0.95f)).coerceIn(10f, minOf(W, H))
            Entry(host, rot, rot.project(cx, cy, orbitR), rot.z, si)
        }.sortedBy { it.depth }

        subnetOrder.forEachIndexed { index, _ -> drawCircle(subnetColorOf(index).copy(alpha = 0.08f), globeR * (1.02f + index * 0.09f), Offset(cx, cy), style = Stroke(1f)) }

        for (i in entries.indices) {
            val a = entries[i]
            for (j in i + 1 until entries.size) {
                val b = entries[j]
                val d = sqrt((a.rotated.x - b.rotated.x).pow(2) + (a.rotated.y - b.rotated.y).pow(2) + (a.rotated.z - b.rotated.z).pow(2))
                if (d < 0.92f) drawLine(cGraphics.copy(alpha = ((2f - d) / 2f).coerceIn(0.05f, 0.22f) * ((a.depth + b.depth + 2f) / 4f).coerceIn(0.15f, 1f)), a.screen, b.screen, 0.8f)
            }
        }

        entries.forEach { e ->
            val isSel = e.host == selectedNode; val isTgt = e.host.ip == activeTarget
            val col = blendCol(nodeColorPure(e.host, selectedNode, portScanning, gatewayIp, activeTarget, cAccentGlow, cSuccess, cWarning, cAccent, cOnSurfaceMid), subnetColorOf(e.subnetIndex))
            val da = ((e.depth + 1f) / 2f).coerceIn(0.1f, 1f)
            drawLine(col.copy(alpha = (if (isSel || isTgt) 0.55f else 0.15f) * da), Offset(cx, cy), e.screen, if (isSel || isTgt) 1.8f else 0.8f)
            val lineLen = (e.screen - Offset(cx, cy)).getDistance()
            if (lineLen > 0) {
                val dir = (e.screen - Offset(cx, cy)) / lineLen
                val t1 = ((time / 6.2832f + e.host.ip.hashCode() * 0.1f) % 1f).let { if (it < 0) it + 1f else it }
                drawCircle(col.copy(alpha = 0.75f * da), 3f, Offset(cx, cy) + dir * (lineLen * t1))
            }
        }

        entries.forEach { e ->
            val host = e.host; val pos = e.screen
            val isSel = host == selectedNode; val isTgt = host.ip == activeTarget
            val isGw = host.ip == gatewayIp; val isScanning = host.ip in portScanning
            val col = blendCol(nodeColorPure(host, selectedNode, portScanning, gatewayIp, activeTarget, cAccentGlow, cSuccess, cWarning, cAccent, cOnSurfaceMid), subnetColorOf(e.subnetIndex))
            val ds = ((e.depth + 1f) / 2f * 0.4f + 0.6f).coerceIn(0.5f, 1f)
            val da = ((e.depth + 1f) / 2f * 0.7f + 0.3f).coerceIn(0.15f, 1f)
            if (e.depth < -0.35f && !isSel && !isTgt) return@forEach

            if (isSel || isTgt) {
                val br = 32.dp.toPx() * ds; val bl = 8.dp.toPx()
                drawCircle(col.copy(alpha = 0.18f * da), br, pos)
                listOf(-1f to -1f, 1f to -1f, -1f to 1f, 1f to 1f).forEach { (sx, sy) ->
                    drawLine(col.copy(alpha = 0.85f), Offset(pos.x + sx * (br - bl), pos.y + sy * br), Offset(pos.x + sx * br, pos.y + sy * br), 1.6f)
                    drawLine(col.copy(alpha = 0.85f), Offset(pos.x + sx * br, pos.y + sy * (br - bl)), Offset(pos.x + sx * br, pos.y + sy * br), 1.6f)
                }
            }
            drawCircle(col.copy(alpha = 0.15f * da), 22.dp.toPx() * ds, pos)
            drawCircle(col.copy(alpha = 0.35f * da), 12.dp.toPx() * ds, pos, style = Stroke(1.2.dp.toPx()))
            drawCircle(col.copy(alpha = 0.85f * da), 7.dp.toPx() * ds, pos)
            drawCircle(Color.White.copy(alpha = da), 2.5.dp.toPx() * ds, pos)

            if (host.openPorts.isNotEmpty()) {
                val badgeText = host.openPorts.size.coerceAtMost(99).toString()
                val badgeStyle = ipStyle.copy(color = cBackground, fontSize = 7.sp, fontWeight = FontWeight.Bold)
                val badgeMeasure = textMeasurer.measure(badgeText, badgeStyle)
                val badgeCenter = Offset(pos.x + 15.dp.toPx() * ds, pos.y - 15.dp.toPx() * ds)
                drawCircle(cSuccess.copy(alpha = 0.95f), 7.dp.toPx() * ds, badgeCenter)
                drawText(textMeasurer, badgeText, Offset(badgeCenter.x - badgeMeasure.size.width / 2f, badgeCenter.y - badgeMeasure.size.height / 2f), badgeStyle)
            }
            if (isScanning) {
                val prog = portScanProgress[host.ip]
                val frac = if (prog != null && prog.second > 0) prog.first.toFloat() / prog.second else scanSpin
                drawCircle(cSuccess.copy(alpha = 0.4f * da), 13.dp.toPx() * ds + frac * 12.dp.toPx(), pos, style = Stroke(1.2f))
            }

            val ta = ((e.depth + 1f) / 2f * 0.6f + 0.4f).coerceIn(0.2f, 1f)
            val isYou = host.ip == selfIp
            val ipText = when {
                isGw -> "GW ${host.ip}"
                isYou -> "*YOU ${host.ip}"
                else -> host.ip
            }
            val hostname = host.alias?.takeIf { it.isNotBlank() }
                ?: host.hostname?.takeIf { it.isNotBlank() && it != host.ip }
                ?: host.deviceType
            val ipStyle2 = if (isSel) ipStyleSel else ipStyle.copy(
                color = when {
                    isGw -> cWarning.copy(alpha = ta)
                    isTgt -> cAccentGlow.copy(alpha = ta)
                    else -> cTextAccent.copy(alpha = ta)
                }
            )
            val hostStyle = ipStyle.copy(color = cOnSurfaceMid.copy(alpha = ta * 0.85f), fontSize = 7.5.sp)
            val ipM = textMeasurer.measure(ipText, ipStyle2)
            val hostM = if (hostname != null) textMeasurer.measure(hostname, hostStyle) else null
            val boxW = maxOf(ipM.size.width, hostM?.size?.width ?: 0).toFloat()
            val boxH = ipM.size.height + (hostM?.size?.height ?: 0).toFloat()
            val tx = pos.x - boxW / 2f
            val ty = pos.y - 22.dp.toPx() * ds - boxH - 6.dp.toPx()
            val showLabel = e.depth > -0.1f && screenPositions.none { (otherPos, _) -> (otherPos - pos).getDistance() < 36.dp.toPx() }
            if (showLabel) {
                drawRoundRect(cBackground.copy(alpha = 0.82f * ta), Offset(tx - 3.dp.toPx(), ty - 2.dp.toPx()), androidx.compose.ui.geometry.Size(boxW + 6.dp.toPx(), boxH + 4.dp.toPx()), androidx.compose.ui.geometry.CornerRadius(4.dp.toPx()))
                if (hostM != null) drawText(textMeasurer, hostname!!, Offset(pos.x - hostM.size.width / 2f, ty), hostStyle)
                drawText(textMeasurer, ipText, Offset(pos.x - ipM.size.width / 2f, ty + (hostM?.size?.height ?: 0).toFloat()), ipStyle2)
            }
            screenPositions.add(pos to host)
        }
    }
}

@Composable
private fun nodeColor(host: HostResult, selected: HostResult?, portScanning: Set<String>, gatewayIp: String?, activeTarget: String?) = when {
    host == selected          -> T.accentGlow
    host.ip == activeTarget   -> T.accentGlow
    host.ip in portScanning   -> T.success
    host.ip == gatewayIp      -> T.warning
    host.openPorts.isNotEmpty() -> T.accent
    else                      -> T.onSurfaceMid.copy(alpha = 0.4f)
}

private fun nodeColorPure(host: HostResult, selected: HostResult?, portScanning: Set<String>, gatewayIp: String?, activeTarget: String?,
    accentGlow: Color, success: Color, warning: Color, accent: Color, onSurfaceMid: Color) = when {
    host == selected            -> accentGlow
    host.ip == activeTarget     -> accentGlow
    host.ip in portScanning     -> success
    host.ip == gatewayIp        -> warning
    host.openPorts.isNotEmpty() -> accent
    else                        -> onSurfaceMid.copy(alpha = 0.4f)
}

// ─────────────────────────────────────────────────────────────────────────────
// Workflow builder sheet — save current/suggested steps, list & run saved chains
// ─────────────────────────────────────────────────────────────────────────────

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun WorkflowSheet(
    state: LanMapUiState,
    onSaveCurrent: (String) -> Unit,
    onSaveSuggested: (String) -> Unit,
    onRun: (Long) -> Unit,
    onDelete: (Long) -> Unit,
    onDismiss: () -> Unit,
) {
    var name by remember { mutableStateOf("") }

    Column(
        modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp),
        verticalArrangement = Arrangement.spacedBy(10.dp),
    ) {
        Text("WORKFLOWS", color = T.accent, style = MaterialTheme.typography.titleMedium)

        state.workflowValidationError?.let {
            Text(it, color = T.warning, style = MaterialTheme.typography.labelSmall)
        }

        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Workflow name", color = T.onSurfaceMid) },
            singleLine = true,
            colors = TextFieldDefaults.colors(
                focusedIndicatorColor = T.accent, cursorColor = T.accent,
                focusedTextColor = T.onSurface, unfocusedTextColor = T.onSurface,
            ),
            modifier = Modifier.fillMaxWidth(),
        )

        Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
            val canSaveCurrent = state.selectedTool != null
            val canSaveSuggested = state.suggestedChain != null
            Button(
                onClick = { onSaveCurrent(name); name = "" },
                enabled = canSaveCurrent && name.isNotBlank(),
                colors = ButtonDefaults.buttonColors(containerColor = T.accent, contentColor = if (T.isDark) Color.Black else Color.White),
                shape = RoundedCornerShape(3.dp),
                modifier = Modifier.weight(1f),
            ) {
                Text("Save current (${state.selectedTool?.name ?: "none"})", style = MaterialTheme.typography.labelSmall)
            }
            Button(
                onClick = { onSaveSuggested(name); name = "" },
                enabled = canSaveSuggested && name.isNotBlank(),
                colors = ButtonDefaults.buttonColors(containerColor = T.surfaceVar, contentColor = T.onSurface),
                shape = RoundedCornerShape(3.dp),
                modifier = Modifier.weight(1f),
            ) {
                Text("Save suggested", style = MaterialTheme.typography.labelSmall)
            }
        }

        Divider(color = T.border.copy(alpha = 0.5f))

        Text("SAVED (${state.workflows.size})", color = T.onSurfaceMid, style = MaterialTheme.typography.labelSmall)
        if (state.workflows.isEmpty()) {
            Text("No workflows saved yet.", color = T.onSurfaceMid, style = MaterialTheme.typography.bodySmall)
        } else {
            LazyColumn(
                verticalArrangement = Arrangement.spacedBy(8.dp),
                modifier = Modifier.fillMaxWidth().heightIn(max = 320.dp),
            ) {
                items(state.workflows, key = { it.id }) { wf ->
                    Row(
                        modifier = Modifier.fillMaxWidth()
                            .background(T.surfaceVar, RoundedCornerShape(4.dp))
                            .border(1.dp, T.border.copy(alpha = 0.5f), RoundedCornerShape(4.dp))
                            .padding(10.dp),
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.SpaceBetween,
                    ) {
                        Column(modifier = Modifier.weight(1f)) {
                            Text(wf.name, color = T.onSurface, style = MaterialTheme.typography.bodyMedium)
                            Text(
                                wf.steps.joinToString(" → ") { it.toolSlug },
                                color = T.onSurfaceMid, style = MaterialTheme.typography.labelSmall,
                            )
                        }
                        Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
                            IconButton(onClick = { onRun(wf.id) }, modifier = Modifier.size(32.dp)) {
                                Icon(Icons.Default.PlayArrow, null, tint = T.success, modifier = Modifier.size(18.dp))
                            }
                            IconButton(onClick = { onDelete(wf.id) }, modifier = Modifier.size(32.dp)) {
                                Icon(Icons.Default.Delete, null, tint = T.warning, modifier = Modifier.size(18.dp))
                            }
                        }
                    }
                }
            }
        }

        Button(
            onClick = onDismiss,
            colors = ButtonDefaults.buttonColors(containerColor = T.surfaceVar, contentColor = T.onSurface),
            shape = RoundedCornerShape(3.dp),
            modifier = Modifier.fillMaxWidth(),
        ) { Text("Close") }
    }
}
