import { useFormikContext } from 'formik';
import React, { PropsWithChildren, useMemo } from 'react';
import { OptionalObjectSchema } from 'yup/lib/object';
import { ObjectSchemaDefinition } from '..';
import { ErrorTypeToStationError } from '../../../utils/ErrorTypeToStationError';
import { Actions, ActionsProps } from '../../Actions';
import { isNavigationAction } from '../../Actions/Action/Action';
import { ErrorType } from '../../models';
import { FormActionData, StationErrorStateType } from '../FormStation.models';

interface FormActionProps<T, Y> extends Omit<ActionsProps, 'actions'> {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  validationSchema?: OptionalObjectSchema<ObjectSchemaDefinition<any>>;
  actions?: FormActionData<T, Y>[];
  setStationError: (error: StationErrorStateType) => void;
  setValidationError: () => void;
  submitResponse?: React.MutableRefObject<Y | undefined>;
  alwaysSubmitBeforeAction?: boolean;
  className?: string;
  isFormSubmitting?: boolean;
}

/**
 * Saves the form before the action is performed.
 */
export const FormStationAction = <T, Y>(
  props: PropsWithChildren<FormActionProps<T, Y>>,
): JSX.Element => {
  const {
    actions,
    validationSchema,
    setStationError,
    setValidationError,
    submitResponse,
    alwaysSubmitBeforeAction,
    className = '',
  } = props;
  const {
    submitForm,
    resetForm,
    values,
    validateForm,
    isValid,
    dirty,
    isSubmitting,
  } = useFormikContext<T>();

  const updatedActions = useMemo(() => {
    return actions?.map((action) => {
      const { onActionSelected } = action;

      return isNavigationAction(action)
        ? action
        : {
            ...action,
            isDisabled: action.isDisabled || isSubmitting,
            onActionSelected: () => {
              (async () => {
                //TODO: Busy indicator (disable form?)
                if (dirty || alwaysSubmitBeforeAction) {
                  // We can't rely on 'isValid' alone, since that will only evaluate if the form is touched or 'submitForm' is called.
                  // On a create station though the from might not be touched but still being invalid.
                  // Also the validation on submitForm in here will not help, since the value of isValid is already bound to that method and will not change
                  // anymore while this method executes - even if it changes on the context!
                  if (
                    !isValid ||
                    (await validationSchema?.isValid(values)) === false
                  ) {
                    setValidationError();
                    // Making sure that the fields will actually show the validation messages (they won't if the from was not touched yet - e.g. on a create station)
                    validateForm();
                    return;
                  }

                  try {
                    await submitForm();

                    // Committing the changed values to the form to get the current version as the new "reset" state.
                    resetForm({ values });
                  } catch (error) {
                    // we will abort the action if saving is not successful
                    // a station error is already set by the "onSubmit"
                    return;
                  }
                }

                try {
                  const result =
                    onActionSelected &&
                    (await onActionSelected({
                      values,
                      submitResponse: submitResponse?.current,
                    }));
                  if (result !== undefined) {
                    setStationError(
                      ErrorTypeToStationError(
                        result,
                        'An error occurred when trying to execute the action.',
                      ),
                    );
                  }
                } catch (error) {
                  const stationError = ErrorTypeToStationError(
                    error as ErrorType,
                    'An error occurred when trying to execute the action.',
                  );
                  setStationError(stationError);
                }
              })();
            },
          };
    });
  }, [
    actions,
    alwaysSubmitBeforeAction,
    dirty,
    isSubmitting,
    isValid,
    resetForm,
    setStationError,
    setValidationError,
    submitForm,
    submitResponse,
    validateForm,
    validationSchema,
    values,
  ]);

  return <Actions actions={updatedActions} className={className} />;
};
