import { screen, fireEvent, waitFor, act } from '@testing-library/react';
import * as utils from './utils';

const { queryByRole, queryAllByRole } = screen;

test.each([false, true])(
  'Menu is unmounted before opening and closes after losing focus (portal = %s)',
  async (portal) => {
    utils.renderMenu({ portal });

    // menu is unmounted
    utils.expectButtonToBeExpanded(false);
    utils.expectMenuToBeInTheDocument(false);
    expect(queryByRole('menuitem')).not.toBeInTheDocument();

    // Click the menu button, menu is expected to mount and open, and get focus
    utils.clickMenuButton();
    utils.expectButtonToBeExpanded(true);
    utils.expectMenuToHaveState('opening', false);
    utils.expectMenuToBeOpen(true);
    expect(utils.queryMenu()).toHaveAttribute('aria-label', 'Open');
    await waitFor(() => utils.expectMenuToHaveFocus());
    const menuItems = queryAllByRole('menuitem');
    expect(menuItems).toHaveLength(3);
    menuItems.forEach((item) => utils.expectMenuItemToBeHover(item, false));

    // focus something outside menu, expecting menu to close but keep mounted
    act(() => queryByRole('button').focus());
    utils.expectButtonToBeExpanded(false);
    utils.expectMenuToHaveState('closing', false);
    utils.expectMenuToBeOpen(false);
  }
);

test.each([false, true])(
  'Menu moves through different states when transition is true (portal = %s)',
  async (portal) => {
    utils.renderMenu({ portal, transition: true, transitionTimeout: 20 });
    utils.clickMenuButton();
    utils.expectMenuToHaveState('opening', true);
    await waitFor(() => utils.expectMenuToHaveFocus());
    utils.expectMenuToHaveState('opening', false);
    utils.expectMenuToHaveState('open', true);

    act(() => queryByRole('button').focus());
    utils.expectMenuToHaveState('closing', true);
    await waitFor(() => utils.expectMenuToHaveState('closed', true));
    utils.expectMenuToHaveState('closing', false);
  }
);

test.each([false, true])(
  'Menu is removed from DOM after closing when unmountOnClose is true (portal = %s)',
  async (portal) => {
    utils.renderMenu({ portal, unmountOnClose: true });
    utils.expectMenuToBeInTheDocument(false);

    utils.clickMenuButton();
    utils.expectMenuToBeInTheDocument(true);
    await waitFor(() => utils.expectMenuToHaveFocus());

    act(() => queryByRole('button').focus());
    utils.expectMenuToBeInTheDocument(false);
  }
);

test('Menu is in the DOM before first opening when initialMounted is true', () => {
  utils.renderMenu({ initialMounted: true });
  utils.expectMenuToBeInTheDocument(true);
  utils.expectMenuToHaveState('closed', true);
});

test('Clicking a menu item fires onClick event and closes the menu', () => {
  const menuItemText = 'Save';
  const onClick = jest.fn();
  const onChange = jest.fn();
  const onItemClick = jest.fn();
  utils.renderMenu(
    { onItemClick, onMenuChange: onChange },
    {
      children: menuItemText,
      value: menuItemText,
      onClick
    }
  );

  // Open menu and click a menu item, expecting onClick to fire on the menu item and menu
  utils.clickMenuButton();
  expect(onChange).toHaveBeenLastCalledWith({ open: true });

  fireEvent.click(utils.queryMenuItem(menuItemText));
  expect(onClick).toHaveBeenLastCalledWith(utils.clickEvent({ value: menuItemText }));
  expect(onItemClick).toHaveBeenLastCalledWith(utils.clickEvent({ value: menuItemText }));
  expect(onChange).toHaveBeenLastCalledWith({ open: false });
  expect(onClick).toHaveBeenCalledTimes(1);
  expect(onItemClick).toHaveBeenCalledTimes(1);

  // menu closes after clicking a menu item
  utils.expectButtonToBeExpanded(false);
  utils.expectMenuToBeOpen(false);

  // onClick stopPropagation skips subsequent onItemClick
  onClick.mockImplementationOnce((e) => (e.stopPropagation = true));
  utils.clickMenuButton();
  fireEvent.click(utils.queryMenuItem(menuItemText));
  expect(onClick).toHaveBeenLastCalledWith(
    utils.clickEvent({ value: menuItemText, stopPropagation: true })
  );
  expect(onClick).toHaveBeenCalledTimes(2);
  expect(onItemClick).toHaveBeenCalledTimes(1);
  expect(onChange).toHaveBeenCalledTimes(4);
});

test.each([false, true])('Open and close menu with keyboard (portal = %s)', async (portal) => {
  utils.renderMenu({ portal });
  utils.clickMenuButton({ keyboard: true });
  const menuButton = queryByRole('button');

  const firstItem = utils.queryMenuItem('First');
  await waitFor(() => utils.expectMenuItemToBeHover(firstItem, true));
  fireEvent.keyDown(firstItem, { key: 'Escape' });
  utils.expectMenuToBeOpen(false);
  expect(menuButton).toHaveFocus();

  fireEvent.keyDown(menuButton, { key: 'ArrowUp' });
  const lastItem = utils.queryMenuItem('Last');
  await waitFor(() => utils.expectMenuItemToBeHover(lastItem, true));
  fireEvent.keyDown(lastItem, { key: 'Escape' });

  fireEvent.keyDown(menuButton, { key: 'ArrowDown' });
  await waitFor(() => utils.expectMenuItemToBeHover(firstItem, true));
});

test('Navigate with arrow keys', async () => {
  utils.renderMenu();
  utils.clickMenuButton();
  await waitFor(() => utils.expectMenuToHaveFocus());

  const menu = utils.queryMenu();
  fireEvent.keyDown(menu, { key: 'ArrowUp' });
  utils.expectMenuItemToBeHover(utils.queryMenuItem('Last'), true);
  fireEvent.keyDown(menu, { key: 'ArrowUp' });
  utils.expectMenuItemToBeHover(utils.queryMenuItem('Middle'), true);
  fireEvent.keyDown(menu, { key: 'ArrowUp' });
  utils.expectMenuItemToBeHover(utils.queryMenuItem('First'), true);
  fireEvent.keyDown(menu, { key: 'ArrowUp' });
  utils.expectMenuItemToBeHover(utils.queryMenuItem('Last'), true);
  fireEvent.keyDown(menu, { key: 'ArrowDown' });
  utils.expectMenuItemToBeHover(utils.queryMenuItem('First'), true);
});

test('Additional props are forwarded to Menu', () => {
  const onMouseEnter = jest.fn();
  const onKeyDown = jest.fn();
  utils.renderMenu({
    ['aria-label']: 'test',
    ['aria-haspopup']: true,
    randomattr: 'random',
    onMouseEnter,
    onKeyDown,
    containerProps: {
      'data-testid': 'container',
      id: 'menu-container',
      style: { color: 'blue' },
      onMouseEnter,
      onKeyDown
    }
  });
  utils.clickMenuButton();

  expect(screen.getByText('Some texts')).toHaveClass('some-class');

  const container = screen.getByTestId('container');
  expect(container).toHaveAttribute('id', 'menu-container');
  expect(container).toHaveStyle({ color: 'blue', position: 'relative' });

  const menu = utils.queryMenu();
  expect(menu).toHaveAttribute('aria-label', 'test');
  expect(menu).toHaveAttribute('aria-haspopup', 'true');
  expect(menu).toHaveAttribute('randomattr', 'random');
  fireEvent.mouseEnter(menu);
  expect(onMouseEnter).toHaveBeenCalledTimes(2);
  fireEvent.keyDown(menu, { key: 'm' });
  expect(onKeyDown).toHaveBeenCalledTimes(2);
});

test('Portal will render Menu into document.body', () => {
  const { container } = utils.renderMenu({ portal: true });
  utils.clickMenuButton();

  expect(container.querySelector('.szh-menu-container')).toBeNull();
  expect(container.querySelector('.szh-menu')).toBeNull();
  expect(document.querySelector('.szh-menu-container')).toBeInTheDocument();
  utils.expectMenuToBeInTheDocument(true);
});

test('Use keepOpen of onClick to customise when menu is closed', () => {
  utils.renderMenu(
    {
      onItemClick: (e) => (e.keepOpen = true)
    },
    {
      onClick: (e) => (e.stopPropagation = true)
    }
  );
  utils.clickMenuButton();

  utils.expectMenuToBeOpen(true);
  fireEvent.click(utils.queryMenuItem('Middle'));
  utils.expectMenuToBeOpen(false);

  utils.clickMenuButton();
  utils.expectMenuToBeOpen(true);
  fireEvent.click(utils.queryMenuItem('First'));
  utils.expectMenuToBeOpen(true);
});

test.each([
  ['left', 'start', 'auto'],
  ['right', 'center', 'anchor'],
  ['top', 'end', 'initial'],
  ['bottom', 'center', 'anchor']
])('Menu direction: %s, align: %s, position: %s', (direction, align, position) => {
  const { container } = utils.renderMenu({
    direction,
    align,
    position,
    transition: { open: true, close: true },
    arrow: true,
    offsetX: 10,
    offsetY: -10,
    overflow: 'auto'
  });
  utils.clickMenuButton();
  expect(utils.queryMenu()).toHaveClass(`szh-menu--dir-${direction}`);
  expect(container.querySelector(`.szh-menu__arrow--dir-${direction}`)).toBeInTheDocument();
});