import SwiftUI struct RoundView: View { @EnvironmentObject private var localization: LocalizationStore @EnvironmentObject private var analytics: HermesAnalyticsClient @StateObject private var playerCoordinator = PlayerCoordinator() @State private var phase: Phase = .preview @State private var selectedOutcomeID: String? = nil @State private var lockAt: Date = Date().addingTimeInterval(47) @State private var transitionTask: Task? private let previewDuration: TimeInterval = 47 private let winningOutcomeID = "home" private enum Phase { case preview case locked case reveal case result } private var selectionOptions: [SelectionOption] { [ SelectionOption( id: "home", title: localization.string(for: "round.home"), subtitle: localization.string(for: "round.selection_prompt"), odds: "1.85" ), SelectionOption( id: "away", title: localization.string(for: "round.away"), subtitle: localization.string(for: "round.selection_prompt"), odds: "2.05" ), ] } private var selectedOutcome: SelectionOption? { guard let selectedOutcomeID else { return nil } return selectionOptions.first { $0.id == selectedOutcomeID } } private var winningOutcome: SelectionOption { selectionOptions.first { $0.id == winningOutcomeID } ?? selectionOptions[0] } var body: some View { TimelineView(.periodic(from: Date(), by: 1)) { context in let remaining = max(lockAt.timeIntervalSince(context.date), 0) let timerLocked = remaining <= 0 let countdownText = Self.countdownText(for: remaining) VStack(alignment: .leading, spacing: HermesTheme.sectionSpacing) { HermesSectionHeader( title: localization.string(for: "round.title"), subtitle: localization.string(for: "round.subtitle") ) videoSection(countdownText: countdownText, remaining: remaining, isTimerLocked: timerLocked) phaseContent(isTimerLocked: timerLocked) } } .hermesCard(elevated: true) .onAppear { startPreview() } .onDisappear { transitionTask?.cancel() playerCoordinator.pause() } } @ViewBuilder private func phaseContent(isTimerLocked: Bool) -> some View { switch phase { case .preview, .locked: SelectionView( statusText: isTimerLocked || phase != .preview ? localization.string(for: "round.locked_label") : localization.string(for: "round.selection_prompt"), options: selectionOptions, selectedOptionID: selectedOutcomeID, isLocked: isTimerLocked || phase != .preview, confirmTitle: localization.string(for: "round.primary_cta"), onSelect: handleSelection, onConfirm: confirmSelection ) case .reveal: RevealView( title: localization.string(for: "reveal.title"), subtitle: localization.string(for: "reveal.subtitle"), statusText: localization.string(for: "reveal.status"), selectionLabel: localization.string(for: "result.selection_label"), selectionValue: selectedOutcome?.title ?? localization.string(for: "round.selection_prompt"), continueTitle: localization.string(for: "reveal.cta"), onContinue: showResult ) case .result: ResultView( title: localization.string(for: "result.title"), subtitle: localization.string(for: "result.subtitle"), selectionLabel: localization.string(for: "result.selection_label"), selectionValue: selectedOutcome?.title ?? localization.string(for: "round.selection_prompt"), outcomeLabel: localization.string(for: "result.outcome_label"), outcomeValue: winningOutcome.title, didWin: selectedOutcomeID == winningOutcomeID, winLabel: localization.string(for: "result.win"), loseLabel: localization.string(for: "result.lose"), nextRoundTitle: localization.string(for: "result.next_round"), onNextRound: resetRound ) } } @ViewBuilder private func videoSection(countdownText: String, remaining: TimeInterval, isTimerLocked: Bool) -> some View { ZStack(alignment: .topTrailing) { StudyVideoPlayerView(coordinator: playerCoordinator) VStack(alignment: .trailing, spacing: 10) { HermesCountdownBadge( label: localization.string(for: "round.countdown_label"), value: countdownText, warning: !isTimerLocked && remaining <= 10 ) HermesMetricPill( label: localization.string(for: "round.odds_label"), value: "1.85 / 2.05" ) } .padding(12) Text(phaseLabel(isTimerLocked: isTimerLocked)) .font(.caption.weight(.bold)) .foregroundStyle(HermesTheme.textPrimary) .padding(.horizontal, 12) .padding(.vertical, 8) .background(.black.opacity(0.35)) .clipShape(Capsule()) .padding(12) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading) } } private func phaseLabel(isTimerLocked: Bool) -> String { switch phase { case .preview: return isTimerLocked ? localization.string(for: "round.locked_label") : localization.string(for: "round.preview_label") case .locked: return localization.string(for: "round.locked_label") case .reveal: return localization.string(for: "reveal.title") case .result: return localization.string(for: "result.title") } } private func startPreview() { transitionTask?.cancel() phase = .preview lockAt = Date().addingTimeInterval(previewDuration) selectedOutcomeID = nil playerCoordinator.restart() analytics.track("round_loaded", attributes: ["screen_name": "round"]) analytics.track("preview_started", attributes: ["screen_name": "round"]) analytics.track("screen_viewed", attributes: ["screen_name": "round"]) } private func handleSelection(_ option: SelectionOption) { guard phase == .preview else { return } selectedOutcomeID = option.id analytics.track("outcome_focused", attributes: ["screen_name": "round", "outcome_id": option.id]) analytics.track("outcome_selected", attributes: ["screen_name": "round", "outcome_id": option.id]) } private func confirmSelection() { guard phase == .preview, let selectedOutcomeID else { return } analytics.track("selection_submitted", attributes: ["screen_name": "round", "outcome_id": selectedOutcomeID]) analytics.track("selection_accepted", attributes: ["screen_name": "round", "outcome_id": selectedOutcomeID]) analytics.track("market_locked", attributes: ["screen_name": "round", "lock_reason": "manual_selection"]) phase = .locked playerCoordinator.pause() transitionTask?.cancel() transitionTask = Task { @MainActor in try? await Task.sleep(nanoseconds: 750_000_000) guard !Task.isCancelled else { return } phase = .reveal } } private func showResult() { analytics.track("reveal_completed", attributes: ["screen_name": "round"]) phase = .result } private func resetRound() { transitionTask?.cancel() selectedOutcomeID = nil phase = .preview lockAt = Date().addingTimeInterval(previewDuration) playerCoordinator.restart() } private static func countdownText(for remaining: TimeInterval) -> String { let totalSeconds = max(Int(remaining.rounded(.down)), 0) let minutes = totalSeconds / 60 let seconds = totalSeconds % 60 return String(format: "%02d:%02d", minutes, seconds) } }