import fs from 'fs/promises';
import path from 'path';

import { WorkflowExecutor } from './src/vl/workflow-executor.js';
import { ToolRegistry } from './src/core/tool-registry.js';
import { createReadFileTool } from './src/tools/read-file.js';
import { createWriteFileTool } from './src/tools/write-file.js';
import { createBashTool } from './src/tools/bash.js';
import { createWorkflowRunTool } from './src/tools/workflow-run.js';

let passed = 0;
let failed = 0;
function test(name, fn) {
  return Promise.resolve()
    .then(fn)
    .then(() => {
      console.log(`  ✓ ${name}`);
      passed++;
    })
    .catch((err) => {
      console.log(`  ✗ ${name}: ${err.message}`);
      failed++;
    });
}
function assertEqual(a, b, msg) {
  if (a !== b) throw new Error(msg || `Expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}`);
}
function assert(cond, msg) {
  if (!cond) throw new Error(msg || 'Assertion failed');
}

const rootDir = '/tmp/vlcode-lite-subflow-runtime-test';
const simpleChildDir = path.join(rootDir, 'simple-child-space');
const dynamicChildDir = path.join(rootDir, 'sandboxes', 'branch-a');
const toolChildDir = path.join(rootDir, 'tool-child-space');

console.log('\n── Workflow Subflow Runtime Regression ──');

await fs.rm(rootDir, { recursive: true, force: true });
await fs.mkdir(simpleChildDir, { recursive: true });
await fs.mkdir(dynamicChildDir, { recursive: true });
await fs.mkdir(toolChildDir, { recursive: true });

const registry = new ToolRegistry();
const toolConfig = {
  workDir: rootDir,
  toolRegistry: registry,
  _toolRegistry: registry,
  model: 'claude-opus-4-6',
  llmProvider: 'anthropic',
};
registry.register('ReadFile', createReadFileTool({ workDir: rootDir }));
registry.register('WriteFile', createWriteFileTool({ workDir: rootDir }));
registry.register('Bash', createBashTool({ workDir: rootDir }));
registry.register('WorkflowRun', createWorkflowRunTool(toolConfig));

const simpleChildWorkflow = {
  version: '3.16',
  name: 'SimpleChildWorkflow',
  steps: [
    { id: 'Set_010_Result', target: '$result', value: '="child:" + goal', next: 'Write_020_Result' },
    { id: 'Write_020_Result', target: '="Artifacts/simple.txt"', value: '=$result', next: 'Stop_End' },
    { id: 'Stop_End' },
  ],
};
await fs.writeFile(path.join(simpleChildDir, 'simple-child.json'), JSON.stringify(simpleChildWorkflow, null, 2), 'utf8');

await test('Subflow_* alias executes WorkflowRun sync and maps child outputs', async () => {
  const parentWorkflow = {
    version: '3.16',
    name: 'ParentSubflowAlias',
    steps: [
      {
        id: 'Subflow_010_RunChild',
        workflow_path: 'simple-child.json',
        work_dir: 'simple-child-space',
        params: { goal: '="alpha"' },
        out: {
          '$childStatus': '=_result.status',
          '$childValue': '=_result.variables["$result"]',
          '$childWorkDir': '=_result.workDir',
        },
        next: 'Stop_End',
      },
      { id: 'Stop_End' },
    ],
  };

  const toolMessages = [];
  const executor = new WorkflowExecutor({
    workDir: rootDir,
    model: 'claude-opus-4-6',
    toolRegistry: registry,
  });

  await executor.execute(parentWorkflow, {}, {
    onToolMessage: (info) => toolMessages.push(info),
  });

  assertEqual(executor._ctx.variables.$childStatus, 'completed');
  assertEqual(executor._ctx.variables.$childValue, 'child:alpha');
  assert(String(executor._ctx.variables.$childWorkDir || '').endsWith('/simple-child-space'), 'child workDir should point at isolated subdir');
  assertEqual(
    await fs.readFile(path.join(simpleChildDir, 'Artifacts', 'simple.txt'), 'utf8'),
    'child:alpha'
  );
  assert(toolMessages.some((info) => info.name === 'WorkflowRun' && info.data?.event === 'node_start'), 'missing forwarded child node_start event');
  assert(toolMessages.some((info) => info.name === 'WorkflowRun' && info.data?.event === 'done'), 'missing forwarded child done event');
});

await test('tools can create a child workflow and run it inside another subspace', async () => {
  const dynamicChildWorkflow = {
    version: '3.16',
    name: 'DynamicExploreChild',
    steps: [
      { id: 'Set_010_Result', target: '$result', value: '="branch=" + branch + ";seed=" + seed', next: 'Write_020_Result' },
      { id: 'Write_020_Result', target: '="Artifacts/explore.txt"', value: '=$result', next: 'Stop_End' },
      { id: 'Stop_End' },
    ],
  };
  const childJsonText = JSON.stringify(dynamicChildWorkflow);
  const parentWorkflow = {
    version: '3.16',
    name: 'DynamicSubflowExploration',
    steps: [
      { id: 'Set_005_SpawnExplore', target: '$spawnExplore', value: '=true', next: 'Tool_020_WriteChild' },
      {
        id: 'Tool_020_WriteChild',
        tool: 'WriteFile',
        if: '=$spawnExplore',
        input: {
          file_path: '="sandboxes/branch-a/generated-child.json"',
          content: childJsonText,
        },
        next: 'Subflow_030_RunExplore',
      },
      {
        id: 'Subflow_030_RunExplore',
        if: '=$spawnExplore',
        workflow_path: '="generated-child.json"',
        work_dir: '="sandboxes/branch-a"',
        params: {
          seed: '="beta"',
          branch: '="branch-a"',
        },
        out: {
          '$dynamicStatus': '=_result.status',
          '$dynamicArtifact': '=_result.variables["$result"]',
          '$dynamicFiles': '=_result.filesWritten',
        },
        next: 'Tool_040_ReadExplore',
      },
      {
        id: 'Tool_040_ReadExplore',
        tool: 'ReadFile',
        if: '=$spawnExplore',
        input: {
          file_path: '="sandboxes/branch-a/Artifacts/explore.txt"',
        },
        out: {
          '$artifactText': '=_result',
        },
        next: 'Stop_End',
      },
      { id: 'Stop_End' },
    ],
  };

  const toolStarts = [];
  const toolMessages = [];
  const executor = new WorkflowExecutor({
    workDir: rootDir,
    model: 'claude-opus-4-6',
    toolRegistry: registry,
  });

  await executor.execute(parentWorkflow, {}, {
    onToolStart: (info) => toolStarts.push(info),
    onToolMessage: (info) => toolMessages.push(info),
  });

  const childWorkflowPath = path.join(dynamicChildDir, 'generated-child.json');
  const childArtifactPath = path.join(dynamicChildDir, 'Artifacts', 'explore.txt');
  assertEqual(executor._ctx.variables.$dynamicStatus, 'completed');
  assertEqual(executor._ctx.variables.$dynamicArtifact, 'branch=branch-a;seed=beta');
  assert(String(executor._ctx.variables.$artifactText || '').includes('branch=branch-a;seed=beta'), 'parent should read child artifact back');
  assert((executor._ctx.variables.$dynamicFiles || []).some((fp) => String(fp).includes('Artifacts/explore.txt')), 'child filesWritten should include explore artifact');
  assertEqual(await fs.readFile(childArtifactPath, 'utf8'), 'branch=branch-a;seed=beta');
  assert(await fs.readFile(childWorkflowPath, 'utf8'), 'generated child workflow should exist');
  let rootArtifactMissing = false;
  try {
    await fs.access(path.join(rootDir, 'Artifacts', 'explore.txt'));
  } catch {
    rootArtifactMissing = true;
  }
  assert(rootArtifactMissing, 'child artifact should remain isolated in child subspace');
  assert(toolStarts.some((info) => info.stepId === 'Tool_020_WriteChild' && info.name === 'WriteFile'), 'missing dynamic child WriteFile tool start');
  assert(toolStarts.some((info) => info.stepId === 'Subflow_030_RunExplore' && info.name === 'WorkflowRun'), 'missing subflow WorkflowRun tool start');
  assert(toolMessages.some((info) => info.stepId === 'Subflow_030_RunExplore' && info.data?.event === 'node_start'), 'missing forwarded child node_start');
  assert(toolMessages.some((info) => info.stepId === 'Subflow_030_RunExplore' && info.data?.event === 'done'), 'missing forwarded child done');
});

await test('child workflows scope file and bash tools to their own work_dir', async () => {
  const toolChildWorkflow = {
    version: '3.16',
    name: 'ToolScopedChild',
    steps: [
      {
        id: 'Tool_010_WriteNote',
        tool: 'WriteFile',
        input: {
          file_path: '="Artifacts/note.txt"',
          content: '="note:" + seed',
        },
        next: 'Tool_020_ReadNote',
      },
      {
        id: 'Tool_020_ReadNote',
        tool: 'ReadFile',
        input: {
          file_path: '="Artifacts/note.txt"',
        },
        out: {
          '$noteText': '=_result',
        },
        next: 'Tool_030_Pwd',
      },
      {
        id: 'Tool_030_Pwd',
        tool: 'Bash',
        input: {
          command: '="pwd"',
        },
        out: {
          '$pwdText': '=_result',
        },
        next: 'Stop_End',
      },
      { id: 'Stop_End' },
    ],
  };
  await fs.writeFile(path.join(toolChildDir, 'tool-child.json'), JSON.stringify(toolChildWorkflow, null, 2), 'utf8');

  const parentWorkflow = {
    version: '3.16',
    name: 'ScopedToolChildParent',
    steps: [
      {
        id: 'Subflow_010_RunToolChild',
        workflow_path: 'tool-child.json',
        work_dir: 'tool-child-space',
        params: { seed: '="gamma"' },
        out: {
          '$childNote': '=_result.variables["$noteText"]',
          '$childPwd': '=_result.variables["$pwdText"]',
        },
        next: 'Stop_End',
      },
      { id: 'Stop_End' },
    ],
  };

  const executor = new WorkflowExecutor({
    workDir: rootDir,
    model: 'claude-opus-4-6',
    toolRegistry: registry,
  });

  await executor.execute(parentWorkflow, {}, {});

  const childNotePath = path.join(toolChildDir, 'Artifacts', 'note.txt');
  assert(String(executor._ctx.variables.$childNote || '').includes('note:gamma'), 'child note should be returned to parent');
  assert(String(executor._ctx.variables.$childPwd || '').trim() === toolChildDir, 'bash pwd should execute inside child work_dir');
  assertEqual(await fs.readFile(childNotePath, 'utf8'), 'note:gamma');
  let rootNoteMissing = false;
  try {
    await fs.access(path.join(rootDir, 'Artifacts', 'note.txt'));
  } catch {
    rootNoteMissing = true;
  }
  assert(rootNoteMissing, 'root workspace should not receive child note artifact');
});

console.log(`\n── Results ──\n\n  ${passed} passed, ${failed} failed\n`);
process.exit(failed > 0 ? 1 : 0);
