import Foundation

/// Vends one shared ``MCPApprovalServer`` per **control container**.
///
/// Nucleic Control runs every session as an `exec` inside a single shared container (the primary
/// `nucleic-control`, or the per-family split-outs). Those sessions must therefore share **one**
/// approval server — a container has exactly one relayed control socket, and the server already
/// multiplexes sessions by their per-session bearer token ("one server can serve all sessions"). A
/// per-backend server can't satisfy that: two sessions in the same box would both try to own the
/// container's single socket.
///
/// The server is intentionally long-lived (keyed by the stable *logical* container name): it
/// outlives container restarts — e.g. a memory-balloon stop+recreate — because the relay re-attaches
/// to the same stable host socket path on recreate, and the still-listening server is right there.
///
/// Host (non-containerized) runs and per-session sandbox containers do **not** use this; they keep a
/// per-backend server (loopback for host, the per-session container gateway otherwise).
public actor ApprovalServerRegistry {
    private var servers: [String: MCPApprovalServer] = [:]

    public init() {}

    /// The shared server for `containerName`, created on first use. The caller starts it
    /// (idempotently) and registers its per-session token; teardown only unregisters that token, so
    /// the server keeps serving the other sessions in the container.
    public func server(for containerName: String) -> MCPApprovalServer {
        if let existing = servers[containerName] { return existing }
        let server = MCPApprovalServer()
        servers[containerName] = server
        return server
    }

    /// Stop and drop the server for a container (best-effort; e.g. the container was deleted).
    public func teardown(containerName: String) async {
        guard let server = servers.removeValue(forKey: containerName) else { return }
        await server.stop()
    }

    /// Stop every server (app shutdown).
    public func stopAll() async {
        let all = Array(servers.values)
        servers.removeAll()
        for server in all { await server.stop() }
    }

    /// The short, container-stable **host** path for a control container's approval socket, relayed
    /// into the guest at ``ContainerSpec/controlSocketGuestPath``. Kept under a short dir because the
    /// AF_UNIX `sun_path` caps at ~104 bytes — the long Application-Support session path would
    /// overflow. Stable per logical container name, so the persistent server and the relay
    /// (re-attached on container recreate) always agree on where to meet.
    public nonisolated static func controlSocketPath(for containerName: String) -> String {
        "/tmp/nucleic/\(containerName).sock"
    }
}
