package com.jsjdesigns.poker.core

import kotlin.random.Random

/**
 * Monte Carlo equity: the share of the pot a hand wins on average against random
 * opposition, with ties split.
 *
 * This is the number every bot decision is built on, and the number the coach
 * explains to the player. The LLM is never asked to compute it.
 */
object Equity {

    /**
     * @param hole the two hole cards
     * @param board 0, 3, 4 or 5 community cards
     * @param opponents how many opponents are still live
     * @param iterations rollouts to run; 2000 is accurate to roughly +/-1%
     */
    fun estimate(
        hole: IntArray,
        board: IntArray,
        opponents: Int,
        iterations: Int = 2000,
        random: Random = Random.Default,
    ): Double {
        require(hole.size == 2) { "expected 2 hole cards, got ${hole.size}" }
        require(board.size <= 5) { "board too large: ${board.size}" }
        require(opponents >= 1) { "need at least one opponent" }

        val known = BooleanArray(Card.DECK_SIZE)
        for (c in hole) known[c] = true
        for (c in board) known[c] = true

        val deck = IntArray(Card.DECK_SIZE - hole.size - board.size)
        var n = 0
        for (c in 0 until Card.DECK_SIZE) if (!known[c]) deck[n++] = c

        val boardNeeded = 5 - board.size
        val draws = boardNeeded + opponents * 2
        check(draws <= deck.size) { "not enough cards left to simulate" }

        // hero = [hole0, hole1, board0..board4]
        val hero = IntArray(7)
        hero[0] = hole[0]
        hero[1] = hole[1]
        for (i in board.indices) hero[2 + i] = board[i]

        val opp = IntArray(7)
        val boardFillStart = 2 + board.size
        var won = 0.0

        repeat(iterations) {
            // Partial Fisher-Yates: only shuffle the cards we actually draw.
            for (i in 0 until draws) {
                val j = i + random.nextInt(deck.size - i)
                val tmp = deck[i]; deck[i] = deck[j]; deck[j] = tmp
            }

            var idx = 0
            for (i in 0 until boardNeeded) hero[boardFillStart + i] = deck[idx++]

            val heroScore = HandEvaluator.evaluate(hero, 7)
            for (i in 2..6) opp[i] = hero[i]

            var bestOpp = -1
            var tiedAtBest = 0
            for (o in 0 until opponents) {
                opp[0] = deck[idx++]
                opp[1] = deck[idx++]
                val s = HandEvaluator.evaluate(opp, 7)
                if (s > bestOpp) {
                    bestOpp = s
                    tiedAtBest = 1
                } else if (s == bestOpp) {
                    tiedAtBest++
                }
            }

            if (heroScore > bestOpp) won += 1.0
            else if (heroScore == bestOpp) won += 1.0 / (tiedAtBest + 1)
        }

        return won / iterations
    }

    /**
     * Pot odds as a break-even equity: call [toCall] to win [pot], and you need at
     * least this much equity for the call to show a profit.
     */
    fun potOdds(pot: Int, toCall: Int): Double =
        if (toCall <= 0) 0.0 else toCall.toDouble() / (pot + toCall)
}
