import Foundation

public enum Street: Int, Comparable, Sendable, CaseIterable, Codable {
    case preflop = 0, flop, turn, river, showdown

    public static func < (a: Street, b: Street) -> Bool { a.rawValue < b.rawValue }

    public var name: String {
        switch self {
        case .preflop: return "preflop"
        case .flop: return "flop"
        case .turn: return "turn"
        case .river: return "river"
        case .showdown: return "showdown"
        }
    }

    /// Community cards dealt when this street begins.
    var cardsDealt: Int {
        switch self {
        case .preflop, .showdown: return 0
        case .flop: return 3
        case .turn, .river: return 1
        }
    }
}

public enum PlayerStatus: Sendable, Equatable, Codable {
    case active      // still has chips and a decision to make
    case folded
    case allIn
    case sittingOut  // busted or not dealt in

    public var isInHand: Bool { self == .active || self == .allIn }
}

/// Seat position relative to the button, which is what strategy is actually indexed by.
public enum Position: String, Sendable, Codable, CaseIterable {
    case smallBlind = "SB"
    case bigBlind = "BB"
    case underTheGun = "UTG"
    case underTheGunPlus1 = "UTG+1"
    case middle = "MP"
    case lojack = "LJ"
    case hijack = "HJ"
    case cutoff = "CO"
    case button = "BTN"

    public var isBlind: Bool { self == .smallBlind || self == .bigBlind }

    public var fullName: String {
        switch self {
        case .smallBlind: return "small blind"
        case .bigBlind: return "big blind"
        case .underTheGun: return "under the gun"
        case .underTheGunPlus1: return "under the gun + 1"
        case .middle: return "middle position"
        case .lojack: return "lojack"
        case .hijack: return "hijack"
        case .cutoff: return "cutoff"
        case .button: return "the button"
        }
    }

    /// Where this seat sits in the order of play: the small blind is 1, counting round the table,
    /// and the button is last. Nil for a seat that does not exist at a table this size.
    ///
    /// This is display only. The engine and the reference ranges key on the case and never on this
    /// number — "UTG+1" is a word you have to be taught, while "seat 4 of 9" is one you can act on
    /// the first time you read it, because subtracting tells you how many people still get to
    /// speak after you.
    ///
    /// Heads-up the button posts the small blind, so following the blind would make the button
    /// seat 1 and stand the whole idea on its head: the number would go *down* as the seat got
    /// better. Two-handed is therefore numbered big blind 1, button 2, which keeps both of the
    /// things the number is meant to say — a higher number acts later once the cards are out, and
    /// the button is always last.
    /// Nine is as large as a table can be numbered because there are nine seat names, which is also
    /// as large as this app ever deals. Anything else gets no number and falls back to the name,
    /// rather than being told it is seat 9 of 10.
    public func number(inTableOf count: Int) -> Int? {
        guard count >= 2, count <= 9 else { return nil }
        if count == 2 {
            switch self {
            case .bigBlind: return 1
            case .button: return 2
            default: return nil
            }
        }
        guard let index = Position.names(forPlayerCount: count).firstIndex(of: self) else { return nil }
        return index + 1
    }

    /// "seat 4 of 9" — what leads wherever a seat is shown to the player.
    public func seatPhrase(inTableOf count: Int) -> String {
        guard let number = number(inTableOf: count) else { return fullName }
        return "seat \(number) of \(count)"
    }

    /// The number with the traditional name kept alongside it, for prose: the number is the part
    /// that means something, but the name is what everyone else at a card table will say.
    public func described(inTableOf count: Int) -> String {
        guard number(inTableOf: count) != nil else { return fullName }
        return "\(seatPhrase(inTableOf: count)) (\(fullName))"
    }

    /// Short enough to print on the felt: "4 UTG+1".
    public func badge(inTableOf count: Int) -> String {
        guard let number = number(inTableOf: count) else { return rawValue }
        return "\(number) \(rawValue)"
    }

    /// What the number actually means, in the only terms that matter to someone learning: how many
    /// people still get to speak after you once the shared cards are down.
    public func actOrderNote(inTableOf count: Int) -> String {
        guard let number = number(inTableOf: count) else { return "" }
        switch count - number {
        case 0:
            return "Nobody acts after you once the shared cards are out, so you decide knowing what "
                + "everyone else has done. It is the best seat at the table."
        case 1:
            return "One player acts after you once the shared cards are out, so there is only ever "
                + "one answer you are missing when you decide."
        case let after:
            let blind = isBlind
                ? " You also put money in before you had seen your cards, which is why this seat "
                  + "loses money over time and is meant to."
                : ""
            return "\(after) players act after you once the shared cards are out, so you have to "
                + "decide before you know what any of them will do.\(blind)"
        }
    }

    /// Names the seats for a table of `count` players, ordered from the small blind around to the button.
    /// Blinds and button always exist; the seats between them are named the way players actually
    /// name them as a table shortens — the first seat after the blinds stays "under the gun".
    public static func names(forPlayerCount count: Int) -> [Position] {
        precondition(count >= 2 && count <= 10, "tables run 2 to 10 handed")
        if count == 2 { return [.button, .bigBlind] }   // heads-up: the button posts the small blind

        let canonical: [Position] = [.underTheGun, .underTheGunPlus1, .middle, .lojack, .hijack, .cutoff]
        // Which middle seats survive as the table shrinks, most-kept first.
        let keepOrder: [Position] = [.underTheGun, .cutoff, .middle, .hijack, .underTheGunPlus1, .lojack]
        let kept = Set(keepOrder.prefix(count - 3))
        let middle = canonical.filter { kept.contains($0) }
        return [.smallBlind, .bigBlind] + middle + [.button]
    }
}

public struct SeatState: Sendable, Equatable {
    public let seat: Int
    public var playerID: String
    public var stack: Int
    public var hole: [Card] = []
    public var status: PlayerStatus = .active

    /// Chips pushed in during the current betting round.
    public var committedThisRound: Int = 0
    /// Chips pushed in across the whole hand — the basis for side pots.
    public var committedThisHand: Int = 0
    /// Antes are dead money: they go straight to the main pot and are never matched by anyone,
    /// so they must stay out of the side-pot layering.
    public var antePaid: Int = 0

    var hasActedThisRound = false
    /// The bet level when this seat last acted voluntarily; nil means it hasn't acted yet.
    /// Used to decide whether an undersized all-in reopens the betting for them.
    var lastActedAtBet: Int? = nil

    public var isInHand: Bool { status.isInHand }
    public var canAct: Bool { status == .active }

    public init(seat: Int, playerID: String, stack: Int) {
        self.seat = seat
        self.playerID = playerID
        self.stack = stack
    }
}

public enum AnteStyle: Sendable, Equatable, Codable {
    case none
    /// Every player antes each hand — the old structure.
    case perPlayer(Int)
    /// The big blind antes for the whole table, which is how modern tournaments run.
    case bigBlindAnte(Int)
}

public struct BlindLevel: Sendable, Equatable, Codable {
    public let smallBlind: Int
    public let bigBlind: Int
    public let ante: AnteStyle

    public init(smallBlind: Int, bigBlind: Int, ante: AnteStyle = .none) {
        self.smallBlind = smallBlind
        self.bigBlind = bigBlind
        self.ante = ante
    }
}

/// How a pot is settled once nobody can bet again.
public enum Settlement: Sendable, Equatable {
    /// Deal the rest of the board and pay the winner. This is poker.
    case runout
    /// Split each pot by the players' chances of winning it, without dealing the cards.
    ///
    /// Not for playing — for measuring. A single all-in swings a hundred big blinds on a river
    /// card, which buries any real difference in skill under noise for tens of thousands of hands.
    /// Paying out by equity asks "who got their money in better", which is the thing worth
    /// measuring, and is the same calculation behind telling a player they played it right and
    /// lost anyway.
    case allInEquity(trials: Int)
}

public struct HandConfig: Sendable, Equatable {
    public let level: BlindLevel
    /// Index into the seat array, not a seat number — the engine works with dense seat arrays.
    public let buttonSeat: Int
    public let seed: UInt64
    public let settlement: Settlement
    /// No-limit unless a table says otherwise, so every hand ever dealt before the cash apps
    /// existed is the hand it always was.
    public let structure: BettingStructure

    public init(level: BlindLevel, buttonSeat: Int, seed: UInt64, settlement: Settlement = .runout,
                structure: BettingStructure = .noLimit) {
        self.level = level
        self.buttonSeat = buttonSeat
        self.seed = seed
        self.settlement = settlement
        self.structure = structure
    }
}

public enum HandError: Error, CustomStringConvertible, Equatable {
    case notYourTurn
    case handComplete
    case illegal(String)

    public var description: String {
        switch self {
        case .notYourTurn: return "it is not that seat's turn to act"
        case .handComplete: return "the hand is already over"
        case .illegal(let why): return "illegal action: \(why)"
        }
    }
}
