import { BaseTransport } from '../../../core/blockchain-provider/transports/base.transport';
import { RateLimiter } from '../../../core/blockchain-provider/transports/rate-limiter';
import { RpcError } from '../../../core/blockchain-provider/transports/errors';
export class NodeRpcTransport extends BaseTransport {
    constructor(options) {
        super();
        this.type = 'node-rpc';
        this.requestId = 1;
        const url = new URL(options.httpUrl);
        const basicAuth = url.username
            ? `Basic ${this.encodeBase64(`${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}`)}`
            : undefined;
        url.username = '';
        url.password = '';
        this.httpUrl = url.toString();
        this.responseTimeout = options.responseTimeout ?? 5000;
        this.rateLimiter = new RateLimiter(options.rateLimits ?? {});
        this.headers = {
            'Content-Type': 'application/json',
            ...(options.headers ?? {}),
            ...(basicAuth ? { Authorization: basicAuth } : {}),
        };
    }
    async request(method, params = []) {
        try {
            const [result] = await this.batch([{ method, params }]);
            if (result === null || result === undefined) {
                throw new RpcError(`EVM JSON-RPC ${method} returned empty result`);
            }
            return result;
        }
        catch (error) {
            throw this.normalizeError(error, method);
        }
    }
    async batch(requests) {
        try {
            return await this.rateLimiter.execute(requests, async (calls) => this.batchCall(calls));
        }
        catch (error) {
            throw this.normalizeError(error, 'batch');
        }
    }
    async close() {
        await this.rateLimiter.stop();
    }
    async batchCall(calls) {
        if (calls.length === 0)
            return [];
        const payload = calls.map((call) => ({
            jsonrpc: '2.0',
            id: this.requestId++,
            method: call.method,
            params: call.params,
        }));
        const idToIndex = new Map(payload.map((item, index) => [item.id, index]));
        const response = await fetch(this.httpUrl, {
            method: 'POST',
            headers: this.headers,
            body: JSON.stringify(payload),
            signal: AbortSignal.timeout(this.responseTimeout),
        });
        if (!response.ok) {
            throw new RpcError(`EVM JSON-RPC batch failed with HTTP ${response.status}: ${response.statusText}`);
        }
        const raw = await response.json();
        const items = Array.isArray(raw) ? raw : [raw];
        const results = new Array(calls.length).fill(null);
        for (const item of items) {
            const index = typeof item?.id === 'number' ? idToIndex.get(item.id) : undefined;
            if (index == null)
                continue;
            if (item?.error) {
                throw new RpcError(`JSON-RPC Error ${item.error.code}: ${item.error.message}`, item.error);
            }
            results[index] = (item?.result ?? null);
        }
        return results;
    }
    encodeBase64(value) {
        if (typeof btoa === 'function') {
            return btoa(value);
        }
        if (typeof globalThis === 'object' && typeof globalThis.Buffer !== 'undefined') {
            return globalThis.Buffer.from(value).toString('base64');
        }
        const bytes = typeof TextEncoder !== 'undefined' ? new TextEncoder().encode(value) : undefined;
        if (!bytes) {
            throw new Error('Base64 encoding is not available in this runtime');
        }
        const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
        let out = '';
        for (let i = 0; i < bytes.length; i += 3) {
            const a = bytes[i] ?? 0;
            const b = bytes[i + 1] ?? 0;
            const c = bytes[i + 2] ?? 0;
            const triple = (a << 16) | (b << 8) | c;
            out += alphabet[(triple >> 18) & 63];
            out += alphabet[(triple >> 12) & 63];
            out += i + 1 < bytes.length ? alphabet[(triple >> 6) & 63] : '=';
            out += i + 2 < bytes.length ? alphabet[triple & 63] : '=';
        }
        return out;
    }
}
