<?php

namespace SugarCalendarBookings\Support\DataProcessing;

use SugarCalendarBookings\Support\Helpers\Data;

/**
 * Class Normalizer.
 *
 * @since 1.0.0
 */
class Normalizer {

	use RulesTrait;

	/**
	 * Map of rule names to normalization methods.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private static $rule_map = [
		'trim'      => 'trim',
		'lowercase' => 'lowercase',
	];

	/**
	 * Trim whitespace.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $value Value to trim.
	 *
	 * @return mixed
	 */
	private static function trim( $value ) {

		return is_string( $value ) ? trim( $value ) : '';
	}

	/**
	 * Convert to lowercase.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $value Value to convert.
	 *
	 * @return mixed
	 */
	private static function lowercase( $value ) {

		return is_string( $value ) ? strtolower( $value ) : '';
	}

	/**
	 * Normalize data according to rules.
	 *
	 * @since 1.0.0
	 *
	 * @param array $data  The data to normalize.
	 * @param array $rules The normalization rules.
	 *
	 * @return array Normalized data.
	 */
	public static function normalize( $data, $rules ) {

		return self::process_rules( $data, $rules );
	}

	/**
	 * Normalize a single field (dot notation or flat).
	 *
	 * @since 1.0.0
	 *
	 * @param array  $normalized  The current normalized array.
	 * @param array  $data        The original data array.
	 * @param string $field       The field name.
	 * @param array  $field_rules The normalization rules for the field.
	 */
	private static function normalize_field( &$normalized, $data, $field, $field_rules ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$value = Data::get( $data, $field, '__not_exists__' );

		if ( $value === '__not_exists__' && in_array( 'form_boolean', $field_rules, true ) ) {
			Data::set( $normalized, $field, false );

			return;
		}

		// If field from rules does not exist in data, skip.
		if ( $value === '__not_exists__' ) {
			return;
		}

		// If nullable and value is null or empty string, set to null and return.
		if (
			in_array( 'nullable', $field_rules, true ) &&
			( is_null( $value ) || $value === '' )
		) {
			Data::set( $normalized, $field, null );

			return;
		}

		foreach ( $field_rules as $rule ) {
			$method = self::$rule_map[ $rule ] ?? null;

			if ( $method ) {
				$value = self::$method( $value );

				Data::set( $normalized, $field, $value );
			}
		}
	}

	/**
	 * Process rules.
	 *
	 * @since 1.0.0
	 *
	 * @param array $data          The data to normalize.
	 * @param array $rules         The normalization rules.
	 * @param bool  $preserve_data Whether to preserve all input data.
	 *
	 * @return array Normalized data.
	 */
	private static function process_rules( $data, $rules, $preserve_data = true ) {

		$normalized = $preserve_data ? $data : [];

		foreach ( $rules as $field => $field_rules ) {
			$field_rules = self::parse_rules( $field_rules );

			if ( self::is_wildcard( $field ) ) {
				self::process_wildcard_rule( $normalized, $data, $field, $field_rules );
				continue;
			}

			self::normalize_field( $normalized, $data, $field, $field_rules );
		}

		return $normalized;
	}

	/**
	 * Process wildcard rule.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $normalized  The current normalized array.
	 * @param array  $data        The original data array.
	 * @param string $field       The field name (with wildcard).
	 * @param array  $field_rules The normalization rules for the field.
	 */
	private static function process_wildcard_rule( &$normalized, $data, $field, $field_rules ) {

		$segments       = explode( '.', $field );
		$wildcard_index = array_search( '*', $segments, true );

		// Build prefix and suffix.
		$prefix = array_slice( $segments, 0, $wildcard_index );
		$suffix = array_slice( $segments, $wildcard_index + 1 );

		// Get the array to iterate.
		$array_path       = implode( '.', $prefix );
		$array_to_iterate = Data::get( $data, $array_path, [] );

		if ( is_array( $array_to_iterate ) ) {
			foreach ( $array_to_iterate as $i => $item ) {
				// Build the full key for this item.
				$item_key = implode( '.', array_merge( $prefix, [ $i ], $suffix ) );

				$sub_rules  = [ $item_key => $field_rules ];
				$normalized = array_replace_recursive(
					$normalized,
					self::process_rules( $data, $sub_rules, false )
				);
			}
		}
	}
}
