import { createModuleQueue } from "@open-mercato/queue";
import {
  safeOutboundFetch,
  UnsafeOutboundUrlError
} from "@open-mercato/shared/lib/url-safety";
import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
import { callWebhookConfigSchema } from "../data/validators.js";
import { WORKFLOW_ACTIVITIES_QUEUE_NAME } from "./activity-queue-types.js";
import { logWorkflowEvent } from "./event-logger.js";
import { parseDuration } from "./duration.js";
import { isPrivateUrl } from "@open-mercato/shared/lib/network";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("workflows");
function isAllowPrivateWorkflowWebhookUrlsEnabled() {
  if (parseBooleanWithDefault(process.env.OM_WORKFLOWS_ALLOW_PRIVATE_URLS, false)) {
    if (process.env.NODE_ENV === "production") {
      logger.warn("OM_WORKFLOWS_ALLOW_PRIVATE_URLS is set but ignored in production. SSRF protection remains enabled.", { component: "CALL_WEBHOOK" });
      return false;
    }
    logger.warn("OM_WORKFLOWS_ALLOW_PRIVATE_URLS is enabled. SSRF protection is bypassed for workflow webhooks; use only in development.", { component: "CALL_WEBHOOK" });
    return true;
  }
  if (parseBooleanWithDefault(process.env.WORKFLOW_WEBHOOK_ALLOW_PRIVATE_URLS, false)) {
    if (process.env.NODE_ENV === "production") {
      logger.warn("WORKFLOW_WEBHOOK_ALLOW_PRIVATE_URLS is deprecated and ignored in production. Use OM_WORKFLOWS_ALLOW_PRIVATE_URLS for development only. SSRF protection remains enabled.", { component: "CALL_WEBHOOK" });
      return false;
    }
    logger.warn("WORKFLOW_WEBHOOK_ALLOW_PRIVATE_URLS is deprecated. Use OM_WORKFLOWS_ALLOW_PRIVATE_URLS instead. SSRF protection is bypassed.", { component: "CALL_WEBHOOK" });
    return true;
  }
  return false;
}
const DEFAULT_WORKFLOW_ENV_INTERPOLATION_ALLOWLIST = /* @__PURE__ */ new Set(["APP_URL"]);
const WORKFLOW_ENV_INTERPOLATION_ALLOWLIST_KEY = "OM_WORKFLOWS_ENV_INTERPOLATION_ALLOWLIST";
function getWorkflowEnvInterpolationAllowlist() {
  const allowlist = new Set(DEFAULT_WORKFLOW_ENV_INTERPOLATION_ALLOWLIST);
  const configuredKeys = process.env[WORKFLOW_ENV_INTERPOLATION_ALLOWLIST_KEY];
  if (!configuredKeys) {
    return allowlist;
  }
  for (const key of configuredKeys.split(",")) {
    const trimmedKey = key.trim();
    if (trimmedKey) {
      allowlist.add(trimmedKey);
    }
  }
  return allowlist;
}
function resolveWorkflowEnvInterpolation(envKey) {
  if (!getWorkflowEnvInterpolationAllowlist().has(envKey)) {
    return "";
  }
  return process.env[envKey] ?? "";
}
class ActivityExecutionError extends Error {
  constructor(message, activityType, activityName, details) {
    super(message);
    this.activityType = activityType;
    this.activityName = activityName;
    this.details = details;
    this.name = "ActivityExecutionError";
  }
}
let activityQueue = null;
function getActivityQueue() {
  if (!activityQueue) {
    activityQueue = createModuleQueue(
      WORKFLOW_ACTIVITIES_QUEUE_NAME,
      { concurrency: parseInt(process.env.WORKFLOW_WORKER_CONCURRENCY || "5") }
    );
  }
  return activityQueue;
}
async function enqueueActivity(em, activity, context) {
  const { workflowInstance, workflowContext, stepContext, transitionId, stepInstanceId, branchInstanceId } = context;
  const interpolatedConfig = interpolateVariables(activity.config, workflowContext, workflowInstance);
  const job = {
    workflowInstanceId: workflowInstance.id,
    stepInstanceId,
    branchInstanceId: branchInstanceId ?? void 0,
    transitionId,
    activityId: activity.activityId,
    activityName: activity.activityName || activity.activityType,
    activityType: activity.activityType,
    activityConfig: interpolatedConfig,
    workflowContext,
    stepContext,
    retryPolicy: activity.retryPolicy,
    timeoutMs: activity.timeoutMs,
    tenantId: workflowInstance.tenantId,
    organizationId: workflowInstance.organizationId,
    userId: context.userId
  };
  const queue = getActivityQueue();
  const enqueueOptions = activity.activityType === "WAIT" && (interpolatedConfig.duration || interpolatedConfig.until) ? { delayMs: calculateWaitDelayMs(interpolatedConfig) } : void 0;
  const jobId = await queue.enqueue(job, enqueueOptions);
  await logWorkflowEvent(em, {
    workflowInstanceId: workflowInstance.id,
    stepInstanceId,
    eventType: "ACTIVITY_QUEUED",
    eventData: {
      activityId: activity.activityId,
      activityName: activity.activityName,
      activityType: activity.activityType,
      async: true,
      jobId
    },
    tenantId: workflowInstance.tenantId,
    organizationId: workflowInstance.organizationId
  });
  return jobId;
}
async function enqueueTimerJob(params) {
  const { workflowInstanceId, stepInstanceId, branchInstanceId, tenantId, organizationId, userId, fireAt, delayMs } = params;
  const queue = getActivityQueue();
  const jobId = await queue.enqueue(
    {
      kind: "timer",
      workflowInstanceId,
      stepInstanceId,
      branchInstanceId: branchInstanceId ?? void 0,
      tenantId,
      organizationId,
      userId,
      fireAt
    },
    { delayMs: delayMs > 0 ? delayMs : void 0 }
  );
  return jobId;
}
async function executeActivity(em, container, activity, context) {
  const retryPolicy = activity.retryPolicy || {
    maxAttempts: 1,
    initialIntervalMs: 0,
    backoffCoefficient: 1,
    maxIntervalMs: 0
  };
  let lastError;
  let retryCount = 0;
  for (let attempt = 0; attempt < retryPolicy.maxAttempts; attempt++) {
    try {
      const startTime = Date.now();
      const result = activity.timeoutMs ? await executeWithTimeout(
        () => executeActivityByType(em, container, activity, context),
        activity.timeoutMs
      ) : await executeActivityByType(em, container, activity, context);
      const executionTimeMs = Date.now() - startTime;
      return {
        activityId: activity.activityId,
        activityName: activity.activityName,
        activityType: activity.activityType,
        success: true,
        output: result,
        retryCount: attempt,
        executionTimeMs,
        async: activity.async || false
      };
    } catch (error) {
      lastError = error;
      retryCount = attempt + 1;
      if (attempt < retryPolicy.maxAttempts - 1) {
        logger.error("Activity failed; will retry", {
          activityId: activity.activityId,
          activityType: activity.activityType,
          attempt: attempt + 1,
          maxAttempts: retryPolicy.maxAttempts,
          instanceId: context.workflowInstance.id,
          err: error
        });
      }
      if (attempt < retryPolicy.maxAttempts - 1) {
        const backoff = calculateBackoff(
          retryPolicy.initialIntervalMs,
          retryPolicy.backoffCoefficient,
          attempt,
          retryPolicy.maxIntervalMs
        );
        await sleep(backoff);
      }
    }
  }
  const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
  logger.error("Activity failed after all attempts", {
    activityId: activity.activityId,
    activityType: activity.activityType,
    attempts: retryCount,
    instanceId: context.workflowInstance.id,
    err: lastError
  });
  return {
    activityId: activity.activityId,
    activityName: activity.activityName,
    activityType: activity.activityType,
    success: false,
    error: `Activity failed after ${retryCount} attempts: ${errorMessage}`,
    retryCount,
    executionTimeMs: 0,
    async: activity.async || false
  };
}
async function executeActivities(em, container, activities, context) {
  const results = [];
  for (let i = 0; i < activities.length; i++) {
    const activity = activities[i];
    if (activity.async) {
      const jobId = await enqueueActivity(em, activity, context);
      results.push({
        activityId: activity.activityId,
        activityName: activity.activityName,
        activityType: activity.activityType,
        success: true,
        // Queued successfully
        async: true,
        jobId,
        retryCount: 0,
        executionTimeMs: 0
      });
    } else {
      const result = await executeActivity(em, container, activity, context);
      results.push(result);
      if (!result.success) {
        break;
      }
      if (result.output && typeof result.output === "object") {
        const key = activity.activityName || activity.activityType;
        context.workflowContext = {
          ...context.workflowContext,
          [key]: result.output
        };
      }
    }
  }
  return results;
}
async function executeActivityByType(em, container, activity, context) {
  const interpolatedConfig = interpolateVariables(activity.config, context.workflowContext, context.workflowInstance);
  switch (activity.activityType) {
    case "SEND_EMAIL":
      return await executeSendEmail(interpolatedConfig, context, container);
    case "CALL_API":
      return await executeCallApi(em, interpolatedConfig, context, container);
    case "EMIT_EVENT":
      return await executeEmitEvent(interpolatedConfig, context, container);
    case "UPDATE_ENTITY":
      return await executeUpdateEntity(em, interpolatedConfig, context, container);
    case "CALL_WEBHOOK":
      return await executeCallWebhook(interpolatedConfig, context);
    case "EXECUTE_FUNCTION":
      return await executeFunction(interpolatedConfig, context, container);
    case "WAIT":
      return await executeWait(interpolatedConfig);
    default:
      throw new ActivityExecutionError(
        `Unknown activity type: ${activity.activityType}`,
        activity.activityType,
        activity.activityName
      );
  }
}
async function executeSendEmail(config, context, container) {
  const { to, subject, template, templateData, body } = config;
  if (!to || !subject) {
    throw new Error('SEND_EMAIL requires "to" and "subject" fields');
  }
  logger.info("Send email activity invoked", { component: "SEND_EMAIL", subject });
  try {
    const emailService = container.resolve("emailService");
    if (emailService && typeof emailService.send === "function") {
      await emailService.send({
        to,
        subject,
        template,
        templateData,
        body
      });
      return { sent: true, to, subject, via: "emailService" };
    }
  } catch (error) {
  }
  return { sent: true, to, subject, via: "console" };
}
async function executeEmitEvent(config, context, container) {
  const { eventName, payload } = config;
  if (!eventName) {
    throw new Error('EMIT_EVENT requires "eventName" field');
  }
  const eventBus = container.resolve("eventBus");
  if (!eventBus || typeof eventBus.emitEvent !== "function") {
    throw new Error("Event bus not available in container");
  }
  const enrichedPayload = {
    ...payload,
    _workflow: {
      workflowInstanceId: context.workflowInstance.id,
      workflowId: context.workflowInstance.workflowId,
      tenantId: context.workflowInstance.tenantId,
      organizationId: context.workflowInstance.organizationId
    }
  };
  await eventBus.emitEvent(eventName, enrichedPayload, {
    tenantId: context.workflowInstance.tenantId,
    organizationId: context.workflowInstance.organizationId
  });
  return { emitted: true, eventName, payload: enrichedPayload };
}
async function executeUpdateEntity(em, config, context, container) {
  const { commandId, input, statusDictionary } = config;
  if (!commandId) {
    throw new Error('UPDATE_ENTITY requires "commandId" field (e.g., "sales.documents.update")');
  }
  if (!input || typeof input !== "object") {
    throw new Error('UPDATE_ENTITY requires "input" object with entity data');
  }
  const commandBus = container.resolve("commandBus");
  if (!commandBus || typeof commandBus.execute !== "function") {
    throw new Error("CommandBus not available in container");
  }
  let finalInput = { ...input };
  if (finalInput.statusValue && statusDictionary) {
    const statusEntryId = await resolveDictionaryEntryId(
      em,
      statusDictionary,
      finalInput.statusValue,
      context.workflowInstance.tenantId,
      context.workflowInstance.organizationId
    );
    if (statusEntryId) {
      finalInput.statusEntryId = statusEntryId;
    }
    delete finalInput.statusValue;
  }
  const SYSTEM_USER_ID = "00000000-0000-0000-0000-000000000000";
  const ctx = {
    container,
    auth: {
      sub: context.userId || SYSTEM_USER_ID,
      tenantId: context.workflowInstance.tenantId,
      orgId: context.workflowInstance.organizationId,
      isSuperAdmin: false
    },
    organizationScope: null,
    selectedOrganizationId: context.workflowInstance.organizationId,
    organizationIds: context.workflowInstance.organizationId ? [context.workflowInstance.organizationId] : null
  };
  const { result, logEntry } = await commandBus.execute(commandId, {
    input: finalInput,
    ctx
  });
  return {
    executed: true,
    commandId,
    result,
    logEntryId: logEntry?.id
  };
}
async function resolveDictionaryEntryId(em, dictionaryKey, value, tenantId, organizationId) {
  try {
    const { Dictionary, DictionaryEntry } = await import("@open-mercato/core/modules/dictionaries/data/entities");
    const dictionary = await em.findOne(Dictionary, {
      key: dictionaryKey,
      tenantId,
      organizationId,
      deletedAt: null
    });
    if (!dictionary) {
      logger.warn("Dictionary not found", { component: "UPDATE_ENTITY", dictionaryKey });
      return null;
    }
    const normalizedValue = value.toLowerCase().trim();
    const entry = await em.findOne(DictionaryEntry, {
      dictionary: dictionary.id,
      tenantId,
      organizationId,
      normalizedValue
    });
    if (!entry) {
      logger.warn("Dictionary entry not found", { component: "UPDATE_ENTITY", dictionaryKey, value });
      return null;
    }
    return entry.id;
  } catch (error) {
    logger.error("Error resolving dictionary entry", { component: "UPDATE_ENTITY", err: error });
    return null;
  }
}
async function executeCallWebhook(config, context, deps = {}) {
  const parsed = callWebhookConfigSchema.safeParse(config);
  if (!parsed.success) {
    const issues = parsed.error.issues.map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`).join("; ");
    throw new Error(`CALL_WEBHOOK config invalid: ${issues}`);
  }
  const { url, method, headers: rawHeaders, body } = parsed.data;
  const headers = rawHeaders ?? {};
  const allowPrivate = deps.allowPrivate ?? isAllowPrivateWorkflowWebhookUrlsEnabled();
  let response;
  try {
    response = await safeOutboundFetch(
      url,
      {
        method,
        headers: {
          "Content-Type": "application/json",
          ...headers
        },
        body: body !== void 0 && body !== null ? JSON.stringify(body) : void 0,
        redirect: "manual",
        signal: deps.signal
      },
      {
        subject: "Workflow webhook URL",
        allowPrivate,
        lookupHost: deps.lookupHost,
        fetchImpl: deps.fetchImpl
      }
    );
  } catch (error) {
    if (error instanceof UnsafeOutboundUrlError) {
      throw new Error(
        `CALL_WEBHOOK rejected unsafe URL (reason=${error.reason}): ${error.message}`
      );
    }
    throw error;
  }
  if (response.status >= 300 && response.status < 400) {
    const location = response.headers.get("location");
    throw new Error(
      `CALL_WEBHOOK refused to follow redirect ${response.status} to ${location ?? "(no Location header)"}`
    );
  }
  let result;
  const contentType = response.headers.get("content-type");
  if (contentType && contentType.includes("application/json")) {
    result = await response.json();
  } else {
    result = await response.text();
  }
  if (!response.ok) {
    throw new Error(
      `Webhook request failed with status ${response.status}: ${JSON.stringify(result)}`
    );
  }
  return {
    status: response.status,
    statusText: response.statusText,
    result
  };
}
async function executeFunction(config, context, container) {
  const { functionName, args = {} } = config;
  if (!functionName) {
    throw new Error('EXECUTE_FUNCTION requires "functionName" field');
  }
  const fnKey = `workflowFunction:${functionName}`;
  try {
    const fn = container.resolve(fnKey);
    if (typeof fn !== "function") {
      throw new Error(`Registered workflow function "${functionName}" is not a function`);
    }
    const result = await fn(args, context);
    return { executed: true, functionName, result };
  } catch (error) {
    if (error instanceof Error && error.message.includes("not registered")) {
      throw new Error(
        `Workflow function "${functionName}" not registered in DI container (key: ${fnKey})`
      );
    }
    throw error;
  }
}
function calculateWaitDelayMs(config) {
  if (config.until) {
    const targetDate = new Date(config.until);
    if (isNaN(targetDate.getTime())) {
      throw new Error(`WAIT activity: invalid "until" datetime: ${config.until}`);
    }
    const delayMs = targetDate.getTime() - Date.now();
    return Math.max(0, delayMs);
  }
  if (config.duration) {
    return parseDuration(config.duration);
  }
  throw new Error('WAIT activity requires "duration" (e.g., "PT5M", "1h") or "until" (ISO 8601 datetime)');
}
async function executeWait(config) {
  const durationMs = calculateWaitDelayMs(config);
  await sleep(durationMs);
  return { waited: true, durationMs };
}
async function executeCallApi(em, config, context, container, signal) {
  const interpolatedConfig = interpolateVariables(config, context.workflowContext, context.workflowInstance);
  const {
    endpoint,
    method = "GET",
    headers = {},
    body,
    validateTenantMatch = true
  } = interpolatedConfig;
  if (!endpoint) {
    throw new Error('CALL_API requires "endpoint" field');
  }
  const fullUrl = buildApiUrl(endpoint);
  const { withOnetimeApiKey } = await import("../../api_keys/services/apiKeyService.js");
  const apiKeyEm = container.resolve("em").fork({ clear: true, freshEventManager: true, useContext: false });
  const resolvedRoleIds = await resolveCallApiRoleIds(apiKeyEm, context.workflowInstance);
  if (resolvedRoleIds.length === 0) {
    throw new Error(
      `[CALL_API] Refusing to execute CALL_API for workflow instance ${context.workflowInstance.id}: no traceable user roles could be resolved from the workflow instance or definition. CALL_API activities must run under the identity of the user who triggered them.`
    );
  }
  return await withOnetimeApiKey(
    apiKeyEm,
    {
      name: `__workflow_${context.workflowInstance.id}__`,
      description: `One-time key for workflow ${context.workflowInstance.workflowId} instance ${context.workflowInstance.id}`,
      tenantId: context.workflowInstance.tenantId,
      organizationId: context.workflowInstance.organizationId,
      roles: resolvedRoleIds,
      expiresAt: null
    },
    async (apiKeySecret) => {
      const requestHeaders = {
        "Content-Type": "application/json",
        "Authorization": `apikey ${apiKeySecret}`,
        "X-Tenant-Id": context.workflowInstance.tenantId,
        "X-Organization-Id": context.workflowInstance.organizationId,
        "X-Workflow-Instance-Id": context.workflowInstance.id,
        ...headers
      };
      const response = await fetch(fullUrl, {
        method,
        headers: requestHeaders,
        body: body ? JSON.stringify(body) : void 0,
        signal
      });
      let responseBody;
      const contentType = response.headers.get("content-type");
      try {
        if (contentType && contentType.includes("application/json")) {
          responseBody = await response.json();
        } else {
          responseBody = await response.text();
        }
      } catch (error) {
        responseBody = null;
      }
      if (!response.ok) {
        classifyAndThrowError(response.status, responseBody, fullUrl);
      }
      if (validateTenantMatch && responseBody && typeof responseBody === "object") {
        if (responseBody.tenantId && responseBody.tenantId !== context.workflowInstance.tenantId) {
          throw new Error(
            `Tenant ID mismatch: workflow expects ${context.workflowInstance.tenantId} but API returned ${responseBody.tenantId}`
          );
        }
      }
      return {
        status: response.status,
        statusText: response.statusText,
        headers: Object.fromEntries(response.headers.entries()),
        body: responseBody,
        authenticated: true,
        tenantId: context.workflowInstance.tenantId,
        organizationId: context.workflowInstance.organizationId
      };
    }
  );
}
async function resolveActiveRoleIdsForUser(em, userId, scope) {
  const { findOneWithDecryption, findWithDecryption } = await import("@open-mercato/shared/lib/encryption/find");
  const { User, UserRole, Role } = await import("../../auth/data/entities.js");
  const user = await findOneWithDecryption(em, User, {
    id: userId,
    tenantId: scope.tenantId,
    deletedAt: null
  }, {}, scope);
  if (!user) return [];
  const userRoles = await findWithDecryption(
    em,
    UserRole,
    { user: user.id, deletedAt: null },
    { populate: ["role"] },
    scope
  );
  const roleIds = userRoles.map((ur) => typeof ur.role === "string" ? ur.role : ur.role?.id).filter((id) => typeof id === "string" && id.length > 0);
  if (roleIds.length === 0) return [];
  const scopedRoles = await findWithDecryption(em, Role, {
    id: { $in: roleIds },
    tenantId: scope.tenantId,
    deletedAt: null
  }, {}, scope);
  return scopedRoles.map((r) => r.id);
}
async function resolveCallApiRoleIds(em, instance) {
  if (!instance.definitionId) return [];
  const { findOneWithDecryption } = await import("@open-mercato/shared/lib/encryption/find");
  const { WorkflowDefinition } = await import("../data/entities.js");
  const scope = { tenantId: instance.tenantId, organizationId: instance.organizationId };
  const initiatorUserId = instance.metadata?.initiatedBy ?? null;
  if (initiatorUserId) {
    return resolveActiveRoleIdsForUser(em, initiatorUserId, scope);
  }
  const definition = await findOneWithDecryption(em, WorkflowDefinition, {
    id: instance.definitionId,
    tenantId: instance.tenantId,
    deletedAt: null
  }, {}, scope);
  const authorUserId = definition?.createdBy;
  if (!authorUserId) return [];
  return resolveActiveRoleIdsForUser(em, authorUserId, scope);
}
function buildApiUrl(endpoint) {
  const appUrl = process.env.APP_URL || "http://localhost:3000";
  if (endpoint.startsWith("/")) {
    if (!endpoint.startsWith("/api/")) {
      throw new Error(`CALL_API only supports /api/* paths, got: ${endpoint}`);
    }
    return `${appUrl}${endpoint}`;
  }
  try {
    const endpointUrl = new URL(endpoint);
    const appUrlObj = new URL(appUrl);
    if (endpointUrl.host !== appUrlObj.host) {
      throw new Error(
        `SSRF Prevention: CALL_API endpoint domain (${endpointUrl.host}) does not match APP_URL (${appUrlObj.host})`
      );
    }
    return endpoint;
  } catch (error) {
    if (error instanceof TypeError) {
      throw new Error(`Invalid endpoint URL: ${endpoint}`);
    }
    throw error;
  }
}
function classifyAndThrowError(status, body, url) {
  const bodyStr = typeof body === "string" ? body : JSON.stringify(body);
  if (status >= 400 && status < 500) {
    throw new Error(
      `CALL_API request failed with status ${status} (non-retriable): ${bodyStr}`
    );
  }
  if (status >= 500) {
    const error = new Error(
      `CALL_API request failed with status ${status} (retriable): ${bodyStr}`
    );
    error.retriable = true;
    throw error;
  }
  throw new Error(`CALL_API request failed with status ${status}: ${bodyStr}`);
}
function interpolateVariables(config, context, workflowInstance) {
  if (typeof config === "string") {
    const singleVarMatch = config.match(/^\{\{([^}]+)\}\}$/);
    if (singleVarMatch) {
      const trimmedPath = singleVarMatch[1].trim();
      if (trimmedPath.startsWith("workflow.") && workflowInstance) {
        const workflowKey = trimmedPath.substring("workflow.".length);
        switch (workflowKey) {
          case "instanceId":
            return workflowInstance.id;
          case "tenantId":
            return workflowInstance.tenantId;
          case "organizationId":
            return workflowInstance.organizationId;
          case "currentStepId":
            return workflowInstance.currentStepId;
          case "workflowId":
            return workflowInstance.workflowId;
          case "version":
            return workflowInstance.version;
          // Return as number
          default:
            return config;
        }
      }
      if (trimmedPath.startsWith("env.")) {
        const envKey = trimmedPath.substring("env.".length);
        return resolveWorkflowEnvInterpolation(envKey);
      }
      if (trimmedPath === "now") {
        return (/* @__PURE__ */ new Date()).toISOString();
      }
      const contextPath = trimmedPath.startsWith("context.") ? trimmedPath.substring("context.".length) : trimmedPath;
      const value = getNestedValue(context, contextPath);
      return value !== void 0 ? value : config;
    }
    return config.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
      const trimmedPath = path.trim();
      if (trimmedPath.startsWith("workflow.") && workflowInstance) {
        const workflowKey = trimmedPath.substring("workflow.".length);
        switch (workflowKey) {
          case "instanceId":
            return workflowInstance.id;
          case "tenantId":
            return workflowInstance.tenantId;
          case "organizationId":
            return workflowInstance.organizationId;
          case "currentStepId":
            return workflowInstance.currentStepId;
          case "workflowId":
            return workflowInstance.workflowId;
          case "version":
            return String(workflowInstance.version);
          default:
            return match;
        }
      }
      if (trimmedPath.startsWith("env.")) {
        const envKey = trimmedPath.substring("env.".length);
        return resolveWorkflowEnvInterpolation(envKey);
      }
      if (trimmedPath === "now") {
        return (/* @__PURE__ */ new Date()).toISOString();
      }
      const contextPath = trimmedPath.startsWith("context.") ? trimmedPath.substring("context.".length) : trimmedPath;
      const value = getNestedValue(context, contextPath);
      return value !== void 0 ? String(value) : match;
    });
  }
  if (Array.isArray(config)) {
    return config.map((item) => interpolateVariables(item, context, workflowInstance));
  }
  if (config && typeof config === "object") {
    const result = {};
    for (const [key, value] of Object.entries(config)) {
      result[key] = interpolateVariables(value, context, workflowInstance);
    }
    return result;
  }
  return config;
}
function getNestedValue(obj, path) {
  const parts = path.split(".");
  let value = obj;
  for (const part of parts) {
    if (value && typeof value === "object" && part in value) {
      value = value[part];
    } else {
      return void 0;
    }
  }
  return value;
}
function calculateBackoff(initialIntervalMs, backoffCoefficient, attempt, maxIntervalMs) {
  const backoff = initialIntervalMs * Math.pow(backoffCoefficient, attempt);
  return Math.min(backoff, maxIntervalMs || Infinity);
}
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
async function executeWithTimeout(executor, timeoutMs) {
  let timeoutId;
  const timeoutPromise = new Promise((_, reject) => {
    timeoutId = setTimeout(() => {
      reject(new Error(`Activity execution timeout after ${timeoutMs}ms`));
    }, timeoutMs);
  });
  try {
    return await Promise.race([executor(), timeoutPromise]);
  } finally {
    clearTimeout(timeoutId);
  }
}
export {
  ActivityExecutionError,
  enqueueActivity,
  enqueueTimerJob,
  executeActivities,
  executeActivity,
  executeCallApi,
  executeCallWebhook,
  executeEmitEvent,
  executeFunction,
  executeSendEmail,
  executeUpdateEntity,
  isPrivateUrl,
  resolveCallApiRoleIds
};
//# sourceMappingURL=activity-executor.js.map
