<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Scheme_Interface;


/**
 * Class Sanitizer
 *
 * Sanitizes input data based on a schema's rules.
 *
 * @since 1.0.0
 */
class Sanitizer {

	/**
	 * The schema class implementing Scheme_Interface that provides sanitization rules.
	 *
	 * @var Scheme_Interface
	 * @since 1.0.0
	 */
	private Scheme_Interface $schema_class;

	/**
	 * Holds the sanitized data after calling sanitize().
	 *
	 * @var array
	 * @since 1.0.0
	 */
	protected array $sanitized;

	/**
	 * Constructor.
	 *
	 * @param Scheme_Interface $schema_class Schema class providing sanitization rules.
	 * @since 1.0.0
	 */
	public function __construct( Scheme_Interface $schema_class ) {
		$this->schema_class = $schema_class;
	}

	/**
	 * Sanitizes input data based on schema rules.
	 *
	 * Supports partial sanitization via $only parameter, and different contexts such as 'create'.
	 *
	 * @param array       $data    Input data to sanitize.
	 * @param string      $context Context of sanitization, e.g. 'create' or 'update'.
	 * @param bool|null   $preview Optional flag for preview mode affecting rules.
	 * @param array|null  $only    Optional list of keys to sanitize exclusively.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function sanitize( array $data, string $context = 'create', ?bool $preview = false, ?array $only = [] ): void {
		$this->sanitized = [];
		$rules           = ! empty( $only ) ? array_filter( $this->schema_class::rules( $preview ), function ( $key ) use ( $only ) {
			return in_array( $key, $only );
		}, ARRAY_FILTER_USE_KEY ) : $this->schema_class::rules( $preview );

		foreach ( $rules as $key => $rule ) {
			if ( array_key_exists( $key, $data ) ) {
				$value = $data[ $key ];
				if ( isset( $rule['sanitize_callback'] ) && is_callable( $rule['sanitize_callback'] ) ) {
					$value = call_user_func( $rule['sanitize_callback'], $value );
				} elseif ( isset( $rule['type'] ) ) {
					$value = $this->sanitize_by_type( $value, $rule['type'] );
				}
				$this->sanitized[ $key ] = $value;
			} elseif ( $context === 'create' && array_key_exists( 'default', $rule ) ) {
				$this->sanitized[ $key ] = $rule['default'];
			}
		}
	}

	/**
	 * Sanitize a single value by its expected type.
	 *
	 * @param mixed  $value Value to sanitize.
	 * @param string $type  Expected type (string, int, float, bool, array).
	 *
	 * @return mixed Sanitized value.
	 * @since 1.0.0
	 */
	private function sanitize_by_type( $value, string $type ) {
		switch ( $type ) {
			case 'string':
				return sanitize_text_field( $value );
			case 'int':
			case 'integer':
				return (int) $value;
			case 'float':
				return (float) $value;
			case 'bool':
			case 'boolean':
				return (bool) $value;
			case 'array':
				return is_array( $value ) ? array_map( 'sanitize_text_field', $value ) : [];
			default:
				return $value;
		}
	}

	/**
	 * Returns the sanitized data.
	 *
	 * @return array Sanitized key-value pairs.
	 * @since 1.0.0
	 */
	public function get_sanitized(): array {
		return $this->sanitized;
	}
}