import Foundation
import PokerCore
import PokerCoach

/// One hand, written down.
///
/// Deliberately made of plain strings and numbers rather than the game's own types. A saved hand
/// has to still be readable after the enums it came from have been renamed or reordered, and this
/// file will outlive several rewrites of everything around it.
public struct HandRecord: Codable, Sendable, Equatable, Identifiable {
    public var id: String { "\(sessionID)-\(handNumber)" }

    public var sessionID: String
    public var sessionStarted: Date
    public var playedAt: Date
    public var handNumber: Int
    public var mode: String
    public var bigBlind: Int
    public var position: String
    /// How many players were dealt into the hand. Without it a stored seat cannot be numbered —
    /// the cutoff is seat 8 of nine and seat 5 of six, and a tournament table passes through both.
    /// Optional because every hand written down before this existed has no such field, and a
    /// required one would make `HistoryStore.load` quietly throw the whole history away.
    public var tableSize: Int?
    /// "Ah Kd" — the shorthand the rest of the project already parses.
    public var holeCards: String
    public var board: String
    public var netChips: Int
    public var wentToShowdown: Bool
    public var sawFlop: Bool
    /// Put money in by choice before the flop. Blinds do not count — that is the whole point of
    /// the number.
    public var putMoneyInPreflop: Bool
    public var raisedPreflop: Bool
    public var verdicts: [VerdictRecord]
    /// The small blind, so a replay can say what the forced bets actually were rather than halving
    /// the big one and hoping.
    public var smallBlind: Int?
    /// Which seat had the dealer button.
    public var buttonSeat: Int?
    /// Everyone who was dealt in. Optional for the same reason as everything else here: a hand
    /// written down before the replay existed has none, and a required field would make
    /// `HistoryStore.load` throw the whole history away a line at a time.
    public var players: [PlayerLine]?
    /// Every action in the hand, in the order they happened. This is what a replay is made of.
    public var actions: [ActionLine]?
    /// What kind of hand this was: "assessment" for one dealt inside a skill assessment, "drill"
    /// for one of a focused set aimed at a single habit, "retest" for the check days after a drill
    /// cleared, and absent for an ordinary hand. Optional, like everything added here after the
    /// fact — a required field would make `HistoryStore.load` throw the whole history away a line
    /// at a time.
    public var kind: String?
    /// For an arranged hand — an assessment hand or a drill hand — how the table was set up for it:
    /// "big-blind", "short-stack". Kept so the file says plainly which hands were arranged and how,
    /// which is the difference between a measurement and a claim.
    public var setup: String?
    /// For a drill hand or a re-test hand, which habit it was aimed at — "bb-overfold".
    ///
    /// This is the whole of how a drill, and the check scheduled days after it clears, survive the
    /// app being closed. There is no separate file holding a date that could disagree with what was
    /// actually played: the hands say which habit was drilled and when, and everything else is
    /// counted back off them. See `DrillBook`.
    public var drill: String?
    /// Cash hands only: the betting structure the table was playing — "noLimit", "potLimit",
    /// "fixedLimit". Absent for a tournament hand, which is always no-limit here.
    public var structure: String?
    /// Cash hands only: how many times the player had bought back in by the end of this hand.
    /// The session result is counted off the hands, so the rebuys have to be in them.
    public var rebuys: Int?
    /// Which rung of the ladder this was played at — "casino-low". Absent for an assessment hand
    /// and for every hand played before the ladder existed, and those hands simply do not count
    /// towards a promotion. Optional, like everything added here after the fact: a required field
    /// would make `HistoryStore.load` throw the whole history away a line at a time.
    ///
    /// This is the whole of how the ladder survives a restart. There is no separate file holding a
    /// rung that could disagree with the hands — the hands say which table they were played at,
    /// and the standing is counted back off them. See `LadderStanding`.
    public var tier: String?

    public init(sessionID: String, sessionStarted: Date, playedAt: Date, handNumber: Int,
                mode: String, bigBlind: Int, position: String, holeCards: String, board: String,
                netChips: Int, wentToShowdown: Bool, sawFlop: Bool, putMoneyInPreflop: Bool,
                raisedPreflop: Bool, verdicts: [VerdictRecord], tableSize: Int? = nil,
                smallBlind: Int? = nil, buttonSeat: Int? = nil,
                players: [PlayerLine]? = nil, actions: [ActionLine]? = nil,
                kind: String? = nil, setup: String? = nil, tier: String? = nil,
                drill: String? = nil, structure: String? = nil, rebuys: Int? = nil) {
        self.kind = kind
        self.setup = setup
        self.tier = tier
        self.drill = drill
        self.structure = structure
        self.rebuys = rebuys
        self.tableSize = tableSize
        self.smallBlind = smallBlind
        self.buttonSeat = buttonSeat
        self.players = players
        self.actions = actions
        self.sessionID = sessionID
        self.sessionStarted = sessionStarted
        self.playedAt = playedAt
        self.handNumber = handNumber
        self.mode = mode
        self.bigBlind = bigBlind
        self.position = position
        self.holeCards = holeCards
        self.board = board
        self.netChips = netChips
        self.wentToShowdown = wentToShowdown
        self.sawFlop = sawFlop
        self.putMoneyInPreflop = putMoneyInPreflop
        self.raisedPreflop = raisedPreflop
        self.verdicts = verdicts
    }

    /// A hand dealt inside a skill assessment, where the seat and the chip stacks were chosen
    /// rather than dealt. Its decisions count towards the leaks; what it won or lost does not
    /// count towards a win rate. See `GameAnalysis` for why those two answers differ.
    public var isAssessment: Bool { kind == HandRecord.assessmentKind }
    public static let assessmentKind = "assessment"

    /// A hand dealt inside a drill: the same situation on purpose, over and over, with the player
    /// knowing which one it is. Its decisions are what the drill is judged on and are deliberately
    /// kept out of the leak scorecard — see `LeakScorecard` for why practice is not evidence.
    public var isDrill: Bool { kind == HandRecord.drillKind }
    public static let drillKind = "drill"

    /// A hand dealt inside the check that runs days after a drill cleared.
    public var isRetest: Bool { kind == HandRecord.retestKind }
    public static let retestKind = "retest"

    /// Any hand where the seat and the chip stacks were chosen rather than dealt. What these won
    /// or lost is not a fair sample of anything, so they stay out of the win rate.
    public var isArranged: Bool { isAssessment || isDrill || isRetest }

    public var netBB: Double { bigBlind > 0 ? Double(netChips) / Double(bigBlind) : 0 }
    public var won: Bool { netChips > 0 }
    /// The verdicts the coach was confident enough to say out loud.
    public var flagged: [VerdictRecord] { verdicts.filter(\.spoke) }
    public var costInBB: Double { flagged.reduce(0) { $0 + $1.costInBB } }
    public var playedWell: Bool { flagged.isEmpty }

    /// The decisions this hand contributes to a habit on the scorecard, whether or not the coach
    /// said anything at the table.
    ///
    /// The two bars differ on purpose — the coach stays quiet on anything under a big blind or so,
    /// while the scorecard counts a third of a big blind made forty times an hour — and that gap is
    /// where the screens used to contradict the card. A hand holding three counted instances of the
    /// habit at the top of somebody's list was shown to them as "clean", with "no comment" against
    /// each one. Every screen they opened to check the coach's work denied the finding. So the
    /// hand-level wording asks this rather than `playedWell`, which stays what it is: whether the
    /// coach spoke.
    public var counted: [VerdictRecord] { verdicts.filter(\.countsTowardsLeak) }
    /// Whether nothing in this hand is behind any row of the scorecard.
    public var nothingCounted: Bool { counted.isEmpty }
    public var cards: [Card] { [Card].parse(holeCards) }
    public var boardCards: [Card] { [Card].parse(board) }

    /// The seat this was played from, if the current build still recognises the name it was
    /// written down under. Parsed back out rather than stored, exactly like the cards.
    public var seat: Position? { Position(rawValue: position) }

    /// How many were dealt in. Hands recorded before the table size was kept read back as a full
    /// nine-handed table, which is what the app deals.
    public var seatsAtTable: Int { tableSize ?? 9 }

    /// "seat 8 of 9" — the number leads everywhere the player reads a seat.
    public var seatPhrase: String { seat?.seatPhrase(inTableOf: seatsAtTable) ?? position }

    /// "seat 8 of 9 (cutoff)".
    public var seatDescription: String { seat?.described(inTableOf: seatsAtTable) ?? position }

    /// Whether enough was written down to play this hand back street by street. False for every
    /// hand recorded before the transcript was kept, which is why the Hands screen still has a
    /// summary to fall back on.
    public var canReplay: Bool {
        guard let actions, let players else { return false }
        return !actions.isEmpty && !players.isEmpty
    }
}

/// One action inside a saved hand: who did what, and what the pot stood at once they had.
///
/// The verb is a word rather than the game's own type, so a hand recorded today still reads after
/// `PlayerAction` has been renamed — which is the rule the whole of this file is built on.
public struct ActionLine: Codable, Sendable, Equatable {
    public var seat: Int
    /// "preflop", "flop", "turn", "river".
    public var street: String
    /// "fold", "check", "call", "bet", "raise", "blind" or "ante".
    public var action: String
    /// The total this seat had in for the round once the action landed. Nought where the action
    /// moved no chips, which is what makes a check and a bet of nothing different things.
    public var to: Int
    public var chipsAdded: Int
    public var potAfter: Int
    /// Blinds and antes: money that went in without anybody choosing to put it in.
    public var forced: Bool

    public init(seat: Int, street: String, action: String, to: Int, chipsAdded: Int,
                potAfter: Int, forced: Bool) {
        self.seat = seat
        self.street = street
        self.action = action
        self.to = to
        self.chipsAdded = chipsAdded
        self.potAfter = potAfter
        self.forced = forced
    }

    public var isForced: Bool { forced }
}

/// One player in a saved hand.
public struct PlayerLine: Codable, Sendable, Equatable {
    public var seat: Int
    public var name: String
    /// The seat name it was written down under — "BTN", "UTG+1". Numbered when it is read back.
    public var position: String
    public var startingStack: Int
    /// "Ah Kd" where the cards were actually seen: the player's own always, an opponent's only
    /// where they were turned over at the end. Empty otherwise — a replay must never show somebody
    /// cards they were not shown at the table.
    public var cards: String
    public var net: Int
    public var isHero: Bool

    public init(seat: Int, name: String, position: String, startingStack: Int, cards: String,
                net: Int, isHero: Bool) {
        self.seat = seat
        self.name = name
        self.position = position
        self.startingStack = startingStack
        self.cards = cards
        self.net = net
        self.isHero = isHero
    }

    public var holeCards: [Card] { [Card].parse(cards) }
    public var seatName: Position? { Position(rawValue: position) }
}

/// One decision inside a hand, as the coach saw it.
public struct VerdictRecord: Codable, Sendable, Equatable {
    public var street: String
    public var spot: String
    public var leak: String
    public var severity: Int
    public var costInBB: Double
    public var headline: String
    public var detail: String
    public var taken: String
    public var best: String
    /// Whether the coach said this out loud. It watches every decision and comments on few.
    public var spoke: Bool
    /// How sure the coach was, nought to one. Optional because hands written down before it was
    /// kept have no such field, and a required one would make `HistoryStore.load` throw the whole
    /// history away a line at a time. Those older hands count towards the scorecard only where the
    /// coach spoke, which is the only place they prove it was confident.
    public var confidence: Double?

    public init(street: String, spot: String, leak: String, severity: Int, costInBB: Double,
                headline: String, detail: String, taken: String, best: String, spoke: Bool,
                confidence: Double? = nil) {
        self.confidence = confidence
        self.street = street
        self.spot = spot
        self.leak = leak
        self.severity = severity
        self.costInBB = costInBB
        self.headline = headline
        self.detail = detail
        self.taken = taken
        self.best = best
        self.spoke = spoke
    }

    public init(_ verdict: Verdict) {
        self.init(street: verdict.street.name,
                  spot: verdict.spot.name,
                  leak: verdict.error?.name ?? "",
                  severity: verdict.severity.rawValue,
                  costInBB: verdict.costInBB,
                  headline: verdict.headline,
                  detail: verdict.detail,
                  taken: "\(verdict.taken)",
                  best: "\(verdict.best)",
                  spoke: verdict.shouldSpeak,
                  confidence: verdict.confidence)
    }

    /// The spot this decision was in, if the current build still recognises the name it was written
    /// down under. Parsed back out rather than stored as the game's own type, exactly like the seat
    /// and the cards.
    public var spotKind: SpotKind? { SpotKind.recorded(spot) }

    /// Which way it went wrong, or nil where nothing did.
    public var errorKind: ErrorKind? { ErrorKind.recorded(leak) }

    /// Whether this decision counts as one instance of a leak.
    ///
    /// Deliberately a lower bar than the one for interrupting the game. Opening too many hands
    /// costs a third of a big blind at a time — never worth stopping someone over, and ruinous
    /// forty times an hour — so the scorecard counts anything the coach put a price on at all.
    /// What it will not do is count a decision it cannot show the coach was sure about: an older
    /// hand with no confidence written down counts only if the coach spoke, because speaking is
    /// itself proof it was over the line.
    public var countsTowardsLeak: Bool {
        guard errorKind != nil, severity >= Severity.slight.rawValue else { return false }
        guard let confidence else { return spoke }
        return confidence >= DecisionEvaluator.confidenceThreshold
    }
}

/// One sitting, summarised for the picker.
public struct SessionSummary: Sendable, Equatable, Identifiable {
    public let id: String
    public let started: Date
    public let mode: String
    public let hands: Int
    public let netChips: Int
    public let netBB: Double
    public let flagged: Int
}
