package holdem

import (
	"math/rand/v2"

	"github.com/chehsunliu/poker"

	"pete/internal/games/cards"
)

// How good is this hand, really?
//
// A bot's decision starts here: deal the cards it cannot see, a thousand times,
// and count how often it wins. That number — plus the board's texture, the
// stack-to-pot ratio and the action so far — is the key it looks its trained
// strategy up under.
//
// Monte Carlo rather than exhaustive because exhaustive is 2.1 million river
// runouts against one opponent and rather more against five, and a thousand
// samples puts the estimate inside a percentage point or so. The bot does not
// need the fourth decimal place; it needs to know whether it is ahead.

// Equity is the fraction of runouts a hand wins, ties and loses against the
// given number of unknown opponents.
type Equity struct {
	Win  float64
	Tie  float64
	Loss float64
}

// Strength collapses a result into the one number the policy is keyed on: a tie
// is worth half a win, because that is what half a pot is.
func (e Equity) Strength() float64 { return e.Win + e.Tie*0.5 }

// deck52 is the evaluator's whole deck, built once.
var deck52 = func() []poker.Card {
	d := make([]poker.Card, 0, 52)
	for s := cards.Spades; s <= cards.Clubs; s++ {
		for r := cards.Ace; r <= cards.King; r++ {
			d = append(d, pokerOf[s][r])
		}
	}
	return d
}()

// equityOf runs the simulation. The RNG is threaded like everything else here,
// so a bot's decision replays from the session's seed along with the deal.
func equityOf(hole [2]cards.Card, board []cards.Card, opponents, iterations int, rng *rand.Rand) Equity {
	if opponents < 1 {
		opponents = 1
	}

	h0, h1 := toPoker(hole[0]), toPoker(hole[1])

	// Seven cards, checked by hand. A map here would be the most expensive thing
	// in the trainer, which calls this function millions of times.
	var known [7]poker.Card
	n := 2
	known[0], known[1] = h0, h1
	pb := make([]poker.Card, len(board))
	for i, c := range board {
		pb[i] = toPoker(c)
		known[n] = pb[i]
		n++
	}

	rest := make([]poker.Card, 0, 52)
	for _, c := range deck52 {
		seen := false
		for _, k := range known[:n] {
			if k == c {
				seen = true
				break
			}
		}
		if !seen {
			rest = append(rest, c)
		}
	}

	need := opponents*2 + (5 - len(pb))
	if need > len(rest) {
		return Equity{Tie: 1}
	}

	hero := make([]poker.Card, 7)
	hero[0], hero[1] = h0, h1
	villain := make([]poker.Card, 7)
	full := make([]poker.Card, 5)

	var wins, ties int
	for i := 0; i < iterations; i++ {
		// A partial Fisher-Yates: only the cards actually needed get shuffled into
		// place, which is the difference between this being cheap and being the
		// slowest thing in the request.
		for j := 0; j < need; j++ {
			k := j + rng.IntN(len(rest)-j)
			rest[j], rest[k] = rest[k], rest[j]
		}

		copy(full, pb)
		at := opponents * 2
		for b := len(pb); b < 5; b++ {
			full[b] = rest[at]
			at++
		}

		copy(hero[2:], full)
		mine := poker.Evaluate(hero)

		best := int32(7463) // one worse than the worst real hand
		for o := 0; o < opponents; o++ {
			villain[0], villain[1] = rest[o*2], rest[o*2+1]
			copy(villain[2:], full)
			if r := poker.Evaluate(villain); r < best {
				best = r
			}
		}

		switch {
		case mine < best:
			wins++
		case mine == best:
			ties++
		}
	}

	total := float64(iterations)
	return Equity{
		Win:  float64(wins) / total,
		Tie:  float64(ties) / total,
		Loss: float64(iterations-wins-ties) / total,
	}
}
