import { DevTool } from "@hookform/devtools";
import { yupResolver } from "@hookform/resolvers/yup";
import React, { Children, ReactElement, useEffect } from "react";
import {
  DeepPartial,
  FormProvider,
  UnpackNestedValue,
  useForm
} from "react-hook-form";
import styled from "styled-components";
import { ObjectSchema } from "yup";

const Form = styled.form`
  height: 100%;
  width: 100%;
  position: relative;
`;

enum RequestStatus {
  Fulfilled = "fulfilled",
  Rejected = "rejected"
}

interface Response {
  type: RequestStatus;
  message?: string;
  meta?: {
    requestStatus: RequestStatus;
  };
}

interface Props<DV, R> {
  // DV = Default values, R = Response

  /**
   * The default values for the form.
   */
  defaultValues: UnpackNestedValue<DeepPartial<DV>>;
  /**
   * Function called when submitting the form.
   */
  onSubmit: (values: UnpackNestedValue<DV>) => R & Response;
  /**
   * Optional function that is called if the form is submitted successfully.
   */
  onSuccess?: (response: R & Response) => void;
  /**
   * Children of the form e.g. Inputs / Buttons.
   */
  children: ReactElement[] | ReactElement;
  /**
   * Yup Validation Schema
   */
  validation: ObjectSchema;
  /**
   * Optionally show the devtools.
   */
  devtools?: boolean;
}

export const Form2 = <DV, R>({
  defaultValues,
  onSubmit,
  onSuccess,
  children,
  validation,
  devtools
}: Props<DV, R>) => {
  const methods = useForm({
    resolver: yupResolver(validation),
    defaultValues
  });

  // Resetting the form when we move between pages.
  useEffect(() => methods.reset(defaultValues), [window.location.href]);

  const { reset, control, setError } = methods;

  /**
   * Call the onSubmit function, if successful reset the form and call the onSuccess function.
   * If not successful set the submitError to the response message.
   */
  const handleSubmit = async (data: UnpackNestedValue<DV>) => {
    const response = await onSubmit(data);

    if (
      response.type === RequestStatus.Fulfilled ||
      response.meta.requestStatus === RequestStatus.Fulfilled
    ) {
      reset(data); // Reset the form after a successful submit with the data submitted.
      onSuccess?.(response);
      return;
    }

    return setError("submitError" as any, {
      type: "custom",
      message: response.message
    });
  };

  /**
   * Register and render Form Fields otherwise render any non-form fields normally.
   */
  function renderChild(child: ReactElement) {
    return child.props.name
      ? React.createElement(child.type, {
          ...{
            ...child.props,
            register: methods.register,
            key: child.props.name
          }
        })
      : child;
  }

  return (
    <FormProvider {...methods}>
      <Form onSubmit={methods.handleSubmit(handleSubmit)}>
        {Children.map(children, Child => renderChild(Child))}
      </Form>

      {devtools && <DevTool control={control} />}
    </FormProvider>
  );
};
