import type { AbstractControl } from './abstract-control';

import { isArray, isNull, isNumber, isString } from 'lodash';

export interface ValidationErrors {
  [key: string]: any;
}

export type ValidatorFn = (control: AbstractControl) => ValidationErrors | null;

export type AsyncValidatorFn = (control: AbstractControl) => Promise<ValidationErrors | null>;

export class Validators {
  static min(min: number): ValidatorFn {
    return (control) => {
      if (isString(control.value) || isNumber(control.value)) {
        const value = Number(control.value);
        if (Number.isNaN(value)) {
          return null;
        } else {
          return value < min ? { min: { min, actual: control.value } } : null;
        }
      }

      return null;
    };
  }

  static max(max: number): ValidatorFn {
    return (control) => {
      if (isString(control.value) || isNumber(control.value)) {
        const value = Number(control.value);
        if (Number.isNaN(value)) {
          return null;
        } else {
          return value > max ? { max: { max, actual: control.value } } : null;
        }
      }

      return null;
    };
  }

  static required(control: AbstractControl): ValidationErrors | null {
    const isEmpty = isArray(control.value) || isString(control.value) ? control.value.length === 0 : isNull(control.value) ? true : false;
    return isEmpty ? { required: true } : null;
  }

  static email(control: AbstractControl): ValidationErrors | null {
    if (
      isString(control.value) &&
      !/^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(
        control.value
      )
    ) {
      return { email: true };
    }

    return null;
  }

  static minLength(minLength: number): ValidatorFn {
    return (control) => {
      if ((isArray(control.value) || isString(control.value)) && control.value.length < minLength) {
        return { minLength: { minLength, actual: control.value.length } };
      }

      return null;
    };
  }

  static maxLength(maxLength: number): ValidatorFn {
    return (control) => {
      if ((isArray(control.value) || isString(control.value)) && control.value.length > maxLength) {
        return { maxLength: { maxLength, actual: control.value.length } };
      }

      return null;
    };
  }

  static pattern(pattern: string | RegExp): ValidatorFn {
    if (!pattern) return () => null;
    let regex: RegExp;
    let regexStr: string;
    if (isString(pattern)) {
      regexStr = '';

      if (pattern.charAt(0) !== '^') regexStr += '^';

      regexStr += pattern;

      if (pattern.charAt(pattern.length - 1) !== '$') regexStr += '$';

      regex = new RegExp(regexStr);
    } else {
      regexStr = pattern.toString();
      regex = pattern;
    }

    return (control) => {
      if ((isString(control.value) || isNumber(control.value)) && !regex.test(control.value.toString())) {
        return { pattern: { pattern: regexStr, actual: control.value } };
      }

      return null;
    };
  }
}