import { ledgerArtifacts } from "../../db/runtime/runtime-schema.js";
import { generateId } from "../../utils/conversations.js";
import { projectScopedWhere } from "../manage/scope-helpers.js";
import { isRetryableError } from "../../retry/retryable-errors.js";
import { and, count, eq, inArray } from "drizzle-orm";

//#region src/data-access/runtime/ledgerArtifacts.ts
/**
* Validate artifact data before database insertion
*/
function validateArtifactData(artifact, index) {
	if (!artifact.artifactId?.trim()) throw new Error(`Artifact at index ${index} missing required artifactId`);
	if (artifact.parts) try {
		JSON.stringify(artifact.parts);
	} catch (error) {
		throw new Error(`Artifact ${artifact.artifactId} has invalid parts data: ${error instanceof Error ? error.message : "Unknown error"}`);
	}
	if (artifact.metadata) try {
		JSON.stringify(artifact.metadata);
	} catch (error) {
		throw new Error(`Artifact ${artifact.artifactId} has invalid metadata: ${error instanceof Error ? error.message : "Unknown error"}`);
	}
}
/**
* Determine appropriate MIME types based on artifact parts
* Uses the 'kind' values from parts to determine content types (A2A principle)
*/
function determineMimeTypes(artifact) {
	if (artifact.parts && artifact.parts.length > 0) {
		const kinds = /* @__PURE__ */ new Set();
		for (const part of artifact.parts) if (part.kind) kinds.add(part.kind);
		if (kinds.size > 0) return Array.from(kinds);
	}
	if (artifact.type?.toLowerCase().includes("document")) return ["text"];
	if (artifact.type?.toLowerCase().includes("image")) return ["image"];
	if (artifact.type?.toLowerCase().includes("code")) return ["text"];
	return ["data"];
}
/**
* Sanitize artifact data to ensure database compatibility
*/
function sanitizeArtifactForDatabase(artifact) {
	return {
		...artifact,
		name: artifact.name?.slice(0, 255) || void 0,
		description: artifact.description?.slice(0, 1e3) || void 0,
		parts: artifact.parts ? JSON.parse(JSON.stringify(artifact.parts)) : null,
		metadata: artifact.metadata ? JSON.parse(JSON.stringify(artifact.metadata)) : null
	};
}
/**
* Fallback insert strategy for when normal insert fails
*/
async function tryFallbackInsert(db, rows, _originalError) {
	for (let i = 0; i < rows.length; i++) {
		const row = rows[i];
		try {
			await db.insert(ledgerArtifacts).values([row]);
		} catch (_fallbackError) {
			try {
				const minimalRow = {
					id: row.id,
					tenantId: row.tenantId,
					projectId: row.projectId,
					taskId: row.taskId,
					toolCallId: row.toolCallId,
					contextId: row.contextId,
					type: row.type || "source",
					name: row.name || `Artifact ${row.id.substring(0, 8)}`,
					description: row.description || "Artifact from tool results",
					parts: null,
					metadata: null,
					summary: null,
					mime: null,
					visibility: "context",
					allowedAgents: null,
					derivedFrom: null,
					createdAt: row.createdAt,
					updatedAt: row.updatedAt
				};
				await db.insert(ledgerArtifacts).values([minimalRow]);
			} catch (_finalError) {}
		}
	}
}
/**
* Atomic upsert operation for a single artifact - prevents race conditions
*/
const upsertLedgerArtifact = (db) => async (params) => {
	const { scopes, contextId, taskId, toolCallId = null, artifact } = params;
	validateArtifactData(artifact, 0);
	const sanitizedArt = sanitizeArtifactForDatabase(artifact);
	const now = (/* @__PURE__ */ new Date()).toISOString();
	const artifactRow = {
		id: sanitizedArt.artifactId ?? generateId(),
		tenantId: scopes.tenantId,
		projectId: scopes.projectId,
		taskId,
		toolCallId,
		contextId,
		type: sanitizedArt.type ?? "source",
		name: sanitizedArt.name,
		description: sanitizedArt.description,
		parts: sanitizedArt.parts,
		metadata: sanitizedArt.metadata,
		summary: sanitizedArt.description?.slice(0, 200) ?? null,
		mime: determineMimeTypes(sanitizedArt),
		visibility: sanitizedArt.metadata?.visibility ?? "context",
		allowedAgents: sanitizedArt.metadata?.allowedAgents ?? [],
		derivedFrom: sanitizedArt.metadata?.derivedFrom ?? null,
		createdAt: now,
		updatedAt: now
	};
	try {
		await db.insert(ledgerArtifacts).values([artifactRow]);
		return { created: true };
	} catch (error) {
		if (error.message?.includes("UNIQUE") || error.message?.includes("duplicate")) {
			const existing = await db.select().from(ledgerArtifacts).where(and(projectScopedWhere(ledgerArtifacts, scopes), eq(ledgerArtifacts.id, artifactRow.id), eq(ledgerArtifacts.taskId, taskId))).limit(1);
			if (existing.length > 0) return {
				created: false,
				existing: existing[0]
			};
		}
		const sanitizedError = /* @__PURE__ */ new Error(`Failed to insert artifact ${artifactRow.id}: ${error.message?.split("\nparams:")[0] || error.message}`);
		sanitizedError.name = error.name;
		sanitizedError.cause = error.code || error.errno;
		throw sanitizedError;
	}
};
/**
* Save one or more artifacts to the ledger
*/
const addLedgerArtifacts = (db) => async (params) => {
	const { scopes, contextId, taskId = null, toolCallId = null, artifacts } = params;
	if (artifacts.length === 0) return;
	for (let i = 0; i < artifacts.length; i++) validateArtifactData(artifacts[i], i);
	const now = (/* @__PURE__ */ new Date()).toISOString();
	const rows = artifacts.map((art) => {
		const sanitizedArt = sanitizeArtifactForDatabase(art);
		const resolvedTaskId = taskId ?? sanitizedArt.taskId ?? sanitizedArt.metadata?.taskId ?? null;
		return {
			id: sanitizedArt.artifactId ?? generateId(),
			tenantId: scopes.tenantId,
			projectId: scopes.projectId,
			taskId: resolvedTaskId,
			toolCallId: toolCallId ?? sanitizedArt.metadata?.toolCallId ?? null,
			contextId,
			type: sanitizedArt.type ?? "source",
			name: sanitizedArt.name,
			description: sanitizedArt.description,
			parts: sanitizedArt.parts,
			metadata: sanitizedArt.metadata,
			summary: sanitizedArt.description?.slice(0, 200) ?? null,
			mime: determineMimeTypes(sanitizedArt),
			visibility: sanitizedArt.metadata?.visibility ?? "context",
			allowedAgents: sanitizedArt.metadata?.allowedAgents ?? [],
			derivedFrom: sanitizedArt.metadata?.derivedFrom ?? null,
			createdAt: now,
			updatedAt: now
		};
	});
	const maxRetries = 3;
	let lastError = null;
	for (let attempt = 1; attempt <= maxRetries; attempt++) try {
		await db.insert(ledgerArtifacts).values(rows);
		return;
	} catch (error) {
		lastError = error;
		if (!isRetryableError(error) || attempt === maxRetries) {
			await tryFallbackInsert(db, rows, error);
			return;
		}
		const backoffMs = Math.min(1e3 * 2 ** (attempt - 1), 5e3);
		await new Promise((resolve) => setTimeout(resolve, backoffMs));
	}
	const sanitizedError = /* @__PURE__ */ new Error(`Failed to insert ${rows.length} artifacts after ${maxRetries} attempts: ${lastError?.message?.split("\nparams:")[0] || lastError?.message}`);
	sanitizedError.name = lastError?.name;
	sanitizedError.cause = lastError?.code || lastError?.errno;
	throw sanitizedError;
};
/**
* Retrieve artifacts by taskId, toolCallId, toolCallIds, and/or artifactId.
* At least one of taskId, toolCallId, toolCallIds, or artifactId must be provided.
* Use toolCallIds for batch queries to avoid N+1 query problems.
*/
const getLedgerArtifacts = (db) => async (params) => {
	const { scopes, taskId, toolCallId, toolCallIds, artifactId } = params;
	if (!taskId && !toolCallId && !toolCallIds && !artifactId) throw new Error("At least one of taskId, toolCallId, toolCallIds, or artifactId must be provided");
	if (toolCallId && toolCallIds) throw new Error("Cannot provide both toolCallId and toolCallIds. Use toolCallIds for batch queries.");
	const conditions = [projectScopedWhere(ledgerArtifacts, scopes)];
	if (artifactId) conditions.push(eq(ledgerArtifacts.id, artifactId));
	if (taskId) conditions.push(eq(ledgerArtifacts.taskId, taskId));
	if (toolCallId) conditions.push(eq(ledgerArtifacts.toolCallId, toolCallId));
	if (toolCallIds && toolCallIds.length > 0) conditions.push(inArray(ledgerArtifacts.toolCallId, toolCallIds));
	return (await db.select().from(ledgerArtifacts).where(conditions.length > 1 ? and(...conditions) : conditions[0])).map((row) => ({
		artifactId: row.id,
		type: row.type ?? "source",
		taskId: row.taskId ?? void 0,
		toolCallId: row.toolCallId ?? void 0,
		name: row.name ?? void 0,
		description: row.description ?? void 0,
		parts: row.parts ?? [],
		metadata: row.metadata || {},
		createdAt: row.createdAt
	}));
};
/**
* Get ledger artifacts by context ID
*/
const getLedgerArtifactsByContext = (db) => async (params) => {
	return await db.select().from(ledgerArtifacts).where(and(projectScopedWhere(ledgerArtifacts, params.scopes), eq(ledgerArtifacts.contextId, params.contextId)));
};
/**
* Update the parts column of an existing ledger artifact by ID.
* Used to persist modifications (e.g. key_findings) to artifacts that already exist.
*/
const updateLedgerArtifactParts = (db) => async (params) => {
	const { scopes, artifactId, parts } = params;
	return (await db.update(ledgerArtifacts).set({
		parts,
		updatedAt: (/* @__PURE__ */ new Date()).toISOString()
	}).where(and(projectScopedWhere(ledgerArtifacts, scopes), eq(ledgerArtifacts.id, artifactId))).returning()).length > 0;
};
/**
* Delete ledger artifacts by task ID
*/
const deleteLedgerArtifactsByTask = (db) => async (params) => {
	return (await db.delete(ledgerArtifacts).where(and(projectScopedWhere(ledgerArtifacts, params.scopes), eq(ledgerArtifacts.taskId, params.taskId))).returning()).length > 0;
};
/**
* Delete ledger artifacts by context ID
*/
const deleteLedgerArtifactsByContext = (db) => async (params) => {
	return (await db.delete(ledgerArtifacts).where(and(projectScopedWhere(ledgerArtifacts, params.scopes), eq(ledgerArtifacts.contextId, params.contextId))).returning()).length > 0;
};
/**
* Count ledger artifacts by task ID
*/
const countLedgerArtifactsByTask = (db) => async (params) => {
	const countValue = (await db.select({ count: count() }).from(ledgerArtifacts).where(and(projectScopedWhere(ledgerArtifacts, params.scopes), eq(ledgerArtifacts.taskId, params.taskId))))[0]?.count;
	return typeof countValue === "string" ? parseInt(countValue, 10) : countValue || 0;
};

//#endregion
export { addLedgerArtifacts, countLedgerArtifactsByTask, deleteLedgerArtifactsByContext, deleteLedgerArtifactsByTask, getLedgerArtifacts, getLedgerArtifactsByContext, updateLedgerArtifactParts, upsertLedgerArtifact };