import Foundation

/// One of the 169 distinct starting hands — "AKs", "77", "T9o".
///
/// Two hole cards can be dealt 1,326 ways, but only 169 of them play differently, because suits
/// have no rank. Every piece of preflop strategy is indexed by this.
public struct HandClass: Hashable, Sendable, Comparable, CustomStringConvertible, Codable {
    public let high: Rank
    public let low: Rank
    public let suited: Bool

    public init(high: Rank, low: Rank, suited: Bool) {
        precondition(high >= low, "the higher rank comes first")
        precondition(!(high == low && suited), "a pair cannot be suited")
        self.high = high
        self.low = low
        self.suited = suited
    }

    public init(_ cards: [Card]) {
        precondition(cards.count == 2, "a starting hand is two cards")
        let a = cards[0], b = cards[1]
        let high = max(a.rank, b.rank)
        let low = min(a.rank, b.rank)
        self.init(high: high, low: low, suited: a.suit == b.suit && a.rank != b.rank)
    }

    /// Parses "AKs", "77", "T9o".
    public init?(_ text: String) {
        let chars = Array(text)
        guard chars.count == 2 || chars.count == 3,
              let r1 = Rank(letter: chars[0]),
              let r2 = Rank(letter: chars[1])
        else { return nil }
        let suited = chars.count == 3 && Character(String(chars[2]).lowercased()) == "s"
        guard !(r1 == r2 && suited) else { return nil }
        self.init(high: max(r1, r2), low: min(r1, r2), suited: suited)
    }

    public var isPair: Bool { high == low }
    public var isSuited: Bool { suited }
    /// True for hands like AKo or T9s where the two cards touch.
    public var gap: Int { Int(high.rawValue) - Int(low.rawValue) - 1 }
    public var isConnected: Bool { !isPair && gap == 0 }

    /// How many of the 1,326 possible two-card deals belong to this class.
    public var combos: Int { isPair ? 6 : (suited ? 4 : 12) }

    public var description: String {
        isPair ? "\(high.letter)\(low.letter)" : "\(high.letter)\(low.letter)\(suited ? "s" : "o")"
    }

    /// Ordering is by rank only, for stable output — not by strength. Use `PreflopTable` for that.
    public static func < (a: HandClass, b: HandClass) -> Bool {
        if a.high != b.high { return a.high < b.high }
        if a.low != b.low { return a.low < b.low }
        return !a.suited && b.suited
    }

    /// All 169 starting hands.
    public static let all: [HandClass] = {
        var out: [HandClass] = []
        let ranks = Rank.allCases.sorted(by: >)
        for (i, high) in ranks.enumerated() {
            for low in ranks[i...] {
                if high == low {
                    out.append(HandClass(high: high, low: low, suited: false))
                } else {
                    out.append(HandClass(high: high, low: low, suited: true))
                    out.append(HandClass(high: high, low: low, suited: false))
                }
            }
        }
        return out
    }()

    /// Every two-card combination belonging to this class.
    public func combinations() -> [[Card]] {
        var out: [[Card]] = []
        if isPair {
            let cards = Suit.allCases.map { Card(rank: high, suit: $0) }
            for i in 0..<cards.count {
                for j in (i + 1)..<cards.count { out.append([cards[i], cards[j]]) }
            }
        } else if suited {
            for suit in Suit.allCases { out.append([Card(rank: high, suit: suit), Card(rank: low, suit: suit)]) }
        } else {
            for a in Suit.allCases {
                for b in Suit.allCases where a != b {
                    out.append([Card(rank: high, suit: a), Card(rank: low, suit: b)])
                }
            }
        }
        return out
    }
}
