<?php
declare( strict_types=1 );

namespace FiboFilters\SeoRules;

use FiboFilters\Filters\StoreFilters;
use FiboFilters\Integrations\Multilingual;
use FiboFilters\Util\FilterUtil;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Get the best matching SEO rule for the current request context.
 */
class SeoRuleMatcher {
	/**
	 * @var SeoRuleRepository
	 */
	private SeoRuleRepository $seo_rule_repository;
	/**
	 * @var SeoRuleUrlResolver
	 */
	private SeoRuleUrlResolver $url_resolver;
	/**
	 * @var StoreFilters
	 */
	private StoreFilters $store_filters;
	/**
	 * @var Multilingual
	 */
	private Multilingual $multilingual;

	public function __construct(
		SeoRuleRepository $seo_rule_repository,
		SeoRuleUrlResolver $url_resolver,
		StoreFilters $store_filters,
		Multilingual $multilingual
	) {
		$this->seo_rule_repository = $seo_rule_repository;
		$this->url_resolver        = $url_resolver;
		$this->store_filters       = $store_filters;
		$this->multilingual        = $multilingual;
	}

	/**
	 * Finds best matching SEO rule ID for the current request.
	 */
	public function find_best_matching_rule_id(): int {
		$context = apply_filters( 'fibofilters/request/get_context', [] );

		if ( empty( $context ) ) {
			return 0;
		}

		if ( ! $context['show_filters'] || ! $context['conditions']['has_active_filters'] ) {
			return 0;
		}

		$context_base_view = $this->url_resolver->resolve_base_view_from_context( $context );

		if ( $context_base_view === '' ) {
			return 0;
		}

		$request_filters = $this->get_normalized_seo_filter_state();

		if ( empty( $request_filters ) ) {
			return 0;
		}

		$lang = $this->multilingual->is_multilingual()
				? $this->multilingual->get_current_language()
				: '';

		$rules = $this->seo_rule_repository->get_all_rules( $lang );

		if ( empty( $rules ) ) {
			return 0;
		}

		return $this->find_best_rule_id( $context_base_view, $request_filters, $rules );
	}

	/**
	 * Reads SEO-relevant filter state from $_GET and normalizes it to canonical form:
	 *  - only filter slugs allowed,
	 *  - keys sorted alphabetically,
	 *  - multi-select values split by comma, trimmed, deduplicated and sorted.
	 *
	 * Result example:
	 * [
	 *   'color'        => ['black', 'green'],
	 *   'clothes-size' => ['m', 'xl'],
	 * ]
	 */
	private function get_normalized_seo_filter_state(): array {

		$normalized = [];
		$url_slugs  = $this->store_filters->get_all_filter_url_slugs();

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		foreach ( $_GET as $param => $raw_value ) {
			if ( ! is_string( $param ) || ! is_string( $raw_value ) ) {
				continue;
			}

			$param_sanitized = FilterUtil::sanitize_value( $param );
			if ( $param_sanitized === '' ) {
				continue;
			}

			if ( ! in_array( $param_sanitized, $url_slugs, true ) ) {
				continue;
			}

			$value_sanitized = FilterUtil::sanitize_value( $raw_value );
			if ( $value_sanitized === '' ) {
				continue;
			}

			$values = explode( ',', $value_sanitized );
			$values = array_map( 'trim', $values );
			$values = array_filter(
				$values,
				static function ( string $v ): bool {
					return '' !== $v;
				}
			);
			$values = array_values( array_unique( $values ) );
			sort( $values );

			if ( ! empty( $values ) ) {
				$normalized[ $param_sanitized ] = $values;
			}
		}

		if ( ! empty( $normalized ) ) {
			ksort( $normalized );
		}

		return $normalized;
	}

	/**
	 * Finds best matching rule for given base_view and normalized filters.
	 *
	 * @param string    $context_base_view       e.g. "shop:main", "product_cat:123".
	 * @param array     $request_filters normalized filter state (slug => [values...]).
	 * @param SeoRule[] $rules           list of all rules for current language.
	 */
	private function find_best_rule_id(
		string $context_base_view,
		array $request_filters,
		array $rules
	): int {
		$best_rule_id     = 0;
		$best_match_score = 0;

		foreach ( $rules as $rule ) {
			if ( ! $this->is_base_view_match( $rule->get_base_view(), $context_base_view ) ) {
				continue;
			}

			$rule_filters = $rule->get_rules();
			$score        = $this->calculate_match_score( $rule_filters, $request_filters );

			if ( $score <= 0 ) {
				continue;
			}

			// More specific rule (more required values) wins.
			if ( $score > $best_match_score ) {
				$best_match_score = $score;
				$best_rule_id     = $rule->get_id();
			}
		}

		return $best_rule_id;
	}

	/**
	 * Checks if rule base_view matches current base_view.
	 *
	 * Supports patterns like:
	 *  - "shop:any"         → matches any "shop:*"
	 *  - "product_cat:any"  → matches any "product_cat:*"
	 *  - otherwise requires exact match.
	 *
	 * @param string $rule_base_view    e.g. "shop:main", "product_cat:any".
	 * @param string $context_base_view e.g. "shop:main", "product_cat:123".
	 */
	private function is_base_view_match( string $rule_base_view, string $context_base_view ): bool {

		// Exact match.
		if ( $rule_base_view === $context_base_view ) {
			return true;
		}

		list( $rule_type, $rule_arg )       = SeoRuleUrlResolver::parse_base_view( $rule_base_view );
		list( $current_type, $current_arg ) = SeoRuleUrlResolver::parse_base_view( $context_base_view );

		if ( $rule_type !== $current_type ) {
			return false;
		}

		// Pattern "*:any" → match any non-empty argument of the selected type.
		if ( $rule_arg === 'any' && $current_arg !== '' ) {
			return true;
		}

		return false;
	}

	/**
	 * Calculates how well the rule filters match the request filters.
	 *
	 * @param array $rule_filters    Rule filters (slug => [required values...]).
	 * @param array $request_filters Request filters (slug => [selected values...]).
	 *
	 * @return int Match score (number of matched required values), or 0 if not matching.
	 */
	private function calculate_match_score(
		array $rule_filters,
		array $request_filters
	): int {
		$score = 0;

		foreach ( $rule_filters as $slug => $required_values ) {

			if ( ! array_key_exists( $slug, $request_filters ) ) {
				return 0;
			}

			$request_values = $request_filters[ $slug ];

			if ( is_array( $required_values ) ) {
				$required_values = array_values( array_unique( $required_values ) );
			} else {
				$required_values = [];
			}

			// The rule requires the filter to be present, but has no specific value requirements.
			if ( empty( $required_values ) ) {
				// The filter must have at least one value.
				if ( empty( $request_values ) ) {
					return 0;
				}

				$score++;
				continue;
			}

			// The rule requires specific values.
			$matches = 0;

			foreach ( $required_values as $required_value_slug ) {
				if ( in_array( $required_value_slug, $request_values, true ) ) {
					$matches++;
				}
			}

			if ( $matches === 0 ) {
				return 0;
			}

			// Add the number of hits to the score.
			$score += $matches;
		}

		return $score;
	}
}
