package com.jsjdesigns.poker

import com.jsjdesigns.poker.core.HandEvaluator
import com.jsjdesigns.poker.game.HandResult
import com.jsjdesigns.poker.game.PotAward
import com.jsjdesigns.poker.game.PotPayout

data class AwardWinner(
    val seat: Int,
    val label: String,
    val amountWon: Int,
    val hole: List<Int>,
    val handDescription: String?,
)

data class PotAwardSummary(
    val label: String,
    val amount: Int,
    val winners: List<AwardWinner>,
)

data class ShowdownPlayerSummary(
    val seat: Int,
    val label: String,
    val hole: List<Int>,
    val handDescription: String,
    /** Total won across every main/side pot; zero means the hand was shown but lost. */
    val amountWon: Int,
)

/**
 * Immutable, presentation-ready outcome for one completed hand.
 *
 * This is built from [HandResult], not inferred from the final table snapshot:
 * side pots can produce several winners and a player's net can differ from the
 * amount of any pot they won.
 */
data class HandSummary(
    val handNumber: Int,
    /** Public community cards retained so the modal can explain the winning hand. */
    val board: List<Int>,
    /** Display labels in the engine's winner order; the human seat is always "You". */
    val winnerLabels: List<String>,
    /** Derived from winner seat indices, never from a player-controlled name. */
    val heroWon: Boolean,
    val potSize: Int,
    val heroNet: Int,
    val wentToShowdown: Boolean,
    /** Every public hand turned face-up, including contenders that won no pot. */
    val showdownPlayers: List<ShowdownPlayerSummary>,
    /** Main/side-pot breakdown with public winning cards and evaluated hand names. */
    val awards: List<PotAwardSummary>,
) {
    val title: String
        get() = when {
            winnerLabels.size == 1 && heroWon -> "You win"
            winnerLabels.size == 1 -> "${winnerLabels.single()} wins"
            else -> "${winnerLabels.joinToString(" & ")} win"
        }

    val detail: String
        get() {
            val ending = when {
                heroNet > 0 -> "You won $heroNet"
                heroNet < 0 -> "You lost ${-heroNet}"
                else -> "You broke even"
            }
            val finish = if (wentToShowdown) "Showdown" else "Uncontested"
            return "$finish  •  Pot $potSize  •  $ending"
        }
}

fun handSummaryFor(
    handNumber: Int,
    result: HandResult,
    seatNames: List<String>,
    heroSeat: Int = HERO_SEAT,
): HandSummary {
    require(heroSeat in result.net.indices) { "hero seat is outside the result" }
    require(result.winners.isNotEmpty()) { "a completed hand must have a winner" }
    require(result.winners.all { it in seatNames.indices }) { "winner seat is outside the roster" }
    require(result.potAwards.isNotEmpty()) { "a completed hand must award at least one pot" }
    require(result.potAwards.sumOf { it.amount } == result.potSize) {
        "pot awards must account for the contested pot"
    }
    val showdownBySeat = result.showdownHands.associateBy { it.seat }
    if (result.wentToShowdown) {
        require(result.potAwards.flatMap { it.payouts }.all { it.seat in showdownBySeat }) {
            "every showdown winner must include its public hand"
        }
    }
    val multiplePots = result.potAwards.size > 1
    val wonBySeat = result.potAwards
        .flatMap(PotAward::payouts)
        .groupBy(PotPayout::seat)
        .mapValues { (_, payouts) -> payouts.sumOf(PotPayout::amount) }
    val showdownPlayers = result.showdownHands.map { showdown ->
        require(showdown.seat in seatNames.indices) {
            "showdown seat is outside the roster"
        }
        ShowdownPlayerSummary(
            seat = showdown.seat,
            label = if (showdown.seat == heroSeat) "You" else seatNames[showdown.seat],
            hole = showdown.hole.toList(),
            handDescription = HandEvaluator.describe(showdown.score),
            amountWon = wonBySeat[showdown.seat] ?: 0,
        )
    }
    val awards = result.potAwards.mapIndexed { index, award ->
        require(award.payouts.all { it.seat in seatNames.indices }) {
            "pot winner seat is outside the roster"
        }
        PotAwardSummary(
            label = when {
                !multiplePots -> "Pot"
                index == 0 -> "Main pot"
                else -> "Side pot $index"
            },
            amount = award.amount,
            winners = award.payouts.map { payout ->
                val showdown = showdownBySeat[payout.seat]
                AwardWinner(
                    seat = payout.seat,
                    label = if (payout.seat == heroSeat) "You" else seatNames[payout.seat],
                    amountWon = payout.amount,
                    hole = showdown?.hole.orEmpty(),
                    handDescription = showdown?.let { HandEvaluator.describe(it.score) },
                )
            },
        )
    }

    return HandSummary(
        handNumber = handNumber,
        board = result.board.toList(),
        winnerLabels = result.winners.map { winner ->
            if (winner == heroSeat) "You" else seatNames[winner]
        },
        heroWon = heroSeat in result.winners,
        potSize = result.potSize,
        heroNet = result.net[heroSeat],
        wentToShowdown = result.wentToShowdown,
        showdownPlayers = showdownPlayers,
        awards = awards,
    )
}
