<?php
/**
 * Instant Search feature
 *
 * @package elasticpress
 */

namespace ElasticPress\Feature\InstantResults;

use ElasticPress\Elasticsearch;
use ElasticPress\ElasticPressIoTemplateManager;
use ElasticPress\Feature;
use ElasticPress\FeatureRequirementsStatus;
use ElasticPress\Features;
use ElasticPress\Indexables;
use ElasticPress\Utils;

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

/**
 * Instant Results feature class.
 *
 * @since 4.0.0
 */
class InstantResults extends Feature {

	use ElasticPressIoTemplateManager;

	/**
	 * Elasticsearch index name.
	 *
	 * @var string
	 */
	protected $index;

	/**
	 * Host URL.
	 *
	 * @var string
	 */
	protected $host;

	/**
	 * WooCommerce is in use.
	 *
	 * @var boolean
	 */
	protected $is_woocommerce;

	/**
	 * Elasticsearch query template.
	 *
	 * @var string
	 */
	protected $search_template = '';

	/**
	 * Feature settings
	 *
	 * @var array
	 */
	protected $settings = [];

	/**
	 * Initialize feature.
	 *
	 * @return void
	 */
	public function __construct() {
		$this->slug = 'instant-results';

		$this->group = 'live-search';

		$this->host = trailingslashit( Utils\get_host() );

		$this->index = Indexables::factory()->get( 'post' )->get_index_name();

		$this->is_woocommerce = function_exists( 'WC' );

		$this->default_settings = [
			'highlight_tag'   => 'mark',
			'facets'          => 'post_type,tax-category,tax-post_tag',
			'match_type'      => 'all',
			'term_count'      => '1',
			'per_page'        => get_option( 'posts_per_page', 6 ),
			'search_behavior' => '0',
		];

		$this->settings = $this->get_settings();

		$this->requires_install_reindex = true;

		$this->available_during_installation = true;

		$this->is_powered_by_epio = Utils\is_epio();

		parent::__construct();
	}

	/**
	 * Sets i18n strings.
	 *
	 * @return void
	 * @since 5.2.0
	 */
	public function set_i18n_strings(): void {
		$this->title = esc_html__( 'Instant Results', 'elasticpress' );

		$this->short_title = esc_html__( 'Instant Results', 'elasticpress' );

		$this->summary = '<p>' . __( 'WordPress search forms will display results instantly. When the search query is submitted, a modal will open that populates results by querying ElasticPress directly, bypassing WordPress. As the user refines their search, results are refreshed.', 'elasticpress' ) . '</p>' .
		'<p>' . __( 'Requires an <a href="https://www.elasticpress.io/" target="_blank">ElasticPress.io plan</a> or a custom proxy to function.', 'elasticpress' ) . '</p>';

		$this->docs_url = __( 'https://www.elasticpress.io/documentation/article/configuring-elasticpress-via-the-plugin-dashboard/#instant-results', 'elasticpress' );
	}

	/**
	 * Tell user whether requirements for feature are met or not.
	 *
	 * @return array $status Status array
	 */
	public function requirements_status() {
		$status = new FeatureRequirementsStatus( 2 );

		$status->message = [];

		if ( Utils\is_epio() ) {
			$status->code = 1;

			/**
			 * Whether the feature is available for non ElasticPress.io customers.
			 *
			 * Installations using self-hosted Elasticsearch will need to implement an API for
			 * handling search requests before making the feature available.
			 *
			 * @since 4.0.0
			 * @hook ep_instant_results_available
			 * @param {string} $available Whether the feature is available.
			 */
		} elseif ( apply_filters( 'ep_instant_results_available', false ) ) {
			$status->code      = 1;
			$status->message[] = esc_html__( 'You are using a custom proxy. Make sure you implement all security measures needed.', 'elasticpress' );
		} else {
			$status->message[] = wp_kses_post( __( "To use this feature you need to be an <a href='https://elasticpress.io'>ElasticPress.io</a> customer or implement a <a href='https://github.com/10up/elasticpress-proxy'>custom proxy</a>.", 'elasticpress' ) );
		}

		/**
		 * Display a warning if ElasticPress is network activated.
		 */
		if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
			$status->message[] = wp_kses_post(
				sprintf(
					/* translators: Article URL */
					__(
						'ElasticPress is network activated. Additional steps are required to ensure Instant Results works for all sites on the network. See our article on <a href="%s" target="_blank">running ElasticPress in network mode</a> for more details.',
						'elasticpress'
					),
					'https://www.elasticpress.io/documentation/article/running-elasticpress-in-a-wordpress-multisite-network-mode/'
				)
			);
		}

		return $status;
	}

	/**
	 * Setup feature functionality.
	 *
	 * @return void
	 */
	public function setup() {
		add_filter( 'ep_after_update_feature', [ $this, 'after_update_feature' ], 10, 3 );
		add_filter( 'ep_formatted_args', [ $this, 'maybe_apply_aggs_args' ], 10, 3 );
		add_filter( 'ep_post_mapping', [ $this, 'add_mapping_properties' ] );
		add_filter( 'ep_post_sync_args', [ $this, 'add_post_sync_args' ], 10, 2 );
		add_filter( 'ep_after_sync_index', [ $this, 'epio_save_search_template' ] );
		add_filter( 'ep_saved_weighting_configuration', [ $this, 'epio_save_search_template' ] );
		add_action( 'pre_get_posts', [ $this, 'maybe_apply_product_visibility' ] );
		add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_frontend_assets' ] );
		add_action( 'wp_footer', [ $this, 'render' ] );
	}

	/**
	 * Output modal markup.
	 */
	public function render() {
		echo '<div id="ep-instant-results"></div>';
	}

	/**
	 * Enqueue our autosuggest script.
	 */
	public function enqueue_frontend_assets() {
		if ( Utils\is_indexing() ) {
			return;
		}

		wp_enqueue_style(
			'elasticpress-instant-results',
			EP_URL . 'dist/css/instant-results-styles.css',
			Utils\get_asset_info( 'instant-results-styles', 'dependencies' ),
			Utils\get_asset_info( 'instant-results-styles', 'version' )
		);

		wp_enqueue_script(
			'elasticpress-instant-results',
			EP_URL . 'dist/js/instant-results-script.js',
			Utils\get_asset_info( 'instant-results-script', 'dependencies' ),
			Utils\get_asset_info( 'instant-results-script', 'version' ),
			true
		);

		wp_set_script_translations( 'elasticpress-instant-results', 'elasticpress' );

		/**
		 * The search API endpoint.
		 *
		 * @since 4.0.0
		 * @hook ep_instant_results_search_endpoint
		 * @param {string} $endpoint Endpoint path.
		 * @param {string} $index Elasticsearch index.
		 */
		$api_endpoint = apply_filters( 'ep_instant_results_search_endpoint', "api/v1/search/posts/{$this->index}", $this->index );

		wp_localize_script(
			'elasticpress-instant-results',
			'epInstantResults',
			array(
				'apiEndpoint'         => $api_endpoint,
				'apiHost'             => ( 0 !== strpos( $api_endpoint, 'http' ) ) ? esc_url_raw( $this->host ) : '',
				'argsSchema'          => $this->get_args_schema(),
				'currencyCode'        => $this->is_woocommerce ? get_woocommerce_currency() : false,
				'facets'              => $this->get_facets_for_frontend(),
				'highlightTag'        => $this->settings['highlight_tag'],
				'isWooCommerce'       => $this->is_woocommerce,
				'locale'              => str_replace( '_', '-', get_locale() ),
				'matchType'           => $this->settings['match_type'],
				'paramPrefix'         => 'ep-',
				'postTypeLabels'      => $this->get_post_type_labels(),
				'termCount'           => $this->settings['term_count'],
				'requestIdBase'       => Utils\get_request_id_base(),
				'showSuggestions'     => \ElasticPress\Features::factory()->get_registered_feature( 'did-you-mean' )->is_active(),
				'suggestionsBehavior' => $this->settings['search_behavior'],
			)
		);
	}

	/**
	 * Get the endpoint for the Instant Results search template.
	 *
	 * @return string Instant Results search template endpoint.
	 */
	public function get_template_endpoint(): string {
		/**
		 * Filters the search template API endpoint.
		 *
		 * @since 4.0.0
		 * @hook ep_instant_results_template_endpoint
		 * @param {string} $endpoint Endpoint path.
		 * @param {string} $index Elasticsearch index.
		 * @returns {string} Search template API endpoint.
		 */
		return apply_filters( 'ep_instant_results_template_endpoint', "api/v1/search/posts/{$this->index}/template/", $this->index );
	}

	/**
	 * Generate a search template.
	 *
	 * A search template is the JSON for an Elasticsearch query with a
	 * placeholder search term. The template is sent to ElasticPress.io where
	 * it's used to make Elasticsearch queries using search terms sent from
	 * the front end.
	 *
	 * @return string The search template as JSON.
	 */
	public function get_search_template(): string {
		$post_types    = Features::factory()->get_registered_feature( 'search' )->get_searchable_post_types();
		$post_statuses = get_post_stati(
			[
				'public'              => true,
				'exclude_from_search' => false,
			]
		);

		/**
		 * The ID of the current user when generating the Instant Results
		 * search template.
		 *
		 * By default Instant Results sets the current user as anomnymous when
		 * generating the search template, so that any filters applied to
		 * queries for logged-in or specific users are not applied to the
		 * template. This filter supports setting a specific user as the
		 * current user while the template is generated.
		 *
		 * @since 4.1.0
		 * @hook ep_search_template_user_id
		 * @param {int} $user_id User ID to use.
		 * @return {int} New user ID to use.
		 */
		$template_user_id = apply_filters( 'ep_search_template_user_id', 0 );
		$original_user_id = get_current_user_id();

		wp_set_current_user( $template_user_id );

		add_filter( 'ep_do_intercept_request', [ $this, 'intercept_search_request' ], 10, 4 );
		add_filter( 'ep_is_integrated_request', [ $this, 'is_integrated_request' ], 10, 2 );

		$query = new \WP_Query(
			array(
				'ep_integrate'             => true,
				'ep_search_template'       => true,
				'post_status'              => array_values( $post_statuses ),
				'post_type'                => $post_types,
				's'                        => '{{ep_placeholder}}',
				'ep_intercept_request'     => true,
				'ep_skip_search_exclusion' => true,
			)
		);

		remove_filter( 'ep_do_intercept_request', [ $this, 'intercept_search_request' ], 10 );
		remove_filter( 'ep_is_integrated_request', [ $this, 'is_integrated_request' ], 10 );

		wp_set_current_user( $original_user_id );

		return $this->search_template;
	}

	/**
	 * Return true if a given feature is supported by Instant Results.
	 *
	 * Applied as a filter on Utils\is_integrated_request() so that features
	 * are enabled for the query that is used to generate the search template,
	 * regardless of the request type. This avoids the need to send a request
	 * to the front end.
	 *
	 * @param bool   $is_integrated Whether queries for the request will be
	 *                              integrated.
	 * @param string $context       Context for the original check. Usually the
	 *                              slug of the feature doing the check.
	 * @return bool True if the check is for a feature supported by instant
	 *              search.
	 */
	public function is_integrated_request( $is_integrated, $context ) {
		$supported_contexts = [
			'autosuggest',
			'documents',
			'search',
			'weighting',
			'woocommerce',
		];

		return in_array( $context, $supported_contexts, true );
	}

	/**
	 * Store intercepted request body and return request result.
	 *
	 * @param object $response Response
	 * @param array  $query Query
	 * @param array  $args WP_Query argument array
	 * @param int    $failures Count of failures in request loop
	 * @return object $response Response
	 */
	public function intercept_search_request( $response, $query = [], $args = [], $failures = 0 ) {
		$this->search_template = $query['args']['body'];

		return wp_remote_request( $query['url'], $args );
	}

	/**
	 * If generating the search template query, do not bypass the post exclusion
	 *
	 * @since 4.4.0
	 * @param bool     $bypass_exclusion_from_search Whether the post exclusion from search should be applied or not
	 * @param WP_Query $query The WP Query
	 * @return bool
	 */
	public function maybe_bypass_post_exclusion( $bypass_exclusion_from_search, $query ) {
		_doing_it_wrong(
			__METHOD__,
			esc_html__( 'Use the WP_Query argument `ep_skip_search_exclusion`.', 'elasticpress' ),
			'ElasticPress 5.3.0'
		);

		return true === $query->get( 'ep_search_template' ) ?
			false : // not bypass, apply
			$bypass_exclusion_from_search;
	}

	/**
	 * Apply product visibility taxonomy query to search template queries.
	 *
	 * @param \WP_Query $query Query instance.
	 * @return void
	 */
	public function maybe_apply_product_visibility( $query ) {
		if ( true !== $query->get( 'ep_search_template' ) ) {
			return;
		}

		if ( ! $this->is_woocommerce ) {
			return;
		}

		$this->apply_product_visibility( $query );
	}

	/**
	 * Apply product visibility taxonomy query.
	 *
	 * Applies filters to exclude products set to be excluded from search. Out
	 * of stock products will also be excluded if WooCommerce is configured to
	 * hide those products.
	 *
	 * Mimics the logic of WC_Query::get_tax_query().
	 *
	 * @param \WP_Query $query Query instance.
	 * @return void
	 */
	public function apply_product_visibility( $query ) {
		$product_visibility_terms  = wc_get_product_visibility_term_ids();
		$product_visibility_not_in = (array) $product_visibility_terms['exclude-from-search'];

		if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
			$product_visibility_not_in[] = $product_visibility_terms['outofstock'];
		}

		if ( ! empty( $product_visibility_not_in ) ) {
			$tax_query = $query->get( 'tax_query', array() );

			$tax_query[] = array(
				'taxonomy' => 'product_visibility',
				'field'    => 'term_taxonomy_id',
				'terms'    => $product_visibility_not_in,
				'operator' => 'NOT IN',
			);

			$query->set( 'tax_query', $tax_query );
		}
	}

	/**
	 * Apply aggregation args to search templates.
	 *
	 * @param array     $formatted_args Formatted Elasticsearch query.
	 * @param array     $query_vars Query variables
	 * @param \WP_Query $query Query instance.
	 * @return array Formatted Elasticsearch query.
	 */
	public function maybe_apply_aggs_args( $formatted_args, $query_vars, $query ) {
		if ( true !== $query->get( 'ep_search_template' ) ) {
			return $formatted_args;
		}

		return $this->apply_aggs_args( $formatted_args );
	}

	/**
	 * Add aggregation args to Elasticsearch query for facets.
	 *
	 * @param array $formatted_args Formatted Elasticsearch query.
	 * @return array Formatted Elasticsearch query.
	 */
	public function apply_aggs_args( $formatted_args ) {
		$filter = $formatted_args['post_filter'];
		$facets = $this->get_facets();

		foreach ( $facets as $key => $facet ) {
			$formatted_args['aggs'][ $key ]['aggs'] = $facet['aggs'];

			if ( $filter ) {
				$formatted_args['aggs'][ $key ]['filter'] = $filter;
			}
		}

		return $formatted_args;
	}

	/**
	 * Add additional fields to post mapping.
	 *
	 * @param array $mapping Post mapping.
	 * @return array Post mapping.
	 */
	public function add_mapping_properties( $mapping ) {
		$elasticsearch_version = Elasticsearch::factory()->get_elasticsearch_version();

		$properties = array(
			'post_content_plain' => array( 'type' => 'text' ),
			'price_html'         => array( 'type' => 'text' ),
		);

		if ( version_compare( (string) $elasticsearch_version, '7.0', '<' ) ) {
			$mapping['mappings']['post']['properties'] = array_merge(
				$mapping['mappings']['post']['properties'],
				$properties
			);
		} else {
			$mapping['mappings']['properties'] = array_merge(
				$mapping['mappings']['properties'],
				$properties
			);
		}

		return $mapping;
	}

	/**
	 * Add data for additional mapping properties.
	 *
	 * @param array   $post_args Post arguments.
	 * @param integer $post_id   Post ID.
	 * @return array Post sync args.
	 */
	public function add_post_sync_args( $post_args, $post_id ) {
		$post = get_post( $post_id );

		$post_args['post_content_plain'] = $this->prepare_plain_content_arg( $post );
		$post_args['price_html']         = $this->prepare_price_html_arg( $post );

		return $post_args;
	}


	/**
	 * Get data for the plain post content.
	 *
	 * @param WP_Post $post Post object.
	 * @return string Post content.
	 */
	public function prepare_plain_content_arg( $post ) {
		$post_content = apply_filters( 'the_content', $post->post_content );

		return wp_strip_all_tags( $post_content );
	}

	/**
	 * Get data for the price HTML arg.
	 *
	 * @param WP_Post $post Post object.
	 * @return string|null Price HTML.
	 */
	public function prepare_price_html_arg( $post ) {
		if ( 'product' !== $post->post_type ) {
			return null;
		}

		if ( ! $this->is_woocommerce ) {
			return null;
		}

		$product = wc_get_product( $post );

		return $product->get_price_html();
	}

	/**
	 * Get post type labels.
	 *
	 * Only the post type slug is indexed, so we'll need the labels on the
	 * front end for display.
	 *
	 * @return array Array of post types and their labels.
	 */
	public function get_post_type_labels() {
		$labels = [];

		$post_types = Features::factory()->get_registered_feature( 'search' )->get_searchable_post_types();

		foreach ( $post_types as $post_type ) {
			$post_type_object = get_post_type_object( $post_type );
			$post_type_labels = get_post_type_labels( $post_type_object );

			$labels[ $post_type ] = array(
				'plural'   => $post_type_labels->name,
				'singular' => $post_type_labels->singular_name,
			);
		}

		return $labels;
	}

	/**
	 * Get available facets.
	 *
	 * @return array Available facets.
	 */
	public function get_facets() {
		$facets = [];

		/**
		 * Post type facet.
		 */
		$facets['post_type'] = array(
			'type'       => 'post_type',
			'post_types' => [],
			'labels'     => array(
				'admin'    => __( 'Post type', 'elasticpress' ),
				'frontend' => __( 'Type', 'elasticpress' ),
			),
			'aggs'       => array(
				'post_type' => array(
					'terms' => array(
						'field' => 'post_type.raw',
					),
				),
			),
			/**
			 * The post_type arg needs to be supported regardless of whether
			 * the Post Type facet is present to be able to support setting the
			 * post type from the search form.
			 *
			 * @see ElasticPress\Feature\InstantResults::get_args_schema()
			 */
			'args'       => array(),
		);

		/**
		 * Taxonomy facets.
		 */
		$taxonomies = get_taxonomies( array( 'public' => true ), 'object' );
		$taxonomies = apply_filters( 'ep_facet_include_taxonomies', $taxonomies );

		foreach ( $taxonomies as $slug => $taxonomy ) {
			$name   = 'tax-' . $slug;
			$labels = get_taxonomy_labels( $taxonomy );

			$admin_label = sprintf(
				/* translators: $1$s: Taxonomy name. %2$s: Taxonomy slug. */
				esc_html__( '%1$s (%2$s)' ),
				$labels->singular_name,
				$slug
			);

			$post_types = Features::factory()->get_registered_feature( 'search' )->get_searchable_post_types();
			$post_types = array_intersect( $post_types, $taxonomy->object_type );
			$post_types = array_values( $post_types );

			$facets[ $name ] = array(
				'type'       => 'taxonomy',
				'post_types' => $post_types,
				'labels'     => array(
					'admin'    => $admin_label,
					'frontend' => $labels->singular_name,
				),
				'aggs'       => array(
					$name => array(
						'terms' => array(
							'field' => 'terms.' . $slug . '.facet',
							'size'  => apply_filters( 'ep_facet_taxonomies_size', 10000, $taxonomy ),
						),
					),
				),
				'args'       => array(
					$name => array(
						'type' => 'strings',
					),
				),
			);
		}

		/**
		 * Price facet.
		 */
		if ( $this->is_woocommerce ) {
			$facets['price_range'] = array(
				'type'       => 'price_range',
				'post_types' => [ 'product' ],
				'labels'     => array(
					'admin'    => __( 'Price range', 'elasticpress' ),
					'frontend' => __( 'Price', 'elasticpress' ),
				),
				'aggs'       => array(
					'max_price' => array(
						'max' => array(
							'field' => 'meta._price.double',
						),
					),
					'min_price' => array(
						'min' => array(
							'field' => 'meta._price.double',
						),
					),
				),
				'args'       => array(
					'max_price' => array(
						'type' => 'number',
					),
					'min_price' => array(
						'type' => 'number',
					),
				),
			);
		}

		return $facets;
	}

	/**
	 * Get facet configuration for the front end.
	 *
	 * @return Array Facet configuration for the front end.
	 */
	public function get_facets_for_frontend() {
		$selected_facets  = explode( ',', $this->settings['facets'] );
		$available_facets = $this->get_facets();

		$facets = [];

		foreach ( $selected_facets as $key ) {
			if ( isset( $available_facets[ $key ] ) ) {
				$facet = $available_facets[ $key ];

				$facets[] = array(
					'name'      => $key,
					'label'     => $facet['labels']['frontend'],
					'type'      => $facet['type'],
					'postTypes' => $facet['post_types'],
				);
			}
		}

		return $facets;
	}

	/**
	 * Get facet configuration for the admin.
	 *
	 * @return Array Facet configuration for the admin.
	 */
	public function get_facets_for_admin() {
		$available_facets = $this->get_facets();

		$facets = [];

		foreach ( $available_facets as $key => $facet ) {
			$facets[ $key ] = array(
				'label' => $facet['labels']['admin'],
				'value' => $key,
			);
		}

		return $facets;
	}

	/**
	 * Get schema for search args.
	 *
	 * @return array Search args schema.
	 */
	public function get_args_schema() {
		/**
		 * The number of results per page for Instant Results.
		 *
		 * @since 4.5.0
		 * @hook ep_instant_results_per_page
		 * @param {int} $per_page Results per page.
		 */
		$per_page = apply_filters( 'ep_instant_results_per_page', $this->settings['per_page'] );

		$args_schema = array(
			'highlight' => array(
				'type'          => 'string',
				'default'       => $this->settings['highlight_tag'],
				'allowedValues' => [ $this->settings['highlight_tag'] ],
			),
			'offset'    => array(
				'type'    => 'number',
				'default' => 0,
			),
			'orderby'   => array(
				'type'          => 'string',
				'default'       => 'relevance',
				'allowedValues' => [ 'date', 'price', 'relevance' ],
			),
			'order'     => array(
				'type'          => 'string',
				'default'       => 'desc',
				'allowedValues' => [ 'asc', 'desc' ],
			),
			'per_page'  => array(
				'type'    => 'number',
				'default' => absint( $per_page ),
			),
			'post_type' => array(
				'type' => 'strings',
			),
			'search'    => array(
				'type'    => 'string',
				'default' => '',
			),
			'relation'  => array(
				'type'          => 'string',
				'default'       => 'all' === $this->settings['match_type'] ? 'and' : 'or',
				'allowedValues' => [ 'and', 'or' ],
			),
		);

		$selected_facets  = explode( ',', $this->settings['facets'] );
		$available_facets = $this->get_facets();

		foreach ( $selected_facets as $key ) {
			if ( isset( $available_facets[ $key ] ) ) {
				$args_schema = array_merge( $args_schema, $available_facets[ $key ]['args'] );
			}
		}

		/**
		 * The schema defining the API arguments used by Instant Results.
		 *
		 * The argument schema is used to configure the APISearchProvider
		 * component used by Instant Results, and should conform to what is
		 * supported by the API being used. The Instant Results UI expects
		 * the default list of arguments to be available, so caution is advised
		 * when adding or removing arguments.
		 *
		 * @since 4.5.1
		 * @hook ep_instant_results_args_schema
		 * @param {array} $args_schema Results per page.
		 */
		return apply_filters( 'ep_instant_results_args_schema', $args_schema );
	}

	/**
	 * Set the `settings_schema` attribute
	 *
	 * @since 5.0.0
	 */
	protected function set_settings_schema() {
		$facets = $this->get_facets_for_admin();

		$this->settings_schema = [
			[
				'default' => 'mark',
				'help'    => __( 'Select the HTML tag used to highlight search terms.', 'elasticpress' ),
				'key'     => 'highlight_tag',
				'label'   => __( 'Highlight tag', 'elasticpress' ),
				'options' => [
					[
						'label' => __( 'None', 'elasticpress' ),
						'value' => '',
					],
					[
						'label' => 'mark',
						'value' => 'mark',
					],
					[
						'label' => 'span',
						'value' => 'span',
					],
					[
						'label' => 'strong',
						'value' => 'strong',
					],
					[
						'label' => 'em',
						'value' => 'em',
					],
					[
						'label' => 'i',
						'value' => 'i',
					],
				],
				'type'    => 'select',
			],
			[
				'default' => 'post_type,tax-category,tax-post_tag',
				'key'     => 'facets',
				'label'   => __( 'Filters', 'elasticpress' ),
				'options' => array_values( $facets ),
				'type'    => 'multiple',
			],
			[
				'default' => 'all',
				'key'     => 'match_type',
				'label'   => __( 'Filter matching', 'elasticpress' ),
				'options' => [
					[
						'label' => __( 'Show results that match <strong>all</strong> selected filters', 'elasticpress' ),
						'value' => 'all',
					],
					[
						'label' => __( 'Show results that match <strong>any</strong> selected filter', 'elasticpress' ),
						'value' => 'any',
					],
				],
				'type'    => 'radio',
			],
			[
				'default' => '1',
				'help'    => __( 'Enable to show the number of matching results next to filter options.', 'elasticpress' ),
				'key'     => 'term_count',
				'label'   => __( 'Show filter counts', 'elasticpress' ),
				'type'    => 'checkbox',
			],
			[
				'default' => get_option( 'posts_per_page', 6 ),
				'key'     => 'per_page',
				'type'    => 'hidden',
			],
			[
				'default'          => '0',
				'key'              => 'search_behavior',
				'label'            => __( 'Search behavior when no result is found', 'elasticpress' ),
				'options'          => [
					[
						'label' => __( 'Display the top suggestion', 'elasticpress' ),
						'value' => '0',
					],
					[
						'label' => __( 'Display all the suggestions', 'elasticpress' ),
						'value' => 'list',
					],
				],
				'requires_feature' => 'did-you-mean',
				'type'             => 'radio',
			],
		];
	}
}
