import { describe, expect, it } from "vitest";
import type { SemanticProgramIr } from "../../src/grl/ir/index.js";
import type { PathPlanResult, TrajectoryPoint } from "../../src/kdl/types.js";
import { IrExecutionRuntime } from "../../src/controller/index.js";
import { IoImageRuntime, MotionQueue, type MotionPlanner } from "../../src/runtime/index.js";
import { DebugFacade, WorkbenchFacade } from "../../src/workbench/index.js";

const POSE = {
  position: [0, 0, 0] as [number, number, number],
  quaternion: [0, 0, 0, 1] as [number, number, number, number]
};

function point(index: number, time: number, joints: number[]): TrajectoryPoint {
  return {
    index,
    time,
    dt: time,
    s: time,
    sd: 1,
    sdd: 0,
    joints,
    jointVelocity: [0],
    jointAcceleration: [0],
    flange: POSE,
    tcp: POSE,
    motion: "MOVEJ",
    diagnostics: []
  };
}

function program(): SemanticProgramIr {
  return {
    moduleName: "Main",
    symbols: [{ kind: "path", name: "path1" }, { kind: "operation", name: "op1" }],
    semanticChecks: [],
    diagnostics: [],
    sourceMap: [
      { kind: "RUN_PATH", id: "run_path", procedureId: "main", pathId: "path1", sourceMap: { file: "main.grl", line: 5 } },
      { kind: "path_point", id: "p1", pathId: "path1", pointId: "p1", sourceMap: { file: "main.grl", line: 10 } },
      { kind: "operation_action", id: "op1_start_action_0", operationId: "op1", sourceMap: { file: "main.grl", line: 20 } }
    ],
    procedures: [
      {
        name: "main",
        sourceMap: { file: "main.grl", line: 1 },
        instructions: [
          { kind: "RAW_STATEMENT", text: "part_ready = true", sourceMap: { file: "main.grl", line: 4 } },
          { kind: "RUN_PATH", pathId: "path1", sourceMap: { file: "main.grl", line: 5 } }
        ]
      }
    ],
    paths: [
      {
        pathId: "path1",
        request: {
          pathId: "path1",
          startJoints: [0],
          sampleTime: 0.1,
          segments: [
            {
              id: "p1",
              motion: "MOVEJ",
              target: { joints: [1] },
              speed: { kind: "joint_abs", velocity: 1 },
              zone: { kind: "fine" },
              sourceMap: { file: "main.grl", line: 10 }
            }
          ]
        },
        motions: [
          {
            kind: "MOVEJ",
            id: "p1",
            pathId: "path1",
            pointId: "p1",
            target: { joints: [1] },
            speed: { kind: "joint_abs", velocity: 1 },
            zone: { kind: "fine" },
            sourceMap: { file: "main.grl", line: 10 },
            source: { brand: { vendor: "KUKA", file: "src.src", line: 99 } }
          }
        ],
        events: []
      }
    ],
    operations: [
      {
        operationId: "op1",
        kind: "handling",
        pathId: "path1",
        process: {},
        startActions: [],
        endActions: []
      }
    ],
    kdlBridge: { motionRequests: [], pathRequests: [] }
  };
}

describe("debug and workbench facades", () => {
  it("models breakpoints, motion breakpoints, watches, replay, and cross-source lookup", async () => {
    const ir = program();
    const runtime = new IrExecutionRuntime();
    runtime.load(ir);
    runtime.step();

    const planner: MotionPlanner = {
      planPath: (_handle, request): PathPlanResult => ({
        ok: true,
        duration: 1,
        segments: [],
        points: [point(0, 0, request.startJoints), point(1, 1, [1])],
        diagnostics: []
      })
    };
    const queue = new MotionQueue({ startJoints: [0], sampleTime: 0.1, planner });
    const item = queue.enqueueRunPath({ kind: "RUN_PATH", pathId: "path1", sourceMap: { file: "main.grl", line: 5 } }, ir.paths[0]!);
    await queue.planAll();
    queue.advance(1);

    const io = new IoImageRuntime();
    const debug = new DebugFacade({ runtime, motionQueue: queue, io, program: ir });
    debug.addBreakpoint({ id: "bp-run-path", procedure: "main", source: { line: 5 }, enabled: true });
    debug.addMotionBreakpoint({ id: "mb-path", pathId: "path1", enabled: true });
    const watch = debug.addWatch("watch-ready", "part_ready");

    expect(watch).toMatchObject({ value: true, source: { sourceMap: { line: 5 } } });
    expect(debug.checkMotionBreakpoint(item)).toMatchObject({ id: "mb-path" });
    expect(debug.locatePathPoint("path1", "p1")).toMatchObject({ sourceMap: { line: 10 } });
    expect(debug.locateOperation("op1")).toMatchObject([{ sourceMap: { line: 20 } }]);
    expect(debug.playbackAt(1)).toMatchObject({ point: { joints: [1] } });
    expect(debug.replayTrace()).toEqual(expect.arrayContaining([expect.objectContaining({ kind: "load" })]));
    expect(debug.snapshot()).toMatchObject({
      breakpoints: [expect.objectContaining({ id: "bp-run-path" })],
      motionBreakpoints: [expect.objectContaining({ id: "mb-path" })],
      watches: [expect.objectContaining({ id: "watch-ready", value: true })]
    });
  });

  it("builds object tree, editor, teach pendant, IO panel, and report entries", async () => {
    const ir = program();
    const runtime = new IrExecutionRuntime();
    runtime.load(ir);
    runtime.step();

    const queue = new MotionQueue({
      startJoints: [0],
      sampleTime: 0.1,
      planner: {
        planPath: (_handle, request): PathPlanResult => ({
          ok: true,
          duration: 1,
          segments: [],
          points: [point(0, 0, request.startJoints), point(1, 1, [1])],
          diagnostics: []
        })
      }
    });
    queue.enqueueRunPath({ kind: "RUN_PATH", pathId: "path1" }, ir.paths[0]!);
    await queue.planAll();
    queue.advance(1);

    const io = new IoImageRuntime();
    io.write({ domain: "do", index: 1, raw: "io.do[1]" }, true);

    const workbench = new WorkbenchFacade();
    const snapshot = workbench.snapshot({
      program: ir,
      runtime: runtime.snapshot(),
      io: io.snapshot(),
      motion: queue.snapshot(),
      controllerState: "paused"
    });

    expect(snapshot.objectTree[0]).toMatchObject({
      kind: "station",
      children: [
        expect.objectContaining({ id: "programs" }),
        expect.objectContaining({ id: "paths" }),
        expect.objectContaining({ id: "operations" }),
        expect.objectContaining({ id: "reports" })
      ]
    });
    expect(snapshot.editor).toMatchObject({ activeFile: "main.grl", cursor: { line: 5 } });
    expect(snapshot.teachPendant).toMatchObject({ state: "paused", procedure: "main", joints: [1] });
    expect(snapshot.ioPanel.image).toMatchObject({ "io.do[1]": true });
    expect(snapshot.reports.map((report) => report.kind)).toEqual([
      "reachability",
      "cycle_time",
      "io_wait",
      "post",
      "import"
    ]);
  });
});
