type ArrayType<TEachValue> = Array<TEachValue> | ReadonlyArray<TEachValue> | Readonly<Array<TEachValue>>;

type ValueValidationResult<TValue> = (TValue extends ArrayType<infer TEachValue> ? Array<ValueValidationResult<TEachValue>> | string | null : TValue extends object ? {
    [propertyName in keyof TValue]?: ValueValidationResult<TValue[propertyName]>;
} | string | null : string | null) | string | null;

type ValidationErrors<TModel> = {
    [propertyName in keyof TModel]?: ValueValidationResult<TModel[propertyName]>;
};

type AppliesTo = 'AppliesToAllValidators' | 'AppliesToCurrentValidator';

type MessageGenerator<TModel, TTransformedValue> = (value: TTransformedValue, model: TModel) => string;
type Message<TModel, TTransformedValue> = string | MessageGenerator<TModel, TTransformedValue>;

type SimplePredicate<TModel, TTransformedValue> = (value: TTransformedValue, model: TModel) => boolean;
type SimpleAsyncPredicate<TModel, TTransformedValue> = (value: TTransformedValue, model: TModel) => Promise<boolean>;
type SimplePredicateWithMessage<TModel, TTransformedValue> = {
    predicate: SimplePredicate<TModel, TTransformedValue>;
    message: Message<TModel, TTransformedValue>;
};
type SimpleAsyncPredicateWithMessage<TModel, TTransformedValue> = {
    predicate: SimpleAsyncPredicate<TModel, TTransformedValue>;
    message: Message<TModel, TTransformedValue>;
};
type Predicate<TModel, TTransformedValue> = SimplePredicate<TModel, TTransformedValue> | SimplePredicateWithMessage<TModel, TTransformedValue> | Array<SimplePredicate<TModel, TTransformedValue> | SimplePredicateWithMessage<TModel, TTransformedValue>>;
type AsyncPredicate<TModel, TTransformedValue> = SimpleAsyncPredicate<TModel, TTransformedValue> | SimpleAsyncPredicateWithMessage<TModel, TTransformedValue> | Array<SimpleAsyncPredicate<TModel, TTransformedValue> | SimpleAsyncPredicateWithMessage<TModel, TTransformedValue>>;

interface IValidator<TModel> {
    validate: (model: TModel) => ValidationErrors<TModel>;
}

type IfString<TValue, TOut> = TValue extends string ? TOut : never;
type IfNumber<TValue, TOut> = TValue extends number ? TOut : never;
type IfObject<TValue, TOut> = TValue extends object ? TOut : never;

type NotNullRuleOptions = {
    includeUndefined?: boolean;
};

type NullRuleOptions = {
    includeUndefined: boolean;
};

type RuleValidators<TModel, TValue> = {
    notEqual: (forbiddenValue: TValue) => RuleValidatorsAndExtensions<TModel, TValue>;
    equal: (requiredValue: TValue) => RuleValidatorsAndExtensions<TModel, TValue>;
    must: (predicate: Predicate<TModel, TValue>) => RuleValidatorsAndExtensions<TModel, TValue>;
    notNull: (options?: NotNullRuleOptions) => RuleValidatorsAndExtensions<TModel, TValue>;
    notUndefined: () => RuleValidatorsAndExtensions<TModel, TValue>;
    null: (ruleOptions?: NullRuleOptions) => RuleValidatorsAndExtensions<TModel, TValue>;
    undefined: () => RuleValidatorsAndExtensions<TModel, TValue>;
    notEmpty: IfString<TValue, () => RuleValidatorsAndExtensions<TModel, TValue>>;
    length: IfString<TValue, (minLength: number, maxLength: number) => RuleValidatorsAndExtensions<TModel, TValue>>;
    maxLength: IfString<TValue, (maxLength: number) => RuleValidatorsAndExtensions<TModel, TValue>>;
    minLength: IfString<TValue, (minLength: number) => RuleValidatorsAndExtensions<TModel, TValue>>;
    matches: IfString<TValue, (pattern: RegExp) => RuleValidatorsAndExtensions<TModel, TValue>>;
    emailAddress: IfString<TValue, () => RuleValidatorsAndExtensions<TModel, TValue>>;
    lessThan: IfNumber<TValue, (threshold: number) => RuleValidatorsAndExtensions<TModel, TValue>>;
    lessThanOrEqualTo: IfNumber<TValue, (threshold: number) => RuleValidatorsAndExtensions<TModel, TValue>>;
    greaterThan: IfNumber<TValue, (threshold: number) => RuleValidatorsAndExtensions<TModel, TValue>>;
    greaterThanOrEqualTo: IfNumber<TValue, (threshold: number) => RuleValidatorsAndExtensions<TModel, TValue>>;
    exclusiveBetween: IfNumber<TValue, (lowerBound: number, upperBound: number) => RuleValidatorsAndExtensions<TModel, TValue>>;
    inclusiveBetween: IfNumber<TValue, (lowerBound: number, upperBound: number) => RuleValidatorsAndExtensions<TModel, TValue>>;
    precisionScale: IfNumber<TValue, (precision: number, scale: number) => RuleValidatorsAndExtensions<TModel, TValue>>;
    setValidator: IfObject<TValue, (validatorProducer: (model: TModel) => IValidator<NonNullable<TValue>>) => RuleValidatorsAndExtensions<TModel, TValue>>;
};
type WhenCondition<TModel, TValue> = (condition: (model: TModel) => boolean, appliesTo?: AppliesTo) => RuleValidators<TModel, TValue>;
type UnlessCondition<TModel, TValue> = (condition: (model: TModel) => boolean, appliesTo?: AppliesTo) => RuleValidators<TModel, TValue>;
type RuleValidatorsAndConditionExtensions<TModel, TValue> = RuleValidators<TModel, TValue> & {
    when: WhenCondition<TModel, TValue>;
    unless: UnlessCondition<TModel, TValue>;
};
type WithMessage<TModel, TValue> = (message: string) => RuleValidatorsAndConditionExtensions<TModel, TValue>;
type RuleValidatorsAndExtensions<TModel, TValue> = RuleValidatorsAndConditionExtensions<TModel, TValue> & {
    withMessage: WithMessage<TModel, TValue>;
};

/**
 * Constrain
 * @desc Constrains type `T` to only those properties that exist in type `U`
 */
type Constrain<T, U> = {
    [key in keyof T]: key extends keyof U ? T[key] : never;
};

type FlatType = string | number | boolean | symbol;

type TransformedValue<TValue> = TValue | FlatType | null | undefined;

type Optional<T> = T | null | undefined;

type IfNotNeverThen<TValue, TOut> = TValue extends never ? never : TOut;

declare class SyncValidator<TModel> {
    private valueValidatorBuildersByPropertyName;
    protected _validate: (value: TModel) => ValidationErrors<TModel>;
    validate: (value: TModel) => ValidationErrors<TModel>;
    private rebuildValidate;
    protected ruleFor: <TPropertyName extends keyof TModel, TValue extends TModel[TPropertyName]>(propertyName: TPropertyName) => RuleValidators<TModel, TValue>;
    protected ruleForTransformed: <TPropertyName extends keyof TModel, TValue extends TModel[TPropertyName], TTransformedValue extends TransformedValue<TValue>>(propertyName: TPropertyName, transformValue: (value: TValue) => TTransformedValue extends object ? Constrain<TTransformedValue, TValue> : TTransformedValue) => RuleValidators<TModel, TTransformedValue>;
    protected ruleForEach: <TPropertyName extends keyof TModel, TEachValue extends TModel[TPropertyName] extends Optional<ArrayType<infer TEachValueInferred>> ? TEachValueInferred : never, TValue extends TModel[TPropertyName] & ArrayType<TEachValue>>(propertyName: IfNotNeverThen<TEachValue, TPropertyName>) => IfNotNeverThen<TEachValue, RuleValidators<TModel, TEachValue>>;
    protected ruleForEachTransformed: <TPropertyName extends keyof TModel, TEachValue extends TModel[TPropertyName] extends Optional<ArrayType<infer TEachValueInferred>> ? TEachValueInferred : never, TValue extends TModel[TPropertyName] & ArrayType<TEachValue>, TEachTransformedValue extends TransformedValue<TEachValue>>(propertyName: IfNotNeverThen<TEachValue, TPropertyName>, transformValue: (value: TEachValue) => TEachTransformedValue extends object ? Constrain<TEachTransformedValue, TEachValue> : TEachTransformedValue) => IfNotNeverThen<TEachValue, RuleValidators<TModel, TEachTransformedValue>>;
}

interface IAsyncValidator<TModel> {
    validateAsync: (model: TModel) => Promise<ValidationErrors<TModel>>;
}

type AsyncRuleValidators<TModel, TValue> = {
    notEqual: (forbiddenValue: TValue) => AsyncRuleValidatorsAndExtensions<TModel, TValue>;
    equal: (requiredValue: TValue) => AsyncRuleValidatorsAndExtensions<TModel, TValue>;
    must: (predicate: Predicate<TModel, TValue>) => AsyncRuleValidatorsAndExtensions<TModel, TValue>;
    mustAsync: (predicate: AsyncPredicate<TModel, TValue>) => AsyncRuleValidatorsAndExtensions<TModel, TValue>;
    notNull: (ruleOptions?: NotNullRuleOptions) => AsyncRuleValidatorsAndExtensions<TModel, TValue>;
    notUndefined: () => AsyncRuleValidatorsAndExtensions<TModel, TValue>;
    null: (ruleOptions?: NullRuleOptions) => AsyncRuleValidatorsAndExtensions<TModel, TValue>;
    undefined: () => AsyncRuleValidatorsAndExtensions<TModel, TValue>;
    notEmpty: IfString<TValue, () => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    length: IfString<TValue, (minLength: number, maxLength: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    maxLength: IfString<TValue, (maxLength: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    minLength: IfString<TValue, (minLength: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    matches: IfString<TValue, (pattern: RegExp) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    emailAddress: IfString<TValue, () => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    lessThan: IfNumber<TValue, (threshold: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    lessThanOrEqualTo: IfNumber<TValue, (threshold: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    greaterThan: IfNumber<TValue, (threshold: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    greaterThanOrEqualTo: IfNumber<TValue, (threshold: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    exclusiveBetween: IfNumber<TValue, (lowerBound: number, upperBound: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    inclusiveBetween: IfNumber<TValue, (lowerBound: number, upperBound: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    precisionScale: IfNumber<TValue, (precision: number, scale: number) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    setValidator: IfObject<TValue, (validatorProducer: (model: TModel) => IValidator<NonNullable<TValue>>) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
    setAsyncValidator: IfObject<TValue, (validatorProducer: (model: TModel) => IAsyncValidator<NonNullable<TValue>>) => AsyncRuleValidatorsAndExtensions<TModel, TValue>>;
};
type AsyncWhenCondition<TModel, TValue> = (condition: (model: TModel) => boolean, appliesTo?: AppliesTo) => AsyncRuleValidators<TModel, TValue>;
type AsyncUnlessCondition<TModel, TValue> = (condition: (model: TModel) => boolean, appliesTo?: AppliesTo) => AsyncRuleValidators<TModel, TValue>;
type AsyncRuleValidatorsAndConditionExtensions<TModel, TValue> = AsyncRuleValidators<TModel, TValue> & {
    when: AsyncWhenCondition<TModel, TValue>;
    unless: AsyncUnlessCondition<TModel, TValue>;
};
type AsyncWithMessage<TModel, TValue> = (message: string) => AsyncRuleValidatorsAndConditionExtensions<TModel, TValue>;
type AsyncRuleValidatorsAndExtensions<TModel, TValue> = AsyncRuleValidatorsAndConditionExtensions<TModel, TValue> & {
    withMessage: AsyncWithMessage<TModel, TValue>;
};

declare class AsyncValidator<TModel> {
    private asyncValueValidatorBuildersByPropertyName;
    protected _validateAsync: (value: TModel) => Promise<ValidationErrors<TModel>>;
    validateAsync: (value: TModel) => Promise<ValidationErrors<TModel>>;
    private rebuildValidateAsync;
    protected ruleFor: <TPropertyName extends keyof TModel, TValue extends TModel[TPropertyName]>(propertyName: TPropertyName) => AsyncRuleValidators<TModel, TValue>;
    protected ruleForTransformed: <TPropertyName extends keyof TModel, TValue extends TModel[TPropertyName], TTransformedValue extends TransformedValue<TValue>>(propertyName: TPropertyName, transformValue: (value: TValue) => TTransformedValue extends object ? Constrain<TTransformedValue, TValue> : TTransformedValue) => AsyncRuleValidators<TModel, TTransformedValue>;
    protected ruleForEach: <TPropertyName extends keyof TModel, TEachValue extends TModel[TPropertyName] extends Optional<ArrayType<infer TEachValueInferred>> ? TEachValueInferred : never, TValue extends TModel[TPropertyName] & ArrayType<TEachValue>>(propertyName: IfNotNeverThen<TEachValue, TPropertyName>) => IfNotNeverThen<TEachValue, AsyncRuleValidators<TModel, TEachValue>>;
    protected ruleForEachTransformed: <TPropertyName extends keyof TModel, TEachValue extends TModel[TPropertyName] extends Optional<ArrayType<infer TEachValueInferred>> ? TEachValueInferred : never, TValue extends TModel[TPropertyName] & ArrayType<TEachValue>, TEachTransformedValue extends TransformedValue<TEachValue>>(propertyName: IfNotNeverThen<TEachValue, TPropertyName>, transformValue: (value: TEachValue) => TEachTransformedValue extends object ? Constrain<TEachTransformedValue, TEachValue> : TEachTransformedValue) => IfNotNeverThen<TEachValue, AsyncRuleValidators<TModel, TEachTransformedValue>>;
}

export { type AsyncRuleValidators, AsyncValidator, type RuleValidators, type ValidationErrors, SyncValidator as Validator, type ValueValidationResult };
