import { type RegisterOptions, type Validate } from 'react-hook-form';

import { type Option, type Row } from 'src/types/forms';
import {
  validateAlphanumeric,
  validateArrayMaxLength,
  validateAtLeastOneChecked,
  validateDatesCollision,
  validateDateYearsBetweenAndBefore,
  validateDecimalFormat,
  validateDecimalFormatDotOnly,
  validateEmail,
  validateEqual,
  validateEqualLength,
  validateFormatDate,
  validateIsAfterDate,
  validateIsBeforeDate,
  validateLowerCaseLetter,
  validateMaxLength,
  validateMinLength,
  validateMinLengthWithoutMessage,
  validateMinValueAllowed,
  validateNotEqualArray,
  validateNumberFormat,
  validateOnlyNumber,
  validateOnlyText,
  validatePhoneNumber,
  validateRequired,
  validateRequiredCheckboxGrid,
  validateRequiredPhoneNumber,
  validateRequiredWithOptional,
  validateRequiredWithoutMessage,
  validateUpperCaseLetter,
  validateUrl,
  validateUser,
  validateWithNumber,
  validateWithSpecialCharacter,
} from 'src/utils/validation';

type ValidationRule = Record<string, unknown> | Function;

type ValidateFunction = (value?: any) => Record<string, ValidationRule>;

type DateRange = { start: string; end: string };

const ruleToValidation: Record<string, ValidateFunction> = {
  requiredWithMessage: (message: any) =>
    validateRequired(typeof message === 'string' ? message : undefined),
  required: (isRequired: boolean) => validateRequiredWithoutMessage(isRequired),
  requiredPhoneNumber: (code: string) => validateRequiredPhoneNumber(code),
  requiredCheckboxGrid: (value: { rows: Row[]; options: Option[] }) => {
    const { rows, options } = value;
    return validateRequiredCheckboxGrid(rows, options);
  },
  checkValidFormatDate: (value: boolean | string) =>
    validateFormatDate(typeof value === 'string' ? value : undefined),
  checkYearsOfDate: () => validateDateYearsBetweenAndBefore,
  email: () => validateEmail(),
  url: () => validateUrl(),
  user: () => validateUser(),
  phoneNumber: (code: string) => validatePhoneNumber(code),
  alphanumeric: () => validateAlphanumeric,
  onlyText: () => validateOnlyText,
  onlyNumber: () => validateOnlyNumber,
  numberFormat: () => validateNumberFormat(),
  decimalFormat: () => validateDecimalFormat(),
  decimalFormatDotOnly: () => validateDecimalFormatDotOnly(),
  minLength: (value: any) => validateMinLength(value),
  maxLength: (value: number) => validateMaxLength(value),
  arrayMaxLength: (value: { value: number; message?: string }) => {
    const { value: maxLength, message } = value;
    return validateArrayMaxLength(maxLength, message);
  },
  equalLength: (value: number) => validateEqualLength(value),
  validateIsAfterDate: (value: {
    dateToCompare: Date | string;
    allowEqual?: boolean;
    message?: string;
  }) => {
    const { dateToCompare, allowEqual = true, message } = value;
    return validateIsAfterDate(dateToCompare, allowEqual, message || '');
  },
  validateIsBeforeDate: (value: {
    dateToCompare: Date | string;
    allowEqual?: boolean;
    message?: string;
  }) => {
    const { dateToCompare, allowEqual = true, message } = value;
    return validateIsBeforeDate(dateToCompare, allowEqual, message || '');
  },
  notEqualEmail: (value: { value: string[]; message: string }) => {
    const { value: equalValue, message } = value;
    return validateNotEqualArray(equalValue, message);
  },
  equal: (value: { value: unknown; message: string }) => {
    const { value: equalValue, message } = value;

    return validateEqual(equalValue, message);
  },
  validateAtLeastOneChecked: (values: boolean[]) =>
    validateAtLeastOneChecked(values),
  validateUpperCaseLetter: () => validateUpperCaseLetter,
  validateLowerCaseLetter: () => validateLowerCaseLetter,
  containsNumbers: () => validateWithNumber,
  containsSpecialCharacter: () => validateWithSpecialCharacter,
  validateMinLengthWithoutMessage: () => validateMinLengthWithoutMessage,
  validateDatesCollision: (value: {
    datesRange: DateRange[];
    errorMessage: string;
    allowInvertedRanges: boolean;
  }) => {
    const { datesRange, errorMessage, allowInvertedRanges } = value;
    return validateDatesCollision(
      datesRange,
      errorMessage,
      allowInvertedRanges,
    );
  },
  validateMinValueAllowed: (value: number) => validateMinValueAllowed(value),
  validateRequiredWithOptional: ({ valuesToCompare, message }) =>
    validateRequiredWithOptional(valuesToCompare, message),
};

export type RulesOptions = Partial<{
  requiredWithMessage: boolean | string;
  requiredPhoneNumber: boolean | string;
  requiredCheckboxGrid: boolean | { rows: Row[]; options: Option[] };
  checkValidFormatDate: boolean | string;
  checkYearsOfDate: boolean;
  required: boolean;
  email: boolean;
  url: boolean;
  user: boolean;
  phoneNumber: boolean | string;
  validateAtLeastOneChecked: boolean[];
  alphanumeric: boolean;
  onlyText: boolean;
  onlyNumber: boolean;
  numberFormat: boolean;
  decimalFormat: boolean;
  decimalFormatDotOnly: boolean;
  minLength: number;
  maxLength: number;
  equalLength: number;
  validateIsAfterDate:
    | false
    | {
        dateToCompare: Date | string;
        allowEqual?: boolean;
        message?: string;
      };
  validateIsBeforeDate: {
    dateToCompare: Date | string;
    allowEqual?: boolean;
    message?: string;
  };
  equal: { value: unknown; message: string };
  notEqualEmail: { value: string; message: string };
  validateUpperCaseLetter: boolean;
  validateLowerCaseLetter: boolean;
  containsNumbers: boolean;
  containsSpecialCharacter: boolean;
  validateMinLengthWithoutMessage: boolean;
  arrayMaxLength: { value: number; message?: string };
  validateDatesCollision: {
    datesRange: DateRange[];
    errorMessage: string;
    allowInvertedRanges: boolean;
  };
  validateMinValueAllowed: number;
  validateRequiredWithOptional: { valuesToCompare: any[]; message?: string };
}>;

export type Rules = Omit<RegisterOptions, 'validate'> &
  Partial<{ validate: Record<string, Validate<any, any>> }>;

export const useRules = (options: RulesOptions, customValidations?: any) => {
  let rules: Record<string, ValidationRule> = {};

  Object.entries(options).forEach(([option, value]) => {
    if (!value) return;

    const validate = ruleToValidation[option];
    if (!validate) return;

    const validation = validate(value);
    const type = Object.keys(validation)[0];

    if (type === 'validate') {
      const oldRules = rules[type] || {};
      const newRules = {
        [option]: validation[type as keyof typeof validation],
      };

      rules = {
        ...rules,
        [type]: {
          ...oldRules,
          ...newRules,
        },
      };
    } else {
      rules = {
        ...rules,
        ...validation,
      };
    }
  });

  return {
    ...rules,
    validate: {
      ...customValidations,
      ...rules.validate,
    },
  };
};

export default useRules;
