# CasinoKit Session System

This document describes the generic session tracking system in CasinoKit that can be used by any casino game.

## Overview

The session system provides:
- **Session tracking**: Track play sessions from start to end
- **Common statistics**: Wins, losses, pushes, bets, duration
- **Game-specific statistics**: Each game can track its own custom stats
- **Session history**: Store and display completed sessions
- **Aggregated statistics**: Combine stats across all sessions

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                         CasinoKit                                │
├─────────────────────────────────────────────────────────────────┤
│  GameSession<Stats>          - Generic session with custom stats │
│  GameSpecificStats           - Protocol for game stats          │
│  SessionManagedGame          - Protocol for game state classes  │
│  AggregatedSessionStats      - Combined stats across sessions   │
│  SessionFormatter            - Formatting utilities             │
│  UI Components               - Reusable session UI views        │
└─────────────────────────────────────────────────────────────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    │                               │
            ┌───────▼───────┐               ┌───────▼───────┐
            │   Blackjack   │               │   Baccarat    │
            ├───────────────┤               ├───────────────┤
            │ BlackjackStats│               │ BaccaratStats │
            │ (implements   │               │ (implements   │
            │ GameSpecific) │               │ GameSpecific) │
            └───────────────┘               └───────────────┘
```

## Core Types

### GameSession<Stats>

A generic session that works with any game-specific stats type.

```swift
public struct GameSession<Stats: GameSpecificStats>: Codable, Identifiable {
    // Identity
    let id: UUID
    let gameStyle: String
    
    // Timing
    let startTime: Date
    var endTime: Date?
    
    // Balance
    let startingBalance: Int
    var endingBalance: Int
    
    // Common statistics (all games track these)
    var roundsPlayed: Int
    var wins: Int
    var losses: Int
    var pushes: Int
    var totalWinnings: Int
    var biggestWin: Int
    var biggestLoss: Int
    var totalBetAmount: Int
    var biggestBet: Int
    
    // Game-specific statistics
    var gameStats: Stats
    
    // Computed
    var isActive: Bool
    var duration: TimeInterval
    var netResult: Int
    var winRate: Double
    var averageBet: Int
}
```

### GameSpecificStats Protocol

Each game implements this to track game-specific statistics.

```swift
public protocol GameSpecificStats: Codable, Equatable, Sendable {
    init()
    var displayItems: [StatDisplayItem] { get }
}
```

**Example: Blackjack Implementation**

```swift
struct BlackjackStats: GameSpecificStats {
    var blackjacks: Int = 0
    var busts: Int = 0
    var surrenders: Int = 0
    var doubles: Int = 0
    var splits: Int = 0
    var insuranceTaken: Int = 0
    var insuranceWon: Int = 0
    
    var displayItems: [StatDisplayItem] {
        [
            StatDisplayItem(icon: "21.circle.fill", iconColor: .yellow, 
                          label: "Blackjacks", value: "\(blackjacks)"),
            StatDisplayItem(icon: "flame.fill", iconColor: .orange,
                          label: "Busts", value: "\(busts)"),
            // ... more items
        ]
    }
}

typealias BlackjackSession = GameSession<BlackjackStats>
```

**Example: Baccarat Implementation**

```swift
struct BaccaratStats: GameSpecificStats {
    var naturals: Int = 0
    var bankerWins: Int = 0
    var playerWins: Int = 0
    var ties: Int = 0
    var playerPairs: Int = 0
    var bankerPairs: Int = 0
    
    var displayItems: [StatDisplayItem] {
        [
            StatDisplayItem(icon: "sparkles", iconColor: .yellow,
                          label: "Naturals", value: "\(naturals)"),
            // ... more items
        ]
    }
}

typealias BaccaratSession = GameSession<BaccaratStats>
```

### SessionManagedGame Protocol

Game state classes conform to this to get session management.

```swift
@MainActor
public protocol SessionManagedGame: AnyObject {
    associatedtype Stats: GameSpecificStats
    
    var currentSession: GameSession<Stats>? { get set }
    var sessionHistory: [GameSession<Stats>] { get set }
    var balance: Int { get set }
    var startingBalance: Int { get }
    var currentGameStyle: String { get }
    
    func saveGameData()
    func resetForNewSession()
}
```

**Default implementations provided:**
- `ensureActiveSession()` - Create session if needed
- `startNewSession()` - Start a fresh session
- `endCurrentSession(reason:)` - End and archive session
- `endSessionAndStartNew()` - End current and start new
- `handleSessionGameOver()` - Handle running out of money
- `recordSessionRound(...)` - Record a round result
- `aggregatedStats` - Get combined stats
- `sessions(forStyle:)` - Filter by game style

## Integration Guide

### Step 1: Create Game-Specific Stats

```swift
// In your game's Models folder
struct MyGameStats: GameSpecificStats {
    var customStat1: Int = 0
    var customStat2: Int = 0
    
    init() {}
    
    var displayItems: [StatDisplayItem] {
        [
            StatDisplayItem(
                icon: "star.fill",
                iconColor: .yellow,
                label: "Custom Stat 1",
                value: "\(customStat1)"
            ),
            // Add more as needed
        ]
    }
}

typealias MyGameSession = GameSession<MyGameStats>
```

### Step 2: Update Game Data for Persistence

```swift
struct MyGameData: PersistableGameData, SessionPersistable {
    var currentSession: MyGameSession?
    var sessionHistory: [MyGameSession]
    var balance: Int
    // ... other data
}
```

### Step 3: Conform GameState to SessionManagedGame

```swift
@Observable
@MainActor
final class GameState: SessionManagedGame {
    typealias Stats = MyGameStats
    
    var currentSession: MyGameSession?
    var sessionHistory: [MyGameSession] = []
    var balance: Int
    
    var startingBalance: Int { settings.startingBalance }
    var currentGameStyle: String { settings.gameStyle.rawValue }
    
    func saveGameData() {
        // Persist to CloudSyncManager
    }
    
    func resetForNewSession() {
        // Reset game-specific state (reshuffle deck, clear history, etc.)
    }
    
    init() {
        // Load saved data...
        ensureActiveSession()
    }
}
```

### Step 4: Record Rounds

```swift
// At the end of each round:
recordSessionRound(
    winnings: roundWinnings,
    betAmount: betAmount,
    outcome: .win // or .lose, .push
) { stats in
    // Update game-specific stats
    if wasSpecialOutcome {
        stats.customStat1 += 1
    }
}
```

### Step 5: Add Aggregation Extension (Optional)

```swift
extension Array where Element == MyGameSession {
    func aggregatedGameStats() -> MyGameStats {
        var combined = MyGameStats()
        for session in self {
            combined.customStat1 += session.gameStats.customStat1
            combined.customStat2 += session.gameStats.customStat2
        }
        return combined
    }
}
```

## UI Components

CasinoKit provides reusable UI components:

### EndSessionButton
A styled button to trigger ending a session.

```swift
EndSessionButton {
    state.showEndSessionConfirmation = true
}
```

### EndSessionConfirmation
A confirmation dialog showing session summary.

```swift
EndSessionConfirmation(
    sessionDuration: session.duration,
    netResult: session.netResult,
    onConfirm: { state.endSessionAndStartNew() },
    onCancel: { dismiss() }
)
```

### CurrentSessionHeader
Header showing active session with end button.

```swift
CurrentSessionHeader(
    duration: session.duration,
    roundsPlayed: session.roundsPlayed,
    netResult: session.netResult,
    onEndSession: { /* show confirmation */ }
)
```

### SessionSummaryRow
A row for displaying a session in a list.

```swift
SessionSummaryRow(
    styleDisplayName: "Vegas Strip",
    duration: session.duration,
    roundsPlayed: session.roundsPlayed,
    netResult: session.netResult,
    startTime: session.startTime,
    isActive: session.isActive,
    endReason: session.endReason
)
```

### GameStatRow
Display a single stat item from `displayItems`.

```swift
ForEach(session.gameStats.displayItems) { item in
    GameStatRow(item: item)
}
```

## SessionFormatter

Utility for formatting session data:

```swift
SessionFormatter.formatDuration(seconds)      // "02h 15min"
SessionFormatter.formatMoney(amount)          // "$500" or "-$250"
SessionFormatter.formatPercent(value)         // "65.5%"
SessionFormatter.formatSessionDate(date)      // "Dec 29, 2024, 10:30 AM"
SessionFormatter.formatRelativeDate(date)     // "2h ago"
```

## Session Flow

```
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  App Launch │────▶│ Load Data   │────▶│ Ensure      │
│             │     │             │     │ Session     │
└─────────────┘     └─────────────┘     └──────┬──────┘
                                               │
                    ┌──────────────────────────┘
                    ▼
┌─────────────────────────────────────────────────────┐
│                  ACTIVE SESSION                      │
│  ┌─────────┐     ┌─────────┐     ┌─────────┐       │
│  │  Bet    │────▶│  Play   │────▶│ Record  │──┐    │
│  │         │     │  Round  │     │ Round   │  │    │
│  └─────────┘     └─────────┘     └─────────┘  │    │
│       ▲                                       │    │
│       └───────────────────────────────────────┘    │
└────────────────────────┬───────────────────────────┘
                         │
         ┌───────────────┼───────────────┐
         ▼               ▼               ▼
┌─────────────┐   ┌─────────────┐   ┌─────────────┐
│ End Session │   │ Game Over   │   │ App Close   │
│ (Manual)    │   │ (Broke)     │   │ (Auto-save) │
└──────┬──────┘   └──────┬──────┘   └─────────────┘
       │                 │
       └────────┬────────┘
                ▼
┌─────────────────────────────────────────────────────┐
│  Session archived to history                         │
│  New session can be started                          │
└─────────────────────────────────────────────────────┘
```

## Best Practices

1. **Always call `ensureActiveSession()` on init** - Guarantees a session exists.

2. **Update balance in session after each round** - The `recordSessionRound` method handles this.

3. **Implement `resetForNewSession()` properly** - Clear round history, reshuffle decks, reset UI state.

4. **Use type aliases for convenience** - `typealias BlackjackSession = GameSession<BlackjackStats>`

5. **Provide aggregation extensions** - For combining game-specific stats across sessions.

6. **Use `StatDisplayItem` for consistent UI** - Makes stats display automatic in UI components.

## Data Storage

Session data is stored via `CloudSyncManager` which handles:
- Local persistence to UserDefaults
- iCloud sync when available
- Conflict resolution using `lastModified` timestamps

The `SessionPersistable` protocol extends `PersistableGameData` to add:
```swift
public protocol SessionPersistable {
    associatedtype Stats: GameSpecificStats
    var currentSession: GameSession<Stats>? { get set }
    var sessionHistory: [GameSession<Stats>] { get set }
}
```

