import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { usePageVisibility } from "../usePageVisibility";

describe("usePageVisibility", () => {
  let visibilityChangeListeners: Array<(event: Event) => void> = [];

  beforeEach(() => {
    visibilityChangeListeners = [];

    // Mock document.hidden
    Object.defineProperty(document, "hidden", {
      configurable: true,
      get: vi.fn(() => false),
    });

    // Mock addEventListener
    vi.spyOn(document, "addEventListener").mockImplementation((event, listener) => {
      if (event === "visibilitychange") {
        visibilityChangeListeners.push(listener as (event: Event) => void);
      }
    });

    vi.spyOn(document, "removeEventListener");
  });

  afterEach(() => {
    vi.clearAllMocks();
    visibilityChangeListeners = [];
  });

  it("initializes with correct visibility state", () => {
    const { result } = renderHook(() => usePageVisibility());
    expect(result.current).toBe(true); // not hidden on init
  });

  it("returns false when document.hidden is true", () => {
    (Object.getOwnPropertyDescriptor(document, "hidden")?.get as any).mockReturnValue(true);

    const { result } = renderHook(() => usePageVisibility());
    expect(result.current).toBe(false);
  });

  it("attaches visibilitychange listener on mount", () => {
    renderHook(() => usePageVisibility());

    expect(document.addEventListener).toHaveBeenCalledWith(
      "visibilitychange",
      expect.any(Function),
    );
  });

  it("removes visibilitychange listener on unmount", () => {
    const { unmount } = renderHook(() => usePageVisibility());

    unmount();

    expect(document.removeEventListener).toHaveBeenCalledWith("visibilitychange", expect.any(Function));
  });

  it("updates state when page becomes hidden", () => {
    const { result } = renderHook(() => usePageVisibility());

    expect(result.current).toBe(true);

    // Simulate visibility change
    act(() => {
      (Object.getOwnPropertyDescriptor(document, "hidden")?.get as any).mockReturnValue(true);
      visibilityChangeListeners.forEach((listener) => {
        listener(new Event("visibilitychange"));
      });
    });

    expect(result.current).toBe(false);
  });

  it("updates state when page becomes visible", () => {
    (Object.getOwnPropertyDescriptor(document, "hidden")?.get as any).mockReturnValue(true);
    const { result } = renderHook(() => usePageVisibility());
    expect(result.current).toBe(false);

    // Simulate visibility change
    act(() => {
      (Object.getOwnPropertyDescriptor(document, "hidden")?.get as any).mockReturnValue(false);
      visibilityChangeListeners.forEach((listener) => {
        listener(new Event("visibilitychange"));
      });
    });

    expect(result.current).toBe(true);
  });

  it("handles multiple rapid visibility changes", () => {
    const { result } = renderHook(() => usePageVisibility());

    act(() => {
      // Hidden
      (Object.getOwnPropertyDescriptor(document, "hidden")?.get as any).mockReturnValue(true);
      visibilityChangeListeners.forEach((listener) => listener(new Event("visibilitychange")));
      // Visible
      (Object.getOwnPropertyDescriptor(document, "hidden")?.get as any).mockReturnValue(false);
      visibilityChangeListeners.forEach((listener) => listener(new Event("visibilitychange")));
      // Hidden again
      (Object.getOwnPropertyDescriptor(document, "hidden")?.get as any).mockReturnValue(true);
      visibilityChangeListeners.forEach((listener) => listener(new Event("visibilitychange")));
    });

    expect(result.current).toBe(false);
  });
});
