import React from 'react';
import { fireEvent, render, screen, within, act } from '@testing-library/react';
import { SilkeSelectField } from './silke-select-field';
import userEvent, { UserEvent } from '@testing-library/user-event';
import { SilkeForm } from '../silke-form';
import { ARROW_DOWN, ARROW_UP, ENTER } from '../../jest/keys';

describe('silke-select-field', () => {
  beforeAll(() => {
    window.HTMLElement.prototype.scrollIntoView = function () {};
  });

  const makeItem = (value: string) => ({ label: value, value });
  const pressKey = (key: string, user: UserEvent) => act(async () => await user.keyboard(key));

  const items = [
    makeItem('rat'),
    makeItem('lion'),
    makeItem('bear'),
    makeItem('deer'),
    makeItem('cat'),
  ];

  it('Has no value when no value passed', () => {
    render(<SilkeSelectField items={items} label="Animal" />);
    expect(screen.getByLabelText('Animal').getAttribute('value')).toBeNull();
  });

  it('Has value when value passed', () => {
    render(<SilkeSelectField items={items} label="Animal" value="rat" />);
    expect(screen.getByLabelText('Animal').getAttribute('value')).toEqual('rat');
  });

  it('Displays no optionslist', () => {
    render(<SilkeSelectField items={items} label="Animal" value="rat" />);

    const optionsList = screen.queryAllByRole('listbox');

    expect(optionsList).toHaveLength(0);
  });

  it('Displays options when clicked', async () => {
    const user = userEvent.setup();
    const onChange = jest.fn();
    render(<SilkeSelectField items={items} label="Animal" value="rat" onChange={onChange} />);
    const input = screen.getByLabelText('Animal');
    await act(async () => await user.click(input));

    const optionsList = screen.queryAllByRole('listbox');

    expect(optionsList).toHaveLength(1);
    const { getByText } = within(optionsList[0]);
    const lionOption = getByText('lion');
    const ratOption = getByText('rat');

    expect(lionOption).toBeDefined();
    expect(ratOption).toBeDefined();
  });

  it('Calls onChange with value when option is clicked', async () => {
    const user = userEvent.setup();
    const onChange = jest.fn();

    render(<SilkeSelectField items={items} label="Animal" value="rat" onChange={onChange} />);

    const input = screen.getByLabelText('Animal');
    await act(async () => await user.click(input));
    const option = screen.getByText('lion');
    await act(async () => await user.click(option));

    expect(onChange).toHaveBeenCalledWith('lion');
  });

  it('Highlights 1st item in list when opened', async () => {
    const onChange = jest.fn();
    const user = userEvent.setup();

    render(<SilkeSelectField items={items} label="Animal" onChange={onChange} />);

    const input = screen.getByLabelText('Animal');
    act(() => input.focus());

    await pressKey(ARROW_DOWN, user);
    await pressKey(ENTER, user);

    expect(onChange).toHaveBeenCalledWith('lion');
  });

  it('Can be selected with keyboard only', async () => {
    const onChange = jest.fn();
    const user = userEvent.setup();

    render(<SilkeSelectField items={items} label="Animal" onChange={onChange} />);

    const input = screen.getByLabelText('Animal');
    act(() => input.focus());
    await pressKey(ARROW_DOWN, user);
    await pressKey(ARROW_DOWN, user);
    await pressKey(ARROW_DOWN, user);
    await pressKey(ENTER, user);

    expect(onChange).toHaveBeenCalledWith('deer');
  });

  it('When it has value already, pressing arrow key selects item next to value', async () => {
    const onChange = jest.fn();
    const user = userEvent.setup();

    render(<SilkeSelectField items={items} label="Animal" value="bear" onChange={onChange} />);

    const input = screen.getByLabelText('Animal');
    act(() => input.focus());

    await pressKey(ARROW_UP, user);
    await pressKey(ARROW_UP, user);
    await pressKey(ARROW_UP, user);
    await pressKey(ENTER, user);

    expect(onChange).toHaveBeenCalledWith('cat');
  });

  it('Does not hide dropdown when item is selected on a multiselect select', async () => {
    const onChange = jest.fn();
    const user = userEvent.setup();

    render(<SilkeSelectField items={items} label="Animal" multiple onChange={onChange} />);

    const input = screen.getByLabelText('Animal');
    act(() => input.focus());

    await act(async () => await user.keyboard(ARROW_UP));

    const lionOption = screen.getByText('lion');

    const dropdown = screen.getByRole('listbox');
    expect(dropdown).toBeDefined();

    await act(async () => await user.click(lionOption));

    expect(onChange).toHaveBeenCalledWith(['lion']);
    expect(dropdown).toBeDefined();
  });

  describe('When Silke Select field is within a Silke form', () => {
    it('Gets value from form', async () => {
      const onChange = jest.fn();
      const currentIndex = 2;
      render(
        <SilkeForm value={{ animal: items[currentIndex].value }} onChange={onChange}>
          <SilkeSelectField items={items} name="animal" label="Animal" />
        </SilkeForm>,
      );

      const input = screen.getByLabelText('Animal');
      act(() => input.focus());

      expect(input.getAttribute('value')).toEqual('bear');

      const dropdown = screen.getByRole('listbox');
      expect(dropdown).toBeDefined();

      const searchField = dropdown.querySelector('input') as HTMLInputElement;
      expect(searchField).toBeDefined();

      act(() => fireEvent.keyUp(searchField, { key: 'ArrowDown', code: 'ArrowDown' }));
      act(() => fireEvent.keyUp(searchField, { key: 'ArrowDown', code: 'ArrowDown' }));
      act(() => fireEvent.keyUp(searchField, { key: 'Enter', code: 'Enter' }));

      expect(onChange).toHaveBeenCalledWith({ animal: items[currentIndex + 2].value }, true);
    });
  });
});
