import Foundation

public struct EquityResult: Sendable {
    public let win: Double
    public let tie: Double
    public let lose: Double
    public let trials: Int

    /// Share of the pot this hand expects to win, counting split pots proportionally.
    public var equity: Double { win + tie }
}

/// Monte Carlo equity. Deterministic for a given seed so the coach's numbers are reproducible
/// when a hand is replayed.
public enum EquityCalculator {

    /// Hero's equity against a set of opponents. An opponent given as `[]` holds unknown cards.
    public static func equity(
        hero: [Card],
        opponents: [[Card]],
        board: [Card] = [],
        trials: Int = 10_000,
        rng: inout SeededRandom
    ) -> EquityResult {
        precondition(hero.count == 2, "hero needs two hole cards")
        precondition(board.count <= 5, "board can hold at most five cards")
        precondition(!opponents.isEmpty, "need at least one opponent")

        var dead = Set(hero + board)
        for o in opponents { dead.formUnion(o) }
        let stub = Card.all.filter { !dead.contains($0) }

        let boardNeeded = 5 - board.count
        let unknownSeats = opponents.enumerated().filter { $0.element.isEmpty }.map(\.offset)
        let drawCount = boardNeeded + unknownSeats.count * 2

        var wins = 0.0, ties = 0.0, losses = 0.0
        var pool = stub
        var opponentCards = opponents

        for _ in 0..<trials {
            // Partial Fisher-Yates: only the first `drawCount` slots need to be random.
            for i in 0..<drawCount {
                let j = i + Int(rng.next() % UInt64(pool.count - i))
                pool.swapAt(i, j)
            }
            var next = 0
            var fullBoard = board
            for _ in 0..<boardNeeded { fullBoard.append(pool[next]); next += 1 }
            for seat in unknownSeats {
                opponentCards[seat] = [pool[next], pool[next + 1]]
                next += 2
            }

            let heroRank = HandEvaluator.evaluate(hero + fullBoard)
            var bestOpponent = HandRank(value: 0)
            var tiedWithHero = 0
            for o in opponentCards {
                let r = HandEvaluator.evaluate(o + fullBoard)
                if r > bestOpponent { bestOpponent = r; tiedWithHero = 0 }
                if r == heroRank { tiedWithHero += 1 }
            }

            if heroRank > bestOpponent {
                wins += 1
            } else if heroRank == bestOpponent {
                ties += 1.0 / Double(tiedWithHero + 1)
                losses += Double(tiedWithHero) / Double(tiedWithHero + 1)
            } else {
                losses += 1
            }
        }

        let t = Double(trials)
        return EquityResult(win: wins / t, tie: ties / t, lose: losses / t, trials: trials)
    }

    /// Equity against `count` opponents holding unknown cards.
    public static func equity(
        hero: [Card],
        opponentCount: Int,
        board: [Card] = [],
        trials: Int = 10_000,
        rng: inout SeededRandom
    ) -> EquityResult {
        equity(hero: hero, opponents: Array(repeating: [], count: opponentCount), board: board, trials: trials, rng: &rng)
    }
}
