"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketRpcTransport = void 0;
const base_transport_1 = require("../../../core/blockchain-provider/transports/base.transport");
const errors_1 = require("../../../core/blockchain-provider/transports/errors");
class WebSocketRpcTransport extends base_transport_1.BaseTransport {
    type = 'ws-rpc';
    wsUrl;
    responseTimeout;
    socket = null;
    requestId = 1;
    pendingRequests = new Map();
    subscriptionCallbacks = new Map();
    connectPromise = null;
    constructor(options) {
        super();
        this.wsUrl = options.wsUrl;
        this.responseTimeout = options.responseTimeout ?? 5000;
    }
    async connect() {
        if (this.isSocketOpen()) {
            return;
        }
        if (this.connectPromise) {
            return this.connectPromise;
        }
        this.connectPromise = new Promise((resolve, reject) => {
            const WebSocketCtor = globalThis.WebSocket;
            if (typeof WebSocketCtor !== 'function') {
                this.connectPromise = null;
                reject(new Error('Global WebSocket is not available'));
                return;
            }
            const socket = new WebSocketCtor(this.wsUrl);
            this.socket = socket;
            socket.onopen = () => {
                this.connectPromise = null;
                resolve();
            };
            socket.onmessage = (event) => {
                this.handleMessage(event.data);
            };
            socket.onerror = (event) => {
                const error = new errors_1.RpcError(`EVM WebSocket connection error for ${this.wsUrl}`, event);
                this.rejectAll(error);
                this.socket = null;
                this.connectPromise = null;
                reject(error);
            };
            socket.onclose = () => {
                const error = new errors_1.RpcError(`EVM WebSocket connection closed for ${this.wsUrl}`);
                this.rejectAll(error);
                this.socket = null;
                this.connectPromise = null;
            };
        });
        return this.connectPromise;
    }
    async request(method, params = []) {
        const [result] = await this.batch([{ method, params }]);
        if (result === null || result === undefined) {
            throw new errors_1.RpcError(`EVM WebSocket ${method} returned empty result`);
        }
        return result;
    }
    async batch(requests) {
        if (requests.length === 0)
            return [];
        await this.connect();
        const socket = this.requireSocket();
        const promises = requests.map((request) => {
            const id = this.requestId++;
            const payload = {
                jsonrpc: '2.0',
                id,
                method: request.method,
                params: request.params,
            };
            const promise = new Promise((resolve, reject) => {
                const timer = setTimeout(() => {
                    this.pendingRequests.delete(id);
                    reject(new errors_1.RpcError(`EVM WebSocket ${request.method} timed out after ${this.responseTimeout}ms`));
                }, this.responseTimeout);
                this.pendingRequests.set(id, { resolve, reject, timer });
            });
            socket.send(JSON.stringify(payload));
            return promise;
        });
        return Promise.all(promises);
    }
    async close() {
        this.rejectAll(new errors_1.RpcError('EVM WebSocket transport closed'));
        if (this.socket) {
            this.socket.close();
            this.socket = null;
        }
    }
    async subscribe(method, params, onMessage) {
        const subscriptionId = await this.request('eth_subscribe', [method, ...params]);
        this.subscriptionCallbacks.set(subscriptionId, onMessage);
        return {
            unsubscribe: () => {
                this.subscriptionCallbacks.delete(subscriptionId);
                void this.request('eth_unsubscribe', [subscriptionId]).catch(() => undefined);
            },
        };
    }
    handleMessage(raw) {
        const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
        if (parsed?.method === 'eth_subscription') {
            const subscriptionId = parsed?.params?.subscription;
            const callback = typeof subscriptionId === 'string' ? this.subscriptionCallbacks.get(subscriptionId) : undefined;
            if (callback) {
                callback(parsed.params.result);
            }
            return;
        }
        const id = parsed?.id;
        if (typeof id !== 'number') {
            return;
        }
        const pending = this.pendingRequests.get(id);
        if (!pending) {
            return;
        }
        clearTimeout(pending.timer);
        this.pendingRequests.delete(id);
        if (parsed?.error) {
            pending.reject(new errors_1.RpcError(`JSON-RPC Error ${parsed.error.code}: ${parsed.error.message}`, parsed.error));
            return;
        }
        pending.resolve(parsed?.result ?? null);
    }
    requireSocket() {
        if (!this.socket || !this.isSocketOpen()) {
            throw new errors_1.RpcError(`EVM WebSocket is not connected: ${this.wsUrl}`);
        }
        return this.socket;
    }
    isSocketOpen() {
        return !!this.socket && this.socket.readyState === WebSocket.OPEN;
    }
    rejectAll(error) {
        for (const pending of this.pendingRequests.values()) {
            clearTimeout(pending.timer);
            pending.reject(error);
        }
        this.pendingRequests.clear();
    }
}
exports.WebSocketRpcTransport = WebSocketRpcTransport;
