<?php
declare( strict_types=1 );

namespace FiboFilters\Filters;

use FiboFilters\Config\Env;
use FiboFilters\Indexer\SourceQueryFactory;
use FiboFilters\Util\FilterUtil;
use FiboFilters\WooCommerce\Helper as WooCommerceHelper;

/**
 * Class SourceAnalyser
 */
class SourceAnalyser {

	/** @var FilterSources */
	private $filter_sources;

	/** @var SourceQueryFactory */
	private $source_query_factory;

	/** @var WooCommerceHelper */
	private $woocommerce_helper;

	const NUMBER_EXTENDED_REGEX = '/^[^\d]{0,15}\s?(\d+([.,]\d+)?)\s?[^\d]{0,15}$|^(\d+([.,]\d+)?)$/u';

	public function __construct( FilterSources $filter_sources, SourceQueryFactory $source_query_factory, WooCommerceHelper $woocommerce_helper ) {
		$this->filter_sources       = $filter_sources;
		$this->source_query_factory = $source_query_factory;
		$this->woocommerce_helper   = $woocommerce_helper;
	}

	public function analyse_source_value_types(): void {
		$source_analysis_data = get_transient( Env::$plugin_prefix . 'sources_analyse_data' );
		if ( empty( $source_analysis_data ) ) {
			$source_analysis_data = [];
		}

		$source_query = $this->source_query_factory->get( 'filters', [ 'skip_filters' => true ] );

		$total_products         = $this->woocommerce_helper->get_total_products_count() + $this->woocommerce_helper->get_total_product_variations_count();
		$max_sources_to_analyse = $total_products > 5000 ? 5 : 15;
		$max_sources_to_analyse = apply_filters( 'fibofilters/source_analyser/max_sources_to_analyse_per_set', $max_sources_to_analyse, $total_products );
		$save_results           = false;

		foreach ( $this->filter_sources->get_sources() as $source ) {
			if ( $max_sources_to_analyse === 0 ) {
				continue;
			}
			if ( isset( $source_analysis_data[ $source->get_data_attr( 'value' ) ] ) ) {
				continue;
			}

			$save_results = true;
			$data         = [
				'percentageShare'      => null,
				'type'                 => null,
				'typeMeta'             => null,
				'distinctValuesNumber' => null,
				'allValues'            => null,
				'hasMultivalues'       => null,
			];

			if ( $source->is_calculate_percentage_share() ) {
				$data['percentageShare'] = $source->get_calculate_percentage_share();
			}

			if ( $source->is_analyse_source_values() ) {
				$values     = $source_query->get_source_values( $source->get_data() );
				$all_values = $this->split_values( $values );

				$data['hasMultivalues'] = $this->has_multivalues( $values );

				$all_values_without_null = $this->filter_null_values( $all_values );

				if ( ! empty( $all_values_without_null ) ) {
					// Override values. Use only for "number_extended" type!
					$source_value_overrides = apply_filters( "fibofilters/indexer/number_extended_overrides/source={$source->get_data_attr( 'value' )}", [] );
					if ( ! empty( $source_value_overrides ) && is_array( $source_value_overrides ) ) {
						foreach ( $all_values_without_null as $key => $value ) {
							if ( isset( $source_value_overrides[ $value ] ) ) {
								$all_values_without_null[ $key ] = (string) $source_value_overrides[ $value ];
							}
						}
					}

					$data = array_merge( $data, $this->get_values_type( $all_values_without_null, $source->get_data_attr( 'value' ) ) );
				}
				$data['allValues'] = $all_values_without_null;
			}

			$source_analysis_data[ $source->get_data_attr( 'value' ) ] = $data;

			$max_sources_to_analyse--;
		}

		if ( $save_results ) {
			set_transient( Env::$plugin_prefix . 'sources_analyse_data', $source_analysis_data, HOUR_IN_SECONDS );
		}
	}

	private function get_values_type( array $values ): array {
		$result = [];

		$result['distinctValuesNumber'] = count( $values );

		if ( $this->test_bool_two_values( $values ) ) {
			$result['type'] = 'bool_two_values';
		} elseif ( $this->test_bool_one_value( $values ) ) {
			$result['type'] = 'bool_one_value';
		} elseif ( $this->test_number( $values ) ) {
			$result['type'] = 'number';
		} elseif ( $this->test_number_extended( $values ) ) {
			$type_meta = $this->get_number_extended_meta( $values );
			if ( ! empty( $type_meta ) ) {
				$result['type']     = 'number_extended';
				$result['typeMeta'] = $type_meta;
			} else {
				$result['type'] = 'string';
			}
		} else {
			$result['type'] = 'string';
		}

		return $result;
	}

	private function test_bool_two_values( array $values ): bool {
		if ( count( $values ) !== 2 ) {
			return false;
		}

		foreach ( $values as $value ) {
			if (
				FilterUtil::is_filter_true_value( $value ) ||
				FilterUtil::is_filter_false_value( $value )
			) {
				continue;
			}

			return false;
		}

		return true;
	}

	private function test_bool_one_value( array $values ): bool {
		if ( count( $values ) !== 1 ) {
			return false;
		}

		foreach ( $values as $value ) {
			if (
				FilterUtil::is_filter_true_value( $value ) ||
				FilterUtil::is_filter_false_value( $value )
			) {
				continue;
			}

			return false;
		}

		return true;
	}

	private function test_number_extended( array $values ): bool {
		foreach ( $values as $value ) {
			if ( preg_match( self::NUMBER_EXTENDED_REGEX, $value ) ) {
				continue;
			}

			return false;
		}

		return true;
	}

	private function test_number( array $values ): bool {
		foreach ( $values as $value ) {
			if ( is_numeric( $value ) ) {
				continue;
			}

			return false;
		}

		return true;
	}

	private function has_multivalues( array $values ): bool {
		foreach ( $values as $value ) {
			if ( is_null( $value ) ) {
				continue;
			}

			$result = preg_split( '/ \|\| /', (string) $value );

			if ( $result !== false && count( $result ) > 1 ) {
				return true;
			}
		}

		return false;
	}

	private function split_values( array $values ): array {
		/**
		 * Examples:
		 * $values = ['Yes', null]
		 * $values = ['Yes', 'No']
		 * $values = ['Yes', 'No', null]
		 * $values = ['Green || Red || Blue', null]
		 */
		$values = array_merge(
			array_map(
				function ( $item ): array {
					if ( is_null( $item ) ) {
						return [ $item ];
					}

					// Split "Green || Red || Blue" to array.
					$result = preg_split( '/ \|\| /', $item );

					return $result === false ? [] : $result;
				},
				$values
			),
			[]
		);

		$all_values = [];

		foreach ( $values as $values_arr ) {
			$all_values = array_merge( $all_values, array_values( $values_arr ) );
		}

		// Remove '1 ___ ' prefix from values.
		$all_values = array_map(
			function ( $value ) {
				if ( is_string( $value ) && preg_match( '/^\d+ ___ /', $value ) ) {
					return preg_replace( '/^\d+ ___ /', '', $value );
				}

				return $value;
			},
			$all_values
		);

		return array_values( array_unique( $all_values ) );
	}

	private function filter_null_values( array $values ): array {
		return array_values(
			array_filter(
				$values,
				function ( $value ): bool {
					return ! is_null( $value );
				}
			)
		);
	}

	/**
	 * Get meta for extended number type
	 *
	 * @param array $values Values to analyse.
	 */
	private function get_number_extended_meta( array $values = [] ): array {
		$prefixes     = [];
		$suffixes     = [];
		$max_decimals = 0;

		foreach ( $values as $value ) {
			preg_match( self::NUMBER_EXTENDED_REGEX, $value, $matches );
			$number = $matches[1] ?? '';
			if ( $number === '' ) {
				return [];
			}
			$number_normalized = str_replace( ',', '.', $number );
			$parts             = explode( $number, $value );
			if ( sizeof( $parts ) !== 2 ) {
				return [];
			}
			if ( ! in_array( $parts[0], $prefixes, true ) ) {
				$prefixes[] = $parts[0];
			}
			if ( ! in_array( $parts[1], $suffixes, true ) ) {
				$suffixes[] = $parts[1];
			}
			if ( strpos( $number_normalized, '.' ) !== false ) {
				$decimal_places = strlen( substr( strrchr( (string) $number_normalized, '.' ), 1 ) );
				$max_decimals   = max( $max_decimals, $decimal_places );
			}
		}
		// There may be only one prefix and one suffix.
		if ( sizeof( $prefixes ) > 1 || sizeof( $suffixes ) > 1 ) {
			return [];
		}

		return [
			'prefix'    => $prefixes[0],
			'suffix'    => $suffixes[0],
			'precision' => $max_decimals,
		];
	}
}
