import { LockMode } from "@mikro-orm/core";
import {
  WorkflowInstance,
  WorkflowBranchInstance,
  StepInstance,
  UserTask,
  WorkflowEvent
} from "../data/entities.js";
import { logWorkflowEvent } from "./event-logger.js";
import * as stepHandler from "./step-handler.js";
import { branchToken } from "./execution-token.js";
async function resumeBranch(em, options) {
  const branch = await em.findOne(WorkflowBranchInstance, {
    id: options.branchInstanceId,
    workflowInstanceId: options.instanceId,
    tenantId: options.tenantId,
    organizationId: options.organizationId
  });
  if (!branch) return false;
  if (branch.status !== "PAUSED" && branch.status !== "WAITING_FOR_ACTIVITIES") {
    return false;
  }
  const now = /* @__PURE__ */ new Date();
  if (options.contextMerge) {
    branch.contextNamespace = { ...branch.contextNamespace || {}, ...options.contextMerge };
  }
  if (options.exitStepInstanceId) {
    const stepInstance = await em.findOne(StepInstance, {
      id: options.exitStepInstanceId,
      workflowInstanceId: options.instanceId,
      status: "ACTIVE"
    });
    if (stepInstance) {
      await stepHandler.exitStep(em, stepInstance, options.exitOutput);
    }
  }
  branch.status = "ACTIVE";
  branch.pendingTransition = null;
  branch.updatedAt = now;
  await em.flush();
  return true;
}
function getNestedValue(obj, path) {
  return path.split(".").reduce((current, key) => current == null ? void 0 : current[key], obj);
}
async function openFork(em, instance, definition, forkStepDef) {
  const forkStepId = forkStepDef.stepId;
  const joinStepId = forkStepDef.config?.joinStepId;
  if (!joinStepId) {
    throw new Error(`[internal] PARALLEL_FORK "${forkStepId}" missing config.joinStepId`);
  }
  const outgoing = (definition.definition.transitions || []).filter(
    (transition) => transition.fromStepId === forkStepId && transition.trigger === "auto"
  );
  const now = /* @__PURE__ */ new Date();
  const branchKeys = [];
  for (const transition of outgoing) {
    branchKeys.push(transition.transitionId);
    const branch = em.create(WorkflowBranchInstance, {
      workflowInstanceId: instance.id,
      forkStepId,
      joinStepId,
      branchKey: transition.transitionId,
      parentBranchId: null,
      // Start ON the fork; advanceBranches runs the branch_key transition first.
      currentStepId: forkStepId,
      status: "ACTIVE",
      contextNamespace: {},
      tenantId: instance.tenantId,
      organizationId: instance.organizationId,
      startedAt: now,
      createdAt: now,
      updatedAt: now
    });
    em.persist(branch);
  }
  instance.status = "FORKED";
  instance.activeForkStepId = forkStepId;
  instance.updatedAt = now;
  await em.flush();
  await logWorkflowEvent(em, {
    workflowInstanceId: instance.id,
    eventType: "PARALLEL_FORK_OPENED",
    eventData: { forkStepId, joinStepId, branchKeys },
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
}
async function resumeBranchAfterActivities(em, container, instanceId, branchInstanceId) {
  const branch = await em.findOne(
    WorkflowBranchInstance,
    { id: branchInstanceId, workflowInstanceId: instanceId },
    { lockMode: LockMode.PESSIMISTIC_WRITE }
  );
  if (!branch) {
    throw new Error("[internal] Branch not found during async resume");
  }
  if (branch.status !== "WAITING_FOR_ACTIVITIES") {
    return { continueExecution: false };
  }
  const namespace = branch.contextNamespace || {};
  const pendingJobIds = namespace._pendingAsyncActivities || [];
  const completedEvents = await em.find(WorkflowEvent, {
    workflowInstanceId: instanceId,
    branchInstanceId,
    eventType: "ACTIVITY_COMPLETED",
    eventData: { async: true }
  });
  const failedCount = await em.count(WorkflowEvent, {
    workflowInstanceId: instanceId,
    branchInstanceId,
    eventType: "ACTIVITY_FAILED",
    eventData: { async: true }
  });
  if (completedEvents.length + failedCount < pendingJobIds.length) {
    return { continueExecution: false };
  }
  const now = /* @__PURE__ */ new Date();
  if (failedCount > 0) {
    branch.status = "FAILED";
    branch.errorMessage = `${failedCount} async activities failed in branch "${branch.branchKey}"`;
    branch.updatedAt = now;
    await em.flush();
    return { continueExecution: true };
  }
  const mergedNamespace = { ...namespace };
  for (const event of completedEvents) {
    if (event.eventData?.output) {
      mergedNamespace[`${event.eventData.activityId}_result`] = event.eventData.output;
    }
  }
  delete mergedNamespace._pendingAsyncActivities;
  branch.contextNamespace = mergedNamespace;
  const pending = branch.pendingTransition;
  branch.pendingTransition = null;
  branch.status = "ACTIVE";
  branch.updatedAt = now;
  await em.flush();
  if (pending) {
    const instance = await em.findOne(WorkflowInstance, { id: instanceId });
    if (instance) {
      branch.currentStepId = pending.toStepId;
      await em.flush();
      await stepHandler.executeStep(
        em,
        instance,
        pending.toStepId,
        { workflowContext: { ...instance.context || {}, ...branch.contextNamespace || {} } },
        container,
        branch
      );
    }
  }
  return { continueExecution: true };
}
async function advanceBranches(em, container, instance, definition, context) {
  const forkStepId = instance.activeForkStepId;
  if (!forkStepId) {
    throw new Error("[internal] advanceBranches called but instance has no active fork");
  }
  const allBranchesForBudget = await em.find(WorkflowBranchInstance, {
    workflowInstanceId: instance.id,
    forkStepId
  });
  const maxIterations = Math.max(1, allBranchesForBudget.length) * 100;
  let iterations = 0;
  while (iterations < maxIterations) {
    iterations++;
    const branches = await em.find(WorkflowBranchInstance, {
      workflowInstanceId: instance.id,
      forkStepId
    });
    const active = branches.filter((b) => b.status === "ACTIVE");
    if (active.length === 0) {
      const failed = branches.find((b) => b.status === "FAILED");
      if (failed) {
        await cancelSiblings(em, instance, failed);
        return { outcome: "failed", error: failed.errorMessage || "Branch failed" };
      }
      if (branches.every((b) => b.status === "COMPLETED")) {
        await fireJoin(em, instance, definition, branches, forkStepId);
        return { outcome: "joined" };
      }
      return { outcome: "waiting" };
    }
    for (const branch of active) {
      const result = await advanceOneBranch(em, container, instance, definition, branch, context);
      if (result === "failed") {
        await cancelSiblings(em, instance, branch);
        return { outcome: "failed", error: branch.errorMessage || "Branch failed" };
      }
    }
  }
  throw new Error("[internal] Maximum branch execution iterations reached - possible infinite loop");
}
async function advanceOneBranch(em, container, instance, definition, branch, context) {
  if (branch.currentStepId === branch.joinStepId) {
    await completeBranchAtJoin(em, instance, branch);
    return "advanced";
  }
  const transitionHandler = await import("./transition-handler.js");
  const token = branchToken(instance, branch);
  const readContext = { ...instance.context || {}, ...branch.contextNamespace || {} };
  const evalContext = { workflowContext: readContext, userId: context.userId };
  let selected = null;
  if (branch.currentStepId === branch.forkStepId) {
    selected = (definition.definition.transitions || []).find(
      (transition) => transition.transitionId === branch.branchKey
    ) || null;
  } else {
    const valid = await transitionHandler.findValidTransitions(
      em,
      instance,
      branch.currentStepId,
      evalContext
    );
    const validAuto = valid.filter((vt) => vt.isValid && vt.transition?.trigger === "auto");
    selected = validAuto.length > 0 ? validAuto[0].transition : null;
  }
  if (!selected) {
    await failBranch(em, instance, branch, `Branch "${branch.branchKey}" has no valid transition from "${branch.currentStepId}"`);
    return "failed";
  }
  try {
    const transitionResult = await transitionHandler.executeTransitionForToken(
      em,
      container,
      token,
      selected.fromStepId,
      selected.toStepId,
      evalContext
    );
    if (!transitionResult.success) {
      await failBranch(em, instance, branch, transitionResult.error || "Branch transition failed");
      return "failed";
    }
    if (branch.currentStepId === branch.joinStepId) {
      await completeBranchAtJoin(em, instance, branch);
    }
    return "advanced";
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    await failBranch(em, instance, branch, message);
    return "failed";
  }
}
async function completeBranchAtJoin(em, instance, branch) {
  const now = /* @__PURE__ */ new Date();
  branch.status = "COMPLETED";
  branch.completedAt = now;
  branch.updatedAt = now;
  await em.flush();
  await logWorkflowEvent(em, {
    workflowInstanceId: instance.id,
    branchInstanceId: branch.id,
    eventType: "PARALLEL_BRANCH_COMPLETED",
    eventData: { branchKey: branch.branchKey, joinStepId: branch.joinStepId },
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
}
async function failBranch(em, instance, branch, message) {
  const now = /* @__PURE__ */ new Date();
  branch.status = "FAILED";
  branch.errorMessage = message;
  branch.updatedAt = now;
  await em.flush();
  await logWorkflowEvent(em, {
    workflowInstanceId: instance.id,
    branchInstanceId: branch.id,
    eventType: "PARALLEL_BRANCH_FAILED",
    eventData: { branchKey: branch.branchKey, error: message },
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
}
async function cancelSiblings(em, instance, failedBranch) {
  const siblings = await em.find(WorkflowBranchInstance, {
    workflowInstanceId: instance.id,
    forkStepId: failedBranch.forkStepId
  });
  const now = /* @__PURE__ */ new Date();
  for (const sibling of siblings) {
    if (sibling.id === failedBranch.id) continue;
    if (sibling.status === "ACTIVE" || sibling.status === "PAUSED" || sibling.status === "WAITING_FOR_ACTIVITIES") {
      sibling.status = "CANCELLED";
      sibling.updatedAt = now;
      const openTasks = await em.find(UserTask, {
        workflowInstanceId: instance.id,
        branchInstanceId: sibling.id,
        status: "PENDING"
      });
      for (const task of openTasks) {
        task.status = "CANCELLED";
        task.updatedAt = now;
      }
      await em.flush();
      await logWorkflowEvent(em, {
        workflowInstanceId: instance.id,
        branchInstanceId: sibling.id,
        eventType: "PARALLEL_BRANCH_CANCELLED",
        eventData: { branchKey: sibling.branchKey, reason: "sibling-failed" },
        tenantId: instance.tenantId,
        organizationId: instance.organizationId
      });
    }
  }
  await logWorkflowEvent(em, {
    workflowInstanceId: instance.id,
    eventType: "PARALLEL_FORK_FAILED",
    eventData: { forkStepId: failedBranch.forkStepId, failedBranchKey: failedBranch.branchKey },
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
}
async function fireJoin(em, instance, definition, branches, forkStepId) {
  const joinStepId = branches[0]?.joinStepId;
  if (!joinStepId) {
    throw new Error("[internal] fireJoin called without a join step id");
  }
  const joinStep = definition.definition.steps.find((s) => s.stepId === joinStepId);
  const branchesContext = { ...instance.context?.branches || {} };
  const mergedBranchKeys = [];
  for (const branch of branches) {
    branchesContext[branch.branchKey] = branch.contextNamespace || {};
    mergedBranchKeys.push(branch.branchKey);
  }
  let nextContext = {
    ...instance.context || {},
    branches: branchesContext
  };
  const outputMapping = joinStep?.config?.outputMapping;
  if (outputMapping) {
    for (const [topKey, sourcePath] of Object.entries(outputMapping)) {
      if (topKey === "branches") continue;
      const value = getNestedValue(nextContext, sourcePath);
      if (value !== void 0) nextContext[topKey] = value;
    }
  }
  const afterJoinStepId = (definition.definition.transitions || []).find(
    (transition) => transition.fromStepId === joinStepId
  )?.toStepId ?? null;
  const now = /* @__PURE__ */ new Date();
  instance.context = nextContext;
  instance.status = "RUNNING";
  instance.activeForkStepId = null;
  instance.currentStepId = joinStepId;
  instance.updatedAt = now;
  await em.flush();
  await logWorkflowEvent(em, {
    workflowInstanceId: instance.id,
    eventType: "PARALLEL_JOIN_COMPLETED",
    eventData: { forkStepId, joinStepId, mergedBranchKeys, afterJoinStepId },
    tenantId: instance.tenantId,
    organizationId: instance.organizationId
  });
}
export {
  advanceBranches,
  openFork,
  resumeBranch,
  resumeBranchAfterActivities
};
//# sourceMappingURL=parallel-handler.js.map
