import { et as pubsubTopicToDhtKeyCid, ir as PKCError } from "./schema-B7HIUI8w.js";
//#region dist/node/helia/util.js
const TOPIC_SUBSCRIBER_WAIT_TIMEOUT_MS = 1e4;
const MESH_PEER_WAIT_TIMEOUT_MS = 3e3;
function getHeliaDebugContext(helia) {
	const connections = helia.libp2p.getConnections();
	return {
		heliaPeerId: helia.libp2p.peerId.toString(),
		heliaStatus: helia.libp2p.status,
		connectedPeerCount: connections.length,
		connections: connections.map((c) => ({
			peer: c.remotePeer.toString(),
			multiaddr: String(c.remoteAddr),
			direction: c.direction
		}))
	};
}
function waitForTopicSubscriber({ helia, topic, timeoutMs, abortSignal }) {
	const pubsub = helia.libp2p.services.pubsub;
	if (pubsub.getSubscribers(topic).length > 0) return Promise.resolve();
	if (abortSignal?.aborted) return Promise.reject(new PKCError("ERR_PUBSUB_TOPIC_PEER_WAIT_ABORTED", { topic }));
	return new Promise((resolve, reject) => {
		const cleanup = () => {
			pubsub.removeEventListener("subscription-change", onChange);
			clearInterval(safetyPoll);
			clearTimeout(timer);
			abortSignal?.removeEventListener("abort", onAbort);
		};
		const tryResolve = () => {
			if (pubsub.getSubscribers(topic).length > 0) {
				cleanup();
				resolve();
				return true;
			}
			return false;
		};
		const onChange = (e) => {
			if (e.detail.subscriptions.some((s) => s.topic === topic && s.subscribe)) tryResolve();
		};
		const onAbort = () => {
			cleanup();
			reject(new PKCError("ERR_PUBSUB_TOPIC_PEER_WAIT_ABORTED", { topic }));
		};
		const timer = setTimeout(() => {
			cleanup();
			reject(new PKCError("ERR_TIMEOUT_WAITING_FOR_PUBSUB_TOPIC_PEERS", {
				topic,
				timeoutMs
			}));
		}, timeoutMs);
		const safetyPoll = setInterval(tryResolve, 1e3);
		pubsub.addEventListener("subscription-change", onChange);
		abortSignal?.addEventListener("abort", onAbort, { once: true });
		tryResolve();
	});
}
function waitForMeshPeer({ helia, topic, timeoutMs, abortSignal }) {
	const pubsub = helia.libp2p.services.pubsub;
	if (pubsub.getMeshPeers(topic).length > 0) return Promise.resolve();
	if (abortSignal?.aborted) return Promise.resolve();
	return new Promise((resolve) => {
		const cleanup = () => {
			pubsub.removeEventListener("gossipsub:graft", onGraft);
			clearTimeout(timer);
			abortSignal?.removeEventListener("abort", onAbort);
		};
		const tryResolve = () => {
			if (pubsub.getMeshPeers(topic).length > 0) {
				cleanup();
				resolve();
				return true;
			}
			return false;
		};
		const onGraft = (e) => {
			if (e.detail.topic === topic) tryResolve();
		};
		const onAbort = () => {
			cleanup();
			resolve();
		};
		const timer = setTimeout(() => {
			cleanup();
			resolve();
		}, timeoutMs);
		pubsub.addEventListener("gossipsub:graft", onGraft);
		abortSignal?.addEventListener("abort", onAbort, { once: true });
		tryResolve();
	});
}
async function connectToPubsubPeers({ helia, pubsubTopic, maxPeers, options, log }) {
	const contentCid = pubsubTopicToDhtKeyCid(pubsubTopic);
	const peersWithContent = [];
	const connectedPeersWithContent = [];
	const peerDialToError = {};
	const findProvidersAbort = new AbortController();
	const findProvidersSignal = options?.signal ? AbortSignal.any([options.signal, findProvidersAbort.signal]) : findProvidersAbort.signal;
	const subscriberAppearedPromise = waitForTopicSubscriber({
		helia,
		topic: pubsubTopic,
		timeoutMs: TOPIC_SUBSCRIBER_WAIT_TIMEOUT_MS,
		abortSignal: findProvidersSignal
	});
	subscriberAppearedPromise.then(() => {
		if (!findProvidersAbort.signal.aborted) {
			log.trace("Aborting findProviders iterator - gossipsub subscription-change observed for topic", pubsubTopic);
			findProvidersAbort.abort();
		}
	}, () => {});
	const findProvidersStart = Date.now();
	const inflightDialPromises = [];
	const dialOptions = { ...options };
	try {
		for await (const peer of helia.libp2p.contentRouting.findProviders(contentCid, {
			...options,
			signal: findProvidersSignal
		})) {
			peersWithContent.push(peer);
			const dialPromise = (async () => {
				try {
					if (peer.multiaddrs?.length) await helia.libp2p.peerStore.merge(peer.id, { multiaddrs: peer.multiaddrs });
					const conn = await helia.libp2p.dial(peer.id, dialOptions);
					connectedPeersWithContent.push(conn);
					if (connectedPeersWithContent.length >= maxPeers && !findProvidersAbort.signal.aborted) {
						log.trace("Aborting findProviders after reaching maxPeers", maxPeers);
						findProvidersAbort.abort();
					}
				} catch (e) {
					const err = e;
					peerDialToError[peer.id.toString()] = {
						multiaddrs: peer.multiaddrs.map(String),
						errorName: err.name,
						errorMessage: err.message,
						errorStack: err.stack
					};
					log.trace("Failed to dial IPNS-Over-Pubsub peer", peer.id.toString(), "Due to error", e);
				}
			})();
			inflightDialPromises.push(dialPromise);
			if (connectedPeersWithContent.length >= maxPeers) {
				log.trace("Breaking findProviders loop after reaching maxPeers", maxPeers);
				break;
			}
		}
		log.trace("findProviders for", pubsubTopic, "took", Date.now() - findProvidersStart, "ms");
	} catch (e) {
		if (!findProvidersAbort.signal.aborted) {
			e.details = {
				...e.details,
				contentCid,
				options,
				maxPeersBeforeWeStopLookingForProviders: maxPeers,
				connectedPeersWithContent,
				peersWithContent,
				peerDialToError,
				...getHeliaDebugContext(helia)
			};
			throw e;
		}
	}
	let graftError = null;
	try {
		await subscriberAppearedPromise;
	} catch (graftErr) {
		graftError = graftErr;
		log.trace("gossipsub subscription-change did not arrive within timeout after warmup; resolver may still fall back", graftErr);
		if (inflightDialPromises.length) await Promise.race(inflightDialPromises).catch(() => {});
	}
	log.trace("Connected to", connectedPeersWithContent.length, "peers (snapshot at subscription-change)", "for content", contentCid);
	const pubsub = helia.libp2p.services.pubsub;
	if (!graftError && pubsub.getTopics().includes(pubsubTopic)) {
		const meshWaitStart = Date.now();
		await waitForMeshPeer({
			helia,
			topic: pubsubTopic,
			timeoutMs: MESH_PEER_WAIT_TIMEOUT_MS,
			abortSignal: options?.signal
		});
		const meshPeerCount = pubsub.getMeshPeers(pubsubTopic).length;
		const waitMs = Date.now() - meshWaitStart;
		if (meshPeerCount === 0) log.trace("Timed out waiting for mesh peers for topic", pubsubTopic, "after", waitMs, "ms");
		else log.trace("Mesh peers count after wait", meshPeerCount, "for topic", pubsubTopic, "took", waitMs, "ms");
	}
	if (graftError && options?.signal?.aborted) throw graftError;
	if (connectedPeersWithContent.length === 0 && graftError) {
		const error = new PKCError("ERR_FAILED_TO_DIAL_ANY_PEERS_PROVIDING_CID", {
			contentCid,
			peerDialToError,
			peersWithContent,
			options,
			graftError,
			...getHeliaDebugContext(helia)
		});
		log.error(error);
		throw error;
	}
	return connectedPeersWithContent;
}
//#endregion
export { getHeliaDebugContext as n, connectToPubsubPeers as t };
