import {
  StepInstance,
  UserTask,
  WorkflowEvent
} from "../data/entities.js";
import { parseDuration } from "./duration.js";
import { logWorkflowEvent } from "./event-logger.js";
import { findDefinitionForInstance } from "./find-definition.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("workflows");
class StepExecutionError extends Error {
  constructor(message, code, details) {
    super(message);
    this.code = code;
    this.details = details;
    this.name = "StepExecutionError";
  }
}
async function enterStep(em, instance, stepId, context, branch) {
  const definition = await findDefinitionForInstance(em, instance);
  if (!definition) {
    throw new StepExecutionError(
      `Workflow definition not found: ${instance.definitionId}`,
      "DEFINITION_NOT_FOUND",
      { definitionId: instance.definitionId }
    );
  }
  const stepDef = definition.definition.steps.find((s) => s.stepId === stepId);
  if (!stepDef) {
    throw new StepExecutionError(
      `Step not found in workflow definition: ${stepId}`,
      "STEP_NOT_FOUND",
      { workflowId: definition.workflowId, stepId }
    );
  }
  const now = /* @__PURE__ */ new Date();
  const stepInstance = em.create(StepInstance, {
    workflowInstanceId: instance.id,
    branchInstanceId: branch ? branch.id : null,
    stepId: stepDef.stepId,
    stepName: stepDef.stepName,
    stepType: stepDef.stepType,
    status: "ACTIVE",
    inputData: context.triggerData || null,
    enteredAt: now,
    retryCount: 0,
    tenantId: instance.tenantId,
    organizationId: instance.organizationId,
    createdAt: now,
    updatedAt: now
  });
  await em.persist(stepInstance).flush();
  await logStepEvent(em, {
    workflowInstanceId: instance.id,
    stepInstanceId: stepInstance.id,
    ...branch ? { branchInstanceId: branch.id } : {},
    eventType: "STEP_ENTERED",
    eventData: {
      stepId: stepDef.stepId,
      stepName: stepDef.stepName,
      stepType: stepDef.stepType
    },
    userId: context.userId,
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
  return stepInstance;
}
async function exitStep(em, stepInstance, outputData) {
  const now = /* @__PURE__ */ new Date();
  let executionTimeMs = null;
  if (stepInstance.enteredAt) {
    executionTimeMs = now.getTime() - stepInstance.enteredAt.getTime();
  }
  stepInstance.status = "COMPLETED";
  stepInstance.outputData = outputData || null;
  stepInstance.exitedAt = now;
  stepInstance.executionTimeMs = executionTimeMs;
  stepInstance.updatedAt = now;
  await em.flush();
  await logStepEvent(em, {
    workflowInstanceId: stepInstance.workflowInstanceId,
    stepInstanceId: stepInstance.id,
    eventType: "STEP_EXITED",
    eventData: {
      stepId: stepInstance.stepId,
      status: "COMPLETED",
      executionTimeMs,
      hasOutput: !!outputData
    },
    tenantId: stepInstance.tenantId,
    organizationId: stepInstance.organizationId
  });
}
async function executeStep(em, instance, stepId, context, container, branch) {
  try {
    const stepInstance = await enterStep(em, instance, stepId, context, branch);
    const definition = await findDefinitionForInstance(em, instance);
    if (!definition) {
      throw new StepExecutionError(
        `Workflow definition not found: ${instance.definitionId}`,
        "DEFINITION_NOT_FOUND",
        { definitionId: instance.definitionId }
      );
    }
    const stepDef = definition.definition.steps.find((s) => s.stepId === stepId);
    if (!stepDef) {
      throw new StepExecutionError(
        `Step not found: ${stepId}`,
        "STEP_NOT_FOUND",
        { stepId }
      );
    }
    const result = await executeStepByType(
      em,
      instance,
      stepInstance,
      stepDef,
      context,
      container,
      branch
    );
    if (result.status === "COMPLETED") {
      await exitStep(em, stepInstance, result.outputData);
    }
    return result;
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    try {
      const failedStepInstance = await em.findOne(StepInstance, {
        workflowInstanceId: instance.id,
        stepId,
        status: "ACTIVE"
      });
      if (failedStepInstance) {
        failedStepInstance.status = "FAILED";
        failedStepInstance.errorData = {
          error: errorMessage,
          details: error instanceof StepExecutionError ? error.details : void 0
        };
        failedStepInstance.exitedAt = /* @__PURE__ */ new Date();
        failedStepInstance.updatedAt = /* @__PURE__ */ new Date();
        await em.flush();
        await logStepEvent(em, {
          workflowInstanceId: instance.id,
          stepInstanceId: failedStepInstance.id,
          eventType: "STEP_FAILED",
          eventData: { error: errorMessage },
          tenantId: instance.tenantId,
          organizationId: instance.organizationId
        });
      }
    } catch (updateError) {
      logger.error("Failed to update step instance with error", { err: updateError });
    }
    return {
      status: "FAILED",
      error: errorMessage
    };
  }
}
async function executeStepByType(em, instance, stepInstance, stepDef, context, container, branch) {
  const stepType = stepDef.stepType;
  switch (stepType) {
    case "START":
      return handleStartStep(stepDef, context);
    case "END":
      return handleEndStep(stepDef, context);
    case "AUTOMATED":
      return await handleAutomatedStep(em, instance, stepInstance, stepDef, context, container, branch);
    case "USER_TASK":
      return await handleUserTaskStep(em, instance, stepInstance, stepDef, context, branch);
    case "SUB_WORKFLOW":
      if (!container) {
        throw new StepExecutionError(
          "Container required for SUB_WORKFLOW execution",
          "CONTAINER_REQUIRED",
          { stepType }
        );
      }
      return await handleSubWorkflowStep(em, container, instance, stepInstance, stepDef, context);
    case "WAIT_FOR_SIGNAL":
      return await handleWaitForSignalStep(em, instance, stepInstance, stepDef, context, branch);
    case "WAIT_FOR_TIMER":
      return await handleWaitForTimerStep(em, instance, stepInstance, stepDef, context, branch);
    case "PARALLEL_FORK": {
      if (branch) {
        throw new StepExecutionError(
          "Nested PARALLEL_FORK is not supported",
          "NESTED_FORK_NOT_SUPPORTED",
          { stepType, stepId: stepDef.stepId }
        );
      }
      const definition = await findDefinitionForInstance(em, instance);
      if (!definition) {
        throw new StepExecutionError(
          `Workflow definition not found: ${instance.definitionId}`,
          "DEFINITION_NOT_FOUND",
          { definitionId: instance.definitionId }
        );
      }
      const { openFork } = await import("./parallel-handler.js");
      await openFork(em, instance, definition, stepDef);
      return { status: "WAITING", waitReason: "FORK", outputData: { stepType: "PARALLEL_FORK", forkStepId: stepDef.stepId } };
    }
    case "PARALLEL_JOIN":
      return { status: "COMPLETED", outputData: { stepType: "PARALLEL_JOIN", timestamp: (/* @__PURE__ */ new Date()).toISOString() } };
    default:
      throw new StepExecutionError(
        `Unknown step type: ${stepType}`,
        "UNKNOWN_STEP_TYPE",
        { stepType }
      );
  }
}
function handleStartStep(stepDef, context) {
  return {
    status: "COMPLETED",
    outputData: {
      stepType: "START",
      timestamp: (/* @__PURE__ */ new Date()).toISOString()
    }
  };
}
function handleEndStep(stepDef, context) {
  return {
    status: "COMPLETED",
    outputData: {
      stepType: "END",
      timestamp: (/* @__PURE__ */ new Date()).toISOString(),
      finalContext: context.workflowContext
    }
  };
}
async function handleAutomatedStep(em, instance, stepInstance, stepDef, context, container, branch) {
  const activities = stepDef.activities || [];
  if (activities.length === 0) {
    return {
      status: "COMPLETED",
      outputData: {
        stepType: "AUTOMATED",
        timestamp: (/* @__PURE__ */ new Date()).toISOString()
      }
    };
  }
  const { executeActivities } = await import("./activity-executor.js");
  try {
    const results = await executeActivities(em, container, activities, {
      workflowInstance: instance,
      workflowContext: context.workflowContext,
      stepContext: { stepId: stepDef.stepId, stepName: stepDef.stepName },
      stepInstanceId: stepInstance.id,
      userId: context.userId
    });
    const pendingActivities = results.filter((r) => r.async && !r.success);
    if (pendingActivities.length > 0) {
      const now = /* @__PURE__ */ new Date();
      if (branch) {
        branch.status = "WAITING_FOR_ACTIVITIES";
        branch.updatedAt = now;
      } else {
        instance.status = "WAITING_FOR_ACTIVITIES";
        instance.pausedAt = now;
        instance.updatedAt = now;
      }
      await em.flush();
      return {
        status: "WAITING",
        waitReason: "SIGNAL",
        // Reuse SIGNAL wait reason (will be resumed by activity completion)
        outputData: {
          pendingActivities: pendingActivities.map((r) => ({
            activityId: r.activityId,
            activityName: r.activityName,
            jobId: r.jobId
          }))
        }
      };
    }
    const failures = results.filter((r) => !r.success && !r.async);
    if (failures.length > 0) {
      const errorMessages = failures.map((f) => `${f.activityName || f.activityId}: ${f.error}`).join("; ");
      return {
        status: "FAILED",
        error: `${failures.length} activity(ies) failed: ${errorMessages}`,
        outputData: {
          failures: failures.map((f) => ({
            activityId: f.activityId,
            activityName: f.activityName,
            error: f.error,
            retryCount: f.retryCount
          }))
        }
      };
    }
    const activityOutputs = results.reduce((acc, r) => {
      if (r.output) {
        acc[r.activityId] = r.output;
      }
      return acc;
    }, {});
    return {
      status: "COMPLETED",
      outputData: {
        stepType: "AUTOMATED",
        timestamp: (/* @__PURE__ */ new Date()).toISOString(),
        activityResults: activityOutputs,
        activityCount: results.length
      }
    };
  } catch (error) {
    return {
      status: "FAILED",
      error: `Activity execution failed: ${error.message}`
    };
  }
}
async function handleUserTaskStep(em, instance, stepInstance, stepDef, context, branch) {
  const userTaskConfig = stepDef.userTaskConfig || {};
  let assignedTo = userTaskConfig.assignedTo || null;
  let assignedToRoles = userTaskConfig.assignedToRoles || null;
  if (Array.isArray(assignedTo)) {
    assignedToRoles = assignedTo;
    assignedTo = null;
  }
  const now = /* @__PURE__ */ new Date();
  const userTask = em.create(UserTask, {
    workflowInstanceId: instance.id,
    stepInstanceId: stepInstance.id,
    branchInstanceId: branch ? branch.id : null,
    taskName: stepDef.stepName,
    description: stepDef.description || null,
    status: "PENDING",
    formSchema: userTaskConfig.formSchema || null,
    formData: null,
    assignedTo,
    assignedToRoles,
    dueDate: userTaskConfig.slaDuration ? calculateDueDate(userTaskConfig.slaDuration) : null,
    tenantId: instance.tenantId,
    organizationId: instance.organizationId,
    createdAt: now,
    updatedAt: now
  });
  await em.persist(userTask).flush();
  await logStepEvent(em, {
    workflowInstanceId: instance.id,
    stepInstanceId: stepInstance.id,
    ...branch ? { branchInstanceId: branch.id } : {},
    eventType: "USER_TASK_CREATED",
    eventData: {
      userTaskId: userTask.id,
      taskName: userTask.taskName,
      assignedTo: userTask.assignedTo,
      assignedToRoles: userTask.assignedToRoles
    },
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
  if (branch) {
    branch.status = "PAUSED";
    branch.updatedAt = now;
  } else {
    instance.status = "PAUSED";
    instance.updatedAt = now;
  }
  await em.flush();
  return {
    status: "WAITING",
    waitReason: "USER_TASK",
    outputData: {
      userTaskId: userTask.id
    }
  };
}
async function handleSubWorkflowStep(em, container, instance, stepInstance, stepDef, context) {
  const { subWorkflowId, inputMapping, outputMapping, version } = stepDef.config || {};
  if (!subWorkflowId) {
    return {
      status: "FAILED",
      error: "Sub-workflow ID not specified in step configuration"
    };
  }
  const childContext = mapInputData(instance.context, inputMapping || {});
  const { startWorkflow, executeWorkflow } = await import("./workflow-executor.js");
  try {
    const childInstance = await startWorkflow(em, {
      workflowId: subWorkflowId,
      version,
      initialContext: childContext,
      correlationKey: instance.correlationKey || void 0,
      metadata: {
        ...instance.metadata,
        labels: {
          ...instance.metadata?.labels,
          parentInstanceId: instance.id,
          parentStepId: stepDef.stepId,
          parentStepInstanceId: stepInstance.id
        }
      },
      tenantId: instance.tenantId,
      organizationId: instance.organizationId
    });
    await logStepEvent(em, {
      workflowInstanceId: instance.id,
      stepInstanceId: stepInstance.id,
      eventType: "SUB_WORKFLOW_STARTED",
      eventData: {
        childInstanceId: childInstance.id,
        subWorkflowId,
        version,
        inputData: childContext
      },
      userId: context.userId,
      tenantId: instance.tenantId,
      organizationId: instance.organizationId
    });
    const result = await executeWorkflow(em, container, childInstance.id, {
      userId: context.userId
    });
    if (result.status === "COMPLETED") {
      const outputData = mapOutputData(result.context, outputMapping || {});
      await logStepEvent(em, {
        workflowInstanceId: instance.id,
        stepInstanceId: stepInstance.id,
        eventType: "SUB_WORKFLOW_COMPLETED",
        eventData: {
          childInstanceId: childInstance.id,
          outputData
        },
        userId: context.userId,
        tenantId: instance.tenantId,
        organizationId: instance.organizationId
      });
      return {
        status: "COMPLETED",
        outputData
      };
    } else if (result.status === "FAILED") {
      await logStepEvent(em, {
        workflowInstanceId: instance.id,
        stepInstanceId: stepInstance.id,
        eventType: "SUB_WORKFLOW_FAILED",
        eventData: {
          childInstanceId: childInstance.id,
          error: result.errors?.join(", ")
        },
        userId: context.userId,
        tenantId: instance.tenantId,
        organizationId: instance.organizationId
      });
      return {
        status: "FAILED",
        error: `Sub-workflow failed: ${result.errors?.join(", ")}`
      };
    } else {
      return {
        status: "FAILED",
        error: `Sub-workflow ended in unexpected state: ${result.status}`
      };
    }
  } catch (error) {
    await logStepEvent(em, {
      workflowInstanceId: instance.id,
      stepInstanceId: stepInstance.id,
      eventType: "SUB_WORKFLOW_FAILED",
      eventData: {
        subWorkflowId,
        error: error.message
      },
      userId: context.userId,
      tenantId: instance.tenantId,
      organizationId: instance.organizationId
    });
    return {
      status: "FAILED",
      error: `Sub-workflow execution failed: ${error.message}`
    };
  }
}
async function handleWaitForSignalStep(em, instance, stepInstance, stepDef, context, branch) {
  const signalConfig = stepDef.signalConfig || {};
  const signalName = signalConfig.signalName || stepDef.stepId;
  const timeout = signalConfig.timeout ? parseDuration(signalConfig.timeout) : null;
  const now = /* @__PURE__ */ new Date();
  await logStepEvent(em, {
    workflowInstanceId: instance.id,
    stepInstanceId: stepInstance.id,
    ...branch ? { branchInstanceId: branch.id } : {},
    eventType: "SIGNAL_AWAITING",
    eventData: {
      signalName,
      timeout,
      description: stepDef.description
    },
    userId: context.userId,
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
  if (branch) {
    branch.status = "PAUSED";
    branch.updatedAt = now;
  } else {
    instance.status = "PAUSED";
    instance.pausedAt = now;
    instance.updatedAt = now;
  }
  await em.flush();
  return {
    status: "WAITING",
    waitReason: "SIGNAL",
    outputData: {
      signalName,
      timeout,
      awaitingSince: now
    }
  };
}
async function handleWaitForTimerStep(em, instance, stepInstance, stepDef, context, branch) {
  const timerConfig = stepDef.config || stepDef.timerConfig || {};
  const duration = timerConfig.duration;
  const until = timerConfig.until;
  if (!duration && !until) {
    throw new StepExecutionError(
      'WAIT_FOR_TIMER requires either "duration" (e.g., "PT5M") or "until" (ISO 8601 datetime)',
      "TIMER_CONFIG_MISSING",
      { stepId: stepDef.stepId }
    );
  }
  let fireAtMs;
  if (until) {
    const targetDate = new Date(until);
    if (isNaN(targetDate.getTime())) {
      throw new StepExecutionError(
        `WAIT_FOR_TIMER invalid "until" datetime: ${until}`,
        "TIMER_CONFIG_INVALID",
        { until }
      );
    }
    fireAtMs = targetDate.getTime();
  } else {
    fireAtMs = Date.now() + parseDuration(duration);
  }
  const delayMs = fireAtMs - Date.now();
  const fireAt = new Date(fireAtMs);
  if (delayMs <= 0) {
    return {
      status: "COMPLETED",
      outputData: {
        stepType: "WAIT_FOR_TIMER",
        timerFiredImmediately: true,
        fireAt,
        duration,
        until
      }
    };
  }
  const now = /* @__PURE__ */ new Date();
  const { enqueueTimerJob } = await import("./activity-executor.js");
  const jobId = await enqueueTimerJob({
    workflowInstanceId: instance.id,
    stepInstanceId: stepInstance.id,
    branchInstanceId: branch ? branch.id : void 0,
    tenantId: instance.tenantId,
    organizationId: instance.organizationId,
    userId: context.userId,
    fireAt: fireAt.toISOString(),
    delayMs
  });
  await logWorkflowEvent(em, {
    workflowInstanceId: instance.id,
    stepInstanceId: stepInstance.id,
    ...branch ? { branchInstanceId: branch.id } : {},
    eventType: "TIMER_AWAITING",
    eventData: {
      fireAt: fireAt.toISOString(),
      duration: duration || null,
      until: until || null,
      jobId
    },
    userId: context.userId,
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
  if (branch) {
    branch.status = "PAUSED";
    branch.updatedAt = now;
  } else {
    instance.status = "PAUSED";
    instance.pausedAt = now;
    instance.updatedAt = now;
  }
  await em.flush();
  return {
    status: "WAITING",
    waitReason: "TIMER",
    outputData: {
      fireAt,
      duration,
      until,
      jobId
    }
  };
}
async function logStepEvent(em, event) {
  const workflowEvent = em.create(WorkflowEvent, {
    ...event,
    occurredAt: /* @__PURE__ */ new Date()
  });
  await em.persist(workflowEvent).flush();
  return workflowEvent;
}
function calculateDueDate(duration) {
  const now = /* @__PURE__ */ new Date();
  const daysMatch = duration.match(/P(\d+)D/);
  if (daysMatch) {
    const days = parseInt(daysMatch[1], 10);
    return new Date(now.getTime() + days * 24 * 60 * 60 * 1e3);
  }
  const hoursMatch = duration.match(/PT(\d+)H/);
  if (hoursMatch) {
    const hours = parseInt(hoursMatch[1], 10);
    return new Date(now.getTime() + hours * 60 * 60 * 1e3);
  }
  const weeksMatch = duration.match(/P(\d+)W/);
  if (weeksMatch) {
    const weeks = parseInt(weeksMatch[1], 10);
    return new Date(now.getTime() + weeks * 7 * 24 * 60 * 60 * 1e3);
  }
  return new Date(now.getTime() + 24 * 60 * 60 * 1e3);
}
function getNestedValue(obj, path) {
  return path.split(".").reduce((current, key) => current?.[key], obj);
}
function setNestedValue(obj, path, value) {
  const keys = path.split(".");
  const lastKey = keys.pop();
  const target = keys.reduce((current, key) => {
    if (!(key in current)) current[key] = {};
    return current[key];
  }, obj);
  target[lastKey] = value;
}
function mapInputData(sourceContext, mapping) {
  const result = {};
  for (const [targetKey, sourcePath] of Object.entries(mapping)) {
    const value = getNestedValue(sourceContext, sourcePath);
    if (value !== void 0) {
      setNestedValue(result, targetKey, value);
    }
  }
  return Object.keys(result).length > 0 ? result : sourceContext;
}
function mapOutputData(childContext, mapping) {
  const result = {};
  for (const [targetKey, sourcePath] of Object.entries(mapping)) {
    const value = getNestedValue(childContext, sourcePath);
    if (value !== void 0) {
      setNestedValue(result, targetKey, value);
    }
  }
  return Object.keys(result).length > 0 ? result : childContext;
}
export {
  StepExecutionError,
  enterStep,
  executeStep,
  exitStep
};
//# sourceMappingURL=step-handler.js.map
