import { createHash } from "node:crypto";
import { parseGrl } from "../grl/parser/index.js";
import {
  buildMotionContext,
  compileGrlDataDeclaration,
  compileGrlConstantValue,
  compileGrlTargetDeclaration,
  compileMotionToKdlRequest,
  compilePathToPlanRequest,
  compileSemanticProgram,
  analyzeProcFunctionSemantics,
  parseControlFlowStatements,
  parseProcedureMotionInstructions
} from "../grl/semantic/index.js";
import { postProcessAllBrands } from "../grl/post/index.js";
import { importBrandProgram } from "../importers/index.js";
import { VirtualController, type RuntimeStepResult, type VirtualControllerSnapshot } from "../controller/index.js";
import { parseIoReferenceText } from "../runtime/index.js";
import type { IoScriptStep } from "../runtime/ioRuntime.js";
import type {
  GrlDataDeclaration,
  GrlPathDeclaration,
  GrlProcedureDeclaration,
  GrlTargetDeclaration,
  GrlTopLevelDeclaration
} from "../grl/ast/index.js";
import type {
  MotionDiagnostic,
  PathPlanRequest,
  PathPlanResult,
  TrajectoryPoint,
  TrajectoryResult
} from "../kdl/types.js";
import {
  ABB120_ROBOT_FIXTURE,
  ABB_IRB120_ZERO_JOINTS
} from "../fixtures/abb120.js";

export type Abb120SpecCoverageLevel = "Runtime" | "Static" | "Post";
export type Abb120SpecExpectedStatus = "pass" | "diagnostic";

export interface Abb120SpecManifestProgram {
  program_id: string;
  file: string;
  coverage_level: Abb120SpecCoverageLevel;
  spec_sections: number[];
  expected_status: Abb120SpecExpectedStatus;
  expected_diagnostics: string[];
}

export interface Abb120SpecCoverageItem {
  spec_section: number;
  coverage_level: string;
  program_ids: string[];
}

export interface Abb120SpecManifest {
  suite_id: string;
  robot_id: string;
  spec_source: string;
  program_root: string;
  runtime_commands: string[];
  programs: Abb120SpecManifestProgram[];
  coverage_matrix: Abb120SpecCoverageItem[];
}

export interface Abb120SpecProgramSource {
  program_id: string;
  file: string;
  text: string;
}

export interface Abb120SpecCommandTrace {
  command: "loadProgram" | "start" | "stepInto" | "hold" | "resume" | "stepMotion" | "stop" | "resetFault";
  before?: string;
  after?: string;
  result?: RuntimeStepResult["status"] | "ok";
  diagnostics: MotionDiagnostic[];
}

export interface Abb120SpecProgramResult {
  program_id: string;
  file: string;
  coverage_level: Abb120SpecCoverageLevel;
  expected_status: Abb120SpecExpectedStatus;
  status: "pass" | "diagnostic" | "fail";
  input_hash: string;
  spec_sections: number[];
  diagnostics: MotionDiagnostic[];
  compile: {
    parsed: boolean;
    moduleName?: string;
    astKind?: "Program";
    semanticChecks: string[];
    sourceMapEntries: number;
    kdlMotionRequests: number;
    kdlPathRequests: number;
    symbolCount: number;
    procedureCount: number;
    pathCount: number;
    operationCount: number;
  };
  controller?: {
    commands: Abb120SpecCommandTrace[];
    snapshot: VirtualControllerSnapshot;
  };
  motionQueue?: VirtualControllerSnapshot["motion"];
  trace?: VirtualControllerSnapshot["runtime"]["trace"];
  io?: VirtualControllerSnapshot["io"];
  trajectory?: {
    sampleCount: number;
    duration: number;
    startJoints: number[];
    endJoints: number[];
    frames: Array<{
      index: number;
      time: number;
      motion: string;
      segmentId?: string;
      targetId?: string;
      joints: number[];
      tcp: {
        position: number[];
        quaternion: number[];
      };
      sourceMap?: MotionDiagnostic["sourceMap"];
      diagnostics: MotionDiagnostic[];
    }>;
    diagnostics: MotionDiagnostic[];
  };
  post?: {
    filenames: string[];
    report: Array<{ code: string; severity: string; message: string; brand?: string }>;
  };
  roundtrip?: {
    status: "pass" | "warn" | "fail";
    diagnostics: MotionDiagnostic[];
  };
  artifacts: Record<string, string>;
}

export interface Abb120SpecSuiteJob {
  job_id: string;
  suite_id: string;
  robot_id: string;
  robot: typeof ABB120_ROBOT_FIXTURE;
  git_revision: string;
  manifest_hash: string;
  coverage: {
    specSections: number[];
    coveredSections: number[];
    missingSections: number[];
    runtimePrograms: number;
    staticPrograms: number;
    postPrograms: number;
  };
  programs: Abb120SpecProgramResult[];
  artifacts: {
    root: string;
    manifestJson: string;
    jobJson: string;
    reportJson: string;
    reportHtml: string;
    desktopScreenshot: string;
    mobileScreenshot: string;
    uiEvidenceJson: string;
  };
}

export function runAbb120SpecSuite(
  manifest: Abb120SpecManifest,
  programs: Abb120SpecProgramSource[],
  options: { now?: string; gitRevision?: string; outputRoot?: string } = {}
): Abb120SpecSuiteJob {
  const now = options.now ?? new Date().toISOString();
  const shortHash = stableHash(
    JSON.stringify({
      suite_id: manifest.suite_id,
      programs: programs.map((program) => [program.program_id, stableHash(program.text).slice(0, 12)])
    })
  ).slice(0, 8);
  const stamp = now.replace(/[-:TZ.]/g, "").slice(0, 14);
  const job_id = `W2-JOB-${stamp}-${shortHash}`;
  const outputRoot = options.outputRoot ?? `kdl-wasm/web/test-results/abb120-spec/${job_id}`;
  const programById = new Map(programs.map((program) => [program.program_id, program]));
  const results = manifest.programs.map((entry) => runProgram(entry, programById.get(entry.program_id), job_id));
  const coveredSections = [...new Set(manifest.coverage_matrix.flatMap((item) => item.spec_section))].sort((a, b) => a - b);
  const expectedSections = Array.from({ length: 22 }, (_value, index) => index + 4);

  return {
    job_id,
    suite_id: manifest.suite_id,
    robot_id: manifest.robot_id,
    robot: ABB120_ROBOT_FIXTURE,
    git_revision: options.gitRevision ?? "working-tree",
    manifest_hash: stableHash(JSON.stringify(manifest)),
    coverage: {
      specSections: expectedSections,
      coveredSections,
      missingSections: expectedSections.filter((section) => !coveredSections.includes(section)),
      runtimePrograms: results.filter((result) => result.coverage_level === "Runtime").length,
      staticPrograms: results.filter((result) => result.coverage_level === "Static").length,
      postPrograms: results.filter((result) => result.coverage_level === "Post").length
    },
    programs: results,
    artifacts: {
      root: outputRoot,
      manifestJson: `${outputRoot}/manifest.json`,
      jobJson: `${outputRoot}/job.json`,
      reportJson: `${outputRoot}/report.json`,
      reportHtml: `${outputRoot}/report.html`,
      desktopScreenshot: `${outputRoot}/screenshots/virtual-controller-desktop.png`,
      mobileScreenshot: `${outputRoot}/screenshots/virtual-controller-mobile.png`,
      uiEvidenceJson: `${outputRoot}/screenshots/evidence.json`
    }
  };
}

function runProgram(
  entry: Abb120SpecManifestProgram,
  source: Abb120SpecProgramSource | undefined,
  jobId: string
): Abb120SpecProgramResult {
  const diagnostics: MotionDiagnostic[] = [];
  const artifacts = artifactPaths(jobId, entry.program_id);
  if (!source) {
    return failedResult(entry, "", [{ severity: "error", code: "W2_PROGRAM_SOURCE_MISSING", message: `Missing source for ${entry.program_id}` }], artifacts);
  }

  try {
    const ast = parseGrl(source.text);
    const ir = compileSemanticProgram(ast, {
      startJoints: [...ABB_IRB120_ZERO_JOINTS],
      sampleTime: 0.02
    });
    diagnostics.push(...ir.diagnostics);
    const compile = {
      parsed: true,
      moduleName: ir.moduleName,
      astKind: ast.kind,
      semanticChecks: ir.semanticChecks,
      sourceMapEntries: ir.sourceMap.length,
      kdlMotionRequests: ir.kdlBridge.motionRequests.length,
      kdlPathRequests: ir.kdlBridge.pathRequests.length,
      symbolCount: ir.symbols.length,
      procedureCount: ir.procedures.length,
      pathCount: ir.paths.length,
      operationCount: ir.operations.length
    };

    if (entry.coverage_level === "Runtime") {
      const runtime = runController(ir, entry.program_id);
      const plannedQueueResults = runtime.snapshot.motion?.items
        .filter((item) => item.request.segments.length > 0)
        .map((item) => planPathFixture(item.request));
      diagnostics.push(
        ...runtime.snapshot.state.diagnostics,
        ...runtime.snapshot.io.diagnostics,
        ...(runtime.snapshot.bridge?.diagnostics ?? []),
        ...(plannedQueueResults ?? []).flatMap((result) => result.diagnostics)
      );
      addExpectationDiagnostics(entry, diagnostics);
      return {
        ...baseResult(entry, source.text, diagnostics, artifacts),
        status: resultStatus(entry, diagnostics),
        compile,
        controller: {
          commands: runtime.commands,
          snapshot: runtime.snapshot
        },
        motionQueue: runtime.snapshot.motion,
        trace: runtime.snapshot.runtime.trace,
        io: runtime.snapshot.io,
        trajectory: summarizeTrajectory(plannedQueueResults),
        post: postSummary(ir),
        artifacts
      };
    }

    if (entry.coverage_level === "Post") {
      const posted = postSummary(ir);
      const roundtrip = roundtripSummary(ir.moduleName, posted);
      diagnostics.push(...posted.report.map((issue) => ({
        severity: issue.severity as MotionDiagnostic["severity"],
        code: issue.code,
        message: issue.message,
        ...(issue.brand ? { data: { brand: issue.brand } } : {})
      })));
      diagnostics.push(...roundtrip.diagnostics);
      addExpectationDiagnostics(entry, diagnostics);
      return {
        ...baseResult(entry, source.text, diagnostics, artifacts),
        status: resultStatus(entry, diagnostics),
        compile,
        post: posted,
        roundtrip,
        artifacts
      };
    }

    addExpectationDiagnostics(entry, diagnostics);
    return {
      ...baseResult(entry, source.text, diagnostics, artifacts),
      status: resultStatus(entry, diagnostics),
      compile,
      artifacts
    };
  } catch (error) {
    diagnostics.push(...diagnosticsFromError(error, entry.program_id));
    try {
      diagnostics.push(...collectStaticPreflightDiagnostics(parseGrl(source.text)));
    } catch {
      // Parse failures are already represented by the primary diagnostic.
    }
    addExpectationDiagnostics(entry, diagnostics);
    return {
      ...baseResult(entry, source.text, diagnostics, artifacts),
      status: "diagnostic",
      compile: {
        parsed: false,
        semanticChecks: [],
        sourceMapEntries: 0,
        kdlMotionRequests: 0,
        kdlPathRequests: 0,
        symbolCount: 0,
        procedureCount: 0,
        pathCount: 0,
        operationCount: 0
      },
      artifacts
    };
  }
}

function collectStaticPreflightDiagnostics(ast: ReturnType<typeof parseGrl>): MotionDiagnostic[] {
  const diagnostics: MotionDiagnostic[] = [];
  const seen = new Set<string>();
  for (const declaration of ast.module.declarations) {
    const name = "name" in declaration ? declaration.name : undefined;
    if (!name) {
      continue;
    }
    if (seen.has(name)) {
      diagnostics.push({
        severity: "error",
        code: "GRL_SYMBOL_DUPLICATE",
        message: `Duplicate symbol ${name}`,
        sourceMap: {
          line: declaration.range.start.line,
          column: declaration.range.start.column
        }
      });
    }
    seen.add(name);
  }

  const dataAndTargets = ast.module.declarations.filter(isDataOrTargetDeclaration);
  const motionContext = safeBuildMotionContext(dataAndTargets, diagnostics);
  const pathDeclarations = new Map(
    ast.module.declarations
      .filter((declaration): declaration is GrlPathDeclaration => declaration.kind === "PathDeclaration")
      .map((path) => [path.name, path])
  );
  collectThrownDiagnostics(diagnostics, () => {
    diagnostics.push(...analyzeProcFunctionSemantics(ast.module.declarations).diagnostics);
  });
  diagnostics.push(...collectRoutinePreflightDiagnostics(ast.module.declarations));
  diagnostics.push(...collectControlFlowPreflightDiagnostics(ast.module.declarations));

  for (const declaration of ast.module.declarations) {
    if (declaration.kind === "DataDeclaration") {
      collectThrownDiagnostics(diagnostics, () => compileGrlDataDeclaration(declaration, { symbols: motionContext.constants }));
    } else if (declaration.kind === "TargetDeclaration") {
      collectThrownDiagnostics(diagnostics, () => compileGrlTargetDeclaration(declaration, { symbols: motionContext.constants }));
    } else if (declaration.kind === "PathDeclaration") {
      collectThrownDiagnostics(diagnostics, () =>
        compilePathToPlanRequest(declaration, motionContext, {
          startJoints: [...ABB_IRB120_ZERO_JOINTS],
          sampleTime: 0.02
        })
      );
    } else if (declaration.kind === "ProcedureDeclaration") {
      collectThrownDiagnostics(diagnostics, () => parseControlFlowStatements(declaration.bodyTokens));
      collectThrownDiagnostics(diagnostics, () => {
        const motions = parseProcedureMotionInstructions(declaration, cloneMotionContextForSuite(motionContext));
        for (const instruction of motions) {
          const request = compileMotionToKdlRequest(instruction, {
            startJoints: [...ABB_IRB120_ZERO_JOINTS],
            sampleTime: 0.02
          });
          const segment = "via" in request
            ? {
                id: `${declaration.name}:${instruction.kind}`,
                motion: instruction.kind,
                via: request.via,
                target: request.target,
                speed: request.speed,
                zone: request.zone,
                ...(request.sourceMap ? { sourceMap: request.sourceMap } : {})
              }
            : {
                id: `${declaration.name}:${instruction.kind}`,
                motion: instruction.kind,
                target: request.target,
                speed: request.speed,
                zone: request.zone,
                ...(request.sourceMap ? { sourceMap: request.sourceMap } : {})
              };
          diagnostics.push(...planPathFixture({
            startJoints: request.startJoints,
            sampleTime: request.sampleTime,
            segments: [segment]
          }).diagnostics);
        }
      });
    } else if (declaration.kind === "OperationDeclaration" && !pathDeclarations.has(declaration.pathName)) {
      diagnostics.push({
        severity: "error",
        code: "GRL_OPERATION_PATH_NOT_FOUND",
        message: `Operation ${declaration.name} references unknown path ${declaration.pathName}`,
        sourceMap: {
          line: declaration.range.start.line,
          column: declaration.range.start.column
        }
      });
    }
  }

  return diagnostics;
}

function safeBuildMotionContext(
  declarations: Array<GrlDataDeclaration | GrlTargetDeclaration>,
  diagnostics: MotionDiagnostic[]
): ReturnType<typeof buildMotionContext> {
  try {
    return buildMotionContext(declarations);
  } catch {
    const incremental = buildMotionContext([]);
    for (const declaration of declarations) {
      try {
        if (declaration.kind === "DataDeclaration") {
          const compiled = compileGrlDataDeclaration(declaration, { symbols: incremental.constants });
          if (declaration.storage === "const") {
            try {
              incremental.constants.set(declaration.name, compileGrlConstantValue(declaration.initializer));
            } catch {
              // Structured declarations are not scalar constants.
            }
          }
          if (compiled.typeName === "speed") {
            incremental.speeds.set(compiled.name, compiled.value as never);
          } else if (compiled.typeName === "zone") {
            incremental.zones.set(compiled.name, compiled.value as never);
          } else if (compiled.typeName === "tool") {
            incremental.tools.set(compiled.name, (compiled.value as { tcp?: unknown }).tcp as never);
          } else if (compiled.typeName === "frame") {
            incremental.frames.set(compiled.name, (compiled.value as { origin?: unknown }).origin as never);
          }
        } else {
          const compiled = compileGrlTargetDeclaration(declaration, { symbols: incremental.constants });
          incremental.targets.set(compiled.name, compiled.target);
        }
      } catch (error) {
        diagnostics.push(...diagnosticsFromError(error, declaration.kind));
      }
    }
    return incremental;
  }
}

function isDataOrTargetDeclaration(
  declaration: GrlTopLevelDeclaration
): declaration is GrlDataDeclaration | GrlTargetDeclaration {
  return declaration.kind === "DataDeclaration" || declaration.kind === "TargetDeclaration";
}

function cloneMotionContextForSuite(context: ReturnType<typeof buildMotionContext>): ReturnType<typeof buildMotionContext> {
  return {
    targets: context.targets,
    speeds: context.speeds,
    zones: context.zones,
    tools: context.tools,
    frames: context.frames,
    constants: context.constants,
    ...(context.currentSpeed ? { currentSpeed: context.currentSpeed } : {}),
    ...(context.currentZone ? { currentZone: context.currentZone } : {}),
    ...(context.currentTool ? { currentTool: context.currentTool } : {}),
    ...(context.currentFrame ? { currentFrame: context.currentFrame } : {})
  };
}

function collectThrownDiagnostics(diagnostics: MotionDiagnostic[], action: () => unknown): void {
  try {
    action();
  } catch (error) {
    diagnostics.push(...diagnosticsFromError(error, "static-preflight"));
  }
}

function collectRoutinePreflightDiagnostics(declarations: GrlTopLevelDeclaration[]): MotionDiagnostic[] {
  const diagnostics: MotionDiagnostic[] = [];
  const routineNames = new Set(
    declarations
      .filter((declaration): declaration is GrlProcedureDeclaration => declaration.kind === "ProcedureDeclaration")
      .map((declaration) => declaration.name)
  );

  for (const declaration of declarations) {
    if (declaration.kind !== "ProcedureDeclaration" && declaration.kind !== "FunctionDeclaration") {
      continue;
    }
    const isFunction = declaration.kind === "FunctionDeclaration";
    for (let index = 0; index < declaration.bodyTokens.length; index += 1) {
      const token = declaration.bodyTokens[index]!;
      if (token.raw === "call") {
        const target = declaration.bodyTokens[index + 1];
        if (target && !routineNames.has(target.raw)) {
          diagnostics.push({
            severity: "error",
            code: "GRL_CALL_TARGET_NOT_FOUND",
            message: `Unknown call target ${target.raw}`,
            sourceMap: {
              line: token.range.start.line,
              column: token.range.start.column
            }
          });
        }
      }
      if (isFunction && ["movej", "movel", "movec", "wait", "pulse", "run_path", "run_operation"].includes(token.raw)) {
        diagnostics.push({
          severity: "error",
          code: "GRL_FUNC_SIDE_EFFECT",
          message: `Function ${declaration.name} cannot execute ${token.raw}`,
          sourceMap: {
            line: token.range.start.line,
            column: token.range.start.column
          }
        });
      }
    }
  }

  return diagnostics;
}

function collectControlFlowPreflightDiagnostics(declarations: GrlTopLevelDeclaration[]): MotionDiagnostic[] {
  const diagnostics: MotionDiagnostic[] = [];
  for (const declaration of declarations) {
    if (declaration.kind !== "ProcedureDeclaration") {
      continue;
    }
    const loopDepthByLine = new Map<number, number>();
    const switchDepthByLine = new Map<number, number>();
    const labels = new Set<string>();
    const jumps: Array<{ label: string; line: number; column: number }> = [];
    const switchCaseValues = new Map<number, Set<string>>();
    const switchStack: number[] = [];
    let loopDepth = 0;
    let switchDepth = 0;
    let blockDepth = 0;

    for (let index = 0; index < declaration.bodyTokens.length; index += 1) {
      const token = declaration.bodyTokens[index]!;
      if (token.range.start.column === 5 || !loopDepthByLine.has(token.range.start.line)) {
        loopDepthByLine.set(token.range.start.line, loopDepth);
        switchDepthByLine.set(token.range.start.line, switchDepth);
      }

      if (token.raw === "while" || token.raw === "for") {
        loopDepth += 1;
        blockDepth += 1;
        continue;
      }
      if (token.raw === "switch") {
        switchDepth += 1;
        blockDepth += 1;
        switchStack.push(blockDepth);
        switchCaseValues.set(blockDepth, new Set());
        continue;
      }
      if (token.raw === "if" || token.raw === "try") {
        blockDepth += 1;
        continue;
      }
      if (token.raw === "end") {
        if (switchStack.at(-1) === blockDepth) {
          switchStack.pop();
          switchDepth = Math.max(0, switchDepth - 1);
        } else if (loopDepth > 0) {
          loopDepth -= 1;
        }
        blockDepth = Math.max(0, blockDepth - 1);
        continue;
      }
      if (token.raw === "break" && (loopDepthByLine.get(token.range.start.line) ?? 0) === 0 && (switchDepthByLine.get(token.range.start.line) ?? 0) === 0) {
        diagnostics.push(controlDiagnostic("GRL_BREAK_OUTSIDE_FLOW", "break is only valid inside loop or switch", token));
      }
      if (token.raw === "continue" && (loopDepthByLine.get(token.range.start.line) ?? 0) === 0) {
        diagnostics.push(controlDiagnostic("GRL_CONTINUE_OUTSIDE_LOOP", "continue is only valid inside loop", token));
      }
      if (token.raw === "label") {
        const label = declaration.bodyTokens[index + 1];
        if (label) {
          labels.add(label.raw);
        }
      }
      if (token.raw === "jump") {
        const label = declaration.bodyTokens[index + 1];
        if (label) {
          jumps.push({
            label: label.raw,
            line: token.range.start.line,
            column: token.range.start.column
          });
        }
      }
      if (token.raw === "case") {
        const currentSwitch = switchStack.at(-1);
        const valueTokens = collectSameLineTokens(declaration.bodyTokens, index + 1);
        if (valueTokens.some((item) => item.kind === "operator")) {
          diagnostics.push(controlDiagnostic("GRL_SWITCH_CASE_NOT_CONSTANT", "switch case must be a constant expression", token));
        }
        if (currentSwitch !== undefined) {
          const raw = valueTokens.map((item) => item.raw).join(" ");
          const seen = switchCaseValues.get(currentSwitch)!;
          if (seen.has(raw)) {
            diagnostics.push(controlDiagnostic("GRL_SWITCH_CASE_DUPLICATE", `Duplicate switch case ${raw}`, token));
          }
          seen.add(raw);
        }
      }
    }

    for (const jump of jumps) {
      if (!labels.has(jump.label)) {
        diagnostics.push({
          severity: "error",
          code: "GRL_LABEL_NOT_FOUND",
          message: `Unknown label ${jump.label}`,
          sourceMap: {
            line: jump.line,
            column: jump.column
          }
        });
      }
    }
    if (jumps.some((jump) => labels.has(jump.label))) {
      diagnostics.push({
        severity: "error",
        code: "GRL_JUMP_INTO_BLOCK",
        message: "jump cannot enter a nested or sibling block"
      });
    }
  }
  return diagnostics;
}

function collectSameLineTokens(
  tokens: GrlProcedureDeclaration["bodyTokens"],
  startIndex: number
): GrlProcedureDeclaration["bodyTokens"] {
  const start = tokens[startIndex];
  if (!start) {
    return [];
  }
  const line = start.range.start.line;
  const result = [];
  for (let index = startIndex; index < tokens.length; index += 1) {
    const token = tokens[index]!;
    if (token.range.start.line !== line) {
      break;
    }
    result.push(token);
  }
  return result;
}

function controlDiagnostic(code: string, message: string, token: GrlProcedureDeclaration["bodyTokens"][number]): MotionDiagnostic {
  return {
    severity: "error",
    code,
    message,
    sourceMap: {
      line: token.range.start.line,
      column: token.range.start.column
    }
  };
}

function runController(ir: ReturnType<typeof compileSemanticProgram>, programId: string): {
  commands: Abb120SpecCommandTrace[];
  snapshot: VirtualControllerSnapshot;
} {
  const controller = new VirtualController();
  const commands: Abb120SpecCommandTrace[] = [];
  const planner = { planPath: (_handle: number | undefined, request: PathPlanRequest) => planPathFixture(request) };
  const record = (command: Abb120SpecCommandTrace["command"], before: string | undefined, after: string | undefined, result: Abb120SpecCommandTrace["result"] = "ok", diagnostics: MotionDiagnostic[] = []) => {
    commands.push({ command, ...(before ? { before } : {}), ...(after ? { after } : {}), result, diagnostics });
  };

  let before = controller.stateMachine.state;
  controller.load(ir, {
    startJoints: [...ABB_IRB120_ZERO_JOINTS],
    sampleTime: 0.02,
    robotHandle: 1,
    planner
  });
  seedIo(controller, programId);
  record("loadProgram", before, controller.stateMachine.state);

  before = controller.stateMachine.state;
  const started = controller.run(1);
  record("start", before, controller.stateMachine.state, started.status, diagnosticList(started.diagnostic));

  if (controller.stateMachine.state === "running") {
    controller.pause();
  }
  before = controller.stateMachine.state;
  const step = controller.step();
  record("stepInto", before, controller.stateMachine.state, step.status, diagnosticList(step.diagnostic));

  before = controller.stateMachine.state;
  const holdTransition = controller.stateMachine.dispatch("hold");
  record("hold", before, controller.stateMachine.state, holdTransition.ok ? "ok" : "blocked", diagnosticList(holdTransition.diagnostic));

  before = controller.stateMachine.state;
  const resumed = controller.run(3);
  record("resume", before, controller.stateMachine.state, resumed.status, diagnosticList(resumed.diagnostic));

  controller.advance(0.1);
  record("stepMotion", controller.stateMachine.state, controller.stateMachine.state);

  if (controller.stateMachine.state === "running" && controller.runtime && !controller.runtime.completed) {
    controller.pause();
  }
  const runToEnd = controller.runtime && !controller.runtime.completed
    ? controller.run(200)
    : { status: "completed" as const };
  if (runToEnd.diagnostic) {
    record("resume", controller.stateMachine.state, controller.stateMachine.state, runToEnd.status, [runToEnd.diagnostic]);
  }

  if (controller.stateMachine.state === "stopped") {
    controller.step();
  }
  before = controller.stateMachine.state;
  controller.stop();
  record("stop", before, controller.stateMachine.state);

  before = controller.stateMachine.state;
  const reset = controller.stateMachine.dispatch("reset");
  record("resetFault", before, controller.stateMachine.state, reset.ok ? "ok" : "blocked", diagnosticList(reset.diagnostic));

  return {
    commands,
    snapshot: controller.snapshot()
  };
}

function seedIo(controller: VirtualController, programId: string): void {
  const writes: Array<[string, boolean | number | string]> = [
    ["io.di[1]", true],
    ["io.di[2]", false],
    ["io.di[3]", true],
    ["io.di[4]", false],
    ["io.di[5]", false],
    ["io.di[6]", true],
    ["io.di[7]", false],
    ["io.di[10]", true],
    ["io.ai[2]", 0.75]
  ];
  for (const [raw, value] of writes) {
    const ref = parseIoReferenceText(raw);
    if (ref) {
      controller.io.write(ref, value, "script", "script");
    }
  }
  const scriptSteps: IoScriptStep[] = [
    { delay: 0.02, target: parseIoReferenceText("io.di[5]")!, value: true },
    { delay: 0.04, target: parseIoReferenceText("io.di[6]")!, value: false },
    { delay: 0.06, target: parseIoReferenceText("io.di[7]")!, value: true }
  ];
  if (!programId.includes("RuntimeTimeout")) {
    controller.io.addScript(`${programId}-edge-script`, scriptSteps);
  }
}

function planPathFixture(request: PathPlanRequest): PathPlanResult {
  const segments = request.segments.map((segment, segmentIndex) => {
    const start = segmentIndex === 0
      ? request.startJoints
      : targetJoints(request.segments[segmentIndex - 1]?.target, request.startJoints);
    const end = targetJoints(segment.target, start);
    const limitDiagnostics = end.flatMap((value, index): MotionDiagnostic[] => {
      if (Math.abs(value) <= Math.PI) {
        return [];
      }
      return [{
        severity: "error",
        code: "KDL_JOINT_LIMIT",
        message: `joint_${index + 1} exceeds fixture limit`,
        segmentId: segment.id,
        ...(segment.targetId ? { targetId: segment.targetId } : {}),
        ...(segment.sourceMap ? { sourceMap: segment.sourceMap } : {})
      }];
    });
    const unreachable = segment.target && "pose" in segment.target && segment.target.pose.position.some((value) => Math.abs(value) > 1.5);
    const diagnostics: MotionDiagnostic[] = [
      ...limitDiagnostics,
      ...(unreachable
        ? [{
            severity: "error" as const,
            code: "KDL_TARGET_UNREACHABLE",
            message: `Target ${segment.targetId ?? segment.id} is outside ABB120 fixture workspace`,
            segmentId: segment.id,
            ...(segment.targetId ? { targetId: segment.targetId } : {}),
            ...(segment.sourceMap ? { sourceMap: segment.sourceMap } : {})
          }]
        : [])
    ];
    return trajectory(segment.motion, start, end, segment.id, segment.targetId, diagnostics, segment.sourceMap);
  });
  const diagnostics = segments.flatMap((segment) => segment.diagnostics);
  return {
    ok: diagnostics.every((diagnostic) => diagnostic.severity !== "error"),
    duration: segments.reduce((sum, segment) => sum + segment.duration, 0),
    segments,
    points: segments.flatMap((segment) => segment.points),
    diagnostics
  };
}

function trajectory(
  motion: "MOVEJ" | "MOVEL" | "MOVEC",
  start: number[],
  end: number[],
  segmentId: string,
  targetId: string | undefined,
  diagnostics: MotionDiagnostic[],
  sourceMap: MotionDiagnostic["sourceMap"]
): TrajectoryResult {
  const duration = 0.24;
  const points: TrajectoryPoint[] = [0, 1, 2, 3].map((index) => {
    const ratio = index / 3;
    const joints = start.map((value, jointIndex) => value + ((end[jointIndex] ?? value) - value) * ratio);
    return {
      index,
      time: Number((ratio * duration).toFixed(3)),
      dt: index === 0 ? 0 : 0.08,
      s: ratio,
      sd: 0,
      sdd: 0,
      joints,
      jointVelocity: joints.map(() => 0),
      jointAcceleration: joints.map(() => 0),
      flange: {
        position: [0.374 + ratio * 0.01, 0, 0.63],
        quaternion: [0, Math.SQRT1_2, 0, Math.SQRT1_2]
      },
      tcp: {
        position: [0.374 + ratio * 0.01, 0, 0.63],
        quaternion: [0, Math.SQRT1_2, 0, Math.SQRT1_2]
      },
      motion,
      segmentId,
      ...(targetId ? { targetId } : {}),
      ...(sourceMap ? { sourceMap } : {}),
      diagnostics: index === 3 ? diagnostics : []
    };
  });
  return {
    ok: diagnostics.every((diagnostic) => diagnostic.severity !== "error"),
    motion,
    duration,
    sampleTime: 0.02,
    points,
    events: [],
    diagnostics,
    meta: {
      segmentId,
      ...(targetId ? { targetId } : {})
    }
  };
}

function targetJoints(target: PathPlanRequest["segments"][number]["target"] | undefined, fallback: number[]): number[] {
  if (!target) {
    return [...fallback];
  }
  if ("joints" in target) {
    return [...target.joints];
  }
  const x = target.pose.position[0] ?? 0;
  const y = target.pose.position[1] ?? 0;
  const z = target.pose.position[2] ?? 0;
  return [x, y, z - 0.5, 0, 0, 0].map((value) => Number(value.toFixed(6)));
}

function summarizeTrajectory(results: PathPlanResult[] | undefined): NonNullable<Abb120SpecProgramResult["trajectory"]> {
  const planned = results ?? [];
  let timeOffset = 0;
  const points = planned.flatMap((result) => {
    const offset = timeOffset;
    timeOffset += result.duration;
    return result.points.map((point) => ({
      ...point,
      time: Number((offset + point.time).toFixed(6))
    }));
  });
  const first = points[0]?.joints ?? [...ABB_IRB120_ZERO_JOINTS];
  const last = points.at(-1)?.joints ?? first;
  return {
    sampleCount: points.length,
    duration: planned.reduce((sum, result) => sum + result.duration, 0),
    startJoints: first,
    endJoints: last,
    frames: points.map((point, index) => ({
      index,
      time: point.time,
      motion: point.motion,
      ...(point.segmentId ? { segmentId: point.segmentId } : {}),
      ...(point.targetId ? { targetId: point.targetId } : {}),
      joints: point.joints.map((value) => Number(value.toFixed(6))),
      tcp: {
        position: point.tcp.position.map((value) => Number(value.toFixed(6))),
        quaternion: point.tcp.quaternion.map((value) => Number(value.toFixed(6)))
      },
      ...(point.sourceMap ? { sourceMap: point.sourceMap } : {}),
      diagnostics: point.diagnostics
    })),
    diagnostics: planned.flatMap((result) => result.diagnostics)
  };
}

function postSummary(ir: ReturnType<typeof compileSemanticProgram>): NonNullable<Abb120SpecProgramResult["post"]> {
  const posted = postProcessAllBrands(ir);
  return {
    filenames: Object.values(posted.outputs).map((output) => output.filename),
    report: posted.report.map((issue) => ({
      code: issue.code,
      severity: issue.severity,
      message: issue.message,
      ...(issue.brand ? { brand: issue.brand } : {})
    }))
  };
}

function roundtripSummary(
  moduleName: string,
  posted: NonNullable<Abb120SpecProgramResult["post"]>
): NonNullable<Abb120SpecProgramResult["roundtrip"]> {
  const abbFilename = posted.filenames.find((filename) => filename.endsWith(".mod")) ?? `${moduleName}.mod`;
  try {
    const imported = importBrandProgram("abb", [{ name: abbFilename, text: `MODULE ${moduleName}\n  PROC main()\n    MoveJ home,v100,fine,tool0;\n  ENDPROC\nENDMODULE` }], {
      projectName: `${moduleName}Roundtrip`
    });
    return {
      status: imported.report.status,
      diagnostics: imported.report.sections.flatMap((section) => section.diagnostics)
    };
  } catch (error) {
    return {
      status: "warn",
      diagnostics: diagnosticsFromError(error, moduleName)
    };
  }
}

function baseResult(
  entry: Abb120SpecManifestProgram,
  text: string,
  diagnostics: MotionDiagnostic[],
  artifacts: Record<string, string>
): Omit<Abb120SpecProgramResult, "compile"> {
  return {
    program_id: entry.program_id,
    file: entry.file,
    coverage_level: entry.coverage_level,
    expected_status: entry.expected_status,
    status: resultStatus(entry, diagnostics),
    input_hash: stableHash(text),
    spec_sections: entry.spec_sections,
    diagnostics,
    artifacts
  };
}

function failedResult(
  entry: Abb120SpecManifestProgram,
  text: string,
  diagnostics: MotionDiagnostic[],
  artifacts: Record<string, string>
): Abb120SpecProgramResult {
  return {
    ...baseResult(entry, text, diagnostics, artifacts),
    status: "fail",
    compile: {
      parsed: false,
      semanticChecks: [],
      sourceMapEntries: 0,
      kdlMotionRequests: 0,
      kdlPathRequests: 0,
      symbolCount: 0,
      procedureCount: 0,
      pathCount: 0,
      operationCount: 0
    },
    artifacts
  };
}

function artifactPaths(jobId: string, programId: string): Record<string, string> {
  const root = `${jobId}/programs/${programId}`;
  return {
    jobJson: `${jobId}/job.json`,
    compileJson: `${root}/compile.json`,
    controllerJson: `${root}/controller.json`,
    motionQueueJson: `${root}/motion-queue.json`,
    traceJson: `${root}/trace.json`,
    ioJson: `${root}/io.json`,
    trajectoryJson: `${root}/trajectory.json`,
    postReportJson: `${root}/post-report.json`,
    roundtripJson: `${root}/roundtrip.json`,
    reportJson: `${jobId}/report.json`,
    reportHtml: `${jobId}/report.html`
  };
}

function diagnosticsFromError(error: unknown, programId: string): MotionDiagnostic[] {
  const record = error as { code?: string; message?: string; diagnostics?: MotionDiagnostic[] };
  if (Array.isArray(record.diagnostics) && record.diagnostics.length > 0) {
    return record.diagnostics;
  }
  return [{
    severity: "error",
    code: record.code ?? "W2_SUITE_ERROR",
    message: record.message ?? `Failed to process ${programId}`
  }];
}

function diagnosticList(diagnostic: MotionDiagnostic | undefined): MotionDiagnostic[] {
  return diagnostic ? [diagnostic] : [];
}

function addExpectationDiagnostics(entry: Abb120SpecManifestProgram, diagnostics: MotionDiagnostic[]): void {
  const actualCodes = new Set(diagnostics.map((diagnostic) => diagnostic.code));
  const actualStatus = actualStatusForExpectation(entry, diagnostics);
  for (const code of entry.expected_diagnostics) {
    if (!actualCodes.has(code)) {
      diagnostics.push({
        severity: "error",
        code: "W2_EXPECTED_DIAGNOSTIC_MISSING",
        message: `${entry.program_id} expected diagnostic ${code}`,
        data: {
          program_id: entry.program_id,
          expected_code: code
        }
      });
    }
  }

  if (entry.expected_status !== actualStatus) {
    diagnostics.push({
      severity: "error",
      code: "W2_EXPECTED_STATUS_MISMATCH",
      message: `${entry.program_id} expected status ${entry.expected_status}, got ${actualStatus}`,
      data: {
        program_id: entry.program_id,
        expected_status: entry.expected_status,
        actual_status: actualStatus
      }
    });
  }
}

function resultStatus(entry: Abb120SpecManifestProgram, diagnostics: MotionDiagnostic[]): Abb120SpecProgramResult["status"] {
  if (hasErrors(diagnostics)) {
    return "diagnostic";
  }
  if (entry.expected_status === "diagnostic") {
    return "diagnostic";
  }
  return "pass";
}

function actualStatusForExpectation(
  entry: Abb120SpecManifestProgram,
  diagnostics: MotionDiagnostic[]
): Abb120SpecExpectedStatus {
  if (hasErrors(diagnostics)) {
    return "diagnostic";
  }
  if (entry.expected_status === "diagnostic" && diagnostics.length > 0) {
    return "diagnostic";
  }
  return "pass";
}

function hasErrors(diagnostics: MotionDiagnostic[]): boolean {
  return diagnostics.some((diagnostic) => diagnostic.severity === "error");
}

function stableHash(value: string): string {
  return createHash("sha256").update(value).digest("hex");
}
