import Foundation

/// Minimal `text/event-stream` reader over `URLSession.bytes`.
/// iOS has no native `EventSource`; the server's frames are simple
/// (`event: <type>\ndata: <json>\n\n`, plus `: keepalive` comments), so we
/// parse them directly rather than pulling a dependency.
public struct SSEEvent: Sendable {
    public let event: String   // SSE event name (the tether message `type`)
    public let data: String    // raw `data:` payload (JSON)
}

public actor SSEClient {
    private let url: URL
    private let session: URLSession

    public init(url: URL, session: URLSession = .shared) {
        self.url = url
        self.session = session
    }

    /// Async stream of parsed SSE events. Terminates when the connection drops
    /// or the task is cancelled; callers handle reconnect (see `TetherClient`).
    public func events() -> AsyncThrowingStream<SSEEvent, Error> {
        AsyncThrowingStream { continuation in
            let task = Task {
                do {
                    var req = URLRequest(url: url)
                    req.setValue("text/event-stream", forHTTPHeaderField: "Accept")
                    req.timeoutInterval = .infinity
                    let (bytes, response) = try await session.bytes(for: req)
                    if let http = response as? HTTPURLResponse, http.statusCode != 200 {
                        throw URLError(.badServerResponse)
                    }
                    var event = "message"
                    var data = ""
                    for try await line in bytes.lines {
                        if line.isEmpty {                     // dispatch on blank line
                            if !data.isEmpty {
                                continuation.yield(SSEEvent(event: event, data: data))
                            }
                            event = "message"; data = ""
                        } else if line.hasPrefix(":") {        // comment / keepalive
                            continue
                        } else if line.hasPrefix("event:") {
                            event = line.dropFirst(6).trimmingCharacters(in: .whitespaces)
                        } else if line.hasPrefix("data:") {
                            let chunk = line.dropFirst(5).trimmingCharacters(in: .whitespaces)
                            data += data.isEmpty ? chunk : "\n" + chunk
                        }
                    }
                    continuation.finish()
                } catch {
                    continuation.finish(throwing: error)
                }
            }
            continuation.onTermination = { _ in task.cancel() }
        }
    }
}
