import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { ArtifactParser } from "./ArtifactParser.js";
import { MESSAGE_ATTACHMENT_TOOL_CALL_PREFIX } from "../services/blob-storage/attachment-artifacts.js";
import { getLedgerArtifacts } from "@inkeep/agents-core";

//#region src/domains/run/artifacts/replay-hydration.ts
const logger = getLogger$1("replay-hydration");
/**
* Normalize a data part's raw `data` field. DB rows sometimes store the JSON
* as a string; upstream code elsewhere may have already parsed it.
*/
function parseDataPart(raw) {
	if (typeof raw !== "string") return raw;
	try {
		return JSON.parse(raw);
	} catch {
		return raw;
	}
}
/**
* A data part is an artifact ref iff it carries both `artifactId` and
* `toolCallId` strings.
*/
function asArtifactRef(data) {
	if (!data || typeof data !== "object") return null;
	const d = data;
	if (typeof d.artifactId === "string" && typeof d.toolCallId === "string") return {
		artifactId: d.artifactId,
		toolCallId: d.toolCallId
	};
	return null;
}
/**
* True for refs that exist purely as server-side bookkeeping for a user's
* file upload. These are paired with a sibling `file` part that already
* carries everything the UI needs — returning the ref on replay just
* duplicates the attachment in the UI.
*/
function isAttachmentBookkeepingRef(ref) {
	return ref.toolCallId.startsWith(MESSAGE_ATTACHMENT_TOOL_CALL_PREFIX);
}
function artifactMapKey(artifactId, toolCallId) {
	return `${artifactId}:${toolCallId}`;
}
/**
* Construct an ArtifactParser scoped for replay. The parser only touches
* `tenantId`/`projectId` on the execution context during the `getArtifactSummary`
* path we exercise here — and that path short-circuits on the pre-loaded
* `artifactMap` before the stricter guards run, so no further context is
* required.
*/
function createReplayArtifactParser(scopes) {
	return new ArtifactParser({
		tenantId: scopes.tenantId,
		projectId: scopes.projectId
	});
}
/**
* Walk a batch of messages, collect every artifact ref's `toolCallId`, and
* fetch the matching ledger rows in a single query. Callers pass the
* resulting map to `hydrateArtifactRef` so the parser's summary-lookup hits
* the map branch and skips any DB fallback.
*/
async function loadArtifactMapForMessages(scopes, messages) {
	const toolCallIds = /* @__PURE__ */ new Set();
	for (const msg of messages) {
		const parts = msg.content.parts;
		if (!parts) continue;
		for (const p of parts) {
			if ((p.kind ?? p.type) !== "data") continue;
			const ref = asArtifactRef(parseDataPart(p.data));
			if (ref && !isAttachmentBookkeepingRef(ref)) toolCallIds.add(ref.toolCallId);
		}
	}
	if (toolCallIds.size === 0) return /* @__PURE__ */ new Map();
	const batchToolCallIds = Array.from(toolCallIds);
	let artifacts;
	try {
		artifacts = await getLedgerArtifacts(runDbClient_default)({
			scopes,
			toolCallIds: batchToolCallIds
		});
	} catch (error) {
		logger.error({
			err: error,
			tenantId: scopes.tenantId,
			projectId: scopes.projectId,
			toolCallIdCount: batchToolCallIds.length
		}, "Failed to batch-load ledger artifacts for conversation replay");
		return /* @__PURE__ */ new Map();
	}
	const map = /* @__PURE__ */ new Map();
	for (const a of artifacts) if (a.toolCallId) map.set(artifactMapKey(a.artifactId, a.toolCallId), a);
	return map;
}
/**
* Build everything a conversation-replay pass needs to hydrate artifact refs
* in a single shot: one batched ledger query + one scoped parser. Safe to
* call on empty message lists (returns a context whose map is empty and
* whose parser is never consulted).
*/
async function createReplayHydrationContext(scopes, messages) {
	const artifactMap = await loadArtifactMapForMessages(scopes, messages);
	return {
		parser: createReplayArtifactParser(scopes),
		artifactMap
	};
}
/**
* Hydrate a single `{artifactId, toolCallId}` ref into the Vercel-facing
* `data-artifact` part shape. Delegates to `ArtifactParser.parseObject` so
* we inherit the streaming path's summary-formatting and `typeSchema`
* attachment logic without copying it.
*
* Returns `null` on a ledger miss — matching streaming behavior (which emits
* `[]` rather than an unresolved placeholder).
*/
async function hydrateArtifactRef(ctx, ref) {
	let streamParts;
	try {
		streamParts = await ctx.parser.parseObject({
			name: "Artifact",
			props: {
				artifact_id: ref.artifactId,
				tool_call_id: ref.toolCallId
			}
		}, ctx.artifactMap);
	} catch (error) {
		logger.error({
			err: error,
			artifactId: ref.artifactId,
			toolCallId: ref.toolCallId
		}, "Failed to hydrate artifact ref on conversation replay");
		return null;
	}
	const first = streamParts[0];
	if (!first || first.kind !== "data" || !first.data) return null;
	return {
		type: "data-artifact",
		data: first.data
	};
}

//#endregion
export { asArtifactRef, createReplayHydrationContext, hydrateArtifactRef, isAttachmentBookkeepingRef, parseDataPart };