import { createRoot } from "solid-js";
import { describe, expect, it } from "vitest";
import { createFocusTrap } from "../../src/utilities/focus-trap/create-focus-trap";

describe("createFocusTrap", () => {
  it("focuses the first focusable element when activated", () => {
    const container = document.createElement("div");
    const button1 = document.createElement("button");
    const button2 = document.createElement("button");
    button1.textContent = "First";
    button2.textContent = "Second";
    container.appendChild(button1);
    container.appendChild(button2);
    document.body.appendChild(container);

    createRoot((dispose) => {
      const trap = createFocusTrap(() => container);
      trap.activate();
      expect(document.activeElement).toBe(button1);
      trap.deactivate();
      dispose();
    });

    document.body.removeChild(container);
  });

  it("does nothing when container is null", () => {
    createRoot((dispose) => {
      const trap = createFocusTrap(() => null);
      // Should not throw
      expect(() => trap.activate()).not.toThrow();
      dispose();
    });
  });

  it("returns focus to previously focused element on deactivate", () => {
    const outside = document.createElement("button");
    outside.textContent = "Outside";
    document.body.appendChild(outside);
    outside.focus();

    const container = document.createElement("div");
    const inner = document.createElement("button");
    inner.textContent = "Inner";
    container.appendChild(inner);
    document.body.appendChild(container);

    createRoot((dispose) => {
      const trap = createFocusTrap(() => container);
      trap.activate();
      expect(document.activeElement).toBe(inner);
      trap.deactivate();
      expect(document.activeElement).toBe(outside);
      dispose();
    });

    document.body.removeChild(outside);
    document.body.removeChild(container);
  });

  it("wraps Tab forward from last focusable to first", () => {
    const container = document.createElement("div");
    const button1 = document.createElement("button");
    const button2 = document.createElement("button");
    button1.textContent = "First";
    button2.textContent = "Second";
    container.appendChild(button1);
    container.appendChild(button2);
    document.body.appendChild(container);

    createRoot((dispose) => {
      const trap = createFocusTrap(() => container);
      trap.activate();
      button2.focus();
      const event = new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true });
      document.dispatchEvent(event);
      expect(document.activeElement).toBe(button1);
      trap.deactivate();
      dispose();
    });

    document.body.removeChild(container);
  });

  it("wraps Shift+Tab backward from first focusable to last", () => {
    const container = document.createElement("div");
    const button1 = document.createElement("button");
    const button2 = document.createElement("button");
    button1.textContent = "First";
    button2.textContent = "Second";
    container.appendChild(button1);
    container.appendChild(button2);
    document.body.appendChild(container);

    createRoot((dispose) => {
      const trap = createFocusTrap(() => container);
      trap.activate();
      // button1 is already focused after activate
      const event = new KeyboardEvent("keydown", {
        key: "Tab",
        shiftKey: true,
        bubbles: true,
        cancelable: true,
      });
      document.dispatchEvent(event);
      expect(document.activeElement).toBe(button2);
      trap.deactivate();
      dispose();
    });

    document.body.removeChild(container);
  });

  it("does not throw when container has no focusable elements", () => {
    const container = document.createElement("div");
    container.textContent = "No focusable children";
    document.body.appendChild(container);

    createRoot((dispose) => {
      const trap = createFocusTrap(() => container);
      trap.activate();
      // Tab should be prevented but not throw
      const event = new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true });
      expect(() => document.dispatchEvent(event)).not.toThrow();
      trap.deactivate();
      dispose();
    });

    document.body.removeChild(container);
  });

  it("second activate() is a no-op — does not attach duplicate listeners", () => {
    const container = document.createElement("div");
    const button = document.createElement("button");
    container.appendChild(button);
    document.body.appendChild(container);

    createRoot((dispose) => {
      const trap = createFocusTrap(() => container);
      trap.activate();
      trap.activate(); // second call should be ignored
      button.focus();
      // Tab from the only element — Tab wraps back to itself (first === last)
      // What matters is it doesn't fire twice causing erratic behaviour
      expect(() => {
        document.dispatchEvent(
          new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true }),
        );
      }).not.toThrow();
      trap.deactivate();
      dispose();
    });

    document.body.removeChild(container);
  });
});
