import path from "node:path";
import { privateKeyToProtobuf } from "@libp2p/crypto/keys";
import { Thread, Worker, spawn } from "@chainsafe/threads";
import { chainConfigToJson } from "@lodestar/config";
import { AsyncIterableBridgeCaller, AsyncIterableBridgeHandler } from "../../util/asyncIterableToEvents.js";
import { peerIdFromString } from "../../util/peerId.js";
import { terminateWorkerThread, wireEventsOnMainThread } from "../../util/workerEvents.js";
import { networkEventDirection } from "../events.js";
import { NetworkWorkerThreadEventType, ReqRespBridgeEventBus, getReqRespBridgeReqEvents, getReqRespBridgeRespEvents, reqRespBridgeEventDirection, } from "./events.js";
// Worker constructor consider the path relative to the current working directory
const workerDir = process.env.NODE_ENV === "test" ? "../../../lib/network/core/" : "./";
const NETWORK_WORKER_EXIT_TIMEOUT_MS = 1000;
const NETWORK_WORKER_EXIT_RETRY_COUNT = 3;
/**
 * NetworkCore implementation using a Worker thread
 */
export class WorkerNetworkCore {
    modules;
    reqRespBridgeReqCaller;
    reqRespBridgeRespHandler;
    reqRespBridgeEventBus = new ReqRespBridgeEventBus();
    constructor(modules) {
        this.modules = modules;
        // Get called from main thread to issue a ReqResp request, and emits event to worker
        this.reqRespBridgeReqCaller = new AsyncIterableBridgeCaller(getReqRespBridgeReqEvents(this.reqRespBridgeEventBus));
        // Handles ReqResp response from worker and calls async generator in main thread
        this.reqRespBridgeRespHandler = new AsyncIterableBridgeHandler(getReqRespBridgeRespEvents(this.reqRespBridgeEventBus), (data) => modules.getReqRespHandler(data.method)(data.req, peerIdFromString(data.peerId), data.peerClient));
        wireEventsOnMainThread(NetworkWorkerThreadEventType.networkEvent, modules.events, modules.worker, modules.metrics, networkEventDirection);
        wireEventsOnMainThread(NetworkWorkerThreadEventType.reqRespBridgeEvents, this.reqRespBridgeEventBus, modules.worker, modules.metrics, reqRespBridgeEventDirection);
        Thread.errors(modules.networkThreadApi).subscribe((err) => {
            this.modules.logger.error("Network worker thread error", {}, err);
        });
        const { metrics } = modules;
        if (metrics) {
            metrics.networkWorkerHandler.reqRespBridgeReqCallerPending.addCollect(() => {
                metrics.networkWorkerHandler.reqRespBridgeReqCallerPending.set(this.reqRespBridgeReqCaller.pendingCount);
            });
        }
    }
    static async init(modules) {
        const { opts, config, privateKey } = modules;
        const { genesisTime, peerStoreDir, activeValidatorCount, localMultiaddrs, metricsEnabled, initialStatus, initialCustodyGroupCount, } = opts;
        const workerData = {
            opts,
            chainConfigJson: chainConfigToJson(config),
            genesisValidatorsRoot: config.genesisValidatorsRoot,
            privateKeyProto: privateKeyToProtobuf(privateKey),
            localMultiaddrs,
            metricsEnabled,
            peerStoreDir,
            genesisTime,
            initialStatus,
            initialCustodyGroupCount,
            activeValidatorCount,
            loggerOpts: modules.logger.toOpts(),
        };
        const workerOpts = {
            workerData,
        };
        if (globalThis.Bun) {
            workerOpts.suppressTranspileTS = true;
        }
        else {
            /**
             * maxYoungGenerationSizeMb defaults to 152mb through the cli option defaults.
             * That default value was determined via https://github.com/ChainSafe/lodestar/issues/2115 and
             * should be tuned further as needed.  If we update network code and see substantial
             * difference in the quantity of garbage collected this should get updated.  A value that is
             * too low will result in too much GC time and a value that is too high causes increased mark
             * and sweep for some reason (which is much much slower than scavenge).  A marginally too high
             * number causes detrimental slowdown from increased variable lookup time.  Empirical evidence
             * showed that there is a pretty big window of "correct" values but we can always tune as
             * necessary
             */
            workerOpts.resourceLimits = { maxYoungGenerationSizeMb: opts.maxYoungGenerationSizeMb };
        }
        const worker = new Worker(path.join(workerDir, "networkCoreWorker.js"), workerOpts);
        // biome-ignore lint/suspicious/noExplicitAny: Don't know any specific interface for the spawn
        const networkThreadApi = (await spawn(worker, {
            // A Lodestar Node may do very expensive task at start blocking the event loop and causing
            // the initialization to timeout. The number below is big enough to almost disable the timeout
            timeout: 5 * 60 * 1000,
            // TODO: types are broken on spawn, which claims that `NetworkWorkerApi` does not satisfies its contrains
        }));
        return new WorkerNetworkCore({
            ...modules,
            networkThreadApi,
            worker,
        });
    }
    async close() {
        this.modules.logger.debug("closing network core running in network worker");
        await this.getApi().close();
        this.modules.logger.debug("terminating network worker");
        await terminateWorkerThread({
            worker: this.getApi(),
            retryCount: NETWORK_WORKER_EXIT_RETRY_COUNT,
            retryMs: NETWORK_WORKER_EXIT_TIMEOUT_MS,
            logger: this.modules.logger,
        });
        this.modules.logger.debug("terminated network worker");
    }
    async test() {
        return;
    }
    scrapeMetrics() {
        return this.getApi().scrapeMetrics();
    }
    updateStatus(status) {
        return this.getApi().updateStatus(status);
    }
    reStatusPeers(peers) {
        return this.getApi().reStatusPeers(peers);
    }
    reportPeer(peer, action, actionName) {
        return this.getApi().reportPeer(peer, action, actionName);
    }
    // TODO: Should this just be events? Do they need to report errors back?
    prepareBeaconCommitteeSubnets(subscriptions) {
        return this.getApi().prepareBeaconCommitteeSubnets(subscriptions);
    }
    prepareSyncCommitteeSubnets(subscriptions) {
        return this.getApi().prepareSyncCommitteeSubnets(subscriptions);
    }
    subscribeGossipCoreTopics() {
        return this.getApi().subscribeGossipCoreTopics();
    }
    unsubscribeGossipCoreTopics() {
        return this.getApi().unsubscribeGossipCoreTopics();
    }
    // REST API queries
    getConnectedPeerCount() {
        return this.getApi().getConnectedPeerCount();
    }
    getConnectedPeers() {
        return this.getApi().getConnectedPeers();
    }
    getNetworkIdentity() {
        return this.getApi().getNetworkIdentity();
    }
    // ReqResp and gossip outgoing
    sendReqRespRequest(data) {
        return this.reqRespBridgeReqCaller.getAsyncIterable(data);
    }
    publishGossip(topic, data, opts) {
        return this.getApi().publishGossip(topic, data, opts);
    }
    // Custody
    setTargetGroupCount(count) {
        return this.getApi().setTargetGroupCount(count);
    }
    // Debug
    connectToPeer(peer, multiaddr) {
        return this.getApi().connectToPeer(peer, multiaddr);
    }
    disconnectPeer(peer) {
        return this.getApi().disconnectPeer(peer);
    }
    addDirectPeer(peer) {
        return this.getApi().addDirectPeer(peer);
    }
    removeDirectPeer(peerId) {
        return this.getApi().removeDirectPeer(peerId);
    }
    getDirectPeers() {
        return this.getApi().getDirectPeers();
    }
    dumpPeers() {
        return this.getApi().dumpPeers();
    }
    dumpPeer(peerIdStr) {
        return this.getApi().dumpPeer(peerIdStr);
    }
    dumpPeerScoreStats() {
        return this.getApi().dumpPeerScoreStats();
    }
    dumpGossipPeerScoreStats() {
        return this.getApi().dumpGossipPeerScoreStats();
    }
    dumpDiscv5KadValues() {
        return this.getApi().dumpDiscv5KadValues();
    }
    dumpMeshPeers() {
        return this.getApi().dumpMeshPeers();
    }
    writeNetworkThreadProfile(durationMs, dirpath) {
        return this.getApi().writeProfile(durationMs, dirpath);
    }
    writeDiscv5Profile(durationMs, dirpath) {
        return this.getApi().writeDiscv5Profile(durationMs, dirpath);
    }
    writeNetworkHeapSnapshot(prefix, dirpath) {
        return this.getApi().writeHeapSnapshot(prefix, dirpath);
    }
    writeDiscv5HeapSnapshot(prefix, dirpath) {
        return this.getApi().writeDiscv5HeapSnapshot(prefix, dirpath);
    }
    getApi() {
        return this.modules.networkThreadApi;
    }
}
//# sourceMappingURL=networkCoreWorkerHandler.js.map