import { L as hideClassPrivateProps, R as ipnsNameToIpnsOverPubsubTopic, ir as PKCError, or as logger_default } from "./schema-Bo3XK8Oe.js";
import { n as getHeliaDebugContext, t as connectToPubsubPeers } from "./util-I3gVOp6w.js";
import { CID } from "multiformats/cid";
import { peerIdFromString } from "@libp2p/peer-id";
import { multihashToIPNSRoutingKey, unmarshalIPNSRecord } from "ipns";
import { ipnsValidator } from "ipns/validator";
import { MemoryBlockstore } from "blockstore-core";
import { EventEmitter as EventEmitter$1 } from "events";
import { createHelia, libp2pDefaults } from "helia";
import { ipns } from "@helia/ipns";
import { gossipsub } from "@libp2p/gossipsub";
import { identify } from "@libp2p/identify";
import { bitswap } from "@helia/block-brokers";
import { delegatedRoutingV1HttpApiClient } from "@helia/delegated-routing-v1-http-api-client";
import { unixfs } from "@helia/unixfs";
import { fetch } from "@libp2p/fetch";
import { pubsub } from "@helia/ipns/routing";
//#region dist/node/runtime/node/libp2p-extra-transports.js
const extraLibp2pTransports = [];
//#endregion
//#region dist/node/helia/libp2pjsClient.js
var Libp2pJsClient = class {
	constructor(libp2pJsClientOptions) {
		this._helia = libp2pJsClientOptions.helia;
		this._heliaUnixfs = libp2pJsClientOptions.heliaUnixfs;
		this._heliaIpnsRouter = libp2pJsClientOptions.heliaIpnsRouter;
		this.heliaWithKuboRpcClientFunctions = libp2pJsClientOptions.heliaWithKuboRpcClientFunctions;
		this._libp2pJsClientsOptions = libp2pJsClientOptions.libp2pJsClientsOptions;
		this._mergedHeliaOptions = libp2pJsClientOptions.mergedHeliaOptions;
		this.key = libp2pJsClientOptions.key;
		this.countOfUsesOfInstance = libp2pJsClientOptions.countOfUsesOfInstance;
		hideClassPrivateProps(this);
	}
};
//#endregion
//#region dist/node/helia/helia-for-pkc.js
const log = logger_default("pkc-js:libp2p-js");
const libp2pJsClients = {};
const creatingLibp2pJsClients = {};
function getDelegatedRoutingFields(routers) {
	const routersObj = {};
	for (let i = 0; i < routers.length; i++) {
		const factory = delegatedRoutingV1HttpApiClient({ url: routers[i] });
		routersObj["delegatedRouting" + i] = (components) => {
			const client = factory(components);
			client.getIPNS = client.getPeers = client.putIPNS = void 0;
			return client;
		};
	}
	return routersObj;
}
async function createLibp2pJsClientOrUseExistingOne(pkcOptions) {
	if (!pkcOptions.httpRoutersOptions?.length) throw Error("You need to have pkc.httpRouterOptions to set up helia");
	const existingClient = libp2pJsClients[pkcOptions.key];
	if (existingClient) {
		existingClient.countOfUsesOfInstance++;
		return existingClient;
	}
	const creatingClientPromise = creatingLibp2pJsClients[pkcOptions.key];
	if (creatingClientPromise) {
		const client = await creatingClientPromise;
		client.countOfUsesOfInstance++;
		return client;
	}
	creatingLibp2pJsClients[pkcOptions.key] = (async () => {
		const mergedHeliaInit = {
			libp2p: {
				addresses: { listen: [] },
				peerDiscovery: void 0,
				transports: [...libp2pDefaults().transports ?? [], ...extraLibp2pTransports],
				connectionMonitor: { abortConnectionOnPingFailure: false },
				...pkcOptions.libp2pOptions,
				services: {
					identify: identify(),
					pubsub: gossipsub(),
					fetch: fetch(),
					...getDelegatedRoutingFields(pkcOptions.httpRoutersOptions),
					...pkcOptions.libp2pOptions?.services
				}
			},
			blockstore: new MemoryBlockstore(),
			blockBrokers: [bitswap()],
			start: false,
			...pkcOptions.heliaOptions
		};
		const helia = await createHelia(mergedHeliaInit);
		helia.routing.routers = helia.routing.routers.filter((r) => r.constructor.name !== "HTTPGatewayRouter");
		log("Initialized libp2pjs helia with key", pkcOptions.key, "peer id", helia.libp2p.peerId.toString());
		const pubsubEventHandler = new EventEmitter$1();
		helia.libp2p.services.pubsub.addEventListener("message", (evt) => {
			log.trace(`Event from helia libp2p pubsub: on pubsub topic (string, e.g. community address) ${evt.detail.topic}`);
			const msgFormatted = {
				data: evt.detail.data,
				topic: evt.detail.topic,
				type: evt.detail.type
			};
			pubsubEventHandler.emit(evt.detail.topic, msgFormatted);
		});
		const heliaFs = unixfs(helia);
		const ipnsNameResolver = ipns(helia, { routers: [pubsub(helia)] });
		ipnsNameResolver.routers = ipnsNameResolver.routers.filter((r) => r?.constructor?.name !== "LocalStoreRouting");
		const WARMUP_MAX_PEERS = 4;
		const warmupPromisesByTopic = /* @__PURE__ */ new Map();
		const warmupForTopic = (topic, options) => {
			if (helia.libp2p.services.pubsub.getSubscribers(topic).length > 0) return Promise.resolve();
			const existing = warmupPromisesByTopic.get(topic);
			if (existing) return existing;
			const p = connectToPubsubPeers({
				helia,
				pubsubTopic: topic,
				maxPeers: WARMUP_MAX_PEERS,
				options,
				log: logger_default("pkc-js:helia:pubsub:warmup")
			}).then(() => void 0).finally(() => {
				warmupPromisesByTopic.delete(topic);
			});
			warmupPromisesByTopic.set(topic, p);
			return p;
		};
		const throwIfHeliaIsStoppingOrStopped = () => {
			if (helia.libp2p.status === "stopped" || helia.libp2p.status === "stopping") throw new PKCError("ERR_HELIAS_STOPPING_OR_STOPPED", {
				heliaKey: pkcOptions.key,
				helia,
				...getHeliaDebugContext(helia)
			});
		};
		const heliaWithKuboRpcClientShape = {
			name: { resolve: (ipnsName, options) => {
				throwIfHeliaIsStoppingOrStopped();
				async function* generator() {
					const currentName = typeof ipnsName === "string" ? ipnsName : ipnsName.toString();
					const ipnsNameAsPeerId = peerIdFromString(currentName);
					log.trace("Resolving ipns name", currentName, "with options", options);
					const ipnsPubsubTopic = ipnsNameToIpnsOverPubsubTopic(ipnsNameAsPeerId.toString());
					let warmupOutcome = { attempted: false };
					if (helia.libp2p.services.pubsub.getSubscribers(ipnsPubsubTopic).length === 0) {
						const warmupStart = Date.now();
						try {
							await warmupForTopic(ipnsPubsubTopic, options);
							warmupOutcome = {
								attempted: true,
								durationMs: Date.now() - warmupStart,
								subscribersAfterWarmup: helia.libp2p.services.pubsub.getSubscribers(ipnsPubsubTopic).length
							};
						} catch (warmupErr) {
							warmupOutcome = {
								attempted: true,
								durationMs: Date.now() - warmupStart,
								error: warmupErr
							};
							log.error("Pre-resolve peer warmup failed for", ipnsPubsubTopic, warmupErr);
						}
					}
					const routingKey = multihashToIPNSRoutingKey(ipnsNameAsPeerId.toMultihash());
					let recordBytes;
					const routerErrors = [];
					for (const router of ipnsNameResolver.routers) try {
						const got = await router.get(routingKey, {
							...options,
							validate: false
						});
						if (got) {
							recordBytes = got;
							break;
						}
					} catch (err) {
						routerErrors.push(err);
					}
					if (!recordBytes) throw new PKCError("ERR_RESOLVED_IPNS_P2P_TO_UNDEFINED", {
						ipnsName,
						currentName,
						ipnsPubsubTopic,
						ipnsResolveOptions: options,
						warmupOutcome,
						routerErrors,
						subscribersAtResolveTime: helia.libp2p.services.pubsub.getSubscribers(ipnsPubsubTopic).length,
						httpRouters: pkcOptions.httpRoutersOptions,
						...getHeliaDebugContext(helia)
					});
					await ipnsValidator(routingKey, recordBytes);
					yield unmarshalIPNSRecord(recordBytes).value;
				}
				return generator();
			} },
			cat(ipfsPath, options) {
				throwIfHeliaIsStoppingOrStopped();
				if (ipfsPath.includes("/")) {
					const rootCid = ipfsPath.split("/")[0];
					const path = ipfsPath.split("/").slice(1).join("/");
					return heliaFs.cat(CID.parse(rootCid), {
						...options,
						path
					});
				} else return heliaFs.cat(CID.parse(ipfsPath), options);
			},
			pubsub: {
				ls: async () => helia.libp2p.services.pubsub.getTopics(),
				peers: async (topic, options) => helia.libp2p.services.pubsub.getSubscribers(topic),
				publish: async (topic, data, options) => {
					throwIfHeliaIsStoppingOrStopped();
					const wasAlreadySubscribed = helia.libp2p.services.pubsub.getTopics().includes(topic);
					if (!wasAlreadySubscribed) helia.libp2p.services.pubsub.subscribe(topic);
					try {
						await warmupForTopic(topic, options);
						log("Published new data to pubsub topic (string, e.g. community address)", topic, "Direct gossipsub recipients (libp2p peer IDs, NOT signer/community addresses):", (await helia.libp2p.services.pubsub.publish(topic, data)).recipients.map((p) => p.toString()));
					} finally {
						if (!wasAlreadySubscribed) helia.libp2p.services.pubsub.unsubscribe(topic);
					}
				},
				subscribe: async (topic, handler, options) => {
					throwIfHeliaIsStoppingOrStopped();
					pubsubEventHandler.on(topic, handler);
					const warmupPromise = warmupForTopic(topic, options);
					helia.libp2p.services.pubsub.subscribe(topic);
					await warmupPromise;
				},
				unsubscribe: async (topic, handler, options) => {
					throwIfHeliaIsStoppingOrStopped();
					pubsubEventHandler.removeListener(topic, handler);
					if (pubsubEventHandler.listenerCount(topic) === 0) helia.libp2p.services.pubsub.unsubscribe(topic);
				}
			},
			async add(entry, options) {
				throw Error("Helia 'add' is not supported at the moment in pkc-js API");
			},
			async stop(options) {
				const clientFromMap = libp2pJsClients[pkcOptions.key];
				if (!clientFromMap) return;
				if (clientFromMap.countOfUsesOfInstance <= 0) return;
				clientFromMap.countOfUsesOfInstance--;
				if (clientFromMap.countOfUsesOfInstance === 0) {
					delete libp2pJsClients[pkcOptions.key];
					for (const router of ipnsNameResolver.routers) {
						const lifecycle = router;
						if (typeof lifecycle.stop === "function") try {
							await lifecycle.stop();
						} catch (e) {
							log.error("Error stopping IPNS router", router?.constructor?.name, e);
						}
					}
					for (const topic of helia.libp2p.services.pubsub.getTopics()) helia.libp2p.services.pubsub.unsubscribe(topic);
					try {
						await helia.stop();
					} catch (e) {
						log.error("Error stopping helia", e);
					}
					log("Helia/libp2p-js stopped with key", pkcOptions.key, "and peer id", helia.libp2p.peerId.toString());
				}
			}
		};
		const originalSubscribe = helia.libp2p.services.pubsub.subscribe.bind(helia.libp2p.services.pubsub);
		helia.libp2p.services.pubsub.subscribe = (topic) => {
			throwIfHeliaIsStoppingOrStopped();
			warmupForTopic(topic).catch((err) => log.error("warmup failed for topic", topic, err));
			originalSubscribe(topic);
		};
		const client = new Libp2pJsClient({
			helia,
			heliaWithKuboRpcClientFunctions: heliaWithKuboRpcClientShape,
			heliaUnixfs: heliaFs,
			heliaIpnsRouter: ipnsNameResolver,
			mergedHeliaOptions: mergedHeliaInit,
			countOfUsesOfInstance: 1,
			libp2pJsClientsOptions: pkcOptions,
			key: pkcOptions.key
		});
		await helia.start();
		log("Helia/libp2p-js started with key", pkcOptions.key, "and peer id", helia.libp2p.peerId.toString());
		libp2pJsClients[pkcOptions.key] = client;
		return client;
	})();
	const createdClientPromise = creatingLibp2pJsClients[pkcOptions.key];
	if (!createdClientPromise) throw new Error("Missing creation promise after initialization");
	try {
		return await createdClientPromise;
	} finally {
		delete creatingLibp2pJsClients[pkcOptions.key];
	}
}
//#endregion
export { createLibp2pJsClientOrUseExistingOne };
