import React, { useState, RefObject } from 'react';
import get from 'lodash.get';
import set from 'lodash.set';
import { FieldValidator } from './types';

const initialContext: {
  formRef: RefObject<HTMLFormElement>;
  formName: string;
  setFormName: (name: string) => void;
  submitCount: number;
  incrementSubmitCount: () => void;
  isSubmitting: boolean;
  setIsSubmitting: (submitting: boolean) => void;
  submitButtonName?: string;
  setSubmitButtonName: (name?: string) => void;
  fieldNameToErrorId: Map<string, string>;
  setError: (fieldName: string, errorId: string) => void;
  removeError: (fieldName: string) => void;
  hasValidation: boolean;
  fieldValidators: Map<string, FieldValidator<any> | FieldValidator<any>[]>;
  validate: (values: any) => object;
} = {
  formRef: { current: null },
  formName: '',
  setFormName: (name: string) => {},
  submitCount: 0,
  incrementSubmitCount: () => {},
  isSubmitting: false,
  setIsSubmitting: () => {},
  submitButtonName: undefined,
  setSubmitButtonName: () => {},
  fieldNameToErrorId: new Map<string, string>(),
  setError: (fieldName: string, errorId: string) => {},
  removeError: (fieldName: string) => {},
  hasValidation: false,
  fieldValidators: new Map(),
  validate: () => ({})
};

const FormContext = React.createContext(initialContext);

type Props = {
  formRef: RefObject<HTMLFormElement>;
  children: React.ReactNode;
};

export function FormContextProvider({ formRef, children }: Props) {
  const [formName, setFormName] = useState('');

  // submitCount is for redux-form. Formik maintains its own submitCount
  const [submitCount, setSubmitCount] = useState(0);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submitButtonName, setSubmitButtonName] = useState<string>();
  const [fieldNameToErrorId, setFieldNameToErrorId] = useState<
    Map<string, string>
  >(new Map<string, string>());
  const [fieldValidators] = useState(
    new Map<string, (fieldName: string) => React.ReactNode | undefined>()
  );

  const validate = (values: any) => {
    const errors = {};
    [...fieldValidators.entries()].forEach(([key, validator]) => {
      const value = get(values, key);
      let error: React.ReactNode;
      if (Array.isArray(validator)) {
        for (let validate of validator) {
          error = validate(value);
          if (error) break;
        }
      } else {
        error = validator(value);
      }
      error && set(errors, key, error);
    });
    return errors;
  };

  const context = {
    formRef,
    formName,
    setFormName,
    submitCount,
    incrementSubmitCount: () => setSubmitCount(submitCount + 1),
    isSubmitting,
    setIsSubmitting,
    submitButtonName,
    setSubmitButtonName,
    fieldNameToErrorId,
    fieldValidators,
    hasValidation: false,
    validate,
    setError: (fieldName: string, errorId: string) => {
      const map = new Map<string, string>(
        fieldNameToErrorId.set(fieldName, errorId)
      );
      setFieldNameToErrorId(map);
    },
    removeError: (fieldName: string) => {
      if (fieldNameToErrorId.has(fieldName)) {
        fieldNameToErrorId.delete(fieldName);
        const map = new Map<string, string>(fieldNameToErrorId);
        setFieldNameToErrorId(map);
      }
    }
  };

  return (
    <FormContext.Provider value={context}>{children}</FormContext.Provider>
  );
}

export default FormContext;
