import { describe, expect, it, vi, afterEach } from 'vitest';
import { reveal } from './reveal';

class TestIntersectionObserver {
  static instances: TestIntersectionObserver[] = [];

  callback: IntersectionObserverCallback;
  disconnect = vi.fn();
  observe = vi.fn();
  unobserve = vi.fn();

  constructor(callback: IntersectionObserverCallback) {
    this.callback = callback;
    TestIntersectionObserver.instances.push(this);
  }

  trigger(target: Element, isIntersecting: boolean) {
    this.callback(
      [{ isIntersecting, target } as IntersectionObserverEntry],
      this as unknown as IntersectionObserver
    );
  }
}

describe('reveal action', () => {
  afterEach(() => {
    TestIntersectionObserver.instances = [];
    vi.unstubAllGlobals();
  });

  it('toggles visibility as the element enters and leaves the viewport', () => {
    vi.stubGlobal('IntersectionObserver', TestIntersectionObserver);
    vi.spyOn(window, 'matchMedia').mockReturnValue({
      matches: false,
      media: '(prefers-reduced-motion: reduce)',
      onchange: null,
      addListener: vi.fn(),
      removeListener: vi.fn(),
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
      dispatchEvent: vi.fn()
    } as MediaQueryList);

    const node = document.createElement('div');
    document.body.appendChild(node);
    vi.spyOn(node, 'getBoundingClientRect').mockReturnValue({
      width: 100,
      height: 100,
      top: window.innerHeight + 100,
      right: 100,
      bottom: window.innerHeight + 200,
      left: 0,
      x: 0,
      y: window.innerHeight + 100,
      toJSON() {
        return {};
      }
    } as DOMRect);

    const action = reveal(node, { delay: 40, distance: 32 });
    const observer = TestIntersectionObserver.instances[0];

    expect(node.classList.contains('reveal-ready')).toBe(true);
    expect(node.classList.contains('reveal-visible')).toBe(false);
    expect(node.style.getPropertyValue('--reveal-delay')).toBe('40ms');
    expect(node.style.getPropertyValue('--reveal-distance')).toBe('32px');

    observer.trigger(node, true);
    expect(node.classList.contains('reveal-visible')).toBe(true);

    observer.trigger(node, false);
    expect(node.classList.contains('reveal-visible')).toBe(false);

    action.destroy();
    expect(observer.disconnect).toHaveBeenCalledTimes(1);
  });
});
