import { describe, expect, it } from "vitest";
import type { GrlFunctionDeclaration, GrlProcedureDeclaration } from "../../src/grl/ast/index.js";
import { parseGrl } from "../../src/grl/parser/index.js";
import { analyzeProcFunctionSemantics } from "../../src/grl/semantic/index.js";

function declarations(source: string) {
  return parseGrl(source).module.declarations;
}

describe("GRL proc, func, call, return, and scope semantics", () => {
  it("parses function declarations and analyzes proc/func signatures, calls, returns, and warnings", () => {
    const decls = declarations(`language grl 0.1
module Main
  var int global_count = 0
  proc read_sensor(out bool ok)
    ok = true
  end
  proc main(in bool start, out bool done, inout int count)
    call read_sensor(done)
    call helper(count)
    call self_check()
    return
  end
  proc self_check()
    call self_check()
  end
  func int helper(inout int value)
    var int global_count = 1
    return value
  end
end
`);
    const func = decls.find((decl): decl is GrlFunctionDeclaration => decl.kind === "FunctionDeclaration")!;

    expect(func).toMatchObject({
      kind: "FunctionDeclaration",
      returnType: "int",
      name: "helper",
      bodyTokens: [
        { raw: "var" },
        { raw: "int" },
        { raw: "global_count" },
        { raw: "=" },
        { raw: "1" },
        { raw: "return" },
        { raw: "value" }
      ]
    });

    const analysis = analyzeProcFunctionSemantics(decls);

    expect(analysis.procedures).toEqual([
      expect.objectContaining({
        name: "read_sensor",
        parameters: [expect.objectContaining({ name: "ok", typeName: "bool", direction: "out" })]
      }),
      expect.objectContaining({
        name: "main",
        parameters: [
          expect.objectContaining({ name: "start", typeName: "bool", direction: "in" }),
          expect.objectContaining({ name: "done", typeName: "bool", direction: "out" }),
          expect.objectContaining({ name: "count", typeName: "int", direction: "inout" })
        ]
      }),
      expect.objectContaining({ name: "self_check", parameters: [] })
    ]);
    expect(analysis.functions).toEqual([
      expect.objectContaining({
        name: "helper",
        returnType: "int",
        parameters: [expect.objectContaining({ name: "value", typeName: "int", direction: "inout" })]
      })
    ]);
    expect(analysis.calls).toEqual([
      expect.objectContaining({ kind: "CALL", target: "read_sensor", args: [expect.objectContaining({ text: "done" })] }),
      expect.objectContaining({ kind: "CALL", target: "helper", args: [expect.objectContaining({ text: "count" })] }),
      expect.objectContaining({ kind: "CALL", target: "self_check", args: [] }),
      expect.objectContaining({ kind: "CALL", target: "self_check", args: [] })
    ]);
    expect(analysis.returns).toEqual([
      expect.objectContaining({ kind: "RETURN" }),
      expect.objectContaining({ kind: "RETURN", value: expect.objectContaining({ text: "value" }) })
    ]);
    expect(analysis.diagnostics).toEqual([
      expect.objectContaining({ severity: "warning", code: "GRL_RECURSIVE_CALL" }),
      expect.objectContaining({ severity: "warning", code: "GRL_NAME_SHADOWS_OUTER_SCOPE" })
    ]);
  });

  it("reports out parameters that are not assigned on all normal return paths", () => {
    const decls = declarations(`language grl 0.1
module Main
  proc main(out bool done)
    if ready == true
      done = true
    end
    return
  end
end
`);

    expect(() => analyzeProcFunctionSemantics(decls)).toThrowError(
      expect.objectContaining({ code: "GRL_OUT_PARAM_NOT_ASSIGNED" })
    );
  });

  it("reports out and inout call arguments that are not lvalues", () => {
    const decls = declarations(`language grl 0.1
module Main
  proc set_done(out bool done)
    done = true
  end
  proc main()
    call set_done(true)
  end
end
`);

    expect(() => analyzeProcFunctionSemantics(decls)).toThrowError(
      expect.objectContaining({ code: "GRL_ARGUMENT_NOT_LVALUE" })
    );
  });

  it("reports missing or incompatible function returns", () => {
    const missingReturn = declarations(`language grl 0.1
module Main
  func int bad(in bool ready)
    if ready == true
      return 1
    end
  end
end
`);
    const wrongReturn = declarations(`language grl 0.1
module Main
  func bool bad()
    return 1
  end
end
`);

    expect(() => analyzeProcFunctionSemantics(missingReturn)).toThrowError(
      expect.objectContaining({ code: "GRL_FUNC_MISSING_RETURN" })
    );
    expect(() => analyzeProcFunctionSemantics(wrongReturn)).toThrowError(
      expect.objectContaining({ code: "GRL_RETURN_TYPE_MISMATCH" })
    );
  });

  it("reports illegal function side effects and procedure return values", () => {
    const functionSideEffect = declarations(`language grl 0.1
module Main
  func bool bad()
    wait io.di[1] == true
    return true
  end
end
`);
    const procedureReturnValue = declarations(`language grl 0.1
module Main
  proc main()
    return true
  end
end
`);

    expect(() => analyzeProcFunctionSemantics(functionSideEffect)).toThrowError(
      expect.objectContaining({ code: "GRL_FUNC_SIDE_EFFECT" })
    );
    expect(() => analyzeProcFunctionSemantics(procedureReturnValue)).toThrowError(
      expect.objectContaining({ code: "GRL_RETURN_VALUE_IN_PROC" })
    );
  });

  it("reports call target and argument type errors", () => {
    const missingCall = declarations(`language grl 0.1
module Main
  proc main()
    call missing()
  end
end
`);
    const typeMismatch = declarations(`language grl 0.1
module Main
  proc expects_int(in int value)
    return
  end
  proc main()
    call expects_int("bad")
  end
end
`);

    expect(() => analyzeProcFunctionSemantics(missingCall)).toThrowError(
      expect.objectContaining({ code: "GRL_CALL_TARGET_NOT_FOUND" })
    );
    expect(() => analyzeProcFunctionSemantics(typeMismatch)).toThrowError(
      expect.objectContaining({ code: "GRL_CALL_ARGUMENT_TYPE" })
    );
  });
});
