import { scheduledTriggerInvocations } from "../../db/runtime/runtime-schema.js";
import { agentScopedWhere, projectScopedWhere } from "../manage/scope-helpers.js";
import { and, asc, count, desc, eq, gte, inArray, lte, ne, sql } from "drizzle-orm";

//#region src/data-access/runtime/scheduledTriggerInvocations.ts
/**
* Get a scheduled trigger invocation by ID (agent-scoped)
*/
const getScheduledTriggerInvocationById = (db) => async (params) => {
	return await db.query.scheduledTriggerInvocations.findFirst({ where: and(agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), eq(scheduledTriggerInvocations.id, params.invocationId)) });
};
/**
* Get a scheduled trigger invocation by idempotency key
* Used to check if an invocation already exists for a given schedule
*/
const getScheduledTriggerInvocationByIdempotencyKey = (db) => async (params) => {
	return await db.query.scheduledTriggerInvocations.findFirst({ where: eq(scheduledTriggerInvocations.idempotencyKey, params.idempotencyKey) });
};
/**
* List scheduled trigger invocations with optional filtering (agent-scoped)
*/
const listScheduledTriggerInvocationsPaginated = (db) => async (params) => {
	const page = params.pagination?.page || 1;
	const limit = Math.min(params.pagination?.limit || 10, 100);
	const offset = (page - 1) * limit;
	const conditions = [agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId)];
	if (params.filters?.status) conditions.push(eq(scheduledTriggerInvocations.status, params.filters.status));
	if (params.filters?.from) conditions.push(gte(scheduledTriggerInvocations.scheduledFor, params.filters.from));
	if (params.filters?.to) conditions.push(lte(scheduledTriggerInvocations.scheduledFor, params.filters.to));
	const whereClause = and(...conditions);
	const [data, totalResult] = await Promise.all([db.select().from(scheduledTriggerInvocations).where(whereClause).limit(limit).offset(offset).orderBy(desc(scheduledTriggerInvocations.scheduledFor)), db.select({ count: count() }).from(scheduledTriggerInvocations).where(whereClause)]);
	const total = totalResult[0]?.count || 0;
	return {
		data,
		pagination: {
			page,
			limit,
			total,
			pages: Math.ceil(total / limit)
		}
	};
};
/**
* List pending invocations for a trigger, ordered by scheduledFor (earliest first)
* Used by workflow to get the next invocation to execute
*/
const listPendingScheduledTriggerInvocations = (db) => async (params) => {
	const maxLimit = Math.min(params.limit || 10, 100);
	return await db.select().from(scheduledTriggerInvocations).where(and(agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), eq(scheduledTriggerInvocations.status, "pending"))).orderBy(asc(scheduledTriggerInvocations.scheduledFor)).limit(maxLimit);
};
/**
* Delete all pending invocations for a trigger
* Used when cron expression changes or trigger is disabled
*/
const deletePendingInvocationsForTrigger = (db) => async (params) => {
	return (await db.delete(scheduledTriggerInvocations).where(and(agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), eq(scheduledTriggerInvocations.status, "pending"))).returning()).length;
};
/**
* Create a new scheduled trigger invocation (agent-scoped)
*/
const createScheduledTriggerInvocation = (db) => async (params) => {
	return (await db.insert(scheduledTriggerInvocations).values(params).returning())[0];
};
/**
* Update scheduled trigger invocation status (agent-scoped)
*/
const updateScheduledTriggerInvocationStatus = (db) => async (params) => {
	return (await db.update(scheduledTriggerInvocations).set(params.data).where(and(agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), eq(scheduledTriggerInvocations.id, params.invocationId))).returning())[0];
};
/**
* Mark invocation as running
*/
const markScheduledTriggerInvocationRunning = (db) => async (params) => {
	const now = (/* @__PURE__ */ new Date()).toISOString();
	return (await db.update(scheduledTriggerInvocations).set({
		status: "running",
		startedAt: sql`COALESCE(${scheduledTriggerInvocations.startedAt}, ${now})`
	}).where(and(agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), eq(scheduledTriggerInvocations.id, params.invocationId))).returning())[0];
};
/**
* Mark invocation as completed
* Note: Will not update if status is already 'cancelled' to respect user cancellation
*/
const markScheduledTriggerInvocationCompleted = (db) => async (params) => {
	return (await db.update(scheduledTriggerInvocations).set({
		status: "completed",
		completedAt: (/* @__PURE__ */ new Date()).toISOString()
	}).where(and(agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), eq(scheduledTriggerInvocations.id, params.invocationId), ne(scheduledTriggerInvocations.status, "cancelled"))).returning())[0];
};
/**
* Mark invocation as failed
* Note: Will not update if status is already 'cancelled' to respect user cancellation
*/
const markScheduledTriggerInvocationFailed = (db) => async (params) => {
	return (await db.update(scheduledTriggerInvocations).set({
		status: "failed",
		completedAt: (/* @__PURE__ */ new Date()).toISOString()
	}).where(and(agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), eq(scheduledTriggerInvocations.id, params.invocationId), ne(scheduledTriggerInvocations.status, "cancelled"))).returning())[0];
};
/**
* Reset a cancelled invocation back to pending.
* Only updates if the current status is 'cancelled' to prevent race conditions.
*/
const resetCancelledInvocationToPending = (db) => async (params) => {
	return (await db.update(scheduledTriggerInvocations).set({
		status: "pending",
		attemptNumber: 1,
		startedAt: null,
		completedAt: null
	}).where(and(agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), eq(scheduledTriggerInvocations.id, params.invocationId), eq(scheduledTriggerInvocations.status, "cancelled"))).returning())[0];
};
/**
* Add a conversation ID to the invocation's conversationIds array
* Used to track all conversations created during retries
*/
const addConversationIdToInvocation = (db) => async (params) => {
	return (await db.update(scheduledTriggerInvocations).set({ conversationIds: sql`${scheduledTriggerInvocations.conversationIds} || ${JSON.stringify([params.conversationId])}::jsonb` }).where(and(agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), eq(scheduledTriggerInvocations.id, params.invocationId))).returning())[0];
};
/**
* Mark invocation as cancelled
*/
const markScheduledTriggerInvocationCancelled = (db) => async (params) => {
	return (await db.update(scheduledTriggerInvocations).set({
		status: "cancelled",
		completedAt: (/* @__PURE__ */ new Date()).toISOString()
	}).where(and(agentScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), eq(scheduledTriggerInvocations.id, params.invocationId))).returning())[0];
};
/**
* Cancel all pending invocations for a trigger
* Used when a trigger is deleted
*/
const cancelPendingInvocationsForTrigger = (db) => async (params) => {
	return (await db.update(scheduledTriggerInvocations).set({
		status: "cancelled",
		completedAt: (/* @__PURE__ */ new Date()).toISOString()
	}).where(and(projectScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), inArray(scheduledTriggerInvocations.status, ["pending", "running"]))).returning()).length;
};
/**
* Cancel only PAST pending invocations for a trigger (scheduledFor <= now)
* Used when a trigger is disabled - keeps future invocations pending
*/
const cancelPastPendingInvocationsForTrigger = (db) => async (params) => {
	const now = (/* @__PURE__ */ new Date()).toISOString();
	return (await db.update(scheduledTriggerInvocations).set({
		status: "cancelled",
		completedAt: now
	}).where(and(projectScopedWhere(scheduledTriggerInvocations, params.scopes), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), inArray(scheduledTriggerInvocations.status, ["pending", "running"]), lte(scheduledTriggerInvocations.scheduledFor, now))).returning()).length;
};
/**
* Get run info for multiple scheduled triggers in a single query.
* Groups invocations by (triggerId, scheduledFor) to support multi-user triggers.
* Excludes manual runs (manual-run-* and manual-rerun-* idempotency keys).
* Returns lastRunSummary with per-status counts for the most recent scheduled tick.
*/
const getScheduledTriggerRunInfoBatch = (db) => async (params) => {
	if (params.triggerIds.length === 0) return /* @__PURE__ */ new Map();
	const triggerIdValues = params.triggerIds.map((t) => t.triggerId);
	const allInvocations = await db.select({
		scheduledTriggerId: scheduledTriggerInvocations.scheduledTriggerId,
		status: scheduledTriggerInvocations.status,
		scheduledFor: scheduledTriggerInvocations.scheduledFor,
		completedAt: scheduledTriggerInvocations.completedAt,
		conversationIds: scheduledTriggerInvocations.conversationIds,
		idempotencyKey: scheduledTriggerInvocations.idempotencyKey
	}).from(scheduledTriggerInvocations).where(and(projectScopedWhere(scheduledTriggerInvocations, params.scopes), inArray(scheduledTriggerInvocations.scheduledTriggerId, triggerIdValues))).orderBy(desc(scheduledTriggerInvocations.scheduledFor));
	const result = /* @__PURE__ */ new Map();
	for (const trigger of params.triggerIds) result.set(trigger.triggerId, {
		lastRunAt: null,
		lastRunStatus: null,
		lastRunConversationIds: [],
		nextRunAt: null,
		lastRunSummary: null
	});
	const isManualRun = (key) => key.startsWith("manual-run-") || key.startsWith("manual-rerun-");
	const tickMap = /* @__PURE__ */ new Map();
	for (const inv of allInvocations) {
		const triggerInfo = result.get(inv.scheduledTriggerId);
		if (!triggerInfo) continue;
		if (inv.status === "pending" && !triggerInfo.nextRunAt) triggerInfo.nextRunAt = inv.scheduledFor;
		if (isManualRun(inv.idempotencyKey)) continue;
		let triggerTicks = tickMap.get(inv.scheduledTriggerId);
		if (!triggerTicks) {
			triggerTicks = /* @__PURE__ */ new Map();
			tickMap.set(inv.scheduledTriggerId, triggerTicks);
		}
		let tickInvocations = triggerTicks.get(inv.scheduledFor);
		if (!tickInvocations) {
			tickInvocations = [];
			triggerTicks.set(inv.scheduledFor, tickInvocations);
		}
		tickInvocations.push({
			status: inv.status,
			completedAt: inv.completedAt,
			conversationIds: inv.conversationIds
		});
	}
	for (const [triggerId, triggerTicks] of tickMap) {
		const triggerInfo = result.get(triggerId);
		if (!triggerInfo) continue;
		let bestScheduledFor = null;
		let bestInvocations = null;
		for (const [scheduledFor, invocations] of triggerTicks) {
			if (!invocations.some((i) => i.status === "completed" || i.status === "failed")) continue;
			if (!bestScheduledFor || scheduledFor > bestScheduledFor) {
				bestScheduledFor = scheduledFor;
				bestInvocations = invocations;
			}
		}
		if (bestInvocations) {
			const summary = {
				total: bestInvocations.length,
				completed: 0,
				failed: 0,
				running: 0,
				pending: 0
			};
			let maxCompletedAt = null;
			for (const inv of bestInvocations) {
				if (inv.status === "completed") summary.completed++;
				else if (inv.status === "failed") summary.failed++;
				else if (inv.status === "running") summary.running++;
				else if (inv.status === "pending") summary.pending++;
				if (inv.completedAt && (!maxCompletedAt || inv.completedAt > maxCompletedAt)) maxCompletedAt = inv.completedAt;
			}
			triggerInfo.lastRunAt = maxCompletedAt;
			triggerInfo.lastRunSummary = summary;
			if (summary.failed > 0) triggerInfo.lastRunStatus = "failed";
			else if (summary.completed > 0) triggerInfo.lastRunStatus = "completed";
			const allConvIds = [];
			for (const inv of bestInvocations) {
				const ids = inv.conversationIds;
				if (ids) for (const id of ids) allConvIds.push(id);
			}
			triggerInfo.lastRunConversationIds = allConvIds;
		}
	}
	return result;
};
/**
* List upcoming invocations across ALL triggers for an agent with pagination
* Used for the upcoming runs dashboard with full pagination support
*/
const listUpcomingInvocationsForAgentPaginated = (db) => async (params) => {
	const page = params.pagination?.page || 1;
	const limit = Math.min(params.pagination?.limit || 20, 100);
	const offset = (page - 1) * limit;
	const statusCondition = params.includeRunning ? inArray(scheduledTriggerInvocations.status, ["pending", "running"]) : eq(scheduledTriggerInvocations.status, "pending");
	const whereClause = and(...[agentScopedWhere(scheduledTriggerInvocations, params.scopes), statusCondition]);
	const [data, totalResult] = await Promise.all([db.select().from(scheduledTriggerInvocations).where(whereClause).limit(limit).offset(offset).orderBy(asc(scheduledTriggerInvocations.scheduledFor)), db.select({ count: count() }).from(scheduledTriggerInvocations).where(whereClause)]);
	const total = totalResult[0]?.count || 0;
	return {
		data,
		pagination: {
			page,
			limit,
			total,
			pages: Math.ceil(total / limit)
		}
	};
};
/**
* List all invocations across ALL triggers for a PROJECT with pagination
* Used for the project-level invocations dashboard
*/
const listProjectScheduledTriggerInvocationsPaginated = (db) => async (params) => {
	const page = params.pagination?.page || 1;
	const limit = Math.min(params.pagination?.limit || 20, 100);
	const offset = (page - 1) * limit;
	const conditions = [projectScopedWhere(scheduledTriggerInvocations, params.scopes)];
	if (params.filters?.status) conditions.push(eq(scheduledTriggerInvocations.status, params.filters.status));
	const whereClause = and(...conditions);
	const [data, totalResult] = await Promise.all([db.select().from(scheduledTriggerInvocations).where(whereClause).limit(limit).offset(offset).orderBy(desc(scheduledTriggerInvocations.createdAt)), db.select({ count: count() }).from(scheduledTriggerInvocations).where(whereClause)]);
	const total = totalResult[0]?.count || 0;
	return {
		data,
		pagination: {
			page,
			limit,
			total,
			pages: Math.ceil(total / limit)
		}
	};
};
/**
* Get status summary for all invocations with a given scheduledTriggerId.
* Used for dataset run status aggregation where scheduledTriggerId = datasetRunId.
*/
const getScheduledTriggerInvocationStatusSummary = (db) => async (params) => {
	return (await db.select({
		status: scheduledTriggerInvocations.status,
		cnt: count()
	}).from(scheduledTriggerInvocations).where(and(eq(scheduledTriggerInvocations.tenantId, params.scopes.tenantId), eq(scheduledTriggerInvocations.projectId, params.scopes.projectId), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId))).groupBy(scheduledTriggerInvocations.status)).reduce((acc, { status, cnt }) => {
		const key = status;
		if (key in acc) acc[key] += Number(cnt);
		return acc;
	}, {
		pending: 0,
		running: 0,
		completed: 0,
		failed: 0,
		cancelled: 0
	});
};
/**
* List invocations by scheduledTriggerId without agentId filter (project-scoped).
* Used for dataset run items listing where scheduledTriggerId = datasetRunId.
*/
const listScheduledTriggerInvocationsByTriggerId = (db) => async (params) => {
	const conditions = [
		eq(scheduledTriggerInvocations.tenantId, params.scopes.tenantId),
		eq(scheduledTriggerInvocations.projectId, params.scopes.projectId),
		eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId)
	];
	if (params.filters?.status) conditions.push(eq(scheduledTriggerInvocations.status, params.filters.status));
	return await db.select().from(scheduledTriggerInvocations).where(and(...conditions)).orderBy(asc(scheduledTriggerInvocations.createdAt));
};
const listScheduledTriggerInvocationsByDatasetRunId = (db) => async (params) => {
	const conditions = [
		eq(scheduledTriggerInvocations.tenantId, params.scopes.tenantId),
		eq(scheduledTriggerInvocations.projectId, params.scopes.projectId),
		sql`${scheduledTriggerInvocations.resolvedPayload}->>'datasetRunId' = ${params.datasetRunId}`
	];
	if (params.filters?.status) conditions.push(eq(scheduledTriggerInvocations.status, params.filters.status));
	return await db.select().from(scheduledTriggerInvocations).where(and(...conditions)).orderBy(asc(scheduledTriggerInvocations.createdAt));
};
const getLastRunAtForTrigger = (db) => async (params) => {
	const [row] = await db.select({ completedAt: scheduledTriggerInvocations.completedAt }).from(scheduledTriggerInvocations).where(and(eq(scheduledTriggerInvocations.tenantId, params.scopes.tenantId), eq(scheduledTriggerInvocations.projectId, params.scopes.projectId), eq(scheduledTriggerInvocations.scheduledTriggerId, params.scheduledTriggerId), inArray(scheduledTriggerInvocations.status, ["completed", "failed"]))).orderBy(desc(scheduledTriggerInvocations.completedAt)).limit(1);
	return row?.completedAt ?? null;
};

//#endregion
export { addConversationIdToInvocation, cancelPastPendingInvocationsForTrigger, cancelPendingInvocationsForTrigger, createScheduledTriggerInvocation, deletePendingInvocationsForTrigger, getLastRunAtForTrigger, getScheduledTriggerInvocationById, getScheduledTriggerInvocationByIdempotencyKey, getScheduledTriggerInvocationStatusSummary, getScheduledTriggerRunInfoBatch, listPendingScheduledTriggerInvocations, listProjectScheduledTriggerInvocationsPaginated, listScheduledTriggerInvocationsByDatasetRunId, listScheduledTriggerInvocationsByTriggerId, listScheduledTriggerInvocationsPaginated, listUpcomingInvocationsForAgentPaginated, markScheduledTriggerInvocationCancelled, markScheduledTriggerInvocationCompleted, markScheduledTriggerInvocationFailed, markScheduledTriggerInvocationRunning, resetCancelledInvocationToPending, updateScheduledTriggerInvocationStatus };