import {
  WorkflowEvent
} from "../data/entities.js";
import * as ruleEvaluator from "../../business_rules/lib/rule-evaluator.js";
import * as ruleEngine from "../../business_rules/lib/rule-engine.js";
import * as activityExecutor from "./activity-executor.js";
import * as stepHandler from "./step-handler.js";
import { findDefinitionForInstance } from "./find-definition.js";
import {
  rootToken,
  tokenBranchInstanceId,
  tokenReadContext,
  setTokenCurrentStepId,
  applyTokenContextWrites,
  mergeTokenContext,
  setTokenWaitingForActivities,
  setTokenPendingTransition,
  touchToken
} from "./execution-token.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("workflows");
class TransitionError extends Error {
  constructor(message, code, details) {
    super(message);
    this.code = code;
    this.details = details;
    this.name = "TransitionError";
  }
}
async function evaluateTransition(em, instance, fromStepId, toStepId, context) {
  const startTime = Date.now();
  try {
    const definition = await findDefinitionForInstance(em, instance);
    if (!definition) {
      return {
        isValid: false,
        reason: `Workflow definition not found: ${instance.definitionId}`,
        evaluationTime: Date.now() - startTime
      };
    }
    const transitions = definition.definition.transitions || [];
    let transition;
    if (toStepId) {
      transition = transitions.find(
        (t) => t.fromStepId === fromStepId && t.toStepId === toStepId
      );
      if (!transition) {
        return {
          isValid: false,
          reason: `No transition found from ${fromStepId} to ${toStepId}`,
          evaluationTime: Date.now() - startTime
        };
      }
    } else {
      const availableTransitions = transitions.filter(
        (t) => t.fromStepId === fromStepId
      );
      if (availableTransitions.length === 0) {
        return {
          isValid: false,
          reason: `No transitions available from step ${fromStepId}`,
          evaluationTime: Date.now() - startTime
        };
      }
      for (const t of availableTransitions) {
        const result = await evaluateTransitionConditions(
          em,
          instance,
          t,
          context
        );
        if (result.isValid) {
          transition = t;
          break;
        }
      }
      if (!transition) {
        return {
          isValid: false,
          reason: `No valid transitions found from step ${fromStepId}`,
          evaluationTime: Date.now() - startTime
        };
      }
    }
    const conditionResult = await evaluateTransitionConditions(
      em,
      instance,
      transition,
      context
    );
    return {
      ...conditionResult,
      transition,
      evaluationTime: Date.now() - startTime
    };
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    return {
      isValid: false,
      reason: `Transition evaluation error: ${errorMessage}`,
      evaluationTime: Date.now() - startTime
    };
  }
}
async function findValidTransitions(em, instance, fromStepId, context) {
  try {
    const definition = await findDefinitionForInstance(em, instance);
    if (!definition) {
      return [];
    }
    const transitions = (definition.definition.transitions || []).filter((t) => t.fromStepId === fromStepId).sort((a, b) => (b.priority || 0) - (a.priority || 0));
    const results = [];
    for (const transition of transitions) {
      const conditionResult = await evaluateTransition(
        em,
        instance,
        fromStepId,
        transition.toStepId,
        context
      );
      if (!conditionResult.isValid) {
        results.push(conditionResult);
        continue;
      }
      const preConditions = transition.preConditions || [];
      if (preConditions.length > 0) {
        const preConditionsResult = await evaluatePreConditions(
          em,
          instance,
          transition,
          context,
          null
        );
        if (!preConditionsResult.allowed) {
          const failedRules = preConditionsResult.executedRules.filter((r) => !r.conditionResult).map((r) => r.rule.ruleId || r.rule.ruleName);
          results.push({
            isValid: false,
            transition,
            reason: `Pre-conditions failed: ${failedRules.join(", ")}`,
            failedConditions: failedRules
          });
          continue;
        }
      }
      results.push({
        ...conditionResult,
        transition
      });
    }
    return results;
  } catch (error) {
    logger.error("Error finding valid transitions", { err: error });
    return [];
  }
}
async function executeTransition(em, container, instance, fromStepId, toStepId, context) {
  return executeTransitionForToken(em, container, rootToken(instance), fromStepId, toStepId, context);
}
async function executeTransitionForToken(em, container, token, fromStepId, toStepId, context) {
  const instance = token.instance;
  const branch = token.kind === "branch" ? token.branch : null;
  const branchInstanceId = tokenBranchInstanceId(token);
  try {
    let eventBus = null;
    try {
      eventBus = container.resolve("eventBus");
    } catch {
      eventBus = null;
    }
    const evaluation = await evaluateTransition(
      em,
      instance,
      fromStepId,
      toStepId,
      context
    );
    if (!evaluation.isValid) {
      return {
        success: false,
        error: evaluation.reason || "Transition validation failed"
      };
    }
    const transition = evaluation.transition;
    const preConditionsResult = await evaluatePreConditions(
      em,
      instance,
      transition,
      context,
      eventBus
    );
    if (!preConditionsResult.allowed) {
      const failedRules = preConditionsResult.executedRules.filter((r) => !r.conditionResult).map((r) => ({
        ruleId: r.rule.ruleId,
        ruleName: r.rule.ruleName,
        error: r.error
      }));
      const failedRulesDetails = failedRules.length > 0 ? failedRules.map((r) => `${r.ruleId}: ${r.error || "condition failed"}`).join("; ") : preConditionsResult.errors?.join(", ") || "Unknown pre-condition failure";
      await logTransitionEvent(em, {
        workflowInstanceId: instance.id,
        eventType: "TRANSITION_REJECTED",
        eventData: {
          fromStepId,
          toStepId,
          transitionId: transition.transitionId || `${fromStepId}->${toStepId}`,
          reason: "Pre-conditions failed",
          failedRules: failedRulesDetails,
          failedRulesDetail: failedRules
        },
        userId: context.userId,
        tenantId: instance.tenantId,
        organizationId: instance.organizationId
      });
      return {
        success: false,
        error: `Pre-conditions failed: ${failedRulesDetails}`,
        conditionsEvaluated: {
          preConditions: false,
          postConditions: false
        }
      };
    }
    let activityOutputs = {};
    const activityResults = [];
    if (transition.activities && transition.activities.length > 0) {
      const activityContext = {
        workflowInstance: instance,
        workflowContext: {
          ...tokenReadContext(token),
          ...context.workflowContext
        },
        branchInstanceId,
        userId: context.userId
      };
      const results = await activityExecutor.executeActivities(
        em,
        container,
        transition.activities,
        activityContext
      );
      activityResults.push(...results);
      const failedActivities = results.filter((r) => !r.success);
      if (failedActivities.length > 0) {
        const continueOnFailure = transition.continueOnActivityFailure ?? false;
        await logTransitionEvent(em, {
          workflowInstanceId: instance.id,
          eventType: "ACTIVITY_FAILED",
          eventData: {
            fromStepId,
            toStepId,
            transitionId: transition.transitionId || `${fromStepId}->${toStepId}`,
            failedActivities: failedActivities.map((f) => ({
              activityType: f.activityType,
              activityName: f.activityName,
              error: f.error,
              retryCount: f.retryCount
            })),
            continueOnFailure
          },
          userId: context.userId,
          tenantId: instance.tenantId,
          organizationId: instance.organizationId
        });
        if (!continueOnFailure) {
          return {
            success: false,
            error: `Activities failed: ${failedActivities.map((f) => f.error).join(", ")}`,
            conditionsEvaluated: {
              preConditions: true,
              postConditions: false
            }
          };
        }
      }
      results.forEach((result) => {
        if (result.success && result.output) {
          const key = result.activityName || result.activityType;
          activityOutputs[key] = result.output;
        }
      });
    }
    const hasAsyncActivities = activityResults.some((r) => r.async);
    if (hasAsyncActivities) {
      const pendingJobIds = activityResults.filter((a) => a.async && a.jobId).map((a) => ({ activityId: a.activityId, jobId: a.jobId }));
      setTokenPendingTransition(token, {
        toStepId,
        activityResults,
        timestamp: /* @__PURE__ */ new Date()
      });
      applyTokenContextWrites(token, context.workflowContext, activityOutputs);
      mergeTokenContext(token, { _pendingAsyncActivities: pendingJobIds });
      setTokenWaitingForActivities(token);
      touchToken(token, /* @__PURE__ */ new Date());
      await em.flush();
      await logTransitionEvent(em, {
        workflowInstanceId: instance.id,
        branchInstanceId,
        eventType: "TRANSITION_PAUSED_FOR_ACTIVITIES",
        eventData: {
          fromStepId,
          toStepId,
          transitionId: transition.transitionId,
          pendingActivities: pendingJobIds
        },
        userId: context.userId,
        tenantId: instance.tenantId,
        organizationId: instance.organizationId
      });
      return {
        success: true,
        pausedForActivities: true,
        nextStepId: toStepId,
        conditionsEvaluated: {
          preConditions: true,
          postConditions: false
          // Not evaluated yet
        },
        activitiesExecuted: activityResults
      };
    }
    setTokenCurrentStepId(token, toStepId);
    applyTokenContextWrites(token, context.workflowContext, activityOutputs);
    touchToken(token, /* @__PURE__ */ new Date());
    await em.flush();
    const stepExecutionResult = await stepHandler.executeStep(
      em,
      instance,
      toStepId,
      {
        workflowContext: tokenReadContext(token),
        userId: context.userId,
        triggerData: context.triggerData
      },
      container,
      branch
    );
    await em.flush();
    if (stepExecutionResult.status === "FAILED") {
      return {
        success: false,
        error: stepExecutionResult.error || "Step execution failed"
      };
    }
    const postConditionsResult = await evaluatePostConditions(
      em,
      instance,
      transition,
      context,
      eventBus
    );
    if (!postConditionsResult.allowed) {
      const failedRules = postConditionsResult.errors?.join(", ") || "Unknown post-condition failure";
      await logTransitionEvent(em, {
        workflowInstanceId: instance.id,
        eventType: "TRANSITION_POST_CONDITION_FAILED",
        eventData: {
          fromStepId,
          toStepId,
          transitionId: transition.transitionId || `${fromStepId}->${toStepId}`,
          reason: "Post-conditions failed",
          failedRules
        },
        userId: context.userId,
        tenantId: instance.tenantId,
        organizationId: instance.organizationId
      });
    }
    await logTransitionEvent(em, {
      workflowInstanceId: instance.id,
      eventType: "TRANSITION_EXECUTED",
      eventData: {
        fromStepId,
        toStepId,
        transitionId: transition.transitionId || `${fromStepId}->${toStepId}`,
        transitionName: transition.transitionName,
        preConditionsPassed: true,
        postConditionsPassed: postConditionsResult.allowed,
        activitiesExecuted: activityResults.length,
        activitiesSucceeded: activityResults.filter((r) => r.success).length,
        activitiesFailed: activityResults.filter((r) => !r.success).length
      },
      userId: context.userId,
      tenantId: instance.tenantId,
      organizationId: instance.organizationId
    });
    return {
      success: true,
      nextStepId: toStepId,
      conditionsEvaluated: {
        preConditions: true,
        postConditions: postConditionsResult.allowed
      },
      activitiesExecuted: activityResults
    };
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    await logTransitionEvent(em, {
      workflowInstanceId: instance.id,
      eventType: "TRANSITION_FAILED",
      eventData: {
        fromStepId,
        toStepId,
        error: errorMessage
      },
      userId: context.userId,
      tenantId: instance.tenantId,
      organizationId: instance.organizationId
    });
    return {
      success: false,
      error: `Transition execution failed: ${errorMessage}`
    };
  }
}
async function evaluateTransitionConditions(em, instance, transition, context) {
  try {
    if (!transition.condition) {
      return {
        isValid: true
      };
    }
    const data = {
      ...instance.context,
      ...context.workflowContext,
      triggerData: context.triggerData
    };
    const evalContext = {
      entityType: "workflow:transition",
      entityId: instance.id,
      user: context.userId ? { id: context.userId } : void 0
    };
    const result = await ruleEvaluator.evaluateConditions(
      transition.condition,
      data,
      evalContext
    );
    return {
      isValid: result,
      reason: result ? void 0 : "Transition condition evaluated to false"
    };
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    return {
      isValid: false,
      reason: `Condition evaluation error: ${errorMessage}`
    };
  }
}
async function evaluatePreConditions(em, instance, transition, context, eventBus) {
  try {
    const definition = await findDefinitionForInstance(em, instance);
    if (!definition) {
      return {
        allowed: true,
        executedRules: [],
        totalExecutionTime: 0
      };
    }
    const preConditions = transition.preConditions || [];
    if (preConditions.length === 0) {
      return {
        allowed: true,
        executedRules: [],
        totalExecutionTime: 0
      };
    }
    const startTime = Date.now();
    const executedRules = [];
    const errors = [];
    let allowed = true;
    for (const condition of preConditions) {
      const result = await ruleEngine.executeRuleByRuleId(em, {
        ruleId: condition.ruleId,
        // String identifier
        data: {
          workflowInstanceId: instance.id,
          workflowId: definition.workflowId,
          fromStepId: transition.fromStepId,
          toStepId: transition.toStepId,
          workflowContext: {
            ...instance.context,
            ...context.workflowContext
          },
          triggerData: context.triggerData
        },
        user: context.userId ? { id: context.userId } : void 0,
        tenantId: instance.tenantId,
        organizationId: instance.organizationId,
        executedBy: context.userId,
        entityType: `workflow:${definition.workflowId}:transition`,
        entityId: transition.transitionId || `${transition.fromStepId}->${transition.toStepId}`,
        eventType: "pre_transition"
      });
      const ruleResult = {
        rule: {
          ruleId: result.ruleId,
          ruleName: result.ruleName,
          ruleType: "GUARD"
        },
        conditionResult: result.conditionResult,
        actionsExecuted: result.actionsExecuted,
        executionTime: result.executionTime,
        error: result.error,
        logId: result.logId
      };
      executedRules.push(ruleResult);
      if (result.error) {
        const isRequired2 = condition.required !== false;
        if (isRequired2) {
          allowed = false;
          errors.push(`Rule '${result.ruleId}': ${result.error}`);
        }
        continue;
      }
      const isRequired = condition.required !== false;
      if (isRequired && !result.conditionResult) {
        allowed = false;
        errors.push(`Pre-condition '${result.ruleName || result.ruleId}' failed`);
      }
    }
    return {
      allowed,
      executedRules,
      totalExecutionTime: Date.now() - startTime,
      errors: errors.length > 0 ? errors : void 0
    };
  } catch (error) {
    logger.error("Error evaluating pre-conditions", { err: error });
    return {
      allowed: false,
      executedRules: [],
      totalExecutionTime: 0,
      errors: [error instanceof Error ? error.message : String(error)]
    };
  }
}
async function evaluatePostConditions(em, instance, transition, context, eventBus) {
  try {
    const definition = await findDefinitionForInstance(em, instance);
    if (!definition) {
      return {
        allowed: true,
        executedRules: [],
        totalExecutionTime: 0
      };
    }
    const postConditions = transition.postConditions || [];
    if (postConditions.length === 0) {
      return {
        allowed: true,
        executedRules: [],
        totalExecutionTime: 0
      };
    }
    const startTime = Date.now();
    const executedRules = [];
    const errors = [];
    let allowed = true;
    for (const condition of postConditions) {
      const result = await ruleEngine.executeRuleByRuleId(em, {
        ruleId: condition.ruleId,
        // String identifier
        data: {
          workflowInstanceId: instance.id,
          workflowId: definition.workflowId,
          fromStepId: transition.fromStepId,
          toStepId: transition.toStepId,
          workflowContext: {
            ...instance.context,
            ...context.workflowContext
          },
          triggerData: context.triggerData
        },
        user: context.userId ? { id: context.userId } : void 0,
        tenantId: instance.tenantId,
        organizationId: instance.organizationId,
        executedBy: context.userId,
        entityType: `workflow:${definition.workflowId}:transition`,
        entityId: transition.transitionId || `${transition.fromStepId}->${transition.toStepId}`,
        eventType: "post_transition"
      });
      const ruleResult = {
        rule: {
          ruleId: result.ruleId,
          ruleName: result.ruleName,
          ruleType: "GUARD"
        },
        conditionResult: result.conditionResult,
        actionsExecuted: result.actionsExecuted,
        executionTime: result.executionTime,
        error: result.error,
        logId: result.logId
      };
      executedRules.push(ruleResult);
      if (result.error) {
        errors.push(`Rule '${result.ruleId}': ${result.error}`);
        allowed = false;
        continue;
      }
      if (!result.conditionResult) {
        allowed = false;
        errors.push(`Post-condition '${result.ruleName || result.ruleId}' failed`);
      }
    }
    return {
      allowed,
      executedRules,
      totalExecutionTime: Date.now() - startTime,
      errors: errors.length > 0 ? errors : void 0
    };
  } catch (error) {
    logger.error("Error evaluating post-conditions", { err: error });
    return {
      allowed: false,
      executedRules: [],
      totalExecutionTime: 0,
      errors: [error instanceof Error ? error.message : String(error)]
    };
  }
}
async function logTransitionEvent(em, event) {
  const workflowEvent = em.create(WorkflowEvent, {
    ...event,
    occurredAt: /* @__PURE__ */ new Date()
  });
  await em.persist(workflowEvent).flush();
  return workflowEvent;
}
export {
  TransitionError,
  evaluateTransition,
  executeTransition,
  executeTransitionForToken,
  findValidTransitions
};
//# sourceMappingURL=transition-handler.js.map
