//
// LiveRunCoverView.swift
// Workouts
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//

import SwiftUI
import SwiftData

/// Full-screen surface shown on a propped-up iPhone while the Apple Watch is running a live
/// exercise (see `LiveProgress`). Rather than a read-only mirror, this presents the iPhone's
/// real `ExerciseProgressView` driver, seeded to the watch's current page — so either device
/// can drive the run and the other follows:
///
///   • Incoming watch frames jump this driver's page (it never re-broadcasts them).
///   • A human transition *here* broadcasts to the watch and persists durably via `SyncEngine`,
///     exactly as the watch's own driver does.
///
/// Auto-advances (rest / timed-work end) are never sent either way — both devices reach them
/// independently off the shared wall-clock anchors carried in each frame.
struct LiveRunCoverView: View {
    /// The frame that triggered presentation — fixes which workout/log this cover drives.
    let frame: LiveProgress
    /// Dismiss + suppress re-presentation of this run (the user tapped close).
    let onClose: () -> Void

    @Environment(SyncEngine.self) private var sync
    @Environment(AppServices.self) private var services
    @Environment(LiveRunState.self) private var liveRun
    @Environment(\.modelContext) private var modelContext

    /// The live workout entity, observed so durable updates the watch drove flow back into our
    /// working copy. Filtered to just this run's id.
    @Query private var workouts: [Workout]

    /// Working copy the driver mutates; resolved from the cache on appear. `nil` until then.
    @State private var doc: WorkoutDocument?

    init(frame: LiveProgress, onClose: @escaping () -> Void) {
        self.frame = frame
        self.onClose = onClose
        let workoutID = frame.workoutID
        _workouts = Query(filter: #Predicate<Workout> { $0.id == workoutID })
    }

    private var workout: Workout? { workouts.first }

    /// The latest frame to follow, scoped to this cover's run.
    private var incomingFrame: LiveProgress? {
        liveRun.current.flatMap { $0.logID == frame.logID ? $0 : nil }
    }

    var body: some View {
        NavigationStack {
            Group {
                if let binding = Binding($doc) {
                    ExerciseProgressView(
                        doc: binding,
                        logID: frame.logID,
                        onChange: { persist() },
                        onLive: { services.watchBridge.sendLiveProgress($0) },
                        onLiveEnded: {
                            services.watchBridge.sendLiveEnded(workoutID: frame.workoutID, logID: frame.logID)
                        },
                        onActivity: { services.liveActivity.update($0) },
                        onActivityEnded: { services.liveActivity.end() },
                        incomingFrame: incomingFrame,
                        // The run screen hides the nav bar (and this toolbar's close button
                        // with it); its own header hosts the close action instead.
                        closeSymbol: "xmark",
                        onClose: onClose
                    )
                } else {
                    // The workout hasn't resolved from the cache yet (or has vanished).
                    ProgressView()
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                }
            }
            .toolbar {
                ToolbarItem(placement: .topBarLeading) {
                    Button(action: onClose) {
                        Image(systemName: "xmark")
                    }
                    .accessibilityLabel("Close")
                }
            }
        }
        .onAppear {
            if doc == nil, let workout { doc = WorkoutDocument(from: workout) }
        }
        .onChange(of: workout?.updatedAt) { _, incoming in
            // Absorb the durable progress the watch drove (currentStateIndex / status). The
            // live *page* is driven by frames, not this — so re-seeding the doc is safe.
            // Strictly newer only (matching the list absorbs): a stale cache echo must not
            // regress the working copy, which the next persist would re-stamp as newest.
            guard let incoming, incoming > (doc?.updatedAt ?? .distantPast), let workout else { return }
            doc = WorkoutDocument(from: workout)
        }
    }

    private func persist() {
        guard let doc else { return }
        let snapshot = doc
        Task { await sync.save(workout: snapshot) }
    }
}
