import PokerCore
import PokerCoach

/// Whether a habit was ever named during a run, rather than only at the end of it.
///
/// The distinction is the whole point. `Coach.profile.confirmed()` is read live: a player who sits
/// down for an evening sees the list at every point along the way, so a bar that convicts them at
/// three hundred hands and clears them again by four thousand has still told them something untrue.
/// Reading the profile only at the end of a long run hides exactly that, and it is a close cousin
/// of the fault the river-call bar was found by — where a floor no run ever reached was doing the
/// work a correct bar should have been doing.
///
/// So this replays the decisions in the order they happened, keeps the same running tallies the
/// coach keeps, and applies the same three tests after every one: enough chances, more often than a
/// steady player, and at least three of them. The moment all three are true of a player with no
/// planted flaw, the bar is wrong.
public struct NamingSweep {

    public struct Moment: Sendable {
        public let leak: Leak
        /// The chances behind it the first time it was named.
        public let chances: Int
        public let times: Int
        public var rate: Double { chances > 0 ? Double(times) / Double(chances) : 0 }
    }

    /// Every habit named at any point in the run, with the numbers it was first named on.
    public let named: [Moment]
    /// How many chances the run produced at each habit, all the way through.
    public let chances: [String: Int]
    /// The rate each habit finished on.
    public let finalRate: [String: Double]
    /// The worst the cumulative rate ever got, at or beyond each of a few chance counts.
    ///
    /// This is the number a floor has to be chosen from. A habit is named the moment the running
    /// rate crosses the bar with the floor behind it, so what matters is not where a flawless
    /// player ends up but the highest his running rate ever reaches once there are enough chances
    /// to be judged on. `worstFrom[id]![25]` is "the worst this ever read once it had 25 chances".
    public let worstFrom: [String: [Int: Double]]

    /// The chance counts `worstFrom` is measured at.
    public static let marks = [12, 25, 50, 75, 100, 150, 200, 300, 500, 800]

    public init(_ coach: Coach) {
        var counts: [String: (chances: Int, times: Int)] = [:]
        var firstNamed: [String: Moment] = [:]
        var worst: [String: [Int: Double]] = [:]
        for review in coach.reviews {
            for verdict in review.verdicts {
                for leak in LeakCatalogue.all where leak.spot == verdict.spot {
                    counts[leak.id, default: (0, 0)].chances += 1
                }
                if verdict.countsTowardsLeak, let error = verdict.error,
                   let leak = LeakCatalogue.matching(spot: verdict.spot, error: error) {
                    counts[leak.id, default: (0, 0)].times += 1
                }
                // Checked after every decision, because that is when a player could read it.
                for leak in LeakCatalogue.nameable {
                    guard let count = counts[leak.id], count.chances > 0 else { continue }
                    let rate = Double(count.times) / Double(count.chances)
                    for mark in NamingSweep.marks where count.chances >= mark && count.times >= 3 {
                        worst[leak.id, default: [:]][mark] = max(worst[leak.id]?[mark] ?? 0, rate)
                    }
                    guard firstNamed[leak.id] == nil,
                          count.chances >= leak.minimumSample, count.times >= 3,
                          rate > leak.acceptableRate
                    else { continue }
                    firstNamed[leak.id] = Moment(leak: leak, chances: count.chances, times: count.times)
                }
            }
        }
        named = LeakCatalogue.nameable.compactMap { firstNamed[$0.id] }
        var chances: [String: Int] = [:]
        var rates: [String: Double] = [:]
        for leak in LeakCatalogue.all {
            guard let count = counts[leak.id] else { continue }
            chances[leak.id] = count.chances
            rates[leak.id] = count.chances > 0 ? Double(count.times) / Double(count.chances) : 0
        }
        self.chances = chances
        self.finalRate = rates
        self.worstFrom = worst
    }
}
