import React, { ReactNode, useCallback, useEffect, useRef } from "react";
import { TextInputProps } from "react-native";

export type FormCallback = {
  onChangeValue?: (key: any, value: any) => void;
  onValidateValue?: (value: boolean) => void;
};

export type FormHook = {
  validate: (key: string, value: string, valid: boolean) => void;
};

// @ts-ignore
export interface FormProps extends TextInputProps {
  isSubmitted?: boolean;
  validateOnBlur?: boolean;
  requiredMsg?: string;
  validate?: {
    key: string;
    rules?: Array<{
      message: string;
      negative?: boolean;
      pattern: RegExp | string;
    }>;
    form: FormHook;
  };
  children?: ReactNode | ((props: FormProps, errMsg: string) => any);
  onChangeValue?: (value: string) => string;
  onChangeStatus?: (v: number) => void;
}

const testRegex = (regex: RegExp, value: string) => {
  const result = regex.test(value);
  regex.lastIndex = 0;
  return result;
};

export const useForm = (props?: FormCallback): FormHook => {
  const form = useRef<any>({});

  const check = useCallback(() => {
    for (let i in form.current) if (!form.current[i]) return false;
    return true;
  }, []);

  const validate = useCallback((key: string, value: string, valid: boolean) => {
    form.current[key] = valid;
    props?.onChangeValue?.(key, value);
    props?.onValidateValue?.(check());
  }, []);

  return { validate };
};

export const Form = (props: FormProps) => {
  const {
    editable,
    isSubmitted,
    validateOnBlur,
    maxLength,
    value,
    requiredMsg,
    validate,
    children,
    onBlur,
    onFocus,
    onChangeText,
    onChangeValue,
    onChangeStatus,
  } = props;
  const text = String(value || "");
  const { key, form, rules } = validate || {};
  const required = Array.isArray(rules);

  const refKey = useRef(key);
  const refFocus = useRef(false);
  const refValidate = useRef(false);

  const getErrMsg = (val = text) => {
    if (!required) return "";
    if (val || isSubmitted) refValidate.current = true;
    if (!refValidate.current) return "";
    if (!val.trim())
      return typeof requiredMsg === "string"
        ? requiredMsg
        : "This field is required";
    for (let i of rules) {
      const { pattern, message, negative } = i;
      const isMatch =
        typeof pattern === "string" ? pattern === val : testRegex(pattern, val);
      if (negative ? isMatch : !isMatch) return message;
    }
    return "";
  };

  const errMsg = getErrMsg();

  const changeStatus = () => {
    const v = errMsg ? 2 : editable && (text || refFocus.current) ? 1 : 0;
    onChangeStatus?.(v);
  };

  const _onChangeText = (v: string) => {
    const val = onChangeValue ? onChangeValue(v) : String(v);
    if (maxLength && val.length > maxLength) return;
    key && form?.validate(key, val, !getErrMsg(val));
    onChangeText?.(val);
  };

  const _onFocus = (e: any) => {
    refFocus.current = true;
    changeStatus();
    onFocus?.(e);
  };

  const _onBlur = (e: any) => {
    if (validateOnBlur) refValidate.current = true;
    refFocus.current = false;
    changeStatus();
    onBlur?.(e);
  };

  useEffect(() => {
    refKey.current && form?.validate(refKey.current, text, true);
    refKey.current = key;
  }, [key]);

  useEffect(() => {
    return () => {
      key && form?.validate(key, text, true);
    };
  }, [key, text]);

  useEffect(() => {
    changeStatus();
    key && form?.validate(key, text, !errMsg && (!required || !!text.trim()));
  }, [text + JSON.stringify(rules)]);

  return (
    <>
      {typeof children === "function"
        ? children(
            {
              ...props,
              onBlur: _onBlur,
              onFocus: _onFocus,
              onChangeText: _onChangeText,
            },
            errMsg
          )
        : children}
    </>
  );
};
