import { getLogger as getLogger$1 } from "../../../../logger.js";
import manageDbClient_default from "../../../../data/db/manageDbClient.js";
import manageDbPool_default from "../../../../data/db/manageDbPool.js";
import runDbClient_default from "../../../../data/db/runDbClient.js";
import "../../../../data/db/index.js";
import { buildTimezoneHeaders, executeAgentAsync } from "../../services/TriggerService.js";
import { addConversationIdToInvocation, canUseProjectStrict, createScheduledTriggerInvocation, deletePendingInvocationsForTrigger, generateId, getProjectScopedRef, getScheduledTriggerById, getScheduledTriggerInvocationById, getScheduledTriggerInvocationByIdempotencyKey, getScheduledWorkflowByTriggerId, interpolateTemplate, listPendingScheduledTriggerInvocations, markScheduledTriggerInvocationCompleted, markScheduledTriggerInvocationFailed, markScheduledTriggerInvocationRunning, resolveRef, updateScheduledTriggerInvocationStatus, withRef } from "@inkeep/agents-core";
import { CronExpressionParser } from "cron-parser";

//#region src/domains/run/workflow/steps/scheduledTriggerSteps.ts
/**
* Step functions for scheduled trigger workflow.
*
* These step functions have full Node.js access and handle all database
* operations and external service calls.
*/
const logger = getLogger$1("workflow-scheduled-trigger-steps");
/**
* Step: Log a message (allows logging from workflow context)
*/
async function logStep(message, data) {
	"use step";
	logger.info(data, message);
}
/**
* Step: Calculate the next execution time relative to a base time.
*/
async function calculateNextExecutionStep(params) {
	"use step";
	const { cronExpression, cronTimezone, runAt, lastScheduledFor } = params;
	if (runAt) return {
		nextExecutionTime: runAt,
		isOneTime: true
	};
	if (cronExpression) {
		const baseDate = lastScheduledFor ? new Date(lastScheduledFor) : /* @__PURE__ */ new Date();
		const nextIso = CronExpressionParser.parse(cronExpression, {
			currentDate: baseDate,
			tz: cronTimezone || "UTC"
		}).next().toISOString();
		if (!nextIso) throw new Error("Failed to calculate next execution time from cron expression");
		return {
			nextExecutionTime: nextIso,
			isOneTime: false
		};
	}
	throw new Error("Trigger must have either cronExpression or runAt");
}
/**
* Step: Compute sleep duration
* Returns milliseconds to sleep.
*/
async function computeSleepDurationStep(targetTime) {
	"use step";
	const target = new Date(targetTime);
	const now = /* @__PURE__ */ new Date();
	const diffMs = target.getTime() - now.getTime();
	return Math.max(diffMs, 1e3);
}
/**
* Step: Get the next pending invocation to execute (earliest scheduledFor).
* Returns null if no pending invocations exist.
*/
async function getNextPendingInvocationStep(params) {
	"use step";
	return (await listPendingScheduledTriggerInvocations(runDbClient_default)({
		scopes: {
			tenantId: params.tenantId,
			projectId: params.projectId,
			agentId: params.agentId
		},
		scheduledTriggerId: params.scheduledTriggerId,
		limit: 1
	}))[0] || null;
}
/**
* Step: Delete all pending invocations for a trigger.
* Used when cron expression changes or trigger is disabled.
*/
async function deletePendingInvocationsStep(params) {
	"use step";
	const deletedCount = await deletePendingInvocationsForTrigger(runDbClient_default)({
		scopes: {
			tenantId: params.tenantId,
			projectId: params.projectId,
			agentId: params.agentId
		},
		scheduledTriggerId: params.scheduledTriggerId
	});
	logger.info({
		scheduledTriggerId: params.scheduledTriggerId,
		deletedCount
	}, "Deleted pending invocations");
	return deletedCount;
}
/**
* Step: Check if trigger is still enabled and this runner is authoritative.
*/
async function checkTriggerEnabledStep(params) {
	"use step";
	const scopes = {
		tenantId: params.tenantId,
		projectId: params.projectId,
		agentId: params.agentId
	};
	const ref = getProjectScopedRef(params.tenantId, params.projectId, "main");
	const resolvedRef = await resolveRef(manageDbClient_default)(ref);
	if (!resolvedRef) {
		logger.warn({
			tenantId: params.tenantId,
			projectId: params.projectId
		}, "Failed to resolve ref for project, treating trigger as deleted");
		return {
			shouldContinue: false,
			reason: "deleted",
			trigger: null
		};
	}
	const [trigger, workflow] = await withRef(manageDbPool_default, resolvedRef, async (db) => {
		return Promise.all([getScheduledTriggerById(db)({
			scopes,
			scheduledTriggerId: params.scheduledTriggerId
		}), getScheduledWorkflowByTriggerId(db)({
			scopes,
			scheduledTriggerId: params.scheduledTriggerId
		})]);
	});
	if (!trigger || !trigger.enabled) {
		logger.info({
			scheduledTriggerId: params.scheduledTriggerId,
			reason: !trigger ? "deleted" : "disabled"
		}, "Scheduled trigger workflow stopping");
		return {
			shouldContinue: false,
			reason: !trigger ? "deleted" : "disabled",
			trigger: null
		};
	}
	if (workflow?.workflowRunId && workflow.workflowRunId !== params.runnerId) {
		logger.info({
			scheduledTriggerId: params.scheduledTriggerId,
			reason: "superseded"
		}, "Scheduled trigger workflow stopping");
		return {
			shouldContinue: false,
			reason: "superseded",
			trigger: null
		};
	}
	return {
		shouldContinue: true,
		trigger
	};
}
/**
* Step: Try to create invocation idempotently.
* Returns existing invocation if already created.
*/
async function createInvocationIdempotentStep(params) {
	"use step";
	const existing = await getScheduledTriggerInvocationByIdempotencyKey(runDbClient_default)({ idempotencyKey: params.idempotencyKey });
	if (existing) {
		logger.info({
			scheduledTriggerId: params.scheduledTriggerId,
			idempotencyKey: params.idempotencyKey
		}, "Invocation already exists, skipping creation");
		return {
			invocation: existing,
			alreadyExists: true
		};
	}
	const invocationId = generateId();
	const invocation = await createScheduledTriggerInvocation(runDbClient_default)({
		id: invocationId,
		tenantId: params.tenantId,
		projectId: params.projectId,
		agentId: params.agentId,
		scheduledTriggerId: params.scheduledTriggerId,
		status: "pending",
		scheduledFor: params.scheduledFor,
		resolvedPayload: params.payload,
		idempotencyKey: params.idempotencyKey,
		attemptNumber: 1
	});
	logger.info({
		tenantId: params.tenantId,
		projectId: params.projectId,
		scheduledTriggerId: params.scheduledTriggerId,
		invocationId,
		scheduledFor: params.scheduledFor
	}, "Created scheduled trigger invocation");
	return {
		invocation,
		alreadyExists: false
	};
}
/**
* Step: Check if invocation was cancelled before execution
* Returns true if cancelled (should skip execution), false otherwise
*/
async function checkInvocationCancelledStep(params) {
	"use step";
	const invocation = await getScheduledTriggerInvocationById(runDbClient_default)({
		scopes: {
			tenantId: params.tenantId,
			projectId: params.projectId,
			agentId: params.agentId
		},
		scheduledTriggerId: params.scheduledTriggerId,
		invocationId: params.invocationId
	});
	if (!invocation) {
		logger.warn({
			scheduledTriggerId: params.scheduledTriggerId,
			invocationId: params.invocationId
		}, "Invocation not found when checking cancellation status");
		return { cancelled: true };
	}
	if (invocation.status === "cancelled") {
		logger.info({
			scheduledTriggerId: params.scheduledTriggerId,
			invocationId: params.invocationId
		}, "Invocation was cancelled, skipping execution");
		return { cancelled: true };
	}
	return { cancelled: false };
}
/**
* Step: Mark invocation as running
*/
async function markRunningStep(params) {
	"use step";
	logger.info({
		scheduledTriggerId: params.scheduledTriggerId,
		invocationId: params.invocationId
	}, "Marking invocation as running");
	return markScheduledTriggerInvocationRunning(runDbClient_default)({
		scopes: {
			tenantId: params.tenantId,
			projectId: params.projectId,
			agentId: params.agentId
		},
		scheduledTriggerId: params.scheduledTriggerId,
		invocationId: params.invocationId
	});
}
/**
* Step: Add a conversation ID to the invocation's conversationIds array
* Called after each attempt to track all conversations created during retries
*/
async function addConversationIdStep(params) {
	"use step";
	return addConversationIdToInvocation(runDbClient_default)({
		scopes: {
			tenantId: params.tenantId,
			projectId: params.projectId,
			agentId: params.agentId
		},
		scheduledTriggerId: params.scheduledTriggerId,
		invocationId: params.invocationId,
		conversationId: params.conversationId
	});
}
/**
* Step: Mark invocation as completed
*/
async function markCompletedStep(params) {
	"use step";
	return markScheduledTriggerInvocationCompleted(runDbClient_default)({
		scopes: {
			tenantId: params.tenantId,
			projectId: params.projectId,
			agentId: params.agentId
		},
		scheduledTriggerId: params.scheduledTriggerId,
		invocationId: params.invocationId
	});
}
/**
* Step: Mark invocation as failed
*/
async function markFailedStep(params) {
	"use step";
	return markScheduledTriggerInvocationFailed(runDbClient_default)({
		scopes: {
			tenantId: params.tenantId,
			projectId: params.projectId,
			agentId: params.agentId
		},
		scheduledTriggerId: params.scheduledTriggerId,
		invocationId: params.invocationId
	});
}
/**
* Step: Increment attempt number for retry
*/
async function incrementAttemptStep(params) {
	"use step";
	await updateScheduledTriggerInvocationStatus(runDbClient_default)({
		scopes: {
			tenantId: params.tenantId,
			projectId: params.projectId,
			agentId: params.agentId
		},
		scheduledTriggerId: params.scheduledTriggerId,
		invocationId: params.invocationId,
		data: {
			attemptNumber: params.currentAttempt + 1,
			status: "pending"
		}
	});
}
/**
* Step: Execute the scheduled trigger using executeAgentAsync.
*
* Uses the shared executeAgentAsync from TriggerService which includes
* proper tracing, error handling, and conversation management.
*/
async function executeScheduledTriggerStep(params) {
	"use step";
	const { tenantId, projectId, agentId, scheduledTriggerId, invocationId, messageTemplate, payload, timeoutSeconds, runAsUserId, cronTimezone } = params;
	if (runAsUserId) try {
		if (!await canUseProjectStrict({
			userId: runAsUserId,
			tenantId,
			projectId
		})) {
			logger.warn({
				scheduledTriggerId,
				invocationId,
				runAsUserId,
				projectId
			}, "User no longer has access to project, failing invocation");
			return {
				success: false,
				error: `User ${runAsUserId} no longer has 'use' permission on project ${projectId}. An org admin should update or remove the runAsUserId on this trigger.`
			};
		}
	} catch (err) {
		logger.error({
			scheduledTriggerId,
			invocationId,
			runAsUserId,
			projectId,
			error: err
		}, "Failed to check user project access");
		return {
			success: false,
			error: `Permission check failed for user ${runAsUserId}: ${err instanceof Error ? err.message : String(err)}`
		};
	}
	logger.info({
		scheduledTriggerId,
		invocationId,
		runAsUserId
	}, "Executing scheduled trigger via executeAgentAsync");
	const conversationId = generateId();
	try {
		const ref = getProjectScopedRef(tenantId, projectId, "main");
		const resolvedRef = await resolveRef(manageDbClient_default)(ref);
		if (!resolvedRef) return {
			success: false,
			conversationId,
			error: `Failed to resolve ref for project ${projectId}`
		};
		const effectivePayload = payload ?? {};
		let userMessage;
		if (messageTemplate) userMessage = interpolateTemplate(messageTemplate, effectivePayload);
		else userMessage = JSON.stringify(effectivePayload);
		const messageParts = [];
		if (messageTemplate) messageParts.push({
			kind: "text",
			text: userMessage
		});
		messageParts.push({
			kind: "data",
			data: effectivePayload,
			metadata: {
				source: "scheduled-trigger",
				triggerId: scheduledTriggerId
			}
		});
		const timeoutMs = timeoutSeconds * 1e3;
		const timeoutPromise = new Promise((_, reject) => {
			setTimeout(() => reject(/* @__PURE__ */ new Error(`Execution timed out after ${timeoutSeconds}s`)), timeoutMs);
		});
		await Promise.race([executeAgentAsync({
			tenantId,
			projectId,
			agentId,
			triggerId: scheduledTriggerId,
			invocationId,
			conversationId,
			userMessage,
			messageParts,
			resolvedRef,
			runAsUserId: runAsUserId ?? void 0,
			forwardedHeaders: buildTimezoneHeaders(cronTimezone)
		}), timeoutPromise]);
		logger.info({
			scheduledTriggerId,
			invocationId,
			conversationId
		}, "Scheduled trigger execution completed");
		return {
			success: true,
			conversationId
		};
	} catch (error) {
		const errorMessage = error instanceof Error ? error.message : String(error);
		logger.error({
			scheduledTriggerId,
			invocationId,
			conversationId,
			error: errorMessage
		}, "Execute scheduled trigger step failed");
		return {
			success: false,
			conversationId,
			error: errorMessage
		};
	}
}

//#endregion
export { addConversationIdStep, calculateNextExecutionStep, checkInvocationCancelledStep, checkTriggerEnabledStep, computeSleepDurationStep, createInvocationIdempotentStep, deletePendingInvocationsStep, executeScheduledTriggerStep, getNextPendingInvocationStep, incrementAttemptStep, logStep, markCompletedStep, markFailedStep, markRunningStep };