import { isEqual } from "lodash";
import React from "react";
import { Controller, get, useFormContext } from "react-hook-form";
import Select, { Options, MenuPlacement, MultiValue } from "react-select";
import CreatableSelect from "react-select/creatable";
import styled from "styled-components";
import { theme } from "../../../../css/globalStyle";
import {
  Description,
  Label,
  LabelContainer,
  Title,
  FieldErrMessage
} from "../../TextField";

const Container = styled.div`
  width: 100%;
  margin: 0.5rem 0;
`;

const selectStyle = {
  control: (
    styles: React.CSSProperties,
    { isFocused, selectProps: { width } }
  ) => ({
    ...styles,
    padding: "5px 8px",
    borderColor: isFocused ? theme.color.primary[500] : "#e5e5e5",
    textTransform: "capitalize",
    transition: "box-shadow 200ms",
    boxShadow: isFocused && "0 0 0 4px rgb(3, 165, 150, 30%)",
    width: width || "100%",

    "&:hover": {
      boxShadow: "0 0 0 4px rgb(3, 165, 150, 30%)"
    }
  }),
  option: (
    styles: React.CSSProperties,
    { data, isSelected, isDisabled, isFocused, isHovered }
  ) => {
    function backgroundColor() {
      if (isSelected) return theme.color.primary[100];
      if (isFocused || isHovered) return theme.color.grey[100];

      return "#fff";
    }

    return {
      ...styles,
      display: "flex",
      alignItems: "center",
      minHeight: "30px",
      backgroundColor: backgroundColor(),
      color: isDisabled ? theme.color.grey[600] : data.color
    };
  },
  singleValue: (styles: React.CSSProperties) => {
    return {
      ...styles,
      height: "100%",
      width: "100%",
      display: "flex",
      alignItems: "center"
    };
  },
  menuPortal: (styles: React.CSSProperties) => ({ ...styles, zIndex: 9999 })
};

export interface Option {
  value: string | number | Record<string, unknown>;
  label: string;
  isDisabled?: boolean;
}

interface Props {
  isMulti?: boolean;
  isCreatable?: boolean;
  options: Options<Option>;
  name: string;
  menuPlacement?: MenuPlacement;
  title?: string;
  isDisabled?: boolean;
  description?: string;
}

/**
 * Select component that can be used in forms. Now no longer needs to be wrapped in StandardFormField
 * @returns JSX.Element
 */
export const StandardFormSelect = ({
  isMulti,
  isCreatable,
  options,
  name,
  menuPlacement,
  isDisabled = false,
  title,
  description
}: Props) => {
  const {
    control,
    formState: { errors }
  } = useFormContext();

  // Get error message based on field name
  const error = get(errors, name);

  function handleChange(onChange: any, options: MultiValue<Option> | Option) {
    if (Array.isArray(options)) {
      const result = (options as Option[]).map((option: any) => option.value);

      return onChange(result);
    }

    onChange((options as Option).value);
  }

  // Get the initial values from the options
  function getValue(value: any) {
    // 0 is a valid value but considered falsey in JavaScript so need to exclude it.
    if (value !== 0 && !value) return;

    if (isMulti) {
      return options.reduce((acc: Option[], option: Option) => {
        value.forEach((value: any) => {
          if (isEqual(value, option.value)) {
            acc.push(option);
          }
        });
        return acc;
      }, []);
    }

    return options.find(option => option.value === value);
  }

  // If it is a multi select we filter out all the options that are already selected
  function filterOptions(options: Options<Option>, value: any) {
    if (isMulti) {
      options.filter(option => option.value !== value);
    }

    return options;
  }

  return (
    <Container>
      <Title>
        <LabelContainer>
          <Label htmlFor={name}>{title}</Label>
        </LabelContainer>
        {description && <Description>{description}</Description>}
      </Title>

      <Controller
        render={({ field: { value, onChange } }) => {
          /*
           * Currently allergens are the only value we are passing that are not a string.
           * Old allergen values have comment set to NULL which is causing the select to not render them.
           * This maps through any of the allergen values and changes the comment to "" if it is null.
           */
          if (Array.isArray(value) && typeof value[0] === "object") {
            value.forEach(v => {
              if (v.comment === null) {
                v.comment = "";
              }

              return v;
            });
          }

          return isCreatable ? (
            <CreatableSelect
              onChange={options => handleChange(onChange, options)}
              options={filterOptions(options, value)}
              styles={selectStyle as any}
              isMulti={isMulti}
              menuPlacement={menuPlacement || "auto"}
              value={getValue(value)}
              menuPortalTarget={document.body}
              menuShouldBlockScroll
              isDisabled={isDisabled}
            />
          ) : (
            <Select
              onChange={options => handleChange(onChange, options)}
              options={filterOptions(options, value)}
              styles={selectStyle as any}
              isMulti={isMulti}
              menuPlacement={menuPlacement || "auto"}
              value={getValue(value)}
              menuPortalTarget={document.body}
              menuShouldBlockScroll
              isDisabled={isDisabled}
            />
          );
        }}
        control={control}
        name={name}
      />
      {error && <FieldErrMessage>{error.message}</FieldErrMessage>}
    </Container>
  );
};
