import { Field } from '../field/Field';
import { mockMatchMedia, mockResizeObserver, render, screen, userEvent } from '../test-utils';
import DateLookup, { DateLookupProps } from './DateLookup';

import { act } from 'react';

const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTimeAsync });

mockMatchMedia();
mockResizeObserver();

const initialValue = new Date(2000, 0, 1);

describe('DateLookup', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(async () => {
    await jest.runOnlyPendingTimersAsync();
    jest.useRealTimers();
    jest.clearAllMocks();
  });

  it('supports `Field` for labeling', () => {
    render(
      <Field label="Date of birth">
        <DateLookup value={initialValue} onChange={() => {}} />
      </Field>,
    );
    const button = screen.getByRole('button', { name: /^Date of birth/ });

    expect(button).toBeInTheDocument();
    expect(button).toHaveAttribute('aria-haspopup');
  });

  it.each([' ', '{Enter}', '{ArrowDown}', '{ArrowUp}', '{ArrowRight}', '{ArrowLeft}'])(
    "opens with '%s' and closes with '{Escape}'",
    async (text) => {
      render(<DateLookup value={initialValue} onChange={() => {}} />);

      await user.tab();
      await user.keyboard(text);

      expect(screen.getByRole('button', { name: /next/iu })).toBeInTheDocument();

      await user.keyboard('{Escape}');
      await act(async () => {
        await jest.runOnlyPendingTimersAsync();
      });

      expect(screen.queryByRole('button', { name: /next/iu })).not.toBeInTheDocument();
    },
  );

  const setupAndOpenWithMouse = async (props: Partial<DateLookupProps> = {}) => {
    const view = render(<DateLookup value={initialValue} onChange={() => {}} {...props} />);

    await user.click(screen.getByRole('button'));
    await act(async () => {
      await jest.runOnlyPendingTimersAsync();
    });

    return view;
  };

  it('opens and closes with mouse', async () => {
    await setupAndOpenWithMouse();

    expect(screen.getByRole('button', { name: /next/iu })).toBeInTheDocument();

    const dimmerElement = screen.getByRole('dialog').parentElement?.parentElement;
    if (dimmerElement != null) {
      await user.click(dimmerElement);
    }
    await act(async () => {
      await jest.runOnlyPendingTimersAsync();
    });

    expect(screen.queryByRole('button', { name: /next/iu })).not.toBeInTheDocument();
  });

  describe('in day view', () => {
    it.each([
      ['{ArrowLeft}', -1],
      ['{ArrowRight}', +1],
      ['{ArrowUp}', -7],
      ['{ArrowDown}', +7],
    ])("handles '%s' to step %d day(s)", async (text, step) => {
      const handleChange = jest.fn();
      await setupAndOpenWithMouse({ onChange: handleChange });

      expect(handleChange).not.toHaveBeenCalled();

      await user.keyboard(text);

      const value = new Date(initialValue);
      value.setDate(initialValue.getDate() + step);
      expect(handleChange).toHaveBeenCalledWith(value);

      await user.keyboard('{Escape}');
      await act(async () => {
        await jest.runOnlyPendingTimersAsync();
      });

      expect(handleChange).toHaveBeenCalledWith(initialValue);
    });
  });

  describe('in year view', () => {
    it.each([
      ['{ArrowLeft}', -1],
      ['{ArrowRight}', +1],
      ['{ArrowUp}', -4],
      ['{ArrowDown}', +4],
    ])("handles '%s' to step %d year(s)", async (text, step) => {
      const handleChange = jest.fn();
      await setupAndOpenWithMouse({ onChange: handleChange });

      await user.click(screen.getByRole('button', { name: /year view/iu }));
      await act(async () => {
        await jest.runOnlyPendingTimersAsync();
      });

      expect(handleChange).not.toHaveBeenCalled();

      await user.keyboard(text);

      const value = new Date(initialValue);
      value.setFullYear(initialValue.getFullYear() + step);
      expect(handleChange).toHaveBeenCalledWith(value);

      await user.keyboard('{Escape}');
      await act(async () => {
        await jest.runOnlyPendingTimersAsync();
      });

      expect(handleChange).toHaveBeenCalledWith(initialValue);
    });
  });

  describe('in month view', () => {
    it.each([
      ['{ArrowLeft}', -1],
      ['{ArrowRight}', +1],
      ['{ArrowUp}', -4],
      ['{ArrowDown}', +4],
    ])("handles '%s' to step %d month(s)", async (text, step) => {
      const handleChange = jest.fn();
      await setupAndOpenWithMouse({ onChange: handleChange });

      await user.click(screen.getByRole('button', { name: /year view/iu }));
      await act(async () => {
        await jest.runOnlyPendingTimersAsync();
      });
      await user.keyboard(' ');
      await act(async () => {
        await jest.runOnlyPendingTimersAsync();
      });

      expect(handleChange).not.toHaveBeenCalled();

      await user.keyboard(text);

      const value = new Date(initialValue);
      value.setMonth(initialValue.getMonth() + step);
      expect(handleChange).toHaveBeenCalledWith(value);

      await user.keyboard('{Escape}');
      await act(async () => {
        await jest.runOnlyPendingTimersAsync();
      });

      expect(handleChange).toHaveBeenCalledWith(initialValue);
    });
  });

  it('limits min value', async () => {
    const min = new Date(initialValue);
    min.setDate(min.getDate() - 1);
    const handleChange = jest.fn();
    await setupAndOpenWithMouse({ min, onChange: handleChange });

    await user.keyboard('{ArrowLeft}{ArrowLeft}');

    expect(handleChange).toHaveBeenCalledWith(min);
  });

  it('limits max value', async () => {
    const max = new Date(initialValue);
    max.setDate(max.getDate() + 1);
    const handleChange = jest.fn();
    await setupAndOpenWithMouse({ max, onChange: handleChange });

    await user.keyboard('{ArrowRight}{ArrowRight}');

    expect(handleChange).toHaveBeenCalledWith(max);
  });
});
