import { ValidationFunction } from './ValidationFunction';

export const required = (value: any) =>
  Array.isArray(value)
    ? value.length > 0
      ? undefined
      : 'Required'
    : value || value === false || value === 0
    ? undefined
    : 'Required';

/** Asserts that the value is a certain number of characters. numbers are coerced to a string */
export const length =
  (len: number) => (value: string | number | undefined | null) => {
    const hasError = getLengthOfValue(value) !== len;
    return !hasError ? undefined : `Length must be ${len}`;
  };

export const integer = (val: string | number | undefined | null) => {
  if (!val) {
    return undefined;
  }
  if (!Number.isInteger(typeof val === 'number' ? val : parseFloat(val))) {
    return 'Must be a whole number';
  }
};

export const maxLength =
  (len: number) => (value: string | number | undefined | null) => {
    const hasError = getLengthOfValue(value) > len;
    return !hasError ? undefined : `Maximum length ${len} exceeded`;
  };

export const maxCount =
  (count: number) => (value: { isDeleted?: boolean }[] | undefined | null) => {
    const hasError =
      !!value && value.filter((x) => !x.isDeleted).length > count;
    return !hasError ? undefined : `Should not have more than ${count}`;
  };

export const minCount =
  (count: number) => (value: { isDeleted?: boolean }[] | undefined | null) => {
    const hasError = !value || value.filter((x) => !x.isDeleted).length < count;
    return !hasError ? undefined : `Should have at least ${count}`;
  };

export const maxValue = (max: number) => (value: number | undefined | null) => {
  const hasError = !!value && value > max;
  return !hasError ? undefined : `Maximum value ${max} exceeded`;
};

export const minValue = (min: number) => (value: number | undefined | null) => {
  const hasError = !value || value < min;
  return !hasError ? undefined : `Minimum value ${min} not met`;
};

/** Validate for a ZIP Code. Accepts formats: ##### and #####-#### */
export const zipCode = (value: string | undefined | null) => {
  return value && !/^[0-9]{5}(?:-[0-9]{4})?$/.test(value)
    ? 'Invalid ZIP Code'
    : undefined;
};

/** One of the validators provided must be true. */
export const any =
  <TValue>(validatorList: ValidationFunction<TValue>[]) =>
  (value: TValue) => {
    if (validatorList.length === 0 || !value) {
      return;
    }

    return validatorList.reduce(
      (isAnyTrue, x) => (isAnyTrue ? isAnyTrue : x(value)),
      undefined as string | undefined
    );
  };

function getLengthOfValue(value: string | number | undefined | null) {
  if (value === null) {
    return 0;
  }
  switch (typeof value) {
    case 'undefined':
      return 0;
    case 'string':
      return value.length;
    case 'number':
      // TODO: this seems iffy to me - what if the actual visible value is a fixed length or some special format (e.g. with commas?)
      return value.toString().length;
    default:
      assertUnreachable(value);
      return 0;
  }
}
function assertUnreachable(x: never) {
  return null;
}
