<?php
/**
 * Abstract FormValidator.
 *
 * Provides base mechanism for validating form data using defined rules and types.
 *
 * @package Ilabs\Firevert\Core\Form\Validation
 */

declare( strict_types=1 );

namespace Ilabs\Firevert\Core\Form\Validation;

/**
 * Abstract class FormValidator
 */
abstract class FormValidator implements FormValidatorInterface {

	/**
	 * Validation errors.
	 *
	 * @var array<string, string[]>
	 */
	protected array $errors = array();

	/**
	 * Field definitions (name => type).
	 *
	 * @var array<string, string>
	 */
	protected array $field_types = array();

	/**
	 * Custom validation rules per field.
	 *
	 * @var array<string, callable[]>
	 */
	protected array $custom_rules = array();

	/**
	 * Sets the field types (from FormHandler).
	 *
	 * @param array<string, string> $types Field definitions.
	 *
	 * @return void
	 */
	public function set_field_types( array $types ): void {
		$this->field_types = $types;
	}

	/**
	 * Sets custom validation rules per field.
	 *
	 * @param array<string, callable[]> $rules Custom rules.
	 *
	 * @return void
	 */
	public function set_custom_rules( array $rules ): void {
		$this->custom_rules = $rules;
	}

	/**
	 * Returns validation rules based on field types and custom logic.
	 *
	 * @return array<string, callable[]>
	 */
	protected function rules(): array {
		$rules      = array();
		$type_rules = $this->type_validators();

		foreach ( $this->field_types as $field => $type ) {
			$base            = $type_rules[ $type ] ?? array();
			$custom          = $this->custom_rules[ $field ] ?? array();
			$rules[ $field ] = array_merge( $base, $custom );
		}

		return $rules;
	}

	/**
	 * Default type validators.
	 *
	 * @return array<string, callable[]>
	 */
	protected function type_validators(): array {
		return array(
			'text'  => array(
				function ( $value ) {
					if ( is_null( $value ) || is_scalar( $value ) ) {
						return null;
					}
					return __( 'Invalid value. Text expected.', 'firevert' );
				},
			),
			'url'   => array(
				function ( $value ) {
					if ( empty( $value ) ) {
						return null;
					}
					if ( filter_var( $value, FILTER_VALIDATE_URL ) ) {
						return null;
					}
					return __( 'Invalid URL.', 'firevert' );
				},
			),
			'int'   => array(
				function ( $value ) {
					if ( is_null( $value ) || '' === $value ) {
						return null;
					}
					return is_numeric( $value )
						? null
						: __( 'Invalid number.', 'firevert' );
				},
			),
			'bool'  => array(
				function ( $value ) {
					if ( is_null( $value ) ) {
						return null;
					}
					if ( is_bool( $value ) || in_array( $value, array( '0', '1', 0, 1 ), true ) ) {
						return null;
					}
					return __( 'Invalid boolean value.', 'firevert' );
				},
			),
			'array' => array(
				function ( $value ) {
					if ( is_null( $value ) || '' === $value ) {
						return null;
					}
					return is_array( $value )
						? null
						: __( 'Invalid array value.', 'firevert' );
				},
			),
		);
	}


	/**
	 * Validates the data using generated rules.
	 *
	 * @param array<string, mixed> $data Submitted form data.
	 * @return ValidationResult
	 */
	public function validate( array $data ): ValidationResult {
		$this->errors = array();

		foreach ( $this->rules() as $field => $callbacks ) {
			$value = $data[ $field ] ?? null;

			foreach ( $callbacks as $callback ) {
				$error = $callback( $value, $data );
				if ( is_string( $error ) ) {
					$this->errors[ $field ][] = $error;
				}
			}
		}

		return empty( $this->errors )
			? ValidationResult::success()
			: ValidationResult::failure( $this->errors );
	}
}
