<?php
declare( strict_types=1 );

namespace FiboFilters\SeoRules;

use FiboFilters\Filters\FiltersManager;

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

/**
 * Builds all possible URL combinations for a given SEO rule
 * based on the allowed filter values.
 */
class SeoRuleUrlGenerator {
	/**
	 * @var SeoRuleRepository
	 */
	private SeoRuleRepository $seo_rule_repository;
	/**
	 * @var FiltersManager
	 */
	private FiltersManager $filters_manager;
	/**
	 * @var SeoRuleUrlResolver
	 */
	private SeoRuleUrlResolver $path_resolver;

	public function __construct(
		SeoRuleRepository $seo_rule_repository,
		FiltersManager $filters_manager,
		SeoRuleUrlResolver $path_resolver
	) {
		$this->seo_rule_repository = $seo_rule_repository;
		$this->filters_manager     = $filters_manager;
		$this->path_resolver       = $path_resolver;
	}

	/**
	 * Generates all potential URLs for all SEO rules.
	 *
	 * @param string $lang Language.
	 *
	 * @return array
	 */
	public function generate_urls_for_rules( string $lang = '' ): array {

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

		if ( empty( $rules ) ) {
			return [];
		}

		$this->ensure_filters_initialized( $lang );

		$filters_by_slug = $this->get_filters_by_slug();

		if ( empty( $filters_by_slug ) ) {
			return [];
		}

		$result = [];

		foreach ( $rules as $rule ) {
			$rule_id = $rule->get_id();

			if ( null === $rule_id ) {
				continue;
			}

			$result[ $rule_id ] = $this->build_rule_data( $rule, $filters_by_slug );
		}

		return $result;
	}

	/**
	 * Ensures FiltersManager has filter instances with all values filled.
	 *
	 * @param string $lang Language code.
	 */
	private function ensure_filters_initialized( string $lang = '' ): void {

		if ( ! empty( $this->filters_manager->get_instances() ) ) {
			return;
		}

		$this->filters_manager->set_all_filter_instances( $lang );

		foreach ( $this->filters_manager->get_instances() as $instance ) {
			$this->filters_manager->fill_filter_with_all_values( $instance, $lang );
		}
	}

	/**
	 * Returns filters indexed by url_slug.
	 *
	 * @return array
	 */
	private function get_filters_by_slug(): array {

		$filters_data = $this->filters_manager->get_filters_data();

		return array_column( $filters_data, null, 'url_slug' );
	}

	/**
	 * Builds full data structure for a single rule:
	 *  - allowed value sets per filter,
	 *  - max_possible (theoretical max combinations),
	 *  - all URL + filters combinations.
	 *
	 * @param SeoRule $rule            SEO rule.
	 * @param array   $filters_by_slug Filters indexed by url_slug.
	 *
	 * @return array
	 */
	private function build_rule_data( SeoRule $rule, array $filters_by_slug ): array {

		$value_sets = $this->build_value_sets_for_rule( $rule, $filters_by_slug );

		$urls = [];

		if ( ! empty( $value_sets ) ) {
			$filter_slugs = array_keys( $value_sets );

			$urls = $this->generate_combinations_recursive(
				$rule,
				$value_sets,
				$filter_slugs,
				0,
				[]
			);
		}

		return [
			'rule'         => $rule,
			'max_possible' => count( $urls ),
			'urls'         => $urls,
			'value_sets'   => $value_sets,
		];
	}

	/**
	 * Builds the sets of allowed values for each filter in the rule.
	 *
	 * @param SeoRule $rule            SEO rule for which we generate URL combinations.
	 * @param array   $filters_by_slug Filter data indexed by url_slug (output of FiltersManager::get_filters_data()).
	 *
	 * @return array
	 */
	private function build_value_sets_for_rule( SeoRule $rule, array $filters_by_slug ): array {

		$result       = [];
		$rule_filters = $rule->get_rules();

		if ( empty( $rule_filters ) ) {
			return $result;
		}

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

			if ( ! isset( $filters_by_slug[ $slug ] ) ) {
				continue;
			}

			$filter_data = $filters_by_slug[ $slug ];

			if ( empty( $filter_data['values'] ) || ! is_array( $filter_data['values'] ) ) {
				continue;
			}

			$source_id       = isset( $filter_data['source_id'] ) ? (int) $filter_data['source_id'] : 0;
			$values_for_rule = [];

			foreach ( $filter_data['values'] as $value_row ) {
				if ( empty( $value_row['label_url'] ) ) {
					continue;
				}

				$value_slug = $value_row['label_url'];

				$can_use_value = empty( $allowed_value_slugs )
								|| in_array( $value_slug, $allowed_value_slugs, true );

				if ( ! $can_use_value ) {
					continue;
				}

				$values_for_rule[] = [
					'slug'      => $value_slug,
					'value_id'  => isset( $value_row['value_id'] ) ? (int) $value_row['value_id'] : 0,
					'source_id' => $source_id,
				];
			}

			if ( ! empty( $values_for_rule ) ) {
				$result[ $slug ] = $values_for_rule;
			}
		}

		return $this->sort_value_sets_alphabetically_by_slug( $result );
	}

	/**
	 * Sorts value sets:
	 * - filter keys (url_slug) alphabetically,
	 * - values within each filter alphabetically by 'slug'.
	 *
	 * @param array $value_sets Allowed values per filter slug for this rule.
	 */
	private function sort_value_sets_alphabetically_by_slug( array $value_sets ): array {

		foreach ( $value_sets as $slug => $values_for_rule ) {
			usort(
				$values_for_rule,
				static function ( array $a, array $b ): int {
					return strcmp(
						(string) ( $a['slug'] ?? '' ),
						(string) ( $b['slug'] ?? '' )
					);
				}
			);

			$value_sets[ $slug ] = $values_for_rule;
		}

		ksort( $value_sets, SORT_STRING );

		return $value_sets;
	}

	/**
	 * Generates all possible combinations of filter values recursively.
	 *
	 * @param SeoRule $rule                SEO rule for which we generate URL combinations.
	 * @param array   $value_sets          Allowed values per filter slug for this rule.
	 * @param array   $filter_slugs        Ordered list of filter slugs (dimension order).
	 * @param int     $level               Current recursion depth.
	 * @param array   $current_combination Partially built combination for filters processed so far.
	 *
	 * @return array
	 */
	private function generate_combinations_recursive(
		SeoRule $rule,
		array $value_sets,
		array $filter_slugs,
		int $level,
		array $current_combination
	): array {

		// Base case: all filter dimensions processed → build final URL.
		if ( $level >= count( $filter_slugs ) ) {
			$rows_from_resolver = $this->path_resolver->build_url_rows_from_combination(
				$rule,
				$current_combination
			);

			$result = [];

			foreach ( $rows_from_resolver as $row ) {
				$url = isset( $row['url'] ) ? (string) $row['url'] : '';

				if ( $url === '' ) {
					continue;
				}

				$item = [
					'url'     => $url,
					'filters' => $current_combination,
				];

				if ( ! empty( $row['context'] ) && is_array( $row['context'] ) ) {
					$item['context'] = $row['context'];
				}

				$result[] = $item;
			}

			return $result;
		}

		$slug = $filter_slugs[ $level ];

		// No values for this filter – skip this dimension.
		if ( empty( $value_sets[ $slug ] ) ) {
			return $this->generate_combinations_recursive(
				$rule,
				$value_sets,
				$filter_slugs,
				$level + 1,
				$current_combination
			);
		}

		$result = [];

		// Iterate over all values for this filter and branch recursion for each.
		foreach ( $value_sets[ $slug ] as $value_data ) {

			$next_combination          = $current_combination;
			$next_combination[ $slug ] = [
				'slug'      => $value_data['slug'],
				'value_id'  => $value_data['value_id'] ?? 0,
				'source_id' => $value_data['source_id'] ?? 0,
			];

			$child_results = $this->generate_combinations_recursive(
				$rule,
				$value_sets,
				$filter_slugs,
				$level + 1,
				$next_combination
			);

			foreach ( $child_results as $row ) {
				$result[] = $row;
			}
		}

		return $result;
	}
}
