import Foundation import OpenClawKit enum WatchMessagingPayloadCodec { static func nowMs() -> Int { Int(Date().timeIntervalSince1970 * 1000) } static func nonEmpty(_ value: String?) -> String? { let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" return trimmed.isEmpty ? nil : trimmed } static func encodeNotificationPayload( id: String, params: OpenClawWatchNotifyParams) -> [String: Any] { var payload: [String: Any] = [ "type": OpenClawWatchPayloadType.notify.rawValue, "id": id, "title": params.title, "body": params.body, "priority": params.priority?.rawValue ?? OpenClawNotificationPriority.active.rawValue, "sentAtMs": nowMs(), ] if let promptId = nonEmpty(params.promptId) { payload["promptId"] = promptId } if let sessionKey = nonEmpty(params.sessionKey) { payload["sessionKey"] = sessionKey } if let kind = nonEmpty(params.kind) { payload["kind"] = kind } if let details = nonEmpty(params.details) { payload["details"] = details } if let expiresAtMs = params.expiresAtMs { payload["expiresAtMs"] = expiresAtMs } if let risk = params.risk { payload["risk"] = risk.rawValue } if let actions = params.actions, !actions.isEmpty { payload["actions"] = actions.map { action in var encoded: [String: Any] = [ "id": action.id, "label": action.label, ] if let style = nonEmpty(action.style) { encoded["style"] = style } return encoded } } return payload } static func encodeExecApprovalItem(_ item: OpenClawWatchExecApprovalItem) -> [String: Any] { var payload: [String: Any] = [ "id": item.id, "commandText": item.commandText, "allowedDecisions": item.allowedDecisions.map(\.rawValue), ] if let commandPreview = nonEmpty(item.commandPreview) { payload["commandPreview"] = commandPreview } if let host = nonEmpty(item.host) { payload["host"] = host } if let nodeId = nonEmpty(item.nodeId) { payload["nodeId"] = nodeId } if let agentId = nonEmpty(item.agentId) { payload["agentId"] = agentId } if let expiresAtMs = item.expiresAtMs { payload["expiresAtMs"] = expiresAtMs } if let risk = item.risk { payload["risk"] = risk.rawValue } return payload } static func encodeExecApprovalPromptPayload( _ message: OpenClawWatchExecApprovalPromptMessage) -> [String: Any] { var payload: [String: Any] = [ "type": OpenClawWatchPayloadType.execApprovalPrompt.rawValue, "approval": encodeExecApprovalItem(message.approval), ] if let sentAtMs = message.sentAtMs { payload["sentAtMs"] = sentAtMs } if let deliveryId = nonEmpty(message.deliveryId) { payload["deliveryId"] = deliveryId } if message.resetResolvingState == true { payload["resetResolvingState"] = true } return payload } static func encodeExecApprovalResolvedPayload( _ message: OpenClawWatchExecApprovalResolvedMessage) -> [String: Any] { var payload: [String: Any] = [ "type": OpenClawWatchPayloadType.execApprovalResolved.rawValue, "approvalId": message.approvalId, ] if let decision = message.decision { payload["decision"] = decision.rawValue } if let resolvedAtMs = message.resolvedAtMs { payload["resolvedAtMs"] = resolvedAtMs } if let source = nonEmpty(message.source) { payload["source"] = source } return payload } static func encodeExecApprovalExpiredPayload( _ message: OpenClawWatchExecApprovalExpiredMessage) -> [String: Any] { var payload: [String: Any] = [ "type": OpenClawWatchPayloadType.execApprovalExpired.rawValue, "approvalId": message.approvalId, "reason": message.reason.rawValue, ] if let expiredAtMs = message.expiredAtMs { payload["expiredAtMs"] = expiredAtMs } return payload } static func encodeExecApprovalSnapshotPayload( _ message: OpenClawWatchExecApprovalSnapshotMessage) -> [String: Any] { var payload: [String: Any] = [ "type": OpenClawWatchPayloadType.execApprovalSnapshot.rawValue, "approvals": message.approvals.map(encodeExecApprovalItem), ] if let sentAtMs = message.sentAtMs { payload["sentAtMs"] = sentAtMs } if let snapshotId = nonEmpty(message.snapshotId) { payload["snapshotId"] = snapshotId } return payload } static func parseQuickReplyPayload( _ payload: [String: Any], transport: String) -> WatchQuickReplyEvent? { guard (payload["type"] as? String) == OpenClawWatchPayloadType.reply.rawValue else { return nil } guard let actionId = nonEmpty(payload["actionId"] as? String) else { return nil } let promptId = nonEmpty(payload["promptId"] as? String) ?? "unknown" let replyId = nonEmpty(payload["replyId"] as? String) ?? UUID().uuidString let actionLabel = nonEmpty(payload["actionLabel"] as? String) let sessionKey = nonEmpty(payload["sessionKey"] as? String) let note = nonEmpty(payload["note"] as? String) let sentAtMs = (payload["sentAtMs"] as? Int) ?? (payload["sentAtMs"] as? NSNumber)?.intValue return WatchQuickReplyEvent( replyId: replyId, promptId: promptId, actionId: actionId, actionLabel: actionLabel, sessionKey: sessionKey, note: note, sentAtMs: sentAtMs, transport: transport) } static func parseExecApprovalResolvePayload( _ payload: [String: Any], transport: String) -> WatchExecApprovalResolveEvent? { guard (payload["type"] as? String) == OpenClawWatchPayloadType.execApprovalResolve.rawValue else { return nil } guard let approvalId = nonEmpty(payload["approvalId"] as? String), let rawDecision = nonEmpty(payload["decision"] as? String), let decision = OpenClawWatchExecApprovalDecision(rawValue: rawDecision) else { return nil } let replyId = nonEmpty(payload["replyId"] as? String) ?? UUID().uuidString let sentAtMs = (payload["sentAtMs"] as? Int) ?? (payload["sentAtMs"] as? NSNumber)?.intValue return WatchExecApprovalResolveEvent( replyId: replyId, approvalId: approvalId, decision: decision, sentAtMs: sentAtMs, transport: transport) } static func parseExecApprovalSnapshotRequestPayload( _ payload: [String: Any], transport: String) -> WatchExecApprovalSnapshotRequestEvent? { guard (payload["type"] as? String) == OpenClawWatchPayloadType.execApprovalSnapshotRequest.rawValue else { return nil } let requestId = nonEmpty(payload["requestId"] as? String) ?? UUID().uuidString let sentAtMs = (payload["sentAtMs"] as? Int) ?? (payload["sentAtMs"] as? NSNumber)?.intValue return WatchExecApprovalSnapshotRequestEvent( requestId: requestId, sentAtMs: sentAtMs, transport: transport) } }