<?php

namespace DgoraWcas\Engines\WordPressNative;

use DgoraWcas\Analytics\Recorder;
use DgoraWcas\Multilingual;
use DgoraWcas\Product;
use DgoraWcas\Helpers;

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Search {

	/**
	 * Total autocomplete limit
	 */
	private $totalLimit;

	/**
	 * Flexible lmits
	 * bool
	 */
	private $flexibleLimits = true;

	/**
	 * Show heading in autocomplete
	 * bool
	 */
	private $showHeadings = false;

	/**
	 * Autocomplete groups
	 * array
	 */
	private $groups = [];

	/**
	 * Buffer for post IDs uses for search results page
	 *
	 * @var null
	 */
	private $postsIDsBuffer = null;

	/**
	 * List of fields in which the phrase is searched
	 *
	 * @var array
	 */
	private $searchIn = [];

	/**
	 * @var bool Whether the search results have already been overwritten.
	 */
	private $hooked = false;

	public function __construct() {

		if ( dgoraAsfwFs()->is__premium_only() ) {
			// Run only if index isn't completed
			if ( DGWT_WCAS()->engine !== 'native' ) {
				return;
			}

			// Limit the number of fields to be searched using the native engine when the index is not ready
			add_filter(
				'dgwt/wcas/native/search_in',
				function () {
					return [ 'title' ];
				},
				10
			);

			// Set 'placeholder' if the index is being built
			add_filter(
				'dgwt/wcas/search_results/output',
				function ( $output ) {
					$status  = \DgoraWcas\Engines\TNTSearchMySQL\Indexer\Builder::getInfo( 'status', \DgoraWcas\Engines\TNTSearchMySQL\Config::getIndexRole() );
					$startTs = absint( \DgoraWcas\Engines\TNTSearchMySQL\Indexer\Builder::getInfo( 'start_ts', \DgoraWcas\Engines\TNTSearchMySQL\Config::getIndexRole() ) );
					if ( in_array(
						$status,
						[
							'building',
							'preparing',
						]
					) && ( time() - $startTs ) < 12 * HOUR_IN_SECONDS ) {
						$output['engine'] = 'placeholder';
					}

					return $output;
				},
				99999
			);
		}

		$this->searchIn = apply_filters(
			'dgwt/wcas/native/search_in',
			[
				'title',
				'content',
				'excerpt',
				'sku',
			]
		);

		add_filter( 'posts_search', [ $this, 'searchFilters' ], 501, 2 );
		add_filter( 'posts_where', [ $this, 'fixWooExcerptSearch' ], 100, 2 );
		add_filter( 'posts_distinct', [ $this, 'searchDistinct' ], 501, 2 );
		add_filter( 'posts_join', [ $this, 'searchFiltersJoin' ], 501, 2 );

		// Search results page
		add_action(
			'init',
			function () {
				if ( apply_filters( 'dgwt/wcas/override_search_results_page', true ) && ! Helpers::isNoFiboSearchModeActive() ) {
					add_filter( 'pre_get_posts', [ $this, 'overwriteSearchPage' ], 900001 );
					add_filter( 'posts_search', [ 'DgoraWcas\Helpers', 'clearSearchQuery' ], 1000, 2 );
					add_filter( 'the_posts', [ 'DgoraWcas\Helpers', 'rollbackSearchPhrase' ], 1000, 2 );
					add_filter( 'dgwt/wcas/search_page/result_post_ids', [ $this, 'getProductIds' ], 10, 2 );
				}
			}
		);

		// Search results ajax action
		if ( DGWT_WCAS_WC_AJAX_ENDPOINT ) {
			add_action( 'wc_ajax_' . DGWT_WCAS_SEARCH_ACTION, [ $this, 'getSearchResults' ] );
		} else {
			add_action( 'wp_ajax_nopriv_' . DGWT_WCAS_SEARCH_ACTION, [ $this, 'getSearchResults' ] );
			add_action( 'wp_ajax_' . DGWT_WCAS_SEARCH_ACTION, [ $this, 'getSearchResults' ] );
		}

		// Labels
		if ( ! dgoraAsfwFs()->is_premium() ) {
			add_filter( 'dgwt/wcas/labels', [ $this, 'setTaxonomiesLabels' ], 5 );
			add_filter( 'dgwt/wcas/labels', [ $this, 'fixTaxonomiesLabels' ], PHP_INT_MAX - 5 );
		}

		// Fixes if "Polylang" is active but without "Polylang for WooCommerce" or "Hyyan WooCommerce Polylang Integration"
		if ( Multilingual::isPolylang() && ! class_exists( 'Polylang_Woocommerce' ) && ! defined( 'Hyyan_WPI_DIR' ) ) {
			add_filter( 'woocommerce_ajax_get_endpoint', [ $this, 'fixPolylangWooEndpoint' ], 10, 2 );
		}

		// Add "No results" suggestion if all results have been removed in earlier filters.
		add_filter(
			'dgwt/wcas/search_results/output',
			[
				'DgoraWcas\Helpers',
				'noResultsSuggestion',
			],
			PHP_INT_MAX - 10
		);

		// Init Search Analytics
		if ( DGWT_WCAS()->settings->getOption( 'analytics_enabled' ) === 'on' ) {
			$stats = new Recorder();
			$stats->listen();
		}

		Helpers::maybeInjectFiboDebugFields( 'score' );
	}

	/**
	 * Get search results via ajax
	 *
	 * @param string $phrase Search phrase.
	 * @param bool $return Whether to return the results.
	 * @param string $context Search context: 'autocomplete' or 'product-ids'.
	 *
	 * @return mixed|void
	 */
	public function getSearchResults( $phrase = '', $return = false, $context = 'autocomplete' ) {
		if ( $context === 'all-results' ) {
			$context = 'product-ids';
		}
		if ( dgoraAsfwFs()->is__premium_only() ) {
			//phpcs:ignore WordPress.Security.NonceVerification.Recommended
			if ( isset( $_GET['dgwt_wcas_product_ids_only'] ) && $_GET['dgwt_wcas_product_ids_only'] == '1' ) {
				$context = 'product-ids-ff';
			}
		}

		$start = microtime( true );
		$lang  = '';
		$hits  = 0;
		if ( Multilingual::isMultilingual() ) {
			$lang = Multilingual::getCurrentLanguage();
		}

		if ( ! defined( 'DGWT_WCAS_AJAX' ) ) {
			define( 'DGWT_WCAS_AJAX', true );
		}

		$this->groups         = $this->searchResultsGroups();
		$this->flexibleLimits = apply_filters( 'dgwt/wcas/flexible_limits', true );
		$this->showHeadings   = Helpers::canGroupSuggestions();

		if ( $this->flexibleLimits ) {
			$totalLimit       = DGWT_WCAS()->settings->getOption( 'suggestions_limit', 'int', 7 );
			$this->totalLimit = $totalLimit === - 1 ? $this->calcFreeSlots() : $totalLimit;
		}

		$output  = [];
		$results = [];
		$keyword = '';

		if ( $return ) {
			$keyword = sanitize_text_field( $phrase );
		} else {
			// Compatible with v1.1.7
			//phpcs:disable WordPress.Security.NonceVerification.Recommended
			if ( ! empty( $_REQUEST['dgwt_wcas_keyword'] ) ) {
				$keyword = sanitize_text_field( $_REQUEST['dgwt_wcas_keyword'] );
			}

			if ( ! empty( $_REQUEST['s'] ) ) {
				$keyword = sanitize_text_field( $_REQUEST['s'] );
			}
			//phpcs:enable
		}

		$keyword = apply_filters( 'dgwt/wcas/phrase', $keyword );

		// Break early if keyword contains blacklisted phrase.
		if ( Helpers::phraseContainsBlacklistedTerm( $keyword ) ) {
			if ( $return ) {
				return $this->getEmptyOutput();
			} else {
				echo json_encode( Helpers::noResultsSuggestion( $this->getEmptyOutput() ) );
				die();
			}
		}

		/* SEARCH IN WOO CATEGORIES */
		if ( $context === 'autocomplete' && array_key_exists( 'tax_product_cat', $this->groups ) ) {
			$limit                                      = $this->flexibleLimits ? $this->totalLimit : $this->groups['tax_product_cat']['limit'];
			$categories                                 = $this->getCategories( $keyword, $limit );
			$this->groups['tax_product_cat']['results'] = $categories['items'];
			$hits                                      += $categories['total'];
		}

		/* SEARCH IN WOO TAGS */
		if ( $context === 'autocomplete' && array_key_exists( 'tax_product_tag', $this->groups ) ) {
			$limit                                      = $this->flexibleLimits ? $this->totalLimit : $this->groups['tax_product_tag']['limit'];
			$tags                                       = $this->getTags( $keyword, $limit );
			$this->groups['tax_product_tag']['results'] = $tags['items'];
			$hits                                      += $tags ['total'];
		}

		/* SEARCH IN PRODUCTS */
		$totalProducts = 0;
		if ( apply_filters( 'dgwt/wcas/search_in_products', true ) ) {

			$args = [
				's'                   => $keyword,
				'posts_per_page'      => - 1,
				'post_type'           => 'product',
				'post_status'         => 'publish',
				'ignore_sticky_posts' => 1,
				'order'               => 'DESC',
				'suppress_filters'    => false,
			];

			// Backward compatibility WC < 3.0
			if ( Helpers::compareWcVersion( '3.0', '<' ) ) {
				$args['meta_query'] = $this->getMetaQuery();
			} else {
				$args['tax_query'] = $this->getTaxQuery();
			}

			$args = apply_filters( 'dgwt/wcas/search_query/args', $args );

			$products      = get_posts( $args );
			$products      = apply_filters( 'dgwt/wcas/search_results/products_raw', $products );
			$totalProducts = count( $products );
			$hits         += $totalProducts;
			do_action( 'dgwt/wcas/analytics/after_searching', $keyword, $hits, $lang );

			if ( ! empty( $products ) ) {
				$orderedProducts = [];
				$i               = 0;

				foreach ( $products as $post ) {
					if ( $context === 'product-ids' ) {
						$orderedProducts[ $i ]     = new \stdClass();
						$orderedProducts[ $i ]->ID = $post->ID;
					} else {
						$orderedProducts[ $i ] = $post;
					}

					$score                        = Helpers::calcScore( $keyword, $post->post_title );
					$orderedProducts[ $i ]->score = apply_filters( 'dgwt/wcas/search_results/product/score', $score, $keyword, $post->ID, $post );

					$i ++;
				}

				// Sort by relevance
				usort( $orderedProducts, [ 'DgoraWcas\Helpers', 'cmpSimilarity' ] );

				// Response that returns all results.
				if ( $context === 'product-ids' ) {
					$output['suggestions'] = $orderedProducts;
					$output['time']        = number_format( ( microtime( true ) - $start ), 2, '.', '' ) . ' sec';
					$result                = apply_filters( 'dgwt/wcas/page_search_results/output', $output );

					if ( $return ) {
						return $result;
					} else {
						echo json_encode( $result );
						die();
					}
				}
				if ( $context === 'product-ids-ff' ) {
					$output['total']   = count( $orderedProducts );
					$output['results'] = wp_list_pluck( $orderedProducts, 'ID' );
					$output['engine']  = 'free';
					$output['time']    = number_format( ( microtime( true ) - $start ), 2, '.', '' ) . ' sec';
					$result            = apply_filters( 'dgwt/wcas/page_search_results/output/product_ids_only', $output );

					if ( $return ) {
						return $result;
					} else {
						echo json_encode( $result );
						die();
					}
				}

				$productsSlots = $this->flexibleLimits ? $this->totalLimit : $this->groups['product']['limit'];

				$fields = [];
				if ( DGWT_WCAS()->settings->getOption( 'show_product_image' ) === 'on' ) {
					$fields[] = 'thumb_html';
				}
				if ( DGWT_WCAS()->settings->getOption( 'show_product_price' ) === 'on' ) {
					$fields[] = 'price';
				}
				if ( DGWT_WCAS()->settings->getOption( 'show_product_sku' ) === 'on' ) {
					$fields[] = 'sku';
				}

				$relevantProducts = $this->getProductsData( $orderedProducts, $productsSlots, $fields );
			}
			wp_reset_postdata();
		} /* END SEARCH IN PRODUCTS */

		if ( ! empty( $relevantProducts ) ) {
			$this->groups['product']['results'] = $relevantProducts;
		}

		if ( $this->hasResults() ) {
			if ( $this->flexibleLimits ) {
				$this->applyFlexibleLimits();
			}

			$results = $this->convertGroupsToSuggestions();

			// Show more
			$showMoreProductsLink = apply_filters( 'dgwt/wcas/search_results/show_more_products_link', false );

			if (
				! empty( $this->groups['product']['results'] ) &&
				( $showMoreProductsLink || count( $this->groups['product']['results'] ) < $totalProducts )
			) {
				$results[] = [
					'value' => '',
					'total' => $totalProducts,
					'url'   => add_query_arg(
						[
							's'         => $keyword,
							'post_type' => 'product',
							'dgwt_wcas' => '1',
						],
						home_url()
					),
					'type'  => 'more_products',
				];
			}
		} else {
			if ( $context === 'product-ids-ff' ) {
				$output = [
					'total'   => 0,
					'results' => [],
					'engine'  => 'free',
				];
				$result = apply_filters( 'dgwt/wcas/search_results/output/product_ids_only', $output );

				if ( $return ) {
					return $result;
				} else {
					echo json_encode( $result );
					die();
				}
			}
			if ( $context === 'product-ids' ) {
				$emptyResult     = new \stdClass();
				$emptyResult->ID = 0;
				$results[]       = $emptyResult;
			} else {
				$results[] = [
					'value' => '',
					'type'  => 'no-results',
				];
			}
		}

		$output['suggestions'] = $results;
		$output['total']       = $hits;

		$output['time'] = number_format( ( microtime( true ) - $start ), 2, '.', '' ) . ' sec';

		$output['engine'] = 'free';
		$output['v']      = DGWT_WCAS_VERSION;

		$result = apply_filters( 'dgwt/wcas/search_results/output', $output );

		if ( $return ) {
			return $result;
		} else {
			echo json_encode( $result );
			die();
		}
	}

	public function getProductsData( $orderedProducts, $limit = - 1, $fields = [] ) {
		$relevantProducts = [];

		foreach ( $orderedProducts as $post ) {
			$product = new Product( $post );

			if ( ! $product->isCorrect() ) {
				continue;
			}

			// Strip <script> and <style> tags along with their contents.
			$value = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $product->getName() );
			// Strip remaining tags except those indicated.
			$value = html_entity_decode(
				wp_kses(
					$value,
					[
						'b'      => [
							'class' => true,
						],
						'br'     => [],
						'span'   => [
							'class' => true,
						],
						'strong' => [
							'class' => true,
						],
						'sub'    => [],
						'sup'    => [],
					]
				)
			);

			$r = [
				'post_id' => $product->getID(),
				'value'   => $value,
				'url'     => $product->getPermalink(),
				'type'    => 'product',
			];

			// Get thumb HTML
			if ( in_array( 'thumb_html', $fields, true ) ) {
				$r['thumb_html'] = $product->getThumbnail();
			}

			// Get price
			if ( in_array( 'price', $fields, true ) ) {
				$r['price'] = $product->getPriceHTML();
			}

			// Get description
			if ( DGWT_WCAS()->settings->getOption( 'show_product_desc' ) === 'on' ) {
				$wordsLimit = 0;
				if ( DGWT_WCAS()->settings->getOption( 'show_details_box' ) === 'on' ) {
					$wordsLimit = 15;
				}
				$r['desc'] = $product->getDescription( 'suggestions', $wordsLimit );
			}

			// Get SKU
			if ( in_array( 'sku', $fields, true ) ) {
				$r['sku'] = $product->getSKU();
			}

			if ( Helpers::isFiboDebugEnabled( 'score' ) ) {
				$r['score'] = round( $post->score, 2 );
			}

			// Is on sale
			// if ( DGWT_WCAS()->settings->getOption( 'show_sale_badge' ) === 'on' ) {
			// $r[ 'on_sale' ] = $product->is_on_sale();
			// }

			// Is featured
			// if ( DGWT_WCAS()->settings->getOption( 'show_featured_badge' ) === 'on' ) {
			// $r[ 'featured' ] = $product->is_featured();
			// }

			$relevantProducts[] = apply_filters( 'dgwt/wcas/search_results/products', $r, $product );

			$limit --;

			if ( $limit === 0 ) {
				break;
			}
		}

		return $relevantProducts;
	}

	/**
	 * Get meta query
	 * For WooCommerce < 3.0
	 *
	 * Return array
	 */

	private function getMetaQuery() {

		$meta_query = [
			'relation' => 'AND',
			1          => [
				'key'     => '_visibility',
				'value'   => [ 'search', 'visible' ],
				'compare' => 'IN',
			],
			2          => [
				'relation' => 'OR',
				[
					'key'     => '_visibility',
					'value'   => [ 'search', 'visible' ],
					'compare' => 'IN',
				],
			],
		];

		// Exclude out of stock products from suggestions
		if ( DGWT_WCAS()->settings->getOption( 'exclude_out_of_stock' ) === 'on' ) {
			$meta_query[] = [
				'key'     => '_stock_status',
				'value'   => 'outofstock',
				'compare' => 'NOT IN',
			];
		};

		return $meta_query;
	}

	/**
	 * Get tax query
	 * For WooCommerce >= 3.0
	 *
	 * Return array
	 */

	private function getTaxQuery() {

		$product_visibility_term_ids = wc_get_product_visibility_term_ids();

		$tax_query = [
			'relation' => 'AND',
		];

		$tax_query[] = [
			'taxonomy' => 'product_visibility',
			'field'    => 'term_taxonomy_id',
			'terms'    => $product_visibility_term_ids['exclude-from-search'],
			'operator' => 'NOT IN',
		];

		// Exclude out of stock products from suggestions
		if ( DGWT_WCAS()->settings->getOption( 'exclude_out_of_stock' ) === 'on' ) {
			$tax_query[] = [
				'taxonomy' => 'product_visibility',
				'field'    => 'term_taxonomy_id',
				'terms'    => $product_visibility_term_ids['outofstock'],
				'operator' => 'NOT IN',
			];
		};

		return $tax_query;
	}

	/**
	 * Search for matching category
	 *
	 * @param string $keyword
	 * @param int $limit
	 *
	 * @return array
	 */

	public function getCategories( $keyword, $limit = 3 ) {

		$results = [
			'total' => 0,
			'items' => [],
		];

		$args = [
			'taxonomy' => 'product_cat',
		];

		$productCategories = get_terms( 'product_cat', apply_filters( 'dgwt/wcas/search/product_cat/args', $args ) );
		$keywordUnslashed  = wp_unslash( $keyword );

		// Compare keyword and term name
		$i = 0;
		foreach ( $productCategories as $cat ) {

			if ( $i < $limit ) {

				$catName = html_entity_decode( $cat->name );
				$pos     = strpos( mb_strtolower( remove_accents( Helpers::removeGreekAccents( $catName ) ) ), mb_strtolower( remove_accents( Helpers::removeGreekAccents( $keywordUnslashed ) ) ) );

				if ( $pos !== false ) {
					$results['total'] ++;

					$termLang = Multilingual::getTermLang( $cat->term_id, 'product_cat' );

					$results['items'][ $i ] = [
						'term_id'     => $cat->term_id,
						'taxonomy'    => 'product_cat',
						'value'       => $catName,
						'url'         => get_term_link( $cat, 'product_cat' ),
						'breadcrumbs' => Helpers::getTermBreadcrumbs( $cat->term_id, 'product_cat', [], $termLang, [ $cat->term_id ] ),
						'type'        => 'taxonomy',
					];

					// Fix: Remove last separator
					if ( ! empty( $results['items'][ $i ]['breadcrumbs'] ) ) {
						$results['items'][ $i ]['breadcrumbs'] = mb_substr( $results['items'][ $i ]['breadcrumbs'], 0, - 3 );
					}

					$i ++;
				}
			}
		}

		return $results;
	}

	/**
	 * Extend research in the Woo tags
	 *
	 * @param strong $keyword
	 * @param int $limit
	 *
	 * @return array
	 */

	public function getTags( $keyword, $limit = 3 ) {

		$results = [
			'total' => 0,
			'items' => [],
		];

		$args = [
			'taxonomy' => 'product_tag',
		];

		$productTags      = get_terms( 'product_tag', apply_filters( 'dgwt/wcas/search/product_tag/args', $args ) );
		$keywordUnslashed = wp_unslash( $keyword );

		// Compare keyword and term name
		$i = 0;
		foreach ( $productTags as $tag ) {

			if ( $i < $limit ) {

				$tagName = html_entity_decode( $tag->name );
				$pos     = strpos( mb_strtolower( remove_accents( Helpers::removeGreekAccents( $tagName ) ) ), mb_strtolower( remove_accents( Helpers::removeGreekAccents( $keywordUnslashed ) ) ) );

				if ( $pos !== false ) {
					$results['total'] ++;

					$results['items'][ $i ] = [
						'term_id'  => $tag->term_id,
						'taxonomy' => 'product_tag',
						'value'    => $tagName,
						'url'      => get_term_link( $tag, 'product_tag' ),
						'parents'  => '',
						'type'     => 'taxonomy',
					];

					$i ++;
				}
			}
		}

		return $results;
	}

	/**
	 * Search in extra fields
	 *
	 * @param string $search SQL
	 *
	 * @return string prepared SQL
	 */

	public function searchFilters( $search, $wp_query ) {
		global $wpdb;

		if ( empty( $search ) ) {
			return $search; // skip processing - there is no keyword
		}

		if ( $this->isAjaxSearch() ) {

			$q = $wp_query->query_vars;

			if ( $q['post_type'] !== 'product' ) {
				return $search; // skip processing
			}

			$n = ! empty( $q['exact'] ) ? '' : '%';

			// TODO
			//phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found
			$search = $searchand = '';

			if ( ! empty( $q['search_terms'] ) ) {
				foreach ( (array) $q['search_terms'] as $term ) {
					$like    = $n . $wpdb->esc_like( $term ) . $n;
					$search .= "{$searchand} (";

					// Search in title
					if ( in_array( 'title', $this->searchIn ) ) {
						$search .= $wpdb->prepare( "($wpdb->posts.post_title LIKE %s)", $like );
					} else {
						$search .= '(0 = 1)';
					}

					// Search in content
					if ( DGWT_WCAS()->settings->getOption( 'search_in_product_content' ) === 'on' && in_array( 'content', $this->searchIn ) ) {
						$search .= $wpdb->prepare( " OR ($wpdb->posts.post_content LIKE %s)", $like );
					}

					// Search in excerpt
					if ( DGWT_WCAS()->settings->getOption( 'search_in_product_excerpt' ) === 'on' && in_array( 'excerpt', $this->searchIn ) ) {
						$search .= $wpdb->prepare( " OR ($wpdb->posts.post_excerpt LIKE %s)", $like );
					}

					// Search in SKU
					if ( DGWT_WCAS()->settings->getOption( 'search_in_product_sku' ) === 'on' && in_array( 'sku', $this->searchIn ) ) {
						$search .= $wpdb->prepare( " OR (dgwt_wcasmsku.meta_key='_sku' AND dgwt_wcasmsku.meta_value LIKE %s)", $like );
					}

					$search = apply_filters( 'dgwt/wcas/native/search_query/search_or', $search, $like, $this );

					$search .= ')';

					$searchand = ' AND ';
				}
			}

			if ( ! empty( $search ) ) {
				$search = " AND ({$search}) ";
				if ( ! is_user_logged_in() ) {
					$search .= " AND ($wpdb->posts.post_password = '') ";
				}
			}
		}

		return $search;
	}

	/**
	 * @param $where
	 *
	 * @return string
	 */
	public function searchDistinct( $where ) {
		if ( $this->isAjaxSearch() ) {
			return 'DISTINCT';
		}

		return $where;
	}

	/**
	 * Join the postmeta column in the search posts SQL
	 */

	public function searchFiltersJoin( $join, $query ) {
		global $wpdb;

		if ( empty( $query->query_vars['post_type'] ) || $query->query_vars['post_type'] !== 'product' ) {
			return $join; // skip processing
		}

		if ( $this->isAjaxSearch() ) {

			if ( DGWT_WCAS()->settings->getOption( 'search_in_product_sku' ) === 'on' && in_array( 'sku', $this->searchIn ) ) {
				$join .= " INNER JOIN $wpdb->postmeta AS dgwt_wcasmsku ON ( $wpdb->posts.ID = dgwt_wcasmsku.post_id )";
			}

			$join = apply_filters( 'dgwt/wcas/native/search_query/join', $join );
		}

		return $join;
	}

	/**
	 * Corrects the search by excerpt if necessary.
	 * WooCommerce adds search in excerpt by defaults and this should be corrected.
	 *
	 * @param string $where
	 *
	 * @return string
	 * @since 1.1.4
	 */
	public function fixWooExcerptSearch( $where ) {
		global $wp_the_query;

		// If this is not a WC Query, do not modify the query
		if ( empty( $wp_the_query->query_vars['wc_query'] ) || empty( $wp_the_query->query_vars['s'] ) ) {
			return $where;
		}

		if ( DGWT_WCAS()->settings->getOption( 'search_in_product_excerpt' ) !== 'on' && in_array( 'excerpt', $this->searchIn ) ) {

			$where = preg_replace(
				"/OR \(post_excerpt\s+LIKE\s*(\'\%[^\%]+\%\')\)/",
				'',
				$where
			);
		}

		return $where;
	}

	/**
	 * Disable cache results and narrowing search results to those from our engine
	 *
	 * @param \WP_Query $query
	 */
	public function overwriteSearchPage( $query ) {

		if ( ! Helpers::isSearchQuery( $query ) ) {
			return;
		}

		if ( $this->hooked ) {
			return;
		}

		/**
		 * Allowing hook WP_Query more then once
		 *
		 * @since 1.26.0
		 */
		if ( apply_filters( 'dgwt/wcas/native/hook_query_once', true ) ) {
			$this->hooked = true;
		}

		/**
		 * Disable cache: `cache_results` defaults to false but can be enabled
		 */
		$query->set( 'cache_results', false );
		if ( ! empty( $query->query['cache_results'] ) ) {
			$query->set( 'cache_results', true );
		}

		$query->set( 'dgwt_wcas', $query->query_vars['s'] );

		$phrase = $query->query_vars['s'];

		// If phrase is URL encoded, decode it.
		if ( preg_match( '/%[0-9A-Fa-f]{2}/', $phrase ) ) {
			$phrase = urldecode( $phrase );
		}

		// Break early if keyword contains blacklisted phrase.
		if ( Helpers::phraseContainsBlacklistedTerm( $phrase ) ) {
			header( 'X-Robots-Tag: noindex' );
			http_response_code( 400 );
			exit();
		}

		$orderby = 'post__in';
		$order   = 'desc';

		if ( ! empty( $query->query_vars['orderby'] ) ) {
			$orderby = $query->query_vars['orderby'] === 'relevance' ? 'post__in' : $query->query_vars['orderby'];
		}

		if ( ! empty( $query->query_vars['order'] ) ) {
			$order = strtolower( $query->query_vars['order'] );
		}

		$postIn        = [];
		$searchResults = $this->getSearchResults( $phrase, true, 'product-ids' );
		foreach ( $searchResults['suggestions'] as $suggestion ) {
			$postIn[] = $suggestion->ID;
		}

		// Integration with FiboFilters.
		if ( $query->get( 'fibofilters' ) ) {
			$postIn = array_intersect( $query->get( 'post__in' ), $postIn );
		}

		// Save for later use
		$this->postsIDsBuffer = $postIn;

		$query->set( 'orderby', $orderby );
		$query->set( 'order', $order );
		$query->set( 'post__in', $postIn );

		// Resetting the key 's' to disable the default search logic.
		$query->set( 's', '' );
	}

	/**
	 * Check if is ajax search processing
	 *
	 * @return bool
	 * @since 1.1.3
	 */
	public function isAjaxSearch() {
		if ( defined( 'DGWT_WCAS_AJAX' ) && DGWT_WCAS_AJAX ) {
			return true;
		}

		return false;
	}

	/**
	 * Headline output structure
	 *
	 * @return array
	 */
	public function headlineBody( $headline ) {
		return [
			'value' => $headline,
			'type'  => 'headline',
		];
	}

	/**
	 * Check if the query retuns resutls
	 *
	 * @return bool
	 */
	public function hasResults() {
		$hasResults = false;

		foreach ( $this->groups as $group ) {
			if ( ! empty( $group['results'] ) ) {
				$hasResults = true;
				break;
			}
		}

		return $hasResults;
	}

	/**
	 * Calc free slots
	 *
	 * @return int
	 */
	public function calcFreeSlots() {
		$slots = 0;

		foreach ( $this->groups as $key => $group ) {
			if ( ! empty( $group['limit'] ) ) {
				$slots = $slots + absint( $group['limit'] );
			}
		}

		return $slots;
	}

	/**
	 * Apply flexible limits
	 *
	 * @return void
	 */
	public function applyFlexibleLimits() {

		$slots  = $this->totalLimit;
		$total  = 0;
		$groups = 0;

		foreach ( $this->groups as $key => $group ) {
			if ( ! empty( $this->groups[ $key ]['results'] ) ) {
				$total = $total + count( $this->groups[ $key ]['results'] );
				$groups ++;
			}
		}

		$toRemove = $total >= $slots ? $total - $slots : 0;

		if ( $toRemove > 0 ) {
			for ( $i = 0; $i < $toRemove; $i ++ ) {

				$largestGroupCount = 0;
				$largestGroupKey   = 'product';

				foreach ( $this->groups as $key => $group ) {
					if ( ! empty( $this->groups[ $key ]['results'] ) ) {

						$thisGroupTotal = count( $this->groups[ $key ]['results'] );
						if ( $thisGroupTotal > $largestGroupCount ) {
							$largestGroupCount = $thisGroupTotal;
							$largestGroupKey   = $key;
						}
					}
				}

				$last = count( $this->groups[ $largestGroupKey ]['results'] ) - 1;
				if ( isset( $this->groups[ $largestGroupKey ]['results'][ $last ] ) ) {
					unset( $this->groups[ $largestGroupKey ]['results'][ $last ] );
				}
			}
		}

	}

	/**
	 * Prepare suggestions based on groups
	 *
	 * @return array
	 */
	public function convertGroupsToSuggestions() {
		$suggestions = [];

		$totalHeadlines = 0;

		foreach ( $this->groups as $key => $group ) {

			if ( ! empty( $group['results'] ) ) {

				if ( $this->showHeadings ) {
					$suggestions[] = $this->headlineBody( $key );
					$totalHeadlines ++;
				}

				foreach ( $group['results'] as $result ) {
					$suggestions[] = $result;
				}
			}
		}

		// Remove products headline when there are only product type suggestion
		$alwaysShowProductHeadline = apply_filters( 'dgwt/wcas/search_results/always_show_product_headline', false );

		if ( ! $alwaysShowProductHeadline && $totalHeadlines === 1 ) {
			$i     = 0;
			$unset = false;
			foreach ( $suggestions as $key => $suggestion ) {
				if (
					! empty( $suggestion['type'] )
					&& $suggestion['type'] === 'headline'
					&& $suggestion['value'] === 'product'
				) {
					unset( $suggestions[ $i ] );
					$unset = true;
					break;
				}

				$i ++;
			}

			if ( $unset ) {
				$suggestions = array_values( $suggestions );
			}
		}

		return $suggestions;
	}

	/**
	 * Order of the search resutls groups
	 *
	 * @return array
	 */
	public function searchResultsGroups() {
		$groups = [];

		if ( DGWT_WCAS()->settings->getOption( 'show_product_tax_product_cat' ) === 'on' ) {
			$groups['tax_product_cat'] = [
				'limit' => 3,
			];
		}

		if ( DGWT_WCAS()->settings->getOption( 'show_product_tax_product_tag' ) === 'on' ) {
			$groups['tax_product_tag'] = [
				'limit' => 3,
			];
		}

		$groups['product'] = [
			'limit' => 7,
		];

		return apply_filters( 'dgwt/wcas/search_groups', $groups );
	}

	/**
	 * Allow to get the ID of products that have been found
	 *
	 * @param integer[] $postsIDs
	 *
	 * @return mixed
	 */
	public function getProductIds( $postsIDs ) {
		if ( $this->postsIDsBuffer !== null ) {
			return $this->postsIDsBuffer;
		}

		return $postsIDs;
	}

	/**
	 * Add taxonomies labels
	 *
	 * @param array $labels Labels used at frontend
	 *
	 * @return array
	 */
	public function setTaxonomiesLabels( $labels ) {
		$labels['tax_product_cat_plu'] = __( 'Categories', 'woocommerce' );
		$labels['tax_product_cat']     = __( 'Category', 'woocommerce' );
		$labels['tax_product_tag_plu'] = __( 'Tags' );
		$labels['tax_product_tag']     = __( 'Tag' );

		return $labels;
	}

	/**
	 * Backward compatibility for labels
	 *
	 * Full taxonomy names for categories and tags. All with prefix 'tax_'.
	 *
	 * @param array $labels Labels used at frontend
	 *
	 * @return array
	 */
	public function fixTaxonomiesLabels( $labels ) {
		// Product category. Old: 'category', 'product_cat_plu'.
		if ( isset( $labels['category'] ) ) {
			$labels['tax_product_cat'] = $labels['category'];
			unset( $labels['category'] );
		}
		if ( isset( $labels['product_cat_plu'] ) ) {
			$labels['tax_product_cat_plu'] = $labels['product_cat_plu'];
			unset( $labels['product_cat_plu'] );
		}

		// Product tag. Old: 'tag', 'product_tag_plu'.
		if ( isset( $labels['tag'] ) ) {
			$labels['tax_product_tag'] = $labels['tag'];
			unset( $labels['tag'] );
		}
		if ( isset( $labels['product_tag_plu'] ) ) {
			$labels['tax_product_tag_plu'] = $labels['product_tag_plu'];
			unset( $labels['product_tag_plu'] );
		}

		return $labels;
	}

	/**
	 * Add language to WC endpoint if Polylang is active
	 *
	 * @param $url
	 * @param $request
	 *
	 * @return string
	 * @see polylang-wc/frontend/frontend.php:306
	 */
	public function fixPolylangWooEndpoint( $url, $request ) {
		if ( PLL() instanceof \PLL_Frontend ) {
			// Remove wc-ajax to avoid the value %%endpoint%% to be encoded by add_query_arg (used in plain permalinks).
			$url = remove_query_arg( 'wc-ajax', $url );
			$url = PLL()->links_model->switch_language_in_link( $url, PLL()->curlang );

			return add_query_arg( 'wc-ajax', $request, $url );
		}

		return $url;
	}

	/**
	 * Get empty search output
	 *
	 * @return array
	 */
	private function getEmptyOutput() {
		$output = [
			'engine'      => 'free',
			'suggestions' => [],
			'time'        => '0 sec',
			'total'       => 0,
			'v'           => DGWT_WCAS_VERSION,
		];

		return $output;
	}
}
