/**
 * Frontend WebSocket transport — thin adapter over `WebsocketRpcConnection`.
 *
 * Delegates request/response correlation, the durable queue, the heartbeat,
 * and `AbortSignal`-driven cancel to the underlying connection (the
 * canonical implementation is `FrontendWebsocketClient`). The transport's
 * own job is the `Transport` contract: route inbound server-pushed
 * messages into `peer.receive` and translate the connection's
 * `Promise<R>`/`ThrownJsonrpcError` shape into `JsonrpcResponseOrError`
 * envelopes. No parallel pending-request map.
 *
 * @module
 */
import { ThrownJsonrpcError, jsonrpc_error_messages } from '../http/jsonrpc_errors.js';
import { is_jsonrpc_notification, is_jsonrpc_request, to_jsonrpc_message_id, to_jsonrpc_result, create_jsonrpc_response, create_jsonrpc_error_response, } from '../http/jsonrpc_helpers.js';
/**
 * Thin adapter over `WebsocketRpcConnection` (canonical implementation:
 * `FrontendWebsocketClient`). Routes inbound server-pushed requests and
 * notifications into the supplied `receive` callback; responses are owned
 * by the connection's own `request()` pending map and are ignored here.
 */
export class FrontendWebsocketTransport {
    transport_name = 'frontend_websocket_rpc';
    #connection;
    #receive;
    #remove_message_handler;
    #remove_error_handler;
    constructor(connection, receive) {
        this.#connection = connection;
        this.#receive = receive;
        // Inbound dispatch — only server-pushed requests/notifications need
        // routing here. Responses to requests we sent are correlated by the
        // connection's own `request()` pending map.
        this.#remove_message_handler = connection.add_message_handler(async (event) => {
            try {
                const data = JSON.parse(event.data);
                if (is_jsonrpc_request(data) || is_jsonrpc_notification(data)) {
                    await this.#receive(data);
                }
                // Responses are owned by `connection.request()` — ignore here.
            }
            catch (error) {
                console.error('[ws_transport] error parsing WebSocket message:', error);
            }
        });
        this.#remove_error_handler = connection.add_error_handler((event) => {
            console.error('[ws_transport] WebSocket error:', event);
        });
    }
    async send(message, options) {
        // Notifications fail-fast when disconnected regardless of `queue` —
        // `connection.send()` is fire-and-forget with no queue semantic, so
        // silently dropping would masquerade as success at the rpc_client
        // layer (caller would see `{ok: true}` for a lost message).
        //
        // Requests have no such gate here: `connection.request()` throws
        // `ThrownJsonrpcError` with the right code (`service_unavailable`
        // when not connected, `queue_overflow` when the durable queue is
        // full, `request_cancelled` on abort, server's wire code for peer
        // error frames), and the catch block below preserves that code
        // verbatim in the error envelope. Queuing is routed via `queue`.
        const queue = options?.queue ?? false;
        if (is_jsonrpc_notification(message) && !this.is_ready()) {
            return create_jsonrpc_error_response(to_jsonrpc_message_id(message), jsonrpc_error_messages.service_unavailable('WebSocket not connected'));
        }
        if (is_jsonrpc_request(message)) {
            try {
                const result = await this.#connection.request(message.method, message.params, {
                    id: message.id,
                    signal: options?.signal,
                    queue,
                });
                return create_jsonrpc_response(message.id, to_jsonrpc_result(result));
            }
            catch (error) {
                if (error instanceof ThrownJsonrpcError) {
                    return create_jsonrpc_error_response(message.id, {
                        code: error.code,
                        message: error.message,
                        data: error.data,
                    });
                }
                return create_jsonrpc_error_response(message.id, jsonrpc_error_messages.internal_error(error instanceof Error ? error.message : String(error)));
            }
        }
        if (is_jsonrpc_notification(message)) {
            this.#connection.send(message);
            return null;
        }
        return create_jsonrpc_error_response(to_jsonrpc_message_id(message), jsonrpc_error_messages.invalid_request());
    }
    is_ready() {
        return this.#connection.connected;
    }
    /**
     * Detach the inbound message and error handlers registered on the
     * connection. Idempotent — subsequent calls no-op. Does not close the
     * underlying connection (that lifecycle is owned by the caller).
     *
     * @mutates this - clears the two stored unsubscribe references after invoking them
     */
    dispose() {
        if (this.#remove_message_handler) {
            this.#remove_message_handler();
            this.#remove_message_handler = null;
        }
        if (this.#remove_error_handler) {
            this.#remove_error_handler();
            this.#remove_error_handler = null;
        }
    }
}
