import Foundation
import Network
import NucleicProtocol

/// Client-side `FrameChannel` over an outbound `NWConnection` (SYNC_PROTOCOL §3.1). De-frames
/// inbound bytes with `FrameAccumulator`; length-prefixes outbound frames. Mirrors the host's
/// `LANChannel` but for the phone's side of the TCP connection.
final class NWFrameChannel: FrameChannel, @unchecked Sendable {
    let peerDescription: String
    private let connection: NWConnection
    private let queue = DispatchQueue(label: "nucleic.remote.channel")
    private let accumulator = FrameAccumulator()
    private let stream: AsyncStream<Data>
    private let continuation: AsyncStream<Data>.Continuation

    /// State callbacks so the store can reflect connectivity truth (UX_IOS §6).
    var onReady: (@Sendable () -> Void)?
    var onFailed: (@Sendable (String) -> Void)?

    init(endpoint: NWEndpoint) {
        let params = NWParameters.tcp
        params.includePeerToPeer = true
        connection = NWConnection(to: endpoint, using: params)
        peerDescription = "\(endpoint)"
        var cont: AsyncStream<Data>.Continuation!
        stream = AsyncStream(bufferingPolicy: .unbounded) { cont = $0 }
        continuation = cont

        connection.stateUpdateHandler = { [weak self] state in
            switch state {
            case .ready: self?.onReady?()
            case .failed(let error): self?.onFailed?("\(error)"); self?.continuation.finish()
            case .cancelled: self?.continuation.finish()
            default: break
            }
        }
        connection.start(queue: queue)
        receiveLoop()
    }

    func frames() -> AsyncStream<Data> { stream }

    func send(_ frame: Data) {
        connection.send(content: WireFraming.frame(frame), completion: .idempotent)
    }

    func close() {
        connection.cancel()
        continuation.finish()
    }

    private func receiveLoop() {
        connection.receive(minimumIncompleteLength: 1, maximumLength: 65_536) { [weak self] data, _, isComplete, error in
            guard let self else { return }
            if let data, !data.isEmpty {
                if let frames = try? self.accumulator.push(data) {
                    for frame in frames { self.continuation.yield(frame) }
                } else {
                    self.close(); return
                }
            }
            if isComplete || error != nil { self.continuation.finish(); return }
            self.receiveLoop()
        }
    }
}
