import React from "react"
import { render, screen, waitFor } from "@testing-library/react"
import MultiSelectInput from "../input"
import MultiSelect from ".."
import userEvent from "@testing-library/user-event"

describe("MultiSelect", () => {
  it("uses type='input' by default", () => {
    render(<MultiSelect options={[]} />)
    const combobox = screen.getByRole("combobox")
    const input = combobox.querySelector("input")

    expect(input).toBeInTheDocument()
  })

  it("uses type='input' when create is true", () => {
    render(<MultiSelect options={[]} create />)
    const combobox = screen.getByRole("combobox")
    const input = combobox.querySelector("input")

    expect(input).toBeInTheDocument()
  })

  it("uses specified type", () => {
    render(<MultiSelect options={[]} type="filter" />)
    const combobox = screen.getByRole("combobox")
    const input = combobox.querySelector("input")

    expect(input).not.toBeInTheDocument()
  })

  it("applies aria-label to the reference when passed", () => {
    render(
      <MultiSelectInput
        aria-label="Select an option"
        options={[
          { label: "Option 1", value: "option1" },
          { label: "Option 2", value: "option2" },
          { label: "Option 3", value: "option3" }
        ]}
      />
    )

    const trigger = screen.getByRole("combobox")
    userEvent.click(trigger)

    const listbox = screen.getByRole("listbox")

    expect(trigger).toHaveAttribute("aria-label", "Select an option")
    expect(listbox).not.toHaveAttribute("aria-label", "Select an option")
  })

  it("applies aria-labelledby to the reference when passed", () => {
    render(
      <MultiSelect
        aria-labelledby="my-external-id"
        options={[
          { label: "Option 1", value: "option1" },
          { label: "Option 2", value: "option2" },
          { label: "Option 3", value: "option3" }
        ]}
      />
    )

    const trigger = screen.getByRole("combobox")
    userEvent.click(trigger)

    const listbox = screen.getByRole("listbox")

    expect(trigger).toHaveAttribute("aria-labelledby", "my-external-id")
    expect(listbox).not.toHaveAttribute("aria-labelledby", "my-external-id")
  })
})

describe("MultiselectInput", () => {
  const options = [
    { label: "Walleye", value: "walleye" },
    { label: "Bass", value: "bass" },
    { label: "Perch", value: "perch" }
  ]
  it("renders a placeholder when there are no selections", () => {
    render(<MultiSelectInput options={options} />)
    const combobox = screen.getByRole("combobox")
    expect(combobox).toBeInTheDocument()
    const input = combobox.querySelector("input")
    expect(input).toHaveAttribute("placeholder", "Select")
  })

  it("does not render a placeholder when there are selections", () => {
    render(<MultiSelectInput options={options} />)
    const combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    const listOptions = screen.getAllByRole("option")

    expect(listOptions).toHaveLength(3)

    userEvent.click(listOptions[0])

    const input = combobox.querySelector("input")
    expect(input).toHaveAttribute("placeholder", "")
  })


  it("stays open when an option is selected", () => {
    render(<MultiSelectInput options={options} />)
    const combobox = screen.getByRole("combobox")

    expect(combobox).toHaveAttribute("aria-expanded", "false")

    userEvent.click(combobox)

    expect(combobox).toHaveAttribute("aria-expanded", "true")

    const listOptions = screen.getAllByRole("option")

    expect(listOptions).toHaveLength(3)
    expect(listOptions[0]).toHaveAttribute("aria-selected", "false")

    userEvent.click(listOptions[0])

    expect(listOptions[0]).toHaveAttribute("aria-selected", "true")
    expect(combobox).toHaveAttribute("aria-expanded", "true")
  })

  it("adds input selections in the order of the options (not order of selections)", () => {
    render(<MultiSelectInput options={options} />)
    const combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    const listOptions = screen.getAllByRole("option")

    userEvent.click(listOptions[2])
    userEvent.click(listOptions[0])

    const listSelections = screen.getAllByRole("listitem")

    expect(listSelections).toHaveLength(2)
    expect(listSelections[0]).toHaveTextContent("Walleye")
    expect(listSelections[1]).toHaveTextContent("Perch")
  })

  it("deletes selections with backspace", () => {
    render(<MultiSelectInput options={options} />)
    const combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    const listOptions = screen.getAllByRole("option")

    userEvent.click(listOptions[1])
    userEvent.click(listOptions[2])

    let listSelections = screen.getAllByRole("listitem")

    expect(listSelections).toHaveLength(2)

    const input = combobox.querySelector("input")

    userEvent.type(input, "{Backspace}")

    listSelections = screen.getAllByRole("listitem")
    expect(listSelections).toHaveLength(1)
    expect(listSelections[0]).toHaveTextContent("Bass")
  })

  it("closes selections when the pill close button is clicked", () => {
    render(<MultiSelectInput options={options} />)
    const combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    const listOptions = screen.getAllByRole("option")

    userEvent.click(listOptions[1])
    userEvent.click(listOptions[2])

    let listSelections = screen.getAllByRole("listitem")

    expect(listSelections).toHaveLength(2)

    const closeButton = listSelections[0].querySelector("button")

    userEvent.click(closeButton)

    listSelections = screen.getAllByRole("listitem")
    expect(listSelections).toHaveLength(1)
    expect(listSelections[0]).toHaveTextContent("Perch")
  })

  it("passes the selected options to the onChange callback", () => {
    const handleChange = jest.fn()
    render(<MultiSelectInput options={options} onChange={handleChange} />)
    const combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    const listOptions = screen.getAllByRole("option")

    userEvent.click(listOptions[1])
    userEvent.click(listOptions[2])

    expect(handleChange).toHaveBeenCalledWith({
      target: {
        name: undefined,
        value: [options[1], options[2]]
      }
    })
  })

  it("updates listbox when selection changes from input interaction", () => {
    render(<MultiSelectInput options={options} />)
    const combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    const input = combobox.querySelector("input")

    userEvent.type(input, "per")
    userEvent.keyboard("{Enter}")

    let listSelections = screen.getAllByRole("listitem")

    expect(listSelections).toHaveLength(1)
    expect(listSelections[0]).toHaveTextContent("Perch")

    let selectedOptions = screen.getAllByRole("option", { selected: true } )
    expect(selectedOptions).toHaveLength(1)
    expect(selectedOptions[0]).toHaveTextContent("Perch")

    userEvent.type(input, "{Backspace}")
    listSelections = screen.queryAllByRole("listitem")
    expect(listSelections).toHaveLength(0)

    selectedOptions = screen.queryAllByRole("option", { selected: true } )
    expect(selectedOptions).toHaveLength(0)
  })

  it("does not allow any interaction when disabled", () => {
    render(<MultiSelectInput options={options} disabled />)
    const combobox = screen.getByRole("combobox")

    expect(combobox).toHaveAttribute("aria-disabled", "true")

    userEvent.click(combobox)

    const listOptions = screen.queryAllByRole("option")

    expect(listOptions).toHaveLength(0)

    const input = combobox.querySelector("input")

    userEvent.type(input, "per")
    userEvent.keyboard("{Enter}")

    let listSelections = screen.queryAllByRole("listitem")

    expect(input).toBeDisabled()
    expect(input).toHaveValue("")
    expect(listSelections).toHaveLength(0)
  })

  it("applies updates to the value prop", () => {
    render(<ClearableSelections />)

    const clearButton = screen.getByTestId("clear-btn")

    const combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    let listOptions = screen.getAllByRole("option")

    expect(listOptions).toHaveLength(4)

    let selectedOptions = screen.getAllByRole("option", { selected: true })

    expect(selectedOptions).toHaveLength(2)

    userEvent.click(clearButton)

    userEvent.click(combobox)

    listOptions = screen.getAllByRole("option")
    selectedOptions = screen.queryAllByRole("option", { selected: true })

    expect(listOptions).toHaveLength(4)
    expect(selectedOptions).toHaveLength(0)
  })

  describe("create", () => {
    it("allows addition of new options", () => {
      const handleChange = jest.fn()
      render(<MultiSelectInput options={options} create onChange={handleChange} />)
      const combobox = screen.getByRole("combobox")

      userEvent.click(combobox)

      let listOptions = screen.getAllByRole("option")

      expect(listOptions).toHaveLength(3)

      const input = combobox.querySelector("input")

      expect(input).toBeInTheDocument()
      userEvent.type(input, "Yellow Perch")
      expect(input).toHaveValue("Yellow Perch")
      
      listOptions = screen.getAllByRole("option")
      expect(listOptions).toHaveLength(1)
      expect(listOptions[0]).toHaveTextContent("Create “Yellow Perch”")
      userEvent.keyboard("{Enter}")
      expect(input).toHaveValue("")

      expect(handleChange).toHaveBeenCalledWith({
        target: {
          name: undefined,
          value: [{ label: "Yellow Perch", value: "Yellow Perch1", created: true }]
        }
      })
    })

    it("selects existing option when input matches and enter is pressed", () => {
      render(<MultiSelectInput options={options} create />)

      const combobox = screen.getByRole("combobox")

      userEvent.click(combobox)

      const input = combobox.querySelector("input")

      userEvent.type(input, "Perch")
      userEvent.keyboard("{Enter}")

      const listSelections = screen.getAllByRole("listitem")
      expect(listSelections).toHaveLength(1)

      const listOptions = screen.getAllByRole("option")
      expect(listOptions).toHaveLength(3)

      expect(listSelections[0]).toHaveTextContent("Perch")

      const selectedOption = screen.getByRole("option", { selected: true } )
      expect(selectedOption).toHaveTextContent("Perch")
    })
  })

  it("updates options when it changes", async () => {
    const optionsA = [
      { label: "Walleye", value: "walleye" },
      { label: "Bass", value: "bass" },
      { label: "Perch", value: "perch" },
      { label: "Pike", value: "pike" }
    ]
    const { rerender } = render(<MultiSelectInput options={optionsA} value={["bass", "pike"]} />)
    let combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    expect(combobox).toHaveAttribute("aria-expanded", "true")
    let listOptions = screen.getAllByRole("option")

    expect(listOptions).toHaveLength(4)
    expect(listOptions[0]).toHaveTextContent("Walleye")
    expect(listOptions[1]).toHaveTextContent("Bass")
    expect(listOptions[2]).toHaveTextContent("Perch")
    expect(listOptions[3]).toHaveTextContent("Pike")

    const optionsB = [
      { label: "Beef", value: "beef" },
      { label: "Chicken", value: "chicken" },
      { label: "Pork", value: "pork" }
    ]
    rerender(<MultiSelectInput options={optionsB} value={["pork"]} />)

    await waitFor(async () => {
      combobox = await screen.getByRole("combobox")
      userEvent.click(combobox)

      expect(combobox).toHaveAttribute("aria-expanded", "true")
    })

    listOptions = screen.getAllByRole("option")
    expect(listOptions).toHaveLength(3)

    expect(listOptions[0]).toHaveTextContent("Beef")
    expect(listOptions[1]).toHaveTextContent("Chicken")
    expect(listOptions[2]).toHaveTextContent("Pork")
  })

  it("updates onChange when pill is clicked to close", () => {
    const handleChange = jest.fn()
    render(<MultiSelectInput options={options} value={["bass", "perch"]} onChange={handleChange} />)
    const combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    const listSelections = screen.getAllByRole("listitem")

    expect(listSelections).toHaveLength(2)

    const closeButton = listSelections[0].querySelector("button")

    userEvent.click(closeButton)

    expect(handleChange).toHaveBeenCalledWith({
      target: {
        name: undefined,
        value: [{ label: "Perch", value: "perch" }]
      }
    })
  })

  it("updates onChange when pill is backspace deleted", () => {
    const handleChange = jest.fn()
    render(<MultiSelectInput options={options} value={["bass", "perch"]} onChange={handleChange} />)
    const combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    const listSelections = screen.getAllByRole("listitem")

    expect(listSelections).toHaveLength(2)

    const input = combobox.querySelector("input")

    userEvent.type(input, "{Backspace}")

    expect(handleChange).toHaveBeenCalledWith({
      target: {
        name: undefined,
        value: [{ label: "Bass", value: "bass" }]
      }
    })
  })

  it("does not display options that are not matched with input", () => {
    render(<MultiSelectInput options={options} />)
    const combobox = screen.getByRole("combobox")

    userEvent.click(combobox)

    let listOptions = screen.queryAllByRole("option")

    expect(listOptions).toHaveLength(3)

    const input = combobox.querySelector("input")

    userEvent.type(input, "not found")

    listOptions = screen.queryAllByRole("option")

    expect(listOptions).toHaveLength(1)

    expect(listOptions[0]).toHaveTextContent("No result found")

    userEvent.clear(input)

    userEvent.type(input, "a")

    listOptions = screen.queryAllByRole("option")

    expect(listOptions).toHaveLength(2)

    expect(listOptions[0]).toHaveTextContent("Walleye")
    expect(listOptions[1]).toHaveTextContent("Bass")
  })
})

function ClearableSelections() {
  const options = [
    { label: "Walleye", value: "walleye" },
    { label: "Bass", value: "bass" },
    { label: "Perch", value: "perch" },
    { label: "Pike", value: "pike" }
  ]
  const [value, setValue] = React.useState(["bass", "pike"])

  const handleClick = () => {
    setValue([])
  }

  return (
    <div>
      <MultiSelectInput options={options} value={value} />
      <button data-testid="clear-btn" onClick={handleClick}>Clear values</button>
    </div>
  )
}
