/// <summary>
        /// Selects all elements of a `IEnumerable` property for validation.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <typeparam name="TItem">The type of an individual item in the `IEnumerable` property.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="selector">
        ///     The property selector expression.
        ///     <para>
        ///         The expression must be a `MemberExpression` (e.g. `x => x.Property`) or a `ParameterExpression`
        ///         to select all elements of the current value (e.g. `x => x`).
        ///     </para>
        /// </param>
        /// <param name="action">The action to perform for each item of the selected `IEnumerable` property.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> ForEach <T, TItem>(this IValidatorContext <T> validator,
                                                               Expression <Func <T, IEnumerable <TItem> > > selector, Action <IValidatorContext <TItem> > action)
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));
            ValidationInternals.ValidateNotNull(selector, nameof(selector));
            ValidationInternals.ValidateNotNull(action, nameof(action));

            var name = (selector.Body is ParameterExpression)
                ? null
                : ValidationInternals.GetPropertyName(selector);
            var value = selector.Compile().Invoke(validator.Value);

            if (value is null)
            {
                return(validator);
            }

            int i = 0;

            foreach (var item in value)
            {
                var context = (name is null)
                    ? new ValidatorContext <TItem>(item, $"{ValidationInternals.FormatName(validator.Path, null)}[{i++}]")
                    : new ValidatorContext <TItem>(item, validator.Path, $"{name}[{i++}]");
                action.Invoke(context);
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is `null` or empty (contains the default value of the current type).
        /// <para>
        ///     This validation as well applies to empty strings or `IEnumerable` types without at least one element.
        /// </para>
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> NotNullOrEmpty <T>(this IValidatorContext <T> validator)
            where T : class
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            try
            {
                validator.NotNull();
            }
            catch (ArgumentNullException)
            {
                throw new ArgumentNullException(validator.Path.First(), string.Format(CultureInfo.InvariantCulture,
                                                                                      Resources.ArgumentMustNotBeNullOrEmpty, ValidationInternals.FormatName(validator.Path, null)));
            }

            var isEmptyEnum   = (validator.Value is IEnumerable enumerable) && !enumerable.GetEnumerator().MoveNext();
            var isEmptyString = validator.Value is string { Length : 0 };

            if (isEmptyEnum || isEmptyString)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.ArgumentMustNotBeNullOrEmpty, ValidationInternals.FormatName(validator.Path, null)),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is equal to the blacklisted value.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="value">The blacklisted value.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> NotEqual <T>(this IValidatorContext <T> validator, T value)
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (EqualityComparer <T> .Default.Equals(validator.Value, value))
            {
                throw new ArgumentNullException(validator.Path.First(), string.Format(CultureInfo.InvariantCulture,
                                                                                      Resources.ArgumentMustNotBeValue, ValidationInternals.FormatName(validator.Path, null), value));
            }

            return(validator);
        }
        /// <inheritdoc cref="NotNull{T}(ZySharp.Validation.IValidatorContext{T})"/>
        public static IValidatorContext <T?> NotNull <T>(this IValidatorContext <T?> validator)
            where T : struct
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (!validator.Value.HasValue)
            {
                throw new ArgumentNullException(validator.Path.First(), string.Format(CultureInfo.InvariantCulture,
                                                                                      Resources.ArgumentMustNotBeNull, ValidationInternals.FormatName(validator.Path, null)));
            }

            return(validator);
        }
        /// <summary>
        /// Performs validations after projecting the current value to a new type.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <typeparam name="TNew">The type of the projected value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="selector">The selector lambda.</param>
        /// <param name="action">The action to perform with the projected value.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> Select <T, TNew>(this IValidatorContext <T> validator, Func <T, TNew> selector,
                                                             Action <IValidatorContext <TNew> > action)
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));
            ValidationInternals.ValidateNotNull(selector, nameof(selector));
            ValidationInternals.ValidateNotNull(action, nameof(action));

            var context = new ValidatorContext <TNew>(selector.Invoke(validator.Value), validator.Path, null);

            action.Invoke(context);

            return(validator);
        }
        /// <summary>
        /// Throws if the uri does not contain an absolute uri.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> Absolute <T>(this IValidatorContext <T> validator)
            where T : Uri
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (!validator.Value.IsAbsoluteUri)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.UriMustBeAbsolute, ValidationInternals.FormatName(validator.Path, null)),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the stream is not seekable.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> Seekable <T>(this IValidatorContext <T> validator)
            where T : Stream
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (!validator.Value.CanSeek)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.StreamMustBeSeekable, ValidationInternals.FormatName(validator.Path, null)),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is not less than or equal to the given threshold.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="threshold">The threshold value.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> LessThanOrEqualTo <T>(this IValidatorContext <T> validator, T threshold)
            where T : struct, IComparable <T>
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (validator.Value.CompareTo(threshold) > 0)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.ArgumentMustBeGreaterThan, ValidationInternals.FormatName(validator.Path, null), threshold),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is empty (contains the default value of the current type).
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> NotEmpty <T>(this IValidatorContext <T> validator)
            where T : struct
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            if (validator.Value.Equals(default(T)))
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.ArgumentMustNotBeEmpty, ValidationInternals.FormatName(validator.Path, null)),
                                            validator.Path.First());
            }

            return(validator);
        }
        /// <summary>
        /// Performs validations if a user-defined condition is met.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="predicate">The filter predicate.</param>
        /// <param name="action">The action to perform if the condition is met.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> When <T>(this IValidatorContext <T> validator, Func <T, bool> predicate,
                                                     Action <IValidatorContext <T> > action)
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));
            ValidationInternals.ValidateNotNull(predicate, nameof(predicate));
            ValidationInternals.ValidateNotNull(action, nameof(action));

            if (predicate(validator.Value))
            {
                action.Invoke(validator);
            }

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is equal to one of the blacklisted values.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="values">The blacklisted values.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> NotEqual <T>(this IValidatorContext <T> validator, params T[] values)
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            var isValidValue = values.All(x => !EqualityComparer <T> .Default.Equals(validator.Value, x));

            if (!isValidValue)
            {
                throw new ArgumentNullException(validator.Path.First(), string.Format(CultureInfo.InvariantCulture,
                                                                                      Resources.ArgumentMustNotBeOneOf, ValidationInternals.FormatName(validator.Path, null),
                                                                                      string.Join(", ", values.Select(x => $"'{x}'").ToList())));
            }

            return(validator);
        }
        /// <summary>
        /// Selects a property for validation.
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <typeparam name="TNext">The type of the selected property.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="selector">
        ///     The property selector expression.
        ///     <para>
        ///         The expression must be a `MemberExpression` (e.g. `x => x.Property`).
        ///     </para>
        /// </param>
        /// <param name="action">The action to perform for the selected property.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> For <T, TNext>(this IValidatorContext <T> validator,
                                                           Expression <Func <T, TNext> > selector, Action <IValidatorContext <TNext> > action)
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));
            ValidationInternals.ValidateNotNull(selector, nameof(selector));
            ValidationInternals.ValidateNotNull(action, nameof(action));

            var name  = ValidationInternals.GetPropertyName(selector);
            var value = selector.Compile().Invoke(validator.Value);

            var context = new ValidatorContext <TNext>(value, validator.Path, name);

            action.Invoke(context);

            return(validator);
        }
        /// <summary>
        /// Throws if the current value is not in the given range.
        /// <para>
        ///     This range check is "inclusive" which means that the lower- and upper- limits are both valid values.
        /// </para>
        /// </summary>
        /// <typeparam name="T">The type of the current value.</typeparam>
        /// <param name="validator">The current validator context.</param>
        /// <param name="min">The minimum allowed value.</param>
        /// <param name="max">The maximum allowed value.</param>
        /// <returns>The unmodified validator context.</returns>
        public static IValidatorContext <T> InRange <T>(this IValidatorContext <T> validator, T min, T max)
            where T : struct, IComparable <T>
        {
            ValidationInternals.ValidateNotNull(validator, nameof(validator));

            try
            {
                validator.GreaterThanOrEqualTo(min);
                validator.LessThanOrEqualTo(max);
            }
            catch (ArgumentException e)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                          Resources.ArgumentMustBeInRange, ValidationInternals.FormatName(validator.Path, null), min, max),
                                            validator.Path.First(), e);
            }

            return(validator);
        }
        /// <summary>
        /// Selects an argument for validation.
        /// </summary>
        /// <typeparam name="T">The type of the argument.</typeparam>
        /// <param name="value">The value of the argument to validate.</param>
        /// <param name="name">The name of the argument to validate.</param>
        /// <returns>A validator context for the given argument.</returns>
        public static IValidatorContext <T> For <T>([ValidatedNotNull][NoEnumeration] T value, string name)
        {
            ValidationInternals.ValidateNotNull(name, nameof(name));

            return(new ValidatorContext <T>(value, name));
        }