import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { WorkflowInstance, WorkflowBranchInstance, WorkflowDefinition, StepInstance } from "../data/entities.js";
import { resolveCodeDefinitionForInstance } from "./find-definition.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("workflows");
class SignalError extends Error {
  constructor(message, code, details) {
    super(message);
    this.code = code;
    this.details = details;
    this.name = "SignalError";
  }
}
async function sendSignal(em, container, options) {
  const { instanceId, signalName, payload, userId, tenantId, organizationId } = options;
  const eventLogger = container.resolve("eventLogger");
  const stepHandler = container.resolve("stepHandler");
  const transitionHandler = container.resolve("transitionHandler");
  const workflowExecutor = container.resolve("workflowExecutor");
  const instance = await findOneWithDecryption(
    em,
    WorkflowInstance,
    {
      id: instanceId,
      tenantId,
      organizationId
    },
    void 0,
    { tenantId, organizationId }
  );
  if (!instance) {
    throw new SignalError(
      "Workflow instance not found",
      "INSTANCE_NOT_FOUND",
      { instanceId }
    );
  }
  if (instance.status === "FORKED") {
    const branchDefinition = await findOneWithDecryption(
      em,
      WorkflowDefinition,
      { id: instance.definitionId, tenantId: instance.tenantId, organizationId: instance.organizationId, deletedAt: null },
      void 0,
      { tenantId: instance.tenantId, organizationId: instance.organizationId }
    ) ?? resolveCodeDefinitionForInstance(instance);
    if (!branchDefinition) {
      throw new SignalError("Workflow definition not found", "DEFINITION_NOT_FOUND", { definitionId: instance.definitionId });
    }
    const pausedBranches = await em.find(WorkflowBranchInstance, {
      workflowInstanceId: instanceId,
      status: "PAUSED",
      tenantId,
      organizationId
    });
    let targetBranch = null;
    for (const candidate of pausedBranches) {
      const step = branchDefinition.definition.steps.find((s) => s.stepId === candidate.currentStepId);
      if (step?.stepType === "WAIT_FOR_SIGNAL") {
        const candidateSignal = step.signalConfig?.signalName || step.stepId;
        if (candidateSignal === signalName) {
          targetBranch = candidate;
          break;
        }
      }
    }
    if (!targetBranch) {
      throw new SignalError("No parallel branch awaiting this signal", "NO_BRANCH_AWAITING_SIGNAL", { instanceId, signalName });
    }
    const branchStepInstance = await em.findOne(StepInstance, {
      workflowInstanceId: instanceId,
      branchInstanceId: targetBranch.id,
      stepId: targetBranch.currentStepId,
      status: "ACTIVE"
    });
    await eventLogger.logWorkflowEvent(em, {
      workflowInstanceId: instanceId,
      stepInstanceId: branchStepInstance?.id,
      branchInstanceId: targetBranch.id,
      eventType: "SIGNAL_RECEIVED",
      eventData: { signalName, branch: true },
      userId,
      tenantId,
      organizationId
    });
    const { resumeBranch } = await import("./parallel-handler.js");
    const resumed = await resumeBranch(em, {
      instanceId,
      branchInstanceId: targetBranch.id,
      tenantId,
      organizationId,
      contextMerge: payload,
      exitStepInstanceId: branchStepInstance?.id ?? null,
      exitOutput: { signalName, payload }
    });
    if (resumed) {
      await workflowExecutor.executeWorkflow(em, container, instanceId, { userId });
    }
    return;
  }
  if (instance.status !== "PAUSED") {
    throw new SignalError(
      "Workflow is not paused",
      "WORKFLOW_NOT_PAUSED",
      { instanceId, status: instance.status }
    );
  }
  const definition = await findOneWithDecryption(
    em,
    WorkflowDefinition,
    {
      id: instance.definitionId,
      tenantId: instance.tenantId,
      organizationId: instance.organizationId,
      deletedAt: null
    },
    void 0,
    { tenantId: instance.tenantId, organizationId: instance.organizationId }
  ) ?? resolveCodeDefinitionForInstance(instance);
  if (!definition) {
    throw new SignalError(
      "Workflow definition not found",
      "DEFINITION_NOT_FOUND",
      { definitionId: instance.definitionId }
    );
  }
  const currentStep = definition.definition.steps.find(
    (s) => s.stepId === instance.currentStepId
  );
  if (!currentStep || currentStep.stepType !== "WAIT_FOR_SIGNAL") {
    throw new SignalError(
      "Workflow is not waiting for signal",
      "NOT_WAITING_FOR_SIGNAL",
      { instanceId, currentStepId: instance.currentStepId }
    );
  }
  const expectedSignalName = currentStep.signalConfig?.signalName || currentStep.stepId;
  if (expectedSignalName !== signalName) {
    throw new SignalError(
      "Signal name mismatch",
      "SIGNAL_NAME_MISMATCH",
      { expected: expectedSignalName, received: signalName }
    );
  }
  const now = /* @__PURE__ */ new Date();
  if (payload) {
    instance.context = {
      ...instance.context,
      ...payload,
      [`signal_${signalName}_payload`]: payload,
      [`signal_${signalName}_receivedAt`]: now.toISOString()
    };
  }
  instance.updatedAt = now;
  await eventLogger.logWorkflowEvent(em, {
    workflowInstanceId: instance.id,
    eventType: "SIGNAL_RECEIVED",
    eventData: {
      signalName,
      payload
    },
    userId,
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
  const stepInstance = await findOneWithDecryption(
    em,
    StepInstance,
    {
      workflowInstanceId: instance.id,
      stepId: instance.currentStepId,
      status: "ACTIVE",
      tenantId: instance.tenantId,
      organizationId: instance.organizationId
    },
    void 0,
    { tenantId: instance.tenantId, organizationId: instance.organizationId }
  );
  if (stepInstance) {
    await stepHandler.exitStep(em, stepInstance, {
      signalName,
      payload
    });
  }
  const autoTransitions = (definition.definition.transitions || []).filter(
    (t) => t.fromStepId === instance.currentStepId && t.trigger === "auto"
  );
  if (autoTransitions.length === 0) {
    instance.status = "RUNNING";
    await em.flush();
    return;
  }
  const transitionContext = {
    workflowContext: instance.context,
    userId
  };
  const validTransitions = await transitionHandler.findValidTransitions(
    em,
    instance,
    instance.currentStepId,
    transitionContext
  );
  const firstValidTransition = validTransitions.find((t) => t.isValid);
  if (!firstValidTransition || !firstValidTransition.transition) {
    instance.status = "RUNNING";
    await em.flush();
    return;
  }
  const transitionResult = await transitionHandler.executeTransition(
    em,
    container,
    instance,
    instance.currentStepId,
    firstValidTransition.transition.toStepId,
    transitionContext
  );
  if (!transitionResult.success) {
    throw new SignalError(
      "Transition failed after signal",
      "TRANSITION_FAILED",
      { error: transitionResult.error }
    );
  }
  await workflowExecutor.executeWorkflow(em, container, instance.id, { userId });
}
async function sendSignalByCorrelationKey(em, container, options) {
  const { correlationKey, signalName, payload, userId, tenantId, organizationId } = options;
  const instances = await findWithDecryption(
    em,
    WorkflowInstance,
    {
      correlationKey,
      status: "PAUSED",
      tenantId,
      organizationId
    },
    void 0,
    { tenantId, organizationId }
  );
  let signalsProcessed = 0;
  for (const instance of instances) {
    try {
      await sendSignal(em, container, {
        instanceId: instance.id,
        signalName,
        payload,
        userId,
        tenantId,
        organizationId
      });
      signalsProcessed++;
    } catch (error) {
      logger.error("Failed to send signal to instance", { instanceId: instance.id, err: error });
    }
  }
  return signalsProcessed;
}
export {
  SignalError,
  sendSignal,
  sendSignalByCorrelationKey
};
//# sourceMappingURL=signal-handler.js.map
