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

vi.mock("../isMobile", () => ({ isMobileViewport: vi.fn(() => false) }));
import { isMobileViewport } from "../isMobile";

import {
  createWelcomeStep,
  createGameAreaStep,
  createToolboxStep,
  createWorkspaceStep,
  createRunButtonStep,
  createResetInfoStep,
  createPhaseSelectorStep,
  createPhaseInfoStep,
  createHelpButtonStep,
  createCodeEditorStep,
  createAvailableFunctionsStep,
  createControlStructuresStep,
  createCodeExampleStep,
  gameIcons,
  defaultGameTourOptions,
} from "../tourHelpers";

// Helper: mock Shepherd "tour" object for action() calls
function mockTour() {
  return { back: vi.fn(), next: vi.fn(), complete: vi.fn() };
}

beforeEach(() => {
  isMobileViewport.mockReturnValue(false);
  document.body.innerHTML = "";
});

afterEach(() => {
  document.body.innerHTML = "";
});

// ── createWelcomeStep ────────────────────────────────────────────────────────
describe("createWelcomeStep", () => {
  it("returns id 'welcome'", () => {
    expect(createWelcomeStep({ gameName: "G", description: "D", iconSvg: "" }).id).toBe("welcome");
  });

  it("includes gameName in text", () => {
    const step = createWelcomeStep({ gameName: "Semáforo", description: "D", iconSvg: "" });
    expect(step.text).toContain("Semáforo");
  });

  it("includes description in text", () => {
    const step = createWelcomeStep({ gameName: "G", description: "Aprenda", iconSvg: "" });
    expect(step.text).toContain("Aprenda");
  });

  it("includes challenge when provided", () => {
    const step = createWelcomeStep({ gameName: "G", description: "D", challenge: "Faça X", iconSvg: "" });
    expect(step.text).toContain("Faça X");
  });

  it("omits challenge block when not provided", () => {
    const step = createWelcomeStep({ gameName: "G", description: "D", iconSvg: "" });
    expect(step.text).not.toContain("Desafio");
  });

  it("includes iconSvg in text", () => {
    const step = createWelcomeStep({ gameName: "G", description: "D", iconSvg: "<svg/>" });
    expect(step.text).toContain("<svg/>");
  });

  it("has two buttons", () => {
    expect(createWelcomeStep({ gameName: "G", description: "D", iconSvg: "" }).buttons).toHaveLength(2);
  });

  it("first button text is 'Pular Tour'", () => {
    const step = createWelcomeStep({ gameName: "G", description: "D", iconSvg: "" });
    expect(step.buttons[0].text).toBe("Pular Tour");
  });

  it("second button text is 'Começar Tour'", () => {
    const step = createWelcomeStep({ gameName: "G", description: "D", iconSvg: "" });
    expect(step.buttons[1].text).toBe("Começar Tour");
  });

  it("first button action calls this.complete()", () => {
    const step = createWelcomeStep({ gameName: "G", description: "D", iconSvg: "" });
    const t = mockTour();
    step.buttons[0].action.call(t);
    expect(t.complete).toHaveBeenCalled();
  });

  it("second button action calls this.next()", () => {
    const step = createWelcomeStep({ gameName: "G", description: "D", iconSvg: "" });
    const t = mockTour();
    step.buttons[1].action.call(t);
    expect(t.next).toHaveBeenCalled();
  });
});

// ── createGameAreaStep ───────────────────────────────────────────────────────
describe("createGameAreaStep", () => {
  it("returns id 'game-area'", () => {
    expect(createGameAreaStep({ description: "D" }).id).toBe("game-area");
  });

  it("uses provided selector", () => {
    expect(createGameAreaStep({ description: "D", selector: ".my" }).attachTo.element).toBe(".my");
  });

  it("defaults selector to '.game-area-container'", () => {
    expect(createGameAreaStep({ description: "D" }).attachTo.element).toBe(".game-area-container");
  });

  it("defaults title to 'Área do Jogo'", () => {
    expect(createGameAreaStep({ description: "D" }).text).toContain("Área do Jogo");
  });

  it("uses custom title", () => {
    expect(createGameAreaStep({ title: "Minha Área", description: "D" }).text).toContain("Minha Área");
  });

  it("includes description", () => {
    expect(createGameAreaStep({ description: "Clique aqui" }).text).toContain("Clique aqui");
  });

  it("uses desktop position 'right'", () => {
    isMobileViewport.mockReturnValue(false);
    expect(createGameAreaStep({ description: "D" }).attachTo.on).toBe("right");
  });

  it("uses mobile position 'bottom'", () => {
    isMobileViewport.mockReturnValue(true);
    expect(createGameAreaStep({ description: "D" }).attachTo.on).toBe("bottom");
  });

  it("first button (Voltar) calls this.back()", () => {
    const t = mockTour();
    createGameAreaStep({ description: "D" }).buttons[0].action.call(t);
    expect(t.back).toHaveBeenCalled();
  });

  it("second button (Próximo) calls this.next()", () => {
    const t = mockTour();
    createGameAreaStep({ description: "D" }).buttons[1].action.call(t);
    expect(t.next).toHaveBeenCalled();
  });
});

// ── createToolboxStep DOM branches ───────────────────────────────────────────
describe("createToolboxStep", () => {
  it("returns id 'blockly-toolbox'", () => {
    expect(createToolboxStep({}).id).toBe("blockly-toolbox");
  });

  it("includes description", () => {
    expect(createToolboxStep({ description: "Use os blocos" }).text).toContain("Use os blocos");
  });

  it("attachTo.element is a function", () => {
    expect(typeof createToolboxStep({}).attachTo.element).toBe("function");
  });

  it("returns null when no blockly DOM exists", () => {
    expect(createToolboxStep({}).attachTo.element()).toBeNull();
  });

  it("returns .blocklyToolboxDiv when present", () => {
    const el = document.createElement("div");
    el.className = "blocklyToolboxDiv";
    document.body.appendChild(el);
    expect(createToolboxStep({}).attachTo.element()).toBe(el);
  });

  it("falls through to .blocklyToolboxContents", () => {
    const el = document.createElement("div");
    el.className = "blocklyToolboxContents";
    document.body.appendChild(el);
    expect(createToolboxStep({}).attachTo.element()).toBe(el);
  });

  it("falls through to .blocklyTreeRoot", () => {
    const el = document.createElement("div");
    el.className = "blocklyTreeRoot";
    document.body.appendChild(el);
    expect(createToolboxStep({}).attachTo.element()).toBe(el);
  });

  it("uses .blocklyToolboxCategoryContainer with closest ancestor", () => {
    const parent = document.createElement("div");
    parent.className = "blocklyToolboxContents";
    const child = document.createElement("div");
    child.className = "blocklyToolboxCategoryContainer";
    parent.appendChild(child);
    document.body.appendChild(parent);
    expect(createToolboxStep({}).attachTo.element()).toBe(parent);
  });

  it("uses .blocklyToolboxCategory when CategoryContainer absent", () => {
    const parent = document.createElement("div");
    parent.className = "blocklyTreeRoot";
    const child = document.createElement("div");
    child.className = "blocklyToolboxCategory";
    parent.appendChild(child);
    document.body.appendChild(parent);
    expect(createToolboxStep({}).attachTo.element()).toBe(parent);
  });

  it("falls through to parentElement.parentElement when no closest found", () => {
    const grandparent = document.createElement("div");
    const parent = document.createElement("div");
    const child = document.createElement("div");
    child.className = "blocklyToolboxCategoryContainer";
    parent.appendChild(child);
    grandparent.appendChild(parent);
    document.body.appendChild(grandparent);
    expect(createToolboxStep({}).attachTo.element()).toBe(grandparent);
  });

  it("falls through to .blocklyFlyout", () => {
    const el = document.createElement("div");
    el.className = "blocklyFlyout";
    document.body.appendChild(el);
    expect(createToolboxStep({}).attachTo.element()).toBe(el);
  });

  it("falls through to blocklyDiv inner [role=tree]", () => {
    const div = document.createElement("div");
    div.className = "blocklyDiv";
    const inner = document.createElement("div");
    inner.setAttribute("role", "tree");
    div.appendChild(inner);
    document.body.appendChild(div);
    expect(createToolboxStep({}).attachTo.element()).toBe(inner);
  });

  it("falls through to blocklyDiv inner [class*=toolbox]", () => {
    const div = document.createElement("div");
    div.className = "blocklyDiv";
    const inner = document.createElement("div");
    inner.className = "blocklyToolboxInner";
    div.appendChild(inner);
    document.body.appendChild(div);
    expect(createToolboxStep({}).attachTo.element()).toBe(inner);
  });

  it("promotes element to parent when it has 'blocklyToolboxCategoryGroup' and parent has 'blocklyToolbox'", () => {
    const parent = document.createElement("div");
    parent.className = "blocklyToolbox";
    const child = document.createElement("div");
    child.className = "blocklyToolboxDiv blocklyToolboxCategoryGroup";
    parent.appendChild(child);
    document.body.appendChild(parent);
    expect(createToolboxStep({}).attachTo.element()).toBe(parent);
  });

  it("Voltar button calls this.back()", () => {
    const t = mockTour();
    createToolboxStep({}).buttons[0].action.call(t);
    expect(t.back).toHaveBeenCalled();
  });

  it("Próximo button calls this.next()", () => {
    const t = mockTour();
    createToolboxStep({}).buttons[1].action.call(t);
    expect(t.next).toHaveBeenCalled();
  });
});

// ── createWorkspaceStep DOM branches ─────────────────────────────────────────
describe("createWorkspaceStep", () => {
  it("returns id 'blockly-workspace'", () => {
    expect(createWorkspaceStep({}).id).toBe("blockly-workspace");
  });

  it("includes description", () => {
    expect(createWorkspaceStep({ description: "Arraste" }).text).toContain("Arraste");
  });

  it("attachTo.element is a function", () => {
    expect(typeof createWorkspaceStep({}).attachTo.element).toBe("function");
  });

  it("returns null when no blockly DOM exists", () => {
    expect(createWorkspaceStep({}).attachTo.element()).toBeNull();
  });

  it("returns .blocklyWorkspace when present", () => {
    const el = document.createElement("div");
    el.className = "blocklyWorkspace";
    document.body.appendChild(el);
    expect(createWorkspaceStep({}).attachTo.element()).toBe(el);
  });

  it("falls through to .blocklySvg", () => {
    const el = document.createElement("svg");
    el.className = "blocklySvg";
    document.body.appendChild(el);
    expect(createWorkspaceStep({}).attachTo.element()).toBe(el);
  });

  it("falls through to .blocklyDiv", () => {
    const el = document.createElement("div");
    el.className = "blocklyDiv";
    document.body.appendChild(el);
    expect(createWorkspaceStep({}).attachTo.element()).toBe(el);
  });

  it("uses 'left' position on desktop", () => {
    isMobileViewport.mockReturnValue(false);
    expect(createWorkspaceStep({}).attachTo.on).toBe("left");
  });

  it("uses 'bottom' position on mobile", () => {
    isMobileViewport.mockReturnValue(true);
    expect(createWorkspaceStep({}).attachTo.on).toBe("bottom");
  });

  it("Voltar button calls this.back()", () => {
    const t = mockTour();
    createWorkspaceStep({}).buttons[0].action.call(t);
    expect(t.back).toHaveBeenCalled();
  });

  it("Próximo button calls this.next()", () => {
    const t = mockTour();
    createWorkspaceStep({}).buttons[1].action.call(t);
    expect(t.next).toHaveBeenCalled();
  });
});

// ── simple data-tour-selector steps ─────────────────────────────────────────
const selectorSteps = [
  ["createRunButtonStep",     createRunButtonStep,     "run-button",     '[data-tour="run-button"]',     "bottom"],
  ["createResetInfoStep",     createResetInfoStep,     "reset-info",     '[data-tour="reset-button"]',   "bottom"],
  ["createPhaseSelectorStep", createPhaseSelectorStep, "phase-selector", '[data-tour="phase-selector"]', "bottom"],
  ["createHelpButtonStep",    createHelpButtonStep,    "help-button",    '[data-tour="help-button"]',    "top"],
];

for (const [name, fn, expectedId, expectedEl, expectedOn] of selectorSteps) {
  describe(name, () => {
    const step = () => fn({});

    it(`returns id '${expectedId}'`, () => {
      expect(step().id).toBe(expectedId);
    });

    it("uses the correct element selector", () => {
      expect(step().attachTo.element).toBe(expectedEl);
    });

    it(`uses attachment '${expectedOn}'`, () => {
      expect(step().attachTo.on).toBe(expectedOn);
    });

    it("includes description", () => {
      const s = fn({ description: "desc-" + name });
      expect(s.text).toContain("desc-" + name);
    });

    it("Voltar button calls this.back()", () => {
      const t = mockTour();
      step().buttons[0].action.call(t);
      expect(t.back).toHaveBeenCalled();
    });

    if (name === "createHelpButtonStep") {
      it("last button (Entendi!) calls this.complete()", () => {
        const t = mockTour();
        step().buttons[1].action.call(t);
        expect(t.complete).toHaveBeenCalled();
      });
    } else {
      it("Próximo button calls this.next()", () => {
        const t = mockTour();
        step().buttons[1].action.call(t);
        expect(t.next).toHaveBeenCalled();
      });
    }
  });
}

// ── createPhaseInfoStep ──────────────────────────────────────────────────────
describe("createPhaseInfoStep", () => {
  it("returns id 'phase-info'", () => {
    expect(createPhaseInfoStep({}).id).toBe("phase-info");
  });

  it("attaches to '.game-fase-info'", () => {
    expect(createPhaseInfoStep({}).attachTo.element).toBe(".game-fase-info");
  });

  it("includes description", () => {
    expect(createPhaseInfoStep({ description: "Info da fase" }).text).toContain("Info da fase");
  });

  it("Voltar button calls this.back()", () => {
    const t = mockTour();
    createPhaseInfoStep({}).buttons[0].action.call(t);
    expect(t.back).toHaveBeenCalled();
  });

  it("Próximo button calls this.next()", () => {
    const t = mockTour();
    createPhaseInfoStep({}).buttons[1].action.call(t);
    expect(t.next).toHaveBeenCalled();
  });
});

// ── createCodeEditorStep DOM branches ────────────────────────────────────────
describe("createCodeEditorStep", () => {
  it("returns id 'code-editor'", () => {
    expect(createCodeEditorStep({}).id).toBe("code-editor");
  });

  it("includes description", () => {
    const step = createCodeEditorStep({ description: "Escreva código" });
    expect(step.text).toContain("Escreva código");
  });

  it("includes additionalInfo when provided", () => {
    const step = createCodeEditorStep({ additionalInfo: "Dica extra" });
    expect(step.text).toContain("Dica extra");
  });

  it("omits additionalInfo paragraph when empty string", () => {
    const step = createCodeEditorStep({ additionalInfo: "" });
    expect(step.text).not.toContain("<p></p>");
  });

  it("attachTo.element is a function", () => {
    expect(typeof createCodeEditorStep({}).attachTo.element).toBe("function");
  });

  it("returns null when no editor DOM exists", () => {
    expect(createCodeEditorStep({}).attachTo.element()).toBeNull();
  });

  it("returns .cm-editor when present", () => {
    const el = document.createElement("div");
    el.className = "cm-editor";
    document.body.appendChild(el);
    expect(createCodeEditorStep({}).attachTo.element()).toBe(el);
  });

  it("falls through to .CodeMirror", () => {
    const el = document.createElement("div");
    el.className = "CodeMirror";
    document.body.appendChild(el);
    expect(createCodeEditorStep({}).attachTo.element()).toBe(el);
  });

  it("falls through to .code-editor-container", () => {
    const el = document.createElement("div");
    el.className = "code-editor-container";
    document.body.appendChild(el);
    expect(createCodeEditorStep({}).attachTo.element()).toBe(el);
  });

  it("uses 'left' position on desktop", () => {
    isMobileViewport.mockReturnValue(false);
    expect(createCodeEditorStep({}).attachTo.on).toBe("left");
  });

  it("Voltar button calls this.back()", () => {
    const t = mockTour();
    createCodeEditorStep({}).buttons[0].action.call(t);
    expect(t.back).toHaveBeenCalled();
  });

  it("Próximo button calls this.next()", () => {
    const t = mockTour();
    createCodeEditorStep({}).buttons[1].action.call(t);
    expect(t.next).toHaveBeenCalled();
  });
});

// ── createAvailableFunctionsStep ─────────────────────────────────────────────
describe("createAvailableFunctionsStep", () => {
  it("includes description", () => {
    const step = createAvailableFunctionsStep({ description: "Funções disponíveis" });
    expect(step.text).toContain("Funções disponíveis");
  });

  it("renders <ul> with function items when functions provided", () => {
    const step = createAvailableFunctionsStep({ functions: ["mover()", "virar()"] });
    expect(step.text).toContain("<li><code>mover()</code></li>");
    expect(step.text).toContain("<li><code>virar()</code></li>");
  });

  it("omits <ul> when functions array is empty", () => {
    const step = createAvailableFunctionsStep({ functions: [] });
    expect(step.text).not.toContain("<ul");
  });

  it("Voltar button calls this.back()", () => {
    const t = mockTour();
    createAvailableFunctionsStep({}).buttons[0].action.call(t);
    expect(t.back).toHaveBeenCalled();
  });

  it("Próximo button calls this.next()", () => {
    const t = mockTour();
    createAvailableFunctionsStep({}).buttons[1].action.call(t);
    expect(t.next).toHaveBeenCalled();
  });
});

// ── createControlStructuresStep ──────────────────────────────────────────────
describe("createControlStructuresStep", () => {
  it("includes description", () => {
    const step = createControlStructuresStep({ description: "Estruturas" });
    expect(step.text).toContain("Estruturas");
  });

  it("renders <ul> with structure items when provided", () => {
    const step = createControlStructuresStep({ structures: ["if", "for", "while"] });
    expect(step.text).toContain("<li><code>if</code></li>");
    expect(step.text).toContain("<li><code>for</code></li>");
    expect(step.text).toContain("<li><code>while</code></li>");
  });

  it("omits <ul> when structures array is empty", () => {
    const step = createControlStructuresStep({ structures: [] });
    expect(step.text).not.toContain("<ul");
  });

  it("Voltar button calls this.back()", () => {
    const t = mockTour();
    createControlStructuresStep({}).buttons[0].action.call(t);
    expect(t.back).toHaveBeenCalled();
  });

  it("Próximo button calls this.next()", () => {
    const t = mockTour();
    createControlStructuresStep({}).buttons[1].action.call(t);
    expect(t.next).toHaveBeenCalled();
  });
});

// ── createCodeExampleStep ────────────────────────────────────────────────────
describe("createCodeExampleStep", () => {
  it("includes description", () => {
    const step = createCodeExampleStep({ description: "Veja o exemplo" });
    expect(step.text).toContain("Veja o exemplo");
  });

  it("uses default title 'Exemplo de Código'", () => {
    expect(createCodeExampleStep({}).text).toContain("Exemplo de Código");
  });

  it("uses custom title", () => {
    const step = createCodeExampleStep({ title: "Meu Exemplo" });
    expect(step.text).toContain("Meu Exemplo");
  });

  it("includes custom example code in <pre>", () => {
    const step = createCodeExampleStep({ example: "mover(1);" });
    expect(step.text).toContain("mover(1);");
  });

  it("Voltar button calls this.back()", () => {
    const t = mockTour();
    createCodeExampleStep({}).buttons[0].action.call(t);
    expect(t.back).toHaveBeenCalled();
  });

  it("Próximo button calls this.next()", () => {
    const t = mockTour();
    createCodeExampleStep({}).buttons[1].action.call(t);
    expect(t.next).toHaveBeenCalled();
  });
});

// ── gameIcons ────────────────────────────────────────────────────────────────
describe("gameIcons", () => {
  const expectedKeys = ["robot", "bird", "traffic", "turtle", "motorcycle", "emoji", "sentiment", "mole", "lock"];

  it("exports an object with all expected icon keys", () => {
    expectedKeys.forEach((key) => expect(gameIcons).toHaveProperty(key));
  });

  it("each icon is a non-empty string containing <svg", () => {
    expectedKeys.forEach((key) => {
      expect(typeof gameIcons[key]).toBe("string");
      expect(gameIcons[key]).toContain("<svg");
    });
  });
});

// ── defaultGameTourOptions ───────────────────────────────────────────────────
describe("defaultGameTourOptions", () => {
  it("has useModalOverlay true", () => {
    expect(defaultGameTourOptions.useModalOverlay).toBe(true);
  });

  it("has cancelIcon enabled", () => {
    expect(defaultGameTourOptions.defaultStepOptions.cancelIcon.enabled).toBe(true);
  });

  it("has scrollTo configured", () => {
    expect(defaultGameTourOptions.defaultStepOptions.scrollTo).toBeDefined();
  });

  it("has classes set to 'game-tour-step'", () => {
    expect(defaultGameTourOptions.defaultStepOptions.classes).toBe("game-tour-step");
  });
});
