import * as ValidateJS from 'validate.js';

// HACK(steve): weird typescript situation because of strange typings
const Validate: any = ValidateJS.default ? ValidateJS.default : ValidateJS;

/**
 * Defines the rules for validating.
 *
 * Example:
 * ```ts
 * const RULES = {
 *   favoriteBand: {
 *     inclusion: { ['Weezer', 'Other'], message: 'Pick wisely.' }
 *   },
 *   name: {
 *     presence: { message: 'A developer has no name?' }
 *   }
 * }
 * validate(RULES, {})
 * ```
 *
 * See https://validatejs.org/#validators for more examples.
 *
 */
export interface ValidationRules {
  [key: string]: {};
}

/**
 * An object containing any errors found.
 *
 * Example:
 * ```js
 * {
 *   email: ['Invalid email address.'],
 *   password: [
 *     'Password must be 6 characters.',
 *     'Password must have at least 1 digit.'
 *   ]
 * }
 * ```
 */
export interface ValidationErrors {
  [key: string]: {};
}

/**
 * Validates an array item.
 */
type ArrayOptions = {
  attributes?: string[];
  constraints: ValidationRules;
};

Validate.validators.array = (arrayItems: any[], options: ArrayOptions) => {
  if (!arrayItems) return 'Array item is undefined';
  if (!options.constraints) return 'Item constraints not defined';

  const arrayItemErrors = arrayItems.reduce((errors, item, index) => {
    const error = Validate(item, options.constraints);

    if (error) {
      errors[index] = error;
    }

    if (options.attributes && Validate.isArray(options.attributes) && Validate.isObject(item)) {
      const extraAttributes = Object.keys(item).filter((key) => !options.attributes.includes(key));

      if (extraAttributes.length && !errors[index]) {
        errors[index] = {};
      }

      extraAttributes.forEach(
        (attribute) => (errors[index][attribute] = [`Extra attribute '${attribute}'`]),
      );
    }

    return errors;
  }, {});

  return Validate.isEmpty(arrayItemErrors) ? null : { errors: arrayItemErrors };
};

/**
 * Runs the given rules against the data object.
 *
 * @param data The object to validate.
 * @param rules The rules to apply.
 */
export function validate(data: {}, rules: ValidationRules): ValidationErrors {
  if (typeof data !== 'object') {
    return {} as ValidationErrors;
  }
  return Validate(data, rules, { fullMessages: false });
}
