/**
 * Transport abstraction for action communication.
 *
 * Provides the `Transport` interface and `Transports` registry for managing
 * multiple transports with fallback behavior.
 *
 * @module
 */
import { z } from 'zod';
/** WebSocket close code for session revocation. */
export const WS_CLOSE_SESSION_REVOKED = 4001;
/** WebSocket close code — client timed out waiting for a response. */
export const WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT = 4002;
/** WebSocket close code — server timed out with no incoming activity. */
export const WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT = 4003;
// TODO figure out the symmetry of frontend and backend transports (none/partial/full?) --
// we may also need orthogonal abstractions to clarify the transport role
export const TransportName = z.string(); // not branded for convenience, will just error at runtime, the schema is just for docs atm
export class Transports {
    #current_transport = null;
    #transport_by_name = new Map();
    /**
     * Whether to allow fallback to other transports if the current one is not available.
     * @default true
     */
    allow_fallback = true; // TODO allow registering transports with a priority level so this can be customized
    /**
     * Registers a transport. The first transport registered also becomes the current.
     *
     * @mutates this - inserts into `#transport_by_name`; sets `#current_transport`
     *   if no current is set
     */
    register_transport(transport) {
        this.#transport_by_name.set(transport.transport_name, transport); // TODO maybe ensure unregistering of any previous transport?
        if (!this.#current_transport) {
            this.#current_transport = transport;
        }
    }
    /**
     * Switch the current transport selection by name.
     *
     * @mutates this - sets `#current_transport`
     * @throws Error if no transport with `transport_name` has been registered
     */
    set_current_transport(transport_name) {
        const transport = this.#transport_by_name.get(transport_name);
        if (!transport)
            throw new Error(`transport not registered: ${transport_name}`);
        this.#current_transport = transport;
    }
    /**
     * Resolve a transport. With `allow_fallback`, walks specified → current →
     * any-ready; without, returns the named transport (or current) only when
     * it's ready.
     *
     * @returns the resolved transport, or `null` when none is ready
     */
    get_transport(transport_name) {
        return this.allow_fallback
            ? this.#get_first_ready(transport_name)
            : this.#get_exact(transport_name);
    }
    // TODO these 4 arent used yet but seem useful? `get_transport` is the main method
    is_ready() {
        const transport = this.#current_transport;
        if (!transport)
            return null;
        return transport.is_ready();
    }
    get_current_transport() {
        return this.#current_transport ?? null;
    }
    get_current_transport_name() {
        return this.#current_transport?.transport_name ?? null;
    }
    get_transport_by_name(transport_name) {
        return this.#transport_by_name.get(transport_name) ?? null;
    }
    #get_exact(transport_name) {
        const transport = transport_name
            ? this.#transport_by_name.get(transport_name)
            : this.#current_transport;
        if (transport?.is_ready()) {
            return transport;
        }
        return null;
    }
    #get_first_ready(transport_name) {
        if (transport_name) {
            const transport_names = Array.isArray(transport_name) ? transport_name : [transport_name];
            for (const transport_name of transport_names) {
                const transport = this.#transport_by_name.get(transport_name);
                if (transport?.is_ready()) {
                    return transport;
                }
            }
        }
        if (this.#current_transport?.is_ready()) {
            return this.#current_transport;
        }
        for (const transport of this.#transport_by_name.values()) {
            if (transport.is_ready()) {
                return transport;
            }
        }
        return null;
    }
}
