<?php
declare( strict_types=1 );

namespace FiboFilters\Filters;

use FiboFilters\Index\Documents;
use FiboFilters\Index\Sources;
use FiboFilters\Index\Values;
use FiboFilters\Struct\QueryContext;
use FiboFilters\Util\Cache;
use FiboFilters\Util\DbUtil;

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

/**
 * Class FiltersQuery
 */
class FiltersQuery {
	/**
	 * User query
	 * Collection of elementary queries
	 *
	 * @var array
	 */
	private $user_query = [];

	/** @var Cache */
	private $cache;

	/** @var DbUtil */
	private $db_util;

	/** @var Sources */
	private $sources;

	/** @var Values */
	private Values $values;

	/** @var Documents */
	private Documents $documents;

	public function __construct(
		Cache $cache,
		DbUtil $db_util,
		Sources $sources,
		Values $values,
		Documents $documents
	) {
		$this->cache     = $cache;
		$this->db_util   = $db_util;
		$this->sources   = $sources;
		$this->values    = $values;
		$this->documents = $documents;
	}

	/**
	 * Set user query
	 *
	 * @param array $query Filter query.
	 */
	public function set_user_query( array $query ): void {
		$this->user_query = $query;
	}

	/**
	 * Get user query
	 */
	public function get_user_query(): array {
		return apply_filters( 'fibofilters/request/get_user_query', $this->user_query );
	}

	/**
	 * Process of filtering
	 *
	 * @param array|null $docs_in Collection of product IDs.
	 * @param string     $language Language.
	 *
	 * @return array Product IDs (parents and variations).
	 */
	public function filter( array $docs_in = null, string $language = '' ): array {
		$ids = [];
		$ctx = new QueryContext( $language );

		foreach ( $this->get_user_query() as $elementary_query ) {
			$source_id = $this->sources->get_source_id_by_name( $elementary_query['source'], $ctx );

			if ( empty( $source_id ) ) {
				continue;
			}

			$elementary_query['source_id'] = $source_id;

			$res = $this->get_docs_for_elementary_query( $elementary_query, $docs_in, $language );

			// No docs for elementary query? Return empty array.
			if ( empty( $res ) ) {
				$ids = [];
				break;
			}

			if ( empty( $ids ) ) {
				$ids = $res;
			} else {
				$ids = array_intersect( $ids, $res );
			}
		}

		return array_map( 'intval', $ids );
	}

	/**
	 * Looking for products which belong to source with the indicated values
	 * E.g. source may be product category "tax_product_cat" and values are Ids of terms.
	 *
	 * @param array      $elementary_query Elementary query struct.
	 * @param array|null $docs_in Collection of product Ids to narrow the search scope.
	 * @param string     $language Language.
	 *
	 * @return array products IDs
	 */
	public function get_docs_for_elementary_query( array $elementary_query, array $docs_in = null, string $language = '' ): array {
		$docs_ids = [];
		$ctx      = new QueryContext( $language );
		$logic    = $elementary_query['logic'];

		// Break early.
		if ( is_array( $docs_in ) && empty( $docs_in ) ) {
			return $docs_ids;
		}

		$values_ids = $this->values->get_values_ids_by_user_query(
			$elementary_query['values'],
			$elementary_query['source_id'],
			$logic,
			$ctx
		);

		// Break if no values found.
		if ( empty( $values_ids ) ) {
			return [];
		}

		return $this->documents->get_post_ids_by_value_ids( $values_ids, $logic, $docs_in, $ctx );
	}

	/**
	 * Get children for value from selected source
	 *
	 * @param string $selected_item Selected item.
	 * @param int    $source_id Source ID.
	 * @param string $language Language.
	 * @param string $selection_field Selection field.
	 */
	public function get_value_children( string $selected_item, int $source_id, string $language, string $selection_field ): array {
		$ctx    = new QueryContext( $language );
		$values = $this->values->get_values_by_source_id( $source_id, $ctx );

		// When a value is pointed to by "label_url", we need to get its "value" property.
		if ( $selection_field === 'label_url' ) {
			$current = wp_list_filter( $values, [ 'label_url' => $selected_item ] );
			if ( empty( $current ) ) {
				return [];
			}
			$current       = array_values( $current );
			$selected_item = $current[0]['value'];
		}

		return $this->get_children_recursive( $values, $selected_item );
	}

	/**
	 * A recursive function that returns a list of all children of the specified parent
	 *
	 * @param array  $values List of values and the parent is indicated only by the "parent_value" property.
	 * @param string $parent_value ID of parent.
	 */
	private function get_children_recursive( array $values, string $parent_value ): array {
		$children = [];

		foreach ( $values as $value ) {
			if ( $value['parent_value'] === $parent_value ) {
				$children[]     = $value;
				$value_children = $this->get_children_recursive( $values, $value['value'] );
				if ( ! empty( $value_children ) ) {

					$children = array_merge( $children, $value_children );
				}
			}
		}

		return $children;
	}
}
