import { describe, it, expect, vi } from "vitest";
import {
  criarToolboxBase,
  criarCategoria,
  filtrarCategoriasVazias,
  adicionarBlocoCategoria,
  criarDefinicaoBloco,
  validarBloco,
  gerarToolboxDeEstrutura,
} from "../toolbox";

vi.mock("@/blockly/blocklyColors", () => ({
  obterCorCategoria: vi.fn(() => 210),
}));

describe("criarToolboxBase()", () => {
  it("retorna objeto com kind 'categoryToolbox' e contents vazio", () => {
    const tb = criarToolboxBase();
    expect(tb).toEqual({ kind: "categoryToolbox", contents: [] });
  });

  it("cada chamada retorna um novo objeto", () => {
    expect(criarToolboxBase()).not.toBe(criarToolboxBase());
  });
});

describe("criarCategoria()", () => {
  it("retorna objeto com kind 'category' e contents vazio", () => {
    const cat = criarCategoria("Movimento");
    expect(cat.kind).toBe("category");
    expect(cat.name).toBe("Movimento");
    expect(cat.contents).toEqual([]);
  });

  it("usa cor fornecida quando especificada", () => {
    const cat = criarCategoria("X", 180);
    expect(cat.colour).toBe(180);
  });

  it("usa obterCorCategoria quando cor e null", () => {
    const cat = criarCategoria("Logica");
    expect(cat.colour).toBe(210);
  });

  it("adiciona cssConfig quando cssContainer e fornecido", () => {
    const cat = criarCategoria("X", null, "cat_x");
    expect(cat.cssConfig).toEqual({ container: "cat_x" });
  });

  it("nao adiciona cssConfig quando cssContainer e omitido", () => {
    const cat = criarCategoria("X");
    expect(cat.cssConfig).toBeUndefined();
  });
});

describe("filtrarCategoriasVazias()", () => {
  it("remove categorias sem blocos", () => {
    const tb = {
      contents: [
        { kind: "category", contents: [] },
        { kind: "category", contents: [{ kind: "block" }] },
      ],
    };
    filtrarCategoriasVazias(tb);
    expect(tb.contents).toHaveLength(1);
  });

  it("remove categorias sem propriedade contents", () => {
    const tb = { contents: [{ kind: "category" }, { kind: "category", contents: [{}] }] };
    filtrarCategoriasVazias(tb);
    expect(tb.contents).toHaveLength(1);
  });

  it("mantem toolbox com todas as categorias preenchidas", () => {
    const tb = {
      contents: [
        { contents: [{ kind: "block" }] },
        { contents: [{ kind: "block" }] },
      ],
    };
    filtrarCategoriasVazias(tb);
    expect(tb.contents).toHaveLength(2);
  });

  it("muta e retorna o mesmo objeto toolbox", () => {
    const tb = { contents: [] };
    expect(filtrarCategoriasVazias(tb)).toBe(tb);
  });
});

describe("adicionarBlocoCategoria()", () => {
  it("adiciona um bloco a categoria pelo indice", () => {
    const tb = criarToolboxBase();
    tb.contents.push(criarCategoria("A"));
    const bloco = { kind: "block", type: "mover" };
    adicionarBlocoCategoria(tb, 0, bloco);
    expect(tb.contents[0].contents).toHaveLength(1);
    expect(tb.contents[0].contents[0]).toBe(bloco);
  });

  it("nao faz nada quando categoryIndex esta fora do range", () => {
    const tb = criarToolboxBase();
    expect(() => adicionarBlocoCategoria(tb, 99, { kind: "block" })).not.toThrow();
  });

  it("nao faz nada quando blockDef e falsy", () => {
    const tb = criarToolboxBase();
    tb.contents.push(criarCategoria("A"));
    adicionarBlocoCategoria(tb, 0, null);
    expect(tb.contents[0].contents).toHaveLength(0);
  });

  it("nao faz nada quando categoryIndex e undefined", () => {
    const tb = criarToolboxBase();
    tb.contents.push(criarCategoria("A"));
    adicionarBlocoCategoria(tb, undefined, { kind: "block" });
    expect(tb.contents[0].contents).toHaveLength(0);
  });
});

describe("criarDefinicaoBloco()", () => {
  it("retorna objeto com kind 'block' e type correto", () => {
    expect(criarDefinicaoBloco("mover")).toEqual({ kind: "block", type: "mover" });
  });

  it("nao inclui inputs quando nao fornecido", () => {
    expect(criarDefinicaoBloco("x").inputs).toBeUndefined();
  });

  it("inclui inputs quando fornecidos", () => {
    const inputs = { FROM: { shadow: { type: "math_number" } } };
    expect(criarDefinicaoBloco("x", inputs).inputs).toBe(inputs);
  });
});

describe("validarBloco()", () => {
  it("retorna definicao quando bloco existe", () => {
    const defs = { mover: { code: "move()" } };
    expect(validarBloco("mover", defs)).toEqual({ code: "move()" });
  });

  it("retorna null quando bloco nao existe", () => {
    expect(validarBloco("inexistente", {})).toBeNull();
  });

  it("emite console.warn quando bloco nao existe", () => {
    const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
    validarBloco("x", {});
    expect(spy).toHaveBeenCalledWith(expect.stringContaining('"x"'));
    spy.mockRestore();
  });
});

describe("gerarToolboxDeEstrutura()", () => {
  const estrutura = [
    { nome: "Logica", blocos: ["robo_if", "robo_if_else"] },
    { nome: "Movimento", cor: 210, blocos: ["robo_mover", "robo_virar"] },
  ];

  it("retorna toolbox com apenas blocos permitidos", () => {
    const tb = gerarToolboxDeEstrutura(estrutura, ["robo_if", "robo_mover"]);
    expect(tb.kind).toBe("categoryToolbox");
    expect(tb.contents).toHaveLength(2);
    expect(tb.contents[0].contents[0].type).toBe("robo_if");
    expect(tb.contents[1].contents[0].type).toBe("robo_mover");
  });

  it("omite categorias onde nenhum bloco e permitido", () => {
    const tb = gerarToolboxDeEstrutura(estrutura, ["robo_mover"]);
    expect(tb.contents).toHaveLength(1);
    expect(tb.contents[0].name).toBe("Movimento");
  });

  it("retorna toolbox vazia quando allowedBlocks esta vazio", () => {
    const tb = gerarToolboxDeEstrutura(estrutura, []);
    expect(tb.contents).toHaveLength(0);
  });

  it("inclui todos os blocos permitidos de uma categoria", () => {
    const tb = gerarToolboxDeEstrutura(estrutura, ["robo_mover", "robo_virar"]);
    expect(tb.contents[0].contents).toHaveLength(2);
  });

  it("cada bloco na toolbox tem kind 'block'", () => {
    const tb = gerarToolboxDeEstrutura(estrutura, ["robo_if"]);
    expect(tb.contents[0].contents[0].kind).toBe("block");
  });

  it("passa cssContainer quando definido na estrutura", () => {
    const est = [{ nome: "Cat", cssContainer: "cat_css", blocos: ["b1"] }];
    const tb = gerarToolboxDeEstrutura(est, ["b1"]);
    expect(tb.contents[0].cssConfig).toEqual({ container: "cat_css" });
  });
});
