import { useCallback, useEffect, useRef } from 'react'
import { useRerender } from '../../hooks/useRerender.js'
import { FormErrors, FormValues } from './types.js'
import { deepEqual, deepMerge, Path, setByPath } from '@saulx/utils'

type UseFormProps = {
  initialValues?: FormValues
  initialValuesChecksum?: number
  onSubmit: (values: FormValues, entityType?: string) => void | Promise<void>
  validate?: (
    values: FormValues,
    entityType?: string,
  ) => FormErrors | Promise<FormErrors>
  onSubmitError?: (error?: any, entityType?: string) => void
  disabled?: boolean
}

function useForm({
  initialValues,
  initialValuesChecksum,
  onSubmit,
  validate,
  onSubmitError,
  disabled,
}: UseFormProps) {
  const rerender = useRerender()
  const state = useRef<{
    values: FormValues
    changes: FormValues
    mergedValues: FormValues
    errors: FormErrors
    isSubmitting: boolean
    isValidating: boolean
    isDirty: boolean
    prevInitialValues: FormValues
    failedAtLeastOneValidation: boolean
    numberOfInFlightValidations: number
    isDisabledFromOutside: boolean
  }>({
    values: initialValues ? structuredClone(initialValues) : {},
    changes: {},
    mergedValues: initialValues ? structuredClone(initialValues) : {},
    errors: {},
    isSubmitting: false,
    isValidating: false,
    isDirty: false,
    prevInitialValues: initialValues ? structuredClone(initialValues) : {},
    failedAtLeastOneValidation: false,
    numberOfInFlightValidations: 0,
    isDisabledFromOutside: disabled,
  })

  useEffect(() => {
    if (state.current.isSubmitting) return

    state.current.prevInitialValues = initialValues
      ? structuredClone(initialValues)
      : {}
    state.current.values = initialValues ? structuredClone(initialValues) : {}
    state.current.mergedValues = deepMerge(
      state.current.values,
      state.current.changes,
    )
    state.current.isDirty = !deepEqual(
      state.current.mergedValues,
      state.current.prevInitialValues,
    )
    rerender()
  }, [initialValues, initialValuesChecksum])

  useEffect(() => {
    if (state.current.isDisabledFromOutside === disabled) return

    state.current.isDisabledFromOutside = disabled
    rerender()
  }, [disabled])

  const validateForm = useCallback(
    async (entityType?: string) => {
      if (!validate) return

      state.current.numberOfInFlightValidations += 1
      state.current.isValidating = true
      rerender()

      const validationErrors = await validate(
        state.current.mergedValues,
        entityType,
      )
      state.current.numberOfInFlightValidations -= 1

      if (state.current.numberOfInFlightValidations !== 0) return

      if (!deepEqual(state.current.errors, validationErrors)) {
        state.current.errors = validationErrors
      }
      if (
        !state.current.failedAtLeastOneValidation &&
        Object.keys(state.current.errors).length
      ) {
        state.current.failedAtLeastOneValidation = true
      }

      state.current.isValidating = false
      rerender()
    },
    [validate],
  )

  const setValue = useCallback(
    (path: Path, value: string | boolean | number | string[]) => {
      if (state.current.isSubmitting) return

      setByPath(state.current.changes, path, value)
      state.current.mergedValues = deepMerge(
        state.current.values,
        state.current.changes,
      )
      state.current.isDirty = !deepEqual(
        state.current.mergedValues,
        state.current.prevInitialValues,
      )
      if (state.current.failedAtLeastOneValidation) {
        validateForm()
      }
      rerender()
    },
    [validateForm],
  )

  const submitForm = useCallback(
    async (entityType?: string) => {
      if (state.current.isSubmitting) return

      state.current.isSubmitting = true
      rerender()

      await validateForm(entityType)
      if (Object.keys(state.current.errors).length !== 0) {
        onSubmitError?.(new Error('validation errors'), entityType)
        state.current.isSubmitting = false
        rerender()
        return
      }

      try {
        await onSubmit(state.current.mergedValues, entityType)

        state.current.values = state.current.mergedValues
        state.current.changes = {}
        state.current.isDirty = false
        state.current.isSubmitting = false
        rerender()
      } catch (e) {
        state.current.isSubmitting = false
        rerender()
        onSubmitError?.(e, entityType)
      }
    },
    [validateForm, onSubmit],
  )

  const resetForm = useCallback(() => {
    state.current = {
      values: initialValues ? structuredClone(initialValues) : {},
      changes: {},
      mergedValues: initialValues ? structuredClone(initialValues) : {},
      errors: {},
      isSubmitting: false,
      isValidating: false,
      isDirty: false,
      prevInitialValues: initialValues ? structuredClone(initialValues) : {},
      failedAtLeastOneValidation: false,
      numberOfInFlightValidations: 0,
      isDisabledFromOutside: disabled,
    }

    rerender()
  }, [initialValues, initialValuesChecksum])

  return {
    values: state.current.mergedValues,
    errors: state.current.errors,
    isSubmitting: state.current.isSubmitting,
    isValidating: state.current.isValidating,
    isDirty: state.current.isDirty,
    isDisabledFromOutside: state.current.isDisabledFromOutside,
    setValue,
    submitForm,
    validateForm,
    resetForm,
  }
}

export { useForm }
export type { UseFormProps }
