import Foundation import WatchConnectivity class PhoneConnector: NSObject, ObservableObject, WCSessionDelegate { static let shared = PhoneConnector() var workoutManager: WorkoutManager? { didSet { if let pending = pendingWorkouts { pending.forEach { workoutManager?.addWorkoutFromPhone($0) } pendingWorkouts = nil } } } private var pendingWorkouts: [StoredWorkout]? private override init() { super.init() } // Send completed workout data to phone static func sendCompletedWorkout(_ laps: [[String: Any]], duration: TimeInterval, workoutId: String) { let message: [String: Any] = [ "type": "workoutCompleted", "workoutId": workoutId, "laps": laps, "duration": duration, ] if WCSession.default.isReachable { WCSession.default.sendMessage(message, replyHandler: nil) { error in print("[PhoneConnector] sendMessage failed, falling back to transferUserInfo: \(error)") WCSession.default.transferUserInfo(message) } } else { WCSession.default.transferUserInfo(message) } } // MARK: - WCSessionDelegate func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { print("[PhoneConnector] Activated: state=\(activationState.rawValue), isReachable=\(session.isReachable)") } func sessionReachabilityDidChange(_ session: WCSession) { print("[PhoneConnector] Reachability changed: isReachable=\(session.isReachable)") } func session(_ session: WCSession, didReceiveMessage message: [String: Any]) { print("[PhoneConnector] didReceiveMessage (no reply)") DispatchQueue.main.async { [weak self] in self?.handleMessage(message) } } func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) { print("[PhoneConnector] didReceiveMessage (with reply)") replyHandler(["status": "received"]) DispatchQueue.main.async { [weak self] in self?.handleMessage(message) } } func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any]) { print("[PhoneConnector] didReceiveUserInfo") DispatchQueue.main.async { [weak self] in self?.handleMessage(userInfo) } } private func handleMessage(_ message: [String: Any]) { guard let type = message["type"] as? String else { print("[PhoneConnector] No type in message: \(message.keys)") return } print("[PhoneConnector] Received message type: \(type)") switch type { case "workoutPlan": guard let data = try? JSONSerialization.data(withJSONObject: message) else { print("[PhoneConnector] Failed to serialize message") return } print("[PhoneConnector] JSON data: \(String(data: data, encoding: .utf8) ?? "nil")") do { let workout = try JSONDecoder().decode(StoredWorkout.self, from: data) print("[PhoneConnector] Decoded workout: \(workout.name ?? "nil"), sets: \(workout.sets.count)") if let manager = workoutManager { manager.addWorkoutFromPhone(workout) } else { print("[PhoneConnector] workoutManager not set, queuing workout") if pendingWorkouts == nil { pendingWorkouts = [] } pendingWorkouts?.append(workout) } } catch { print("[PhoneConnector] Decode error: \(error)") } default: print("[PhoneConnector] Unknown type: \(type)") } } }