package com.jsjdesigns.poker

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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.draw.rotate
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.jsjdesigns.poker.core.Card
import com.jsjdesigns.poker.game.DecisionOffer
import com.jsjdesigns.poker.game.SeatSnapshot
import com.jsjdesigns.poker.game.TableSnapshot
import kotlin.math.roundToInt
import kotlin.math.sqrt

private val Night = Color(0xFF04100C)
private val TableBackground = Color(0xFF071D16)
private val FeltLight = Color(0xFF1E6E51)
private val FeltMid = Color(0xFF155440)
private val FeltDark = Color(0xFF0C3A2C)
private val RailLight = Color(0xFF3B2A17)
private val RailDark = Color(0xFF211407)
private val TableGold = Color(0xFFE3C27E)
private val GoldLight = Color(0xFFF0D9A5)
private val Cream = Color(0xFFF3EFE6)
private val Muted = Color(0xFF9FB0A6)
private val Danger = Color(0xFF8C242A)
private val Panel = Color(0xE604120D)

/**
 * Minimum accessible touch target. Controls keep this even when their painted
 * area is smaller — see [SLIDER_VISIBLE_HEIGHT].
 */
private val TOUCH_TARGET = 48.dp

/**
 * How much vertical space the slider actually paints. Material reserves a 48dp
 * touch target around a ~16dp track; laying it out at the full 48dp injects
 * invisible padding that makes uniform gaps render unevenly.
 */
private val SLIDER_VISIBLE_HEIGHT = 16.dp

/** One consistent rhythm for stacked controls. */
private val CONTROL_GAP = 8.dp

/** Separation between a group of controls and the action that commits them. */
private val GROUP_GAP = 18.dp

internal fun visibleChipCount(amount: Int): Int = when {
    amount <= 0 -> 0
    amount <= 2 -> 1
    amount <= 5 -> 2
    amount <= 10 -> 3
    amount <= 25 -> 4
    amount <= 60 -> 5
    amount <= 150 -> 6
    else -> 7
}

internal data class SeatPlacement(
    /** Distance in dp from the table's left edge to this seat's left edge. */
    val leftDp: Float,
    /** Where this seat's avatar centre belongs, as a fraction of table height. */
    val centerYFraction: Float,
)

/** Space kept clear at the table's left and right edges. */
private const val SEAT_EDGE_MARGIN_DP = 6f

/** Smallest horizontal gap permitted between two neighbouring seats. */
private const val SEAT_MIN_GAP_DP = 10f

// The felt oval, as fractions of the table box. Seat placement reads the same
// constants the felt is drawn from, so seats cannot drift off the rail when the
// table shape changes — the two were previously unrelated numbers.
private const val FELT_LEFT = 0.012f
private const val FELT_RIGHT = 0.988f
private const val FELT_TOP = 0.055f
private const val FELT_BOTTOM = 0.965f

/**
 * Height fraction where the felt's upper rail sits at a given horizontal
 * position.
 *
 * Seats were previously assigned hand-picked vertical lanes, which left the
 * shoulder seats floating on the felt interior while the crown and side seats
 * straddled the rail. Solving the ellipse puts every seat on the same edge.
 */
internal fun railCenterYFraction(centerXFraction: Float): Float {
    val cx = (FELT_LEFT + FELT_RIGHT) / 2f
    val rx = (FELT_RIGHT - FELT_LEFT) / 2f
    val cy = (FELT_TOP + FELT_BOTTOM) / 2f
    val ry = (FELT_BOTTOM - FELT_TOP) / 2f
    val u = ((centerXFraction - cx) / rx).coerceIn(-1f, 1f)
    return cy - ry * sqrt(1f - u * u)
}

/**
 * Width one opponent seat may occupy, derived from the table's real width.
 *
 * Seat width MUST come from the measured container. A fixed width combined with
 * fractional positions silently overlaps as the screen narrows: an 88dp seat at
 * centre fractions 0.13/0.28 overlaps by 30dp at 384dp wide (Galaxy S24+) while
 * merely looking tight at 427dp (Pixel emulator). Sizing from the container
 * makes five seats fit at any width by construction.
 */
internal fun opponentSeatWidthDp(availableWidthDp: Float): Float {
    val usable = availableWidthDp - 2f * SEAT_EDGE_MARGIN_DP - 4f * SEAT_MIN_GAP_DP
    return (usable / 5f).coerceIn(52f, 96f)
}

/**
 * Five seats spread edge to edge with equal gaps, staggered into three lanes so
 * the row still reads as an arc around the felt rather than a straight line.
 */
internal fun opponentSeatPlacements(
    availableWidthDp: Float,
    seatWidthDp: Float = opponentSeatWidthDp(availableWidthDp),
): List<SeatPlacement> {
    val span = availableWidthDp - 2f * SEAT_EDGE_MARGIN_DP - seatWidthDp
    val step = span / 4f
    return List(5) { index ->
        val left = SEAT_EDGE_MARGIN_DP + step * index
        val centerXFraction = (left + seatWidthDp / 2f) / availableWidthDp
        SeatPlacement(
            leftDp = left,
            centerYFraction = railCenterYFraction(centerXFraction),
        )
    }
}

@Composable
fun TableScreen(vm: PokerViewModel) {
    val state by vm.state.collectAsStateWithLifecycle()
    val rawOffer by vm.offer.collectAsStateWithLifecycle()
    val snap = state.snapshot
    val offer = state.liveOffer(rawOffer)
    val handSummary = state.visibleHandSummary()
    val coachReview = state.visibleCoachReview()
    val tableTalk = state.visibleTableTalk()
    val status = snap?.let(::tableStatus)

    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Night)
            .systemBarsPadding(),
    ) {
        TableHeader(snap, state.gameConfig)
        PokerTable(
            snapshot = snap,
            status = status,
            tableTalk = tableTalk,
            handSummary = handSummary,
            // Fill the space the two-stage raise control freed instead of locking
            // an aspect ratio and leaving ~178dp of dead felt below the hero.
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f),
        )
        TableControls(
            offer = offer,
            handSummary = handSummary,
            nextHandRequested = state.nextHandRequested,
            rebuyRequired = state.heroNeedsRebuy,
            buyIn = state.gameConfig?.buyIn ?: DEFAULT_BUY_IN,
            coachReview = coachReview,
            heroFolded = snap?.seats?.firstOrNull { it.index == HERO_SEAT }?.folded == true,
            onFold = vm::fold,
            onCheckCall = vm::checkOrCall,
            onRaise = vm::raiseTo,
            onNextHand = vm::nextHand,
        )
    }
}

@Composable
private fun TableHeader(
    snapshot: TableSnapshot?,
    config: CashGameConfig?,
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .height(50.dp)
            .padding(horizontal = 18.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.SpaceBetween,
    ) {
        Column(verticalArrangement = Arrangement.spacedBy(1.dp)) {
            Text(
                "HAND ${snapshot?.handNumber ?: 1}",
                color = Cream,
                fontWeight = FontWeight.Bold,
                fontSize = 14.sp,
            )
            Text(
                snapshot?.street?.name.orEmpty(),
                color = Muted.copy(alpha = 0.8f),
                fontWeight = FontWeight.Medium,
                fontSize = 11.sp,
                letterSpacing = 1.4.sp,
            )
        }
        Surface(
            shape = RoundedCornerShape(50),
            color = Color.Black.copy(alpha = 0.24f),
            border = androidx.compose.foundation.BorderStroke(1.dp, TableGold.copy(alpha = 0.24f)),
        ) {
            Row(
                modifier = Modifier.padding(horizontal = 11.dp, vertical = 6.dp),
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(6.dp),
            ) {
                ChipDot(TableGold, 11.dp)
                Text(
                    "${config?.smallBlind ?: DEFAULT_SMALL_BLIND} / " +
                        "${config?.bigBlind ?: DEFAULT_BIG_BLIND}",
                    color = TableGold,
                    fontWeight = FontWeight.Bold,
                    fontSize = 12.sp,
                )
            }
        }
    }
}

@Composable
private fun PokerTable(
    snapshot: TableSnapshot?,
    status: TableStatus?,
    tableTalk: com.jsjdesigns.poker.bot.TableTalkLine?,
    handSummary: HandSummary?,
    modifier: Modifier = Modifier,
) {
    val seats = snapshot?.seats.orEmpty()
    val winners = handSummary?.awards
        .orEmpty()
        .flatMap { award -> award.winners.map(AwardWinner::seat) }
        .toSet()

    BoxWithConstraints(
        modifier = modifier
            .background(TableBackground),
    ) {
        TableFelt(Modifier.fillMaxSize())

        // Seat width and position both come from the measured table, so five
        // seats fit with real gaps on a 384dp phone as well as a 427dp emulator.
        val seatWidth = opponentSeatWidthDp(maxWidth.value)
        val placements = opponentSeatPlacements(maxWidth.value, seatWidth)
        seats.filter { it.index != HERO_SEAT }
            .forEachIndexed { index, seat ->
                val placement = placements.getOrElse(index) { placements.last() }
                OpponentSeat(
                    seat = seat,
                    isTurn = snapshot?.toAct == seat.index,
                    isWinner = seat.index in winners,
                    width = seatWidth.dp,
                    modifier = Modifier.offset(
                        x = placement.leftDp.dp,
                        // Placement gives where the avatar centre belongs on the
                        // rail; the seat's top is half an avatar above it.
                        y = maxHeight * placement.centerYFraction -
                            (seatWidth.dp * 0.58f).coerceIn(34.dp, 48.dp) / 2,
                    ),
                )
            }

        BoardAndStatus(
            snapshot = snapshot,
            status = status,
            tableTalk = tableTalk,
            // Below the side-seat lane (0.30 of height plus the seat itself),
            // measured against height so it tracks the taller felt.
            modifier = Modifier
                .align(Alignment.TopCenter)
                .offset(y = maxHeight * 0.46f),
        )

        HeroSeat(
            seat = seats.firstOrNull { it.index == HERO_SEAT },
            isTurn = snapshot?.toAct == HERO_SEAT,
            isWinner = HERO_SEAT in winners,
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .padding(bottom = 10.dp),
        )

        handSummary?.let { summary ->
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .background(Night.copy(alpha = 0.82f))
                    .padding(8.dp),
                contentAlignment = Alignment.Center,
            ) {
                ShowdownModal(summary)
            }
        }
    }
}

@Composable
private fun TableFelt(modifier: Modifier = Modifier) {
    Canvas(modifier) {
        // An oval sized to the container, so the felt grows into whatever height
        // the table is given rather than staying a width-sized circle.
        val inset = 9.dp.toPx()
        val railRect = androidx.compose.ui.geometry.Rect(
            left = size.width * FELT_LEFT,
            top = size.height * FELT_TOP,
            right = size.width * FELT_RIGHT,
            bottom = size.height * FELT_BOTTOM,
        )
        drawOval(
            brush = Brush.linearGradient(
                colors = listOf(RailLight, RailDark),
                start = Offset(railRect.left, railRect.top),
                end = Offset(railRect.right, railRect.bottom),
            ),
            topLeft = Offset(railRect.left, railRect.top),
            size = androidx.compose.ui.geometry.Size(railRect.width, railRect.height),
        )

        val feltTopLeft = Offset(railRect.left + inset, railRect.top + inset)
        val feltSize = androidx.compose.ui.geometry.Size(
            railRect.width - inset * 2,
            railRect.height - inset * 2,
        )
        drawOval(
            brush = Brush.radialGradient(
                colors = listOf(FeltLight, FeltMid, FeltDark),
                center = Offset(
                    feltTopLeft.x + feltSize.width * 0.5f,
                    feltTopLeft.y + feltSize.height * 0.34f,
                ),
                radius = feltSize.minDimension * 1.15f,
            ),
            topLeft = feltTopLeft,
            size = feltSize,
        )
        drawOval(
            color = TableGold.copy(alpha = 0.30f),
            topLeft = feltTopLeft,
            size = feltSize,
            style = Stroke(width = 1.dp.toPx()),
        )
    }
}

@Composable
private fun OpponentSeat(
    seat: SeatSnapshot,
    isTurn: Boolean,
    isWinner: Boolean,
    width: Dp,
    modifier: Modifier = Modifier,
) {
    val faded = seat.folded && !isWinner
    val revealed = seat.revealed && seat.hole != null
    // Revealed cards scale with the slot instead of widening it. Growing the seat
    // would reintroduce the overlap the measured layout exists to prevent.
    val faceWidth = ((width - 14.dp) / 2).coerceIn(30.dp, 54.dp)
    val faceHeight = faceWidth * 1.5f
    Column(
        modifier = modifier
            .width(width)
            .alpha(if (faded) 0.38f else 1f),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(4.dp),
    ) {
        Box(contentAlignment = Alignment.Center) {
            val highlight = when {
                isWinner -> TableGold
                isTurn -> GoldLight
                else -> Color.White.copy(alpha = 0.12f)
            }
            Box(
                modifier = Modifier
                    .border(
                        width = if (isWinner || isTurn) 2.dp else 1.dp,
                        color = highlight,
                        shape = RoundedCornerShape(12.dp),
                    )
                    .padding(2.dp),
                contentAlignment = Alignment.Center,
            ) {
                val hole = seat.hole
                if (revealed && hole != null) {
                    Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
                        repeat(2) { index ->
                            CardImage(
                                hole.getOrNull(index),
                                Modifier.size(faceWidth, faceHeight),
                            )
                        }
                    }
                } else {
                    // One slot-width box: the fan is drawn first so the avatar
                    // sits on top of it, and neither can push past the seat.
                    Box(
                        modifier = Modifier.width(width - 6.dp),
                        contentAlignment = Alignment.CenterStart,
                    ) {
                        if (!seat.folded) {
                            HoleCardFan(
                                cardWidth = (width * 0.27f).coerceIn(18.dp, 27.dp),
                                modifier = Modifier.align(Alignment.CenterEnd),
                            )
                        }
                        OpponentAvatar(seat, size = (width * 0.58f).coerceIn(34.dp, 48.dp))
                    }
                }
            }
            if (seat.isButton) {
                DealerButton(Modifier.align(Alignment.BottomStart).offset(x = (-5).dp, y = 4.dp))
            }
        }

        Surface(
            shape = RoundedCornerShape(8.dp),
            color = Panel,
            border = androidx.compose.foundation.BorderStroke(
                1.dp,
                if (isWinner) TableGold.copy(alpha = 0.65f) else Color.White.copy(alpha = 0.06f),
            ),
        ) {
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(vertical = 4.dp, horizontal = 3.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
            ) {
                Text(
                    seat.name,
                    color = Cream,
                    fontWeight = FontWeight.Bold,
                    fontSize = 12.sp,
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
                Text(
                    when {
                        seat.folded -> "folded"
                        seat.allIn -> "ALL IN"
                        else -> "${seat.stack}"
                    },
                    color = when {
                        seat.folded -> Muted.copy(alpha = 0.65f)
                        seat.allIn || isWinner -> TableGold
                        else -> TableGold.copy(alpha = 0.9f)
                    },
                    fontWeight = if (seat.allIn || isWinner) FontWeight.Bold else FontWeight.Medium,
                    fontSize = 12.sp,
                )
            }
        }

        if (seat.committedThisRound > 0 && !seat.folded) {
            BetMarker(seat.committedThisRound)
        } else {
            Spacer(Modifier.height(16.dp))
        }
    }
}

/**
 * Two face-down cards fanned like a real hand.
 *
 * Drawn flat and barely overlapped they merged into one red slab; opposing
 * rotation and a real offset make the pair legible at seat scale. The left card
 * tucks behind the avatar, which is why this is drawn after it in the row.
 */
@Composable
private fun HoleCardFan(cardWidth: Dp, modifier: Modifier = Modifier) {
    val cardHeight = cardWidth * 1.5f
    Box(
        modifier = modifier
            .width(cardWidth * 1.40f)
            .height(cardHeight * 1.16f),
        contentAlignment = Alignment.Center,
    ) {
        CardImage(
            null,
            Modifier
                .size(cardWidth, cardHeight)
                .offset(x = (-4).dp)
                .rotate(-11f),
        )
        CardImage(
            null,
            Modifier
                .size(cardWidth, cardHeight)
                .offset(x = 5.dp)
                .rotate(10f),
        )
    }
}

@Composable
private fun OpponentAvatar(seat: SeatSnapshot, size: Dp = 48.dp) {
    Box(
        modifier = Modifier
            .size(size)
            .clip(CircleShape)
            .background(
                Brush.linearGradient(
                    listOf(Color(0xFF3A5C4B), Color(0xFF183226)),
                ),
            ),
        contentAlignment = Alignment.Center,
    ) {
        Text(
            seat.name.take(1).uppercase(),
            color = GoldLight,
            fontWeight = FontWeight.Bold,
            fontSize = 18.sp,
        )
    }
}

@Composable
private fun HeroSeat(
    seat: SeatSnapshot?,
    isTurn: Boolean,
    isWinner: Boolean,
    modifier: Modifier = Modifier,
) {
    val folded = seat?.folded == true
    val cardWidth = 76.dp
    val cardHeight = 114.dp
    Surface(
        modifier = modifier.alpha(if (folded) 0.42f else 1f),
        shape = RoundedCornerShape(16.dp),
        color = Panel,
        border = androidx.compose.foundation.BorderStroke(
            if (isTurn || isWinner) 2.dp else 1.dp,
            when {
                isWinner -> TableGold
                isTurn -> GoldLight
                else -> Color.White.copy(alpha = 0.09f)
            },
        ),
    ) {
        Row(
            modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp),
            verticalAlignment = Alignment.Bottom,
            horizontalArrangement = Arrangement.spacedBy(10.dp),
        ) {
            Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
                repeat(2) { index ->
                    CardImage(
                        seat?.hole?.getOrNull(index),
                        Modifier.size(cardWidth, cardHeight),
                    )
                }
            }
            Column(
                modifier = Modifier.padding(bottom = 4.dp),
                verticalArrangement = Arrangement.spacedBy(3.dp),
            ) {
                Text(
                    seat?.name ?: "You",
                    color = Cream,
                    fontWeight = FontWeight.ExtraBold,
                    fontSize = 15.sp,
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
                Text(
                    "${seat?.stack ?: 0}",
                    color = TableGold,
                    fontWeight = FontWeight.Bold,
                    fontSize = 15.sp,
                )
                when {
                    folded -> StatusTag("FOLDED", Danger)
                    seat?.allIn == true -> StatusTag("ALL IN", TableGold)
                    isWinner -> StatusTag("WINNER", TableGold)
                }
                if ((seat?.committedThisRound ?: 0) > 0) {
                    BetMarker(seat?.committedThisRound ?: 0)
                }
            }
        }
    }
}

@Composable
private fun BoardAndStatus(
    snapshot: TableSnapshot?,
    status: TableStatus?,
    tableTalk: com.jsjdesigns.poker.bot.TableTalkLine?,
    modifier: Modifier = Modifier,
) {
    val board = snapshot?.board.orEmpty()
    val cardWidth = 58.dp
    val cardHeight = 87.dp
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        PotPill(snapshot?.pot ?: 0)
        Row(horizontalArrangement = Arrangement.spacedBy(5.dp)) {
            repeat(5) { index ->
                if (index < board.size) {
                    CardImage(board[index], Modifier.size(cardWidth, cardHeight))
                } else {
                    Box(
                        Modifier
                            .size(cardWidth, cardHeight)
                            .clip(RoundedCornerShape(6.dp))
                            .border(
                                1.dp,
                                TableGold.copy(alpha = 0.18f),
                                RoundedCornerShape(6.dp),
                            )
                            .background(Color.Black.copy(alpha = 0.08f)),
                    )
                }
            }
        }
        status?.let {
            StatusPill(
                text = it.text,
                accent = it.tone == TableStatusTone.ACCENT,
            )
        }
        tableTalk?.let {
            Text(
                "${it.speakerName}: “${it.text}”",
                color = Cream.copy(alpha = 0.9f),
                fontSize = 12.sp,
                fontWeight = FontWeight.Medium,
                textAlign = TextAlign.Center,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.width(292.dp),
            )
        }
    }
}

@Composable
private fun PotPill(pot: Int) {
    Surface(
        shape = RoundedCornerShape(50),
        color = Color(0x9904140E),
        border = androidx.compose.foundation.BorderStroke(1.dp, TableGold.copy(alpha = 0.32f)),
    ) {
        Row(
            modifier = Modifier.padding(horizontal = 14.dp, vertical = 7.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(8.dp),
        ) {
            ChipStack(pot, chipSize = 18.dp)
            Text(
                "POT",
                color = TableGold.copy(alpha = 0.85f),
                fontWeight = FontWeight.Bold,
                fontSize = 10.sp,
                letterSpacing = 1.8.sp,
            )
            Text(
                "$pot",
                color = GoldLight,
                fontWeight = FontWeight.ExtraBold,
                fontSize = 17.sp,
            )
        }
    }
}

@Composable
private fun StatusPill(text: String, accent: Boolean) {
    Surface(
        shape = RoundedCornerShape(50),
        color = if (accent) TableGold.copy(alpha = 0.17f) else Color.Black.copy(alpha = 0.24f),
        border = androidx.compose.foundation.BorderStroke(
            1.dp,
            if (accent) TableGold.copy(alpha = 0.5f) else Color.White.copy(alpha = 0.08f),
        ),
    ) {
        Text(
            text,
            color = if (accent) GoldLight else Cream.copy(alpha = 0.8f),
            fontWeight = if (accent) FontWeight.Bold else FontWeight.Medium,
            fontSize = 12.sp,
            textAlign = TextAlign.Center,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
            modifier = Modifier
                .width(286.dp)
                .padding(horizontal = 11.dp, vertical = 7.dp),
        )
    }
}

@Composable
private fun BetMarker(amount: Int) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(4.dp),
    ) {
        ChipStack(amount, chipSize = 14.dp)
        Text(
            "$amount",
            color = Cream.copy(alpha = 0.9f),
            fontWeight = FontWeight.Bold,
            fontSize = 12.sp,
        )
    }
}

@Composable
private fun DealerButton(modifier: Modifier = Modifier) {
    Box(
        modifier = modifier
            .size(18.dp)
            .clip(CircleShape)
            .background(Color(0xFFF3EBD8))
            .border(1.dp, Color(0xFFA79463), CircleShape),
        contentAlignment = Alignment.Center,
    ) {
        Text("D", color = Color(0xFF5A4A22), fontWeight = FontWeight.ExtraBold, fontSize = 8.sp)
    }
}

@Composable
private fun StatusTag(text: String, color: Color) {
    Surface(
        shape = RoundedCornerShape(6.dp),
        color = color.copy(alpha = 0.16f),
        border = androidx.compose.foundation.BorderStroke(1.dp, color.copy(alpha = 0.5f)),
    ) {
        Text(
            text,
            color = color,
            fontWeight = FontWeight.ExtraBold,
            fontSize = 10.sp,
            letterSpacing = 0.5.sp,
            modifier = Modifier.padding(horizontal = 7.dp, vertical = 3.dp),
        )
    }
}

@Composable
private fun ChipStack(amount: Int, chipSize: Dp) {
    val colors = listOf(
        Color(0xFFE7E4D9),
        Color(0xFF2A9E6B),
        Color(0xFF2F72C4),
        Color(0xFFE2B85B),
        Color(0xFFB94B52),
    )
    Row(
        horizontalArrangement = Arrangement.spacedBy((-4).dp),
        verticalAlignment = Alignment.Bottom,
    ) {
        repeat(visibleChipCount(amount)) { index ->
            Box(Modifier.padding(bottom = ((index % 3) * 3).dp)) {
                ChipDot(colors[index % colors.size], chipSize)
            }
        }
    }
}

@Composable
private fun ChipDot(color: Color, size: Dp) {
    Canvas(Modifier.size(size)) {
        val radius = this.size.minDimension * 0.48f
        drawCircle(color = Color.Black.copy(alpha = 0.34f), radius = radius)
        drawCircle(color = color, radius = radius * 0.91f)
        drawCircle(
            color = Color.White.copy(alpha = 0.62f),
            radius = radius * 0.63f,
            style = Stroke(width = 1.2.dp.toPx()),
        )
        val mark = radius * 0.63f
        drawLine(Color.White.copy(alpha = 0.78f), Offset(center.x, center.y - radius), Offset(center.x, center.y - mark), 1.5.dp.toPx())
        drawLine(Color.White.copy(alpha = 0.78f), Offset(center.x, center.y + mark), Offset(center.x, center.y + radius), 1.5.dp.toPx())
        drawLine(Color.White.copy(alpha = 0.78f), Offset(center.x - radius, center.y), Offset(center.x - mark, center.y), 1.5.dp.toPx())
        drawLine(Color.White.copy(alpha = 0.78f), Offset(center.x + mark, center.y), Offset(center.x + radius, center.y), 1.5.dp.toPx())
    }
}

@Composable
private fun TableControls(
    offer: DecisionOffer?,
    handSummary: HandSummary?,
    nextHandRequested: Boolean,
    rebuyRequired: Boolean,
    buyIn: Int,
    coachReview: com.jsjdesigns.poker.bot.CoachingReview?,
    heroFolded: Boolean,
    onFold: (Long) -> Unit,
    onCheckCall: (Long) -> Unit,
    onRaise: (Long, Int) -> Unit,
    onNextHand: (Int) -> Unit,
) {
    Surface(
        modifier = Modifier.fillMaxWidth(),
        color = Night,
    ) {
        when {
            offer != null -> DecisionControls(offer, onFold, onCheckCall, onRaise)
            handSummary != null -> CompletionControls(
                summary = handSummary,
                nextHandRequested = nextHandRequested,
                rebuyRequired = rebuyRequired,
                buyIn = buyIn,
                onNextHand = onNextHand,
            )
            coachReview != null -> CoachBar(coachPresentation(coachReview))
            else -> Box(
                Modifier
                    .fillMaxWidth()
                    .height(80.dp),
                contentAlignment = Alignment.Center,
            ) {
                Text(
                    if (heroFolded) "You folded — watching the hand finish" else "Waiting…",
                    color = Muted.copy(alpha = 0.55f),
                    fontSize = 13.sp,
                )
            }
        }
    }
}

@Composable
private fun DecisionControls(
    offer: DecisionOffer,
    onFold: (Long) -> Unit,
    onCheckCall: (Long) -> Unit,
    onRaise: (Long, Int) -> Unit,
) {
    val buttons = buttonsFor(offer)
    var sizingRaise by remember(offer.token) { mutableStateOf(false) }
    var raiseTo by remember(offer.token) { mutableIntStateOf(buttons.fixedRaiseTo) }

    BackHandler(enabled = sizingRaise) {
        sizingRaise = false
    }

    Column(
        modifier = Modifier
            .fillMaxWidth()
            .background(
                Brush.verticalGradient(
                    listOf(Color.Transparent, Night.copy(alpha = 0.95f), Night),
                ),
            )
            .padding(horizontal = 14.dp, vertical = 10.dp),
        verticalArrangement = Arrangement.spacedBy(CONTROL_GAP),
    ) {
        if (!sizingRaise) {
            PrimaryDecisionButtons(
                offer = offer,
                buttons = buttons,
                onFold = onFold,
                onCheckCall = onCheckCall,
                onOpenRaise = { sizingRaise = true },
            )
        } else {
            val amount = raiseConfirmationAmount(buttons, raiseTo)
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(TOUCH_TARGET),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.CenterVertically,
            ) {
                BackToActionsButton { sizingRaise = false }
                Column(horizontalAlignment = Alignment.End) {
                    Text(
                        "RAISE TO",
                        color = Muted,
                        fontWeight = FontWeight.Bold,
                        fontSize = 12.sp,
                        letterSpacing = 1.4.sp,
                    )
                    Text(
                        "$amount",
                        color = GoldLight,
                        fontWeight = FontWeight.ExtraBold,
                        fontSize = 24.sp,
                    )
                }
            }

            if (buttons.showSlider) {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(TOUCH_TARGET),
                    verticalAlignment = Alignment.CenterVertically,
                    horizontalArrangement = Arrangement.spacedBy(8.dp),
                ) {
                    FineTuneButton("−") {
                        raiseTo = adjustRaiseAmount(raiseTo, -1, buttons)
                    }
                    // A Slider draws a ~16dp track inside a 48dp accessibility touch
                    // target, so 32dp of it is invisible. Laying it out at 48dp adds
                    // 16dp of phantom space above and below, which is why a uniform
                    // gap rendered as ~20dp around the slider and ~4dp between the
                    // filled buttons. Reserve only the visible height and let the
                    // touch target overflow, so declared gaps are the real gaps.
                    Box(
                        modifier = Modifier
                            .weight(1f)
                            .height(SLIDER_VISIBLE_HEIGHT),
                        contentAlignment = Alignment.Center,
                    ) {
                        Slider(
                            value = amount.toFloat(),
                            onValueChange = { raiseTo = it.roundToInt() },
                            valueRange = buttons.sliderMin.toFloat()..buttons.sliderMax.toFloat(),
                            colors = SliderDefaults.colors(
                                thumbColor = GoldLight,
                                activeTrackColor = TableGold,
                                inactiveTrackColor = Color.White.copy(alpha = 0.12f),
                            ),
                            modifier = Modifier.requiredHeight(TOUCH_TARGET),
                        )
                    }
                    FineTuneButton("+") {
                        raiseTo = adjustRaiseAmount(raiseTo, 1, buttons)
                    }
                }
                val presets = raisePresets(offer, buttons)
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.spacedBy(6.dp),
                ) {
                    presets.forEach { preset ->
                        Button(
                            onClick = { raiseTo = preset.amount },
                            modifier = Modifier
                                .weight(1f)
                                .height(48.dp),
                            contentPadding = PaddingValues(horizontal = 4.dp),
                            colors = ButtonDefaults.buttonColors(
                                containerColor = if (amount == preset.amount) {
                                    TableGold.copy(alpha = 0.2f)
                                } else {
                                    Color.White.copy(alpha = 0.12f)
                                },
                                contentColor = if (amount == preset.amount) GoldLight else Muted,
                            ),
                            shape = RoundedCornerShape(9.dp),
                        ) {
                            Text(
                                "${preset.label} ${preset.amount}",
                                fontWeight = FontWeight.Bold,
                                fontSize = 11.sp,
                                maxLines = 1,
                            )
                        }
                    }
                }
            } else {
                Text(
                    if (amount >= buttons.sliderMax) {
                        "Your stack leaves one legal raise: all in for $amount."
                    } else {
                        "There is one legal raise size: $amount."
                    },
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(vertical = 8.dp),
                    color = Cream.copy(alpha = 0.82f),
                    fontSize = 13.sp,
                    textAlign = TextAlign.Center,
                )
            }

            Spacer(Modifier.height(GROUP_GAP - CONTROL_GAP))
            ActionButton(
                title = if (amount >= buttons.sliderMax) "Confirm all in" else "Confirm raise",
                detail = if (amount >= buttons.sliderMax) "$amount" else "to $amount",
                container = Brush.verticalGradient(listOf(GoldLight, Color(0xFFC79A38))),
                content = Color(0xFF22190A),
                modifier = Modifier.fillMaxWidth(),
                onClick = { onRaise(offer.token, amount) },
            )
        }
    }
}

@Composable
private fun PrimaryDecisionButtons(
    offer: DecisionOffer,
    buttons: ActionButtons,
    onFold: (Long) -> Unit,
    onCheckCall: (Long) -> Unit,
    onOpenRaise: () -> Unit,
) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        if (buttons.showFold) {
            ActionButton(
                title = "Fold",
                detail = null,
                container = Danger.copy(alpha = 0.58f),
                content = Color(0xFFF0BDBD),
                modifier = Modifier.weight(1f),
                onClick = { onFold(offer.token) },
            )
        }
        ActionButton(
            title = when {
                offer.canCheck -> "Check"
                offer.callIsAllIn -> "All in"
                else -> "Call"
            },
            detail = offer.callAmount.takeUnless { offer.canCheck }?.toString(),
            container = Color.White.copy(alpha = 0.09f),
            content = Cream,
            modifier = Modifier.weight(1f),
            onClick = { onCheckCall(offer.token) },
        )
        if (buttons.showRaise) {
            ActionButton(
                title = "Raise",
                detail = null,
                container = Brush.verticalGradient(listOf(GoldLight, Color(0xFFC79A38))),
                content = Color(0xFF22190A),
                modifier = Modifier.weight(1.12f),
                onClick = onOpenRaise,
            )
        }
    }
}

@Composable
private fun BackToActionsButton(onClick: () -> Unit) {
    Button(
        onClick = onClick,
        modifier = Modifier.height(48.dp),
        contentPadding = PaddingValues(horizontal = 14.dp),
        colors = ButtonDefaults.buttonColors(
            containerColor = Color.White.copy(alpha = 0.16f),
            contentColor = Cream,
        ),
        shape = RoundedCornerShape(12.dp),
    ) {
        Text("‹  Back", fontWeight = FontWeight.Bold, fontSize = 14.sp)
    }
}

@Composable
private fun FineTuneButton(
    label: String,
    onClick: () -> Unit,
) {
    Button(
        onClick = onClick,
        modifier = Modifier.size(48.dp),
        contentPadding = PaddingValues(0.dp),
        colors = ButtonDefaults.buttonColors(
            containerColor = Color.White.copy(alpha = 0.16f),
            contentColor = GoldLight,
        ),
        shape = RoundedCornerShape(12.dp),
    ) {
        Text(label, fontWeight = FontWeight.ExtraBold, fontSize = 24.sp)
    }
}

@Composable
private fun ActionButton(
    title: String,
    detail: String?,
    container: Color,
    content: Color,
    modifier: Modifier,
    onClick: () -> Unit,
) {
    Button(
        onClick = onClick,
        modifier = modifier.height(60.dp),
        shape = RoundedCornerShape(14.dp),
        colors = ButtonDefaults.buttonColors(containerColor = container, contentColor = content),
    ) {
        ActionButtonText(title, detail, content)
    }
}

@Composable
private fun ActionButton(
    title: String,
    detail: String?,
    container: Brush,
    content: Color,
    modifier: Modifier,
    onClick: () -> Unit,
) {
    Button(
        onClick = onClick,
        modifier = modifier
            .height(60.dp)
            .background(container, RoundedCornerShape(14.dp)),
        shape = RoundedCornerShape(14.dp),
        colors = ButtonDefaults.buttonColors(
            containerColor = Color.Transparent,
            contentColor = content,
        ),
    ) {
        ActionButtonText(title, detail, content)
    }
}

@Composable
private fun ActionButtonText(title: String, detail: String?, color: Color) {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text(title, color = color, fontWeight = FontWeight.ExtraBold, fontSize = 16.sp)
        detail?.let {
            Text(it, color = color.copy(alpha = 0.78f), fontWeight = FontWeight.Bold, fontSize = 12.sp)
        }
    }
}

@Composable
private fun ShowdownModal(summary: HandSummary) {
    Surface(
        modifier = Modifier.fillMaxWidth(),
        shape = RoundedCornerShape(20.dp),
        color = Color(0xFA07130F),
        border = androidx.compose.foundation.BorderStroke(2.dp, TableGold.copy(alpha = 0.72f)),
        shadowElevation = 14.dp,
    ) {
        Column(
            modifier = Modifier.padding(horizontal = 12.dp, vertical = 12.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(7.dp),
        ) {
            Text(
                if (summary.wentToShowdown) "SHOWDOWN" else "HAND COMPLETE",
                color = TableGold,
                fontWeight = FontWeight.ExtraBold,
                fontSize = 10.sp,
                letterSpacing = 1.8.sp,
            )
            Text(
                summary.title,
                color = GoldLight,
                fontWeight = FontWeight.ExtraBold,
                fontSize = 24.sp,
            )
            Text(
                summary.detail,
                color = Cream.copy(alpha = 0.94f),
                fontWeight = FontWeight.Medium,
                fontSize = 13.sp,
                textAlign = TextAlign.Center,
            )
            if (summary.wentToShowdown && summary.board.isNotEmpty()) {
                Column(
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.spacedBy(4.dp),
                ) {
                    Text(
                        "BOARD",
                        color = Muted.copy(alpha = 0.9f),
                        fontWeight = FontWeight.Bold,
                        fontSize = 9.sp,
                        letterSpacing = 1.3.sp,
                    )
                    Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
                        summary.board.forEach { card ->
                            CardImage(card, Modifier.size(50.dp, 75.dp))
                        }
                    }
                }
            }
            if (summary.showdownPlayers.isNotEmpty()) {
                Column(
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.spacedBy(4.dp),
                ) {
                    Text(
                        "ALL SHOWDOWN HANDS",
                        color = Muted.copy(alpha = 0.9f),
                        fontWeight = FontWeight.Bold,
                        fontSize = 10.sp,
                        letterSpacing = 1.2.sp,
                    )
                    Row(
                        modifier = Modifier
                            .fillMaxWidth()
                            .horizontalScroll(rememberScrollState()),
                        horizontalArrangement = Arrangement.spacedBy(8.dp),
                    ) {
                        summary.showdownPlayers.forEach { player ->
                            ShowdownPlayerCard(player)
                        }
                    }
                }
            }
            Column(
                modifier = Modifier.fillMaxWidth(),
                verticalArrangement = Arrangement.spacedBy(4.dp),
            ) {
                summary.awards.forEach { award -> PotAwardCard(award) }
            }
        }
    }
}

@Composable
private fun CompletionControls(
    summary: HandSummary,
    nextHandRequested: Boolean,
    rebuyRequired: Boolean,
    buyIn: Int,
    onNextHand: (Int) -> Unit,
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 14.dp, vertical = 9.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(6.dp),
    ) {
        if (rebuyRequired) {
            Text(
                "Your stack cannot cover the big blind.",
                color = Color(0xFFC9545B),
                fontSize = 11.sp,
                fontWeight = FontWeight.Medium,
            )
        }
        Button(
            onClick = { onNextHand(summary.handNumber) },
            enabled = !nextHandRequested,
            modifier = Modifier
                .fillMaxWidth()
                .height(48.dp),
            shape = RoundedCornerShape(13.dp),
            colors = ButtonDefaults.buttonColors(
                containerColor = Color(0xFF1C6848),
                contentColor = Cream,
                disabledContainerColor = Color(0xFF1C6848).copy(alpha = 0.45f),
                disabledContentColor = Cream.copy(alpha = 0.65f),
            ),
        ) {
            Text(
                continuationButtonLabel(nextHandRequested, rebuyRequired, buyIn),
                fontWeight = FontWeight.Bold,
            )
        }
    }
}

@Composable
private fun ShowdownPlayerCard(player: ShowdownPlayerSummary) {
    Card(
        modifier = Modifier.width(118.dp),
        colors = CardDefaults.cardColors(
            containerColor = if (player.amountWon > 0) {
                TableGold.copy(alpha = 0.13f)
            } else {
                Color.White.copy(alpha = 0.06f)
            },
        ),
        border = androidx.compose.foundation.BorderStroke(
            1.dp,
            if (player.amountWon > 0) TableGold.copy(alpha = 0.62f) else Color.White.copy(alpha = 0.1f),
        ),
        shape = RoundedCornerShape(12.dp),
    ) {
        Column(
            modifier = Modifier.padding(7.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(3.dp),
        ) {
            Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
                player.hole.take(2).forEach { card ->
                    CardImage(card, Modifier.size(48.dp, 72.dp))
                }
            }
            Text(
                player.label,
                color = Cream,
                fontWeight = FontWeight.ExtraBold,
                fontSize = 13.sp,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
            )
            Text(
                player.handDescription,
                color = if (player.amountWon > 0) GoldLight else Cream.copy(alpha = 0.84f),
                fontWeight = FontWeight.Bold,
                fontSize = 11.sp,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
            )
            Text(
                if (player.amountWon > 0) "WON ${player.amountWon}" else "SHOWED",
                color = if (player.amountWon > 0) TableGold else Muted,
                fontWeight = FontWeight.ExtraBold,
                fontSize = 10.sp,
            )
        }
    }
}

@Composable
private fun PotAwardCard(award: PotAwardSummary) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.07f)),
        border = androidx.compose.foundation.BorderStroke(1.dp, TableGold.copy(alpha = 0.38f)),
        shape = RoundedCornerShape(10.dp),
    ) {
        Row(
            modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(8.dp),
        ) {
            Text(
                "${award.label.uppercase()}  ${award.amount}",
                color = TableGold,
                fontWeight = FontWeight.ExtraBold,
                fontSize = 11.sp,
                letterSpacing = 0.4.sp,
            )
            Text(
                award.winners.joinToString("  •  ") { "${it.label} ${it.amountWon}" },
                modifier = Modifier.weight(1f),
                color = Cream,
                fontWeight = FontWeight.Bold,
                fontSize = 12.sp,
                textAlign = TextAlign.End,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
            )
        }
    }
}

@Composable
private fun CoachBar(presentation: CoachPresentation) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(12.dp),
        colors = CardDefaults.cardColors(containerColor = Color(0xFF14357A).copy(alpha = 0.48f)),
    ) {
        Column(Modifier.fillMaxWidth().padding(12.dp)) {
            Text(
                "COACH  •  ${presentation.title}",
                color = TableGold,
                fontWeight = FontWeight.Bold,
                fontSize = 12.sp,
            )
            Text(
                presentation.detail,
                color = Cream.copy(alpha = 0.78f),
                fontSize = 12.sp,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
            )
        }
    }
}

@Composable
private fun CardImage(index: Int?, modifier: Modifier) {
    Image(
        painter = painterResource(if (index != null) cardFace(index) else cardBack()),
        contentDescription = index?.let { Card(it).toString() } ?: "Face-down card",
        contentScale = ContentScale.Fit,
        modifier = modifier
            .clip(RoundedCornerShape(6.dp))
            .background(Color(0xFFFFFDF8)),
    )
}
