package wang.yaojia.webterm.components

import android.graphics.Bitmap
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
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.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import wang.yaojia.webterm.api.models.LiveSessionInfo
import wang.yaojia.webterm.designsystem.DisplayStatus
import wang.yaojia.webterm.designsystem.Opacity
import wang.yaojia.webterm.designsystem.Radius
import wang.yaojia.webterm.designsystem.Spacing
import wang.yaojia.webterm.designsystem.StatusBadge
import wang.yaojia.webterm.designsystem.WebTermColors
import wang.yaojia.webterm.designsystem.WebTermTheme
import wang.yaojia.webterm.designsystem.WebTermType
import wang.yaojia.webterm.viewmodels.SessionRow
import wang.yaojia.webterm.wire.ClaudeStatus
import wang.yaojia.webterm.wire.StatusTelemetry
import java.util.UUID

/**
 * # SessionListRow (A20) — one dashboard row for a running/just-exited session.
 *
 * Renders the parts the plan §1 session-list row enumerates: **preview thumbnail** (left) · **status
 * badge** + sanitized **title** + **unread dot** (top line) · **cols×rows** geometry + cwd (meta line) ·
 * **telemetry chips** (bottom line). Every server-influenced string ([SessionRow.title] — already run
 * through `TitleSanitizer` in the ViewModel — plus cwd and the telemetry chip labels) renders as INERT
 * [Text]: no linkify / Markdown / `AnnotatedString` autolink (plan §8). Exited rows dim to
 * [Opacity.exited].
 *
 * The whole row is tap-to-open ([onOpen]); swipe-to-kill is owned by the enclosing `SwipeToDismissBox` in
 * `SessionListScreen`. Layout/gesture behaviour is device-QA (plan §7); the row's DERIVED display values
 * are computed by the JVM-tested `sessionRowsOf` in the ViewModel.
 *
 * @param thumbnails the off-screen preview seam — production wires it to the active host's
 *   [ThumbnailPipeline][wang.yaojia.webterm.wiring.ThumbnailPipeline] (`(sessionId, lastOutputAt)`-keyed,
 *   §6.7); `null` renders the placeholder tile (the JVM/preview path draws no bitmap).
 */
@Composable
public fun SessionListRow(
    row: SessionRow,
    onOpen: () -> Unit,
    modifier: Modifier = Modifier,
    thumbnails: SessionThumbnails? = null,
    nowMs: Long = System.currentTimeMillis(),
) {
    Row(
        modifier = modifier
            .fillMaxWidth()
            .alpha(if (row.displayStatus == DisplayStatus.Exited) Opacity.exited else 1f)
            .clip(RoundedCornerShape(Radius.md12))
            .background(MaterialTheme.colorScheme.surfaceVariant)
            .clickable(onClick = onOpen)
            .padding(Spacing.md12),
        horizontalArrangement = Arrangement.spacedBy(Spacing.md12),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        ThumbnailTile(session = row.info, thumbnails = thumbnails)

        Column(
            modifier = Modifier.weight(1f),
            verticalArrangement = Arrangement.spacedBy(Spacing.xs4),
        ) {
            TitleLine(row = row)
            Text(
                text = metaLine(row.info),
                style = WebTermType.metaMono,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
            )
            row.info.telemetry?.let { telemetry ->
                TelemetryChips(telemetry = telemetry, nowMs = nowMs)
            }
        }
    }
}

/** Top line: status badge · unread dot · sanitized title (or an id/cwd fallback when the title is empty). */
@Composable
private fun TitleLine(row: SessionRow) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(Spacing.sm8),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        StatusBadge(status = row.displayStatus)
        if (row.isUnread) UnreadDot()
        Text(
            text = row.title.ifBlank { fallbackLabel(row.info) },
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurface,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
            modifier = Modifier.weight(1f),
        )
    }
}

/** The unread dot (accent-filled), shown when server output is newer than the local seen-watermark (§1). */
@Composable
private fun UnreadDot() {
    Box(
        modifier = Modifier
            .size(Spacing.sm8)
            .clip(CircleShape)
            .background(MaterialTheme.colorScheme.primary),
    )
}

/**
 * The preview thumbnail tile. Loads the bitmap once per `(id, lastOutputAt)` from the [thumbnails] seam
 * (so an unchanged screen is served from the pipeline's cache) and draws it; while loading (or when no
 * seam is wired) it shows a terminal-colored placeholder so the row height never jumps.
 */
@Composable
private fun ThumbnailTile(session: LiveSessionInfo, thumbnails: SessionThumbnails?) {
    var bitmap by remember(session.id, session.lastOutputAt) { mutableStateOf<Bitmap?>(null) }
    LaunchedEffect(session.id, session.lastOutputAt, thumbnails) {
        bitmap = thumbnails?.bitmapFor(session)
    }
    Box(
        modifier = Modifier
            .width(THUMBNAIL_WIDTH)
            .height(THUMBNAIL_HEIGHT)
            .clip(RoundedCornerShape(Radius.sm8))
            .background(WebTermColors.terminalBackground),
        contentAlignment = Alignment.Center,
    ) {
        bitmap?.let {
            Image(
                bitmap = it.asImageBitmap(),
                contentDescription = null,
                contentScale = ContentScale.Crop,
                modifier = Modifier.fillMaxWidth().height(THUMBNAIL_HEIGHT),
            )
        }
    }
}

/** "161×50 · ~/src/web-terminal" — tabular geometry + cwd (× is U+00D7). Both inert (§8). */
private fun metaLine(info: LiveSessionInfo): String {
    val dims = "${info.cols}×${info.rows}"
    val cwd = info.cwd?.takeIf { it.isNotBlank() }
    return if (cwd != null) "$dims · $cwd" else dims
}

/** When a session has no OSC title, label it by cwd (last segment) or the short id — never blank. */
private fun fallbackLabel(info: LiveSessionInfo): String {
    val cwd = info.cwd?.takeIf { it.isNotBlank() }
    if (cwd != null) return cwd.trimEnd('/').substringAfterLast('/').ifBlank { cwd }
    return info.id.toString().substringBefore('-')
}

/**
 * The off-screen thumbnail seam. Production is a thin adapter over the active host's
 * [ThumbnailPipeline][wang.yaojia.webterm.wiring.ThumbnailPipeline]
 * (`{ session -> pipeline.thumbnail(session) }`); the default `null` seam renders the placeholder tile so
 * the row composes with no `android.graphics` dependency under preview/JVM.
 */
public fun interface SessionThumbnails {
    /** The cached/rendered preview bitmap for [session], or `null` when unavailable (placeholder shown). */
    public suspend fun bitmapFor(session: LiveSessionInfo): Bitmap?
}

private val THUMBNAIL_WIDTH = Spacing.xxl24 * 4 // 96dp
private val THUMBNAIL_HEIGHT = Spacing.xxl24 * 2.5f // 60dp

// ── Preview ─────────────────────────────────────────────────────────────────────

@Preview(name = "SessionListRow")
@Composable
private fun SessionListRowPreview() {
    WebTermTheme {
        Column(verticalArrangement = Arrangement.spacedBy(Spacing.sm8), modifier = Modifier.padding(Spacing.md12)) {
            SessionListRow(
                row = SessionRow(
                    info = previewInfo(status = ClaudeStatus.WORKING, title = "web-terminal", lastOutputAt = 2L),
                    displayStatus = DisplayStatus.Working,
                    title = "web-terminal",
                    isUnread = true,
                ),
                onOpen = {},
            )
            SessionListRow(
                row = SessionRow(
                    info = previewInfo(status = ClaudeStatus.IDLE, title = "", cols = 80, rows = 24),
                    displayStatus = DisplayStatus.Exited,
                    title = "",
                    isUnread = false,
                ),
                onOpen = {},
            )
        }
    }
}

private fun previewInfo(
    status: ClaudeStatus,
    title: String,
    cols: Int = 161,
    rows: Int = 50,
    lastOutputAt: Long? = null,
): LiveSessionInfo = LiveSessionInfo(
    id = UUID.fromString("11111111-2222-4333-8444-555555555555"),
    createdAt = 1L,
    clientCount = 1,
    status = status,
    exited = false,
    cwd = "/Users/dev/src/web-terminal",
    title = title.ifBlank { null },
    cols = cols,
    rows = rows,
    telemetry = StatusTelemetry(contextUsedPct = 42.0, costUsd = 0.12, model = "opus", at = 0L),
    lastOutputAt = lastOutputAt,
)
