import { type Dispatch, type SetStateAction, useState } from 'react';
import {
  type FieldErrors,
  type FieldValues,
  type UseFormReturn,
} from 'react-hook-form';
import { type UseMutationResult, useMutation } from 'react-query';

import { differenceInSeconds } from 'date-fns';

import {
  type DynamicFormBody,
  type DynamicFormProgressionBase,
  type DynamicFormValues,
  type ErrorTextsDefinition,
  SectionEvent,
} from '../types';
import { getFieldName } from '../utils/extraUtils';
import { getDynamicFormBody } from '../utils/getDynamicFormBody';

import useBuildDynamicForm, {
  buildFormSchemaAndValues,
} from './useBuildDynamicForm';

export type UseDynamicFormProps<
  T extends DynamicFormProgressionBase = DynamicFormProgressionBase,
> = {
  /** The function to initialize the dynamic form */
  initializeFn?: () => Promise<T>;
  /** The function to get the dynamic form progress */
  getProgressionFn?: () => Promise<T>;
  /** The function to update the dynamic form progress */
  updateProgressionFn?: ({ body }: { body: DynamicFormBody }) => Promise<T>;
  /** The function to handle the error */
  onError?: (error: unknown) => void;
  /** The function to handle the success */
  onProgressionSuccess?: () => void;
  /** The function to handle the error */
  onProgressionError?: () => void;
  /** The function to handle the complete form */
  onCompleteForm?: (formAnswerId: T['formAnswerId']) => void;
  /** The error texts */
  errorTexts: ErrorTextsDefinition;
};

export type UseDynamicFormReturn<
  T extends DynamicFormProgressionBase = DynamicFormProgressionBase,
> = {
  /** Whether the form is initializing */
  isInitializingForm: boolean;
  /** The dynamic form state */
  formResponse: T | null;
  /** Whether the form is loading */
  isLoading: boolean;
  /** The mutation to initialize the dynamic form */
  initializeMutation: UseMutationResult<T, unknown, void>;
  /** The mutation to get the dynamic form progression */
  getProgressionMutation: UseMutationResult<T, unknown, void>;
  /** The mutation to update the dynamic form progress */
  updateProgressionMutation: UseMutationResult<
    T,
    unknown,
    { body: DynamicFormBody }
  >;
  /** The react-hook-form instance */
  form: UseFormReturn<Record<string, unknown>, unknown>;
  /** Function to handle the submit form section */
  submitSection: (params?: { onError?: (error: unknown) => void }) => void;
  /** Function to change the step */
  changeStep: ({
    isBackStep,
    fieldValues,
  }: {
    isBackStep?: boolean;
    fieldValues?: FieldValues;
  }) => void;
  /** Function to get the first error index */
  getFirstErrorIndex: (
    fieldErrors: FieldErrors<DynamicFormValues>,
  ) => number | undefined;
  /** The title of the form */
  formTitle: string;
  /** Whether the form is frozen */
  isFrozen: boolean;
  /** Whether the form is on the first section */
  isOnFirstSection: boolean;
  /** Whether the form body should be shown */
  shouldShowFormBody: boolean;
  /** Whether the form footer should be shown */
  shouldShowFormFooter: boolean;
  /** Whether the form success screen should be shown */
  shouldShowFormSuccess: boolean;
  /** Function to set the form response */
  setFormResponse: Dispatch<SetStateAction<T | null>>;
  /** Function to get the form response */
  setFormTitle: Dispatch<SetStateAction<string>>;
  /** Function to get the form response */
  setIsFrozen: Dispatch<SetStateAction<boolean>>;
  /** Function to get the form response */
  setCanShowSuccessScreen: Dispatch<SetStateAction<boolean>>;
  /** Function to get the form response */
  setStartTime: Dispatch<SetStateAction<Date>>;
};

export const useDynamicForm = <
  T extends DynamicFormProgressionBase = DynamicFormProgressionBase,
>({
  onError,
  onProgressionSuccess,
  onProgressionError,
  initializeFn,
  getProgressionFn,
  updateProgressionFn,
  onCompleteForm,
  errorTexts,
}: UseDynamicFormProps<T>): UseDynamicFormReturn<T> => {
  const [formResponse, setFormResponse] = useState<T | null>(null);
  const [isFrozen, setIsFrozen] = useState(false);
  const [startTime, setStartTime] = useState(new Date());
  const [formTitle, setFormTitle] = useState('');
  const [canShowSuccessScreen, setCanShowSuccessScreen] = useState(false);

  const form = useBuildDynamicForm({
    sectionId: formResponse?.nextSection?.nameId || '',
    components: formResponse?.nextSection?.content.components || [],
    errorTexts,
  });

  // Mutation to INIT the dynamic form
  const initializeMutation = useMutation({
    mutationFn: initializeFn,
    onSuccess: data => {
      if (!data?.form?.title) {
        console.error(
          'No form title was returned from the API – Using empty string',
        );
      }

      setFormResponse(data);
      setFormTitle(data.form?.title || '');
    },
    onError: error => {
      onError?.(error);
    },
  });

  const getProgressionMutation = useMutation({
    mutationFn: getProgressionFn,
    onSuccess: data => {
      setFormResponse(data);
      setFormTitle(data?.form?.title || '');
    },
    onError: error => {
      onError?.(error);
    },
  });

  const updateProgressionMutation = useMutation({
    mutationFn: updateProgressionFn,
    onSuccess: newFormResponse => {
      if (newFormResponse.nextSection) {
        setFormResponse(newFormResponse);
        onProgressionSuccess?.();

        const { values } = buildFormSchemaAndValues({
          sectionId: newFormResponse.nextSection.nameId,
          components: newFormResponse.nextSection.content.components || [],
          errorTexts,
        });
        form.reset(values);
      } else {
        // If next section is null, we check if the form is finished
        if (newFormResponse.currentSection !== 'SUCCESS') {
          onProgressionError?.();
          return;
        }

        setFormResponse(newFormResponse);
        setIsFrozen(true);
        setCanShowSuccessScreen(true);

        onCompleteForm?.(newFormResponse.formAnswerId);
      }
    },
  });

  const changeStep = ({
    isBackStep,
    fieldValues,
  }: {
    isBackStep?: boolean;
    fieldValues?: FieldValues;
  }) => {
    if (!formResponse) {
      throw new Error('No form response provided');
    }

    if (!formResponse.id) {
      throw new Error('No progression ID provided');
    }

    if (!formResponse.formTag) {
      throw new Error('No form tag provided');
    }

    const { currentSection, nextSection } = formResponse || {};

    const currentSectionIsEqualToNextSection =
      currentSection === nextSection?.content.action?.nextSection;

    if (currentSectionIsEqualToNextSection) return;

    if (!nextSection) {
      console.error('No next section');
      return;
    }

    const body = getDynamicFormBody({
      sectionName: nextSection.nameId,
      components: nextSection.content.components || [],
      sectionEvent: isBackStep ? SectionEvent.Back : undefined,
      fieldValues: fieldValues || form.getValues(),
      fillingTime: differenceInSeconds(new Date(), startTime),
    });

    updateProgressionMutation.mutate({ body });

    setStartTime(new Date());
  };

  const getFirstErrorIndex = (fieldErrors: FieldErrors<DynamicFormValues>) => {
    if (!formResponse) {
      throw new Error('No form response provided');
    }

    if (!formResponse.nextSection) {
      throw new Error('No next section provided');
    }

    if (!formResponse.nextSection.content.components) {
      throw new Error('No components provided');
    }

    const firstErrorKey = Object.keys(fieldErrors)[0];

    const {
      nameId,
      content: { components },
    } = formResponse.nextSection || {};

    if (!firstErrorKey || !components) return;

    const firstErrorIndex = components.findIndex(
      component => getFieldName(nameId, component.nameId) === firstErrorKey,
    );

    return firstErrorIndex;
  };

  const submitSection = ({
    onError: onSubmitError,
  }: {
    onError?: (error: unknown) => void;
  } = {}) => {
    form.handleSubmit(
      (fieldValues: FieldValues) => changeStep({ fieldValues }),
      onSubmitError,
    )();
  };

  const isOnFirstSection =
    formResponse?.nextSection?.nameId === formResponse?.firstSection;

  const shouldShowFormBody = Boolean(
    !initializeMutation.isLoading && formResponse && !formResponse?.finished,
  );

  const shouldShowFormSuccess = Boolean(
    !initializeMutation.isLoading &&
      formResponse?.finished &&
      canShowSuccessScreen,
  );

  const shouldShowFormFooter = Boolean(formResponse && !formResponse?.finished);

  return {
    changeStep,
    form,
    formTitle,
    formResponse,
    getFirstErrorIndex,
    getProgressionMutation,
    initializeMutation,
    isFrozen,
    isInitializingForm: initializeMutation.isLoading,
    isLoading: updateProgressionMutation.isLoading,
    isOnFirstSection,
    setFormResponse,
    setFormTitle,
    setIsFrozen,
    setCanShowSuccessScreen,
    setStartTime,
    shouldShowFormBody,
    shouldShowFormFooter,
    shouldShowFormSuccess,
    submitSection,
    updateProgressionMutation,
  };
};

export default useDynamicForm;
