import React, { ChangeEvent, useEffect } from "react";
import styled from "styled-components";
import { FormController } from "../FormController";
import {
  Description,
  Label,
  LabelContainer,
  TextField,
  Title
} from "../../TextField";
import {
  FieldValues,
  get,
  useFormContext,
  UseFormSetValue,
  useWatch
} from "react-hook-form";

const FieldParent = styled.div`
  position: relative;
  width: 100%;
`;

const CustomField = styled.div<{ flex?: boolean }>`
  width: 100%;
  display: ${({ flex }) => (flex ? "flex" : "block")};
  justify-content: space-between;
  align-items: center;
`;

interface Props {
  title?: string;
  description?: string;
  elType?: string;
  type: string;
  name: string;
  extraStyles?: React.CSSProperties;
  customOnChangeFunc?: (
    e: React.ChangeEvent,
    setValue: UseFormSetValue<FieldValues>
  ) => void;
  placeholder?: string;
  setValue?: (
    name: string,
    value: any,
    config?:
      | Partial<{
          shouldValidate: boolean;
          shouldDirty: boolean;
        }>
      | undefined
  ) => void;
  min?: any;
  step?: any;
  disabled?: boolean;
  fontSize?: string;
  validation?: any;
  children?: any;
  pattern?: string;
  watchedField?: string;
  watchedValue?: string | string[] | boolean;
  defaultValue?: any;
  // Component rendered opposite the label
  subComponent?: JSX.Element;
  flex?: boolean;
}

const Content = React.memo(
  ({
    title,
    description,
    children,
    elType,
    type,
    watchedField,
    name,
    customOnChangeFunc,
    placeholder,
    min,
    step,
    watchedValue,
    disabled,
    defaultValue,
    extraStyles,
    fontSize,
    pattern,
    subComponent,
    flex
  }: Props) => {
    const {
      setValue,
      register,
      formState: { errors },
      control
    } = useFormContext();

    // If we pass a defaultValue to the form field we set it automatically here
    // This is because we cant set default values to nested array of objects
    useEffect(() => {
      defaultValue && setValue(name, defaultValue);
    }, [defaultValue, setValue, name]);

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

    // If watched field is passed, we use useWatch hook which allows us to listen to
    // a specific field. In future versions, will include 'watch behaviour' - for now just
    // toggling display.

    const useWatch_WatchedField = useWatch({
      control,
      name: watchedField, // without supply name will watch the entire form, or ['firstName', 'lastName'] to watch both
      defaultValue: "" // default value before the render
    });

    // If we pass in an array of values we check if the watched field is equal to any of those values
    const isWatched = Array.isArray(watchedValue)
      ? watchedValue.includes(useWatch_WatchedField)
      : watchedValue === useWatch_WatchedField;

    // If we pass in a custom change function then we use it otherwise just set the value.
    // Manually setting the value so we can make sure the input is dirtied on change.
    function handleChange(
      e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
    ) {
      !!customOnChangeFunc
        ? customOnChangeFunc(e, setValue)
        : setValue(name, e.target.value, { shouldDirty: true });
    }

    return (
      <>
        {type !== "noRender" && (
          <FieldParent
            style={{
              ...extraStyles,
              display: watchedField ? (isWatched ? "block" : "none") : "block",
              opacity: disabled ? 0.5 : 1
            }}
            className={fontSize}
          >
            {/* If the type prop passed is 'custom' then we create a label. 
          Otherwise, the label is handled by the TextField */}

            {type === "custom" && (
              <CustomField flex={flex}>
                <Title flex={flex}>
                  <LabelContainer>
                    <Label htmlFor={name}>{title}</Label>
                  </LabelContainer>
                  {description && <Description>{description}</Description>}
                </Title>

                {React.cloneElement(children, {
                  name,
                  disabled
                })}
              </CustomField>
            )}

            {/* If the type prop passed is 'custom' then we clone it's children and pass props.
         Otherwise, render either an input or textarea. */}

            {type !== "custom" && type !== "noRender" && (
              <TextField
                {...register(name)}
                description={description}
                placeholder={placeholder}
                type={type}
                autoComplete="off"
                onChange={handleChange}
                label={title}
                min={min}
                step={step}
                disabled={disabled}
                pattern={pattern}
                textarea={elType === "textarea"}
                error={error?.message}
                subComponent={subComponent}
              />
            )}
          </FieldParent>
        )}
      </>
    );
  }
);

export const StandardFormField = (props: Props) => {
  const { type, name, extraStyles, disabled, fontSize } = props;

  return (
    <FormController
      name={name}
      render={() => {
        return (
          <>
            {type !== "noRender" && (
              <FieldParent
                style={{
                  ...extraStyles,
                  opacity: disabled ? 0.5 : 1
                }}
                className={fontSize}
              >
                <Content {...props} />
              </FieldParent>
            )}
          </>
        );
      }}
    />
  );
};
