import Foundation
import PokerCore

/// Preflop hand strength: raw equity, and the ordering real ranges are actually built on.
///
/// Ranking hands purely by equity produces ranges no player would recognise — K2s outranks 76s
/// on raw equity, but every sound chart plays 76s and folds K2s, because a suited connector makes
/// hands that win big pots and a weak ace-x makes hands that lose them. `rangeScore` adds that
/// playability back in, and every range in the app is cut from it.
public enum PreflopTable {

    struct Entry {
        let headsUp: Double
        let multiway: Double
        let rangeScore: Double
    }

    static let entries: [HandClass: Entry] = {
        var out: [HandClass: Entry] = [:]
        for (text, headsUp, multiway) in preflopEquityTable {
            guard let hand = HandClass(text) else { continue }
            out[hand] = Entry(headsUp: headsUp, multiway: multiway, rangeScore: score(hand, headsUp: headsUp))
        }
        return out
    }()

    /// Equity plus playability. The bonuses are small on purpose: they reorder neighbours without
    /// ever promoting a weak hand past a genuinely strong one.
    ///
    /// Two corrections do most of the work, and both are places where raw all-in equity misleads:
    /// pocket pairs are undervalued, because equity-against-a-random-hand ignores how easy they
    /// are to play and how often they flop a set that wins a stack; and big-card-with-a-weak-
    /// kicker hands are badly overvalued, because their equity comes from an ace or king that
    /// makes exactly the top pair which is beaten by a better kicker. That is why every real chart
    /// plays 55 and folds A9o, and why the two are nearly level on raw equity.
    private static func score(_ hand: HandClass, headsUp: Double) -> Double {
        var score = headsUp
        if hand.isPair {
            score += 0.015
            return score
        }
        if hand.suited { score += 0.035 }                       // flushes, and cleaner folds
        switch hand.gap {
        case 0: score += 0.028                                  // connected
        case 1: score += 0.016
        case 2: score += 0.006
        default: break
        }
        // Big card, weak kicker, offsuit: makes dominated top pairs and little else.
        if !hand.suited && hand.high >= .jack && hand.low <= .nine && hand.gap >= 2 {
            score -= 0.035
        }
        return score
    }

    /// Equity against `opponents` random hands, interpolated between the measured endpoints.
    public static func equity(_ hand: HandClass, opponents: Int) -> Double {
        guard let e = entries[hand] else { return 0.5 }
        let n = Double(max(1, min(opponents, 8)))
        // Equity decays smoothly with the field; the two measured points pin the curve.
        let t = (log(n) / log(5.0)).clamped(to: 0...1.6)
        return e.headsUp + (e.multiway - e.headsUp) * t
    }

    public static func equity(_ cards: [Card], opponents: Int) -> Double {
        equity(HandClass(cards), opponents: opponents)
    }

    /// All 169 hands, strongest first, by the ordering ranges are built from.
    public static let ordered: [HandClass] = HandClass.all.sorted {
        (entries[$0]?.rangeScore ?? 0) > (entries[$1]?.rangeScore ?? 0)
    }

    /// Where this hand sits in the deck as a fraction of all 1,326 two-card combinations:
    /// 0 for aces, 1 for the worst hand. A hand is "in the top 20%" when this is at most 0.20.
    ///
    /// Weighting by combinations matters — the 13 pairs are only 6% of deals despite being 8% of
    /// the 169 classes, so counting classes would quietly make every range too tight.
    public static let percentile: [HandClass: Double] = {
        let total = Double(HandClass.all.reduce(0) { $0 + $1.combos })
        var running = 0.0
        var out: [HandClass: Double] = [:]
        for hand in ordered {
            running += Double(hand.combos)
            out[hand] = running / total
        }
        return out
    }()

    public static func percentile(_ hand: HandClass) -> Double { percentile[hand] ?? 1.0 }
    public static func percentile(_ cards: [Card]) -> Double { percentile(HandClass(cards)) }

    /// The strongest `fraction` of all starting hands.
    public static func range(top fraction: Double) -> Set<HandClass> {
        Set(HandClass.all.filter { percentile($0) <= fraction })
    }

    /// Is this hand inside the top `fraction` of hands?
    public static func isInTop(_ fraction: Double, _ cards: [Card]) -> Bool {
        percentile(HandClass(cards)) <= fraction
    }
}

public extension Comparable {
    func clamped(to range: ClosedRange<Self>) -> Self {
        Swift.min(Swift.max(self, range.lowerBound), range.upperBound)
    }
}
