import { useMemo, useReducer } from 'react'

import { createSlice, PayloadAction } from '@reduxjs/toolkit'

import { entries, keys } from '../method'
import bindActionCreators from './bindActionCreators'

export type Error = { error: boolean; errorMessage: string; value: string }
export type Validator = (value: string) => Error

export type ErrorState<S> = {
  [k in keyof S]: S[k] extends string | Date ? Error : S[k] extends Record<string, unknown> ? ErrorState<S> : unknown
}

export type Validators<S> = {
  [k in keyof S]: Validator | Validator[]
}

const createErrorInitState = <ObjectType>(obj: Record<string, unknown>) =>
  entries(obj).reduce((kys, [k, v]) => {
    if (v instanceof Object) return { ...kys, [k]: createErrorInitState(v as Record<string, unknown>) }
    return { ...kys, [k]: { error: false, errorMessage: undefined, value: v } }
  }, {} as ObjectType)

export type ErrorValidatePayload<S> = {
  [key in keyof S]: string
}

export const validState = (value: string): Error => ({ error: false, errorMessage: '', value })
export const invalidState = (value: string, errorMessage: string): Error => ({ errorMessage, error: true, value })
const requiredValidator = (value: string): Error => (value ? validState(value) : { error: true, errorMessage: 'Campo obrigatório.', value })

export const getErrorProps = (error: Error) => ({ error: error.error, helperText: error.errorMessage })

export const useErrorValidator = <S>(initialState: S, validators?: Validators<Partial<S>>, notRequiredKeys?: (keyof S)[]) => {
  const errorsInitState = createErrorInitState<ErrorState<S>>(initialState as Record<string, unknown>)

  const errorSlice = useMemo(
    () =>
      createSlice({
        name: 'errorValidator',
        initialState: errorsInitState,
        reducers: {
          validate: (state: ErrorState<S>, action: PayloadAction<Partial<ErrorValidatePayload<S>>>) => {
            const { payload } = action
            entries(payload).forEach(([k, v]) => {
              const value = v as string
              if (!state[k]) state[k] = validState(value)
              const sError = state[k] as Error
              sError.value = value
              const validator: Validator | Validator[] = validators && validators[k]
              const isRequired = notRequiredKeys ? !notRequiredKeys.find((nrk) => nrk === k) : true

              if (isRequired) {
                const validationError = requiredValidator(value)
                const { error, errorMessage } = validationError || validState(value)
                sError.error = error
                sError.errorMessage = errorMessage
              }
              if (validator) {
                if (validator instanceof Array) {
                  let validationError
                  validator.forEach((vld) => !validationError && (validationError = vld(value)))
                  const { error, errorMessage } = validationError || validState(value)
                  sError.error = error
                  sError.errorMessage = errorMessage
                }
                if (validator instanceof Function) {
                  const validationError: Error = validator(value)
                  const { error, errorMessage } = validationError || validState(value)
                  sError.error = error
                  sError.errorMessage = errorMessage
                }
              }
            })
          },
        },
      }),
    []
  )

  const [state, dispatch] = useReducer(errorSlice.reducer, errorsInitState)

  const actions = bindActionCreators(errorSlice.actions, dispatch) as typeof errorSlice.actions

  // useEffect(() => {
  //   entries(state).forEach(([k, v]) => actions.validate({ [k]: v }))
  // }, [])
  const errorState = state as ErrorState<S>

  const getValidState = useMemo(() => {
    const requiredKeys = notRequiredKeys ? keys(state).filter((k) => !notRequiredKeys.find((nRK) => nRK === k)) : keys(state)
    return requiredKeys.every((k) => !state[k].error && !!state[k].value)
  }, [state, notRequiredKeys])

  return { errorState, actions, validState: getValidState }
}
