import { configure } from '@testing-library/dom'
import '@testing-library/jest-dom'
import {
  render,
  screen,
  waitFor,
  waitForElementToBeRemoved,
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import { MultiSelect } from './MultiSelect'

configure({ testIdAttribute: 'data-qa' })

const options = [
  {
    id: '1',
    label: 'blue bird is a really long item and should overflow',
    groupId: '1',
  },
  { id: '2', label: 'canary', groupId: '1' },
  { id: '3', label: 'eagle', groupId: '1' },
  { id: '4', label: 'falcon', groupId: '1' },
  { id: '5', label: 'finch', groupId: '1' },
  { id: '6', label: 'hawk', groupId: '2' },
  { id: '7', label: 'owl', groupId: '2' },
  { id: '8', label: 'pigeon', groupId: '2' },
  { id: '9', label: 'raven', groupId: '2' },
  { id: '10', label: 'toucan', groupId: '2' },
]

const groups = [
  { id: '1', label: 'Group 1' },
  { id: '2', label: 'Group 2' },
]

const qaTags = {
  option: (option) => `option-${option.id}`,
  token: (option) => `token-${option.id}`,
  tokenClear: (option) => `clear-token-${option.id}`,
}
const values = [options[0], options[5]]
const clearIcon = '[data-qa="core-multiselect-clear"]'
const menuItem = 'option-'
const menuItems = '[data-qa^="option-"]'
const menuGroup = '[data-group="true"]'
const placeholder = 'Select Values'
const selectArrow = 'multiselect-select-arrow'
const selectedInput = '[data-qa^="token-"]'
const selectedMenuItem = '[data-selected="true"]'
const optRemovalIcon = '[data-qa^="clear-token-"]'

const getSelectedItems = (selector) => {
  return getQuerySelectorAll(selector).length
}

const elementExists = (selector) => {
  return getQuerySelector(selector).length > 0
}

const getOptionText = (id) => {
  return options.find((option) => option.id === id).label
}

function BasicProps(props) {
  const [value, setValue] = React.useState(props.values || [])

  return (
    <MultiSelect
      onChange={setValue}
      options={options}
      value={value}
      qa={qaTags}
      {...props}
    />
  )
}

function click(element) {
  return userEvent.click(element)
}

function getByPlaceholderText(text) {
  return screen.getByPlaceholderText(text)
}

function getByTestId(testId) {
  return screen.getByTestId(testId)
}

function getByText(text) {
  return screen.getByText(text)
}

function getQuerySelector(selector) {
  return document.querySelector(selector)
}

function getQuerySelectorAll(selector) {
  return document.querySelectorAll(selector)
}

/**
 *
 * To get selected values in the button(input) and to omit options.
 * Clicking on the removal icon toggles open state,
 * so getByText finds two same texts sometimes
 * @param text
 * @returns {ReturnType<GetByText<HTMLElement>>}
 */
function getByTextSelectedInput(text) {
  return screen.getByText(text, { selector: `${selectedInput} *` })
}

describe('MultiSelect', () => {
  it('should render with selected options', async () => {
    render(<BasicProps values={values} />)

    expect(screen.queryByText(placeholder)).not.toBeInTheDocument()

    await click(getByTestId(selectArrow))
    await waitFor(() => {
      expect(getSelectedItems(selectedInput)).toBe(2)
      expect(Boolean(screen.getAllByText(options[0].label))).toBe(true)
      expect(Boolean(screen.getAllByText(options[5].label))).toBe(true)
      expect(getSelectedItems(selectedMenuItem)).toBe(2)
    })
  })

  it('should select a single option', async () => {
    const optionId = '2'

    render(<BasicProps />)

    await click(getByTestId(selectArrow))
    await waitFor(() => getByTestId(`${menuItem}${optionId}`))

    await click(getByTestId(`${menuItem}${optionId}`))

    expect(getSelectedItems(selectedInput)).toBe(1)
    expect(getSelectedItems(selectedMenuItem)).toBe(1)
    expect(screen.getAllByText(getOptionText(optionId)).length).toBe(2)
  })

  it('should select multiple options and close menu', async () => {
    const ids = ['1', '2', '3']

    render(<BasicProps />)

    await click(getByTestId(selectArrow))
    await waitFor(() => elementExists(menuItems))

    await click(getByTestId(`${menuItem}${ids[0]}`))
    await click(getByTestId(`${menuItem}${ids[1]}`))
    await click(getByTestId(`${menuItem}${ids[2]}`))

    await click(getByTestId(selectArrow))
    await waitForElementToBeRemoved(getQuerySelector(menuItems))

    expect(getSelectedItems(selectedInput)).toBe(3)
    expect(Boolean(getOptionText(ids[0]))).toBe(true)
    expect(Boolean(getOptionText(ids[1]))).toBe(true)
    expect(Boolean(getOptionText(ids[2]))).toBe(true)
  })

  it('should remove a selected option clicking on option removal icon', async () => {
    render(<BasicProps values={values} />)

    await click(getQuerySelector(optRemovalIcon))
    expect(getSelectedItems(selectedInput)).toBe(1)
    expect(Boolean(getByTextSelectedInput(getOptionText('6')))).toBe(true)
  })

  it('should clear all selected options', async () => {
    render(<BasicProps values={values} />)

    await click(getQuerySelector(clearIcon))
    expect(Boolean(getByPlaceholderText(placeholder))).toBe(true)
  })

  it('should add to initially loaded options', async () => {
    render(<BasicProps values={values} />)

    await click(getByTestId(selectArrow))
    await waitFor(() => getByTestId(`${menuItem}2`))
    await click(getByTestId(`${menuItem}2`))
    await click(getByTestId(selectArrow))
    await waitForElementToBeRemoved(getQuerySelector(menuItems))

    expect(getSelectedItems(selectedInput)).toBe(3)
    expect(Boolean(getByText(getOptionText('1')))).toBe(true)
    expect(Boolean(getByText(getOptionText('2')))).toBe(true)
    expect(Boolean(getByText(getOptionText('6')))).toBe(true)
  })

  it('should remove multiple options', async () => {
    render(<BasicProps values={[options[0], options[1], options[5]]} />)
    await click(getQuerySelector(optRemovalIcon))
    await click(getQuerySelector(optRemovalIcon))

    expect(getSelectedItems(selectedInput)).toBe(1)
    expect(Boolean(getByTextSelectedInput(getOptionText('6')))).toBe(true)
  })

  it('should remove all options clicking on option removal icon', async () => {
    render(<BasicProps />)

    await Array.from(getQuerySelectorAll(optRemovalIcon)).map(async (icon) => {
      await click(icon)
    })

    expect(getSelectedItems(selectedInput)).toBe(0)
  })

  it('should remove selection by clicking option in the menu', async () => {
    render(<BasicProps values={values} />)

    await click(getByTestId(selectArrow))
    await waitFor(() => elementExists(menuItems))
    await click(getQuerySelector(selectedMenuItem))

    expect(getSelectedItems(selectedMenuItem)).toBe(1)
  })

  it('should select options from different groups', async () => {
    render(<BasicProps optgroups={groups} />)

    await click(getByTestId(selectArrow))
    await waitFor(() =>
      expect(getQuerySelectorAll(menuGroup).length).toBe(groups.length)
    )

    await click(getByText(getOptionText('2')))
    await click(getByText(getOptionText('6')))

    expect(getSelectedItems(selectedMenuItem)).toBe(2)

    await click(getByTestId(selectArrow))
    await waitForElementToBeRemoved(getQuerySelector(menuItems))

    expect(Boolean(getByText(getOptionText('2')))).toBe(true)
    expect(Boolean(getByText(getOptionText('6')))).toBe(true)
  })

  it('should render footer if defined', async () => {
    render(<BasicProps footer={<b>Footer</b>} />)
    await click(getByTestId(selectArrow))
    await waitFor(() => expect(Boolean(getByText('Footer'))).toBe(true))
  })
})
