import Foundation

/// High-level client for a tether room: POST sends, SSE receive with auto-reconnect.
/// Transport-only — mirrors the Go `client/` half. No clipboard/UI here so this
/// package stays usable from the app, a Share Extension, and a future Mac client.
public actor TetherClient {
    public struct Config: Sendable {
        public var baseURL: URL          // e.g. http://tether.lan:8765
        public var room: String          // 8-hex room id from /r/<id>
        public var from: String          // this device's peer id
        public var source: String        // platform label, e.g. "iphone-app"
        public init(baseURL: URL, room: String, from: String, source: String = "iphone-app") {
            self.baseURL = baseURL; self.room = room; self.from = from; self.source = source
        }
    }

    private let config: Config
    private let session: URLSession
    private let encoder = JSONEncoder()
    private let decoder = JSONDecoder()

    public init(config: Config, session: URLSession = .shared) {
        self.config = config
        self.session = session
    }

    private var streamURL: URL {
        var c = URLComponents(url: config.baseURL.appending(path: "/api/stream"),
                              resolvingAgainstBaseURL: false)!
        c.queryItems = [URLQueryItem(name: "room", value: config.room)]
        return c.url!
    }

    /// POST a clipboard payload to the room.
    public func send(_ text: String) async throws {
        var msg = Message.clipboard(text, room: config.room, from: config.from)
        msg.source = config.source
        var req = URLRequest(url: config.baseURL.appending(path: "/api/send"))
        req.httpMethod = "POST"
        req.setValue("application/json", forHTTPHeaderField: "Content-Type")
        req.setValue(config.source, forHTTPHeaderField: "X-Tether-Source")
        req.httpBody = try encoder.encode(msg)
        let (_, resp) = try await session.data(for: req)
        if let http = resp as? HTTPURLResponse, !(200...299).contains(http.statusCode) {
            throw URLError(.badServerResponse)
        }
    }

    /// Subscribe to the room's clipboard messages, reconnecting on drop with backoff.
    /// Yields only `clipboard` messages; signal/presence are handled elsewhere.
    public func clipboardStream() -> AsyncStream<Message> {
        AsyncStream { continuation in
            let task = Task {
                var backoff: UInt64 = 500_000_000 // 0.5s
                while !Task.isCancelled {
                    do {
                        let sse = SSEClient(url: streamURL, session: session)
                        for try await ev in await sse.events() {
                            guard ev.event == "clipboard" || ev.event == "message" else { continue }
                            if let data = ev.data.data(using: .utf8),
                               let msg = try? decoder.decode(Message.self, from: data),
                               msg.from != config.from {           // skip our own echo
                                continuation.yield(msg)
                            }
                        }
                        backoff = 500_000_000                       // clean EOF → reset
                    } catch {
                        if Task.isCancelled { break }
                    }
                    try? await Task.sleep(nanoseconds: backoff)
                    backoff = min(backoff * 2, 10_000_000_000)      // cap 10s
                }
                continuation.finish()
            }
            continuation.onTermination = { _ in task.cancel() }
        }
    }
}
