const require_store = require("../store.cjs");
const require_namespace = require("../namespace.cjs");
//#region src/stream/discovery/subgraphs.ts
/** Stable empty maps — reused on {@link SubgraphDiscovery.reset}. */
const EMPTY_SUBGRAPH_MAP = /* @__PURE__ */ new Map();
const EMPTY_SUBGRAPH_BY_NODE_MAP = /* @__PURE__ */ new Map();
/**
* LangGraph namespaces a node invocation as `<node_name>:<uuid>`
* (parallel fan-outs share `<node_name>` as a prefix but each get a
* fresh uuid). Extract the node-name half so callers can key
* discovery lookups on names they wrote in `addNode(...)`.
*/
function parseNodeName(segment) {
	const colon = segment.indexOf(":");
	return colon === -1 ? segment : segment.slice(0, colon);
}
var SubgraphDiscovery = class {
	store = new require_store.StreamStore(/* @__PURE__ */ new Map());
	byNodeStore = new require_store.StreamStore(/* @__PURE__ */ new Map());
	/**
	* Latest known status for every namespaced lifecycle event we have
	* ever observed. A shadow entry is NOT necessarily a subgraph —
	* it is only projected into the committed stores once the same
	* namespace also appears in {@link #promoted}.
	*/
	#shadow = /* @__PURE__ */ new Map();
	/**
	* Namespaces that have been observed as a strict prefix of a
	* deeper namespace and are therefore confirmed subgraph hosts.
	* Insertion order is preserved and becomes the iteration order
	* of the committed snapshot maps.
	*/
	#promoted = /* @__PURE__ */ new Set();
	/** Feed a single root event. Non-discovery events are ignored. */
	push(event) {
		if (event.method === "values") {
			this.#onValuesEvent(event);
			return;
		}
		if (event.method !== "lifecycle") return;
		const lifecycle = event;
		const namespace = lifecycle.params.namespace;
		if (require_namespace.isRootNamespace(namespace)) return;
		const id = require_namespace.namespaceKey(namespace);
		const data = lifecycle.params.data;
		const nodeName = parseNodeName(namespace[namespace.length - 1] ?? "");
		let touched = false;
		for (let depth = 1; depth < namespace.length; depth += 1) {
			const ancestorId = require_namespace.namespaceKey(namespace.slice(0, depth));
			if (!this.#promoted.has(ancestorId)) {
				this.#promoted.add(ancestorId);
				if (this.#shadow.has(ancestorId)) touched = true;
			}
		}
		if (data.event === "started") {
			if (!this.#shadow.has(id)) {
				this.#shadow.set(id, {
					id,
					namespace: [...namespace],
					nodeName,
					status: "running",
					startedAt: /* @__PURE__ */ new Date(),
					completedAt: null
				});
				if (this.#promoted.has(id)) touched = true;
			}
		} else if (data.event === "completed" || data.event === "interrupted" || data.event === "failed") {
			const entry = this.#ensureShadow(id, namespace, nodeName);
			if (data.event === "failed") entry.status = "error";
			else entry.status = "complete";
			entry.completedAt = /* @__PURE__ */ new Date();
			if (this.#promoted.has(id)) touched = true;
		}
		if (touched) this.#commit();
	}
	/**
	* Promote subgraph host namespaces from namespaced `values` snapshots.
	*
	* Older protocol streams exposed subgraph structure primarily through
	* nested `lifecycle` events: a host namespace such as
	* `["research:<uuid>"]` was promoted once a deeper namespace like
	* `["research:<uuid>", "inner:<uuid>"]` appeared. Some runtimes now
	* emit the useful subgraph signal as `values` snapshots scoped directly
	* to the host namespace instead, without forwarding inner lifecycle
	* events to the client. In that shape, the namespace on the `values`
	* event is already the selector target that `useMessages(stream,
	* subgraph)` should subscribe to, so we create/promote the shadow
	* subgraph entry from that namespace.
	*
	* Root `values` events are ignored because they represent the parent
	* thread state, not a subgraph. Tool/subagent namespaces are also
	* ignored because deep-agent task tools emit their own namespaced
	* `values` snapshots under `tools:*` / `task:*`; those are discovered
	* by `SubagentDiscovery` and must not be duplicated as subgraphs.
	*
	* A `values` event does not carry lifecycle terminal status, so the
	* entry remains marked `running` until a matching lifecycle terminal
	* event arrives. If no terminal arrives, the discovery map still
	* contains the host namespace, which is the important invariant for
	* scoped selectors.
	*/
	#onValuesEvent(event) {
		const namespace = event.params.namespace;
		if (require_namespace.isRootNamespace(namespace)) return;
		if (require_namespace.isInternalWorkNamespace(namespace)) return;
		const data = event.params.data;
		if (data == null || typeof data !== "object" || Array.isArray(data)) return;
		const id = require_namespace.namespaceKey(namespace);
		const nodeName = parseNodeName(namespace[namespace.length - 1] ?? "");
		const entry = this.#ensureShadow(id, namespace, nodeName);
		if (entry.status !== "complete" && entry.status !== "error") entry.status = "running";
		if (!this.#promoted.has(id)) this.#promoted.add(id);
		this.#commit();
	}
	/**
	* Seed subgraph hosts from checkpoint history (see
	* `namespace-from-history.collectSubgraphHostNamespaces`) so subgraph
	* cards render on thread refresh without waiting for the depth-1 SSE
	* replay. Supplies the host/promotion decision from history instead
	* of the live strict-prefix heuristic, then reuses the same
	* `#ensureShadow` / `#promoted` / `#commit` path. Idempotent: never
	* downgrades an entry that already reached a terminal state.
	*/
	seedFromHistory(hosts) {
		if (hosts.length === 0) return;
		for (const host of hosts) {
			if (require_namespace.isRootNamespace(host.namespace)) continue;
			const id = require_namespace.namespaceKey(host.namespace);
			const lastSegment = host.namespace[host.namespace.length - 1] ?? "";
			const entry = this.#ensureShadow(id, host.namespace, parseNodeName(lastSegment));
			if (host.status !== "running" && entry.status !== "complete" && entry.status !== "error") {
				entry.status = host.status;
				entry.completedAt = /* @__PURE__ */ new Date();
			}
			this.#promoted.add(id);
		}
		this.#commit();
	}
	get snapshot() {
		return this.store.getSnapshot();
	}
	get byNodeSnapshot() {
		return this.byNodeStore.getSnapshot();
	}
	/**
	* Drop all discovery state. Called on thread rebind / dispose so a
	* new thread's subgraphs cannot bleed into the previous UI.
	*/
	reset() {
		this.#shadow.clear();
		this.#promoted.clear();
		this.store.setValue(EMPTY_SUBGRAPH_MAP);
		this.byNodeStore.setValue(EMPTY_SUBGRAPH_BY_NODE_MAP);
	}
	#ensureShadow(id, namespace, nodeName) {
		let entry = this.#shadow.get(id);
		if (entry == null) {
			entry = {
				id,
				namespace: [...namespace],
				nodeName,
				status: "running",
				startedAt: /* @__PURE__ */ new Date(),
				completedAt: null
			};
			this.#shadow.set(id, entry);
		}
		return entry;
	}
	#commit() {
		const snapshots = [];
		for (const id of this.#promoted) {
			const entry = this.#shadow.get(id);
			if (entry == null) continue;
			snapshots.push({ ...entry });
		}
		this.store.setValue(new Map(snapshots.map((s) => [s.id, s])));
		const byNode = /* @__PURE__ */ new Map();
		for (const snap of snapshots) {
			const bucket = byNode.get(snap.nodeName);
			if (bucket == null) byNode.set(snap.nodeName, [snap]);
			else bucket.push(snap);
		}
		this.byNodeStore.setValue(byNode);
	}
};
//#endregion
exports.SubgraphDiscovery = SubgraphDiscovery;

//# sourceMappingURL=subgraphs.cjs.map