import { renderHook, act } from '@testing-library/react-hooks';

import {
  createLegacyMediaMock,
  createMatchMediaMock,
  createMediaMockWithoutApi,
  MatchMediaMock2,
  addMockToGlobal,
  removeMockFromGlobal,
} from './helpers/MatchMediaMock2';

import { useMatchMediaBreakpoints } from '../useMatchMediaBreakpoints';
import { createBreakpointQueryObject } from '../helpers/createBreakpointQueryObject';

describe('useMatchMediaBreakpoints', () => {
  const breakpointUnit = 'px';
  const names = { sm: 'sm', md: 'md', lg: 'lg' };
  const breakpoints = { sm: 320, md: 640, lg: 1200 };
  const media = createBreakpointQueryObject(breakpoints, breakpointUnit);

  describe('useMatchMediaBreakpoints (EventTarget api)', () => {
    let matchMediaMock: MatchMediaMock2;

    beforeEach(() => {
      matchMediaMock = createMatchMediaMock();
      addMockToGlobal(matchMediaMock.matchMedia);
    });

    afterEach(() => {
      matchMediaMock.cleanup();
    });

    afterAll(() => {
      removeMockFromGlobal();
    });

    it('detects the single breakpoint', () => {
      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints: { single: 1 },
          breakpointUnit: 'px',
        }),
      );

      expect(result.result.current).toBe('single');
    });

    it('detects breakpoint if no breakpoint matches', () => {
      matchMediaMock.mediaQuery = 'not all';

      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit,
        }),
      );

      expect(result.result.current).toBe(names.sm);
    });

    it('detects breakpoint sm', () => {
      matchMediaMock.mediaQuery = media.sm;

      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit,
        }),
      );

      expect(result.result.current).toBe(names.sm);
    });

    it('detects breakpoint md', () => {
      matchMediaMock.mediaQuery = media.md;

      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit,
        }),
      );

      expect(result.result.current).toBe(names.md);
    });

    it('detects breakpoint lg', () => {
      matchMediaMock.mediaQuery = media.lg;

      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit,
        }),
      );

      expect(result.result.current).toBe('lg');
    });

    it('adds a single event listeners for a single breakpoint', () => {
      renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints: { single: 1 },
          breakpointUnit,
        }),
      );

      expect(matchMediaMock.numListeners).toBe(1);
    });

    it('adds event listeners', () => {
      renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit,
        }),
      );

      expect(matchMediaMock.numListeners).toBe(3);
    });

    it('removes event listeners', () => {
      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit,
        }),
      );

      result.unmount();
      expect(matchMediaMock.numListeners).toBe(0);
    });

    it('detects changes of the breakpoint', () => {
      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit,
        }),
      );

      act(() => {
        matchMediaMock.mediaQuery = media.md;
      });
      expect(result.result.current).toBe(names.md);

      act(() => {
        matchMediaMock.mediaQuery = media.sm;
      });
      expect(result.result.current).toBe(names.sm);
    });
  });

  describe('useMatchMediaBreakpoints (legacy api)', () => {
    let matchMediaMock: MatchMediaMock2;

    beforeEach(() => {
      matchMediaMock = createLegacyMediaMock();
      addMockToGlobal(matchMediaMock.matchMedia);
    });

    afterEach(() => {
      matchMediaMock.cleanup();
    });

    afterAll(() => {
      removeMockFromGlobal();
    });

    it('detects the single breakpoint', () => {
      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints: { single: 1 },
          breakpointUnit: 'px',
        }),
      );

      expect(result.result.current).toBe('single');
    });

    it('adds event listeners', () => {
      renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit,
        }),
      );
      expect(matchMediaMock.numListeners).toBe(3);
    });

    it('removes event listeners', () => {
      const renderer = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit,
        }),
      );

      renderer.unmount();
      expect(matchMediaMock.numListeners).toBe(0);
    });

    it('executes the callback', () => {
      matchMediaMock.mediaQuery = 'not all';

      renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit,
        }),
      );

      act(() => {
        matchMediaMock.mediaQuery = 'all';
      });
      expect(matchMediaMock.numCalls).toBe(3);
    });
  });

  describe('useMatchMediaBreakpoints (no events)', () => {
    let matchMediaMock: MatchMediaMock2;

    beforeEach(() => {
      matchMediaMock = createMediaMockWithoutApi();
      addMockToGlobal(matchMediaMock.matchMedia);
    });

    afterEach(() => {
      matchMediaMock.cleanup();
    });

    afterAll(() => {
      removeMockFromGlobal();
    });

    it('detects the single breakpoint', () => {
      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints: { single: 1 },
          breakpointUnit: 'px',
        }),
      );

      expect(result.result.current).toBe('single');
    });
  });

  describe('useMatchMediaBreakpoints (matchMedia not available)', () => {
    it('detects the single breakpoint', () => {
      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints: { single: 1 },
          breakpointUnit: 'px',
        }),
      );

      expect(result.result.current).toBe('single');
    });

    it('uses default breakpoint if no breakpoint is available', () => {
      const result = renderHook(() =>
        useMatchMediaBreakpoints({
          breakpoints,
          breakpointUnit: 'px',
          defaultBreakpoint: 'md',
        }),
      );

      expect(result.result.current).toBe('md');
    });
  });
});
