import { describe, it, expect, vi, beforeEach } from "vitest";

// ---------------------------------------------------------------------------
// vi.hoisted garante que mockGenerator e inicializado antes do vi.mock ser
// executado (vi.mock e hoisted para o topo do arquivo pelo compilador).
// ---------------------------------------------------------------------------
const { mockForBlock, mockGenerator } = vi.hoisted(() => {
  const mockForBlock = {};
  const mockGenerator = {
    forBlock: mockForBlock,
    STATEMENT_PREFIX: "",
    ORDER_ATOMIC: 0,
    ORDER_ASSIGNMENT: 3,
    ORDER_FUNCTION_CALL: 2,
    addReservedWords: vi.fn(),
    valueToCode: vi.fn(() => "42"),
  };
  return { mockForBlock, mockGenerator };
});

vi.mock("blockly/javascript", () => ({
  javascriptGenerator: mockGenerator,
}));

import {
  configurarGerador,
  gerarExpressao,
  gerarStatement,
  gerarStatementInline,
  gerarStatementComCampo,
  gerarExpressaoComCampo,
  gerarStatementComValor,
} from "../generator";

beforeEach(() => {
  Object.keys(mockForBlock).forEach((k) => delete mockForBlock[k]);
  mockGenerator.STATEMENT_PREFIX = "";
  vi.clearAllMocks();
});

describe("configurarGerador()", () => {
  it("define STATEMENT_PREFIX no gerador padrao", () => {
    configurarGerador();
    expect(mockGenerator.STATEMENT_PREFIX).toBe("highlightBlock(%1);\n");
  });

  it("adiciona 'highlightBlock' as palavras reservadas", () => {
    configurarGerador();
    expect(mockGenerator.addReservedWords).toHaveBeenCalledWith("highlightBlock");
  });

  it("aceita gerador customizado", () => {
    const custom = { STATEMENT_PREFIX: "", addReservedWords: vi.fn() };
    configurarGerador(custom);
    expect(custom.STATEMENT_PREFIX).toBe("highlightBlock(%1);\n");
    expect(custom.addReservedWords).toHaveBeenCalledWith("highlightBlock");
  });
});

describe("gerarExpressao()", () => {
  it("registra forBlock que retorna [codigo, ORDER_ATOMIC] por default", () => {
    gerarExpressao("bloco_var", "passos");
    const result = mockForBlock["bloco_var"]();
    expect(result).toEqual(["passos", mockGenerator.ORDER_ATOMIC]);
  });

  it("usa precedencia customizada quando fornecida", () => {
    gerarExpressao("bloco_fn", "fn()", mockGenerator.ORDER_FUNCTION_CALL);
    const result = mockForBlock["bloco_fn"]();
    expect(result[1]).toBe(mockGenerator.ORDER_FUNCTION_CALL);
  });

  it("registra no blockId correto", () => {
    gerarExpressao("meu_bloco", "x");
    expect(mockForBlock["meu_bloco"]).toBeDefined();
  });
});

describe("gerarStatement()", () => {
  it("registra forBlock que gera 'funcao();\\n'", () => {
    gerarStatement("robo_mover", "mover");
    expect(mockForBlock["robo_mover"]()).toBe("mover();\n");
  });

  it("usa o nome da funcao fornecido", () => {
    gerarStatement("robo_virar", "virar");
    expect(mockForBlock["robo_virar"]()).toBe("virar();\n");
  });
});

describe("gerarStatementInline()", () => {
  it("registra forBlock que gera 'codigo;\\n'", () => {
    gerarStatementInline("robo_change", "passos = passos + 1");
    expect(mockForBlock["robo_change"]()).toBe("passos = passos + 1;\n");
  });
});

describe("gerarStatementComCampo()", () => {
  it("registra forBlock que usa getFieldValue para gerar codigo", () => {
    gerarStatementComCampo("robo_virar", "virar", "DIRECAO");
    const fakeBlock = { getFieldValue: vi.fn(() => "DIREITA") };
    const code = mockForBlock["robo_virar"](fakeBlock);
    expect(fakeBlock.getFieldValue).toHaveBeenCalledWith("DIRECAO");
    expect(code).toBe("virar('DIREITA');\n");
  });
});

describe("gerarExpressaoComCampo()", () => {
  it("registra forBlock que retorna [codigo, precedencia]", () => {
    gerarExpressaoComCampo(
      "robo_sensor",
      "caminhoBloqueado",
      "SENTIDO",
      mockGenerator.ORDER_FUNCTION_CALL
    );
    const fakeBlock = { getFieldValue: vi.fn(() => "FRENTE") };
    const result = mockForBlock["robo_sensor"](fakeBlock);
    expect(fakeBlock.getFieldValue).toHaveBeenCalledWith("SENTIDO");
    expect(result).toEqual([
      "caminhoBloqueado('FRENTE')",
      mockGenerator.ORDER_FUNCTION_CALL,
    ]);
  });
});

describe("gerarStatementComValor()", () => {
  it("registra forBlock que chama valueToCode e aplica template", () => {
    gerarStatementComValor("robo_set", "VALOR", (v) => `var passos = ${v}`);
    const fakeBlock = {};
    mockGenerator.valueToCode.mockReturnValueOnce("10");
    const code = mockForBlock["robo_set"](fakeBlock);
    expect(mockGenerator.valueToCode).toHaveBeenCalledWith(
      fakeBlock,
      "VALOR",
      mockGenerator.ORDER_ASSIGNMENT
    );
    expect(code).toBe("var passos = 10;\n");
  });

  it("usa '0' como fallback quando valueToCode retorna string vazia", () => {
    gerarStatementComValor("robo_set2", "VALOR", (v) => `x = ${v}`);
    mockGenerator.valueToCode.mockReturnValueOnce("");
    const code = mockForBlock["robo_set2"]({});
    expect(code).toBe("x = 0;\n");
  });
});
