import SwiftUI
import PokerCore
import PokerCoach
import PokerSession

/// What a drill is, before anybody agrees to sit through one.
///
/// Said up front for the same reason the assessment says its own awkward part up front: the table
/// is arranged rather than dealt, these hands are kept out of every number on the My game screen,
/// and somebody who reads that at the end and not at the beginning is entitled to feel they were
/// not told.
struct DrillBrief: View {
    @Environment(AppState.self) var app
    @Environment(\.metrics) var metrics
    @State var showing: GlossaryTerm?

    let target: DrillTarget
    let retest: Bool

    init(target: DrillTarget, retest: Bool) {
        self.target = target
        self.retest = retest
    }

    init(leak: Leak, retest: Bool) { self.init(target: DrillTarget(habit: leak), retest: retest) }

    /// Asked for by target rather than by situation, because that is what the drill itself will
    /// do. Asking by situation printed one seat here and dealt another at the table.
    private var setup: AssessmentSetup { DrillPlanner.next(for: target) }

    /// What the seat is worst at, for a seat drill; nil for a habit's own drill.
    private var seatLeading: Leak? {
        guard let seat = target.seat else { return nil }
        return app.seatDiagnosis.report(for: seat)?.leading.first?.leak
    }

    private var hint: String {
        if retest { return Copy.checkingBackHint }
        return target.leak?.drillHint ?? Copy.seatDrillHint
    }

    private var explanation: String {
        if let leak = target.leak { return leak.explanation }
        if let leading = seatLeading {
            return "From \(target.described) your decisions go wrong more often than from anywhere "
                + "else, mostly \(leading.name.lowercasedFirst). \(leading.explanation)"
        }
        return "From \(target.described) your decisions go wrong more often than from anywhere else."
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            VStack(alignment: .leading, spacing: 8) {
                Text(retest ? Copy.checkingBackTitle : Copy.drillTitle(target.name))
                    .title().foregroundStyle(Theme.text)
                GlossaryText(hint, size: TextRole.caption.base, colour: Theme.dim, showing: $showing)
            }
            .padding(metrics.compact ? 16 : 22)

            Divider().overlay(Color.white.opacity(0.07))

            ScrollView {
                VStack(alignment: .leading, spacing: 14) {
                    GlossaryText(explanation, size: TextRole.body.base, colour: Theme.text, showing: $showing)

                    if target.isSeat {
                        DrillPoint(title: Copy.sameSeat,
                                   text: Copy.sameSeatBlurb(seat: target.described, lean: setup.lean.phrase),
                                   showing: $showing)
                        DrillPoint(title: Copy.seatCounted, text: Copy.seatCountedBlurb, showing: $showing)
                    } else {
                        DrillPoint(title: Copy.sameSituation,
                                   text: Copy.sameSituationBlurb(
                                       seat: setup.seat.described(inTableOf: TableSession.seatsAtTable),
                                       depthInBB: setup.depthInBB,
                                       lean: setup.lean.phrase),
                                   showing: $showing)
                    }

                    DrillPoint(title: Copy.cardsStillNotChosen,
                               text: Copy.cardsStillNotChosenBlurb,
                               showing: $showing)

                    DrillPoint(title: retest ? Copy.checkTakesAsLong : Copy.drillEndsWhenStopped,
                               text: retest
                                   ? Copy.checkTakesAsLongBlurb(DrillRule.retestChances)
                                   : Copy.drillEndsWhenStoppedBlurb,
                               showing: $showing)

                    DrillPoint(title: Copy.drillHandsKeptOut,
                               text: Copy.drillHandsKeptOutBlurb,
                               showing: $showing)

                    DefinitionSlot(showing: $showing)
                }
                .padding(metrics.compact ? 18 : 22)
            }

            Divider().overlay(Color.white.opacity(0.07))

            HStack(spacing: 10) {
                PillButton(title: retest ? "Check it" : "Start drilling", wide: metrics.compact) {
                    app.beginDrill(target, retest: retest)
                }
                PillButton(title: "Not now", quiet: true, wide: metrics.compact) {
                    app.dismissSheet()
                }
            }
            .padding(18)
        }
        .frame(width: metrics.compact ? nil : Theme.size(620))
        .frame(maxWidth: metrics.compact ? .infinity : nil,
               maxHeight: metrics.compact ? .infinity : nil, alignment: .top)
        .background(Theme.panel)
    }
}

/// One numbered thing in the brief.
struct DrillPoint: View {
    let title: String
    let text: String
    @Binding var showing: GlossaryTerm?

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(title).heading().foregroundStyle(Theme.accent)
            GlossaryText(text, size: TextRole.body.base, colour: Theme.dim, showing: $showing)
        }
    }
}

/// The band above the felt while a drill runs: what is being practised, how many chances it has
/// had, and what it would take to finish.
struct DrillBanner: View {
    /// Only so an open definition can be closed when the hand moves on. Ken, 2026-08-22:
    /// "a text box turned up toward the top of the screen giving my advice about position
    /// and it is did not go away in the next hand." A definition is about the hand being
    /// played; it has no business outliving it.
    let handNumber: Int
    let run: DrillRun
    @Environment(AppState.self) var app
    @Environment(\.metrics) var metrics
    @State var showing: GlossaryTerm?

    /// What a seat drill is watching for: the habit the seat is worst at, in lower case for the
    /// middle of a sentence.
    private var seatWatching: String? {
        guard let seat = run.target.seat else { return nil }
        return app.seatDiagnosis.report(for: seat)?.leading.first?.leak.name.lowercasedFirst
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 7) {
            HStack(alignment: .firstTextBaseline, spacing: 10) {
                Text(run.isRetest ? "Checking: \(run.target.name)" : Copy.drillTitle(run.target.name))
                    .caption(weight: .medium).foregroundStyle(Theme.accent)
                Spacer(minLength: 8)
                Text(run.progressLine).caption().foregroundStyle(Theme.dim)
            }
            ProgressStrip(fraction: run.fraction)
            // The seat leads with its number here as it does everywhere else — "seat 2 of 9 (the
            // big blind)" says the thing the traditional name was standing in for.
            GlossaryText(run.target.isSeat
                            ? Copy.seatBannerLine(run.target.name, watching: seatWatching)
                            : run.setup.introduction(atTableOf: TableSession.seatsAtTable),
                         size: 11, colour: Theme.text.opacity(0.85), showing: $showing)
            if !metrics.compact {
                GlossaryText(run.targetLine, size: TextRole.caption.base, colour: Theme.dim, showing: $showing)
            }
            DefinitionSlot(showing: $showing)
        }
        .padding(.horizontal, metrics.compact ? 14 : 22)
        .padding(.vertical, 9)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(Theme.panelRaised)
        .onChange(of: handNumber) { _, _ in showing = nil }
    }
}

/// What the drill concluded — including, often enough, that it did not clear.
struct DrillReportView: View {
    @Environment(AppState.self) var app
    @Environment(\.metrics) var metrics
    @State var showing: GlossaryTerm?
    let run: DrillRun

    private var good: Bool { run.cleared || run.passed }

    /// When the next check actually falls, counted off the hands that were just written down.
    ///
    /// The panel used to be gated on `run.cleared`, which only a drill ever sets, so a check that
    /// held up said "I will leave it a while longer and check once more" and then never said when —
    /// on the one screen where the gap has just doubled and the player has no other way of knowing
    /// it. The number is read from the diary rather than from `firstGapInDays`, which is right only
    /// for the first check of the three.
    private var nextCheckInDays: Int {
        let book = app.drills
        if let standing = book.standing(of: run.target.id), let due = standing.dueOn {
            return max(1, standing.days(until: due, from: book.now))
        }
        return Int(DrillRule.gap(afterChecks: run.isRetest ? 1 : 0) / (24 * 60 * 60))
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            VStack(alignment: .leading, spacing: 8) {
                Text(run.outcomeTitle).title()
                    .foregroundStyle(good ? Theme.accent : Theme.warn)
                Text(run.target.name).body().foregroundStyle(Theme.text)
            }
            .padding(metrics.compact ? 16 : 22)

            Divider().overlay(Color.white.opacity(0.07))

            ScrollView {
                VStack(alignment: .leading, spacing: 18) {
                    Panel(title: Copy.whatHappened,
                          subtitle: Copy.drillHandsAndChances(hands: run.handsPlayed,
                                                              chances: run.chances)) {
                        // The counted rate, not the raw one, so the bar and the sentence under it
                        // are the same number.
                        RateBar(rate: run.countedRate, acceptable: run.target.acceptableRate)
                        GlossaryText(run.outcomeLine, size: TextRole.body.base, colour: Theme.text.opacity(0.9),
                                     showing: $showing)
                    }

                    if run.cleared || run.passed {
                        Panel(title: Copy.willCheckBack,
                              subtitle: Copy.inAboutDays(nextCheckInDays)) {
                            // The check is announced — the home card names it, this screen names
                            // it, and a band above the felt names it while it runs. That is the
                            // right design: somebody dropped into a check without being told is
                            // just confused. So the copy says what the days are for, which is the
                            // part that actually does the work, rather than promising a surprise
                            // the app then does not spring.
                            GlossaryText(run.passed ? Copy.checkHeldBlurb : Copy.willCheckBackBlurb,
                                         size: 12, colour: Theme.dim, showing: $showing)
                        }
                    }

                    if run.failed {
                        Panel(title: Copy.backOnYourList) {
                            GlossaryText(Copy.backOnYourListBlurb,
                                         size: 12, colour: Theme.dim, showing: $showing)
                        }
                    }

                    if !run.isRetest, !run.cleared {
                        Panel(title: Copy.whatToDoAboutIt) {
                            GlossaryText(run.leak?.drillHint ?? Copy.seatDrillHint, size: TextRole.body.base, colour: Theme.dim,
                                         showing: $showing)
                            GlossaryText(Copy.stoppingCostsNothing,
                                         size: 12, colour: Theme.dim, showing: $showing)
                        }
                    }

                    DefinitionSlot(showing: $showing)
                }
                .padding(metrics.compact ? 18 : 22)
            }

            Divider().overlay(Color.white.opacity(0.07))

            HStack(spacing: 10) {
                PillButton(title: "See my game", wide: metrics.compact) {
                    app.dismissSheet()
                    app.scope = .allTime
                    app.screen = .leaks
                }
                PillButton(title: "Close", quiet: true, wide: metrics.compact) {
                    app.dismissSheet()
                }
            }
            .padding(18)
        }
        .frame(width: metrics.compact ? nil : Theme.size(640))
        .frame(maxWidth: metrics.compact ? .infinity : nil,
               maxHeight: metrics.compact ? .infinity : nil, alignment: .top)
        .background(Theme.panel)
    }
}

/// The home screen's drill card: a check that has come due, a drill left half finished, or the
/// habit worth working on next. Nothing at all where there is nothing honest to offer.
struct DrillCard: View {
    @Environment(AppState.self) var app
    @Environment(\.metrics) var metrics
    @State var showing: GlossaryTerm?

    var body: some View { content }

    @ViewBuilder private var content: some View {
        let book = app.drills
        if let due = book.dueNow.first {
            Panel(title: Copy.checkIsDue, subtitle: due.target.name) {
                GlossaryText(due.line(now: book.now), size: TextRole.body.base, colour: Theme.dim, showing: $showing)
                DefinitionSlot(showing: $showing)
                HStack(spacing: 10) {
                    PillButton(title: "Check it",
                               subtitle: Copy.chancesAtTheSameSituation(DrillRule.retestChances),
                               wide: metrics.compact) {
                        app.offerDrill(due.target, retest: true)
                    }
                    if !metrics.compact { Spacer() }
                }
            }
        } else if let open = book.unfinished {
            Panel(title: Copy.drillHalfDone, subtitle: open.target.name) {
                GlossaryText(open.line(now: book.now), size: TextRole.body.base, colour: Theme.dim, showing: $showing)
                DefinitionSlot(showing: $showing)
                HStack(spacing: 10) {
                    PillButton(title: "Carry on", wide: metrics.compact) { app.resumeDrill(open) }
                    PillButton(title: "Start it again", quiet: true,
                               wide: metrics.compact) {
                        app.offerDrill(open.target)
                    }
                    if !metrics.compact { Spacer() }
                }
            }
        } else if let worst = app.focus.flatMap({ f in app.allTimeScorecard.costing.first { $0.id == f.id } }) {
            // `focus` is the most expensive habit, or for a free player the one they are shown.
            Panel(title: Copy.practiseOneThing, subtitle: worst.leak.name) {
                GlossaryText(Copy.practiseOneThingBlurb,
                             size: 12, colour: Theme.dim, showing: $showing)
                DefinitionSlot(showing: $showing)
                HStack(spacing: 10) {
                    PillButton(title: "Drill this", wide: metrics.compact) {
                        app.offerDrill(worst.leak)
                    }
                    GhostButton(title: Copy.practiceSomethingElse, wide: metrics.compact) {
                        app.screen = .practice
                    }
                    if !metrics.compact { Spacer() }
                }
            }
        } else {
            // Nothing confirmed yet, which is every new player and most of the first evening.
            // Before this the card simply vanished, so the only way to a drill was through a habit
            // that takes hundreds of hands to appear.
            Panel(title: Copy.practiceTitle, subtitle: Copy.practicePanelSubtitle) {
                GlossaryText(Copy.practiceBlurb, size: TextRole.body.base, colour: Theme.dim, showing: $showing)
                DefinitionSlot(showing: $showing)
                HStack {
                    PillButton(title: Copy.practiceOpen, wide: metrics.compact) {
                        app.screen = .practice
                    }
                    if !metrics.compact { Spacer() }
                }
            }
        }
    }
}

/// The strip under a scorecard row that says where its drill stands, and offers to start one.
struct DrillStrip: View {
    let entry: LeakScorecard.Entry
    let now: Date
    let start: (Leak) -> Void
    @State var showing: GlossaryTerm?

    private var tint: Color {
        switch entry.drill?.stage {
        case .holding, .due: return Theme.accent
        case .reopened, .stalled: return Theme.warn
        default: return Theme.gold
        }
    }

    /// Says what the tap will actually do. `AppState` decides the same thing off the same standing,
    /// so the two cannot say different things.
    private var buttonTitle: String {
        switch entry.drill?.stage {
        case .none, .some(.notStarted): return "Drill this"
        case .some(.running): return "Carry the drill on"
        case .some(.due): return "Check it"
        default: return "Drill it again"
        }
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            if let drill = entry.drill {
                HStack(alignment: .firstTextBaseline, spacing: 6) {
                    Symbol(drill.isHeld ? "checkmark.seal" : "arrow.counterclockwise", size: Theme.size(10, capped: true))
                        .foregroundStyle(tint)
                    Text(drill.badge(now: now)).caption(weight: .medium).foregroundStyle(tint)
                }
                GlossaryText(drill.line(now: now), size: TextRole.caption.base, colour: Theme.dim, showing: $showing)
            }
            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)
            }
            Button { start(entry.leak) } label: {
                Text(buttonTitle).caption(weight: .medium).foregroundStyle(Theme.background)
                    .padding(.horizontal, 12).padding(.vertical, 6)
                    .background(Capsule().fill(Theme.accent))
                    .contentShape(Capsule())
            }
            .buttonStyle(.plain)
            DefinitionSlot(showing: $showing)
        }
    }
}
