import { describe, expect, it } from "vitest";
import { GRL_KEYWORDS, lexGrl, normalizeUnitLiteral, normalizeUnitValue } from "../../src/grl/lexer/index.js";
import type { GrlNumberToken } from "../../src/grl/lexer/index.js";

function numbers(source: string): GrlNumberToken[] {
  return lexGrl(source).filter((token): token is GrlNumberToken => token.kind === "number");
}

describe("GRL lexer", () => {
  it("recognizes comments, keywords, identifiers, and source positions", () => {
    const tokens = lexGrl(`// generated\nlanguage grl 0.1\nmodule Main\n  proc main()\n  end\nend\n`);

    expect(tokens[0]).toMatchObject({
      kind: "comment",
      style: "line",
      value: " generated",
      range: {
        start: { line: 1, column: 1 },
        end: { line: 1, column: 13 }
      }
    });
    expect(tokens.filter((token) => token.kind === "keyword").map((token) => token.raw)).toEqual([
      "language",
      "module",
      "proc",
      "end",
      "end"
    ]);
    expect(tokens.find((token) => token.raw === "Main")).toMatchObject({
      kind: "identifier",
      range: {
        start: { line: 3, column: 8 }
      }
    });
    expect(tokens.at(-1)).toMatchObject({ kind: "eof" });
  });

  it("normalizes numeric literals with GRL units into SI values", () => {
    const found = numbers("100 mm 0.25 m 180 deg 3.14159 rad 300 mm/s 50 % 200 ms 2.5 kg 500 mm/s2");

    expect(found.map((token) => token.unit?.raw)).toEqual([
      "mm",
      "m",
      "deg",
      "rad",
      "mm/s",
      "%",
      "ms",
      "kg",
      "mm/s2"
    ]);
    expect(found[0]?.unit?.normalizedValue).toBeCloseTo(0.1);
    expect(found[1]?.unit?.normalizedValue).toBeCloseTo(0.25);
    expect(found[2]?.unit?.normalizedValue).toBeCloseTo(Math.PI);
    expect(found[3]?.unit?.normalizedValue).toBeCloseTo(3.14159);
    expect(found[4]?.unit?.normalizedValue).toBeCloseTo(0.3);
    expect(found[5]?.unit?.normalizedValue).toBeCloseTo(0.5);
    expect(found[6]?.unit?.normalizedValue).toBeCloseTo(0.2);
    expect(found[7]?.unit?.normalizedValue).toBeCloseTo(2.5);
    expect(found[8]?.unit?.normalizedValue).toBeCloseTo(0.5);
  });

  it("keeps unit raw text on number tokens", () => {
    const [token] = numbers("linear(300 mm/s)");

    expect(token).toMatchObject({
      kind: "number",
      raw: "300 mm/s",
      value: 300,
      unit: {
        raw: "mm/s",
        kind: "linear_velocity",
        siUnit: "m/s"
      }
    });
  });

  it("exposes the full reserved keyword set from the specification", () => {
    expect(GRL_KEYWORDS).toContain("movej");
    expect(GRL_KEYWORDS).toContain("run_operation");
    expect(GRL_KEYWORDS).toContain("post_hint");
    expect(GRL_KEYWORDS).toContain("continuous");
    expect(GRL_KEYWORDS).toHaveLength(85);
  });

  it("provides direct unit helpers for parser and semantic layers", () => {
    expect(normalizeUnitLiteral("deg/s")).toMatchObject({
      kind: "angular_velocity",
      siUnit: "rad/s"
    });
    expect(normalizeUnitValue(90, "deg/s")).toBeCloseTo(Math.PI / 2);
    expect(() => normalizeUnitLiteral("inch")).toThrow("Unknown GRL unit");
  });
});
