import Foundation

/// Wire envelope — 1:1 with the Go `Message` struct in `server/main.go`.
/// Keep these in lockstep; this is the contract between every tether client.
public struct Message: Codable, Sendable, Identifiable, Equatable {
    public enum Kind: String, Codable, Sendable {
        case clipboard
        case signal   // WebRTC SDP / ICE (carried in `signal`)
        case presence // discovery chirp
    }

    public var type: Kind?
    public var text: String?
    /// Raw WebRTC signaling payload; opaque at this layer. Parsed only by the RTC code.
    public var signal: TetherJSON?
    public var from: String?
    public var to: String?
    public var role: String?
    public var source: String?
    public var room: String?
    /// Unix millis, set server-side on publish. Always present on receive.
    public var ts: Int64

    public var id: String { "\(ts)-\(from ?? "?")-\(text?.hashValue ?? 0)" }

    public init(
        type: Kind? = nil, text: String? = nil, signal: TetherJSON? = nil,
        from: String? = nil, to: String? = nil, role: String? = nil,
        source: String? = nil, room: String? = nil, ts: Int64 = 0
    ) {
        self.type = type; self.text = text; self.signal = signal
        self.from = from; self.to = to; self.role = role
        self.source = source; self.room = room; self.ts = ts
    }

    public var date: Date { Date(timeIntervalSince1970: Double(ts) / 1000) }

    /// A clipboard payload to POST to `/api/send`.
    public static func clipboard(_ text: String, room: String, from: String) -> Message {
        Message(type: .clipboard, text: text, from: from, room: room)
    }
}

/// Minimal JSON value so `signal` round-trips without us modeling the WebRTC SDP/ICE shape.
public enum TetherJSON: Codable, Sendable, Equatable {
    case null
    case bool(Bool)
    case number(Double)
    case string(String)
    case array([TetherJSON])
    case object([String: TetherJSON])

    public init(from decoder: Decoder) throws {
        let c = try decoder.singleValueContainer()
        if c.decodeNil() { self = .null }
        else if let v = try? c.decode(Bool.self) { self = .bool(v) }
        else if let v = try? c.decode(Double.self) { self = .number(v) }
        else if let v = try? c.decode(String.self) { self = .string(v) }
        else if let v = try? c.decode([TetherJSON].self) { self = .array(v) }
        else if let v = try? c.decode([String: TetherJSON].self) { self = .object(v) }
        else { throw DecodingError.dataCorruptedError(in: c, debugDescription: "unsupported JSON") }
    }

    public func encode(to encoder: Encoder) throws {
        var c = encoder.singleValueContainer()
        switch self {
        case .null: try c.encodeNil()
        case .bool(let v): try c.encode(v)
        case .number(let v): try c.encode(v)
        case .string(let v): try c.encode(v)
        case .array(let v): try c.encode(v)
        case .object(let v): try c.encode(v)
        }
    }
}
