/**
 * WorkflowRun Tool — executes VL workflows via VL-Workflow-Engine (JS)
 *
 * Modes:
 *   "local"    → Execute via isolated JS engine vendor copy (primary path, includes VLCode Tool_* extension)
 *   "remote"   → Same as local (legacy alias, Go engine removed)
 *   "sync"     → Execute synchronously and return structured outputs/checkpoint for workflow-of-workflows orchestration
 *   "validate" → Validate workflow JSON structure via engine.validate()
 *   "status"   → Report engine status (always available)
 */
import { createRequire } from 'module';
import fs from 'fs/promises';
import path from 'path';
import { WORKFLOW_ENGINE_VENDOR_ENTRY } from '../vl/workflow-engine-vendor.js';

const require = createRequire(import.meta.url);
const { Engine } = require(WORKFLOW_ENGINE_VENDOR_ENTRY);

/**
 * @param {Object} config - VL-Code configuration
 */
export function createWorkflowRunTool(config) {
  return {
      description: `Execute a VL workflow. Modes:
- "local": Run workflow via isolated JS engine vendor copy (full spec v3.16 support + direct Tool_* calls into the VLCode tool registry)
- "remote": Alias for local
- "sync": Run a child workflow synchronously and return structured result data (status, filesWritten, variables, checkpoint)
- "validate": Validate workflow JSON structure without executing
- "status": Check workflow engine status and current workflow progress`,
    parameters: {
      type: 'object',
      properties: {
        mode: {
          type: 'string',
          enum: ['remote', 'local', 'sync', 'validate', 'status'],
          description: 'Execution mode',
        },
        workflow_path: {
          type: 'string',
          description: 'Path to workflow JSON file',
        },
        params: {
          type: 'object',
          description: 'Runtime parameters (userRequest, targetLang, etc.)',
        },
        workspace_id: {
          type: 'string',
          description: 'Workspace ID for file scoping (default: project directory name)',
        },
        work_dir: {
          type: 'string',
          description: 'Optional child work directory. Relative paths resolve from the current project root.',
        },
        emit_events: {
          type: 'boolean',
          description: 'When true, forward child workflow events through tool runtime messages (default: true for sync, false otherwise).',
        },
      },
      required: ['mode'],
    },
    execute: async (input, runtime = {}) => {
      switch (input.mode) {
        case 'validate':
          return validateWorkflow(config, input.workflow_path);
        case 'remote':
        case 'local':
          return runWorkflow(config, input, runtime);
        case 'sync':
          return runWorkflowSync(config, input, runtime);
        case 'status':
          const workflowState = config._getWorkflowState?.() || {};
          return JSON.stringify({
            available: true,
            engine: 'vl-workflow-engine (JS)',
            spec: 'v3.16',
            active: !!workflowState.active,
            workflowName: workflowState.workflowName || null,
            nodeStatuses: workflowState.nodeStatuses || {},
            nodeDetails: workflowState.nodeDetails || {},
            timeline: workflowState.timeline || [],
            startedAt: workflowState.startedAt || null,
            finishedAt: workflowState.finishedAt || null,
            lastError: workflowState.lastError || null,
            workDir: workflowState.workDir || config.workDir || '',
            message: 'Shared JS workflow engine — always available',
          }, null, 2);
        default:
          return 'Unknown mode. Use: remote, local, sync, validate, status.';
      }
    },
  };
}

/**
 * Run a workflow via the isolated JS engine vendor copy.
 */
async function loadWorkflowFromPath(config, workflowPath) {
  const fullPath = path.resolve(config.workDir, workflowPath);

  try {
    await fs.access(fullPath);
  } catch {
    throw new Error(`Cannot access workflow file: ${fullPath}`);
  }

  let wfJson;
  try {
    const content = await fs.readFile(fullPath, 'utf-8');
    wfJson = JSON.parse(content);
  } catch (err) {
    throw new Error(`Cannot load workflow: ${err.message}`);
  }

  return { wfJson, fullPath };
}

function resolveWorkflowWorkDir(config, input = {}) {
  const requested = input.work_dir || input.workDir || '';
  if (!requested) return config.workDir;
  return path.isAbsolute(requested) ? requested : path.resolve(config.workDir, requested);
}

function scopeFileInput(baseDir, filePath) {
  if (!filePath || typeof filePath !== 'string') return filePath;
  return path.isAbsolute(filePath) ? filePath : path.resolve(baseDir, filePath);
}

function scopeToolInputForChild(toolName, input, childWorkDir) {
  const scoped = input && typeof input === 'object'
    ? JSON.parse(JSON.stringify(input))
    : input;
  if (!scoped || typeof scoped !== 'object') return scoped;

  switch (toolName) {
    case 'ReadFile':
    case 'WriteFile':
    case 'EditFile':
    case 'VLValidate':
    case 'VLEditSection':
    case 'SectionDiff':
    case 'VLSymbols':
    case 'VLAutoFix':
      if (typeof scoped.file_path === 'string') {
        scoped.file_path = scopeFileInput(childWorkDir, scoped.file_path);
      }
      break;
    case 'Glob':
    case 'Grep':
      if (typeof scoped.path === 'string') {
        scoped.path = scopeFileInput(childWorkDir, scoped.path);
      }
      break;
    case 'Bash':
    case 'RunCommand':
      if (typeof scoped.command === 'string' && scoped.command.trim()) {
        const quoted = JSON.stringify(childWorkDir);
        scoped.command = `cd ${quoted} && ${scoped.command}`;
      }
      break;
    case 'WorkflowRun':
      if (typeof scoped.workflow_path === 'string' && scoped.workflow_path.trim()) {
        scoped.workflow_path = scopeFileInput(childWorkDir, scoped.workflow_path);
      }
      if (typeof scoped.work_dir === 'string' && scoped.work_dir.trim()) {
        scoped.work_dir = scopeFileInput(childWorkDir, scoped.work_dir);
      } else {
        scoped.work_dir = childWorkDir;
      }
      break;
  }

  return scoped;
}

function createScopedChildToolRegistry(baseRegistry, childWorkDir) {
  if (!baseRegistry?.execute) return baseRegistry || null;
  return {
    ...baseRegistry,
    execute(name, input, hooks = {}) {
      return baseRegistry.execute(name, scopeToolInputForChild(name, input, childWorkDir), hooks);
    },
  };
}

async function loadWorkflowFromPathInDir(config, workflowPath, workDir) {
  const candidates = [];
  if (path.isAbsolute(workflowPath)) {
    candidates.push(workflowPath);
  } else {
    const raw = String(workflowPath || '').trim();
    const withJson = raw.endsWith('.json') ? raw : `${raw}.json`;
    const childDir = workDir || config.workDir;
    candidates.push(path.resolve(childDir, raw));
    candidates.push(path.resolve(childDir, withJson));
    candidates.push(path.resolve(childDir, '.vl-code', 'workflows', raw));
    candidates.push(path.resolve(childDir, '.vl-code', 'workflows', withJson));
    candidates.push(path.resolve(config.workDir, raw));
    candidates.push(path.resolve(config.workDir, withJson));
    candidates.push(path.resolve(config.workDir, '.vl-code', 'workflows', raw));
    candidates.push(path.resolve(config.workDir, '.vl-code', 'workflows', withJson));
  }

  let fullPath = '';
  for (const candidate of candidates) {
    try {
      await fs.access(candidate);
      fullPath = candidate;
      break;
    } catch {}
  }
  if (!fullPath) {
    throw new Error(`Cannot access workflow file: ${candidates[0] || workflowPath}`);
  }

  let wfJson;
  try {
    const content = await fs.readFile(fullPath, 'utf-8');
    wfJson = JSON.parse(content);
  } catch (err) {
    throw new Error(`Cannot load workflow: ${err.message}`);
  }

  return { wfJson, fullPath };
}

function pushCapturedEvent(events, type, payload = {}) {
  events.push({
    type,
    ts: new Date().toISOString(),
    ...payload,
  });
  if (events.length > 200) events.splice(0, events.length - 200);
}

function forwardSyncEvent(runtime, workflowName, eventType, payload = {}, enabled = true) {
  if (!enabled) return;
  const message = payload.message
    || payload.title
    || payload.nodeId
    || payload.stepId
    || payload.name
    || eventType;
  runtime.emit?.(eventType, {
    message: `[${workflowName}] ${message}`,
    data: {
      workflowName,
      event: eventType,
      ...payload,
    },
  });
}

/**
 * Run a workflow via the isolated JS engine vendor copy.
 */
async function runWorkflow(config, input, runtime = {}) {
  const { workflow_path, params } = input;
  const childWorkDir = resolveWorkflowWorkDir(config, input);

  if (!workflow_path) {
    return 'Error: workflow_path is required for execution.';
  }

  let wfJson;
  try {
    ({ wfJson } = await loadWorkflowFromPathInDir(config, workflow_path, childWorkDir));
  } catch (err) {
    runtime.error?.('WorkflowRun failed to load workflow', {
      workflow_path,
      error: err.message,
    });
    return err.message;
  }

  // Broadcast helper — sends events to frontend via SSE for live visualization
  const broadcast = config._broadcast || (() => {});
  const setWorkflowState = config._onWorkflowNodeStatus || (() => {});
  const recordWorkflowEvent = config._recordWorkflowEvent || (() => {});

  try {
    const { WorkflowExecutor } = await import('../vl/workflow-executor.js');
    const executor = new WorkflowExecutor({
      apiKey: config.apiKey,
      model: config.model,
      llmProvider: config.llmProvider,
      workDir: childWorkDir,
      cookie: config.cookie,
      toolRegistry: createScopedChildToolRegistry(config.toolRegistry || config._toolRegistry || null, childWorkDir),
    });

    runtime.info?.('WorkflowRun started background workflow', {
      workflowName: wfJson.name,
      workflow_path,
      workDir: childWorkDir,
      stepCount: wfJson.steps?.length || 0,
      mode: input.mode || 'local',
    });

    // Notify frontend: switch to Flow tab + load workflow DAG
    if (config._setActiveExecutor) config._setActiveExecutor(executor);
    if (config._onWorkflowStart) config._onWorkflowStart(workflow_path, { stepCount: wfJson.steps?.length || 0 });
    broadcast({
      type: 'workflow_execution_start',
      workflowName: workflow_path,
      workflow: wfJson,
      stepCount: wfJson.steps?.length || 0,
    });
    broadcast({ type: 'wf_start', workflowName: workflow_path, stepCount: wfJson.steps?.length || 0, model: config.model || 'unknown' });
    recordWorkflowEvent({ type: 'wf_start', workflowName: workflow_path, stepCount: wfJson.steps?.length || 0, model: config.model || 'unknown' });

    // Fire-and-forget: run workflow in background, progress via SSE broadcasts
    executor.execute(wfJson, params || {}, {
      onNodeStart: (info) => {
        setWorkflowState(info.nodeId, 'running', {
          title: info.title,
          type: info.type,
          input: info.input,
          resolvedInputs: info.resolvedInputs,
        });
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'running', title: info.title || info.nodeId });
        broadcast({ type: 'wf_node_start', nodeId: info.nodeId, title: info.title, nodeType: info.type, input: info.input, resolvedInputs: info.resolvedInputs });
        recordWorkflowEvent({ type: 'wf_node_start', nodeId: info.nodeId, status: 'running', title: info.title, nodeType: info.type, input: info.input, resolvedInputs: info.resolvedInputs });
      },
      onNodeDone: (info) => {
        setWorkflowState(info.nodeId, 'done', {
          title: info.title,
          output: info.output,
          outputs: info.outputs,
          selected: info.selected,
          duration_ms: info.duration_ms,
        });
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'done', title: info.title || info.nodeId });
        broadcast({ type: 'wf_node_done', nodeId: info.nodeId, title: info.title, duration_ms: info.duration_ms, output: info.output, outputs: info.outputs, selected: info.selected });
        recordWorkflowEvent({ type: 'wf_node_done', nodeId: info.nodeId, status: 'done', title: info.title, duration_ms: info.duration_ms, output: info.output, outputs: info.outputs, selected: info.selected });
      },
      onNodeError: (info) => {
        setWorkflowState(info.nodeId, 'error', {
          title: info.title,
          type: info.type,
          error: info.error,
          detail: info.detail,
          duration_ms: info.duration_ms,
        });
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'error', error: info.error });
        broadcast({ type: 'wf_node_error', nodeId: info.nodeId, title: info.title, error: info.error, detail: info.detail, duration_ms: info.duration_ms });
        recordWorkflowEvent({ type: 'wf_node_error', nodeId: info.nodeId, status: 'error', title: info.title, nodeType: info.type, error: info.error, detail: info.detail, duration_ms: info.duration_ms });
      },
      onNodeSkipped: (info) => {
        setWorkflowState(info.nodeId, 'skipped', { reason: info.reason || 'condition false' });
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'skipped' });
        broadcast({ type: 'wf_node_skipped', nodeId: info.nodeId });
        recordWorkflowEvent({ type: 'wf_node_skipped', nodeId: info.nodeId, status: 'skipped', reason: info.reason || 'condition false' });
      },
      onPause: (info) => {
        setWorkflowState(info.nodeId, 'paused', info);
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'paused', title: info.title || info.nodeId });
        broadcast({ type: 'wf_pause', ...info });
        recordWorkflowEvent({ type: 'wf_pause', nodeId: info.nodeId, status: 'paused', title: info.title, reason: info.reason });
      },
      onResumed: (info) => {
        setWorkflowState(info.nodeId, 'running', info);
        broadcast({ type: 'workflow_node_update', nodeId: info.nodeId, status: 'running', title: info.title || info.nodeId });
        broadcast({ type: 'wf_resumed', ...info });
        recordWorkflowEvent({ type: 'wf_resumed', nodeId: info.nodeId, status: 'running' });
      },
      onVarsChanged: (nodeId, vars) => {
        broadcast({ type: 'wf_vars', nodeId, vars });
        recordWorkflowEvent({ type: 'wf_vars', nodeId, output: vars });
      },
      onLLMThinking: (info) => {
        broadcast({ type: 'wf_llm_thinking', ...info });
        recordWorkflowEvent({ type: 'wf_llm_thinking', nodeId: info.stepId, text: info.delta });
      },
      onLLMToolUse: (info) => {
        broadcast({ type: 'wf_llm_tool_use', ...info });
        recordWorkflowEvent({ type: 'wf_llm_tool_use', nodeId: info.stepId, name: info.name, input: info.input, tool_use_id: info.tool_use_id });
      },
      onLLMToolResult: (info) => {
        broadcast({ type: 'wf_llm_tool_result', ...info });
        recordWorkflowEvent({ type: 'wf_llm_tool_result', nodeId: info.stepId, tool_use_id: info.tool_use_id, output: info.content, error: info.is_error ? info.content : undefined });
      },
      onLLMDone: (info) => {
        broadcast({ type: 'wf_llm_done', ...info });
        recordWorkflowEvent({ type: 'wf_llm_done', nodeId: info.stepId, model: info.model, usage: info.usage, duration_ms: info.latency_ms });
      },
      onLLMError: (info) => {
        broadcast({ type: 'wf_llm_error', ...info });
        recordWorkflowEvent({ type: 'wf_llm_error', nodeId: info.stepId, error: info.error, detail: info.code, duration_ms: info.latency_ms });
      },
      onToolStart: (info) => {
        broadcast({ type: 'wf_tool_start', ...info });
        recordWorkflowEvent({ type: 'wf_tool_start', nodeId: info.stepId, name: info.name, input: info.input });
      },
      onToolDone: (info) => {
        broadcast({ type: 'wf_tool_done', ...info });
        recordWorkflowEvent({ type: 'wf_tool_done', nodeId: info.stepId, name: info.name, output: info.output });
      },
      onToolError: (info) => {
        broadcast({ type: 'wf_tool_error', ...info });
        recordWorkflowEvent({ type: 'wf_tool_error', nodeId: info.stepId, name: info.name, error: info.error });
      },
      onToolMessage: (info) => {
        broadcast({ type: 'wf_tool_message', ...info });
        recordWorkflowEvent({ type: 'wf_tool_message', nodeId: info.stepId, name: info.name, level: info.level, message: info.message, output: info.data });
      },
      onFileStart: (fp) => {
        broadcast({ type: 'wf_file_start', path: fp });
        recordWorkflowEvent({ type: 'wf_file_start', path: fp });
      },
      onVarChanged: (info) => {
        broadcast({ type: 'wf_var_changed', ...info });
        recordWorkflowEvent({ type: 'wf_var_changed', nodeId: info.stepId, title: info.name, output: info.newValue });
      },
      onFileWritten: (fp) => {
        broadcast({ type: 'file_changed', path: fp });
        broadcast({ type: 'wf_file_done', path: fp });
        recordWorkflowEvent({ type: 'wf_file_done', path: fp });
      },
      onFileChanged: (fp, sizeBytes) => {
        broadcast({ type: 'file_changed', path: fp, size_bytes: sizeBytes });
      },
      onToken: (token) => {
        broadcast({ type: 'wf_token', token });
      },
      onText: (text) => {
        broadcast({ type: 'wf_text', text });
        recordWorkflowEvent({ type: 'wf_text', text });
      },
      onCheckpoint: (cp) => {
        broadcast({ type: 'wf_checkpoint', runID: cp.workflowID, stepID: cp.currentStepID, completedSteps: cp.completedSteps, checkpoint: cp });
        recordWorkflowEvent({ type: 'wf_checkpoint', stepID: cp.currentStepID, completedSteps: cp.completedSteps, runID: cp.workflowID });
      },
      onDone: (info) => {
        if (config._onWorkflowDone) config._onWorkflowDone();
        broadcast({ type: 'workflow_done', workflowName: workflow_path, ...info });
        broadcast({ type: 'wf_done', workflowName: workflow_path, ...info });
        broadcast({ type: 'project_reloaded' });
        recordWorkflowEvent({ type: 'wf_done', workflowName: workflow_path, output: info });
      },
      onError: (msg) => {
        if (config._onWorkflowError) config._onWorkflowError(workflow_path, msg);
        broadcast({ type: 'workflow_error', workflowName: workflow_path, error: msg });
        broadcast({ type: 'wf_error', workflowName: workflow_path, error: msg });
        recordWorkflowEvent({ type: 'wf_error', workflowName: workflow_path, error: msg });
      },
    }).catch(err => {
      if (config._onWorkflowError) config._onWorkflowError(workflow_path, err.message);
      broadcast({ type: 'workflow_error', workflowName: workflow_path, error: err.message });
      broadcast({ type: 'wf_error', workflowName: workflow_path, error: err.message });
      recordWorkflowEvent({ type: 'wf_error', workflowName: workflow_path, error: err.message });
    });

    // Return immediately — workflow runs in background
    return JSON.stringify({
      status: 'started',
      workflowName: wfJson.name,
      stepCount: wfJson.steps?.length || 0,
      message: `Workflow "${wfJson.name}" started. Watch the Flow tab for real-time progress.`,
    }, null, 2);
  } catch (err) {
    return `Execution failed: ${err.message}`;
  }
}

async function runWorkflowSync(config, input, runtime = {}) {
  const { workflow_path, params = {} } = input;
  const childWorkDir = resolveWorkflowWorkDir(config, input);
  if (!workflow_path) {
    return { error: 'workflow_path is required for sync execution.' };
  }

  let wfJson;
  let fullPath;
  try {
    ({ wfJson, fullPath } = await loadWorkflowFromPathInDir(config, workflow_path, childWorkDir));
  } catch (err) {
    runtime.error?.('WorkflowRun sync failed to load workflow', {
      workflow_path,
      error: err.message,
    });
    return { error: err.message };
  }

  const emitEvents = input.emit_events !== false;
  const events = [];
  let doneInfo = null;
  let errorInfo = null;

  try {
    const { WorkflowExecutor } = await import('../vl/workflow-executor.js');
    const executor = new WorkflowExecutor({
      apiKey: config.apiKey,
      model: config.model,
      llmProvider: config.llmProvider,
      workDir: childWorkDir,
      cookie: config.cookie,
      toolRegistry: createScopedChildToolRegistry(config.toolRegistry || config._toolRegistry || null, childWorkDir),
    });

    runtime.info?.('WorkflowRun sync started', {
      workflowName: wfJson.name,
      workflow_path,
      fullPath,
      workDir: childWorkDir,
      stepCount: wfJson.steps?.length || 0,
    });

    await executor.execute(wfJson, params, {
      onNodeStart: (info) => {
        pushCapturedEvent(events, 'node_start', info);
        forwardSyncEvent(runtime, wfJson.name, 'node_start', info, emitEvents);
      },
      onNodeDone: (info) => {
        pushCapturedEvent(events, 'node_done', info);
        forwardSyncEvent(runtime, wfJson.name, 'node_done', info, emitEvents);
      },
      onNodeError: (info) => {
        pushCapturedEvent(events, 'node_error', info);
        forwardSyncEvent(runtime, wfJson.name, 'node_error', info, emitEvents);
      },
      onNodeSkipped: (info) => {
        pushCapturedEvent(events, 'node_skipped', info);
        forwardSyncEvent(runtime, wfJson.name, 'node_skipped', info, emitEvents);
      },
      onPause: (info) => {
        pushCapturedEvent(events, 'pause', info);
        forwardSyncEvent(runtime, wfJson.name, 'pause', info, emitEvents);
      },
      onResumed: (info) => {
        pushCapturedEvent(events, 'resumed', info);
        forwardSyncEvent(runtime, wfJson.name, 'resumed', info, emitEvents);
      },
      onToolStart: (info) => {
        pushCapturedEvent(events, 'tool_start', info);
        forwardSyncEvent(runtime, wfJson.name, 'tool_start', info, emitEvents);
      },
      onToolDone: (info) => {
        pushCapturedEvent(events, 'tool_done', info);
        forwardSyncEvent(runtime, wfJson.name, 'tool_done', info, emitEvents);
      },
      onToolError: (info) => {
        pushCapturedEvent(events, 'tool_error', info);
        forwardSyncEvent(runtime, wfJson.name, 'tool_error', info, emitEvents);
      },
      onToolMessage: (info) => {
        pushCapturedEvent(events, 'tool_message', info);
        forwardSyncEvent(runtime, wfJson.name, 'tool_message', info, emitEvents);
      },
      onLLMThinking: (info) => {
        pushCapturedEvent(events, 'llm_thinking', info);
        forwardSyncEvent(runtime, wfJson.name, 'llm_thinking', info, emitEvents);
      },
      onLLMToolUse: (info) => {
        pushCapturedEvent(events, 'llm_tool_use', info);
        forwardSyncEvent(runtime, wfJson.name, 'llm_tool_use', info, emitEvents);
      },
      onLLMToolResult: (info) => {
        pushCapturedEvent(events, 'llm_tool_result', info);
        forwardSyncEvent(runtime, wfJson.name, 'llm_tool_result', info, emitEvents);
      },
      onLLMDone: (info) => {
        pushCapturedEvent(events, 'llm_done', info);
        forwardSyncEvent(runtime, wfJson.name, 'llm_done', info, emitEvents);
      },
      onLLMError: (info) => {
        pushCapturedEvent(events, 'llm_error', info);
        forwardSyncEvent(runtime, wfJson.name, 'llm_error', info, emitEvents);
      },
      onVarsChanged: (nodeId, vars) => {
        const info = { nodeId, vars };
        pushCapturedEvent(events, 'workflow_vars', info);
        forwardSyncEvent(runtime, wfJson.name, 'workflow_vars', info, emitEvents);
      },
      onVarChanged: (info) => {
        pushCapturedEvent(events, 'var_changed', info);
        forwardSyncEvent(runtime, wfJson.name, 'var_changed', info, emitEvents);
      },
      onFileStart: (filePath) => {
        const info = { path: filePath };
        pushCapturedEvent(events, 'file_start', info);
        forwardSyncEvent(runtime, wfJson.name, 'file_start', info, emitEvents);
      },
      onFileWritten: (filePath) => {
        const info = { path: filePath };
        pushCapturedEvent(events, 'file_done', info);
        forwardSyncEvent(runtime, wfJson.name, 'file_done', info, emitEvents);
      },
      onFileChanged: (filePath, sizeBytes) => {
        const info = { path: filePath, size_bytes: sizeBytes };
        pushCapturedEvent(events, 'file_changed', info);
        forwardSyncEvent(runtime, wfJson.name, 'file_changed', info, emitEvents);
      },
      onToken: (token) => {
        const info = { token };
        pushCapturedEvent(events, 'token', info);
        forwardSyncEvent(runtime, wfJson.name, 'token', info, emitEvents);
      },
      onText: (text) => {
        const info = { text, message: text };
        pushCapturedEvent(events, 'text', info);
        forwardSyncEvent(runtime, wfJson.name, 'text', info, emitEvents);
      },
      onCheckpoint: (cp) => {
        const info = {
          runID: cp.workflowID,
          currentStepID: cp.currentStepID,
          completedSteps: cp.completedSteps,
        };
        pushCapturedEvent(events, 'checkpoint', info);
        forwardSyncEvent(runtime, wfJson.name, 'checkpoint', info, emitEvents);
      },
      onDone: (info) => {
        doneInfo = info;
        pushCapturedEvent(events, 'done', info);
        forwardSyncEvent(runtime, wfJson.name, 'done', info, emitEvents);
      },
      onError: (message) => {
        errorInfo = { error: message };
        pushCapturedEvent(events, 'error', errorInfo);
        forwardSyncEvent(runtime, wfJson.name, 'error', errorInfo, emitEvents);
      },
    });

    const checkpoint = executor.getCheckpoint();
    const result = {
      ok: !errorInfo,
      status: errorInfo ? 'failed' : 'completed',
      mode: 'sync',
      workflowName: wfJson.name || path.basename(workflow_path),
      workflowPath: workflow_path,
      workDir: childWorkDir,
      stepCount: wfJson.steps?.length || 0,
      runID: executor.getRunID(),
      filesWritten: [...executor.filesWritten],
      variables: checkpoint?.variables || {},
      checkpoint: checkpoint || null,
      completedSteps: checkpoint?.completedSteps || [],
      eventCount: events.length,
      events,
      done: doneInfo,
    };
    if (errorInfo) result.error = errorInfo.error;

    runtime.info?.('WorkflowRun sync finished', {
      workflowName: result.workflowName,
      status: result.status,
      runID: result.runID || null,
      filesWritten: result.filesWritten.length,
      eventCount: result.eventCount,
    });

    return { result };
  } catch (err) {
    runtime.error?.('WorkflowRun sync crashed', {
      workflow_path,
      error: err.message,
    });
    return {
      error: err.message,
      result: {
        ok: false,
        status: 'failed',
        mode: 'sync',
        workflowName: wfJson?.name || path.basename(workflow_path),
        workflowPath: workflow_path,
        workDir: childWorkDir,
        stepCount: wfJson?.steps?.length || 0,
        runID: null,
        filesWritten: [],
        variables: {},
        checkpoint: null,
        completedSteps: [],
        eventCount: events.length,
        events,
        error: err.message,
      },
    };
  }
}

/**
 * Format execution result into a consistent JSON string.
 */
function formatResult(events, filesWritten, error, executionMode) {
  const stepsDone = events.filter((e) => e.type === 'step_done').length;
  const stepsError = events.filter((e) => e.type === 'step_error').length;

  const result = {
    status: error ? 'failed' : 'completed',
    executionMode,
    stepsDone,
    stepsError,
    eventCount: events.length,
    filesWritten,
  };

  if (error) result.error = error;

  result.events = events.slice(0, 30);
  if (events.length > 30) {
    result.summary = `... and ${events.length - 30} more events`;
  }

  return JSON.stringify(result, null, 2);
}

/**
 * Validate workflow JSON structure using VL-Workflow-Engine's built-in validator.
 */
async function validateWorkflow(config, workflowPath) {
  if (!workflowPath) return 'Error: workflow_path is required for validation.';

  const fullPath = path.resolve(config.workDir, workflowPath);
  let content;
  try {
    content = await fs.readFile(fullPath, 'utf-8');
  } catch (err) {
    return `Cannot read workflow file: ${err.message}`;
  }

  let wf;
  try {
    wf = JSON.parse(content);
  } catch {
    return 'Invalid JSON in workflow file.';
  }

  // Use VL-Workflow-Engine's built-in validator
  const engine = new Engine(wf);
  const errors = engine.validate();

  if (errors.length === 0) {
    return `Workflow "${wf.name}" (v${wf.version}): VALID (${wf.steps?.length || 0} steps)`;
  }

  const report = [`Workflow "${wf.name}" validation:`];
  for (const e of errors) report.push(`  ERROR: ${e}`);
  return report.join('\n');
}
