<?php
/**
 * Validator class.
 *
 * @package DokanKits\Core\Validation
 * @since   3.0.0
 */

namespace DokanKits\Core\Validation;

/**
 * Validator Class
 *
 * @since   3.0.0
 * @package DokanKits\Core\Validation
 */
class Validator {
	/**
	 * Data to validate
	 *
	 * @since 3.0.0
	 * @var array
	 */
	protected array $data = array();

	/**
	 * Validation rules
	 *
	 * @since 3.0.0
	 * @var array
	 */
	protected array $rules = array();

	/**
	 * Validation errors
	 *
	 * @since 3.0.0
	 * @var array
	 */
	protected array $errors = array();

	/**
	 * Constructor
	 *
	 * @since 3.0.0
	 *
	 * @param array $data  Data to validate.
	 * @param array $rules Validation rules.
	 */
	public function __construct( array $data = array(), array $rules = array() ) {
		$this->data  = $data;
		$this->rules = $rules;
	}

	/**
	 * Set data
	 *
	 * @since 3.0.0
	 *
	 * @param array $data Data to validate.
	 *
	 * @return self
	 */
	public function set_data( array $data ): Validator {
		$this->data = $data;

		return $this;
	}

	/**
	 * Set rules
	 *
	 * @since 3.0.0
	 *
	 * @param array $rules Validation rules.
	 *
	 * @return self
	 */
	public function set_rules( array $rules ): Validator {
		$this->rules = $rules;

		return $this;
	}

	/**
	 * Validate data
	 *
	 * @since 3.0.0
	 * @return bool
	 */
	public function validate(): bool {
		$this->errors = array();

		foreach ( $this->rules as $field => $rules_string ) {
			$field_rules = explode( '|', $rules_string );

			foreach ( $field_rules as $rule ) {
				$params = array();

				// Check if rule has parameters.
				if ( strpos( $rule, ':' ) !== false ) {
					[ $rule, $param_string ] = explode( ':', $rule );
					$params                  = explode( ',', $param_string );
				}

				$method = 'validate_' . $rule;

				if ( method_exists( $this, $method ) ) {
					$result = $this->$method( $field, $params );

					if ( true !== $result ) {
						if ( ! isset( $this->errors[ $field ] ) ) {
							$this->errors[ $field ] = array();
						}

						$this->errors[ $field ][] = $result;
					}
				}
			}
		}

		return empty( $this->errors );
	}

	/**
	 * Get validation errors
	 *
	 * @since 3.0.0
	 * @return array
	 */
	public function get_errors(): array {
		/**
		 * Filter the validation errors.
		 *
		 * @since 3.0.0
		 *
		 * @param array $errors Validation errors.
		 */
		return apply_filters( 'dokan_kits_validation_errors', $this->errors );
	}

	/**
	 * Get first error for a field
	 *
	 * @since 3.0.0
	 *
	 * @param string $field Field name.
	 *
	 * @return string|null
	 */
	public function get_first_error( string $field ): ?string {
		return isset( $this->errors[ $field ] ) && ! empty( $this->errors[ $field ] ) ? $this->errors[ $field ][0] : null;
	}

	/**
	 * Validate required field
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 *
	 * @return bool|string
	 */
	protected function validate_required( string $field ) {
		if ( empty( $this->data[ $field ] ) ) {
			// translators: %s is the field name.
			return sprintf( __( 'The %s field is required.', 'dokan-kits' ), $field );
		}

		return true;
	}

	/**
	 * Validate email field
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 *
	 * @return bool|string
	 */
	protected function validate_email( string $field ) {
		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && ! is_email( $this->data[ $field ] ) ) {
			// translators: %s is the field name.
			return sprintf( __( 'The %s field must be a valid email address.', 'dokan-kits' ), $field );
		}

		return true;
	}

	/**
	 * Validate numeric field
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 *
	 * @return bool|string
	 */
	protected function validate_numeric( string $field ) {
		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && ! is_numeric( $this->data[ $field ] ) ) {
			// translators: %s is the field name.
			return sprintf( __( 'The %s field must be numeric.', 'dokan-kits' ), $field );
		}

		return true;
	}

	/**
	 * Validate integer field
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 *
	 * @return bool|string
	 */
	protected function validate_integer( string $field ) {
		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && ! filter_var( $this->data[ $field ], FILTER_VALIDATE_INT ) ) {
			// translators: %s is the field name.
			return sprintf( __( 'The %s field must be an integer.', 'dokan-kits' ), $field );
		}

		return true;
	}

	/**
	 * Validate minimum value
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 * @param array  $params Validation parameters.
	 *
	 * @return bool|string
	 */
	protected function validate_min( string $field, array $params = array() ) {
		if ( ! isset( $params[0] ) ) {
			return true;
		}

		$min = (float) $params[0];

		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && (float) $this->data[ $field ] < $min ) {
			// translators: %1$s is the field name, %2$s is the minimum value.
			return sprintf( __( 'The %1$s field must be at least %2$s.', 'dokan-kits' ), $field, $min );
		}

		return true;
	}

	/**
	 * Validate maximum value
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 * @param array  $params Validation parameters.
	 *
	 * @return bool|string
	 */
	protected function validate_max( string $field, array $params = array() ) {
		if ( ! isset( $params[0] ) ) {
			return true;
		}

		$max = (float) $params[0];

		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && (float) $this->data[ $field ] > $max ) {
			// translators: %1$s is the field name, %2$s is the maximum value.
			return sprintf( __( 'The %1$s field must be at most %2$s.', 'dokan-kits' ), $field, $max );
		}

		return true;
	}

	/**
	 * Validate minimum length
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 * @param array  $params Validation parameters.
	 *
	 * @return bool|string
	 */
	protected function validate_min_length( string $field, array $params = array() ) {
		if ( ! isset( $params[0] ) ) {
			return true;
		}

		$min = (int) $params[0];

		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && mb_strlen( $this->data[ $field ] ) < $min ) {
			// translators: %1$s is the field name, %2$d is the minimum length.
			return sprintf( __( 'The %1$s field must be at least %2$d characters.', 'dokan-kits' ), $field, $min );
		}

		return true;
	}

	/**
	 * Validate maximum length
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 * @param array  $params Validation parameters.
	 *
	 * @return bool|string
	 */
	protected function validate_max_length( string $field, array $params = array() ) {
		if ( ! isset( $params[0] ) ) {
			return true;
		}

		$max = (int) $params[0];

		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && mb_strlen( $this->data[ $field ] ) > $max ) {
			// translators: %1$s is the field name, %2$d is the maximum length.
			return sprintf( __( 'The %1$s field must be at most %2$d characters.', 'dokan-kits' ), $field, $max );
		}

		return true;
	}

	/**
	 * Validate URL field
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 *
	 * @return bool|string
	 */
	protected function validate_url( string $field ) {
		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && ! filter_var( $this->data[ $field ], FILTER_VALIDATE_URL ) ) {
			// translators: %s is the field name.
			return sprintf( __( 'The %s field must be a valid URL.', 'dokan-kits' ), $field );
		}

		return true;
	}

	/**
	 * Validate in list
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 * @param array  $params Validation parameters.
	 *
	 * @return bool|string
	 */
	protected function validate_in( string $field, array $params = array() ) {
		if ( empty( $params ) ) {
			return true;
		}

		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && ! in_array( $this->data[ $field ], $params, true ) ) {
			// translators: %1$s is the field name, %2$s is the list of valid values.
			return sprintf( __( 'The %1$s field must be one of: %2$s.', 'dokan-kits' ), $field, implode( ', ', $params ) );
		}

		return true;
	}

	/**
	 * Validate not in list
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 * @param array  $params Validation parameters.
	 *
	 * @return bool|string
	 */
	protected function validate_not_in( string $field, array $params = array() ) {
		if ( empty( $params ) ) {
			return true;
		}

		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && in_array( $this->data[ $field ], $params, true ) ) {
			// translators: %1$s is the field name, %2$s is the list of invalid values.
			return sprintf( __( 'The %1$s field must not be one of: %2$s.', 'dokan-kits' ), $field, implode( ', ', $params ) );
		}

		return true;
	}

	/**
	 * Validate regex pattern
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 * @param array  $params Validation parameters.
	 *
	 * @return bool|string
	 */
	protected function validate_regex( string $field, array $params = array() ) {
		if ( ! isset( $params[0] ) ) {
			return true;
		}

		$pattern = $params[0];

		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] && ! preg_match( $pattern, $this->data[ $field ] ) ) {
			// translators: %s is the field name.
			return sprintf( __( 'The %s field format is invalid.', 'dokan-kits' ), $field );
		}

		return true;
	}

	/**
	 * Validate date
	 *
	 * @since 3.0.0
	 *
	 * @param string $field  Field name.
	 * @param array  $params Validation parameters.
	 *
	 * @return bool|string
	 */
	protected function validate_date( string $field, array $params = array() ) {
		if ( isset( $this->data[ $field ] ) && '' !== $this->data[ $field ] ) {
			$format = ! empty( $params ) ? $params[0] : 'Y-m-d';
			$date   = \DateTime::createFromFormat( $format, $this->data[ $field ] );

			if ( ! $date || $date->format( $format ) !== $this->data[ $field ] ) {
				// translators: %s is the field name.
				return sprintf( __( 'The %s field must be a valid date.', 'dokan-kits' ), $field );
			}
		}

		return true;
	}
}
