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

function procedure(source: string): GrlProcedureDeclaration {
  return parseGrl(source).module.declarations.find(
    (decl): decl is GrlProcedureDeclaration => decl.kind === "ProcedureDeclaration"
  )!;
}

describe("GRL control-flow compilation", () => {
  it("compiles if, elseif, else, while, for, switch, labels, and jumps", () => {
    const proc = procedure(`language grl 0.1
module Main
  proc main()
    label retry
    if ready == true
      wait io.di[1] == true
    elseif fault == true
      jump recovery
    else
      jump retry
    end
    while all(io.di[1] == true, io.di[2] == false)
      continue
    end
    for i = 1 to 3 step 1
      movej home
    end
    switch mode
      case 1
        break
      case 2
        jump done
      default
        jump recovery
    end
    label recovery
    label done
  end
end
`);

    const flow = parseProcedureControlFlow(proc);

    expect(flow).toEqual([
      expect.objectContaining({ kind: "LABEL", name: "retry", scopePath: [] }),
      expect.objectContaining({
        kind: "IF",
        branches: [
          expect.objectContaining({
            branchKind: "if",
            condition: expect.objectContaining({ text: "ready == true" }),
            body: [expect.objectContaining({ kind: "RAW_STATEMENT", text: "wait io . di [ 1 ] == true" })]
          }),
          expect.objectContaining({
            branchKind: "elseif",
            condition: expect.objectContaining({ text: "fault == true" }),
            body: [expect.objectContaining({ kind: "JUMP", label: "recovery" })]
          }),
          expect.objectContaining({
            branchKind: "else",
            body: [expect.objectContaining({ kind: "JUMP", label: "retry" })]
          })
        ]
      }),
      expect.objectContaining({
        kind: "WHILE",
        condition: expect.objectContaining({
          text: "all ( io . di [ 1 ] == true , io . di [ 2 ] == false )"
        }),
        body: [expect.objectContaining({ kind: "CONTINUE" })]
      }),
      expect.objectContaining({
        kind: "FOR",
        iterator: "i",
        from: expect.objectContaining({ text: "1" }),
        to: expect.objectContaining({ text: "3" }),
        step: expect.objectContaining({ text: "1" }),
        body: [expect.objectContaining({ kind: "RAW_STATEMENT", text: "movej home" })]
      }),
      expect.objectContaining({
        kind: "SWITCH",
        expression: expect.objectContaining({ text: "mode" }),
        cases: [
          expect.objectContaining({ caseKind: "case", value: 1, body: [expect.objectContaining({ kind: "BREAK" })] }),
          expect.objectContaining({ caseKind: "case", value: 2, body: [expect.objectContaining({ kind: "JUMP", label: "done" })] }),
          expect.objectContaining({ caseKind: "default", body: [expect.objectContaining({ kind: "JUMP", label: "recovery" })] })
        ]
      }),
      expect.objectContaining({ kind: "LABEL", name: "recovery", scopePath: [] }),
      expect.objectContaining({ kind: "LABEL", name: "done", scopePath: [] })
    ]);
  });

  it("reports non-boolean control conditions", () => {
    const proc = procedure(`language grl 0.1
module Main
  proc main()
    if 1
    end
  end
end
`);

    expect(() => parseProcedureControlFlow(proc)).toThrowError(
      expect.objectContaining({ code: "GRL_CONTROL_CONDITION_NOT_BOOL" })
    );
  });

  it("reports break and continue outside valid blocks", () => {
    const breakProc = procedure(`language grl 0.1
module Main
  proc main()
    break
  end
end
`);
    const continueProc = procedure(`language grl 0.1
module Main
  proc main()
    switch mode
      case 1
        continue
    end
  end
end
`);

    expect(() => parseProcedureControlFlow(breakProc)).toThrowError(
      expect.objectContaining({ code: "GRL_BREAK_OUTSIDE_FLOW" })
    );
    expect(() => parseProcedureControlFlow(continueProc)).toThrowError(
      expect.objectContaining({ code: "GRL_CONTINUE_OUTSIDE_LOOP" })
    );
  });

  it("reports duplicate or non-constant switch cases", () => {
    const duplicateProc = procedure(`language grl 0.1
module Main
  proc main()
    switch mode
      case 1
        break
      case 1
        break
    end
  end
end
`);
    const nonConstantProc = procedure(`language grl 0.1
module Main
  proc main()
    switch mode
      case mode + 1
        break
    end
  end
end
`);

    expect(() => parseProcedureControlFlow(duplicateProc)).toThrowError(
      expect.objectContaining({ code: "GRL_SWITCH_CASE_DUPLICATE" })
    );
    expect(() => parseProcedureControlFlow(nonConstantProc)).toThrowError(
      expect.objectContaining({ code: "GRL_SWITCH_CASE_NOT_CONSTANT" })
    );
  });

  it("reports labels that cannot be reached by jump", () => {
    const intoBlockProc = procedure(`language grl 0.1
module Main
  proc main()
    jump inner
    if ready == true
      label inner
    end
  end
end
`);
    const missingLabelProc = procedure(`language grl 0.1
module Main
  proc main()
    jump missing
  end
end
`);

    expect(() => parseProcedureControlFlow(intoBlockProc)).toThrowError(
      expect.objectContaining({ code: "GRL_JUMP_INTO_BLOCK" })
    );
    expect(() => parseProcedureControlFlow(missingLabelProc)).toThrowError(
      expect.objectContaining({ code: "GRL_LABEL_NOT_FOUND" })
    );
  });
});
