import { conversations, messages } from "../../db/runtime/runtime-schema.js";
import { getLogger } from "../../utils/logger.js";
import { getConversationId } from "../../utils/conversations.js";
import { projectScopedWhere } from "../manage/scope-helpers.js";
import { deleteEventsByConversationIds } from "./events.js";
import { and, count, desc, eq, inArray } from "drizzle-orm";

//#region src/data-access/runtime/conversations.ts
const logger = getLogger("data-access/runtime/conversations");
const listConversations = (db) => async (params) => {
	const { userId, agentIds, pagination } = params;
	const page = pagination?.page || 1;
	const limit = Math.min(pagination?.limit || 20, 200);
	const offset = (page - 1) * limit;
	const whereConditions = [projectScopedWhere(conversations, params.scopes)];
	if (userId) whereConditions.push(eq(conversations.userId, userId));
	if (agentIds) {
		if (agentIds.length === 0) return {
			conversations: [],
			total: 0
		};
		whereConditions.push(inArray(conversations.agentId, agentIds));
	}
	const conversationList = await db.select().from(conversations).where(and(...whereConditions)).orderBy(desc(conversations.updatedAt)).limit(limit).offset(offset);
	const total = (await db.select({ count: count() }).from(conversations).where(and(...whereConditions)))[0]?.count || 0;
	return {
		conversations: conversationList,
		total: typeof total === "string" ? Number.parseInt(total, 10) : total
	};
};
const createConversation = (db) => async (params) => {
	const now = (/* @__PURE__ */ new Date()).toISOString();
	const [created] = await db.insert(conversations).values({
		...params,
		createdAt: now,
		updatedAt: now
	}).returning();
	return created;
};
const updateConversation = (db) => async (params) => {
	const now = (/* @__PURE__ */ new Date()).toISOString();
	const [updated] = await db.update(conversations).set({
		...params.data,
		updatedAt: now
	}).where(and(projectScopedWhere(conversations, params.scopes), eq(conversations.id, params.conversationId))).returning();
	return updated;
};
const deleteConversation = (db) => async (params) => {
	try {
		await deleteEventsByConversationIds(db)({
			scopes: params.scopes,
			conversationIds: [params.conversationId]
		});
		await db.delete(messages).where(and(projectScopedWhere(messages, params.scopes), eq(messages.conversationId, params.conversationId)));
		await db.delete(conversations).where(and(projectScopedWhere(conversations, params.scopes), eq(conversations.id, params.conversationId)));
		return true;
	} catch (error) {
		logger.error({
			tenantId: params.scopes.tenantId,
			projectId: params.scopes.projectId,
			conversationId: params.conversationId,
			error: error instanceof Error ? error.message : String(error)
		}, "Failed to delete conversation (events/messages/conversation cleanup chain)");
		return false;
	}
};
const updateConversationActiveSubAgent = (db) => async (params) => {
	return updateConversation(db)({
		scopes: params.scopes,
		conversationId: params.conversationId,
		data: { activeSubAgentId: params.activeSubAgentId }
	});
};
const getConversation = (db) => async (params) => {
	return await db.query.conversations.findFirst({ where: and(projectScopedWhere(conversations, params.scopes), eq(conversations.id, params.conversationId)) });
};
/**
* Batch-fetch conversations by id within a project scope.
*
* Set-based alternative to calling {@link getConversation} once per id, so callers
* enriching many results (e.g. evaluation results) issue a single query instead of
* fanning out one connection acquisition per conversation.
*/
const getConversationsByIds = (db) => async (params) => {
	if (params.conversationIds.length === 0) return [];
	return await db.select({
		id: conversations.id,
		agentId: conversations.agentId,
		createdAt: conversations.createdAt
	}).from(conversations).where(and(projectScopedWhere(conversations, params.scopes), inArray(conversations.id, params.conversationIds)));
};
const createOrGetConversation = (db) => async (input) => {
	const conversationId = input.id || getConversationId();
	if (input.id) {
		const existing = await db.query.conversations.findFirst({ where: and(eq(conversations.tenantId, input.tenantId), eq(conversations.id, input.id)) });
		if (existing) {
			const updateSet = { updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
			let needsUpdate = false;
			if (existing.activeSubAgentId !== input.activeSubAgentId) {
				updateSet.activeSubAgentId = input.activeSubAgentId;
				needsUpdate = true;
			}
			if (input.userProperties !== void 0) {
				updateSet.userProperties = input.userProperties;
				needsUpdate = true;
			}
			if (input.properties !== void 0) {
				updateSet.properties = input.properties;
				needsUpdate = true;
			}
			if (needsUpdate) {
				await db.update(conversations).set(updateSet).where(eq(conversations.id, input.id));
				return {
					...existing,
					...updateSet.activeSubAgentId ? { activeSubAgentId: updateSet.activeSubAgentId } : {},
					...input.userProperties !== void 0 ? { userProperties: input.userProperties } : {},
					...input.properties !== void 0 ? { properties: input.properties } : {}
				};
			}
			return existing;
		}
	}
	const newConversation = {
		id: conversationId,
		tenantId: input.tenantId,
		projectId: input.projectId,
		userId: input.userId,
		agentId: input.agentId,
		activeSubAgentId: input.activeSubAgentId,
		title: input.title,
		lastContextResolution: input.lastContextResolution,
		metadata: input.metadata,
		userProperties: input.userProperties,
		properties: input.properties,
		ref: input.ref,
		createdAt: (/* @__PURE__ */ new Date()).toISOString(),
		updatedAt: (/* @__PURE__ */ new Date()).toISOString()
	};
	await db.insert(conversations).values(newConversation);
	return newConversation;
};
/**
* Extract text content from message content object
*/
function extractMessageText(content) {
	if (content.text) return content.text;
	if (content.parts) return content.parts.filter((part) => part.kind === "text" && part.text).map((part) => part.text).join(" ");
	return "";
}
/**
* Apply context window management by truncating or summarizing old messages
*/
function applyContextWindowManagement(messageHistory, maxOutputTokens) {
	const estimateTokens = (text) => Math.ceil(text.length / 4);
	let totalTokens = 0;
	const managedHistory = [];
	for (let i = messageHistory.length - 1; i >= 0; i--) {
		const message = messageHistory[i];
		const messageTokens = estimateTokens(extractMessageText(message.content));
		if (totalTokens + messageTokens <= maxOutputTokens) {
			managedHistory.unshift(message);
			totalTokens += messageTokens;
		} else {
			if (i > 0) {
				const referenceMessage = messageHistory[0];
				const summaryMessage = {
					id: `summary-${getConversationId()}`,
					tenantId: referenceMessage.tenantId,
					projectId: referenceMessage.projectId,
					conversationId: referenceMessage.conversationId,
					role: "system",
					fromSubAgentId: null,
					toSubAgentId: null,
					fromExternalAgentId: null,
					toExternalAgentId: null,
					fromTeamAgentId: null,
					toTeamAgentId: null,
					content: { text: `[Previous conversation history truncated - ${i + 1} earlier messages]` },
					visibility: "system",
					messageType: "chat",
					taskId: null,
					parentMessageId: null,
					a2aTaskId: null,
					a2aSessionId: null,
					metadata: null,
					userProperties: null,
					properties: null,
					createdAt: referenceMessage.createdAt,
					updatedAt: referenceMessage.updatedAt
				};
				managedHistory.unshift(summaryMessage);
			}
			break;
		}
	}
	return managedHistory;
}
/**
* Get conversation history with filtering and context management
*/
const getConversationHistory = (db) => async (params) => {
	const { scopes, conversationId, options = {} } = params;
	const { limit = options.limit ?? 50, includeInternal = options.includeInternal ?? false, maxOutputTokens, messageTypes } = options;
	const whereConditions = [projectScopedWhere(messages, scopes), eq(messages.conversationId, conversationId)];
	if (!includeInternal) whereConditions.push(eq(messages.visibility, "user-facing"));
	if (messageTypes && messageTypes.length > 0) whereConditions.push(inArray(messages.messageType, messageTypes));
	const chronologicalHistory = (await db.select().from(messages).where(and(...whereConditions)).orderBy(desc(messages.createdAt)).limit(limit)).reverse();
	if (maxOutputTokens) return applyContextWindowManagement(chronologicalHistory, maxOutputTokens);
	return chronologicalHistory;
};
/**
* Get active agent for a conversation
*/
const getActiveAgentForConversation = (db) => async (params) => {
	return await db.query.conversations.findFirst({ where: and(projectScopedWhere(conversations, params.scopes), eq(conversations.id, params.conversationId)) });
};
/**
* Set active agent for a conversation (upsert operation)
*/
const setActiveAgentForConversation = (db) => async (params) => {
	const now = (/* @__PURE__ */ new Date()).toISOString();
	await db.insert(conversations).values({
		id: params.conversationId,
		tenantId: params.scopes.tenantId,
		projectId: params.scopes.projectId,
		activeSubAgentId: params.subAgentId,
		agentId: params.agentId,
		ref: params.ref,
		userId: params.userId,
		metadata: params.metadata,
		userProperties: params.userProperties,
		properties: params.properties,
		createdAt: now,
		updatedAt: now
	}).onConflictDoUpdate({
		target: [
			conversations.tenantId,
			conversations.projectId,
			conversations.id
		],
		set: {
			activeSubAgentId: params.subAgentId,
			updatedAt: now,
			...params.userProperties !== void 0 ? { userProperties: params.userProperties } : {},
			...params.properties !== void 0 ? { properties: params.properties } : {}
		}
	});
};
const setActiveAgentForThread = (db) => async ({ scopes, threadId, subAgentId, agentId, ref }) => {
	return setActiveAgentForConversation(db)({
		scopes,
		conversationId: threadId,
		subAgentId,
		agentId,
		ref
	});
};

//#endregion
export { createConversation, createOrGetConversation, deleteConversation, extractMessageText, getActiveAgentForConversation, getConversation, getConversationHistory, getConversationsByIds, listConversations, setActiveAgentForConversation, setActiveAgentForThread, updateConversation, updateConversationActiveSubAgent };