/**
 * @vitest-environment happy-dom
 */
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useRef } from "react";
import { useClickOutside } from "./useClickOutside";

function fireMouseDown(target: EventTarget) {
  target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
}

describe("useClickOutside", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("does not call handler when enabled is false", () => {
    const handler = vi.fn();
    renderHook(() => {
      const ref = useRef<HTMLDivElement>(document.createElement("div"));
      useClickOutside([ref], handler, false);
      return ref;
    });

    act(() => fireMouseDown(document.body));

    expect(handler).not.toHaveBeenCalled();
  });

  it("calls handler when clicking outside all refs", () => {
    const handler = vi.fn();
    const el = document.createElement("div");
    document.body.appendChild(el);

    renderHook(() => {
      const ref = useRef<HTMLDivElement>(el);
      useClickOutside([ref], handler, true);
    });

    act(() => fireMouseDown(document.body));

    expect(handler).toHaveBeenCalledTimes(1);
    document.body.removeChild(el);
  });

  it("does not call handler when clicking inside the ref element", () => {
    const handler = vi.fn();
    const el = document.createElement("div");
    document.body.appendChild(el);

    renderHook(() => {
      const ref = useRef<HTMLDivElement>(el);
      useClickOutside([ref], handler, true);
    });

    act(() => fireMouseDown(el));

    expect(handler).not.toHaveBeenCalled();
    document.body.removeChild(el);
  });

  it("does not call handler when clicking inside a child of the ref element", () => {
    const handler = vi.fn();
    const parent = document.createElement("div");
    const child = document.createElement("button");
    parent.appendChild(child);
    document.body.appendChild(parent);

    renderHook(() => {
      const ref = useRef<HTMLDivElement>(parent);
      useClickOutside([ref], handler, true);
    });

    act(() => fireMouseDown(child));

    expect(handler).not.toHaveBeenCalled();
    document.body.removeChild(parent);
  });

  it("does not call handler when clicking inside any of multiple refs", () => {
    const handler = vi.fn();
    const el1 = document.createElement("div");
    const el2 = document.createElement("div");
    document.body.appendChild(el1);
    document.body.appendChild(el2);

    renderHook(() => {
      const ref1 = useRef<HTMLDivElement>(el1);
      const ref2 = useRef<HTMLDivElement>(el2);
      useClickOutside([ref1, ref2], handler, true);
    });

    act(() => fireMouseDown(el1));
    expect(handler).not.toHaveBeenCalled();

    act(() => fireMouseDown(el2));
    expect(handler).not.toHaveBeenCalled();

    document.body.removeChild(el1);
    document.body.removeChild(el2);
  });

  it("stops calling handler after enabled becomes false", () => {
    const handler = vi.fn();
    const el = document.createElement("div");
    document.body.appendChild(el);

    const { rerender } = renderHook(
      ({ enabled }: { enabled: boolean }) => {
        const ref = useRef<HTMLDivElement>(el);
        useClickOutside([ref], handler, enabled);
      },
      { initialProps: { enabled: true } },
    );

    act(() => fireMouseDown(document.body));
    expect(handler).toHaveBeenCalledTimes(1);

    rerender({ enabled: false });

    act(() => fireMouseDown(document.body));
    expect(handler).toHaveBeenCalledTimes(1); // no additional calls

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

  it("removes listener on unmount", () => {
    const handler = vi.fn();
    const el = document.createElement("div");
    document.body.appendChild(el);

    const { unmount } = renderHook(() => {
      const ref = useRef<HTMLDivElement>(el);
      useClickOutside([ref], handler, true);
    });

    unmount();

    act(() => fireMouseDown(document.body));
    expect(handler).not.toHaveBeenCalled();

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