import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { resolveMessagesListBlobUris } from "../services/blob-storage/resolve-blob-uris.js";
import { OpenAPIHono, z } from "@hono/zod-openapi";
import { ConversationApiSelectSchema, ListResponseSchema, commonGetErrorResponses, countVisibleMessages, createApiError, getConversation, getVisibleMessages, listConversations, toISODateString } from "@inkeep/agents-core";
import { createProtectedRoute, inheritedRunApiKeyAuth } from "@inkeep/agents-core/middleware";

//#region src/domains/run/routes/conversations.ts
const logger = getLogger$1("run-conversations");
const app = new OpenAPIHono();
/** Map internal DB roles to standard roles expected by consumers. */
function normalizeRole(role) {
	if (role === "agent") return "assistant";
	return role;
}
function getPartKind(p) {
	return p.kind ?? p.type;
}
function extractText(content) {
	if (content.text) return content.text;
	if (content.parts) return content.parts.filter((p) => getPartKind(p) === "text" && p.text).map((p) => p.text).join("");
	return "";
}
function toVercelMessage(msg) {
	const role = normalizeRole(msg.role);
	const text = extractText(msg.content);
	const parts = [];
	if (msg.content.parts) for (const p of msg.content.parts) {
		const kind = getPartKind(p);
		if (kind === "text") {
			if (p.text) parts.push({
				type: "text",
				text: p.text
			});
		} else if (kind === "data") {
			let parsed = p.data;
			if (typeof parsed === "string") try {
				parsed = JSON.parse(parsed);
			} catch {}
			const isArtifact = parsed && typeof parsed === "object" && parsed.artifactId && parsed.toolCallId;
			parts.push({
				type: isArtifact ? "data-artifact" : "data-component",
				data: parsed
			});
		} else if (kind === "file") {
			const url = typeof p.data === "string" ? p.data : void 0;
			if (!url) {
				logger.warn({ part: p }, "File part missing data, skipping");
				continue;
			}
			const meta = p.metadata;
			const mediaType = typeof meta?.mimeType === "string" ? meta.mimeType : void 0;
			const filename = typeof meta?.filename === "string" ? meta.filename : void 0;
			parts.push({
				type: "file",
				url,
				...mediaType && { mediaType },
				...filename && { filename }
			});
		}
	}
	else if (text) parts.push({
		type: "text",
		text
	});
	if (msg.content.tool_calls) for (const tc of msg.content.tool_calls) parts.push({
		type: "tool-invocation",
		toolCallId: tc.id,
		toolName: tc.function.name,
		args: tc.function.arguments,
		state: "result"
	});
	return {
		id: msg.id,
		role,
		content: text,
		parts,
		createdAt: toISODateString(msg.createdAt)
	};
}
const ConversationListItemSchema = ConversationApiSelectSchema.pick({
	id: true,
	agentId: true,
	title: true,
	createdAt: true,
	updatedAt: true
});
const ConversationListResponseSchema = ListResponseSchema(ConversationListItemSchema).openapi("EndUserConversationListResponse");
const VercelMessageSchema = z.object({
	id: z.string(),
	role: z.string(),
	content: z.string(),
	parts: z.array(z.record(z.string(), z.unknown())),
	createdAt: z.string()
});
const ConversationDetailResponseSchema = z.object({
	data: ConversationListItemSchema.extend({ messages: z.array(VercelMessageSchema) }),
	pagination: z.object({
		page: z.number(),
		limit: z.number(),
		total: z.number(),
		pages: z.number()
	})
}).openapi("EndUserConversationDetailResponse");
function requireEndUserId(executionContext) {
	const endUserId = executionContext.metadata?.endUserId;
	if (!endUserId) throw createApiError({
		code: "unauthorized",
		message: "End-user authentication required to list conversations"
	});
	return endUserId;
}
app.openapi(createProtectedRoute({
	method: "get",
	path: "/",
	summary: "List End-User Conversations",
	description: "List conversations for the authenticated end-user. Automatically scoped by the JWT sub claim — end-users can only see their own conversations.",
	operationId: "list-end-user-conversations",
	tags: ["Conversations"],
	security: [{ bearerAuth: [] }],
	permission: inheritedRunApiKeyAuth(),
	request: { query: z.object({
		page: z.coerce.number().min(1).default(1).optional(),
		limit: z.coerce.number().min(1).max(200).default(20).optional()
	}) },
	responses: {
		200: {
			description: "List of conversations for the authenticated end-user",
			content: { "application/json": { schema: ConversationListResponseSchema } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const executionContext = c.get("executionContext");
	const { tenantId, projectId } = executionContext;
	const endUserId = requireEndUserId(executionContext);
	const { page = 1, limit = 20 } = c.req.valid("query");
	const result = await listConversations(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		userId: endUserId,
		pagination: {
			page,
			limit
		}
	});
	const conversationsWithTitles = await Promise.all(result.conversations.map(async (conv) => {
		let title = conv.title;
		if (!title) {
			const firstUserMsg = (await getVisibleMessages(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				conversationId: conv.id,
				visibility: ["user-facing"],
				pagination: {
					page: 1,
					limit: 1
				}
			})).find((m) => m.role === "user");
			if (firstUserMsg) {
				const text = extractText(firstUserMsg.content);
				if (text) title = text.length > 100 ? `${text.slice(0, 100)}...` : text;
			}
		}
		return {
			id: conv.id,
			agentId: conv.agentId,
			title,
			createdAt: toISODateString(conv.createdAt),
			updatedAt: toISODateString(conv.updatedAt)
		};
	}));
	logger.debug({
		tenantId,
		projectId,
		endUserId,
		total: result.total
	}, "Listed end-user conversations");
	const pages = Math.ceil(result.total / limit);
	return c.json({
		data: conversationsWithTitles,
		pagination: {
			page,
			limit,
			total: result.total,
			pages
		}
	});
});
app.openapi(createProtectedRoute({
	method: "get",
	path: "/{conversationId}",
	summary: "Get Conversation",
	description: "Get a conversation and its messages. Returns messages in Vercel AI SDK UIMessage format. The conversation must belong to the authenticated end-user.",
	operationId: "get-end-user-conversation",
	tags: ["Conversations"],
	security: [{ bearerAuth: [] }],
	permission: inheritedRunApiKeyAuth(),
	request: {
		params: z.object({ conversationId: z.string() }),
		query: z.object({
			page: z.coerce.number().min(1).default(1).optional(),
			limit: z.coerce.number().min(1).max(200).default(50).optional(),
			format: z.enum(["vercel", "openai"]).default("vercel").optional()
		})
	},
	responses: {
		200: {
			description: "Conversation with messages",
			content: { "application/json": { schema: ConversationDetailResponseSchema } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const executionContext = c.get("executionContext");
	const { tenantId, projectId } = executionContext;
	const endUserId = requireEndUserId(executionContext);
	const { conversationId } = c.req.valid("param");
	const { page = 1, limit = 50, format = "vercel" } = c.req.valid("query");
	if (format === "openai") throw createApiError({
		code: "bad_request",
		message: "OpenAI message format is not available yet. Use format=vercel (default)."
	});
	const conversation = await getConversation(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		conversationId
	});
	if (!conversation || conversation.userId !== endUserId) throw createApiError({
		code: "not_found",
		message: "Conversation not found"
	});
	const [messageList, total] = await Promise.all([getVisibleMessages(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		conversationId,
		visibility: ["user-facing"],
		pagination: {
			page,
			limit
		}
	}), countVisibleMessages(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		conversationId,
		visibility: ["user-facing"]
	})]);
	const formattedMessages = (await resolveMessagesListBlobUris(messageList.map((msg) => ({
		...msg,
		content: msg.content
	})))).map((msg) => toVercelMessage({
		id: msg.id,
		role: msg.role,
		content: msg.content,
		createdAt: msg.createdAt
	}));
	let title = conversation.title;
	if (!title) {
		const firstUserMsg = formattedMessages.find((m) => m.role === "user");
		if (firstUserMsg) {
			const text = firstUserMsg.content;
			if (text) title = text.length > 100 ? `${text.slice(0, 100)}...` : text;
		}
	}
	logger.debug({
		tenantId,
		projectId,
		endUserId,
		conversationId,
		messageCount: messageList.length
	}, "Retrieved conversation");
	const pages = Math.ceil(total / limit);
	return c.json({
		data: {
			id: conversation.id,
			agentId: conversation.agentId,
			title,
			createdAt: toISODateString(conversation.createdAt),
			updatedAt: toISODateString(conversation.updatedAt),
			messages: formattedMessages
		},
		pagination: {
			page,
			limit,
			total,
			pages
		}
	});
});
var conversations_default = app;

//#endregion
export { conversations_default as default };