import Foundation
import Observation
import SwiftData

/// Composition root for the watch app. Owns the local SwiftData cache and the
/// WatchConnectivity bridge. The watch has no iCloud access; all data arrives from
/// the phone via the bridge.
@Observable
@MainActor
final class WatchAppServices {
    let container: ModelContainer
    let bridge: WatchConnectivityBridge

    /// The single owner of workout-session termination. Built in `bootstrap` once we have the
    /// delegate's session manager to reference; nil until then.
    private var sessionCoordinator: WorkoutSessionCoordinator?

    init() {
        let container = WorkoutsModelContainer.make()
        self.container = container
        self.bridge = WatchConnectivityBridge(container: container)
        #if DEBUG
        if ScreenshotSeed.isActive { ScreenshotSeed.populate(container.mainContext) }
        #endif
    }

    /// Wire the session coordinator to the (delegate-owned) session manager, start the bridge,
    /// and seed the coordinator's baseline from whatever state is already applied. The manager
    /// is passed in rather than owned here so the delegate's `handle(_:)` and the view tree's
    /// `.environment(...)` keep referencing the same instance.
    func bootstrap(sessionManager: WorkoutSessionManager) {
        let coordinator = WorkoutSessionCoordinator(
            sessionManager: sessionManager,
            bridge: bridge,
            context: container.mainContext)
        self.sessionCoordinator = coordinator
        // weak break: the coordinator is held here; the bridge only borrows it.
        bridge.onWorkoutsChanged = { [weak coordinator] in coordinator?.reconcile() }
        // Forward each live sample (HR + calories + zone) to a mirroring phone
        // (best-effort, display-only). No cycle: the bridge never references the
        // session manager back.
        sessionManager.onHeartRateSample = { [bridge] sample in bridge.sendLiveSample(sample) }
        bridge.activate()
        // Seed `previouslyActiveIDs` from whatever the bridge already applied at launch, so the
        // first real change is measured against a correct baseline (and the launch race — a
        // running session before the run doc arrives — resolves to `.none`, never a discard).
        coordinator.reconcile()
    }
}
