import { setHasResponded } from 'domains/app/slice'
import { Provider } from 'domains/forms/context'
import { useValidations } from 'domains/forms/hooks'
import { getFormValuesByFormId } from 'domains/forms/selectors'
import {
  deregisterForm,
  updateControlTouched as dispatchControlTouched,
  updateControlValue as dispatchControlValue,
  registerForm,
} from 'domains/forms/slice'
import { useAppDispatch, type RootState } from 'domains/store'
import { FC } from 'preact/compat'
import {
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useState,
} from 'preact/hooks'
import { useSelector } from 'react-redux'

type FormProviderProps = {
  formId: string
  persistData?: boolean
  onError?: (_args: {
    errors: Record<string, any>
    isSubmitted: boolean
    isValid: boolean
  }) => void
  onSubmit: Function
  validationSchema?: Record<
    string,
    {
      fn: Function
      errorText: string
      compareValue: string | number | null
    }[]
  >
}

const FormProvider: FC<FormProviderProps> = ({
  children,
  formId,
  persistData = false,
  onError,
  onSubmit,
  validationSchema = {},
  ...props
}) => {
  const dispatch = useAppDispatch()
  const values = useSelector((store: RootState) =>
    getFormValuesByFormId(store, { formId }),
  )
  const [isSubmitted, setIsSubmitted] = useState(false)
  const [externalErrors, setExternalErrors] = useState({})
  const { isValid: validationIsValid, errors: validationErrors } =
    useValidations(values, validationSchema)
  const errors = useMemo(
    () => ({
      ...validationErrors,
      ...externalErrors,
    }),
    [validationErrors, externalErrors],
  )

  // register
  useLayoutEffect(() => {
    // register form in redux store
    dispatch(registerForm({ formId, persistData }))
  }, [formId, persistData, dispatch])

  // deregister
  useEffect(() => {
    return () => {
      // deregister form from redux store
      dispatch(deregisterForm({ formId }))
    }
  }, [formId, persistData, dispatch])

  const updateControlValue = useCallback(
    (name, value) => {
      dispatch(dispatchControlValue({ formId, name, value }))
    },
    [formId, dispatch],
  )

  const updateControlTouched = useCallback(
    (name, touched) => {
      dispatch(dispatchControlTouched({ formId, name, touched }))
    },
    [dispatch, formId],
  )

  // Function to manually set an error
  const setError = useCallback(
    (name, error) => {
      setExternalErrors((val) => {
        return {
          ...val,
          [name]: error,
        }
      })
    },
    [setExternalErrors],
  )

  const handleSubmit = useCallback(
    (e) => {
      e.preventDefault()
      // If the submitter is set to being aria-disabled, block the submit action
      const ariaDisabled = e.submitter.getAttribute('aria-disabled') === 'true'
      setIsSubmitted(!ariaDisabled)
      if (!ariaDisabled && validationIsValid) {
        dispatch(setHasResponded(true))
        onSubmit(values, { updateControlValue, setError })
      }
    },
    [
      validationIsValid,
      dispatch,
      onSubmit,
      values,
      updateControlValue,
      setError,
    ],
  )

  useEffect(() => {
    if (onError) {
      onError({
        errors,
        isSubmitted,
        isValid: Object.keys(errors).length === 0,
      })
    }
  }, [isSubmitted, errors, onError])

  const contextValue = useMemo(
    () => ({
      formId,
      values,
      errors,
      isValid: Object.keys(errors).length === 0,
      isSubmitted,
      handleSubmit,
      validationSchema,
      updateControlValue,
      updateControlTouched,
    }),
    [
      formId,
      values,
      errors,
      isSubmitted,
      handleSubmit,
      validationSchema,
      updateControlValue,
      updateControlTouched,
    ],
  )

  if (!formId) {
    console.error('"formId" is required.')
    return null
  }
  if (!onSubmit) {
    console.error('"onSubmit" is required.')
    return null
  }

  return (
    <Provider {...props} value={contextValue}>
      {children}
    </Provider>
  )
}

export default FormProvider
