import SwiftUI
import PokerCore
import PokerCoach
import PokerSession

/// The leak scorecard: what is costing the player money, worst first.
///
/// Everything here is read off the saved hands through `LeakScorecard`, so it follows the scope bar
/// at the top of the screen — this sitting, one earlier sitting, or everything ever played — and it
/// is all still there after the app has been closed. The view does no counting and no ranking of its
/// own; the order, the small-sample rules and every sentence of the honest ones come out of
/// `LeakScorecard`, which is testable, while this file only draws them.
struct ScorecardBoard: View {
    let scorecard: LeakScorecard
    /// What to do when a row is picked up as something to practise. Passed in rather than reached
    /// for, so this file still knows nothing about how a drill runs.
    var drill: ((Leak) -> Void)? = nil
    /// What a free player may read of this. `.open` for a subscriber and everywhere the card is
    /// drawn with nobody to charge.
    var free: FreeView = .open
    /// Where a blurred row sends a tap.
    var showPrice: () -> Void = {}
    @State var showing: GlossaryTerm?

    var body: some View {
        VStack(alignment: .leading, spacing: 14) {
            GlossaryText(scorecard.summary, colour: Theme.text, showing: $showing)
                .fixedSize(horizontal: false, vertical: true)
            if let note = scorecard.sampleNote {
                Text(note).caption().foregroundStyle(Theme.dim)
                    .fixedSize(horizontal: false, vertical: true)
            }
            DefinitionSlot(showing: $showing)

            if scorecard.costing.isEmpty {
                Text(Copy.scorecardEmpty)
                    .body().foregroundStyle(Theme.dim)
                    .fixedSize(horizontal: false, vertical: true)
            } else {
                if free.isRestricted, free.hiddenCount > 0, free.basis == .named {
                    HiddenCountLine(count: free.hiddenCount, open: showPrice)
                }
                VStack(spacing: 18) {
                    ForEach(Array(scorecard.costing.enumerated()), id: \.element.id) { place, entry in
                        ScorecardRow(entry: entry, place: place + 1, now: scorecard.asOf, drill: drill,
                                     freeLabel: free.isRestricted && entry.id == free.visibleLeakID
                                        ? Copy.freeRowNamed : nil)
                            .hiddenUntilSubscribed(free.isBlurred(leakID: entry.id), open: showPrice)
                    }
                }
            }
        }
    }
}

/// "N more habits are in your game." — the line over the blurred rows.
struct HiddenCountLine: View {
    let count: Int
    let open: () -> Void

    var body: some View {
        Button(action: open) {
            HStack(alignment: .firstTextBaseline, spacing: 6) {
                Symbol("lock.fill", size: Theme.size(10, capped: true)).foregroundStyle(Theme.gold)
                Text(Copy.hiddenHabits(count)).caption(weight: .medium).foregroundStyle(Theme.gold)
                    .fixedSize(horizontal: false, vertical: true)
                Spacer(minLength: 0)
            }
            .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
    }
}

/// One leak, with everything the player needs to decide whether to do something about it: how often
/// it happens, how often it should, what it has cost, where it happens, and what to practise.
struct ScorecardRow: View {
    let entry: LeakScorecard.Entry
    /// Its place in the ranking, or nil for the ones too thin to be ranked at all.
    var place: Int? = nil
    /// The moment the drill dates on this row are read against.
    var now: Date = Date()
    var drill: ((Leak) -> Void)? = nil
    /// The free tier's line over the one row a free player is shown, if this is that row.
    var freeLabel: String? = nil
    @State var showing: GlossaryTerm?

    private var tint: Color { place == nil ? Theme.gold : Theme.warn }

    var body: some View {
        VStack(alignment: .leading, spacing: 7) {
            if let freeLabel {
                Text(freeLabel).caption(weight: .medium).foregroundStyle(Theme.accent)
                    .fixedSize(horizontal: false, vertical: true)
            }
            HStack(alignment: .firstTextBaseline, spacing: 8) {
                if let place {
                    Text("\(place)").numericSmall().foregroundStyle(Theme.dim)
                        .frame(width: Theme.size(16), alignment: .trailing)
                }
                Text(entry.leak.name).heading().foregroundStyle(tint)
                Spacer(minLength: 8)
                cost
            }
            RateBar(rate: entry.rate, acceptable: entry.leak.acceptableRate)
            Text(entry.frequencyLine).caption().foregroundStyle(Theme.dim)
                .fixedSize(horizontal: false, vertical: true)
            if let priced = entry.pricedNote {
                Text(priced).caption().foregroundStyle(Theme.dim)
                    .fixedSize(horizontal: false, vertical: true)
            }
            GlossaryText(entry.leak.explanation, showing: $showing)
            if let seatLine = entry.seatLine {
                GlossaryText(seatLine, size: TextRole.caption.base, colour: Theme.text.opacity(0.8), showing: $showing)
            }
            if let note = entry.sampleNote {
                HStack(alignment: .firstTextBaseline, spacing: 6) {
                    Symbol("exclamationmark.circle", size: Theme.size(10, capped: true)).foregroundStyle(Theme.gold)
                    Text(note).caption().foregroundStyle(Theme.gold.opacity(0.9))
                        .fixedSize(horizontal: false, vertical: true)
                }
            }
            if let drill {
                DrillStrip(entry: entry, now: now, start: drill)
            } else {
                HStack(alignment: .firstTextBaseline, spacing: 6) {
                    Symbol("target", size: Theme.size(10, capped: true))
                        .foregroundStyle(Theme.accent)
                    GlossaryText(Copy.drillHintLine(entry.leak.drillHint), size: TextRole.caption.base,
                                 colour: Theme.accent.opacity(0.85), showing: $showing)
                }
            }
            DefinitionSlot(showing: $showing)
        }
    }

    /// The money, or an honest blank where there is no honest number to print. A nought here would
    /// read as "this one is free", which is a claim nobody has earned the right to make.
    @ViewBuilder private var cost: some View {
        if let costInBB = entry.costInBB {
            VStack(alignment: .trailing, spacing: 1) {
                Text(String(format: "%.1f bb", costInBB)).numeric().foregroundStyle(Theme.warn)
                Text("so far").caption(weight: .medium).foregroundStyle(Theme.dim)
            }
        } else {
            Text(Copy.costNotKnown).caption().foregroundStyle(Theme.dim)
        }
    }
}

/// The whole catalogue, measured or not — the part of the screen that shows what is going right.
struct ScorecardIndex: View {
    let scorecard: LeakScorecard
    var free: FreeView = .open
    var showPrice: () -> Void = {}
    @Environment(\.metrics) var metrics

    var body: some View {
        VStack(spacing: 10) {
            ForEach(scorecard.allEntries) { entry in
                // A row that has never been measured gives nothing away and stays readable; a
                // measured one is a rate about the player, and is blurred like the rest.
                let hidden = entry.chances > 0 && free.isBlurred(leakID: entry.id)
                HStack(spacing: 12) {
                    Text(entry.leak.name).body()
                        .foregroundStyle(entry.chances > 0 ? Theme.text : Theme.dim.opacity(0.6))
                        .frame(width: Theme.size(metrics.compact ? 150 : 250), alignment: .leading)
                    // No bar at all where the situation has never come up. A nought-length fill is
                    // drawn at a three-point minimum, so an unmeasured habit came out looking
                    // exactly like one measured and clean.
                    RateBar(rate: entry.rate, acceptable: entry.leak.acceptableRate, height: 6,
                            measured: entry.chances > 0)
                    Text(entry.chances > 0 ? "\(entry.times)/\(entry.chances)" : "—")
                        .numericSmall().foregroundStyle(Theme.dim)
                        .frame(width: Theme.size(56), alignment: .trailing)
                }
                .hiddenUntilSubscribed(hidden, open: showPrice)
            }
            HStack {
                Text(Copy.barMarkNote)
                    .caption().foregroundStyle(Theme.dim.opacity(0.8))
                    .fixedSize(horizontal: false, vertical: true)
                Spacer()
            }
        }
    }
}
