import { describe, it, expect, beforeEach, vi } from "vitest";
import { loadCode, createDebouncedCodeSave, clearCodeStorage } from "../codestorage";

// ---------------------------------------------------------------------------
// localStorage mock
// ---------------------------------------------------------------------------
const store = {};
const localStorageMock = {
  getItem: vi.fn((k) => store[k] ?? null),
  setItem: vi.fn((k, v) => { store[k] = v; }),
  removeItem: vi.fn((k) => { delete store[k]; }),
};
vi.stubGlobal("localStorage", localStorageMock);

beforeEach(() => {
  Object.keys(store).forEach((k) => delete store[k]);
  vi.clearAllMocks();
});

// ---------------------------------------------------------------------------
describe("loadCode()", () => {
  it("devolve null quando a chave não existe", () => {
    expect(loadCode("absent")).toBeNull();
  });

  it("devolve o código quando válido", () => {
    store["k"] = JSON.stringify("console.log('oi')");
    expect(loadCode("k")).toBe("console.log('oi')");
  });

  it("devolve null quando o valor não é string", () => {
    store["k"] = JSON.stringify({ not: "a string" });
    expect(loadCode("k")).toBeNull();
  });

  it("devolve null e remove chave quando o JSON está corrompido", () => {
    store["k"] = "{ broken";
    expect(loadCode("k")).toBeNull();
    expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
  });
});

describe("clearCodeStorage()", () => {
  it("remove a chave do localStorage", () => {
    store["k"] = JSON.stringify("código");
    clearCodeStorage("k");
    expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
  });
});

describe("createDebouncedCodeSave()", () => {
  it("retorna uma função de save", () => {
    expect(typeof createDebouncedCodeSave()).toBe("function");
  });

  it("instâncias diferentes são independentes", () => {
    expect(createDebouncedCodeSave()).not.toBe(createDebouncedCodeSave());
  });

  it("quando invocada com delay=0 persiste código válido", async () => {
    const save = createDebouncedCodeSave(0);
    save("k", "let x = 1;");
    await new Promise((r) => setTimeout(r, 10));
    expect(localStorageMock.setItem).toHaveBeenCalledWith(
      "k",
      JSON.stringify("let x = 1;"),
    );
  });

  it("quando invocada com delay=0 remove chave para string vazia", async () => {
    const save = createDebouncedCodeSave(0);
    save("k", "");
    await new Promise((r) => setTimeout(r, 10));
    expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
    expect(localStorageMock.setItem).not.toHaveBeenCalled();
  });

  it("quando invocada com delay=0 remove chave para string em branco", async () => {
    const save = createDebouncedCodeSave(0);
    save("k", "   ");
    await new Promise((r) => setTimeout(r, 10));
    expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
    expect(localStorageMock.setItem).not.toHaveBeenCalled();
  });

  it("quando invocada com delay=0 remove chave para valor não-string", async () => {
    const save = createDebouncedCodeSave(0);
    save("k", null);
    await new Promise((r) => setTimeout(r, 10));
    expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
    expect(localStorageMock.setItem).not.toHaveBeenCalled();
  });
});
