import Foundation
import Observation
import SwiftData
import IndieBackup

/// Composition root for the iOS app. Owns the SwiftData cache container and the
/// iCloud sync engine, and drives the one-shot launch sequence. Injected into the
/// view tree via `.environment(...)`.
@Observable
@MainActor
final class AppServices {
    let container: ModelContainer
    let syncEngine: SyncEngine
    /// Local ZIP backup/restore of the iCloud document tree (`backupRoot` = the
    /// container's `Documents/`). Restores rebuild the SwiftData cache via
    /// `SyncEngine.rebuildCache()`; see `AppBackupConfiguration`.
    let backupController: BackupController
    let watchBridge: PhoneConnectivityBridge
    let workoutLauncher = WorkoutLauncher()
    let workoutHealthDeleter = WorkoutHealthDeleter()

    /// Keeps pending local notifications in sync with the schedules' reminder times
    /// (an idempotent full resync on every cache change).
    let reminderScheduler: ReminderScheduler

    /// Speaks exercise instructions aloud (library Speak button + hands-free workout cues).
    /// Shared so a new exercise's cue cancels whatever the last screen was still saying.
    let speechAnnouncer = SpeechAnnouncer()

    /// Owns the active-workout Live Activity (lock screen + Dynamic Island). Driven by the run
    /// flow's `LiveProgress` frames, so locking the phone mid-set doesn't lose the timer.
    let liveActivity = WorkoutLiveActivityController()

    /// Ephemeral live-run state fed by the watch, observed by the mirror UI. Not persisted.
    let liveRunState: LiveRunState

    private var bootstrapTask: Task<Void, Never>?

    init() {
        let container = WorkoutsModelContainer.make()
        self.container = container
        let syncEngine = SyncEngine(container: container)
        self.syncEngine = syncEngine
        self.backupController = BackupController(
            configuration: AppBackupConfiguration(syncEngine: syncEngine)
        )
        let liveRunState = LiveRunState()
        self.liveRunState = liveRunState
        self.watchBridge = PhoneConnectivityBridge(container: container, syncEngine: syncEngine, liveRunState: liveRunState)
        self.reminderScheduler = ReminderScheduler(container: container)
        // Launch the wrist session only when a run actually begins (the first
        // exercise leaves `.notStarted` on the phone) — creating a workout alone is
        // just a peek and must not start anything on the watch. Watch-originated
        // starts don't fire this hook; the watch already owns its session.
        syncEngine.onWorkoutBecameActive = { [weak self] doc in
            guard let self else { return }
            let routine = doc.routineID.flatMap { id in
                CacheMapper.fetchRoutine(id: syncEngine.currentRoutineID(for: id), in: container.mainContext)
            }
            let activityType = routine?.activityTypeEnum ?? .traditionalStrength
            self.workoutLauncher.launchWatchWorkout(activityType: activityType.hkActivityType)
        }
        #if DEBUG
        if ScreenshotSeed.isActive { ScreenshotSeed.populate(container.mainContext) }
        #endif
    }

    /// Launch step: resolve iCloud and reconcile the cache. Idempotent — repeated
    /// callers await the same one-shot task.
    func bootstrap() async {
        if let bootstrapTask { await bootstrapTask.value; return }
        let task = Task { @MainActor [weak self] in
            guard let self else { return }
            // Clear any Live Activity stranded by a previous launch (app killed mid-run).
            await self.liveActivity.endStaleActivities()
            await self.syncEngine.connect()
            self.watchBridge.activate()
            // `activate()` just claimed `onCacheChanged` for the watch push — multiplex
            // rather than replace, so reminder reconciliation rides the same signal.
            let watchPush = self.syncEngine.onCacheChanged
            self.syncEngine.onCacheChanged = { [weak self] in
                watchPush?()
                self?.reminderScheduler.scheduleReconcile()
            }
            // Initial post-bootstrap resync (covers changes that happened while
            // the app wasn't running — e.g. schedules edited on another device).
            self.reminderScheduler.scheduleReconcile()
            // Past the iCloud gate: request the workout-share scope so the user can still
            // delete legacy phone-estimate workouts from Health when deleting them here.
            self.workoutHealthDeleter.authorizeIfNeeded()
        }
        bootstrapTask = task
        await task.value
    }
}
