import Foundation
import NucleicProtocol

// The host-side transport seam (SYNC_PROTOCOL §2 layer cake). Connections use the shared
// `FrameChannel` (NucleicProtocol) so the client, server, and tests share one abstraction;
// only the listener — which accepts connections — is host-only and lives here.

/// Accepts client connections. The LAN implementation advertises Bonjour + listens on TCP.
public protocol SyncListener: AnyObject, Sendable {
    /// Begin listening/advertising and yield each accepted connection.
    func start() throws -> AsyncStream<any FrameChannel>
    func stop()
}

public enum SyncTransportError: Error, Equatable {
    case listenerFailed(String)
    case connectionClosed
}

/// Merges several listeners into one stream of accepted connections, so the host accepts
/// on every user-enabled connection method at once (Settings ▸ Remote ▸ Connection
/// methods) — the client dials whichever it can reach. One child's failure doesn't end
/// the merged stream; the healthy transports keep accepting. A child that fails to
/// *start* is reported via `onChildStartFailure` (surfaced as per-method status in
/// Settings) and skipped — with multiple user-selected methods, a pending Tailscale
/// login must not take down LAN. Only when every child fails does `start()` throw.
public final class CompositeSyncListener: SyncListener, @unchecked Sendable {
    public typealias ChildStartFailureHandler = @Sendable (_ index: Int, _ error: any Error) -> Void

    private let listeners: [any SyncListener]
    private let onChildStartFailure: ChildStartFailureHandler?
    private var forwardTasks: [Task<Void, Never>] = []
    private var continuation: AsyncStream<any FrameChannel>.Continuation?

    public init(_ listeners: [any SyncListener], onChildStartFailure: ChildStartFailureHandler? = nil) {
        self.listeners = listeners
        self.onChildStartFailure = onChildStartFailure
    }

    public func start() throws -> AsyncStream<any FrameChannel> {
        let (stream, continuation) = AsyncStream<any FrameChannel>.makeStream()
        self.continuation = continuation
        var children: [AsyncStream<any FrameChannel>] = []
        var firstError: (any Error)?
        for (index, listener) in listeners.enumerated() {
            do {
                children.append(try listener.start())
            } catch {
                if firstError == nil { firstError = error }
                onChildStartFailure?(index, error)
            }
        }
        if children.isEmpty, let firstError {
            // Every method failed — nothing is accepting, so the server can't come up.
            continuation.finish()
            throw firstError
        }
        for child in children {
            forwardTasks.append(Task {
                for await connection in child { continuation.yield(connection) }
            })
        }
        return stream
    }

    public func stop() {
        for listener in listeners { listener.stop() }
        for task in forwardTasks { task.cancel() }
        forwardTasks.removeAll()
        continuation?.finish()
    }
}
