import Foundation
import SwiftData
import WatchConnectivity

/// Phone side of the iPhone↔Watch bridge. The phone owns iCloud Drive; the watch
/// is a thin remote that round-trips through it:
///   • Phone → Watch: pushes all splits + recent workouts as the latest
///     application context whenever the cache changes (local or remote).
///   • Watch → Phone: receives an updated `WorkoutDocument` and applies it via the
///     SyncEngine write path (file → observer → cache → push back).
@MainActor
final class PhoneConnectivityBridge: NSObject {
    private let container: ModelContainer
    private let syncEngine: SyncEngine
    private var session: WCSession?

    private var context: ModelContext { container.mainContext }

    init(container: ModelContainer, syncEngine: SyncEngine) {
        self.container = container
        self.syncEngine = syncEngine
        super.init()
    }

    func activate() {
        guard WCSession.isSupported() else { return }
        let session = WCSession.default
        session.delegate = self
        session.activate()
        self.session = session
        // Push fresh state to the watch whenever the cache changes.
        syncEngine.onCacheChanged = { [weak self] in self?.pushAll() }
    }

    /// Sends the current splits + most-recent workouts to the watch.
    func pushAll() {
        guard let session, session.activationState == .activated, session.isPaired,
              session.isWatchAppInstalled else { return }

        let splits = (try? context.fetch(FetchDescriptor<Split>(sortBy: [SortDescriptor(\.order)]))) ?? []
        var wDesc = FetchDescriptor<Workout>(sortBy: [SortDescriptor(\.start, order: .reverse)])
        wDesc.fetchLimit = 25
        let workouts = (try? context.fetch(wDesc)) ?? []

        let restSeconds = UserDefaults.standard.object(forKey: WCPayload.restSecondsKey) as? Int ?? 45
        let payload = WCPayload.encodeState(
            splits: splits.map(SplitDocument.init(from:)),
            workouts: workouts.map(WorkoutDocument.init(from:)),
            restSeconds: restSeconds
        )
        try? session.updateApplicationContext(payload)
    }

    /// Parse the (non-Sendable) WC dictionary in the nonisolated delegate context,
    /// then hop to the MainActor with only Sendable values.
    nonisolated private func route(_ dict: [String: Any]) {
        switch dict[WCPayload.typeKey] as? String {
        case WCPayload.requestSyncType:
            Task { @MainActor in self.pushAll() }
        case WCPayload.workoutUpdateType:
            if let doc = WCPayload.decodeWorkoutUpdate(dict) {
                Task { @MainActor in await self.syncEngine.ingestFromWatch(doc) }
            }
        default:
            break
        }
    }
}

// MARK: - WCSessionDelegate

extension PhoneConnectivityBridge: WCSessionDelegate {
    nonisolated func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        Task { @MainActor in self.pushAll() }
    }

    nonisolated func sessionDidBecomeInactive(_ session: WCSession) {}

    nonisolated func sessionDidDeactivate(_ session: WCSession) {
        session.activate()   // reactivate for a switched watch
    }

    nonisolated func sessionReachabilityDidChange(_ session: WCSession) {
        if session.isReachable { Task { @MainActor in self.pushAll() } }
    }

    nonisolated func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
        route(message)
    }

    nonisolated func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
        route(userInfo)
    }
}
