import { describe, expect, it } from "vitest";
import type {
  GrlOperationDeclaration,
  GrlPathDeclaration,
  GrlProcedureDeclaration
} from "../../src/grl/ast/index.js";
import { parseGrl } from "../../src/grl/parser/index.js";
import {
  compileOperation,
  expandRunOperation,
  parseProcedureRunOperationStatements
} from "../../src/grl/semantic/index.js";

const PROGRAM = `language grl 0.1
module Main
  const speed v = joint(50 %)
  const zone zf = fine
  target home = joint_target { joints: [0 deg] }
  path weld_path {
    defaults { speed: v, zone: zf }
    point p0 movej home
  }
  operation weld_op_01 {
    kind: arc_welding
    path: weld_path
    process {
      weld_id: "WELD_1"
      voltage: 24.0
      current: 180.0
      weave: none
    }
    start_action:
      io.do[20] = true
    end_action:
      io.do[20] = false
  }
  proc main()
    run_operation weld_op_01
  end
end
`;

function declarations() {
  return parseGrl(PROGRAM).module.declarations;
}

function pathsByName(paths: GrlPathDeclaration[]) {
  return new Map(paths.map((path) => [path.name, path]));
}

describe("GRL operation compilation", () => {
  it("parses operation kind, path, process, and action blocks", () => {
    const operation = declarations().find(
      (decl): decl is GrlOperationDeclaration => decl.kind === "OperationDeclaration"
    )!;

    expect(operation).toMatchObject({
      kind: "OperationDeclaration",
      name: "weld_op_01",
      operationKind: "arc_welding",
      pathName: "weld_path",
      items: [
        { kind: "OperationProcessBlock" },
        { kind: "OperationActionBlock", actionKind: "start_action" },
        { kind: "OperationActionBlock", actionKind: "end_action" }
      ]
    });
  });

  it("compiles operation process metadata and action statements", () => {
    const decls = declarations();
    const operation = decls.find((decl): decl is GrlOperationDeclaration => decl.kind === "OperationDeclaration")!;
    const paths = decls.filter((decl): decl is GrlPathDeclaration => decl.kind === "PathDeclaration");
    const compiled = compileOperation(operation, pathsByName(paths));

    expect(compiled).toMatchObject({
      operationId: "weld_op_01",
      kind: "arc_welding",
      pathId: "weld_path",
      process: {
        weld_id: "WELD_1",
        voltage: 24,
        current: 180,
        weave: "none"
      },
      startActions: [
        {
          kind: "ACTION",
          actionKind: "start_action",
          operationId: "weld_op_01",
          statement: "io . do [ 20 ] = true"
        }
      ],
      endActions: [
        {
          kind: "ACTION",
          actionKind: "end_action",
          operationId: "weld_op_01",
          statement: "io . do [ 20 ] = false"
        }
      ]
    });
  });

  it("extracts run_operation and expands to start action, path, and end action", () => {
    const decls = declarations();
    const procedure = decls.find((decl): decl is GrlProcedureDeclaration => decl.kind === "ProcedureDeclaration")!;
    const operation = decls.find((decl): decl is GrlOperationDeclaration => decl.kind === "OperationDeclaration")!;
    const paths = decls.filter((decl): decl is GrlPathDeclaration => decl.kind === "PathDeclaration");
    const compiled = compileOperation(operation, pathsByName(paths));
    const [run] = parseProcedureRunOperationStatements(procedure);

    expect(run).toEqual({
      kind: "RUN_OPERATION",
      operationId: "weld_op_01",
      sourceMap: {
        line: 25,
        column: 5
      }
    });
    expect(expandRunOperation(run!, new Map([[compiled.operationId, compiled]]))).toEqual([
      expect.objectContaining({ kind: "ACTION", actionKind: "start_action" }),
      {
        kind: "RUN_PATH",
        pathId: "weld_path",
        sourceMap: {
          line: 25,
          column: 5
        }
      },
      expect.objectContaining({ kind: "ACTION", actionKind: "end_action" })
    ]);
  });

  it("reports operations that reference missing paths and missing run_operation targets", () => {
    const missingPathOperation = parseGrl(`language grl 0.1
module Main
  operation bad_op {
    kind: handling
    path: missing_path
  }
end
`).module.declarations.find((decl): decl is GrlOperationDeclaration => decl.kind === "OperationDeclaration")!;

    expect(() => compileOperation(missingPathOperation, new Map())).toThrowError(
      expect.objectContaining({ code: "GRL_OPERATION_PATH_NOT_FOUND" })
    );

    expect(() =>
      expandRunOperation({ kind: "RUN_OPERATION", operationId: "missing_op" }, new Map())
    ).toThrowError(expect.objectContaining({ code: "GRL_OPERATION_NOT_FOUND" }));
  });
});
