import React from "react"
import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import SelectMenu, { Select, Option } 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 }
    })
  })
  it("renders a SelectMenu with a default placeholder 'Select'", () => {
    render(
      <SelectMenu
        type="autocomplete"
        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.tagName).toEqual("INPUT")
    expect(trigger.getAttribute("role")).toEqual("combobox")
  })

  it("uses type 'autocomplete' when async prop is set", () => {
    render(
      <SelectMenu
        async
        options={async () => {
          return [
            { 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.tagName).toEqual("INPUT")
    expect(trigger.getAttribute("role")).toEqual("combobox")
  })

  it("uses type 'autocomplete' when create prop is set", () => {
    render(
      <SelectMenu
        create
        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.tagName).toEqual("INPUT")
  })

  describe("Standalone Select", () => {
    it("does not support async prop", () => {
      // mock console.error to prevent error output in the console
      jest.spyOn(console, "error").mockImplementation(() => {})

      expect(() => {
        render(
          <Select async>
            <Option>Option 1</Option>
            <Option>Option 2</Option>
            <Option>Option 3</Option>
          </Select>
        )
      }).toThrow("Select component does not support async prop. Use SelectMenu instead.")

      // restore console.error
      console.error.mockRestore()
    })
  })
})
