import { FormControl, OutlinedInput, Select as MUISelect } from "@mui/material";
import React, { forwardRef } from "react";
import { twJoin } from "tailwind-merge";

import { InputLabel } from "../inputLabel/InputLabel";
import { useInputStyles } from "../input/Input.styles";
import { RequiredIndicator } from "../input/RequiredIndicator";
import { MenuItem } from "../menu/MenuItem";
import { useMenuItemStyles } from "../menu/MenuItem.styles";
import { Typography } from "../typography/Typography";
import { ErrorIcon } from "../input/ErrorIcon";
import { paperStyles } from "../menu/paperStyles";
import { twMerge } from "../../../tailwind.config";
import { GroupedSelectOption, SelectOption, SelectProps } from "./types";
import { RenderedValue } from "./RenderedValue";
import { SelectIcon } from "./SelectIcon";

export const Select = forwardRef<
  HTMLSelectElement,
  Omit<SelectProps, "className" | "style"> & { ESCAPE_className?: string }
>(
  (
    {
      fullWidth,
      required,
      placeholder,
      label,
      id = undefined,
      options,
      multiple,
      onChange,
      value,
      height = "40",
      error = false,
      errorText = undefined,
      disabled,
      ESCAPE_className,
      ...restProps
    },
    ref
  ) => {
    const labelId = id !== undefined ? `${id}-label` : undefined;
    const optionsAreGrouped = "options" in (options?.[0] ?? {});

    const inputClasses = useInputStyles({
      height,
      variant: "outlined",
      error,
      disabled,
    });
    const menuItemClasses = useMenuItemStyles({
      color: "main",
      disabled: false,
    });

    const errorIconComponent = () => (
      <ErrorIcon
        style={{
          position: "absolute",
          marginRight: "12px",
          right: "0",
        }}
        errorText={errorText}
      />
    );

    const mapToMenuItem = ({
      value: optionValue,
      label: optionLabel,
      icon,
      disabled,
    }: SelectOption) => {
      const isSelected = multiple
        ? (value as string[])?.includes(optionValue)
        : value === optionValue;

      return (
        <MenuItem
          key={optionValue}
          value={optionValue}
          classes={menuItemClasses}
          data-testid={optionValue}
          leftIcon={icon}
          rightIcon={isSelected ? "checkmark" : undefined}
          disabled={disabled}
        >
          <div className="flex-1">
            <Typography
              size="200"
              color="main"
              ESCAPE_className="disabled:text-N60"
            >
              {optionLabel}
            </Typography>
          </div>
        </MenuItem>
      );
    };

    return (
      <FormControl classes={{ root: "max-w-full" }} fullWidth={fullWidth}>
        {typeof label === "string" ? (
          <InputLabel id={labelId}>{label}</InputLabel>
        ) : (
          label
        )}
        <MUISelect
          IconComponent={error ? errorIconComponent : SelectIcon}
          variant="outlined"
          classes={{
            select: ESCAPE_className,
            nativeInput: "top-0",
          }}
          displayEmpty={!!placeholder}
          id={id}
          labelId={labelId}
          disabled={disabled}
          input={
            <OutlinedInput
              error={error}
              classes={{
                ...inputClasses,
                input: twMerge(
                  inputClasses.input,
                  "box-border focus:bg-transparent min-w-[140px] !pr-32",
                  height === "40" ? "p-6 min-h-[40px]" : "p-6 min-h-[32px]"
                ),
              }}
            />
          }
          multiple={multiple}
          ref={ref}
          renderValue={(value) => (
            <RenderedValue
              height={height}
              selected={value as string | string[]}
              options={options}
              multiple={multiple}
              placeholder={placeholder}
            />
          )}
          MenuProps={{
            anchorOrigin: {
              vertical: "bottom",
              horizontal: "center",
            },
            transformOrigin: {
              horizontal: "center",
              vertical: -8,
            },
            classes: {
              paper: twMerge(paperStyles, "p-4 box-content max-w-[140px]"),
            },
            MenuListProps: {
              classes: { root: "p-4" },
            },
          }}
          value={value ?? ""}
          onChange={(event, child) => {
            const clickedOnGroup = multiple
              ? (event.target.value as string[])?.some((x) => !x)
              : !event.target.value;

            if (onChange && !clickedOnGroup) {
              onChange(event, child);
            }
          }}
          {...restProps}
        >
          {optionsAreGrouped
            ? (options as GroupedSelectOption[]).map(
                (
                  { label: groupLabel, primary, options: groupOptions },
                  index
                ) => [
                  <Typography
                    key={groupLabel}
                    size="75"
                    weight="medium"
                    uppercase
                    color="inherit"
                    ESCAPE_className={twJoin(
                      "-mx-8 block max-w-full overflow-hidden text-ellipsis whitespace-nowrap py-8 px-16",
                      primary ? "text-B400" : "text-N100",
                      index !== 0 &&
                        "mt-8 border-0 border-t-1 border-solid border-N30 pt-16"
                    )}
                  >
                    {groupLabel}
                  </Typography>,
                  groupOptions.map(mapToMenuItem),
                ]
              )
            : (options as SelectOption[]).map(mapToMenuItem)}
        </MUISelect>
        {required && <RequiredIndicator hasLabel={!!label} />}
      </FormControl>
    );
  }
);

Select.displayName = "Select";
