var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _SmoldotProvider_chainSpec, _SmoldotProvider_coder, _SmoldotProvider_eventemitter, _SmoldotProvider_handlers, _SmoldotProvider_subscriptions, _SmoldotProvider_waitingForId, _SmoldotProvider_connectionStatePingerId, _SmoldotProvider_isConnected, _SmoldotProvider_client, _SmoldotProvider_chain, _SmoldotProvider_parachainSpecs, _SmoldotProvider_smoldot, _SmoldotProvider_handleRpcReponse, _SmoldotProvider_onMessageResult, _SmoldotProvider_onMessageSubscribe, _SmoldotProvider_simulateLifecycle, _SmoldotProvider_checkClientPeercount;
import { RpcCoder } from "@polkadot/rpc-provider/coder";
import { assert, logger } from "@polkadot/util";
import EventEmitter from "eventemitter3";
import * as smoldot from "@substrate/smoldot-light";
import { HealthCheckError } from "../errors.js";
import { isUndefined } from "../utils/index.js";
const l = logger("smoldot-provider");
const ANGLICISMS = {
    chain_finalisedHead: "chain_finalizedHead",
    chain_subscribeFinalisedHeads: "chain_subscribeFinalizedHeads",
    chain_unsubscribeFinalisedHeads: "chain_unsubscribeFinalizedHeads",
};
/*
 * Number of milliseconds to wait between checks to see if we have any
 * connected peers in the smoldot client
 */
const CONNECTION_STATE_PINGER_INTERVAL = 2000;
/**
 * SmoldotProvider
 *
 * The SmoldotProvider allows interacting with a smoldot-based
 * WASM light client.  I.e. without doing RPC to a remote node over HTTP
 * or websockets
 *
 * @example
 * ```javascript
 * import readFileSync from 'fs';
 * import Api from '@polkadot/api/promise';
 * import { SmoldotProvider } from '../';
 *
 * const chainSpec = readFileSync('./path/to/chainSpec.json');
 * const provider = new SmoldotProvider(chainSpec);
 * const api = new Api(provider);
 * ```
 * @example
 * ```javascript
 * import readFileSync from 'fs';
 * import Api from '@polkadot/api/promise';
 * import { SmoldotProvider } from '../';
 *
 * const chainSpec = readFileSync('./path/to/polkadot.json');
 * const pp = new SmoldotProvider(chainSpec);
 * const polkadotApi = new Api(pp);
 *
 * const chainSpec = readFileSync('./path/to/kusama.json');
 * const kp = new SmoldotProvider(chainSpec);
 * const kusamaApi = new Api(pp);
 * ```
 */
export class SmoldotProvider {
    /**
     * @param chainSpec - The chainSpec for the WASM client
     * @param sm - (only used for testing) defaults to the actual smoldot module
     */
    //eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types
    constructor(chainSpec, parachain, sm) {
        _SmoldotProvider_chainSpec.set(this, void 0);
        _SmoldotProvider_coder.set(this, new RpcCoder());
        _SmoldotProvider_eventemitter.set(this, new EventEmitter());
        _SmoldotProvider_handlers.set(this, {});
        _SmoldotProvider_subscriptions.set(this, {});
        _SmoldotProvider_waitingForId.set(this, {});
        _SmoldotProvider_connectionStatePingerId.set(this, void 0);
        _SmoldotProvider_isConnected.set(this, false);
        _SmoldotProvider_client.set(this, undefined);
        _SmoldotProvider_chain.set(this, undefined);
        _SmoldotProvider_parachainSpecs.set(this, undefined
        // reference to the smoldot module so we can defer loading the wasm client
        // until connect is called
        );
        // reference to the smoldot module so we can defer loading the wasm client
        // until connect is called
        _SmoldotProvider_smoldot.set(this, void 0);
        /*
         * How frequently to see if we have any peers
         */
        this.healthPingerInterval = CONNECTION_STATE_PINGER_INTERVAL;
        _SmoldotProvider_handleRpcReponse.set(this, (res) => {
            l.debug(() => ["received", res]);
            const response = JSON.parse(res);
            return isUndefined(response.method)
                ? __classPrivateFieldGet(this, _SmoldotProvider_onMessageResult, "f").call(this, response)
                : __classPrivateFieldGet(this, _SmoldotProvider_onMessageSubscribe, "f").call(this, response);
        });
        _SmoldotProvider_onMessageResult.set(this, (response) => {
            const handler = __classPrivateFieldGet(this, _SmoldotProvider_handlers, "f")[response.id];
            if (!handler) {
                l.debug(() => `Unable to find handler for id=${response.id}`);
                return;
            }
            try {
                const { method, subscription } = handler;
                const result = __classPrivateFieldGet(this, _SmoldotProvider_coder, "f").decodeResponse(response);
                // first send the result - in case of subs, we may have an update
                // immediately if we have some queued results already
                handler.callback(null, result);
                if (subscription) {
                    const subId = `${subscription.type}::${result}`;
                    __classPrivateFieldGet(this, _SmoldotProvider_subscriptions, "f")[subId] = {
                        ...subscription,
                        method,
                    };
                    // if we have a result waiting for this subscription already
                    if (__classPrivateFieldGet(this, _SmoldotProvider_waitingForId, "f")[subId]) {
                        __classPrivateFieldGet(this, _SmoldotProvider_onMessageSubscribe, "f").call(this, __classPrivateFieldGet(this, _SmoldotProvider_waitingForId, "f")[subId]);
                    }
                }
            }
            catch (error) {
                handler.callback(error, undefined);
            }
            delete __classPrivateFieldGet(this, _SmoldotProvider_handlers, "f")[response.id];
        });
        _SmoldotProvider_onMessageSubscribe.set(this, (response) => {
            const method = ANGLICISMS[response.method] || response.method || "invalid";
            const subId = `${method}::${response.params.subscription}`;
            const handler = __classPrivateFieldGet(this, _SmoldotProvider_subscriptions, "f")[subId];
            if (!handler) {
                // store the response, we could have out-of-order subid coming in
                __classPrivateFieldGet(this, _SmoldotProvider_waitingForId, "f")[subId] = response;
                l.debug(() => `Unable to find handler for subscription=${subId} responseId=${response.id}`);
                return;
            }
            // housekeeping
            delete __classPrivateFieldGet(this, _SmoldotProvider_waitingForId, "f")[subId];
            try {
                const result = __classPrivateFieldGet(this, _SmoldotProvider_coder, "f").decodeResponse(response);
                handler.callback(null, result);
            }
            catch (error) {
                handler.callback(error, undefined);
            }
        });
        _SmoldotProvider_simulateLifecycle.set(this, (health) => {
            // development chains should not have peers so we only emit connected
            // once and never disconnect
            if (health.shouldHavePeers == false) {
                if (!__classPrivateFieldGet(this, _SmoldotProvider_isConnected, "f")) {
                    __classPrivateFieldSet(this, _SmoldotProvider_isConnected, true, "f");
                    this.emit("connected");
                    l.debug(`emitted CONNECTED`);
                    return;
                }
                return;
            }
            const peerCount = health.peers;
            const peerChecks = (peerCount > 0 || !health.shouldHavePeers) && !health.isSyncing;
            l.debug(`Simulating lifecylce events from system_health`);
            l.debug(`isConnected: ${__classPrivateFieldGet(this, _SmoldotProvider_isConnected, "f").toString()}, new peerCount: ${peerCount}`);
            if (__classPrivateFieldGet(this, _SmoldotProvider_isConnected, "f") && peerChecks) {
                // still connected
                return;
            }
            if (__classPrivateFieldGet(this, _SmoldotProvider_isConnected, "f") && peerCount === 0) {
                __classPrivateFieldSet(this, _SmoldotProvider_isConnected, false, "f");
                this.emit("disconnected");
                l.debug(`emitted DISCONNECTED`);
                return;
            }
            if (!__classPrivateFieldGet(this, _SmoldotProvider_isConnected, "f") && peerChecks) {
                __classPrivateFieldSet(this, _SmoldotProvider_isConnected, true, "f");
                this.emit("connected");
                l.debug(`emitted CONNECTED`);
                return;
            }
            // still not connected
        });
        _SmoldotProvider_checkClientPeercount.set(this, () => {
            this.send("system_health", [])
                .then(__classPrivateFieldGet(this, _SmoldotProvider_simulateLifecycle, "f"))
                .catch((error) => this.emit("error", new HealthCheckError(error)));
        }
        /**
         * "Connect" the WASM client - starts the smoldot WASM client
         */
        );
        /**
         * "Connect" the WASM client - starts the smoldot WASM client
         */
        this.connect = async () => {
            assert(!__classPrivateFieldGet(this, _SmoldotProvider_client, "f") && !__classPrivateFieldGet(this, _SmoldotProvider_isConnected, "f"), "Client is already connected");
            try {
                __classPrivateFieldSet(this, _SmoldotProvider_client, __classPrivateFieldGet(this, _SmoldotProvider_smoldot, "f").start({
                    forbidWs: true,
                    maxLogLevel: 3 /* no debug/trace messages */,
                }), "f");
                if (__classPrivateFieldGet(this, _SmoldotProvider_parachainSpecs, "f")) {
                    const relay = await __classPrivateFieldGet(this, _SmoldotProvider_client, "f").addChain({
                        chainSpec: __classPrivateFieldGet(this, _SmoldotProvider_chainSpec, "f"),
                    });
                    __classPrivateFieldSet(this, _SmoldotProvider_chain, await __classPrivateFieldGet(this, _SmoldotProvider_client, "f").addChain({
                        chainSpec: __classPrivateFieldGet(this, _SmoldotProvider_parachainSpecs, "f"),
                        jsonRpcCallback: (response) => {
                            __classPrivateFieldGet(this, _SmoldotProvider_handleRpcReponse, "f").call(this, response);
                        },
                        potentialRelayChains: [relay],
                    }), "f");
                }
                else {
                    __classPrivateFieldSet(this, _SmoldotProvider_chain, await __classPrivateFieldGet(this, _SmoldotProvider_client, "f").addChain({
                        chainSpec: __classPrivateFieldGet(this, _SmoldotProvider_chainSpec, "f"),
                        jsonRpcCallback: (response) => {
                            __classPrivateFieldGet(this, _SmoldotProvider_handleRpcReponse, "f").call(this, response);
                        },
                    }), "f");
                }
                __classPrivateFieldSet(this, _SmoldotProvider_connectionStatePingerId, setInterval(__classPrivateFieldGet(this, _SmoldotProvider_checkClientPeercount, "f"), this.healthPingerInterval), "f");
            }
            catch (error) {
                this.emit("error", error);
            }
        };
        __classPrivateFieldSet(this, _SmoldotProvider_chainSpec, chainSpec, "f");
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
        __classPrivateFieldSet(this, _SmoldotProvider_smoldot, sm || smoldot, "f");
        __classPrivateFieldSet(this, _SmoldotProvider_connectionStatePingerId, null, "f");
        if (parachain) {
            __classPrivateFieldSet(this, _SmoldotProvider_parachainSpecs, parachain, "f");
        }
    }
    /**
     * Lets polkadot-js know we support subscriptions
     * @returns `true`
     */
    get hasSubscriptions() {
        return true;
    }
    /**
     * Returns a clone of the object
     * @throws throws an error as this is not supported.
     */
    clone() {
        throw new Error("clone() is not supported.");
    }
    /**
     * Manually "disconnect" - drops the reference to the WASM client
     */
    // eslint-disable-next-line @typescript-eslint/require-await
    async disconnect() {
        try {
            if (__classPrivateFieldGet(this, _SmoldotProvider_client, "f")) {
                // eslint-disable-next-line @typescript-eslint/no-unsafe-call
                __classPrivateFieldGet(this, _SmoldotProvider_client, "f").terminate();
            }
        }
        catch (error) {
            this.emit("error", error);
        }
        finally {
            if (__classPrivateFieldGet(this, _SmoldotProvider_connectionStatePingerId, "f") !== null) {
                clearInterval(__classPrivateFieldGet(this, _SmoldotProvider_connectionStatePingerId, "f"));
            }
            __classPrivateFieldSet(this, _SmoldotProvider_isConnected, false, "f");
            this.emit("disconnected");
        }
    }
    /**
     * Whether the node is connected or not.
     * @returns true if connected
     */
    get isConnected() {
        return __classPrivateFieldGet(this, _SmoldotProvider_isConnected, "f");
    }
    /**
     * Listen to provider events - in practice the smoldot provider only
     * emits a `connected` event after successfully starting the smoldot client
     * and `disconnected` after `disconnect` is called.
     * @param type - Event
     * @param sub  - Callback
     */
    on(type, sub) {
        __classPrivateFieldGet(this, _SmoldotProvider_eventemitter, "f").on(type, sub);
        return () => {
            __classPrivateFieldGet(this, _SmoldotProvider_eventemitter, "f").removeListener(type, sub);
        };
    }
    /**
     * Send an RPC request  the wasm client
     * @param method       - The RPC methods to execute
     * @param params       - Encoded paramaters as applicable for the method
     * @param subscription - Subscription details (internally used by `subscribe`)
     */
    async send(method, 
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    params, subscription) {
        return new Promise((resolve, reject) => {
            assert(__classPrivateFieldGet(this, _SmoldotProvider_client, "f"), "Client is not initialised");
            assert(__classPrivateFieldGet(this, _SmoldotProvider_chain, "f"), "Chain is not initialised");
            const json = __classPrivateFieldGet(this, _SmoldotProvider_coder, "f").encodeJson(method, params);
            const id = __classPrivateFieldGet(this, _SmoldotProvider_coder, "f").getId();
            const callback = (error, result) => {
                error ? reject(error) : resolve(result);
            };
            l.debug(() => ["calling", method, json]);
            __classPrivateFieldGet(this, _SmoldotProvider_handlers, "f")[id] = {
                callback,
                method,
                subscription,
            };
            __classPrivateFieldGet(this, _SmoldotProvider_chain, "f").sendJsonRpc(json);
        });
    }
    /**
     * subscribe
     * Allows subscribing to a specific event.
     * @param  type     - Subscription type
     * @param  method   - Subscription method
     * @param  params   - Parameters of type any[]
     * @param  callback - ProviderInterfaceCallback
     * @returns A promise (Promise\<number|string\>) resolving to the id of the subscription you can use with [[unsubscribe]].
     *
     * @example
     * <BR>
     *
     * ```javascript
     * const provider = new SmoldotProvider(client);
     * const rpc = new Rpc(provider);
     *
     * rpc.state.subscribeStorage([[storage.balances.freeBalance, <Address>]], (_, values) => {
     *   console.log(values)
     * }).then((subscriptionId) => {
     *   console.log('balance changes subscription id: ', subscriptionId)
     * })
     * ```
     */
    async subscribe(
    // the "method" property of the JSON response to this subscription
    type, 
    // the "method" property of the JSON request to register the subscription
    method, 
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    params, callback) {
        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
        return await this.send(method, params, { callback, type });
    }
    /**
     * Allows unsubscribing to subscriptions made with [[subscribe]].
     */
    async unsubscribe(type, method, id) {
        const subscription = `${type}::${id}`;
        if (isUndefined(__classPrivateFieldGet(this, _SmoldotProvider_subscriptions, "f")[subscription])) {
            l.debug(() => `Unable to find active subscription=${subscription}`);
            return false;
        }
        delete __classPrivateFieldGet(this, _SmoldotProvider_subscriptions, "f")[subscription];
        return (await this.send(method, [id]));
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    emit(type, ...args) {
        __classPrivateFieldGet(this, _SmoldotProvider_eventemitter, "f").emit(type, ...args);
    }
}
_SmoldotProvider_chainSpec = new WeakMap(), _SmoldotProvider_coder = new WeakMap(), _SmoldotProvider_eventemitter = new WeakMap(), _SmoldotProvider_handlers = new WeakMap(), _SmoldotProvider_subscriptions = new WeakMap(), _SmoldotProvider_waitingForId = new WeakMap(), _SmoldotProvider_connectionStatePingerId = new WeakMap(), _SmoldotProvider_isConnected = new WeakMap(), _SmoldotProvider_client = new WeakMap(), _SmoldotProvider_chain = new WeakMap(), _SmoldotProvider_parachainSpecs = new WeakMap(), _SmoldotProvider_smoldot = new WeakMap(), _SmoldotProvider_handleRpcReponse = new WeakMap(), _SmoldotProvider_onMessageResult = new WeakMap(), _SmoldotProvider_onMessageSubscribe = new WeakMap(), _SmoldotProvider_simulateLifecycle = new WeakMap(), _SmoldotProvider_checkClientPeercount = new WeakMap();
//# sourceMappingURL=SmoldotProvider.js.map