/**
 * ActionPeer — symmetric send/receive for JSON-RPC actions.
 *
 * Wraps a `Transports` registry and `ActionEventEnvironment` to provide
 * bidirectional action dispatch via JSON-RPC 2.0.
 *
 * @module
 */
import { JsonrpcMessageFromClientToServer, JsonrpcMessageFromServerToClient, JsonrpcNotification, JsonrpcRequest, JsonrpcResponseOrError, JsonrpcErrorResponse, } from '../http/jsonrpc.js';
import { create_jsonrpc_error_response, create_jsonrpc_error_response_from_thrown, to_jsonrpc_message_id, is_jsonrpc_request, is_jsonrpc_notification, } from '../http/jsonrpc_helpers.js';
import { jsonrpc_error_messages } from '../http/jsonrpc_errors.js';
import { create_action_event } from './action_event.js';
import { Transports } from './transports.js';
export class ActionPeer {
    environment;
    transports;
    // TODO maybe expand the pattern of using `transports` in send, so what's used in receive?
    // It seems abstracting that out would make this class much simpler and generic, but too much so?
    // What deps should it actually know about, and what gains could we have by making it more decoupled?
    // e.g. don't just decouple for the sake of imagined flexibility!
    default_send_options;
    constructor(options) {
        this.environment = options.environment;
        this.transports = options.transports ?? new Transports();
        this.default_send_options = options.default_send_options ?? {};
    }
    async send(message, options) {
        try {
            const transport = this.transports.get_transport(options?.transport_name ?? this.default_send_options.transport_name);
            if (!transport) {
                this.environment.log?.error('[peer] send failed: no transport available');
                return create_jsonrpc_error_response(to_jsonrpc_message_id(message), jsonrpc_error_messages.service_unavailable('no transport available'));
            }
            const message_type = is_jsonrpc_request(message) ? 'request' : 'notification';
            this.environment.log?.debug(`[peer] send ${message_type}:`, message.method, `via ${transport.transport_name}`);
            const result = await transport.send(message, {
                signal: options?.signal,
                queue: options?.queue ?? this.default_send_options.queue,
            });
            if (result && 'error' in result) {
                this.environment.log?.error(`[peer] send ${message_type} failed:`, message.method, result.error.message);
            }
            return result;
        }
        catch (error) {
            // TODO add retry handling here?
            this.environment.log?.error('[peer] send unexpected error:', error);
            return create_jsonrpc_error_response_from_thrown(to_jsonrpc_message_id(message), error);
        } // TODO finally?
    }
    /**
     * Dispatch an inbound JSON-RPC message — request, notification, or
     * malformed envelope. Never throws; unexpected failures become
     * JSON-RPC error responses.
     *
     * @returns response message for requests, `null` for notifications, or
     *   an `invalid_request` error for malformed input
     */
    async receive(message) {
        try {
            const result = await this.#receive_message(message);
            return result;
        }
        catch (error) {
            this.environment.log?.error('[peer] receive unexpected error:', error);
            // Return appropriate error response based on the message
            return create_jsonrpc_error_response_from_thrown(to_jsonrpc_message_id(message), error);
        } // TODO finally?
    }
    async #receive_message(message) {
        if (is_jsonrpc_request(message)) {
            return this.#receive_request(message);
        }
        else if (is_jsonrpc_notification(message)) {
            await this.#receive_notification(message);
            return null;
        }
        else {
            return create_jsonrpc_error_response(to_jsonrpc_message_id(message), jsonrpc_error_messages.invalid_request());
        }
    }
    async #receive_request(request) {
        const spec = this.environment.lookup_action_spec(request.method);
        if (!spec) {
            this.environment.log?.warn(`[peer] receive request: method not found:`, request.method);
            return create_jsonrpc_error_response(request.id, jsonrpc_error_messages.method_not_found(request.method));
        }
        this.environment.log?.debug(`[peer] receive request:`, request.method);
        try {
            // Create action event in receive_request phase
            const event = create_action_event(this.environment, spec, request.params, 'receive_request');
            event.set_request(request);
            // Parse and handle
            await event.parse().handle_async();
            // Check if we successfully handled the request
            if (event.data.step === 'handled') {
                // Transition to send_response phase
                event.transition('send_response');
                await event.parse().handle_async();
                // TODO doesn't seem exactly right, shouldn't need the guard, or needs some other tweaks
                // Return the response if any
                if (event.data.response) {
                    return event.data.response;
                }
            }
            // Check for terminal failure
            if (event.data.step === 'failed') {
                this.environment.log?.error(`[peer] receive request failed:`, request.method, event.data.error);
                return create_jsonrpc_error_response(request.id, event.data.error);
            }
            // Check if transitioned to error phase (send_error)
            if (event.data.phase === 'send_error') {
                // Error handler may exist - try to handle it (already parsed)
                await event.handle_async();
                // Return error response (handler may have modified/logged it)
                return create_jsonrpc_error_response(request.id, event.data.error);
            }
            // Fallback for unexpected states
            this.environment.log?.error(`[peer] receive request: unexpected state:`, request.method, event.data);
            return create_jsonrpc_error_response(request.id, jsonrpc_error_messages.internal_error('unknown error'));
        }
        catch (error) {
            this.environment.log?.error(`[peer] receive request exception:`, request.method, error);
            return create_jsonrpc_error_response_from_thrown(request.id, error);
        }
    }
    async #receive_notification(notification) {
        const spec = this.environment.lookup_action_spec(notification.method);
        if (!spec) {
            this.environment.log?.warn(`[peer] receive notification: method not found:`, notification.method);
            return;
        }
        this.environment.log?.debug(`[peer] receive notification:`, notification.method);
        try {
            // Create action event in receive phase
            const event = create_action_event(this.environment, spec, notification.params, 'receive');
            event.set_notification(notification);
            // Parse and handle
            await event.parse().handle_async();
            if (event.data.step === 'failed') {
                this.environment.log?.error(`[peer] receive notification failed:`, notification.method, event.data.error);
            }
        }
        catch (error) {
            this.environment.log?.error(`[peer] receive notification exception:`, notification.method, error);
        }
    }
}
