import { LockMode } from "@mikro-orm/core";
import {
  WorkflowInstance,
  WorkflowEvent
} from "../data/entities.js";
import { compensateWorkflow } from "./compensation-handler.js";
import { findWorkflowDefinition, findDefinitionForInstance } from "./find-definition.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("workflows");
class WorkflowExecutionError extends Error {
  constructor(message, code, details) {
    super(message);
    this.code = code;
    this.details = details;
    this.name = "WorkflowExecutionError";
  }
}
async function startWorkflow(em, options) {
  const {
    workflowId,
    version,
    initialContext = {},
    correlationKey,
    metadata,
    tenantId,
    organizationId
  } = options;
  const definition = await findWorkflowDefinition(em, {
    workflowId,
    version,
    tenantId,
    organizationId
  });
  if (!definition) {
    throw new WorkflowExecutionError(
      `Workflow definition not found: ${workflowId}${version ? ` v${version}` : ""}`,
      "DEFINITION_NOT_FOUND",
      { workflowId, version }
    );
  }
  if (!definition.enabled) {
    throw new WorkflowExecutionError(
      `Workflow definition is disabled: ${workflowId}`,
      "DEFINITION_DISABLED",
      { workflowId, version: definition.version }
    );
  }
  const { steps, transitions } = definition.definition;
  if (!steps || steps.length < 2) {
    throw new WorkflowExecutionError(
      "Workflow definition must have at least START and END steps",
      "INVALID_DEFINITION",
      { workflowId, stepsCount: steps?.length || 0 }
    );
  }
  if (!transitions || transitions.length < 1) {
    throw new WorkflowExecutionError(
      "Workflow definition must have at least one transition",
      "INVALID_DEFINITION",
      { workflowId, transitionsCount: transitions?.length || 0 }
    );
  }
  const startStep = steps.find((s) => s.stepType === "START");
  if (!startStep) {
    throw new WorkflowExecutionError(
      "Workflow definition must have a START step",
      "INVALID_DEFINITION",
      { workflowId }
    );
  }
  if (startStep.preConditions && startStep.preConditions.length > 0) {
    const { validateWorkflowStart } = await import("./start-validator.js");
    const validationResult = await validateWorkflowStart(em, {
      workflowId,
      version: definition.version,
      context: initialContext,
      tenantId,
      organizationId
    });
    if (!validationResult.canStart) {
      throw new WorkflowExecutionError(
        `Workflow start pre-conditions failed: ${validationResult.errors.map((e) => e.message).join("; ")}`,
        "START_PRE_CONDITIONS_FAILED",
        {
          workflowId,
          errors: validationResult.errors,
          validatedRules: validationResult.validatedRules
        }
      );
    }
  }
  const now = /* @__PURE__ */ new Date();
  const instance = em.create(WorkflowInstance, {
    definitionId: definition.id,
    workflowId: definition.workflowId,
    version: definition.version,
    status: "RUNNING",
    currentStepId: startStep.stepId,
    context: initialContext,
    correlationKey,
    metadata,
    startedAt: now,
    retryCount: 0,
    tenantId,
    organizationId,
    createdAt: now,
    updatedAt: now
  });
  await em.persist(instance).flush();
  await logWorkflowEvent(em, {
    workflowInstanceId: instance.id,
    eventType: "WORKFLOW_STARTED",
    eventData: {
      workflowId: instance.workflowId,
      version: instance.version,
      startStepId: startStep.stepId,
      initialContext,
      metadata
    },
    userId: metadata?.initiatedBy,
    tenantId,
    organizationId
  });
  return instance;
}
async function executeWorkflow(em, container, instanceId, context) {
  const startTime = Date.now();
  const transactionalEm = em;
  const runExecution = async (trx) => {
    const events = [];
    const errors = [];
    try {
      const instance = await getWorkflowInstanceForExecution(trx, instanceId);
      if (!instance) {
        throw new WorkflowExecutionError(
          `Workflow instance not found: ${instanceId}`,
          "INSTANCE_NOT_FOUND",
          { instanceId }
        );
      }
      if (instance.status === "COMPLETED") {
        return {
          status: "COMPLETED",
          currentStep: instance.currentStepId,
          context: instance.context,
          events: [],
          executionTime: 0
        };
      }
      if (instance.status === "CANCELLED") {
        throw new WorkflowExecutionError(
          "Cannot execute cancelled workflow",
          "WORKFLOW_CANCELLED",
          { instanceId, status: instance.status }
        );
      }
      const definition = await findDefinitionForInstance(trx, instance);
      if (!definition) {
        throw new WorkflowExecutionError(
          `Workflow definition not found: ${instance.definitionId}`,
          "DEFINITION_NOT_FOUND",
          { definitionId: instance.definitionId }
        );
      }
      const maxIterations = 100;
      let iterations = 0;
      while (iterations < maxIterations) {
        iterations++;
        const currentInstance = await getWorkflowInstanceForExecution(trx, instanceId, { refresh: iterations > 1 });
        if (!currentInstance) {
          throw new WorkflowExecutionError(
            "Instance not found during execution",
            "INSTANCE_NOT_FOUND",
            { instanceId }
          );
        }
        if (currentInstance.status === "FORKED") {
          const { advanceBranches } = await import("./parallel-handler.js");
          const branchResult = await advanceBranches(trx, container, currentInstance, definition, {
            userId: context?.userId
          });
          if (branchResult.outcome === "joined") {
            continue;
          }
          if (branchResult.outcome === "failed") {
            errors.push(branchResult.error || "Parallel branch failed");
            await completeWorkflow(trx, container, instanceId, "FAILED", {
              error: branchResult.error || "Parallel branch failed"
            });
            return {
              status: "FAILED",
              currentStep: currentInstance.currentStepId,
              context: currentInstance.context,
              events,
              errors,
              executionTime: Date.now() - startTime
            };
          }
          return {
            status: "RUNNING",
            currentStep: currentInstance.currentStepId,
            context: currentInstance.context,
            events,
            executionTime: Date.now() - startTime
          };
        }
        const currentStep = definition.definition.steps.find(
          (s) => s.stepId === currentInstance.currentStepId
        );
        if (currentStep?.stepType === "END") {
          await completeWorkflow(trx, container, instanceId, "COMPLETED");
          events.push({
            eventType: "WORKFLOW_COMPLETED",
            occurredAt: /* @__PURE__ */ new Date()
          });
          return {
            status: "COMPLETED",
            currentStep: currentInstance.currentStepId,
            context: currentInstance.context,
            events,
            executionTime: Date.now() - startTime
          };
        }
        if (currentStep?.stepType === "USER_TASK" || currentStep?.stepType === "WAIT_FOR_SIGNAL" || currentStep?.stepType === "WAIT_FOR_TIMER") {
          return {
            status: "RUNNING",
            currentStep: currentInstance.currentStepId,
            context: currentInstance.context,
            events,
            executionTime: Date.now() - startTime
          };
        }
        const transitions = definition.definition.transitions.filter(
          (t) => t.fromStepId === currentInstance.currentStepId && t.trigger === "auto"
        );
        if (transitions.length === 0) {
          return {
            status: "RUNNING",
            currentStep: currentInstance.currentStepId,
            context: currentInstance.context,
            events,
            executionTime: Date.now() - startTime
          };
        }
        const transitionHandler = await import("./transition-handler.js");
        const evalContext = {
          workflowContext: currentInstance.context,
          userId: context?.userId
        };
        const validTransitions = await transitionHandler.findValidTransitions(
          trx,
          currentInstance,
          currentInstance.currentStepId,
          evalContext
        );
        const validAutoTransitions = validTransitions.filter(
          (vt) => vt.isValid && vt.transition?.trigger === "auto"
        );
        if (validAutoTransitions.length === 0) {
          return {
            status: "RUNNING",
            currentStep: currentInstance.currentStepId,
            context: currentInstance.context,
            events,
            executionTime: Date.now() - startTime
          };
        }
        const selectedTransition = validAutoTransitions[0].transition;
        try {
          const transitionResult = await transitionHandler.executeTransition(
            trx,
            container,
            currentInstance,
            selectedTransition.fromStepId,
            selectedTransition.toStepId,
            evalContext
          );
          if (!transitionResult.success) {
            const rejectionMessage = transitionResult.error || "Transition failed";
            logger.error("Transition rejected", {
              instanceId: currentInstance.id,
              workflowId: currentInstance.workflowId,
              fromStepId: currentInstance.currentStepId,
              toStepId: selectedTransition.toStepId,
              err: rejectionMessage
            });
            errors.push(rejectionMessage);
            await completeWorkflow(trx, container, instanceId, "FAILED", {
              error: rejectionMessage
            });
            events.push({
              eventType: "WORKFLOW_FAILED",
              occurredAt: /* @__PURE__ */ new Date()
            });
            return {
              status: "FAILED",
              currentStep: currentInstance.currentStepId,
              context: currentInstance.context,
              events,
              errors,
              executionTime: Date.now() - startTime
            };
          }
          events.push({
            eventType: "TRANSITION_EXECUTED",
            occurredAt: /* @__PURE__ */ new Date(),
            data: {
              fromStepId: selectedTransition.fromStepId,
              toStepId: selectedTransition.toStepId,
              transitionId: selectedTransition.transitionId
            }
          });
          if (transitionResult.pausedForActivities) {
            await logWorkflowEvent(trx, {
              workflowInstanceId: currentInstance.id,
              eventType: "WORKFLOW_WAITING_FOR_ACTIVITIES",
              eventData: {
                pendingActivities: transitionResult.activitiesExecuted?.filter((a) => a.async),
                pausedAtTransition: {
                  fromStepId: selectedTransition.fromStepId,
                  toStepId: selectedTransition.toStepId
                }
              },
              tenantId: currentInstance.tenantId,
              organizationId: currentInstance.organizationId
            });
            events.push({
              eventType: "WORKFLOW_WAITING_FOR_ACTIVITIES",
              occurredAt: /* @__PURE__ */ new Date(),
              data: {
                pendingActivities: transitionResult.activitiesExecuted?.filter((a) => a.async)
              }
            });
            return {
              status: "WAITING_FOR_ACTIVITIES",
              currentStep: currentInstance.currentStepId,
              context: currentInstance.context,
              events,
              executionTime: Date.now() - startTime
            };
          }
          await trx.flush();
        } catch (error) {
          const errorMessage = error instanceof Error ? error.message : String(error);
          logger.error("Transition execution failed", {
            instanceId: currentInstance.id,
            workflowId: currentInstance.workflowId,
            fromStepId: currentInstance.currentStepId,
            toStepId: selectedTransition.toStepId,
            err: error
          });
          errors.push(errorMessage);
          events.push({
            eventType: "TRANSITION_FAILED",
            occurredAt: /* @__PURE__ */ new Date(),
            data: {
              transitionId: selectedTransition.transitionId,
              error: errorMessage
            }
          });
          await completeWorkflow(trx, container, instanceId, "FAILED", {
            error: errorMessage,
            details: error instanceof WorkflowExecutionError ? error.details : void 0
          });
          events.push({
            eventType: "WORKFLOW_FAILED",
            occurredAt: /* @__PURE__ */ new Date()
          });
          return {
            status: "FAILED",
            currentStep: currentInstance.currentStepId,
            context: currentInstance.context,
            events,
            errors,
            executionTime: Date.now() - startTime
          };
        }
      }
      errors.push("Maximum execution iterations reached - possible infinite loop");
      return {
        status: "RUNNING",
        currentStep: instance.currentStepId,
        context: instance.context,
        events,
        errors,
        executionTime: Date.now() - startTime
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      logger.error("Execution failed", { instanceId, err: error });
      errors.push(errorMessage);
      try {
        const instance = await getWorkflowInstanceForExecution(trx, instanceId, { refresh: true });
        if (instance && instance.status === "RUNNING") {
          instance.status = "FAILED";
          instance.errorMessage = errorMessage;
          instance.errorDetails = error instanceof WorkflowExecutionError ? error.details : void 0;
          instance.updatedAt = /* @__PURE__ */ new Date();
          await trx.flush();
          await logWorkflowEvent(trx, {
            workflowInstanceId: instanceId,
            eventType: "WORKFLOW_FAILED",
            eventData: { error: errorMessage },
            tenantId: instance.tenantId,
            organizationId: instance.organizationId
          });
        }
      } catch (updateError) {
        logger.error("Failed to update instance with error state", { instanceId, err: updateError });
      }
      throw error;
    }
  };
  return typeof transactionalEm.transactional === "function" ? transactionalEm.transactional((trx) => runExecution(trx)) : runExecution(em);
}
async function completeWorkflow(em, container, instanceId, status, result) {
  const instance = await getWorkflowInstance(em, instanceId);
  if (!instance) {
    throw new WorkflowExecutionError(
      `Workflow instance not found: ${instanceId}`,
      "INSTANCE_NOT_FOUND",
      { instanceId }
    );
  }
  if (status === "FAILED") {
    const definition = await findDefinitionForInstance(em, instance);
    if (definition && checkIfCompensationNeeded(definition)) {
      try {
        if (result?.error) {
          instance.errorMessage = result.error;
          instance.errorDetails = result.details;
          await em.flush();
        }
        const compensationResult = await compensateWorkflow(
          em,
          container,
          instance,
          definition,
          {
            continueOnError: true
            // Best-effort compensation
          }
        );
        logger.info("Compensation finished", {
          status: compensationResult.status,
          compensatedActivities: compensationResult.compensatedActivities,
          totalActivities: compensationResult.totalActivities
        });
        return;
      } catch (error) {
        logger.error("Compensation failed with exception", { err: error });
      }
    }
  }
  const now = /* @__PURE__ */ new Date();
  instance.status = status;
  instance.updatedAt = now;
  switch (status) {
    case "COMPLETED":
      instance.completedAt = now;
      if (result) {
        instance.context = { ...instance.context, __result: result };
      }
      break;
    case "FAILED":
      instance.completedAt = now;
      if (result?.error) {
        instance.errorMessage = result.error;
        instance.errorDetails = result.details;
      }
      break;
    case "CANCELLED":
      instance.cancelledAt = now;
      break;
  }
  await em.flush();
  const eventType = status === "COMPLETED" ? "WORKFLOW_COMPLETED" : status === "FAILED" ? "WORKFLOW_FAILED" : "WORKFLOW_CANCELLED";
  await logWorkflowEvent(em, {
    workflowInstanceId: instanceId,
    eventType,
    eventData: result || {},
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
}
async function resumeWorkflowAfterActivities(em, container, instanceId, branchInstanceId) {
  const transactionalEm = em;
  if (branchInstanceId) {
    const { resumeBranchAfterActivities } = await import("./parallel-handler.js");
    const branchResume = typeof transactionalEm.transactional === "function" ? await transactionalEm.transactional((trx) => resumeBranchAfterActivities(trx, container, instanceId, branchInstanceId)) : await resumeBranchAfterActivities(em, container, instanceId, branchInstanceId);
    if (branchResume.continueExecution) {
      await executeWorkflow(em, container, instanceId);
    }
    return;
  }
  const runResume = async (trx) => {
    const instance = await trx.findOne(WorkflowInstance, {
      id: instanceId,
      status: "WAITING_FOR_ACTIVITIES"
    }, { lockMode: LockMode.PESSIMISTIC_WRITE });
    if (!instance) {
      throw new Error("Workflow instance not waiting for activities");
    }
    const pendingJobIds = instance.context._pendingAsyncActivities || [];
    const completedActivities = await trx.count(WorkflowEvent, {
      workflowInstanceId: instanceId,
      eventType: "ACTIVITY_COMPLETED",
      eventData: { async: true }
    });
    const failedActivities = await trx.count(WorkflowEvent, {
      workflowInstanceId: instanceId,
      eventType: "ACTIVITY_FAILED",
      eventData: { async: true }
    });
    const totalProcessed = completedActivities + failedActivities;
    if (totalProcessed < pendingJobIds.length) {
      throw new Error("Activities still pending");
    }
    if (failedActivities > 0) {
      const failedEvents = await trx.find(WorkflowEvent, {
        workflowInstanceId: instanceId,
        eventType: "ACTIVITY_FAILED",
        eventData: { async: true }
      });
      instance.status = "FAILED";
      instance.errorMessage = `${failedActivities} async activities failed`;
      instance.errorDetails = {
        failedActivities: failedEvents.map((e) => ({
          activityId: e.eventData.activityId,
          error: e.eventData.error,
          jobId: e.eventData.jobId
        }))
      };
      await trx.flush();
      await logWorkflowEvent(trx, {
        workflowInstanceId: instanceId,
        eventType: "WORKFLOW_FAILED",
        eventData: {
          reason: "Async activities failed",
          failedActivities: instance.errorDetails.failedActivities
        },
        tenantId: instance.tenantId,
        organizationId: instance.organizationId
      });
      return { continueExecution: false };
    }
    const completedEvents = await trx.find(WorkflowEvent, {
      workflowInstanceId: instanceId,
      eventType: "ACTIVITY_COMPLETED",
      eventData: { async: true }
    });
    for (const event of completedEvents) {
      if (event.eventData.output) {
        instance.context = {
          ...instance.context,
          [`${event.eventData.activityId}_result`]: event.eventData.output
        };
      }
    }
    delete instance.context._pendingAsyncActivities;
    const pendingTransition = instance.pendingTransition;
    if (!pendingTransition) {
      logger.warn("No pending transition found during resume");
      instance.status = "RUNNING";
      await trx.flush();
      await logWorkflowEvent(trx, {
        workflowInstanceId: instanceId,
        eventType: "WORKFLOW_RESUMED",
        eventData: {
          reason: "All async activities completed",
          completedActivities
        },
        tenantId: instance.tenantId,
        organizationId: instance.organizationId
      });
      return { continueExecution: true };
    }
    logger.debug("Completing pending transition", {
      toStepId: pendingTransition.toStepId,
      fromStepId: instance.currentStepId
    });
    const definition = await findDefinitionForInstance(trx, instance);
    if (!definition) {
      throw new WorkflowExecutionError(
        `Workflow definition not found: ${instance.definitionId}`,
        "DEFINITION_NOT_FOUND",
        { definitionId: instance.definitionId }
      );
    }
    const step = definition.definition.steps.find((s) => s.stepId === pendingTransition.toStepId);
    instance.currentStepId = pendingTransition.toStepId;
    instance.status = "RUNNING";
    instance.pendingTransition = null;
    instance.updatedAt = /* @__PURE__ */ new Date();
    await trx.flush();
    await logWorkflowEvent(trx, {
      workflowInstanceId: instance.id,
      eventType: "STEP_ENTERED",
      eventData: {
        stepId: pendingTransition.toStepId,
        stepName: step?.stepName,
        stepType: step?.stepType
      },
      tenantId: instance.tenantId,
      organizationId: instance.organizationId
    });
    await logWorkflowEvent(trx, {
      workflowInstanceId: instanceId,
      eventType: "WORKFLOW_RESUMED",
      eventData: {
        reason: "Async activities completed, resuming pending transition",
        completedActivities,
        completedTransitionTo: pendingTransition.toStepId
      },
      tenantId: instance.tenantId,
      organizationId: instance.organizationId
    });
    const { executeStep } = await import("./step-handler.js");
    await executeStep(
      trx,
      instance,
      pendingTransition.toStepId,
      {
        workflowContext: instance.context || {},
        userId: void 0
      },
      container
    );
    return { continueExecution: true };
  };
  const resumeResult = typeof transactionalEm.transactional === "function" ? await transactionalEm.transactional((trx) => runResume(trx)) : await runResume(em);
  if (resumeResult.continueExecution) {
    await executeWorkflow(em, container, instanceId);
  }
}
function checkIfCompensationNeeded(definition) {
  for (const transition of definition.definition.transitions) {
    if (transition.activities) {
      for (const activity of transition.activities) {
        if (activity.compensation?.activityId) {
          return true;
        }
      }
    }
  }
  if (definition.definition.activities) {
    for (const activity of definition.definition.activities) {
      if (activity.compensation?.activityId) {
        return true;
      }
    }
  }
  return false;
}
async function getWorkflowInstance(em, instanceId) {
  return em.findOne(WorkflowInstance, { id: instanceId });
}
async function getWorkflowInstanceForExecution(em, instanceId, options) {
  return em.findOne(
    WorkflowInstance,
    { id: instanceId },
    {
      lockMode: LockMode.PESSIMISTIC_WRITE,
      ...options?.refresh ? { refresh: true } : {}
    }
  );
}
async function updateWorkflowContext(em, instanceId, updates) {
  const instance = await getWorkflowInstance(em, instanceId);
  if (!instance) {
    throw new WorkflowExecutionError(
      `Workflow instance not found: ${instanceId}`,
      "INSTANCE_NOT_FOUND",
      { instanceId }
    );
  }
  instance.context = {
    ...instance.context,
    ...updates
  };
  instance.updatedAt = /* @__PURE__ */ new Date();
  await em.flush();
}
async function logWorkflowEvent(em, event) {
  const workflowEvent = em.create(WorkflowEvent, {
    ...event,
    occurredAt: /* @__PURE__ */ new Date()
  });
  await em.persist(workflowEvent).flush();
  return workflowEvent;
}
export {
  WorkflowExecutionError,
  completeWorkflow,
  executeWorkflow,
  getWorkflowInstance,
  resumeWorkflowAfterActivities,
  startWorkflow,
  updateWorkflowContext
};
//# sourceMappingURL=workflow-executor.js.map
