/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */

/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-base-to-string */

import { type db } from "~/server/db";
import {
  trials,
  steps,
  actions,
  trialEvents,
  plugins,
} from "~/server/db/schema";
import { eq, asc } from "drizzle-orm";
import {
  getRobotCommunicationService,
  type RobotAction,
  type RobotActionResult,
} from "./robot-communication";
import type { ExperimentAction } from "~/lib/experiment-designer/types";

export type TrialStatus =
  | "scheduled"
  | "in_progress"
  | "completed"
  | "aborted"
  | "failed";

export interface ExecutionContext {
  trialId: string;
  experimentId: string;
  participantId: string;
  wizardId?: string;
  currentStepIndex: number;
  startTime: Date;
  variables: Record<string, unknown>;
}

export interface StepDefinition {
  id: string;
  name: string;
  description?: string;
  type: string;
  orderIndex: number;
  condition?: string;
  conditions?: Record<string, any>;
  actions: ActionDefinition[];
}

export interface ActionDefinition {
  id: string;
  stepId: string;
  name: string;
  description?: string;
  type: string;
  orderIndex: number;
  parameters: Record<string, unknown>;
  timeout?: number;
  required: boolean;
  condition?: string;
}

export interface ExecutionResult {
  success: boolean;
  error?: string;
  data?: Record<string, unknown>;
  duration?: number;
  nextStepIndex?: number;
}

export interface ActionExecutionResult {
  success: boolean;
  error?: string;
  data?: Record<string, unknown>;
  duration: number;
  completed: boolean;
}

export class TrialExecutionEngine {
  private db: typeof db;
  private activeTrials = new Map<string, ExecutionContext>();
  private stepDefinitions = new Map<string, StepDefinition[]>();
  private pluginCache = new Map<string, any>();
  private robotComm = getRobotCommunicationService();

  constructor(database: typeof db) {
    this.db = database;
  }

  /**
   * Initialize a trial for execution
   */
  async initializeTrial(trialId: string): Promise<ExecutionContext> {
    // Get trial details
    const [trial] = await this.db
      .select()
      .from(trials)
      .where(eq(trials.id, trialId));

    if (!trial) {
      throw new Error(`Trial ${trialId} not found`);
    }

    if (trial.status === "completed" || trial.status === "aborted") {
      throw new Error(`Trial ${trialId} is already ${trial.status}`);
    }

    // Load experiment steps and actions
    const experimentSteps = await this.loadExperimentProtocol(
      trial.experimentId,
    );
    this.stepDefinitions.set(trialId, experimentSteps);

    // Create execution context
    const context: ExecutionContext = {
      trialId,
      experimentId: trial.experimentId,
      participantId: trial.participantId || "",
      wizardId: trial.wizardId || undefined,
      currentStepIndex: 0,
      startTime: new Date(),
      variables: {},
    };

    this.activeTrials.set(trialId, context);
    return context;
  }

  /**
   * Load experiment protocol (steps and actions) from database
   */
  private async loadExperimentProtocol(
    experimentId: string,
  ): Promise<StepDefinition[]> {
    // Get all steps for the experiment
    const stepRecords = await this.db
      .select()
      .from(steps)
      .where(eq(steps.experimentId, experimentId))
      .orderBy(asc(steps.orderIndex));

    const stepDefinitions: StepDefinition[] = [];

    for (const step of stepRecords) {
      // Get all actions for this step
      const actionRecords = await this.db
        .select()
        .from(actions)
        .where(eq(actions.stepId, step.id))
        .orderBy(asc(actions.orderIndex));

      const actionDefinitions: ActionDefinition[] = actionRecords.map(
        (action: any) => ({
          id: action.id,
          stepId: action.stepId,
          name: action.name,
          description: action.description || undefined,
          type: action.type,
          orderIndex: action.orderIndex,
          parameters: (action.parameters as Record<string, unknown>) || {},
          timeout: action.timeout || undefined,
          required: action.required || true,
          condition: action.condition || undefined,
        }),
      );

      stepDefinitions.push({
        id: step.id,
        name: step.name,
        description: step.description || undefined,
        type: step.type,
        orderIndex: step.orderIndex,
        condition:
          typeof step.conditions === "string" ? step.conditions : undefined,
        conditions:
          typeof step.conditions === "object"
            ? (step.conditions as Record<string, any>)
            : undefined,
        actions: actionDefinitions,
      });
    }

    return stepDefinitions;
  }

  /**
   * Start trial execution
   */
  async startTrial(
    trialId: string,
    wizardId?: string,
  ): Promise<ExecutionResult> {
    try {
      let context = this.activeTrials.get(trialId);
      if (!context) {
        context = await this.initializeTrial(trialId);
      }

      if (wizardId) {
        context.wizardId = wizardId;
      }

      // Update trial status in database
      await this.db
        .update(trials)
        .set({
          status: "in_progress",
          startedAt: context.startTime,
          wizardId: context.wizardId,
        })
        .where(eq(trials.id, trialId));

      return {
        success: true,
        data: {
          trialId,
          status: "in_progress",
          currentStepIndex: context.currentStepIndex,
        },
      };
    } catch (error) {
      return {
        success: false,
        error:
          error instanceof Error
            ? error.message
            : "Unknown error starting trial",
      };
    }
  }

  /**
   * Execute the current step
   */
  async executeCurrentStep(trialId: string): Promise<ExecutionResult> {
    const context = this.activeTrials.get(trialId);
    if (!context) {
      return { success: false, error: "Trial not initialized" };
    }

    const steps = this.stepDefinitions.get(trialId);
    if (!steps || context.currentStepIndex >= steps.length) {
      return await this.completeTrial(trialId);
    }

    const step = steps[context.currentStepIndex];
    if (!step) {
      return { success: false, error: "Invalid step index" };
    }

    try {
      // Check step condition
      if (step.condition && !this.evaluateCondition(step.condition, context)) {
        // Skip this step
        return await this.advanceToNextStep(trialId);
      }

      // Log step start
      await this.logTrialEvent(trialId, "step_started", {
        stepId: step.id,
        stepName: step.name,
        stepIndex: context.currentStepIndex,
      });

      // Execute all actions in the step
      const actionResults = await this.executeStepActions(trialId, step);

      const failedActions = actionResults.filter(
        (result) => !result.success && result.required,
      );
      if (failedActions.length > 0) {
        throw new Error(
          `Step failed: ${failedActions.map((f) => f.error).join(", ")}`,
        );
      }

      // Log step completion
      await this.logTrialEvent(trialId, "step_completed", {
        stepId: step.id,
        stepName: step.name,
        stepIndex: context.currentStepIndex,
        actionResults: actionResults.map((r) => ({
          success: r.success,
          duration: r.duration,
        })),
      });

      return {
        success: true,
        data: {
          stepId: step.id,
          stepName: step.name,
          actionResults,
        },
      };
    } catch (error) {
      await this.logTrialEvent(trialId, "step_failed", {
        stepId: step.id,
        stepName: step.name,
        stepIndex: context.currentStepIndex,
        error: error instanceof Error ? error.message : "Unknown error",
      });

      return {
        success: false,
        error:
          error instanceof Error
            ? error.message
            : "Unknown error executing step",
      };
    }
  }

  /**
   * Execute all actions within a step
   */
  private async executeStepActions(
    trialId: string,
    step: StepDefinition,
  ): Promise<Array<ActionExecutionResult & { required: boolean }>> {
    const context = this.activeTrials.get(trialId)!;
    const results: Array<ActionExecutionResult & { required: boolean }> = [];

    for (const action of step.actions) {
      // Check action condition
      if (
        action.condition &&
        !this.evaluateCondition(action.condition, context)
      ) {
        results.push({
          success: true,
          completed: false,
          duration: 0,
          data: { skipped: true, reason: "condition not met" },
          required: action.required,
        });
        continue;
      }

      const startTime = Date.now();

      try {
        const result = await this.executeAction(trialId, action);
        const duration = Date.now() - startTime;

        await this.logTrialEvent(trialId, "action_executed", {
          actionId: action.id,
          actionName: action.name,
          actionType: action.type,
          stepId: step.id,
          duration,
          success: result.success,
          data: result.data,
        });

        results.push({
          ...result,
          duration,
          required: action.required,
        });
      } catch (error) {
        const duration = Date.now() - startTime;

        await this.logTrialEvent(trialId, "action_failed", {
          actionId: action.id,
          actionName: action.name,
          actionType: action.type,
          stepId: step.id,
          duration,
          error: error instanceof Error ? error.message : "Unknown error",
        });

        results.push({
          success: false,
          completed: false,
          duration,
          error: error instanceof Error ? error.message : "Unknown error",
          required: action.required,
        });
      }
    }

    return results;
  }

  /**
   * Execute a single action
   */
  async executeAction(
    trialId: string,
    action: ActionDefinition,
  ): Promise<ActionExecutionResult> {
    // This is where we'd dispatch to different action executors based on action.type
    // For now, we'll implement basic action types and mock robot actions

    switch (action.type) {
      case "wait":
      case "hristudio-core.wait":
        return await this.executeWaitAction(action);

      case "branch":
      case "hristudio-core.branch":
        // Branch actions are logical markers; execution is just a pass-through
        return {
          success: true,
          completed: true,
          duration: 0,
          data: { message: "Branch point reached" },
        };

      case "wizard_say":
      case "hristudio-woz.wizard_say":
        return await this.executeWizardAction(trialId, action);

      case "wizard_wait_for_response":
      case "hristudio-woz.wizard_wait_for_response":
        return await this.executeWizardAction(trialId, action);

      case "wizard_gesture":
        return await this.executeWizardAction(trialId, action);

      case "observe_behavior":
      case "hristudio-woz.observe":
        return await this.executeObservationAction(trialId, action);

      // Control Flow Actions
      case "sequence":
      case "hristudio-core.sequence":
        return await this.executeSequenceAction(trialId, action);

      case "parallel":
      case "hristudio-core.parallel":
        return await this.executeParallelAction(trialId, action);

      case "loop":
      case "hristudio-core.loop":
        return await this.executeLoopAction(trialId, action);

      default:
        // Check if it's a robot action (contains plugin prefix)
        if (
          action.type.includes(".") &&
          !action.type.startsWith("hristudio-")
        ) {
          return await this.executeRobotAction(trialId, action);
        }

        // Unknown action type - log and continue
        return {
          success: true,
          completed: true,
          duration: 0,
          data: {
            message: `Action type '${action.type}' not implemented yet`,
            parameters: action.parameters,
            localHandler: true, // Indicate this fell through to default local handler
          },
        };
    }
  }

  /**
   * Execute wait action
   */
  private async executeWaitAction(
    action: ActionDefinition,
  ): Promise<ActionExecutionResult> {
    const rawDuration = action.parameters.duration;
    // Duration is in SECONDS per definition, default to 1s
    const durationSeconds =
      typeof rawDuration === "string"
        ? parseFloat(rawDuration)
        : typeof rawDuration === "number"
          ? rawDuration
          : 1;

    const durationMs = durationSeconds * 1000;

    console.log(
      `[TrialExecution] Executing wait action: ${action.id}, rawDuration: ${rawDuration}, parsedSeconds: ${durationSeconds}, ms: ${durationMs}`,
    );

    return new Promise((resolve) => {
      setTimeout(() => {
        console.log(`[TrialExecution] Wait action completed: ${action.id}`);
        resolve({
          success: true,
          completed: true,
          duration: durationMs,

          data: { waitDuration: durationSeconds },
        });
      }, durationMs);
    });
  }

  /**
   * Execute wizard action (requires human input)
   */
  private async executeWizardAction(
    trialId: string,
    action: ActionDefinition,
  ): Promise<ActionExecutionResult> {
    // For wizard actions, we return immediately but mark as requiring wizard input
    // The wizard interface will handle the actual execution

    return {
      success: true,
      completed: false, // Requires wizard confirmation
      duration: 0,
      data: {
        requiresWizardInput: true,
        actionType: action.type,
        parameters: action.parameters,
        instructions: this.getWizardInstructions(action),
      },
    };
  }

  /**
   * Execute observation action
   */
  private async executeObservationAction(
    trialId: string,
    action: ActionDefinition,
  ): Promise<ActionExecutionResult> {
    // Observation actions typically require wizard input to record observations

    return {
      success: true,
      completed: false,
      duration: 0,
      data: {
        requiresWizardInput: true,
        actionType: action.type,
        parameters: action.parameters,
        observationType: action.parameters.type || "behavior",
      },
    };
  }

  /**
   * Execute robot action through plugin system
   */
  private async executeRobotAction(
    trialId: string,
    action: ActionDefinition,
  ): Promise<ActionExecutionResult> {
    const startTime = Date.now();

    try {
      // Parse plugin.action format
      const [pluginName, actionId] = action.type.split(".");

      console.log(
        `[TrialExecution] Parsed action: pluginName=${pluginName}, actionId=${actionId}`,
      );

      if (!pluginName || !actionId) {
        throw new Error(
          `Invalid robot action format: ${action.type}. Expected format: plugin.action`,
        );
      }

      // Get plugin configuration from database
      const plugin = await this.getPluginDefinition(pluginName);
      if (!plugin) {
        throw new Error(`Plugin '${pluginName}' not found`);
      }

      console.log(
        `[TrialExecution] Plugin loaded: ${plugin.name} (ID: ${plugin.id})`,
      );
      console.log(
        `[TrialExecution] Available actions: ${plugin.actions?.map((a: any) => a.id).join(", ")}`,
      );

      // Find action definition in plugin
      const actionDefinition = plugin.actions?.find(
        (a: any) => a.id === actionId,
      );
      if (!actionDefinition) {
        throw new Error(
          `Action '${actionId}' not found in plugin '${pluginName}'`,
        );
      }

      // Validate parameters
      const validatedParams = this.validateActionParameters(
        actionDefinition,
        action.parameters,
      );

      // Execute action through robot communication service
      const result = await this.executeRobotActionWithComm(
        plugin,
        actionDefinition,
        validatedParams,
        trialId,
      );

      const duration = Date.now() - startTime;

      return {
        success: true,
        completed: true,
        duration,
        data: {
          pluginName,
          actionId,
          parameters: validatedParams,
          robotResponse: result,
          platform: plugin.platform,
        },
      };
    } catch (error) {
      const duration = Date.now() - startTime;

      return {
        success: false,
        completed: false,
        duration,
        error:
          error instanceof Error
            ? error.message
            : "Robot action execution failed",
      };
    }
  }

  /**
   * Get plugin definition from database with caching
   */
  private async getPluginDefinition(pluginName: string): Promise<any> {
    // Check cache first
    if (this.pluginCache.has(pluginName)) {
      return this.pluginCache.get(pluginName);
    }

    try {
      // Check if pluginName is a UUID
      const isUuid =
        /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
          pluginName,
        );

      let plugin;
      if (isUuid) {
        const [result] = await this.db
          .select()
          .from(plugins)
          .where(eq(plugins.id, pluginName))
          .limit(1);
        plugin = result;
      } else {
        // Look up by identifier first (e.g., "nao6-ros2"), then fall back to name
        const [byIdentifier] = await this.db
          .select()
          .from(plugins)
          .where(eq(plugins.identifier, pluginName))
          .limit(1);

        if (byIdentifier) {
          plugin = byIdentifier;
        } else {
          const [byName] = await this.db
            .select()
            .from(plugins)
            .where(eq(plugins.name, pluginName))
            .limit(1);
          plugin = byName;
        }
      }

      if (plugin) {
        // Cache the plugin definition
        const cacheKey = isUuid ? plugin.id : plugin.identifier;

        const pluginData = {
          ...plugin,
          actions: plugin.actionDefinitions,
          platform: (plugin.metadata as any)?.platform,
          ros2Config: (plugin.metadata as any)?.ros2Config,
        };

        this.pluginCache.set(cacheKey, pluginData);
        // Also cache by ID and identifier
        if (plugin.id) {
          this.pluginCache.set(plugin.id, pluginData);
        }
        if (plugin.identifier) {
          this.pluginCache.set(plugin.identifier, pluginData);
        }

        return pluginData;
      }

      return null;
    } catch (error) {
      console.error(`Failed to load plugin ${pluginName}:`, error);
      return null;
    }
  }

  /**
   * Validate action parameters against plugin schema
   */
  private validateActionParameters(
    actionDefinition: any,
    parameters: Record<string, unknown>,
  ): Record<string, unknown> {
    const validated: Record<string, unknown> = {};

    if (!actionDefinition.parameters) {
      return parameters;
    }

    for (const paramDef of actionDefinition.parameters) {
      const paramName = paramDef.name;
      const paramValue = parameters[paramName];

      // Required parameter check
      if (
        paramDef.required &&
        (paramValue === undefined || paramValue === null)
      ) {
        throw new Error(`Required parameter '${paramName}' is missing`);
      }

      // Use default value if parameter not provided
      if (paramValue === undefined && paramDef.default !== undefined) {
        validated[paramName] = paramDef.default;
        continue;
      }

      if (paramValue !== undefined) {
        // Type validation
        switch (paramDef.type) {
          case "number":
            const numValue = Number(paramValue);
            if (isNaN(numValue)) {
              throw new Error(`Parameter '${paramName}' must be a number`);
            }
            if (paramDef.min !== undefined && numValue < paramDef.min) {
              throw new Error(
                `Parameter '${paramName}' must be >= ${paramDef.min}`,
              );
            }
            if (paramDef.max !== undefined && numValue > paramDef.max) {
              throw new Error(
                `Parameter '${paramName}' must be <= ${paramDef.max}`,
              );
            }
            validated[paramName] = numValue;
            break;

          case "boolean":
            validated[paramName] = Boolean(paramValue);
            break;

          case "select":
            if (paramDef.options) {
              const validOptions = paramDef.options.map(
                (opt: any) => opt.value,
              );
              if (!validOptions.includes(paramValue)) {
                throw new Error(
                  `Parameter '${paramName}' must be one of: ${validOptions.join(", ")}`,
                );
              }
            }
            validated[paramName] = paramValue;
            break;

          default:
            validated[paramName] = paramValue;
        }
      }
    }

    return validated;
  }

  /**
   * Execute robot action through robot communication service
   */
  private async executeRobotActionWithComm(
    plugin: any,
    actionDefinition: any,
    parameters: Record<string, unknown>,
    trialId: string,
  ): Promise<string> {
    // Plugin JSON uses a top-level "ros2" key; fall back to it if "implementation" is absent
    const impl = actionDefinition.implementation ?? actionDefinition.ros2;

    // Determine if this action uses SSH (animations or explicit sshCommand)
    const sshCommand =
      impl?.payloadMapping?.sshCommand ||
      impl?.ros2?.payloadMapping?.sshCommand;
    const isSSHAction =
      actionDefinition.id?.startsWith("play_animation_") || !!sshCommand;

    // SSH actions bypass ROS bridge — only connect for ROS-dependent actions
    if (!isSSHAction && !this.robotComm.getConnectionStatus()) {
      try {
        await this.robotComm.connect();
      } catch (error) {
        throw new Error(
          `Failed to connect to robot: ${error instanceof Error ? error.message : "Unknown error"}`,
        );
      }
    }

    // Prepare robot action
    const robotAction: RobotAction = {
      pluginName: plugin.name,
      actionId: actionDefinition.id, // e.g., "play_animation_yes"
      parameters,
      implementation: impl,
    };

    // Execute action through robot communication service
    const result: RobotActionResult =
      await this.robotComm.executeAction(robotAction);

    if (!result.success) {
      throw new Error(result.error || "Robot action failed");
    }

    // Log the successful action execution
    await this.logTrialEvent(trialId, "robot_action_executed", {
      actionId: actionDefinition.id,
      parameters,
      platform: plugin.platform,
      topic: actionDefinition.implementation?.topic,
      messageType: actionDefinition.implementation?.messageType,
      duration: result.duration,
      robotResponse: result.data,
    });

    // Return human-readable result
    return this.formatRobotActionResult(
      plugin,
      actionDefinition,
      parameters,
      result,
    );
  }

  /**
   * Format robot action result for human readability
   */
  private formatRobotActionResult(
    plugin: any,
    actionDefinition: any,
    parameters: Record<string, unknown>,
    result: RobotActionResult,
  ): string {
    const actionType = actionDefinition.id;
    const platform = plugin.platform || "Robot";

    switch (actionType) {
      case "say_text":
        return `${platform} said: "${parameters.text}"`;

      case "walk_forward":
        return `${platform} walked forward at speed ${parameters.speed} for ${parameters.duration || "indefinite"} seconds`;

      case "walk_backward":
        return `${platform} walked backward at speed ${parameters.speed} for ${parameters.duration || "indefinite"} seconds`;

      case "turn_left":
      case "turn_right":
        const direction = actionType.split("_")[1];
        return `${platform} turned ${direction} at speed ${parameters.speed}`;

      case "move_head":
        return `${platform} moved head to yaw=${parameters.yaw}, pitch=${parameters.pitch}`;

      case "move_arm":
        return `${platform} moved ${parameters.arm} arm to specified position`;

      case "stop_movement":
        return `${platform} stopped all movement`;

      case "set_volume":
        return `${platform} set volume to ${parameters.volume}`;

      case "set_language":
        return `${platform} set language to ${parameters.language}`;

      default:
        return `${platform} executed action: ${actionType} (${result.duration}ms)`;
    }
  }

  /**
   * Set a variable in the trial context
   */
  setVariable(trialId: string, key: string, value: unknown): void {
    const context = this.activeTrials.get(trialId);
    if (context) {
      context.variables[key] = value;
    }
  }

  /**
   * Advance to the next step
   */
  async advanceToNextStep(trialId: string): Promise<ExecutionResult> {
    const context = this.activeTrials.get(trialId);
    if (!context) {
      return { success: false, error: "Trial not initialized" };
    }

    const steps = this.stepDefinitions.get(trialId);
    if (!steps) {
      return { success: false, error: "No steps loaded for trial" };
    }

    const currentStep = steps[context.currentStepIndex];
    if (!currentStep) {
      return { success: false, error: "Invalid current step" };
    }

    const previousStepIndex = context.currentStepIndex;
    let nextStepIndex = context.currentStepIndex + 1;

    // Check for branching conditions
    if (currentStep.conditions) {
      const {
        variable,
        options,
        nextStepId: unconditionalNextId,
      } = currentStep.conditions as any;

      if (options) {
        // Default to "last_wizard_response" if variable not specified, for backward compatibility
        const variableName = variable || "last_wizard_response";
        const variableValue = context.variables[variableName];

        console.log(
          `[TrialExecution] Checking branch condition for step ${currentStep.id}: variable=${variableName}, value=${variableValue}`,
        );

        if (variableValue !== undefined) {
          // Find matching option
          // option.value matches variableValue (e.g., label string)
          const matchedOption = options.find(
            (opt: any) =>
              opt.value === variableValue || opt.label === variableValue,
          );

          if (matchedOption) {
            if (matchedOption.nextStepId) {
              // Find step by ID
              const targetStepIndex = steps.findIndex(
                (s) => s.id === matchedOption.nextStepId,
              );
              if (targetStepIndex !== -1) {
                nextStepIndex = targetStepIndex;
                console.log(
                  `[TrialExecution] Taking branch to step ID ${matchedOption.nextStepId} (Index ${nextStepIndex})`,
                );
              } else {
                console.warn(
                  `[TrialExecution] Branch target step ID ${matchedOption.nextStepId} not found`,
                );
              }
            } else if (matchedOption.nextStepIndex !== undefined) {
              // Fallback to relative/absolute index if ID not present (legacy)
              nextStepIndex = matchedOption.nextStepIndex;
              console.log(
                `[TrialExecution] Taking branch to index ${nextStepIndex}`,
              );
            }
          }
        }
      }

      // Check for unconditional jump if no branch was taken
      if (
        nextStepIndex === context.currentStepIndex + 1 &&
        unconditionalNextId
      ) {
        const targetStepIndex = steps.findIndex(
          (s) => s.id === unconditionalNextId,
        );
        if (targetStepIndex !== -1) {
          nextStepIndex = targetStepIndex;
          console.log(
            `[TrialExecution] Taking unconditional jump to step ID ${unconditionalNextId} (Index ${nextStepIndex})`,
          );
        } else {
          console.warn(
            `[TrialExecution] Unconditional jump target step ID ${unconditionalNextId} not found`,
          );
        }
      }
    }

    context.currentStepIndex = nextStepIndex;

    await this.logTrialEvent(trialId, "step_transition", {
      fromStepIndex: previousStepIndex,
      toStepIndex: context.currentStepIndex,
      reason: nextStepIndex !== previousStepIndex + 1 ? "branch" : "sequence",
    });

    // Check if we've completed all steps
    if (context.currentStepIndex >= steps.length) {
      return await this.completeTrial(trialId);
    }

    return {
      success: true,
      nextStepIndex: context.currentStepIndex,
      data: { previousStepIndex, currentStepIndex: context.currentStepIndex },
    };
  }

  /**
   * Complete the trial
   */
  async completeTrial(trialId: string): Promise<ExecutionResult> {
    const context = this.activeTrials.get(trialId);
    if (!context) {
      return { success: false, error: "Trial not initialized" };
    }

    const endTime = new Date();
    const duration = endTime.getTime() - context.startTime.getTime();

    try {
      // Update trial in database
      await this.db
        .update(trials)
        .set({
          status: "completed",
          completedAt: endTime,
          duration: Math.round(duration / 1000), // Convert to seconds
        })
        .where(eq(trials.id, trialId));

      // Clean up
      this.activeTrials.delete(trialId);
      this.stepDefinitions.delete(trialId);

      return {
        success: true,
        data: {
          trialId,
          status: "completed",
          duration,
          endTime: endTime.toISOString(),
        },
      };
    } catch (error) {
      return {
        success: false,
        error:
          error instanceof Error ? error.message : "Failed to complete trial",
      };
    }
  }

  /**
   * Abort the trial
   */
  async abortTrial(trialId: string, reason?: string): Promise<ExecutionResult> {
    const context = this.activeTrials.get(trialId);
    if (!context) {
      return { success: false, error: "Trial not initialized" };
    }

    const endTime = new Date();
    const duration = endTime.getTime() - context.startTime.getTime();

    try {
      await this.db
        .update(trials)
        .set({
          status: "aborted",
          completedAt: endTime,
          duration: Math.round(duration / 1000),
        })
        .where(eq(trials.id, trialId));

      await this.logTrialEvent(trialId, "trial_aborted", {
        reason: reason || "Manual abort",
        endTime: endTime.toISOString(),
        duration,
        stepIndex: context.currentStepIndex,
      });

      // Clean up
      this.activeTrials.delete(trialId);
      this.stepDefinitions.delete(trialId);

      return {
        success: true,
        data: {
          trialId,
          status: "aborted",
          reason,
          duration,
        },
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : "Failed to abort trial",
      };
    }
  }

  /**
   * Get current execution status
   */
  getTrialStatus(trialId: string): ExecutionContext | null {
    return this.activeTrials.get(trialId) || null;
  }

  /**
   * Get current step definition
   */
  getCurrentStep(trialId: string): StepDefinition | null {
    const context = this.activeTrials.get(trialId);
    const steps = this.stepDefinitions.get(trialId);

    if (!context || !steps || context.currentStepIndex >= steps.length) {
      return null;
    }

    return steps[context.currentStepIndex] || null;
  }

  /**
   * Log trial event to database
   */
  private async logTrialEvent(
    trialId: string,
    eventType: string,
    data: Record<string, unknown> = {},
  ): Promise<void> {
    try {
      await this.db.insert(trialEvents).values({
        trialId,
        eventType,
        data: data as any, // TODO: Fix typing
        timestamp: new Date(),
        createdBy: this.activeTrials.get(trialId)?.wizardId,
      });
    } catch (error) {
      console.error("Failed to log trial event:", error);
      // Don't throw - logging failures shouldn't stop execution
    }
  }

  /**
   * Evaluate condition (simple implementation)
   */
  private evaluateCondition(
    condition: string,
    context: ExecutionContext,
  ): boolean {
    try {
      // Simple condition evaluation - in production, use a safer evaluator
      // For now, support basic variable checks
      if (condition.includes("variables.")) {
        // Replace variables in condition with actual values
        let evaluableCondition = condition;
        Object.entries(context.variables).forEach(([key, value]) => {
          evaluableCondition = evaluableCondition.replace(
            `variables.${key}`,
            JSON.stringify(value),
          );
        });

        // Basic evaluation - in production, use a proper expression evaluator
        // eslint-disable-next-line @typescript-eslint/no-implied-eval
        return new Function("return " + evaluableCondition)();
      }

      return true; // Default to true if condition can't be evaluated
    } catch (error) {
      console.warn("Failed to evaluate condition:", condition, error);
      return true; // Fail open
    }
  }

  /**
   * Get wizard instructions for an action
   */
  private getWizardInstructions(action: ActionDefinition): string {
    switch (action.type) {
      case "wizard_say":
        return `Say: "${action.parameters.text || "Please speak to the participant"}"`;

      case "wizard_gesture":
        return `Perform gesture: ${action.parameters.gesture || "as specified in the protocol"}`;

      case "observe_behavior":
        return `Observe and record: ${action.parameters.behavior || "participant behavior"}`;

      default:
        return `Execute: ${action.name}`;
    }
  }

  /**
   * Execute a sequence of actions in order
   */
  private async executeSequenceAction(
    trialId: string,
    action: ActionDefinition,
  ): Promise<ActionExecutionResult> {
    const startTime = Date.now();
    const children = action.parameters.children as
      | ActionDefinition[]
      | undefined;

    if (!children || !Array.isArray(children) || children.length === 0) {
      return {
        success: true,
        completed: true,
        duration: Date.now() - startTime,
        data: { message: "Empty sequence completed", childCount: 0 },
      };
    }

    const results: ActionExecutionResult[] = [];

    // Execute children sequentially
    for (const childAction of children) {
      try {
        const result = await this.executeAction(trialId, childAction);
        results.push(result);

        // If any child fails, stop sequence execution
        if (!result.success) {
          return {
            success: false,
            completed: false,
            duration: Date.now() - startTime,
            data: {
              message: `Sequence failed at action: ${childAction.name}`,
              completedActions: results.length,
              totalActions: children.length,
              results,
            },
          };
        }
      } catch (error) {
        return {
          success: false,
          completed: false,
          duration: Date.now() - startTime,
          data: {
            message: `Sequence error at action: ${childAction.name}`,
            error: error instanceof Error ? error.message : String(error),
            completedActions: results.length,
            totalActions: children.length,
          },
        };
      }
    }

    return {
      success: true,
      completed: true,
      duration: Date.now() - startTime,
      data: {
        message: "Sequence completed successfully",
        completedActions: results.length,
        results,
      },
    };
  }

  /**
   * Execute multiple actions in parallel
   */
  private async executeParallelAction(
    trialId: string,
    action: ActionDefinition,
  ): Promise<ActionExecutionResult> {
    const startTime = Date.now();
    const children = action.parameters.children as
      | ActionDefinition[]
      | undefined;

    if (!children || !Array.isArray(children) || children.length === 0) {
      return {
        success: true,
        completed: true,
        duration: Date.now() - startTime,
        data: { message: "Empty parallel block completed", childCount: 0 },
      };
    }

    // Execute all children in parallel
    const promises = children.map((childAction) =>
      this.executeAction(trialId, childAction).catch((error) => ({
        success: false,
        completed: false,
        duration: 0,
        data: {
          error: error instanceof Error ? error.message : String(error),
          actionName: childAction.name,
        },
      })),
    );

    const results = await Promise.all(promises);
    const allSuccessful = results.every((r) => r.success);

    return {
      success: allSuccessful,
      completed: true,
      duration: Date.now() - startTime,
      data: {
        message: allSuccessful
          ? "All parallel actions completed successfully"
          : "Some parallel actions failed",
        completedActions: results.filter((r) => r.success).length,
        totalActions: children.length,
        results,
      },
    };
  }

  /**
   * Execute an action multiple times (loop)
   */
  private async executeLoopAction(
    trialId: string,
    action: ActionDefinition,
  ): Promise<ActionExecutionResult> {
    const startTime = Date.now();
    const children = action.parameters.children as
      | ActionDefinition[]
      | undefined;
    const iterations = (action.parameters.iterations as number) || 1;

    if (!children || !Array.isArray(children) || children.length === 0) {
      return {
        success: true,
        completed: true,
        duration: Date.now() - startTime,
        data: { message: "Empty loop completed", iterations: 0 },
      };
    }

    const allResults: ActionExecutionResult[][] = [];

    // Execute the children sequence for each iteration
    for (let i = 0; i < iterations; i++) {
      const iterationResults: ActionExecutionResult[] = [];

      for (const childAction of children) {
        try {
          const result = await this.executeAction(trialId, childAction);
          iterationResults.push(result);

          // If any child fails, stop the loop
          if (!result.success) {
            return {
              success: false,
              completed: false,
              duration: Date.now() - startTime,
              data: {
                message: `Loop failed at iteration ${i + 1}, action: ${childAction.name}`,
                completedIterations: i,
                totalIterations: iterations,
                results: allResults,
              },
            };
          }
        } catch (error) {
          return {
            success: false,
            completed: false,
            duration: Date.now() - startTime,
            data: {
              message: `Loop error at iteration ${i + 1}, action: ${childAction.name}`,
              error: error instanceof Error ? error.message : String(error),
              completedIterations: i,
              totalIterations: iterations,
            },
          };
        }
      }

      allResults.push(iterationResults);
    }

    return {
      success: true,
      completed: true,
      duration: Date.now() - startTime,
      data: {
        message: `Loop completed ${iterations} iterations successfully`,
        completedIterations: iterations,
        results: allResults,
      },
    };
  }

  /**
   * Execute branch action - prompts wizard to choose a path
   * Returns the selected option which determines next step routing
   */
  private async executeBranchAction(
    trialId: string,
    action: ActionDefinition,
  ): Promise<ActionExecutionResult> {
    const startTime = Date.now();

    await this.logTrialEvent(trialId, "action_started", {
      actionId: action.id,
      actionType: action.type,
      actionName: action.name,
    });

    try {
      const options = (action.parameters.options as any[]) || [];

      if (options.length === 0) {
        return {
          success: false,
          completed: false,
          duration: Date.now() - startTime,
          data: {
            message: "Branch action has no options configured",
            error: "No routing options available",
          },
        };
      }

      // Branch actions are wizard-driven - they pause execution
      // and wait for wizard to make a choice
      // The wizard UI should display the options and record the selection

      await this.logTrialEvent(trialId, "action_completed", {
        actionId: action.id,
        actionType: action.type,
        actionName: action.name,
        duration: Date.now() - startTime,
        data: {
          message: "Branch action presented to wizard",
          optionsCount: options.length,
          options: options.map((opt) => ({
            label: opt.label,
            nextStepId: opt.nextStepId,
          })),
        },
      });

      return {
        success: true,
        completed: true,
        duration: Date.now() - startTime,
        data: {
          message: "Branch action completed - wizard choice required",
          options,
          // The wizard's selected option will determine the next step
          // This is handled by the trial runner's step navigation logic
        },
      };
    } catch (error) {
      await this.logTrialEvent(trialId, "action_failed", {
        actionId: action.id,
        actionType: action.type,
        actionName: action.name,
        error: error instanceof Error ? error.message : String(error),
      });

      return {
        success: false,
        completed: false,
        duration: Date.now() - startTime,
        data: {
          message: "Branch action failed",
          error: error instanceof Error ? error.message : String(error),
        },
      };
    }
  }
}
