//
//  GameState.swift
//  Baccarat
//
//  Observable game state managing the flow of a baccarat game.
//

import Foundation
import SwiftUI
import CasinoKit

/// The current phase of a baccarat round.
enum GamePhase: Equatable {
    case betting
    case dealingInitial
    case playerThirdCard
    case bankerThirdCard
    case showingResult
    case roundComplete
}

/// Result of an individual bet after a round.
struct BetResult: Identifiable {
    let id = UUID()
    let type: BetType
    let amount: Int
    let payout: Int  // Net winnings (positive) or loss (negative)
    
    var isWin: Bool { payout > 0 }
    var isLoss: Bool { payout < 0 }
    var isPush: Bool { payout == 0 }
    
    /// Display name for the bet type
    var displayName: String {
        switch type {
        case .player: return "Player"
        case .banker: return "Banker"
        case .tie: return "Tie"
        case .playerPair: return "P Pair"
        case .bankerPair: return "B Pair"
        case .dragonBonusPlayer: return "Dragon P"
        case .dragonBonusBanker: return "Dragon B"
        }
    }
}

/// Main observable game state class managing all game logic and UI state.
/// Conforms to CasinoGameState for shared game behaviors.
@Observable
@MainActor
final class GameState: CasinoGameState {
    // MARK: - CasinoGameState Conformance
    
    typealias Stats = BaccaratStats
    typealias GameSettingsType = GameSettings
    
    /// The currently active session.
    var currentSession: BaccaratSession?
    
    /// History of completed sessions.
    var sessionHistory: [BaccaratSession] = []
    
    /// Starting balance for new sessions (from settings).
    var startingBalance: Int { settings.startingBalance }
    
    /// Current game style identifier (deck count for Baccarat).
    var currentGameStyle: String { settings.deckCount.displayName }
    
    /// Whether a session end has been requested (shows confirmation).
    var showEndSessionConfirmation: Bool = false
    
    /// Round histories for completed sessions (keyed by session ID string).
    private var sessionRoundHistories: [String: [SavedRoundResult]] = [:]
    
    // MARK: - Settings
    let settings: GameSettings
    
    // MARK: - Onboarding
    let onboarding: OnboardingState
    
    // MARK: - Sound
    private let sound = SoundManager.shared
    
    // MARK: - Persistence
    let persistence: CloudSyncManager<BaccaratGameData>
    
    // MARK: - Game Engine
    private(set) var engine: BaccaratEngine
    
    // MARK: - Player State
    var balance: Int = 1_000
    var currentBets: [Bet] = []
    
    // MARK: - Round State
    var currentPhase: GamePhase = .betting
    var lastResult: GameResult?
    var lastWinnings: Int = 0
    
    // MARK: - Bet Results
    var playerHadPair: Bool = false
    var bankerHadPair: Bool = false
    var betResults: [BetResult] = []
    
    // MARK: - Card Display State (for animations)
    var visiblePlayerCards: [Card] = []
    var visibleBankerCards: [Card] = []
    var playerCardsFaceUp: [Bool] = []
    var bankerCardsFaceUp: [Bool] = []
    
    // MARK: - History
    var roundHistory: [RoundResult] = []
    
    // MARK: - Animation Flags
    var isAnimating: Bool = false
    var showResultBanner: Bool = false
    
    // MARK: - Computed Properties
    
    var totalBetAmount: Int {
        currentBets.reduce(0) { $0 + $1.amount }
    }
    
    var canPlaceBet: Bool {
        currentPhase == .betting && !isAnimating
    }
    
    var canDeal: Bool {
        currentPhase == .betting && hasMainBet && mainBetMeetsMinimum && !isAnimating
    }
    
    var playerHandValue: Int {
        // Only calculate value from visible face-up cards
        guard visiblePlayerCards.count == playerCardsFaceUp.count else { return 0 }
        guard playerCardsFaceUp.allSatisfy({ $0 }) else {
            // If not all cards are face up, calculate value from only the face-up cards
            let faceUpCards = zip(visiblePlayerCards, playerCardsFaceUp)
                .filter { $1 }
                .map { $0.0 }
            return faceUpCards.reduce(0) { $0 + $1.baccaratValue } % 10
        }
        return engine.playerHand.value
    }
    
    var bankerHandValue: Int {
        // Only calculate value from visible face-up cards
        guard visibleBankerCards.count == bankerCardsFaceUp.count else { return 0 }
        guard bankerCardsFaceUp.allSatisfy({ $0 }) else {
            // If not all cards are face up, calculate value from only the face-up cards
            let faceUpCards = zip(visibleBankerCards, bankerCardsFaceUp)
                .filter { $1 }
                .map { $0.0 }
            return faceUpCards.reduce(0) { $0 + $1.baccaratValue } % 10
        }
        return engine.bankerHand.value
    }
    
    // Recent results for the road display (last 20)
    var recentResults: [RoundResult] {
        Array(roundHistory.suffix(20))
    }
    
    /// Whether the game is over (can't afford to meet minimum bet).
    var isGameOver: Bool {
        currentPhase == .betting && balance < settings.minBet
    }
    
    // MARK: - Hint System
    
    /// The current streak type (player wins, banker wins, or alternating/none).
    var currentStreakInfo: (type: GameResult?, count: Int) {
        guard !roundHistory.isEmpty else { return (nil, 0) }
        
        var streakType: GameResult?
        var count = 0
        
        // Count backwards from most recent, ignoring ties for streak purposes
        for result in roundHistory.reversed() {
            // Skip ties when counting streaks
            if result.result == .tie {
                continue
            }
            
            if streakType == nil {
                streakType = result.result
                count = 1
            } else if result.result == streakType {
                count += 1
            } else {
                break
            }
        }
        
        return (streakType, count)
    }
    
    /// Distribution of results in the current session.
    var resultDistribution: (player: Int, banker: Int, tie: Int) {
        let player = roundHistory.filter { $0.result == .playerWins }.count
        let banker = roundHistory.filter { $0.result == .bankerWins }.count
        let tie = roundHistory.filter { $0.result == .tie }.count
        return (player, banker, tie)
    }
    
    /// Whether the game is "choppy" (alternating frequently between Player and Banker).
    var isChoppy: Bool {
        guard roundHistory.count >= 6 else { return false }
        
        // Check last 6 non-tie results for alternating pattern
        let recentNonTie = roundHistory.suffix(10).filter { $0.result != .tie }.suffix(6)
        guard recentNonTie.count >= 6 else { return false }
        
        var alternations = 0
        var previous: GameResult?
        
        for result in recentNonTie {
            if let prev = previous, prev != result.result {
                alternations += 1
            }
            previous = result.result
        }
        
        // If 4+ alternations in 6 hands, it's choppy
        return alternations >= 4
    }
    
    /// Hint information including text and style for the shared BettingHintView.
    struct HintInfo {
        let text: String
        let secondaryText: String?
        let isStreak: Bool
        let isChoppy: Bool
        let isBankerHot: Bool
        let isPlayerHot: Bool
        
        /// Determines the style for CasinoKit.BettingHintView.
        var style: BettingHintStyle {
            if isStreak { return .streak }
            if isChoppy { return .pattern }
            if isBankerHot { return .custom(.red, "chart.line.uptrend.xyaxis") }
            if isPlayerHot { return .custom(.blue, "chart.line.uptrend.xyaxis") }
            return .neutral
        }
    }
    
    /// Current betting hint for beginners.
    /// Returns nil if hints are disabled or no actionable hint is available.
    var currentHint: String? {
        currentHintInfo?.text
    }
    
    /// Full hint information including style.
    var currentHintInfo: HintInfo? {
        guard settings.showHints else { return nil }
        guard currentPhase == .betting else { return nil }
        
        // If no history, give the fundamental advice
        if roundHistory.isEmpty {
            return HintInfo(
                text: String(localized: "Banker has the lowest house edge (1.06%)"),
                secondaryText: nil,
                isStreak: false,
                isChoppy: false,
                isBankerHot: false,
                isPlayerHot: false
            )
        }
        
        let streak = currentStreakInfo
        let dist = resultDistribution
        let total = dist.player + dist.banker + dist.tie
        
        // Calculate percentages if we have enough data
        if total >= 5 {
            let bankerPct = Double(dist.banker) / Double(total) * 100
            let playerPct = Double(dist.player) / Double(total) * 100
            let trendText = "P: \(Int(playerPct))% | B: \(Int(bankerPct))%"
            
            // Strong streak (4+): suggest following or note it
            if let streakType = streak.type, streak.count >= 4 {
                let streakName = streakType == .bankerWins ? 
                    String(localized: "Banker") : String(localized: "Player")
                return HintInfo(
                    text: String(localized: "\(streakName) streak: \(streak.count) in a row"),
                    secondaryText: trendText,
                    isStreak: true,
                    isChoppy: false,
                    isBankerHot: false,
                    isPlayerHot: false
                )
            }
            
            // Choppy game: note the pattern
            if isChoppy {
                return HintInfo(
                    text: String(localized: "Choppy shoe - results alternating"),
                    secondaryText: trendText,
                    isStreak: false,
                    isChoppy: true,
                    isBankerHot: false,
                    isPlayerHot: false
                )
            }
            
            // Significant imbalance (15%+ difference)
            if bankerPct > playerPct + 15 {
                return HintInfo(
                    text: String(localized: "Banker running hot (\(Int(bankerPct))%)"),
                    secondaryText: nil,
                    isStreak: false,
                    isChoppy: false,
                    isBankerHot: true,
                    isPlayerHot: false
                )
            } else if playerPct > bankerPct + 15 {
                return HintInfo(
                    text: String(localized: "Player running hot (\(Int(playerPct))%)"),
                    secondaryText: nil,
                    isStreak: false,
                    isChoppy: false,
                    isBankerHot: false,
                    isPlayerHot: true
                )
            }
        }
        
        // Default hint: remind about odds
        return HintInfo(
            text: String(localized: "Banker bet has lowest house edge"),
            secondaryText: nil,
            isStreak: false,
            isChoppy: false,
            isBankerHot: false,
            isPlayerHot: false
        )
    }
    
    /// Short trend summary for the top bar or compact display.
    var trendSummary: String? {
        guard settings.showHints else { return nil }
        guard !roundHistory.isEmpty else { return nil }
        
        let streak = currentStreakInfo
        if let streakType = streak.type, streak.count >= 2 {
            let letter = streakType == .bankerWins ? "B" : "P"
            return "\(letter)×\(streak.count)"
        }
        
        return nil
    }
    
    /// Warning message for high house edge bets.
    func warningForBet(_ type: BetType) -> String? {
        guard settings.showHints else { return nil }
        
        switch type {
        case .tie:
            return String(localized: "Tie has 14% house edge")
        case .playerPair, .bankerPair:
            return String(localized: "Pair bets have ~10% house edge")
        case .dragonBonusPlayer, .dragonBonusBanker:
            return String(localized: "Dragon Bonus: high risk, high reward")
        default:
            return nil
        }
    }
    
    // MARK: - Animation Timing (based on settings)
    
    private var dealDelay: Duration {
        .milliseconds(Int(400 * settings.dealingSpeed))
    }
    
    private var flipDelay: Duration {
        .milliseconds(Int(300 * settings.dealingSpeed))
    }
    
    private var shortDelay: Duration {
        .milliseconds(Int(200 * settings.dealingSpeed))
    }
    
    private var resultDelay: Duration {
        .milliseconds(Int(500 * settings.dealingSpeed))
    }
    
    // MARK: - Initialization
    
    /// Convenience initializer that constructs default settings on the main actor.
    convenience init() {
        self.init(settings: GameSettings())
    }

    init(settings: GameSettings) {
        self.settings = settings
        self.engine = BaccaratEngine(deckCount: settings.deckCount.rawValue)
        self.balance = settings.startingBalance
        self.onboarding = OnboardingState(gameIdentifier: "baccarat")
        self.onboarding.registerHintKeys("bettingZone", "dealButton", "firstResult")
        self.persistence = CloudSyncManager<BaccaratGameData>()
        
        // Sync sound settings with SoundManager
        syncSoundSettings()
        
        // Set up iCloud callback
        persistence.onCloudDataReceived = { [weak self] cloudData in
            self?.handleCloudDataReceived(cloudData)
        }
        
        // Load saved game data
        loadSavedGame()
        
        // Ensure we have an active session
        ensureActiveSession()
    }
    
    /// Handles data received from iCloud (e.g., after fresh install or from another device).
    private func handleCloudDataReceived(_ cloudData: BaccaratGameData) {
        // Only update if cloud has more progress than current state
        guard cloudData.roundsPlayed > (currentSession?.roundsPlayed ?? 0) + sessionHistory.reduce(0, { $0 + $1.roundsPlayed }) else {
            return
        }
        
        // Restore balance and sessions
        self.balance = cloudData.balance
        self.currentSession = cloudData.currentSession
        self.sessionHistory = cloudData.sessionHistory
        self.sessionRoundHistories = cloudData.sessionRoundHistories
        
        // Restore round history for road map display
        self.roundHistory = cloudData.currentSessionRoundHistory.compactMap { saved in
            saved.toRoundResult()
        }
    }
    
    // MARK: - Persistence
    
    /// Loads saved game data from iCloud/local storage.
    private func loadSavedGame() {
        let data = persistence.load()
        self.balance = data.balance
        self.currentSession = data.currentSession
        self.sessionHistory = data.sessionHistory
        self.sessionRoundHistories = data.sessionRoundHistories
        
        // Restore round history for road map display
        self.roundHistory = data.currentSessionRoundHistory.compactMap { saved in
            saved.toRoundResult()
        }
        
        CasinoDesign.debugLog("📂 Loaded game data:")
        CasinoDesign.debugLog("   - balance: \(data.balance)")
        CasinoDesign.debugLog("   - currentSession: \(data.currentSession?.id.uuidString ?? "none")")
        CasinoDesign.debugLog("   - sessionHistory count: \(data.sessionHistory.count)")
        CasinoDesign.debugLog("   - roundHistory count: \(roundHistory.count)")
        CasinoDesign.debugLog("   - sessionRoundHistories count: \(sessionRoundHistories.count)")
        if let session = data.currentSession {
            CasinoDesign.debugLog("   - current session rounds: \(session.roundsPlayed), duration: \(session.duration)s")
        }
    }
    
    /// Saves current game state to iCloud/local storage.
    func saveGameData() {
        // Update current session before saving
        if var session = currentSession {
            session.endingBalance = balance
            // Keep session's game style in sync with current settings
            session.gameStyle = currentGameStyle
            currentSession = session
            
            // Always keep the current session's round history in the archive
            // This ensures it's available when the session ends and becomes historical
            if !roundHistory.isEmpty {
                let savedHistory = roundHistory.map { SavedRoundResult(from: $0) }
                sessionRoundHistories[session.id.uuidString] = savedHistory
            }
        }
        
        // Convert round history for persistence
        let savedRoundHistory = roundHistory.map { SavedRoundResult(from: $0) }
        
        let data = BaccaratGameData(
            lastModified: Date(),
            balance: balance,
            currentSession: currentSession,
            sessionHistory: sessionHistory,
            currentSessionRoundHistory: savedRoundHistory,
            sessionRoundHistories: sessionRoundHistories
        )
        persistence.save(data)
        
        CasinoDesign.debugLog("💾 Saved game data - session rounds: \(currentSession?.roundsPlayed ?? 0), road history: \(roundHistory.count)")
    }
    
    /// Whether iCloud sync is available.
    var iCloudAvailable: Bool {
        persistence.iCloudAvailable
    }
    
    /// Whether iCloud sync is enabled.
    var iCloudEnabled: Bool {
        get { persistence.iCloudEnabled }
        set { persistence.iCloudEnabled = newValue }
    }
    
    /// Last sync date.
    var lastSyncDate: Date? {
        persistence.lastSyncDate
    }
    
    /// Forces a sync with iCloud.
    func syncWithCloud() {
        persistence.sync()
    }
    
    /// Syncs sound settings from GameSettings to SoundManager.
    private func syncSoundSettings() {
        sound.soundEnabled = settings.soundEnabled
        sound.hapticsEnabled = settings.hapticsEnabled
        sound.volume = settings.soundVolume
    }
    
    // MARK: - Computed Properties for Bets
    
    /// Returns the current main bet (Player or Banker), if any.
    var mainBet: Bet? {
        currentBets.first(where: { $0.type == .player || $0.type == .banker })
    }
    
    /// Returns the current tie bet, if any.
    var tieBet: Bet? {
        currentBets.first(where: { $0.type == .tie })
    }
    
    /// Returns bets for a specific type.
    func bet(for type: BetType) -> Bet? {
        currentBets.first(where: { $0.type == type })
    }
    
    /// Whether the player has placed a main bet (required to deal).
    var hasMainBet: Bool {
        mainBet != nil
    }
    
    /// Whether the player has any side bets.
    var hasSideBets: Bool {
        currentBets.contains(where: { $0.type.isSideBet })
    }
    
    /// Minimum bet for the table.
    var minBet: Int {
        settings.minBet
    }
    
    /// Maximum bet for the table.
    var maxBet: Int {
        settings.maxBet
    }
    
    /// Returns the current bet amount for a specific bet type.
    func betAmount(for type: BetType) -> Int {
        currentBets.first(where: { $0.type == type })?.amount ?? 0
    }
    
    /// Whether the main bet meets the minimum requirement.
    var mainBetMeetsMinimum: Bool {
        guard let bet = mainBet else { return false }
        return bet.amount >= minBet
    }
    
    /// Whether the main bet exists but is below the minimum required.
    var isMainBetBelowMinimum: Bool {
        guard let bet = mainBet else { return false }
        return bet.amount < minBet
    }
    
    /// Which main bet is placed - nil if no main bet, true if Player, false if Banker.
    /// Used to determine card layout ordering (betted hand appears on bottom).
    var bettedOnPlayer: Bool? {
        guard let bet = mainBet else { return nil }
        return bet.type == .player
    }
    
    /// Amount needed to reach the minimum bet.
    var amountNeededForMinimum: Int {
        guard let bet = mainBet else { return minBet }
        return max(0, minBet - bet.amount)
    }
    
    /// Whether a bet type can accept more chips (hasn't hit max).
    func canAddToBet(type: BetType, amount: Int) -> Bool {
        let currentAmount = betAmount(for: type)
        return currentAmount + amount <= maxBet
    }
    
    /// The minimum bet level across all bet types that can accept more chips.
    /// Used by chip selector to determine if chips should be enabled.
    /// Returns the smallest bet so chips stay enabled if ANY bet type can accept more.
    var minBetForChipSelector: Int {
        // All bet types are always available in Baccarat
        let allBetTypes: [BetType] = [
            .player, .banker, .tie,
            .playerPair, .bankerPair,
            .dragonBonusPlayer, .dragonBonusBanker
        ]
        
        // Return the minimum bet amount across all bet types
        // so chips stay enabled if any bet type can accept more
        return allBetTypes.map { betAmount(for: $0) }.min() ?? 0
    }
    
    // MARK: - Betting Actions
    
    /// Places a bet of the specified amount on the given bet type.
    /// Player and Banker are mutually exclusive. Side bets can be added independently.
    /// Enforces min/max table limits.
    func placeBet(type: BetType, amount: Int) {
        guard canPlaceBet, balance >= amount else { return }
        
        // Check if adding this bet would exceed max
        let currentAmount = betAmount(for: type)
        guard currentAmount + amount <= maxBet else { return }
        
        // Handle mutually exclusive Player/Banker bets
        if type == .player || type == .banker {
            // Remove any existing opposite main bet and refund it
            if let existingMainBet = mainBet, existingMainBet.type != type {
                balance += existingMainBet.amount
                currentBets.removeAll(where: { $0.type == existingMainBet.type })
            }
        }
        
        // Check if there's already a bet of this type
        if let index = currentBets.firstIndex(where: { $0.type == type }) {
            // Add to existing bet
            let existingBet = currentBets[index]
            currentBets[index] = Bet(type: type, amount: existingBet.amount + amount)
        } else {
            currentBets.append(Bet(type: type, amount: amount))
        }
        
        balance -= amount
        
        // Play chip placement sound and haptic
        sound.playChipPlace()
    }
    
    /// Clears all current bets and returns the amounts to balance.
    func clearBets() {
        guard canPlaceBet, !currentBets.isEmpty else { return }
        balance += totalBetAmount
        currentBets = []
        
        // Play clear bets sound and haptic
        sound.playClearBets()
    }
    
    /// Undoes the last bet placed.
    func undoLastBet() {
        guard canPlaceBet, let lastBet = currentBets.last else { return }
        balance += lastBet.amount
        currentBets.removeLast()
    }
    
    // MARK: - Game Flow
    
    /// Starts a new round by dealing cards with animation.
    func deal() async {
        guard canDeal else { return }
        
        isAnimating = true
        engine.prepareNewRound()
        
        // Clear visible cards and bet results
        visiblePlayerCards = []
        visibleBankerCards = []
        playerCardsFaceUp = []
        bankerCardsFaceUp = []
        lastResult = nil
        showResultBanner = false
        playerHadPair = false
        bankerHadPair = false
        betResults = []
        
        // Change to dealing phase - triggers layout animation (horizontal to vertical)
        currentPhase = .dealingInitial
        
        // Wait for layout animation to complete before dealing cards
        if settings.showAnimations {
            try? await Task.sleep(for: .seconds(1))
        }
        
        let initialCards = engine.dealInitialCards()
        
        // Check if animations are enabled
        if settings.showAnimations {
            // Animate dealing: P1, B1, P2, B2
            for (index, card) in initialCards.enumerated() {
                try? await Task.sleep(for: dealDelay)
                
                // Play card deal sound
                sound.playCardDeal()
                
                if index % 2 == 0 {
                    visiblePlayerCards.append(card)
                    playerCardsFaceUp.append(false)
                } else {
                    visibleBankerCards.append(card)
                    bankerCardsFaceUp.append(false)
                }
            }
            
            // Brief pause then flip cards
            try? await Task.sleep(for: flipDelay)
            
            // Play card flip sound
            sound.playCardFlip()
            
            // Flip all cards face up
            for i in 0..<playerCardsFaceUp.count {
                playerCardsFaceUp[i] = true
            }
            for i in 0..<bankerCardsFaceUp.count {
                bankerCardsFaceUp[i] = true
            }
            
            try? await Task.sleep(for: resultDelay)
        } else {
            // No animations - show all cards immediately
            for (index, card) in initialCards.enumerated() {
                if index % 2 == 0 {
                    visiblePlayerCards.append(card)
                    playerCardsFaceUp.append(true)
                } else {
                    visibleBankerCards.append(card)
                    bankerCardsFaceUp.append(true)
                }
            }
        }
        
        // Check for naturals
        if engine.playerHand.isNatural || engine.bankerHand.isNatural {
            await showResult()
            return
        }
        
        // Player third card
        currentPhase = .playerThirdCard
        if let playerThird = engine.drawPlayerThirdCard() {
            if settings.showAnimations {
                try? await Task.sleep(for: dealDelay)
                sound.playCardDeal()
                visiblePlayerCards.append(playerThird)
                playerCardsFaceUp.append(false)
                CasinoDesign.debugLog("🃏 Player 3rd card dealt face-down: cards=\(visiblePlayerCards.count), faceUp=\(playerCardsFaceUp)")
                try? await Task.sleep(for: shortDelay)
                sound.playCardFlip()
                playerCardsFaceUp[2] = true
                CasinoDesign.debugLog("🃏 Player 3rd card flipped: cards=\(visiblePlayerCards.count), faceUp=\(playerCardsFaceUp)")
                try? await Task.sleep(for: flipDelay)
            } else {
                visiblePlayerCards.append(playerThird)
                playerCardsFaceUp.append(true)
            }
        }
        
        // Banker third card
        currentPhase = .bankerThirdCard
        if let bankerThird = engine.drawBankerThirdCard() {
            if settings.showAnimations {
                try? await Task.sleep(for: dealDelay)
                sound.playCardDeal()
                visibleBankerCards.append(bankerThird)
                bankerCardsFaceUp.append(false)
                CasinoDesign.debugLog("🃏 Banker 3rd card dealt face-down: cards=\(visibleBankerCards.count), faceUp=\(bankerCardsFaceUp)")
                try? await Task.sleep(for: shortDelay)
                sound.playCardFlip()
                bankerCardsFaceUp[2] = true
                CasinoDesign.debugLog("🃏 Banker 3rd card flipped: cards=\(visibleBankerCards.count), faceUp=\(bankerCardsFaceUp)")
                try? await Task.sleep(for: dealDelay)
            } else {
                visibleBankerCards.append(bankerThird)
                bankerCardsFaceUp.append(true)
            }
        }
        
        await showResult()
    }
    
    /// Shows the result and processes payouts.
    private func showResult() async {
        currentPhase = .showingResult
        
        let result = engine.determineResult()
        lastResult = result
        
        // Record pair results for display
        playerHadPair = engine.playerHasPair
        bankerHadPair = engine.bankerHasPair
        
        // Check for naturals
        let isNatural = engine.playerHand.isNatural || engine.bankerHand.isNatural
        
        // Calculate and apply payouts, track individual results
        var totalWinnings = 0
        var results: [BetResult] = []
        
        // Track dragon bonus wins
        var dragonPlayerWon = false
        var dragonBankerWon = false
        
        for bet in currentBets {
            let payout = engine.calculatePayout(bet: bet, result: result)
            totalWinnings += payout
            
            // Track individual bet result
            results.append(BetResult(type: bet.type, amount: bet.amount, payout: payout))
            
            // Track dragon bonus wins
            if bet.type == .dragonBonusPlayer && payout > 0 {
                dragonPlayerWon = true
            }
            if bet.type == .dragonBonusBanker && payout > 0 {
                dragonBankerWon = true
            }
            
            // Return original bet if not a loss
            if payout >= 0 {
                balance += bet.amount
            }
            
            // Add winnings
            if payout > 0 {
                balance += payout
            }
        }
        
        betResults = results
        lastWinnings = totalWinnings
        
        // Determine round outcome for session stats
        let isWin = totalWinnings > 0
        let isLoss = totalWinnings < 0
        let outcome: RoundOutcome = isWin ? .win : (isLoss ? .lose : .push)
        
        // Capture values for closure
        let roundBetAmount = totalBetAmount
        let wasPlayerWin = result == .playerWins
        let wasBankerWin = result == .bankerWins
        let wasTie = result == .tie
        let hadPlayerPair = playerHadPair
        let hadBankerPair = bankerHadPair
        
        // Record round in session using CasinoKit protocol
        recordSessionRound(
            winnings: totalWinnings,
            betAmount: roundBetAmount,
            outcome: outcome
        ) { stats in
            // Update Baccarat-specific stats
            if isNatural { stats.naturals += 1 }
            if wasPlayerWin { stats.playerWins += 1 }
            if wasBankerWin { stats.bankerWins += 1 }
            if wasTie { stats.ties += 1 }
            if hadPlayerPair { stats.playerPairs += 1 }
            if hadBankerPair { stats.bankerPairs += 1 }
            if dragonPlayerWon { stats.dragonBonusPlayerWins += 1 }
            if dragonBankerWon { stats.dragonBonusBankerWins += 1 }
        }
        
        CasinoDesign.debugLog("📊 Session stats update:")
        CasinoDesign.debugLog("   - roundsPlayed: \(currentSession?.roundsPlayed ?? 0)")
        CasinoDesign.debugLog("   - duration: \(currentSession?.duration ?? 0) seconds")
        
        // Play result sound based on MAIN BET outcome (not total winnings)
        // This way winning the main hand plays win sound even if side bets lost
        let mainBetResult = results.first(where: { $0.type == .player || $0.type == .banker })
        
        if let mainResult = mainBetResult {
            if mainResult.isWin {
                // Main bet won - play win sound
                let maxBetAmount = currentBets.map(\.amount).max() ?? 0
                let isBigWin = totalWinnings >= maxBetAmount * 5 || totalWinnings >= 500
                sound.playWin(isBigWin: isBigWin && totalWinnings > 0)
            } else if mainResult.isPush {
                // Main bet pushed (tie)
                sound.playPush()
            } else {
                // Main bet lost
                sound.playLose()
            }
        } else {
            // No main bet (only side bets) - use total winnings
            if totalWinnings > 0 {
                sound.playWin(isBigWin: false)
            } else if totalWinnings < 0 {
                sound.playLose()
            } else {
                sound.playPush()
            }
        }
        
        // Record result in history (for road map)
        roundHistory.append(RoundResult(
            result: result,
            playerValue: playerHandValue,
            bankerValue: bankerHandValue,
            playerPair: playerHadPair,
            bankerPair: bankerHadPair
        ))
        
        // Save game data to iCloud
        saveGameData()
        
        // Show result banner - stays until user taps New Round
        showResultBanner = true
        currentPhase = .roundComplete
        isAnimating = false
    }
    
    /// Prepares for a new round.
    func newRound() {
        guard currentPhase == .roundComplete else { return }
        
        // Play new round sound
        sound.playNewRound()
        
        // Dismiss result banner
        showResultBanner = false
        
        currentBets = []
        visiblePlayerCards = []
        visibleBankerCards = []
        playerCardsFaceUp = []
        bankerCardsFaceUp = []
        lastResult = nil
        lastWinnings = 0
        playerHadPair = false
        bankerHadPair = false
        betResults = []
        currentPhase = .betting
    }
    
    /// Rebet the same amounts as last round.
    func rebet() {
        guard currentPhase == .roundComplete || (currentPhase == .betting && currentBets.isEmpty) else { return }
        
        if currentPhase == .roundComplete {
            newRound()
        }
    }
    
    // MARK: - Session Cleanup Hooks
    
    /// Clean up round history when deleting a session.
    func onWillDeleteSession(id: UUID) {
        sessionRoundHistories.removeValue(forKey: id.uuidString)
    }
    
    /// Clean up all round histories when deleting all sessions.
    func onWillDeleteAllSessions() {
        sessionRoundHistories.removeAll()
    }
    
    // MARK: - Session Round History
    
    /// Gets round history for a specific session.
    func roundHistory(for session: BaccaratSession) -> [RoundResult] {
        // If it's the current session, return the live round history
        if session.id == currentSession?.id {
            return roundHistory
        }
        
        // Otherwise look up from archived histories
        guard let savedHistory = sessionRoundHistories[session.id.uuidString] else {
            return []
        }
        
        return savedHistory.compactMap { $0.toRoundResult() }
    }
    
    // MARK: - SessionManagedGame Implementation
    
    /// Resets game-specific state when starting a new session.
    func resetForNewSession() {
        // Note: Round history is already archived during saveGameData() calls
        // Just clear the local round history for the new session
        roundHistory = []
        engine = BaccaratEngine(deckCount: settings.deckCount.rawValue)
        newRoundInternal()
    }
    
    /// Internal new round reset (without sound).
    private func newRoundInternal() {
        currentBets = []
        visiblePlayerCards = []
        visibleBankerCards = []
        playerCardsFaceUp = []
        bankerCardsFaceUp = []
        lastResult = nil
        lastWinnings = 0
        playerHadPair = false
        bankerHadPair = false
        betResults = []
        currentPhase = .betting
    }
    
    /// Aggregated Baccarat-specific stats from all sessions.
    var aggregatedBaccaratStats: BaccaratStats {
        allSessions.aggregatedBaccaratStats()
    }
    
    // MARK: - Game Reset
    
    /// Resets the entire game (keeps statistics).
    /// Uses CasinoKit's performResetGame() which properly handles session ending.
    func resetGame() {
        performResetGame()
        // Note: newRoundInternal() is called by resetForNewSession()
        
        // Play new game sound
        sound.playNewRound()
    }
    
    /// Completely clears all saved data and starts fresh.
    func clearAllData() {
        persistence.reset()
        balance = settings.startingBalance
        currentSession = nil
        sessionHistory = []
        sessionRoundHistories = [:]
        roundHistory = []
        startNewSession()
        newRoundInternal()
        
        // Play new game sound
        sound.playNewRound()
    }
    
    /// Applies new settings (call after settings change).
    func applySettings() {
        resetGame()
    }
}
