import { describe, expect, it } from "vitest";
import type { SemanticProgramIr } from "../../src/grl/ir/index.js";
import type { PathPlanRequest, PathPlanResult, TrajectoryPoint } from "../../src/kdl/types.js";
import { KdlRuntimeBridge, MotionQueue, type MotionPlanner } from "../../src/runtime/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[], segmentId: string): TrajectoryPoint {
  return {
    index,
    time,
    dt: index === 0 ? 0 : time,
    s: time,
    sd: 1,
    sdd: 0,
    joints,
    jointVelocity: joints.map(() => 0),
    jointAcceleration: joints.map(() => 0),
    flange: POSE,
    tcp: POSE,
    motion: "MOVEJ",
    segmentId,
    diagnostics: []
  };
}

function pathResult(request: PathPlanRequest): PathPlanResult {
  return {
    ok: true,
    duration: 1,
    segments: [],
    points: [point(0, 0, request.startJoints, request.segments[0]?.id ?? "p0"), point(1, 1, [1], request.segments[0]?.id ?? "p0")],
    diagnostics: []
  };
}

function program(): SemanticProgramIr {
  return {
    moduleName: "Main",
    symbols: [],
    semanticChecks: [],
    diagnostics: [],
    sourceMap: [
      { kind: "path_point", id: "p0", pathId: "pick_path", pointId: "p0", sourceMap: { line: 10 } },
      { kind: "operation_action", id: "pick_op_start_action_0", operationId: "pick_op", sourceMap: { line: 20 } }
    ],
    procedures: [
      {
        name: "main",
        instructions: [
          { kind: "RUN_OPERATION", operationId: "pick_op", sourceMap: { line: 30 } }
        ]
      }
    ],
    paths: [
      {
        pathId: "pick_path",
        request: {
          pathId: "pick_path",
          startJoints: [0],
          sampleTime: 0.1,
          segments: [
            {
              id: "p0",
              motion: "MOVEJ",
              target: { joints: [1] },
              speed: { kind: "joint_abs", velocity: 1 },
              zone: { kind: "fine" },
              sourceMap: { line: 10 }
            }
          ]
        },
        motions: [
          {
            kind: "MOVEJ",
            id: "p0",
            pathId: "pick_path",
            pointId: "p0",
            target: { joints: [1] },
            speed: { kind: "joint_abs", velocity: 1 },
            zone: { kind: "fine" },
            sourceMap: { line: 10 },
            source: { brand: { vendor: "ABB", file: "cell.mod", line: 42 } }
          }
        ],
        events: []
      }
    ],
    operations: [
      {
        operationId: "pick_op",
        kind: "handling",
        pathId: "pick_path",
        process: {},
        startActions: [
          {
            kind: "ACTION",
            actionKind: "start_action",
            operationId: "pick_op",
            statement: "io.do[1] = true",
            sourceMap: { line: 20 }
          }
        ],
        endActions: [
          {
            kind: "ACTION",
            actionKind: "end_action",
            operationId: "pick_op",
            statement: "io.do[1] = false",
            sourceMap: { line: 22 }
          }
        ]
      }
    ],
    kdlBridge: {
      motionRequests: [],
      pathRequests: []
    }
  };
}

describe("motion queue and KDL runtime bridge", () => {
  it("preserves operation/path/action sources and samples planned paths by virtual time", async () => {
    const plannerCalls: PathPlanRequest[] = [];
    const planner: MotionPlanner = {
      planPath: (_handle, request) => {
        plannerCalls.push(request);
        return pathResult(request);
      }
    };
    const queue = new MotionQueue({ startJoints: [0], sampleTime: 0.1, planner, robotHandle: 7 });
    const ir = program();
    const bridge = new KdlRuntimeBridge(ir, queue);

    const items = bridge.enqueueInstruction(ir.procedures[0]!.instructions[0]!);
    expect(items.map((item) => item.kind)).toEqual(["action", "path", "action"]);
    expect(items[0]).toMatchObject({ source: { operationId: "pick_op", sourceMap: { line: 20 } } });
    expect(items[1]).toMatchObject({ source: { pathId: "pick_path", operationId: "pick_op" } });

    await queue.planAll();
    expect(plannerCalls).toHaveLength(1);
    expect(plannerCalls[0]).toMatchObject({ pathId: "pick_path", startJoints: [0] });

    expect(queue.advance(0.5)).toMatchObject({
      itemId: items[1]!.id,
      localTime: 0.5,
      point: { joints: [1] },
      source: { pathId: "pick_path", operationId: "pick_op" }
    });
    expect(queue.snapshot().items[1]).toMatchObject({
      planned: { ok: true, duration: 1 }
    });
  });
});
