import SwiftUI
import PokerCore
import PokerCoach
import PokerSession

/// A saved hand, played back a step at a time.
///
/// It borrows the table's vocabulary — the same cards, the same felt, the same chip capsule — so
/// nobody has to learn a second way of reading a poker hand. It deliberately does not behave like
/// the table: it steps backwards as well as forwards, nothing is on a timer, and it never makes a
/// sound. A
/// review screen that starts dealing chips at you is a review screen you cannot think in, and a
/// noise on a screen somebody opened to read quietly is a noise they did not ask for.
///
/// All of the arithmetic — which cards are face up, what the pot stood at, which decision the
/// coach was talking about — is `HandReplay`, in the session layer, where a test can drive it
/// without a screen. This file only draws.
struct HandReplayView: View {
    let record: HandRecord
    /// Back to the hand's summary. The replay is a way in to one hand, not a place to get stuck.
    var close: (() -> Void)?

    @Environment(\.metrics) var metrics
    @State var step = 0
    @State var showing: GlossaryTerm?

    private var replay: HandReplay? { HandReplay(record) }

    var body: some View {
        if let replay {
            content(replay)
        } else {
            // Every hand played before the transcript was kept. It still has its summary; it just
            // cannot be walked through, and saying so is better than drawing an empty table.
            VStack(alignment: .leading, spacing: 8) {
                Text(Copy.cannotReplay).heading().foregroundStyle(Theme.text)
                Text(Copy.cannotReplayBlurb)
                    .body().foregroundStyle(Theme.dim).fixedSize(horizontal: false, vertical: true)
            }
        }
    }

    private func content(_ replay: HandReplay) -> some View {
        let index = min(step, replay.lastIndex)
        let moment = replay.moments[index]
        return VStack(alignment: .leading, spacing: 14) {
            header(replay, moment: moment, index: index)
            streetStrip(replay, moment: moment)
            felt(replay, moment: moment)
            story(replay, moment: moment, index: index)
            if let verdict = moment.verdict { coachRead(verdict) }
            DefinitionSlot(showing: $showing)
            controls(replay, index: index)
        }
    }

    // MARK: - The pieces

    private func header(_ replay: HandReplay, moment: HandReplay.Moment, index: Int) -> some View {
        HStack(alignment: .firstTextBaseline, spacing: 10) {
            VStack(alignment: .leading, spacing: 3) {
                Text("Playing hand \(record.handNumber) back").heading().foregroundStyle(Theme.text)
                Text("Step \(index + 1) of \(replay.moments.count) · you were \(record.seatDescription) "
                     + "of the \(replay.tableSize) dealt in")
                    .caption().foregroundStyle(Theme.dim)
                    .fixedSize(horizontal: false, vertical: true)
            }
            Spacer(minLength: 8)
            if let close {
                Button(action: close) {
                    Text("Summary").body().foregroundStyle(Theme.dim)
                        .padding(.horizontal, 10).padding(.vertical, 6)
                        .background(Capsule().fill(Color.white.opacity(0.06)))
                        .contentShape(Capsule())
                }
                .buttonStyle(.plain)
            }
        }
    }

    /// The rounds of betting, as jumps. Only the ones the hand actually reached are offered — a
    /// hand that ended before the flop has no flop to look at.
    private func streetStrip(_ replay: HandReplay, moment: HandReplay.Moment) -> some View {
        HStack(spacing: 6) {
            ForEach(replay.streetStarts, id: \.index) { start in
                let here = start.street == moment.street
                Button { step = start.index } label: {
                    Text(start.street.name.capitalized)
                        .caption(weight: .medium)
                        .foregroundStyle(here ? Theme.background : Theme.dim)
                        .padding(.horizontal, 9).padding(.vertical, 5)
                        .background(Capsule().fill(here ? Theme.accent : Color.white.opacity(0.06)))
                        .contentShape(Capsule())
                }
                .buttonStyle(.plain)
            }
            Spacer()
        }
    }

    /// The felt as it stood at this moment: the shared cards that are face up, the pot, the
    /// player's own two cards, and a ring showing which seat is speaking.
    private func felt(_ replay: HandReplay, moment: HandReplay.Moment) -> some View {
        let boardSize: CGFloat = metrics.compact ? 34 : 46
        let stack = metrics.compact
        return VStack(alignment: .leading, spacing: 12) {
            let cards = HStack(alignment: .center, spacing: 12) {
                HStack(spacing: 5) {
                    ForEach(0..<5, id: \.self) { slot in
                        if slot < moment.board.count {
                            CardView(card: moment.board[slot], size: boardSize)
                        } else {
                            RoundedRectangle(cornerRadius: boardSize * 0.13, style: .continuous)
                                .fill(Color.black.opacity(0.16))
                                .frame(width: boardSize * 0.72, height: boardSize)
                        }
                    }
                }
                ChipsView(amount: moment.pot, caption: "\(moment.pot) in the middle")
                Spacer(minLength: 0)
            }

            if stack {
                cards
                HStack(alignment: .top, spacing: 14) {
                    yourCards(replay)
                    Spacer(minLength: 0)
                    TableDiagram(players: replay.tableSize,
                                 highlight: moment.position ?? record.seat, base: 108)
                }
            } else {
                HStack(alignment: .center, spacing: 18) {
                    VStack(alignment: .leading, spacing: 12) {
                        cards
                        yourCards(replay)
                    }
                    Spacer(minLength: 0)
                    TableDiagram(players: replay.tableSize,
                                 highlight: moment.position ?? record.seat, base: 130)
                }
            }
        }
        .padding(14)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(RoundedRectangle(cornerRadius: 12, style: .continuous)
            .fill(LinearGradient(colors: [Theme.feltInner.opacity(0.55), Theme.feltOuter.opacity(0.55)],
                                 startPoint: .topLeading, endPoint: .bottomTrailing)))
        .overlay(RoundedRectangle(cornerRadius: 12, style: .continuous)
            .stroke(Theme.rail.opacity(0.7), lineWidth: 1.5))
    }

    private func yourCards(_ replay: HandReplay) -> some View {
        HStack(alignment: .center, spacing: 10) {
            HStack(spacing: 4) {
                Text("You").caption(weight: .medium).foregroundStyle(Theme.hero)
                ForEach(Array(record.cards.enumerated()), id: \.offset) { _, card in
                    CardView(card: card, size: metrics.compact ? 32 : 40)
                }
            }
            if !replay.revealed.isEmpty {
                ForEach(replay.revealed, id: \.seat) { player in
                    HStack(spacing: 4) {
                        Text(player.name).caption(weight: .medium).foregroundStyle(Theme.dim)
                        ForEach(Array(player.holeCards.enumerated()), id: \.offset) { _, card in
                            CardView(card: card, size: metrics.compact ? 26 : 32)
                        }
                    }
                }
            }
        }
    }

    /// What just happened, and the run of actions leading up to it on this round of betting. The
    /// list is what makes the step meaningful — one line on its own is not a hand.
    private func story(_ replay: HandReplay, moment: HandReplay.Moment, index: Int) -> some View {
        let sofar = replay.moments.filter { $0.kind == .acts && $0.street == moment.street && $0.id <= index }
        return VStack(alignment: .leading, spacing: 10) {
            GlossaryText(moment.headline, size: TextRole.body.base,
                         colour: moment.isHero && moment.kind == .acts ? Theme.hero : Theme.text,
                         showing: $showing)

            if !sofar.isEmpty {
                VStack(alignment: .leading, spacing: 0) {
                    ForEach(sofar) { line in
                        actionRow(line, current: line.id == index)
                    }
                }
                .background(RoundedRectangle(cornerRadius: 10, style: .continuous)
                    .fill(Color.black.opacity(0.16)))
            }
        }
    }

    private func actionRow(_ moment: HandReplay.Moment, current: Bool) -> some View {
        HStack(spacing: 10) {
            Text(moment.actor ?? "")
                .body()
                .foregroundStyle(moment.isHero ? Theme.hero : Theme.text)
                .frame(width: Theme.size(76, capped: true), alignment: .leading)
            // The number is the part that means something; the old word rides along under it
            // everywhere else, and there is no room for it in a row this narrow.
            Text(moment.seatPhrase ?? "").caption(weight: .medium).foregroundStyle(Theme.dim)
                .frame(width: Theme.size(74, capped: true), alignment: .leading)
            Text(moment.action ?? "").body()
                .foregroundStyle(current ? Theme.gold : Theme.dim)
            Spacer(minLength: 0)
            Text("\(moment.pot)").numericSmall().foregroundStyle(Theme.dim)
        }
        .padding(.horizontal, 11)
        .padding(.vertical, 6)
        .background(current ? Color.white.opacity(0.06) : Color.clear)
    }

    /// What the coach made of the player's decision at this point — including where it decided to
    /// keep quiet at the table. Saying nothing mid-hand is a rule about interrupting somebody, not
    /// a rule about what they are allowed to read afterwards.
    private func coachRead(_ verdict: VerdictRecord) -> some View {
        Group {
            switch Copy.decisionNote(spoke: verdict.spoke,
                                     countsTowardsLeak: verdict.countsTowardsLeak) {
            case .flagged:
                VerdictCard(record: verdict)
            case .counted:
                // Counted towards a habit even though the coach said nothing at the table. Saying
                // "calling it a mistake would be guessing" here would deny the row this decision is
                // sitting underneath on the My game screen.
                CountedCard(verdict: verdict)
            case .tooClose:
                VStack(alignment: .leading, spacing: 6) {
                    HStack(spacing: 8) {
                        Text("The coach").caption(weight: .medium).foregroundStyle(Theme.dim)
                        Spacer()
                    }
                    Text(Copy.coachSaidNothing(about: verdict.taken))
                        .body().foregroundStyle(Theme.dim)
                        .fixedSize(horizontal: false, vertical: true)
                }
                .padding(13)
                .frame(maxWidth: .infinity, alignment: .leading)
                .background(RoundedRectangle(cornerRadius: 11, style: .continuous)
                    .fill(Theme.panelRaised.opacity(0.6)))
            }
        }
    }

    private func controls(_ replay: HandReplay, index: Int) -> some View {
        HStack(spacing: 10) {
            stepButton("Back", enabled: index > 0) { step = max(0, index - 1) }
            stepButton("Next", enabled: index < replay.lastIndex, filled: true) {
                step = min(replay.lastIndex, index + 1)
            }
            Button { step = 0 } label: {
                Text("Start again").body().foregroundStyle(Theme.dim)
                    .padding(.horizontal, 10).padding(.vertical, 8)
                    .contentShape(Rectangle())
            }
            .buttonStyle(.plain)
            Spacer(minLength: 0)
        }
    }

    private func stepButton(_ title: String, enabled: Bool, filled: Bool = false,
                            action: @escaping () -> Void) -> some View {
        Button(action: action) {
            Text(title).heading(capped: true)
                .foregroundStyle(filled ? Theme.background : Theme.text)
                .frame(minWidth: metrics.compact ? 92 : 84, minHeight: metrics.compact ? 40 : 32)
                .background(RoundedRectangle(cornerRadius: 9, style: .continuous)
                    .fill(filled ? Theme.accent.opacity(0.9) : Color.white.opacity(0.08)))
                .contentShape(RoundedRectangle(cornerRadius: 9, style: .continuous))
                .opacity(enabled ? 1 : 0.35)
        }
        .buttonStyle(.plain)
        .disabled(!enabled)
    }
}
