import React from "react"
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from "@testing-library/user-event"
import { SelectParent as SelectMenu, Select, Option } from "../select-menu"
import { separator } from ".."

describe("SelectMenu", () => {
  it("renders a trigger with a default placeholder 'Select'", () => {
    render(
      <SelectMenu
        options={[
          { label: "Option 1", value: "option1" },
          { label: "Option 2", value: "option2" },
          { label: "Option 3", value: "option3" }
        ]}
      />
    )

    const trigger = screen.getByRole("combobox")

    expect(trigger).toBeInTheDocument()
    expect(trigger).toHaveTextContent("Select")
  })

  it("allows to set a custom placeholder", () => {
    render(
      <SelectMenu
        options={[
          { label: "Option 1", value: "option1" },
          { label: "Option 2", value: "option2" },
          { label: "Option 3", value: "option3" }
        ]}
        placeholder="Custom placeholder"
      />
    )

    const trigger = screen.getByRole("combobox")

    expect(trigger).toBeInTheDocument()
    expect(trigger).toHaveTextContent("Custom placeholder")
  })

  it("renders a list of options", () => {
    render(
      <SelectMenu
        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 options = screen.getAllByRole("option")

    expect(trigger).toBeInTheDocument()
    expect(options).toHaveLength(3)
  })

  it("allows to select an option", () => {
    const handleChange = jest.fn()

    render(
      <SelectMenu
        options={[
          { label: "Option 1", value: "option1" },
          { label: "Option 2", value: "option2" },
          { label: "Option 3", value: "option3" }
        ]}
        onChange={handleChange}
      />
    )

    const trigger = screen.getByRole("combobox")
    userEvent.click(trigger)
    const option = screen.getByText("Option 2")

    userEvent.click(option)

    expect(handleChange).toHaveBeenCalledWith({
      target: { label: "Option 2", value: "option2", name: undefined }
    })
  })

  it("allows to set a default selected option", () => {
    render(
      <SelectMenu
        options={[
          { label: "Option 1", value: "option1" },
          { label: "Option 2", value: "option2" },
          { label: "Option 3", value: "option3" }
        ]}
        value="option2"
      />
    )

    const trigger = screen.getByRole("combobox")

    expect(trigger).toHaveTextContent("Option 2")
  })

  it("renders a visible label when prop is passed", () => {
    render(
      <SelectMenu
        label="Select an option"
        options={[
          { label: "Option 1", value: "option1" },
          { label: "Option 2", value: "option2" },
          { label: "Option 3", value: "option3" }
        ]}
      />
    )

    const label = screen.getByText("Select an option")

    expect(label).toBeInTheDocument()
  })

  it("applies aria-label to the reference when passed", () => {
    render(
      <SelectMenu
        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(
      <SelectMenu
        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")
  })

  it("provides an event object with the selected option", () => {
    const handleChange = jest.fn()

    render(
      <SelectMenu
        options={[
          { label: "Option 1", value: "option1" },
          { label: "Option 2", value: "option2" },
          { label: "Option 3", value: "option3" }
        ]}
        onChange={handleChange}
      />
    )

    const trigger = screen.getByRole("combobox")
    userEvent.click(trigger)
    const option = screen.getByRole("option", { name: /Option 2/i })

    userEvent.click(option)

    expect(handleChange).toHaveBeenCalledWith({
      target: { label: "Option 2", value: "option2", name: undefined }
    })
  })

  it("renders a custom component as option", () => {
    render(
      <SelectMenu
        options={[
          { label: "Option 1", value: "option1" },
          { label: "Option 2", value: "option2", component: <div>Custom Option</div> },
          { label: "Option 3", value: "option3" }
        ]}
      />
    )

    const trigger = screen.getByRole("combobox")
    userEvent.click(trigger)
    const option = screen.getByText("Custom Option")

    expect(option).toBeInTheDocument()
  })

  it("allows numbers as option values", () => {
    const handleChange = jest.fn()

    render(
      <SelectMenu
        options={[
          { label: "Option 0", value: 0 },
          { label: "Option 1", value: 1 },
          { label: "Option 2", value: 2 },
          { label: "Option 3", value: 3 }
        ]}
        onChange={handleChange}
      />
    )

    const trigger = screen.getByRole("combobox")
    userEvent.click(trigger)
    const option = screen.getByRole("option", { name: /Option 2/i })

    userEvent.click(option)

    expect(handleChange).toHaveBeenCalledWith({
      target: { label: "Option 2", value: 2, name: undefined }
    })
  })
})

describe("Select", () => {
  it("uses a default placeholder 'Select'", () => {
    render(
      <Select>
        <Option>Option 1</Option>
        <Option>Option 2</Option>
        <Option>Option 3</Option>
      </Select>
    )

    const trigger = screen.getByRole("combobox")

    expect(trigger).toBeInTheDocument()
    expect(trigger).toHaveTextContent("Select")
  })

  it("provides index with selected option", () => {
    const handleChange = jest.fn()
    render(
      <Select onChange={handleChange}>
        <Option>Option 1</Option>
        <Option>Option 2</Option>
        <Option>Option 3</Option>
      </Select>
    )

    const trigger = screen.getByRole("combobox")
    userEvent.click(trigger)
    const option = screen.getByRole("option", { name: /Option 2/i })

    userEvent.click(option)

    expect(handleChange).toHaveBeenCalledWith(1)
  })

  it("allows to set a custom placeholder", () => {
    render(
      <Select placeholder="Custom placeholder">
        <Option>Option 1</Option>
        <Option>Option 2</Option>
        <Option>Option 3</Option>
      </Select>
    )

    const trigger = screen.getByRole("combobox")

    expect(trigger).toBeInTheDocument()
    expect(trigger).toHaveTextContent("Custom placeholder")
  })

  it("renders a list of options", () => {
    render(
      <Select>
        <Option>Option 1</Option>
        <Option>Option 2</Option>
        <Option>Option 3</Option>
      </Select>
    )

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

    expect(trigger).toBeInTheDocument()
    expect(options).toHaveLength(3)
  })

  it("recognizes a selected option", () => {
    render(
      <Select>
        <Option>Option 1</Option>
        <Option selected>Option 2</Option>
        <Option>Option 3</Option>
      </Select>
    )

    const trigger = screen.getByRole("combobox")

    expect(trigger).toHaveTextContent("Option 2")
  })

  it("reflects a changed option when there is an initial selected option", () => {
    render(
      <Select>
        <Option>Option 1</Option>
        <Option selected>Option 2</Option>
        <Option>Option 3</Option>
      </Select>
    )

    let trigger = screen.getByRole("combobox")

    expect(trigger).toHaveTextContent("Option 2")

    userEvent.click(trigger)
    const option = screen.getByRole("option", { name: /Option 3/i })

    userEvent.click(option)

    trigger = screen.getByRole("combobox")

    expect(trigger).toHaveTextContent("Option 3")
  })

  it("selects non-string children as option", () => {
    const options = [
      { label: "Option 1", value: 1 },
      { label: "Option 2", value: 2 },
      { label: "Option 3", value: 3, component: <div>Custom Option</div> }
    ]

    render(
      <SelectMenu options={options} value={3} />
    )

    const trigger = screen.getByRole("combobox")

    expect(trigger).toHaveTextContent("Option 3")

    userEvent.click(trigger)

    const renderedOptions = screen.getAllByRole("option")
    expect(renderedOptions).toHaveLength(3)
    expect(renderedOptions[2]).toHaveTextContent("Custom Option")
    expect(renderedOptions[2]).toHaveAttribute("aria-selected", "true")
  })

  it("excludes separators as options", () => {
    const options = [
      { label: "Option 1", value: 1 },
      separator,
      { label: "Option 2", value: 2 },
      separator,
      { label: "Option 3", value: 3 }
    ]

    render(<SelectMenu options={options} />)

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

    const optionsList = screen.getAllByRole("option")

    expect(optionsList).toHaveLength(3)
  })

  it("recalculates the label if the value is changed", async () => {
    const options = [
      { label: "Option 1", value: 1 },
      { label: "Option 2", value: 2 },
    ]

    const { rerender } = render(<SelectMenu options={options} value={1} />)
    expect(await screen.findByRole("combobox")).toHaveTextContent("Option 1")

    rerender(<SelectMenu options={options} value={2} />)
    await waitFor(async () => {
      expect(await screen.findByRole("combobox")).toHaveTextContent("Option 2")
    })
  })

  it("uses typeahead to navigate through options", () => {
    render(
      <Select>
        <Option>Walleye</Option>
        <Option>Pike</Option>
        <Option>Perch</Option>
        <Option>Trout</Option>
      </Select>
    )

    const trigger = screen.getByRole("combobox")

    expect(trigger).toHaveTextContent("Select")
    userEvent.click(trigger)

    userEvent.type(trigger, "erc")
    userEvent.keyboard("{enter}")

    expect(trigger).toHaveTextContent("Perch")
  })

  it("does not allow interaction when disabled", () => {
    render(
      <Select disabled>
        <Option>Option 1</Option>
        <Option>Option 2</Option>
        <Option>Option 3</Option>
      </Select>
    )

    const trigger = screen.getByRole("combobox")

    expect(trigger).toHaveAttribute("aria-disabled", "true")
    expect(trigger).toHaveAttribute("aria-expanded", "false")

    userEvent.click(trigger)

    const options = screen.queryAllByRole("option")
    expect(options).toHaveLength(0)
    expect(trigger).toHaveAttribute("aria-expanded", "false")
  })
})
