import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useRef } from 'react';
import { useIntersectionObserver } from './useIntersectionObserver';

// Mock IntersectionObserver
class MockIntersectionObserver {
  observe = vi.fn();
  disconnect = vi.fn();
  unobserve = vi.fn();

  constructor(
    public callback: IntersectionObserverCallback,
    public options?: IntersectionObserverInit,
  ) {}
}

describe('useIntersectionObserver', () => {
  let mockObserver: MockIntersectionObserver;

  beforeEach(() => {
    mockObserver = new MockIntersectionObserver(() => {});
    global.IntersectionObserver = MockIntersectionObserver as any;
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should return undefined when element ref is null', () => {
    const elementRef = { current: null };
    const { result } = renderHook(() =>
      useIntersectionObserver(elementRef as React.RefObject<Element>, {}),
    );

    // Initially undefined, and stays undefined when ref is null
    expect(result.current).toBeUndefined();
    expect(mockObserver.observe).not.toHaveBeenCalled();
  });

  it('should observe element when ref is provided', () => {
    const element = document.createElement('div');
    const elementRef = { current: element };

    renderHook(() =>
      useIntersectionObserver(elementRef as React.RefObject<Element>, {}),
    );

    // Wait for effect to run
    expect(mockObserver.observe).toHaveBeenCalled();
  });

  it('should disconnect observer on unmount', () => {
    const element = document.createElement('div');
    const elementRef = { current: element };

    const { unmount } = renderHook(() =>
      useIntersectionObserver(elementRef as React.RefObject<Element>, {}),
    );

    unmount();

    expect(mockObserver.disconnect).toHaveBeenCalled();
  });

  it('should use default options', () => {
    const element = document.createElement('div');
    const elementRef = { current: element };

    renderHook(() =>
      useIntersectionObserver(elementRef as React.RefObject<Element>, {}),
    );

    // Verify IntersectionObserver was called
    expect(MockIntersectionObserver).toHaveBeenCalled();
    const call = (MockIntersectionObserver as any).mock.calls[0];
    expect(call[1]).toMatchObject({
      threshold: 0,
      root: null,
      rootMargin: '0%',
    });
  });

  it('should use custom options', () => {
    const element = document.createElement('div');
    const elementRef = { current: element };

    renderHook(() =>
      useIntersectionObserver(elementRef as React.RefObject<Element>, {
        threshold: 0.5,
        rootMargin: '10px',
      }),
    );

    expect(MockIntersectionObserver).toHaveBeenCalled();
    const call = (MockIntersectionObserver as any).mock.calls[0];
    expect(call[1]).toMatchObject({
      threshold: 0.5,
      rootMargin: '10px',
    });
  });

  it('should freeze when freezeOnceVisible is true and element is intersecting', () => {
    const element = document.createElement('div');
    const elementRef = { current: element };

    const { result, rerender } = renderHook(
      ({ freeze }) =>
        useIntersectionObserver(elementRef as React.RefObject<Element>, {
          freezeOnceVisible: freeze,
        }),
      {
        initialProps: { freeze: true },
      },
    );

    // Get the callback from the constructor call
    const constructorCall = (MockIntersectionObserver as any).mock.calls.find(
      (call: any[]) => call[0] && typeof call[0] === 'function',
    );

    if (constructorCall) {
      const callback = constructorCall[0];
      const entry = {
        isIntersecting: true,
      } as IntersectionObserverEntry;

      callback([entry], {} as IntersectionObserver);
    }

    rerender({ freeze: true });

    // Should not observe again when frozen
    expect(mockObserver.observe).toHaveBeenCalledTimes(1);
  });

  it('should handle missing IntersectionObserver gracefully', () => {
    const originalIO = global.IntersectionObserver;
    // @ts-ignore
    delete global.IntersectionObserver;

    const element = document.createElement('div');
    const elementRef = { current: element };

    const { result } = renderHook(() =>
      useIntersectionObserver(elementRef as React.RefObject<Element>, {}),
    );

    expect(result.current).toBeUndefined();

    global.IntersectionObserver = originalIO;
  });
});
