import React, { useEffect, useState } from "react";
import { FormInstance } from "antd/lib/form";

import { MultistageFormStageProps } from "./MultistageFormStage";
import {
  MultistageFormControls,
  MultistageFormControlsProps,
} from "./MultistageFormControls";

import { noop } from "./utilities";
import { to } from "utils/to";

/**
 * The properties of the multistage form
 *
 * @member {React.ReactElement} controls The controls element of the form, for navigating pages
 * @member {FormSubmitCallback} onCancel An event handler fired on form cancellation
 * @member {FormSubmitCallback} onFinish An event handler fired on form submission
 */
export interface MultistageFormProps {
  controls?: React.ReactElement<MultistageFormControlsProps>;
  initialValues?: any;
  onCancel?: (/*values: any*/) => void;
  onFinish: (values: any) => void;
  onStageChange?: (step: number, props: MultistageFormStageProps) => void;
  setStageSetter?: (
    setter: React.Dispatch<React.SetStateAction<number>>
  ) => void;
}

/**
 * Creates a form with multiple stages, defined by the component's children
 *
 * @param {MultistageFormProps} props The propeties for the form
 * @returns {React.ReactElement<MultistageFormStageProps>} The current form page element
 */
export const MultistageForm: React.FC<MultistageFormProps> = ({
  children,
  controls: formControls,
  initialValues: injectedInitialValues = {},
  onCancel = noop,
  onStageChange = noop,
  onFinish,
  setStageSetter = noop,
}) => {
  const [stage, setStage] = useState(1);
  const [currentForm, setCurrentForm] = useState<FormInstance>();

  // const [stageState, setStageState] = useState<any>(injectedInitialValues);
  const [accumulativeFormState, setAccumulativeFormState] = useState<any>(
    injectedInitialValues
  );

  const [loading, setLoading] = useState(false);

  const stageArray = React.useMemo(
    () => React.Children.toArray(children),
    [stage, children]
  );
  const stageCount = React.useMemo(
    () => React.Children.count(children),
    [stage, children]
  );

  const isFirstStage = React.useMemo(() => stage === 1, [stage]);
  const isLastStage = React.useMemo(() => stage === stageCount, [stage]);

  const currentStage = React.useMemo(
    () => stageArray[stage - 1] as React.ReactElement<MultistageFormStageProps>,
    [stage, children, currentForm]
  );

  useEffect(() => {
    setStageSetter(() => setStage);
  }, [setStage, setStageSetter]);

  useEffect(() => {
    onStageChange(stage, currentStage.props);
  }, [stage]);

  /**
   * Handles the transition from the current stage to the next stage, finishing
   * the form if the last stage is past by calling `onFinish`
   *
   * @memberof MultistageForm
   */
  const handleNext: React.MouseEventHandler<HTMLButtonElement> = async () => {
    setLoading(true);

    if (currentForm) {
      const [validationError] = await to(currentForm.validateFields());
      if (validationError) {
        setLoading(false);
      }

      currentForm.submit();
    }
  };

  /**
   * Handles the transition from the current stage to the previous stage,
   * cancelling the form if the first stage is past by calling `onCancel`
   *
   * @memberof MultistageForm
   */
  const handlePrev: React.MouseEventHandler<HTMLButtonElement> = () => {
    setLoading(true);

    if (isFirstStage) {
      onCancel(/*stageState*/);
      return;
    }

    setStage((step) => step - 1);
    setLoading(false);
  };

  const handleFormFailure = () => {
    setLoading(false);
  };

  const handleFormSuccess = (successValues: any) => {
    const formValues = currentForm?.getFieldsValue();
    const accumulativeStageState = {
      ...accumulativeFormState,
      ...formValues,
      ...successValues,
    };

    setAccumulativeFormState((formState: any) => {
      return {
        ...formState,
        ...accumulativeStageState,
      };
    });

    // TODO: Move `onFinish` to `useEffect`?
    if (isLastStage) {
      onFinish(accumulativeStageState);
    } else {
      setStage((prevStage) => prevStage + 1);
    }

    setLoading(false);
  };

  // Destructure step properties to retrieve step-specific controls
  const {
    controls: stageControls,
    initialValues: initialStageValues,
    ...stageProps
  } = React.useMemo(() => currentStage.props, [currentStage.props]);

  // Cascade by priority for the correct controls element
  const controls = React.useMemo(
    () =>
      stageControls ||
      formControls || (
        <MultistageFormControls
          handleNext={handleNext}
          handlePrev={handlePrev}
          stage={stage}
          stageCount={stageCount}
          loading={loading}
        />
      ),
    [stageControls, formControls, loading]
  );

  // Add props to the controls
  const configuredControls = React.useMemo(
    () =>
      React.cloneElement(controls, { handleNext, handlePrev, stage, loading }),
    [controls, handleNext, handlePrev, loading]
  );

  // Add the controls to the child
  // TODO: Augment MultistageFormStage here into an internal component type,
  //   so that users are not exposed to these internal props
  return React.useMemo(
    () =>
      React.cloneElement<MultistageFormStageProps>(currentStage, {
        ...stageProps,
        accumulativeFormState,
        controls: configuredControls,
        initialStageValues, // : { ...initialStageValues, ...stageState },
        // setStageState,
        setStageForm: setCurrentForm,
        onFinishFailure: handleFormFailure,
        onFinishSuccess: handleFormSuccess,
      }),
    [
      accumulativeFormState,
      currentStage,
      configuredControls,
      stageProps,
      setCurrentForm,
      // setStageState,
      handleFormFailure,
      handleFormSuccess,
    ]
  );
};
