import { FormikHelpers } from 'formik';
import {
  Dispatch,
  SetStateAction,
  useCallback,
  useEffect,
  useRef,
  useState,
} from 'react';
import { showNotification } from '../../../initialize';
import { Data } from '../../../types';
import { ErrorTypeToStationError } from '../../../utils/ErrorTypeToStationError';
import { ErrorType } from '../../models';
import {
  InitialFormData,
  SaveDataFunction,
  StationErrorStateType,
} from '../FormStation.models';

export type FormStationDataProvider = <TValues extends Data, TSubmitResponse>(
  initialData: InitialFormData<TValues>,
  saveData: SaveDataFunction<TValues, TSubmitResponse>,
  saveNotificationMessage?: string,
) => {
  onSubmit: (
    values: TValues,
    formikHelpers: FormikHelpers<TValues>,
  ) => Promise<void>;
  stationError?: StationErrorStateType;
  setStationError: Dispatch<SetStateAction<StationErrorStateType | undefined>>;
  isFormSubmitting: boolean;
  lastSubmittedResponse: React.MutableRefObject<TSubmitResponse | undefined>;
  initialValues: TValues | null | undefined;
};

export const useDataProvider: FormStationDataProvider = <
  TValues extends Data,
  TSubmitResponse,
>(
  initialData: InitialFormData<TValues>,
  saveData: SaveDataFunction<TValues, TSubmitResponse>,
  saveNotificationMessage?: string,
) => {
  const [stationError, setStationError] = useState<StationErrorStateType>();
  const [isFormSubmitting, setIsFormSubmitting] = useState<boolean>(false);
  const lastSubmittedResponse = useRef<TSubmitResponse>();

  useEffect(() => {
    if (
      initialData.error ||
      (initialData.data === null && !initialData.loading) ||
      initialData.entityNotFound
    ) {
      const stationError = {
        ...ErrorTypeToStationError(
          initialData.error,
          'An error occurred when trying to load data.',
          'Entity not found',
        ),
        type: 'loading',
      };

      setStationError(stationError);
    } else {
      if (stationError?.type === 'loading') {
        // Only clear the error if it is a loading error, which now seems to be cleared.
        setStationError(undefined);
      }
    }
  }, [
    initialData.loading,
    initialData.error,
    initialData.entityNotFound,
    initialData.data,
    stationError?.type,
  ]);

  const onSubmit = useCallback(
    async (
      values: TValues,
      formikHelpers: FormikHelpers<TValues>,
    ): Promise<void> => {
      if (isFormSubmitting) {
        return;
      }

      try {
        setIsFormSubmitting(true);
        setStationError(undefined);
        if (!initialData.loading && saveData) {
          const response = await saveData(values, initialData, formikHelpers);
          if (response) {
            lastSubmittedResponse.current = response;
          }

          showNotification({
            title:
              saveNotificationMessage ??
              'Your changes were saved successfully.',
            options: {
              type: 'success',
              autoClose: 1500,
            },
          });
        }
      } catch (error) {
        setStationError(
          ErrorTypeToStationError(
            error as ErrorType,
            'An error occurred when trying to save data.',
          ),
        );

        // We still throw the error, to make sure that navigation or action execution
        // will not continue after a failed save.
        throw error;
      } finally {
        formikHelpers.setSubmitting(false);
        setIsFormSubmitting(false);
      }
    },
    [isFormSubmitting, initialData, saveData, saveNotificationMessage],
  );

  return {
    onSubmit,
    stationError,
    setStationError,
    isFormSubmitting,
    lastSubmittedResponse,
    initialValues: initialData.data,
  };
};
