import React, { useState } from 'react';
import { act, cleanup, fireEvent } from '@testing-library/react';
import { render } from '../../../utils/theme-render-wrapper';
import { MenuContent } from '../dropdown-menu';
import { areEqualDropdownMenuItems, processDropdownMenuItemsRecursive } from '../helpers';
import type { DropdownMenuItem, MenuContentProps } from '../types';
import { mockInfinityScroll, mockListItems } from '../__mocks__/menuItems';
import noop from 'lodash/noop';

beforeEach(() => jest.useFakeTimers());

afterEach(() => {
  jest.runAllTimers();
  jest.useRealTimers();
  cleanup();
});

const handleDelete = jest.fn();

const clickableItemName = mockListItems[1].name;
const disabledItemName = mockListItems[4].name;
const hiddenListItems = mockListItems.map(item => ({ ...item, hidden: true }));
hiddenListItems[0].options = mockListItems;
const inputText = 'hello world';

const Wrapper = (props: Partial<MenuContentProps>) => {
  const { multiple } = props;

  const [listItems, setListItems] = useState<DropdownMenuItem[]>(mockListItems);

  const handleMenuClick: NonNullable<MenuContentProps['handleClick']> = (name, value) => () => {
    const itemToSelect = { name, value };
    const newItems = processDropdownMenuItemsRecursive(listItems, item => {
      const areEqual = areEqualDropdownMenuItems(item, itemToSelect);
      if (multiple ? areEqual : areEqual || item.selected) {
        return {
          ...item,
          selected: !item.selected
        };
      }
      return item;
    });
    setListItems(newItems);
  };

  return (
    <MenuContent
      {...props}
      handleClick={handleMenuClick}
      handleDelete={handleDelete}
      listItems={listItems}
    />
  );
};

describe('<MenuContent />', () => {
  describe('when ListItems uses MuiList (default)', () => {
    it(`should render empty menus without and with loader`, () => {
      const { baseElement } = render(
        <>
          <MenuContent listItems={[]} />
          <MenuContent listItems={[]} loading />
          <MenuContent listItems={[]} loading menuListLoader="Wait..." />
        </>
      );
      expect(baseElement).toBeTruthy();
    });

    it(`should render empty menu with InsertBeforeList and InsertAfterList`, () => {
      const { baseElement } = render(
        <MenuContent InsertBeforeList={() => null} InsertAfterList={() => null} listItems={[]} />
      );
      expect(baseElement).toBeTruthy();
    });

    it(`should render hidden items with groups and search`, () => {
      const { queryByTestId } = render(
        <MenuContent
          listItems={hiddenListItems}
          noOptionsText="No options"
          noResultsText="No results"
          search
        />
      );

      const inputEl = queryByTestId('searchField');
      expect(inputEl).toBeTruthy();
      if (inputEl) {
        fireEvent.change(inputEl, { target: { value: inputText } });
      }
    });

    it(`should render with modifyMenuItem`, () => {
      const { baseElement } = render(
        <MenuContent
          listItems={mockListItems}
          modifyMenuItem={props =>
            props.selected
              ? {
                  actionIcons: [],
                  showSelectedIcon: true
                }
              : {}
          }
        />
      );
      expect(baseElement).toBeTruthy();
    });

    it(`should render with menuListHeader and menuListFooter`, () => {
      const menuListHeader = 'Menu List Header';
      const menuListFooter = 'Menu List Footer';

      const { queryByText } = render(
        <MenuContent
          listItems={[]}
          menuListHeader={menuListHeader}
          menuListFooter={menuListFooter}
        />
      );

      expect(queryByText(menuListHeader)).toBeTruthy();
      expect(queryByText(menuListFooter)).toBeTruthy();
    });

    it(`should render multiple variant`, () => {
      const { queryByText, container } = render(<Wrapper multiple />);

      queryByText(clickableItemName)?.click();
      const checkboxEl = container.querySelector('input:checked');
      expect(checkboxEl).toBeTruthy();
    });

    it(`should render single variant and click on items`, () => {
      const { queryByText, queryByTestId } = render(<Wrapper multiple={false} showSelectedIcon />);

      // ignore click on disabled item
      queryByText(disabledItemName)?.click();
      let checkedIconEl = queryByTestId('singleCheckedIcon');
      expect(checkedIconEl).toBeFalsy();

      // handle click on clickable item
      queryByText(clickableItemName)?.click();
      checkedIconEl = queryByTestId('singleCheckedIcon');
      expect(checkedIconEl).toBeTruthy();
    });

    it(`should render sortable menu items`, () => {
      const { queryByTestId } = render(<Wrapper sortable />);

      const sortIconElm = queryByTestId('sortIcon_0');
      expect(sortIconElm).toBeTruthy();
    });

    it(`should render deletable menu items`, () => {
      const { queryByTestId } = render(<Wrapper deletable />);

      const deleteIconElm = queryByTestId('deleteIcon_0');
      deleteIconElm?.click();

      expect(handleDelete).toHaveBeenCalled();
    });

    it(`should render with debounced search`, () => {
      const { queryByTestId } = render(
        <Wrapper search searchDebounceTime={200} searchPlaceholder="Search..." />
      );

      const inputEl = queryByTestId('searchField');
      expect(inputEl).toBeTruthy();
      if (inputEl) {
        fireEvent.change(inputEl, { target: { value: inputText } });
        fireEvent.keyDown(inputEl, { code: 'Space' });
      }
    });

    it(`should render with search highlight and clear icon`, () => {
      const clearSelectItemsHandler = jest.fn();
      const { queryByTestId } = render(
        <Wrapper clearSelectItems={clearSelectItemsHandler} search searchHighlight />
      );

      const inputEl = queryByTestId('searchField');
      expect(inputEl).toBeTruthy();
      if (inputEl) {
        fireEvent.change(inputEl, { target: { value: inputText } });
      }

      act(() => {
        jest.runAllTimers(); // because of `searchDebounceTime`
      });

      queryByTestId('searchClear')?.click();
      expect(clearSelectItemsHandler).toHaveBeenCalled();
    });

    it(`should render with bottomButtonText`, () => {
      const bottomButtonText = 'Bottom Button Text';
      const bottomButtonClickHandler = jest.fn();

      const { queryByText } = render(
        <Wrapper
          bottomButtonText={bottomButtonText}
          bottomButtonClickHandler={bottomButtonClickHandler}
          noOptionsText="No options"
          noResultsText="No results"
        />
      );

      const bottomButtonEl = queryByText(bottomButtonText);
      bottomButtonEl?.click();
      expect(bottomButtonClickHandler).toHaveBeenCalled();
    });
  });

  describe('when ListItems uses VariableSizeList', () => {
    it(`should render empty menus without and with loader`, () => {
      const { baseElement } = render(
        <>
          <MenuContent listItems={[]} variableSizeList />
          <MenuContent listItems={[]} loading variableSizeList />
        </>
      );
      expect(baseElement).toBeTruthy();
    });

    it(`should render empty menu with InsertBeforeList and InsertAfterList`, () => {
      const { baseElement } = render(
        <MenuContent
          InsertBeforeList={() => null}
          InsertAfterList={() => null}
          listItems={[]}
          variableSizeList
        />
      );
      expect(baseElement).toBeTruthy();
    });

    it(`should render empty menu with groups and search`, () => {
      const { queryByTestId } = render(
        <MenuContent
          listItems={[]}
          noOptionsText="No options"
          noResultsText="No results"
          search
          variableSizeList
        />
      );

      const inputEl = queryByTestId('searchField');
      expect(inputEl).toBeTruthy();
      if (inputEl) {
        fireEvent.change(inputEl, { target: { value: inputText } });
      }
    });

    it(`should render hidden items with groups and search`, () => {
      const { queryByTestId } = render(
        <MenuContent
          listItems={hiddenListItems}
          noOptionsText="No options"
          noResultsText="No results"
          search
          variableSizeList
        />
      );

      const inputEl = queryByTestId('searchField');
      expect(inputEl).toBeTruthy();
      if (inputEl) {
        fireEvent.change(inputEl, { target: { value: inputText } });
      }
    });

    it(`should render single variant and click on items`, () => {
      const { queryByText, queryByTestId } = render(
        <Wrapper multiple={false} showSelectedIcon variableSizeList />
      );

      // ignore click on disabled item
      queryByText(disabledItemName)?.click();
      let checkedIconEl = queryByTestId('singleCheckedIcon');
      expect(checkedIconEl).toBeFalsy();

      // handle click on clickable item
      const menuItemEl = queryByText(clickableItemName);
      if (menuItemEl) {
        fireEvent.mouseUp(menuItemEl);
        menuItemEl?.click();
      }

      checkedIconEl = queryByTestId('singleCheckedIcon');
      expect(checkedIconEl).toBeTruthy();
    });

    it(`should render sortable menu items`, () => {
      const { queryByTestId } = render(<Wrapper sortable variableSizeList />);

      const sortIconElm = queryByTestId('sortIcon_0');
      expect(sortIconElm).toBeTruthy();
    });

    it(`should render with search and click on clear icon`, () => {
      const handleTextChange = jest.fn();
      const { queryByTestId } = render(
        <Wrapper handleTextChange={handleTextChange} search variableSizeList />
      );

      const inputEl = queryByTestId('searchField');
      expect(inputEl).toBeTruthy();
      if (inputEl) {
        fireEvent.change(inputEl, { target: { value: inputText } });
      }

      act(() => {
        jest.runAllTimers(); // because of `searchDebounceTime`
      });

      queryByTestId('searchClear')?.click();
      expect(handleTextChange).toHaveBeenCalled();
    });
  });

  describe('when ListItems uses InfinityScroll', () => {
    it(`should render infinityScroll`, () => {
      const { baseElement } = render(
        <MenuContent listItems={mockListItems} infinityScrollProps={mockInfinityScroll} />
      );
      expect(baseElement).toBeTruthy();
    });

    it(`should render infinityScroll with variableSizeList`, () => {
      const { baseElement } = render(
        <MenuContent
          listItems={mockListItems}
          infinityScrollProps={mockInfinityScroll}
          variableSizeList={true}
        />
      );
      expect(baseElement).toBeTruthy();
    });
  });
});
