@file:OptIn(ExperimentalMaterial3Api::class)

package wang.yaojia.webterm.screens

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SwipeToDismissBox
import androidx.compose.material3.SwipeToDismissBoxValue
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
import wang.yaojia.webterm.components.SessionListRow
import wang.yaojia.webterm.components.SessionThumbnails
import wang.yaojia.webterm.designsystem.Radius
import wang.yaojia.webterm.designsystem.Spacing
import wang.yaojia.webterm.viewmodels.HostOption
import wang.yaojia.webterm.viewmodels.SessionListUiState
import wang.yaojia.webterm.viewmodels.SessionListViewModel
import wang.yaojia.webterm.viewmodels.SessionRow
import java.util.UUID

/**
 * # SessionListScreen (A20) — the home session chooser + dashboard + host menu.
 *
 * The Compose shell over [SessionListViewModel]. It is the app's landing surface (plan §1: home is a
 * session chooser, not an auto-tab) AND its only settings surface — the top-bar overflow is the host
 * menu (multi-host switch with a checkmark, plus **配对新主机** → A19 pairing and **设备证书** → A27
 * cert screen).
 *
 * ### What it wires (all logic lives in the JVM-tested ViewModel)
 *  - **STARTED-scoped poll** (plan §6.6): [SessionListViewModel.poll] runs inside
 *    `repeatOnLifecycle(STARTED)`, so `GET /live-sessions` polling stops the instant the app backgrounds
 *    and resumes on return — never in the background.
 *  - **Rows** (§1): status badge · telemetry chips · preview thumbnail · cols×rows · sanitized title ·
 *    unread dot — rendered by [SessionListRow]; opening a row clears its dot ([SessionListViewModel.markSeen]).
 *  - **Swipe-to-kill**: a trailing `SwipeToDismissBox` calls [SessionListViewModel.killSession]
 *    (optimistic remove; 204 and 404 = success; a real failure restores the row).
 *  - **Pull-to-refresh**: a `PullToRefreshBox` drives [SessionListViewModel.refresh].
 *
 * Every server-influenced string on this screen (titles, cwd, telemetry) is rendered inert by
 * [SessionListRow] (plan §8). All Compose / gesture / thumbnail behaviour is device-QA (plan §7).
 *
 * @param onOpenSession open the terminal (A21) for a tapped session id (its dot is cleared first).
 * @param onNewSession start a new session on the active host (`attach(null)`; A21 owns the terminal).
 * @param onPairHost host-menu **配对新主机** → the pairing flow (A19).
 * @param onEnroll host-menu **自动获取证书** → the zero-`.p12` enrollment screen (B4).
 * @param onImportCert host-menu **设备证书** → the device-cert screen (A27).
 * @param thumbnails off-screen preview seam; production wires it to the active host's
 *   [ThumbnailPipeline][wang.yaojia.webterm.wiring.ThumbnailPipeline] (§6.7). `null` = placeholder tiles.
 */
@Composable
public fun SessionListScreen(
    viewModel: SessionListViewModel,
    onOpenSession: (UUID) -> Unit,
    onNewSession: () -> Unit,
    onPairHost: () -> Unit,
    onEnroll: () -> Unit,
    onImportCert: () -> Unit,
    modifier: Modifier = Modifier,
    thumbnails: SessionThumbnails? = null,
) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()
    val scope = rememberCoroutineScope()
    val lifecycleOwner = LocalLifecycleOwner.current

    // STARTED-scoped poll: fetch→wait→repeat while foregrounded, cancelled on background (plan §6.6).
    LaunchedEffect(viewModel, lifecycleOwner) {
        lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.poll()
        }
    }

    Scaffold(
        modifier = modifier,
        topBar = {
            SessionListTopBar(
                state = state,
                onSelectHost = { id -> scope.launch { viewModel.selectHost(id) } },
                onNewSession = onNewSession,
                onPairHost = onPairHost,
                onEnroll = onEnroll,
                onImportCert = onImportCert,
            )
        },
    ) { padding ->
        SessionListBody(
            state = state,
            contentPadding = padding,
            onRefresh = { scope.launch { viewModel.refresh() } },
            onOpen = { id ->
                scope.launch { viewModel.markSeen(id) }
                onOpenSession(id)
            },
            onKill = { id -> scope.launch { viewModel.killSession(id) } },
            onNewSession = onNewSession,
            onPairHost = onPairHost,
            thumbnails = thumbnails,
        )
    }
}

// ── Top bar + host menu ──────────────────────────────────────────────────────────────────────────────

@Composable
private fun SessionListTopBar(
    state: SessionListUiState,
    onSelectHost: (String) -> Unit,
    onNewSession: () -> Unit,
    onPairHost: () -> Unit,
    onEnroll: () -> Unit,
    onImportCert: () -> Unit,
) {
    var menuOpen by remember { mutableStateOf(false) }
    val activeName = state.hosts.firstOrNull { it.isActive }?.name ?: "会话"

    TopAppBar(
        title = { Text(text = activeName, maxLines = 1, overflow = TextOverflow.Ellipsis) },
        actions = {
            TextButton(onClick = onNewSession) { Text("＋ 新会话") }
            Box {
                // "⋮" text glyph avoids a material-icons dependency (not in the :app Compose bundle).
                TextButton(onClick = { menuOpen = true }) { Text("⋮") }
                HostMenu(
                    expanded = menuOpen,
                    hosts = state.hosts,
                    onDismiss = { menuOpen = false },
                    onSelectHost = { menuOpen = false; onSelectHost(it) },
                    onPairHost = { menuOpen = false; onPairHost() },
                    onEnroll = { menuOpen = false; onEnroll() },
                    onImportCert = { menuOpen = false; onImportCert() },
                )
            }
        },
    )
}

/** The host menu: paired hosts (✓ on the active one, 🔒 when a device cert is installed) + the actions. */
@Composable
private fun HostMenu(
    expanded: Boolean,
    hosts: List<HostOption>,
    onDismiss: () -> Unit,
    onSelectHost: (String) -> Unit,
    onPairHost: () -> Unit,
    onEnroll: () -> Unit,
    onImportCert: () -> Unit,
) {
    DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) {
        for (host in hosts) {
            DropdownMenuItem(
                text = { Text(text = host.name, maxLines = 1, overflow = TextOverflow.Ellipsis) },
                onClick = { onSelectHost(host.id) },
                leadingIcon = if (host.isActive) ({ Text("✓") }) else null,
                trailingIcon = if (host.hasDeviceCert) ({ Text("🔒") }) else null,
            )
        }
        if (hosts.isNotEmpty()) HorizontalDivider()
        DropdownMenuItem(text = { Text("配对新主机") }, onClick = onPairHost)
        // 自动获取证书 (zero-.p12 enroll) sits beside 设备证书 (.p12 import) — same host-menu surface as iOS.
        DropdownMenuItem(text = { Text("自动获取证书") }, onClick = onEnroll)
        DropdownMenuItem(text = { Text("设备证书") }, onClick = onImportCert)
    }
}

// ── Body: pull-to-refresh over the list / empty states ─────────────────────────────────────────────────

@Composable
private fun SessionListBody(
    state: SessionListUiState,
    contentPadding: PaddingValues,
    onRefresh: () -> Unit,
    onOpen: (UUID) -> Unit,
    onKill: (UUID) -> Unit,
    onNewSession: () -> Unit,
    onPairHost: () -> Unit,
    thumbnails: SessionThumbnails?,
) {
    androidx.compose.material3.pulltorefresh.PullToRefreshBox(
        isRefreshing = state.isRefreshing,
        onRefresh = onRefresh,
        modifier = Modifier.fillMaxSize().padding(contentPadding),
    ) {
        when {
            state.activeHostId == null ->
                EmptyState(message = "还没有配对主机。", actionLabel = "配对主机", onAction = onPairHost)
            state.rows.isEmpty() ->
                EmptyState(
                    message = if (state.hasLoadError) "无法加载会话列表。下拉重试。" else "该主机没有运行中的会话。",
                    actionLabel = "开新会话",
                    onAction = onNewSession,
                )
            else -> SessionList(state = state, onOpen = onOpen, onKill = onKill, thumbnails = thumbnails)
        }
    }
}

@Composable
private fun SessionList(
    state: SessionListUiState,
    onOpen: (UUID) -> Unit,
    onKill: (UUID) -> Unit,
    thumbnails: SessionThumbnails?,
) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(Spacing.lg16),
        verticalArrangement = Arrangement.spacedBy(Spacing.sm8),
    ) {
        if (state.hasLoadError) {
            item(key = "load-error") { LoadErrorBanner() }
        }
        items(items = state.rows, key = { it.id.toString() }) { row ->
            SwipeToKillRow(row = row, onOpen = onOpen, onKill = onKill, thumbnails = thumbnails)
        }
    }
}

/**
 * One row wrapped in a trailing (end→start) [SwipeToDismissBox]. Swiping left confirms the kill, which the
 * ViewModel handles optimistically (the row leaves the list on the next [state] emission). A start→end
 * swipe is disabled so a left-handed drag can't accidentally kill.
 */
@Composable
private fun SwipeToKillRow(
    row: SessionRow,
    onOpen: (UUID) -> Unit,
    onKill: (UUID) -> Unit,
    thumbnails: SessionThumbnails?,
) {
    val dismissState = rememberSwipeToDismissBoxState(
        confirmValueChange = { value ->
            if (value == SwipeToDismissBoxValue.EndToStart) {
                onKill(row.id)
                true
            } else {
                false
            }
        },
    )
    SwipeToDismissBox(
        state = dismissState,
        enableDismissFromStartToEnd = false,
        backgroundContent = { KillBackground() },
    ) {
        SessionListRow(row = row, onOpen = { onOpen(row.id) }, thumbnails = thumbnails)
    }
}

/** The red "删除" reveal behind a swiped row (end-aligned — the swipe is right→left). */
@Composable
private fun KillBackground() {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colorScheme.error, RoundedCornerShape(Radius.md12))
            .padding(horizontal = Spacing.lg16),
        contentAlignment = Alignment.CenterEnd,
    ) {
        Text(text = "删除", color = MaterialTheme.colorScheme.onError, style = MaterialTheme.typography.labelLarge)
    }
}

/** A transient-error banner shown above still-valid rows (last-good rows are kept; §"transient poll failure"). */
@Composable
private fun LoadErrorBanner() {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .background(MaterialTheme.colorScheme.errorContainer, RoundedCornerShape(Radius.sm8))
            .padding(Spacing.md12),
    ) {
        Text(
            text = "刷新失败，显示的是上次的列表。下拉重试。",
            color = MaterialTheme.colorScheme.onErrorContainer,
            style = MaterialTheme.typography.bodySmall,
        )
    }
}

/** Centered empty/error placeholder with one action; scrollable so pull-to-refresh still fires. */
@Composable
private fun EmptyState(message: String, actionLabel: String, onAction: () -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())
            .padding(Spacing.xxl24),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(Spacing.md12, Alignment.CenterVertically),
    ) {
        Text(text = message, style = MaterialTheme.typography.bodyMedium)
        Button(onClick = onAction) { Text(actionLabel) }
    }
}
