import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import type { ChangeEvent } from "react";
import { Select } from ".";
import { Option } from "..";

const CLASS_NAME = "mobius-select";
const LABEL_CLASS_NAME = "mobius-label";

describe("Select", () => {
  it("should render without error", () => {
    const view = render(
      <Select label="Color">
        <Option>Red</Option>
        <Option>Blue</Option>
      </Select>,
    );
    expect(view).toBeTruthy();
  });

  it("should call onChange when clicked", async () => {
    const valueCallback = vi.fn();
    const callback = (e: ChangeEvent<HTMLSelectElement>) =>
      valueCallback(e.target.value);
    const { getByLabelText } = render(
      <Select label="Color" name="color" onChange={callback}>
        <Option value="red">Red</Option>
        <Option value="blue">Blue</Option>
      </Select>,
    );
    await userEvent.selectOptions(getByLabelText("Color"), ["Red"]);
    const redOption: HTMLOptionElement = screen.getByRole("option", {
      name: "Red",
    });
    expect(valueCallback).toHaveBeenCalledWith("red");
    expect(redOption.selected).toBeTruthy();
  });

  describe("with errors", () => {
    it("should render the error", () => {
      const { getByText } = render(
        <Select label="Color" name="color" errorMessage="Required">
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(getByText("Required")).toBeInTheDocument();
    });

    it("should set aria-invalid to true", () => {
      const { getByLabelText } = render(
        <Select label="Color" name="color" errorMessage="Required">
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(getByLabelText("Color").getAttribute("aria-invalid")).toBeTruthy();
    });

    it("should set the correct aria-describedby attribute", () => {
      const { getByLabelText } = render(
        <Select label="Color" name="color" errorMessage="Required">
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(getByLabelText("Color")).toHaveAccessibleDescription("Required");
    });

    it("should not set aria-describedby when there is no errorMessage", () => {
      const { getByLabelText } = render(
        <Select label="Color" name="color">
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(getByLabelText("Color")).not.toHaveAttribute("aria-describedby");
    });
  });

  describe("given a disabled option", () => {
    it("should render with disabled attribute applied", () => {
      render(
        <Select label="Color" name="color">
          <Option isDisabled>Please select a color</Option>
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );

      const options = screen.queryAllByRole("option");

      expect(options[0]).toBeDisabled();
    });
  });

  describe("isRequired", () => {
    it("should set aria-required to true", () => {
      render(
        <Select label="Color" name="color" errorMessage="Required" isRequired>
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(screen.getByLabelText("Color")).toBeRequired();
    });

    it("should have className of --is-required when true", () => {
      render(
        <Select label="Color" name="color" errorMessage="Required" isRequired>
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(screen.getByLabelText("Color")).toHaveClass("--is-required");
    });

    it("should have className of --is-optional when false", () => {
      render(
        <Select
          label="Color"
          name="color"
          errorMessage="Required"
          isRequired={false}
        >
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(screen.getByLabelText("Color")).toHaveClass("--is-optional");
    });

    it("should have neither --is-required nor --is-optional when unset", () => {
      render(
        <Select label="Color" name="color" errorMessage="Required">
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(screen.getByLabelText("Color")).not.toHaveClass("--is-required");
      expect(screen.getByLabelText("Color")).not.toHaveClass("--is-optional");
    });
  });

  describe("uses correct class names", () => {
    it("uses Mobius class", () => {
      const { container } = render(
        <Select label="Color" data-testid="test">
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(container.firstChild).toHaveClass("mobius");
    });

    it("uses base Mobius class", () => {
      render(
        <Select label="Color" data-testid="test">
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(screen.getByTestId("test")).toHaveClass(CLASS_NAME);
    });

    it("includes custom class name", () => {
      render(
        <Select label="Color" data-testid="test" className="my-class">
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(screen.getByTestId("test")).toHaveClass("my-class");
      expect(screen.getByTestId("test")).toHaveClass(CLASS_NAME);
    });

    it("includes label class name", () => {
      render(
        <Select label="Color" data-testid="test">
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      expect(
        screen.getByTestId("test").closest("div")?.parentNode?.firstChild,
      ).toHaveClass(LABEL_CLASS_NAME);
    });

    it("includes disabled class name", () => {
      render(
        <Select label="Color" data-testid="test" isDisabled>
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      const select = screen.getByTestId("test");
      const selectIcon = select.nextElementSibling;
      expect(select).toHaveClass("--is-disabled");
      expect(selectIcon).toHaveClass("--is-disabled");
    });

    it("includes valid state class name", () => {
      render(
        <Select label="Color" data-testid="test" isInvalid={false}>
          <Option>Red</Option>
          <Option>Blue</Option>
        </Select>,
      );
      const select = screen.getByTestId("test");
      const selectIcon = select.nextElementSibling;
      expect(select).toHaveClass("--is-valid");
      expect(selectIcon).toHaveClass("--is-valid");
    });
  });
});
