import { describe, expect, it } from "vitest";
import { generateGrlProgram, type GrlProgramGenerationSpec } from "../../../src/grl/generator/index.js";
import { postProcessAllBrands } from "../../../src/grl/post/index.js";
import type { GrlOperationDeclaration, GrlPathDeclaration, GrlTargetDeclaration } from "../../../src/grl/ast/index.js";
import { parseGrl } from "../../../src/grl/parser/index.js";
import { compileSemanticProgram } from "../../../src/grl/semantic/index.js";

const SPEC: GrlProgramGenerationSpec = {
  moduleName: "GeneratedCell",
  speeds: {
    v_linear: "linear(200 mm/s)",
    v_joint: "joint(50 %)"
  },
  zones: {
    z10: "z(10 mm)",
    zf: "fine"
  },
  targets: [
    { name: "pick", kind: "pose", values: [500, 0, 0, 0, 0, 0] },
    { name: "home", kind: "joint", values: [0] },
    { name: "place", kind: "pose", values: [600, 0, 0, 0, 0, 0] }
  ],
  path: {
    name: "generated_path",
    source: {
      type: "cad_curve",
      id: "edge_001",
      sample_distance: 5
    },
    defaults: {
      speed: "v_linear",
      zone: "z10"
    },
    points: [
      { motion: "movej", target: "home", speed: "v_joint", zone: "zf" },
      { motion: "movel", target: "pick" },
      { id: "place_point", motion: "movel", target: "place", zone: "zf" }
    ]
  },
  operation: {
    name: "generated_op",
    kind: "handling",
    path: "generated_path",
    startAction: "io.do[1] = true",
    endAction: "io.do[1] = false"
  }
};

describe("GRL generator and roundtrip", () => {
  it("generates stable expanded GRL with target/path/operation first", () => {
    const first = generateGrlProgram(SPEC, "expanded");
    const second = generateGrlProgram(SPEC, "expanded");

    expect(first).toEqual(second);
    expect(first.stableIds).toEqual({
      targets: ["home", "pick", "place"],
      points: ["p00", "p01", "place_point"],
      path: "generated_path",
      operation: "generated_op"
    });
    expect(first.text).toBe(`language grl 0.1
module GeneratedCell
  const speed v_joint = joint(50 %)
  const speed v_linear = linear(200 mm/s)
  const zone z10 = z(10 mm)
  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)
  }
  target place = pose_target {
    pose: pose(600 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg)
  }
  path generated_path {
    source {
      id: "edge_001"
      sample_distance: 5
      type: cad_curve
    }
    defaults {
      speed: v_linear
      zone: z10
    }
    point p00 movej home speed v_joint zone zf
    point p01 movel pick
    point place_point movel place zone zf
  }
  operation generated_op {
    kind: handling
    path: generated_path
    start_action:
      io.do[1] = true
    end_action:
      io.do[1] = false
  }
  proc main()
    run_operation generated_op
  end
end`);
  });

  it("supports compact output that remains parseable", () => {
    const compact = generateGrlProgram(SPEC, "compact");

    expect(compact.text).toContain("path generated_path { source { id: \"edge_001\"");
    expect(parseGrl(compact.text).module.name).toBe("GeneratedCell");
  });

  it("roundtrips through parser, semantic IR, and postprocessors", () => {
    const generated = generateGrlProgram(SPEC, "expanded");
    const ast = parseGrl(generated.text);
    const declarations = ast.module.declarations;
    const targets = declarations.filter((decl): decl is GrlTargetDeclaration => decl.kind === "TargetDeclaration");
    const path = declarations.find((decl): decl is GrlPathDeclaration => decl.kind === "PathDeclaration")!;
    const operation = declarations.find((decl): decl is GrlOperationDeclaration => decl.kind === "OperationDeclaration")!;
    const ir = compileSemanticProgram(ast, {
      startJoints: [0],
      sampleTime: 0.004
    });
    const post = postProcessAllBrands(ir);

    expect(targets.map((target) => target.name)).toEqual(["home", "pick", "place"]);
    expect(path.items).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ kind: "PathSourceBlock" }),
        expect.objectContaining({ kind: "PathDefaultsBlock" }),
        expect.objectContaining({ kind: "PathPoint", id: "p00" }),
        expect.objectContaining({ kind: "PathPoint", id: "p01" }),
        expect.objectContaining({ kind: "PathPoint", id: "place_point" })
      ])
    );
    expect(operation).toMatchObject({
      name: "generated_op",
      operationKind: "handling",
      pathName: "generated_path"
    });
    expect(ir.symbols).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ kind: "path", name: "generated_path" }),
        expect.objectContaining({ kind: "operation", name: "generated_op" })
      ])
    );
    expect(post.outputs.abb.text).toContain("MODULE GeneratedCell");
    expect(post.outputs.fanuc.text).toContain("/PROG MAIN");
    expect(post.outputs.kuka.text).toContain("DEF Main()");
  });
});
