import { describe, expect, it } from "vitest";
import { parseGrl } from "../../src/grl/parser/index.js";
import { compileSemanticProgram } from "../../src/grl/semantic/index.js";

const PROGRAM = `language grl 0.1
module Main
  const speed vj = joint(50 %)
  const speed vl = linear(200 mm/s)
  const zone zf = fine
  target home = joint_target { joints: [0 deg] }
  target pick = pose_target { pose: pose(500 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) }
  path pick_path {
    defaults { speed: vl, zone: zf }
    point p0 movej home speed vj zone fine
    point p1 movel pick
    event before p1 io.do[1] = true
  }
  operation pick_op {
    kind: handling
    path: pick_path
    start_action:
      io.do[2] = true
    end_action:
      io.do[2] = false
  }
  proc set_done(out bool done)
    done = true
  end
  proc main(out bool done)
    set_speed vl
    set_zone zf
    movej home speed vj zone fine
    io.do[3] = true
    wait io.di[1] == true timeout 1 s
    if done == false
      pulse io.do[4] duration 100 ms
    else
      alarm DONE "Already done"
    end
    call set_done(done)
    run_path pick_path
    run_operation pick_op
    return
  end
end
`;

describe("GRL semantic analyzer, executable IR, and source map", () => {
  it("compiles a complete program into unified executable IR and KDL bridge requests", () => {
    const ir = compileSemanticProgram(parseGrl(PROGRAM), {
      startJoints: [0],
      sampleTime: 0.004
    });

    expect(ir.moduleName).toBe("Main");
    expect(ir.semanticChecks).toHaveLength(22);
    expect(ir.symbols).toEqual([
      expect.objectContaining({ kind: "data", name: "vj", typeName: "speed" }),
      expect.objectContaining({ kind: "data", name: "vl", typeName: "speed" }),
      expect.objectContaining({ kind: "data", name: "zf", typeName: "zone" }),
      expect.objectContaining({ kind: "target", name: "home" }),
      expect.objectContaining({ kind: "target", name: "pick" }),
      expect.objectContaining({ kind: "path", name: "pick_path" }),
      expect.objectContaining({ kind: "operation", name: "pick_op" }),
      expect.objectContaining({ kind: "procedure", name: "set_done" }),
      expect.objectContaining({ kind: "procedure", name: "main" })
    ]);
    expect(ir.paths).toHaveLength(1);
    expect(ir.operations).toHaveLength(1);
    expect(ir.kdlBridge.pathRequests).toEqual([
      expect.objectContaining({
        pathId: "pick_path",
        segments: [
          expect.objectContaining({ id: "p0", motion: "MOVEJ" }),
          expect.objectContaining({ id: "p1", motion: "MOVEL" })
        ]
      })
    ]);
    expect(ir.kdlBridge.motionRequests).toEqual([
      expect.objectContaining({
        startJoints: [0],
        speed: { kind: "joint_percent", value: 0.5 },
        zone: { kind: "fine" },
        sampleTime: 0.004
      })
    ]);

    const main = ir.procedures.find((procedure) => procedure.name === "main")!;
    expect(main.instructions).toEqual([
      expect.objectContaining({ kind: "MOVEJ" }),
      expect.objectContaining({ kind: "IO_WRITE", target: expect.objectContaining({ domain: "do", index: 3 }) }),
      expect.objectContaining({ kind: "WAIT", timeout: 1 }),
      expect.objectContaining({
        kind: "EXEC_IF",
        branches: [
          expect.objectContaining({
            branchKind: "if",
            body: expect.arrayContaining([expect.objectContaining({ kind: "PULSE", duration: 0.1 })])
          }),
          expect.objectContaining({
            branchKind: "else",
            body: expect.arrayContaining([expect.objectContaining({ kind: "ALARM", alarmId: "DONE" })])
          })
        ]
      }),
      expect.objectContaining({ kind: "CALL", target: "set_done" }),
      expect.objectContaining({ kind: "RUN_PATH", pathId: "pick_path" }),
      expect.objectContaining({ kind: "RUN_OPERATION", operationId: "pick_op" }),
      expect.objectContaining({ kind: "RETURN" })
    ]);
  });

  it("exposes source map entries for GRL procedure lines, path points, and operation actions", () => {
    const ir = compileSemanticProgram(parseGrl(PROGRAM), {
      startJoints: [0],
      sampleTime: 0.004
    });

    expect(ir.sourceMap).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ kind: "path_point", pathId: "pick_path", pointId: "p0" }),
        expect.objectContaining({ kind: "path_point", pathId: "pick_path", pointId: "p1" }),
        expect.objectContaining({ kind: "operation_action", operationId: "pick_op" }),
        expect.objectContaining({ kind: "MOVEJ", procedureId: "main", sourceMap: expect.objectContaining({ line: 28 }) }),
        expect.objectContaining({ kind: "EXEC_IF", procedureId: "main" }),
        expect.objectContaining({ kind: "RUN_OPERATION", procedureId: "main" })
      ])
    );
  });

  it("reports duplicate symbols through semantic diagnostics", () => {
    const ir = compileSemanticProgram(parseGrl(`language grl 0.1
module Main
  const speed v = joint(10 %)
  const speed v = joint(20 %)
  proc main()
  end
end
`), {
      startJoints: [],
      sampleTime: 0.004
    });

    expect(ir.diagnostics).toEqual([
      expect.objectContaining({ severity: "error", code: "GRL_SYMBOL_DUPLICATE" })
    ]);
  });
});
