import { yupResolver } from "@hookform/resolvers/yup";
import React, { Children, useEffect, useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import styled from "styled-components";
import { FieldErrMessage } from "../../TextField";
import { DevTool } from "@hookform/devtools";

const Parent = styled.div`
  width: 100%;
  z-index: 10;
  display: flex;
  position: relative;
  > form {
    width: inherit;
  }
`;

const FormDiv = styled.div`
  width: 100%;
  position: relative;
  display: flex;
`;

const ButtonsHolder = styled.div`
  display: flex;
`;

interface FormTemplateProps {
  // To be deleted.
  callback?: any; // two callbacks unnessecary.

  // The main button submit the form.
  actionButton?: ({
    submitting,
    isDirty,
    reset
  }: {
    submitting: boolean;
    isDirty: boolean;
    reset: (
      values?: Record<string, any>,
      options?: Record<string, boolean>
    ) => void;
  }) => JSX.Element | null;
  // The button to cancel the form or go back.
  // Note, this is optional.
  cancelButton?: any;
  // Default values for the form.
  defaultValues: any;
  // close is our main callback function which
  // is called after succesfully submitting the form.
  close?: any;
  // The children to be rendered, mainly StandardFormFields and
  // StandardFormField s. can also render out standard JSX etc.
  children?: any;
  // The primary form validation for fields.
  validation?: any;
  // The action creator that's called on submitting the form.
  actionCreator: any;
  // Render custom buttons instead of the default button container
  customButtons?: boolean;
  // Function that renders out the submitting containers, for both
  // when the form is being submitted, and displaying the success or
  // error messages.
  renderSubmittingContainer?: any;
  // An additional div to be rendered next to the form to show additional
  // information, for example in session form.
  sideTab?: any;
  // Add a custom timeout for the submitting container
  customSubmittingContainerTimeout?: number;
  // Passed to the formtemplate if you want to display the submit error message on the
  // same page rather than in the submitting container.
  pageSubmitError?: boolean;
  // Pass the redux dispatch function
  dispatch?: any;
  // Hmm.. random styling that should be passed as a single argument.
  // Will replace this.
  customWidth?: any;
  customHeight?: any;
  customMargin?: any;
  flexDirection?: any;
  formPadding?: any;
  parentJustifyContent?: any;
  buttonsJustifyContent?: any;
  buttonsMargin?: string;
  ref?: React.Ref<HTMLFormElement>;
  /**
   * Show the devtools
   */
  devtools?: boolean;
}

interface SEProps {
  message: string | null;
}

const FormTemplate: React.FC<FormTemplateProps> = React.forwardRef(
  (
    {
      actionButton,
      cancelButton,
      defaultValues,
      close,
      children,
      validation,
      actionCreator,
      renderSubmittingContainer,
      sideTab,
      callback,
      customWidth,
      customHeight,
      customMargin,
      flexDirection = "column",
      formPadding,
      parentJustifyContent,
      buttonsJustifyContent,
      buttonsMargin,
      customSubmittingContainerTimeout,
      pageSubmitError,
      dispatch,
      customButtons,
      devtools
    },
    ref
  ) => {
    const methods = useForm({
      resolver: yupResolver(validation),
      defaultValues
    });

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

    const [submitting, setSubmitting] = useState(false);
    const [submitted, setSubmitted] = useState(false);
    const [submitError, setSubmitError] = useState<SEProps>({ message: null });

    const onSubmit = async (data: any) => {
      setSubmitting(true);
      // Response is an object with data and message.
      const res = await dispatch(actionCreator(data));

      // Still the same time out but the error now dissapears
      // in the same amount of time.
      if (
        res?.message === "success" ||
        res?.meta.requestStatus === "fulfilled"
      ) {
        setSubmitting(false);
        methods.reset(data);
      } else {
        setSubmitError({ message: res?.message || "Error" });
        setSubmitting(false);
        setSubmitted(true);
      }

      if (callback) callback(Object.values(data));

      if (!!renderSubmittingContainer) {
        return setTimeout(
          () => {
            // Getting the submitting container
            const submitContainer = document.getElementById("submitContainer");

            // If it isn't null, we add fadeOut animation, and on animation
            // end we setSubmitting to false.
            if (submitContainer !== null) {
              submitContainer.classList.add("animate__fadeOut");
              submitContainer.addEventListener("animationend", () => {
                setSubmitting(false);
                setSubmitted(false);
                submitContainer.classList.remove("animate__fadeOut");
                // setSubmitError({ message: null });
              });
            }

            // Then close callback is called
            return close?.({
              res,
              reset: methods.reset
            });
          },
          customSubmittingContainerTimeout !== undefined
            ? customSubmittingContainerTimeout
            : res.message === "success"
            ? 1500
            : 2500
        );
      }

      close?.({
        res,
        reset: methods.reset
      });

      return;
    };

    // This is the main helper function that renders of children props. If custom
    // grouping or styling is being applied, we check if a div is being passed,
    // and render out it's formField children, then pass custom formController props.
    function renderChild(Child: any) {
      if (Child?.type === "div" && typeof Child?.props.children !== "string") {
        return (
          <div style={Child.props.style}>
            {Child.props.children.map((subChild: any, i: any) => {
              return subChild.type !== "div" ? (
                <div key={i} style={{ width: "100%" }}>
                  {React.cloneElement(subChild, { validation })}
                </div>
              ) : (
                <div key={i} style={{ width: "100%" }}>
                  {React.cloneElement(subChild, { validation })}
                </div>
              );
            })}
          </div>
        );
      } else {
        return React.cloneElement(Child, { validation });
      }
    }

    return (
      <Parent style={{ justifyContent: parentJustifyContent || "center" }}>
        {!!sideTab && sideTab}

        {renderSubmittingContainer?.({ submitting, submitted, submitError })}

        <FormProvider {...methods}>
          <form
            onSubmit={methods.handleSubmit(onSubmit)}
            style={{
              width: customWidth ? customWidth : "inherit",
              height: customHeight ? customHeight : "inherit",
              margin: customMargin ? customMargin : "0px",
              padding: formPadding
            }}
            ref={ref}
          >
            <FormDiv style={{ flexDirection }}>
              {Children.map(
                children,
                Child => Child !== null && renderChild(Child)
              )}
            </FormDiv>

            {pageSubmitError && (
              <FieldErrMessage>{submitError.message}</FieldErrMessage>
            )}

            {customButtons ? (
              actionButton({
                submitting,
                isDirty: methods.formState.isDirty,
                reset: methods.reset
              })
            ) : (
              <ButtonsHolder
                style={{
                  justifyContent: buttonsJustifyContent || "flex-end",
                  margin: buttonsMargin || "20px 0px 10px 0px"
                }}
              >
                {cancelButton && (
                  <div style={{ marginRight: "12px" }}>{cancelButton}</div>
                )}

                {actionButton({
                  submitting,
                  isDirty: methods.formState.isDirty,
                  reset: methods.reset
                })}
              </ButtonsHolder>
            )}
          </form>

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

export const Form = FormTemplate;
