<?php
namespace SabaiApps\Directories\Component\Search\Field;

use SabaiApps\Directories\Component\Search\SearchComponent;
use SabaiApps\Directories\Request;
use SabaiApps\Directories\Component\Entity\Model\Bundle;
use SabaiApps\Directories\Component\Entity\Type\Query;
use SabaiApps\Directories\Exception\IException;

class KeywordField extends AbstractField {

	protected static $_suggestUrlVersion;

	protected function _searchFieldInfo() {
		return array(
			'label'            => __( 'Keyword Search', 'directories-pro' ),
			'weight'           => 1,
			'default_settings' => array(
				'min_length'         => 2,
				'match'              => 'all',
				'child_bundle_types' => null,
				'taxonomies'         => null,
				'extra_fields'       => null,
				'suggest'            => array(
					'enable'   => true,
					'settings' => array(
						'min_length'      => 2,
						'post_num'        => 5,
						'post_num_cache'  => 100,
						'post_jump'       => true,
						'post_with_image' => false,
					),
				),
				'form'               => array(
					'icon'        => 'fas fa-magnifying-glass',
					'placeholder' => __( 'Search for...', 'directories-pro' ),
					'order'       => 1,
				),
				'exclude_title'      => false,
				'exclude_content'    => false,
			),
		);
	}

	public function searchFieldSupports( Bundle $bundle ) {
		return empty( $bundle->info['parent'] ) && empty( $bundle->info['parent_field'] );
	}

	public function searchFieldSettingsForm( Bundle $bundle, array $settings, array $parents = array() ) {
		$form       = array(
			'min_length' => array(
				'#type'          => 'slider',
				'#title'         => __( 'Min. length of keywords in characters', 'directories-pro' ),
				'#default_value' => $settings['min_length'],
				'#integer'       => true,
				'#min_value'     => 1,
				'#max_value'     => 10,
				'#horizontal'    => true,
				'#weight'        => 2,
			),
			'match'      => array(
				'#type'          => 'select',
				'#title'         => __( 'Match type', 'directories-pro' ),
				'#options'       => array(
					'any'   => __( 'Match any', 'directories-pro' ),
					'all'   => __( 'Match all', 'directories-pro' ),
					'exact' => __( 'Exact match', 'directories-pro' ),
				),
				'#default_value' => $settings['match'],
				'#horizontal'    => true,
				'#weight'        => 1,
			),
		);
		$properties = $this->_application->Entity_Types_impl( $bundle->entitytype_name )->entityTypeInfo( 'properties' );
		if ( ! empty( $properties['title'] ) ) {
			$form['exclude_title'] = array(
				'#type'          => 'checkbox',
				'#title'         => __( 'Do not search post title', 'directories-pro' ),
				'#default_value' => ! empty( $settings['exclude_title'] ),
				'#horizontal'    => true,
				'#weight'        => 3,
			);
		}
		if ( ! empty( $properties['content'] ) ) {
			$form['exclude_content'] = array(
				'#type'          => 'checkbox',
				'#title'         => __( 'Do not search post content', 'directories-pro' ),
				'#default_value' => ! empty( $settings['exclude_content'] ),
				'#horizontal'    => true,
				'#weight'        => 3,
			);
		}

		$child_bundle_types = array();
		foreach ( $this->_application->Entity_BundleTypes_children( $bundle->type ) as $child_bundle_type ) {
			try {
				$child_bundle = $this->_application->Entity_Bundle( $child_bundle_type, $bundle->group, true );
			} catch ( IException $e ) {
				continue;
			}

			$child_bundle_types[ $child_bundle_type ] = $child_bundle->getLabel();
		}
		if ( ! empty( $child_bundle_types ) ) {
			$form['child_bundle_types'] = array(
				'#type'          => 'checkboxes',
				'#title'         => __( 'Search child content items', 'directories-pro' ),
				'#options'       => $child_bundle_types,
				'#default_value' => $settings['child_bundle_types'],
				'#horizontal'    => true,
				'#weight'        => 5,
			);
		}
		if ( ! empty( $bundle->info['taxonomies'] ) ) {
			$form['taxonomies'] = array(
				'#type'          => 'checkboxes',
				'#title'         => __( 'Search taxonomy term names', 'directories-pro' ),
				'#options'       => array(),
				'#default_value' => $settings['taxonomies'],
				'#horizontal'    => true,
				'#weight'        => 6,
				'#description'   => __( 'WARNING! This could slow down the performance of search considerably when there are a large number of taxonomy terms.', 'directories-pro' ),
			);
		}

		// Add extra fields to include in search
		$extra_field_options = $this->_getSearchableFields( $bundle );
		if ( $fieldset_fields = $this->_application->Entity_Field_options( $bundle, array( 'type' => 'fieldset_fieldset' ) ) ) {
			foreach ( array_keys( $fieldset_fields ) as $fieldset_field_name ) {
				if ( ( ! $fieldset_field = $this->_application->Entity_Field( $bundle, $fieldset_field_name ) )
					|| ( ! $fieldset_field_settings = $fieldset_field->getFieldSettings() )
					|| empty( $fieldset_field_settings['bundle'] )
					|| ( ! $fieldset_bundle = $this->_application->Entity_Bundle( $fieldset_field_settings['bundle'] ) )
					|| ( ! $fieldset_extra_field_options = $this->_getSearchableFields( $fieldset_bundle, $fieldset_bundle->getLabel() . ' - ', $fieldset_field_name ) )
				) {
					continue;
				}

				$extra_field_options += $fieldset_extra_field_options;
			}
		}
		if ( ! empty( $extra_field_options ) ) {
			asort( $extra_field_options );
			$form['extra_fields'] = array(
				'#type'          => 'checkboxes',
				'#title'         => __( 'Extra fields to include in search', 'directories-pro' ),
				'#default_value' => $settings['extra_fields'],
				'#options'       => $extra_field_options,
				'#weight'        => 15,
				'#horizontal'    => true,
			);
		}

		$suggest_prefix        = $this->_application->Form_Form_fieldName( array_merge( $parents, array( 'suggest', 'enable' ) ) );
		$suggest_states        = $suggest_post_states = array(
			'visible' => array(
				'input[name="' . $suggest_prefix . '"]' => array(
					'type'  => 'checked',
					'value' => 1,
				),
			),
		);
		$suggest_post_selector = sprintf( 'input[name="%s"]', $this->_application->Form_Form_fieldName( array_merge( $parents, array( 'suggest', 'settings', 'post' ) ) ) );
		$suggest_post_states['visible'][ $suggest_post_selector ] = array( 'value' => 1 );
		$form += array(
			'suggest' => array(
				'#title'   => __( 'Auto-Suggest Settings', 'directories-pro' ),
				'#weight'  => 15,
				'#class'   => 'drts-form-label-lg',
				'enable'   => array(
					'#type'          => 'checkbox',
					'#default_value' => ! empty( $settings['suggest']['enable'] ),
					'#title'         => __( 'Enable auto suggestions', 'directories-pro' ),
					'#horizontal'    => true,
				),
				'settings' => array(
					'#states'         => $suggest_states,
					'min_length'      => array(
						'#type'          => 'slider',
						'#title'         => __( 'Minimum character length needed before triggering auto suggestions', 'directories-pro' ),
						'#default_value' => $settings['suggest']['settings']['min_length'],
						'#integer'       => true,
						'#min_value'     => 1,
						'#max_value'     => 10,
						'#states'        => $suggest_post_states,
						'#horizontal'    => true,
						'#weight'        => 1,
					),
					'post_jump'       => array(
						'#type'          => 'checkbox',
						'#default_value' => ! empty( $settings['suggest']['settings']['post_jump'] ),
						'#title'         => __( 'Redirect to suggested item page when clicked', 'directories-pro' ),
						'#states'        => $suggest_post_states,
						'#horizontal'    => true,
						'#weight'        => 2,
					),
					'post_num'        => array(
						'#type'          => 'slider',
						'#min_value'     => 1,
						'#max_value'     => 20,
						'#title'         => __( 'Number of auto suggested items to display', 'directories-pro' ),
						'#integer'       => true,
						'#default_value' => $settings['suggest']['settings']['post_num'],
						'#horizontal'    => true,
						'#states'        => $suggest_post_states,
						'#weight'        => 3,
					),
					'post_num_cache'  => array(
						'#type'          => 'slider',
						'#min_value'     => 0,
						'#max_value'     => 200,
						'#step'          => 10,
						'#title'         => __( 'Number of auto suggested items to cache', 'directories-pro' ),
						'#integer'       => true,
						'#default_value' => isset( $settings['suggest']['settings']['post_num_cache'] ) ? $settings['suggest']['settings']['post_num_cache'] : 100,
						'#horizontal'    => true,
						'#states'        => $suggest_post_states,
						'#weight'        => 4,
					),
					'post_with_image' => array(
						'#type'          => 'checkbox',
						'#default_value' => ! empty( $settings['suggest']['settings']['post_with_image'] ),
						'#title'         => __( 'Load images for uncached auto-suggested items', 'directories-pro' ),
						'#states'        => $suggest_post_states,
						'#horizontal'    => true,
						'#weight'        => 5,
					),
				),
			),
		);

		$has_taxonomy = false;
		if ( ! empty( $bundle->info['taxonomies'] ) ) {
			foreach ( $bundle->info['taxonomies'] as $taxonomy_bundle_type => $taxonomy_name ) {
				if ( ! $taxonomy_bundle = $this->_application->Entity_Bundle( $taxonomy_name ) ) {
					continue;
				}

				$form['taxonomies']['#options'][ $taxonomy_name ] = $taxonomy_bundle->getLabel();
				$taxonomy_label                                   = $taxonomy_bundle->getLabel( 'singular' );
				$is_hierarchical                                  = ! empty( $taxonomy_bundle->info['is_hierarchical'] );
				$suggest_taxonomy_states                          = $suggest_states;
				$suggest_taxonomy_selector                        = sprintf( 'input[name="%s"]', $this->_application->Form_Form_fieldName( array_merge( $parents, array( 'suggest', $taxonomy_bundle_type ) ) ) );
				$suggest_taxonomy_states['visible'][ $suggest_taxonomy_selector ] = array(
					'type'  => 'checked',
					'value' => true,
				);
				$form['suggest'] += array(
					$taxonomy_bundle_type                 => array(
						'#type'          => 'checkbox',
						'#default_value' => ! empty( $settings['suggest'][ $taxonomy_bundle_type ] ),
						'#title'         => $taxonomy_label . ' - ' . __( 'Enable auto suggestions', 'directories-pro' ),
						'#states'        => $suggest_states,
						'#horizontal'    => true,
					),
					$taxonomy_bundle_type . '_num'        => array(
						'#type'          => 'slider',
						'#min_value'     => 1,
						'#max_value'     => 20,
						'#title'         => $taxonomy_label . ' - ' . __( 'Number of auto suggested terms to display', 'directories-pro' ),
						'#integer'       => true,
						'#default_value' => isset( $settings['suggest'][ $taxonomy_bundle_type . '_num' ] ) ? $settings['suggest'][ $taxonomy_bundle_type . '_num' ] : 3,
						'#states'        => $suggest_taxonomy_states,
						'#horizontal'    => true,
					),
					$taxonomy_bundle_type . '_hide_empty' => array(
						'#type'          => 'checkbox',
						'#title'         => $taxonomy_label . ' - ' . __( 'Hide empty terms', 'directories-pro' ),
						'#default_value' => ! empty( $settings['suggest'][ $taxonomy_bundle_type . '_hide_empty' ] ),
						'#horizontal'    => true,
						'#states'        => $suggest_taxonomy_states,
					),
					$taxonomy_bundle_type . '_hide_count' => array(
						'#type'          => 'checkbox',
						'#title'         => $taxonomy_label . ' - ' . __( 'Hide post counts', 'directories-pro' ),
						'#default_value' => ! empty( $settings['suggest'][ $taxonomy_bundle_type . '_hide_count' ] ),
						'#horizontal'    => true,
						'#states'        => $suggest_taxonomy_states,
					),
					$taxonomy_bundle_type . '_jump'       => array(
						'#type'          => 'checkbox',
						'#title'         => $taxonomy_label . ' - ' . __( 'Redirect to suggested item page when clicked', 'directories-pro' ),
						'#default_value' => ! empty( $settings['suggest'][ $taxonomy_bundle_type . '_jump' ] ),
						'#horizontal'    => true,
						'#states'        => $suggest_taxonomy_states,
					),
				);
				if ( $is_hierarchical ) {
					$form['suggest'] += array(
						$taxonomy_bundle_type . '_depth' => array(
							'#type'          => 'slider',
							'#title'         => $taxonomy_label . ' - ' . __( 'Term hierarchy depth', 'directories-pro' ),
							'#description'   => __( 'Maximum depth of term hierarchy tree to include in auto suggestions when typing in the field.', 'directories-pro' ),
							'#default_value' => isset( $settings['suggest'][ $taxonomy_bundle_type . '_depth' ] ) ? $settings['suggest'][ $taxonomy_bundle_type . '_depth' ] : 1,
							'#min_value'     => 0,
							'#max_value'     => 10,
							'#min_text'      => __( 'Unlimited', 'directories-pro' ),
							'#integer'       => true,
							'#horizontal'    => true,
							'#states'        => $suggest_taxonomy_states,
						),
						$taxonomy_bundle_type . '_inc_parents' => array(
							'#type'          => 'checkbox',
							'#title'         => $taxonomy_label . ' - ' . __( 'Include parent term paths in term title', 'directories-pro' ),
							'#default_value' => ! empty( $settings['suggest'][ $taxonomy_bundle_type . '_inc_parents' ] ),
							'#horizontal'    => true,
							'#states'        => $suggest_taxonomy_states,
						),
					);
				}

				$has_taxonomy = true;
			}
		}

		if ( $has_taxonomy ) {
			$form['suggest']['settings']['post'] = array(
				'#type'          => 'checkbox',
				'#default_value' => ! isset( $settings['suggest']['settings']['post'] ) || $settings['suggest']['settings']['post'],
				'#title'         => __( 'Suggest posts', 'directories-pro' ),
				'#horizontal'    => true,
				'#weight'        => 0,
			);
		} else {
			$form['suggest']['settings']['post'] = array(
				'#type'          => 'hidden',
				'#default_value' => 1,
			);
		}

		return $form;
	}

	protected function _getSearchableFields( Bundle $bundle, $prefix = '', $referencingField = null ) {
		if ( ! $fields = $this->_application->Entity_Field( $bundle ) ) {
			return array();
		}

		$searchable_fields = array(
			'string' => array( 'value' => null ),
			'email'  => array( 'value' => null ),
			'url'    => array( 'value' => null ),
			'phone'  => array( 'value' => null ),
			'text'   => array( 'value' => null ),
			'choice' => array( 'value' => null ),
			'number' => array( 'value' => null ),
			'name'   => array(
				'first_name'   => __( 'First Name', 'directories-pro' ),
				'middle_name'  => __( 'Middle Name', 'directories-pro' ),
				'last_name'    => __( 'Last Name', 'directories-pro' ),
				'display_name' => null,
			),
		);
		$ret               = array();
		foreach ( $fields as $field_name => $field ) {
			if ( $field->isPropertyField() ) {
				continue;
			}

			if ( isset( $searchable_fields[ $field->getFieldType() ] ) ) {
				foreach ( $searchable_fields[ $field->getFieldType() ] as $column => $column_label ) {
					$label = $field->getFieldLabel();
					if ( strlen( (string) $column_label ) ) {
						$label = sprintf( __( '%1$s (%2$s)', 'directories-pro' ), $label, $column_label );
					}
					$key = array( $field_name, $column, $bundle->name );
					if ( isset( $referencingField ) ) {
						$key[] = $referencingField;
					}
					$ret[ implode( ',', $key ) ] = $prefix . $label . ' (' . $field_name . ')';
				}
			}
		}

		return $ret;
	}

	public function searchFieldForm( Bundle $bundle, array $settings, $request = null, ?array $requests = null, array $parents = array() ) {
		$data = array();

		// Enable auto suggestions?
		if ( ! empty( $settings['suggest']['enable'] ) ) {
			$suggest_no_url             = empty( $settings['suggest']['settings']['post_jump'] );
			$suggest_with_image         = ! empty( $settings['suggest']['settings']['post_with_image'] );
			$data['suggest-post']       = ! isset( $settings['suggest']['settings']['post'] ) || $settings['suggest']['settings']['post'] ? 'true' : 'false';
			$suggest_post_num           = empty( $settings['suggest']['settings']['post_num'] ) ? 5 : $settings['suggest']['settings']['post_num'];
			$data['suggest-post-num']   = $suggest_post_num;
			$data['suggest-post-url']   = $this->_getSuggestUrl( $bundle, $suggest_post_num, $suggest_no_url, 'QUERY', $suggest_with_image );
			$data['suggest-post-icon']  = $this->_application->Entity_BundleTypeInfo( $bundle->type, 'icon' );
			$data['suggest-min-length'] = $settings['suggest']['settings']['min_length'];
			if ( ! empty( $settings['suggest']['settings']['post_num_cache'] ) ) {
				$data['suggest-post-prefetch-url'] = $this->_getSuggestUrl( $bundle, $settings['suggest']['settings']['post_num_cache'], $suggest_no_url, null, true );
			}

			// $data['suggest-post-header'] = $bundle->getLabel('singular');
			if ( ! empty( $bundle->info['taxonomies'] ) ) {
				$taxonomies = $bundle->info['taxonomies'];
				foreach ( $taxonomies as $taxonomy_bundle_type => $taxonomy_bundle_name ) {
					if ( ! empty( $settings['suggest'][ $taxonomy_bundle_type ] )
						&& ( $taxonomy_bundle = $this->_application->Entity_Bundle( $taxonomy_bundle_name ) )
					) {
						$suggest_no_url = empty( $settings['suggest'][ $taxonomy_bundle_type . '_jump' ] );
						$data[ 'suggest-taxonomy-' . $taxonomy_bundle_type . '-url' ]     = $this->_getSuggestTaxonomyTermsUrl(
							$taxonomy_bundle_type,
							array( $taxonomies[ $taxonomy_bundle_type ] ),
							isset( $settings['suggest'][ $taxonomy_bundle_type . '_depth' ] ) ? (int) $settings['suggest'][ $taxonomy_bundle_type . '_depth' ] : null,
							! empty( $settings['suggest'][ $taxonomy_bundle_type . '_hide_empty' ] ),
							$suggest_no_url
						);
						$data[ 'suggest-taxonomy-top-' . $taxonomy_bundle_type . '-url' ] = $this->_getSuggestTaxonomyTermsUrl(
							$taxonomy_bundle_type,
							array( $taxonomies[ $taxonomy_bundle_type ] ),
							1,
							! empty( $settings['suggest'][ $taxonomy_bundle_type . '_hide_empty' ] ),
							$suggest_no_url
						);
						$data[ 'suggest-taxonomy-' . $taxonomy_bundle_type . '-header' ]  = $taxonomy_bundle->getLabel( 'singular' );
						$data[ 'suggest-taxonomy-' . $taxonomy_bundle_type . '-icon' ]    = $this->_application->Entity_BundleTypeInfo( $taxonomy_bundle_type, 'icon' );
						$data[ 'suggest-taxonomy-' . $taxonomy_bundle_type . '-num' ]     = isset( $settings['suggest'][ $taxonomy_bundle_type . '_num' ] ) ? $settings['suggest'][ $taxonomy_bundle_type . '_num' ] : 3;
						if ( empty( $settings['suggest'][ $taxonomy_bundle_type . '_hide_count' ] ) ) {
							$data[ 'suggest-taxonomy-' . $taxonomy_bundle_type . '-count' ] = $bundle->type;
						}
						$data[ 'suggest-taxonomy-' . $taxonomy_bundle_type . '-parents' ] = empty( $settings['suggest'][ $taxonomy_bundle_type . '_inc_parents' ] ) ? 'false' : 'true';
					} else {
						unset( $taxonomies[ $taxonomy_bundle_type ] );
					}
				}
				$data['suggest-taxonomy'] = implode( ',', array_keys( $taxonomies ) );
			}
		}

		$form = array(
			'#data'          => $data,
			'#default_value' => $request,
			'text'           => array(
				'#type'                  => 'textfield',
				'#placeholder'           => $settings['form']['placeholder'],
				'#data'                  => array( 'clear-placeholder' => 1 ),
				'#attributes'            => array(
					'class'        => 'drts-search-keyword-text',
					'autocomplete' => 'off',
					'id'           => '__FORM_ID__-search-keyword-text',
				),
				'#add_clear'             => true,
				'#field_prefix'          => empty( $settings['form']['icon'] ) ? null : '<label for="__FORM_ID__-search-keyword-text" class="' . $settings['form']['icon'] . '"></label>',
				'#field_prefix_no_addon' => true,
				'#required'              => ! empty( $settings['required'] ),
				'#error_no_output'       => true,
			),
			'id'             => array(
				'#type'  => 'hidden',
				'#class' => 'drts-search-keyword-id',
			),
			'taxonomy'       => array(
				'#type'  => 'hidden',
				'#class' => 'drts-search-keyword-taxonomy',
			),
		);

		if ( ! empty( $settings['suggest']['enable'] ) ) {
			$form['#pre_render'][ __CLASS__ ] = array( $this, 'preRenderCallback' );
			$form['#id']                      = '__FORM_ID__-search-keyword';
			$form['#js_ready']                = 'DRTS.Search.keyword("#__FORM_ID__-search-keyword");';
		}

		return $form;
	}

	protected function _getSuggestUrl( $bundle, $num, $noUrl = false, $query = null, $withImage = false ) {
		return (string) $this->_application->MainUrl(
			'/_drts/entity/' . $bundle->type . '/query/' . $bundle->name,
			array(
				'query'                     => $query,
				'num'                       => $num,
				'no_url'                    => (int) $noUrl,
				'with_image'                => (int) $withImage,
				'v'                         => $this->_getSuggestUrlVersion( $noUrl ),
				Request::PARAM_CONTENT_TYPE => 'json',
			),
			'',
			'&'
		);
	}

	protected function _getSuggestTaxonomyTermsUrl( $taxonomyBundleType, $taxonomyBundles, $depth = null, $hideEmpty = false, $noUrl = true ) {
		return (string) $this->_application->MainUrl(
			'/_drts/entity/' . $taxonomyBundleType . '/taxonomy_terms/' . implode( ',', $taxonomyBundles ),
			array(
				'depth'                     => empty( $depth ) ? null : $depth,
				'hide_empty'                => $hideEmpty ? 1 : null,
				'no_url'                    => (int) $noUrl,
				'no_depth'                  => 1,
				'all_count_only'            => 1,
				'v'                         => $this->_getSuggestUrlVersion( $noUrl ),
				Request::PARAM_CONTENT_TYPE => 'json',
			),
			'',
			'&'
		);
	}

	protected function _getSuggestUrlVersion( $noUrl ) {
		if ( ! isset( self::$_suggestUrlVersion ) ) {
			self::$_suggestUrlVersion = $this->_application->Filter( 'search_field_keyword_suggest_url_version', SearchComponent::VERSION . '-' . ( new \DateTime() )->format( 'Y-W' ) . '-' . (int) $noUrl );
		}
		return self::$_suggestUrlVersion;
	}

	public function searchFieldIsSearchable( Bundle $bundle, array $settings, &$value, ?array $requests = null ) {
		// Allow request value sent as string instead of array
		if ( is_string( $value ) ) {
			$value = array( 'text' => $value );
		}

		if ( ! empty( $value['id'] ) ) {
			if ( ! empty( $value['taxonomy'] ) ) {
				if ( ! $this->_application->Entity_Bundle( $value['taxonomy'], $bundle->group ) ) {
					return false;
				}
			}
			return true;
		}

		unset( $value['id'] );

		if ( ! isset( $value['text'] )
			|| ( ! $value['text'] = trim( (string) $value['text'] ) )
		) {
			return false;
		}

		$keywords = $this->_application->System_Text_keywords( $value['text'], $settings['min_length'], $settings['match'] !== 'all', $settings['match'] !== 'exact' );

		if ( empty( $keywords[0] ) ) {
			return false; // no valid keywords
		}

		$value['keywords']         = $keywords[0];
		$value['keywords_ignored'] = $keywords[1];

		return true;
	}

	public function searchFieldSearch( Bundle $bundle, Query $query, array $settings, $value, $sort, array &$sorts ) {
		if ( ! empty( $value['id'] ) ) {
			if ( ! empty( $value['taxonomy'] ) ) {
				$taxonomy_bundle = $this->_application->Entity_Bundle( $value['taxonomy'], $bundle->group );
				$query->taxonomyTermIdIs(
					$taxonomy_bundle->type,
					$value['id'],
					$taxonomy_bundle->info['is_hierarchical'] ? false : true,
					$taxonomy_bundle->type . '_search_keyword', // table alias
					$taxonomy_bundle->type . '_search_keyword' // criteria name
				);
			} else {
				$query->fieldIs( 'id', $value['id'] );
			}
			return;
		}

		// Search child content types?
		$on                            = null;
		$bundle_names                  = array();
		$child_bundle_names_with_field = array();
		if ( ! empty( $settings['child_bundle_types'] )
			&& ( $child_bundles = $this->_application->Entity_Bundles( $settings['child_bundle_types'], $bundle->group ) )
			&& ( $entity_type_info = $this->_application->Entity_Types_impl( $bundle->entitytype_name )->entityTypeInfo() )
			&& ! empty( $entity_type_info['properties']['parent'] )
		) {
			$parent_prop = $entity_type_info['properties']['parent'];
			if ( isset( $parent_prop['field_name'] ) ) {
				// parent field is in another table
				$on = 'entity_id = %3$s AND %1$s.entity_type = ' . $this->_application->getDB()->escapeString( $bundle->entitytype_name );
				$query->addTableJoin( 'entity_field_' . $parent_prop['field_name'], $parent_prop['field_name'], $on )
					->addTableIdColumn( $parent_prop['field_name'] . '.' . $parent_prop['column'] );
				$child_bundle_names_with_field[ $parent_prop['field_name'] ] = array_keys( $child_bundles );
			} else {
				$query->addTableIdColumn( $parent_prop['column'] );
				foreach ( array_keys( $child_bundles ) as $_bundle_name ) {
					$bundle_names[] = $_bundle_name;
				}
			}
		}

		// Get any existing bundle_name criteria already added
		foreach ( $query->removeBundleNameCriteria() as $_bundle_name ) {
			$bundle_names[] = $_bundle_name;
		}
		if ( ! in_array( $bundle->name, $bundle_names, true ) ) {
			$bundle_names[] = $bundle->name;
		}

		// Add bundle names criteria
		$bundle_names = array_unique( $bundle_names );
		if ( ! empty( $child_bundle_names_with_field ) ) {
			$query->startCriteriaGroup( 'OR' )->fieldIsIn( 'bundle_name', $bundle_names );
			foreach ( $child_bundle_names_with_field as $reference_field_name => $referencing_bundle_name ) {
				$query->startCriteriaGroup()
					->fieldIsIn( 'bundle_name', (array) $referencing_bundle_name )
					->fieldIsNotNull( $reference_field_name, 'value', null, false )
					->finishCriteriaGroup();
			}
			$query->finishCriteriaGroup();
		} else {
			$query->fieldIsIn( 'bundle_name', $bundle_names );
		}

		$extra_fields = array();
		if ( ! empty( $settings['extra_fields'] ) ) {
			$reference_bundles = array();
			foreach ( $settings['extra_fields'] as $field_name ) {
				list($field_name, $column, $bundle_name, $reference_field_name) = explode( ',', $field_name );
				if ( ! empty( $reference_field_name ) ) {
					$reference_bundles[ $reference_field_name ]  = $bundle_name;
					$extra_fields[ $bundle_name ][ $field_name ] = $column;
				} else {
					$extra_fields[ $bundle->name ][ $field_name ] = $column;
				}
			}
			if ( ! empty( $reference_bundles ) ) {
				$reference_field_names = array();
				foreach ( $reference_bundles as $reference_field_name => $reference_bundle_name ) {
					$reference_field_names[] = $reference_field_name;
				}
				$db = $this->_application->getDB();
				$on = 'entity_id = %3$s AND %1$s.entity_type = ' . $db->escapeString( $bundle->entitytype_name )
					. ' AND %1$s.field_name IN (' . implode( ',', array_map( array( $db, 'escapeString' ), $reference_field_names ) ) . ')';
				$query->addTableJoin( $db->getResourcePrefix() . 'entity_field_entity_reference', 'entity_reference', $on );
			}
		}

		if ( $settings['match'] === 'any' ) {
			$query->startCriteriaGroup( 'OR' );
			$this->_queryKeywords( $bundle, $query, $value['keywords'], $on, $extra_fields, $settings['taxonomies'], ! empty( $settings['exclude_title'] ), ! empty( $settings['exclude_content'] ) );
			$query->finishCriteriaGroup();
		} else {
			$this->_queryKeywords( $bundle, $query, $value['keywords'], $on, $extra_fields, $settings['taxonomies'], ! empty( $settings['exclude_title'] ), ! empty( $settings['exclude_content'] ) );
		}
	}

	protected function _queryKeywords( Bundle $bundle, Query $query, array $keywords, $on, ?array $extraFields = null, ?array $taxonomies = null, $excludeTitle = false, $excludeContent = false ) {
		foreach ( $keywords as $keyword ) {
			$query->startCriteriaGroup( 'OR' );
			if ( empty( $excludeTitle ) ) {
				$query->fieldContains( 'title', $keyword );
			}
			if ( empty( $excludeContent ) ) {
				$query->fieldContains( 'content', $keyword, 'value', null, $on ); // need this to join content field table with child entities as well
			}
			if ( ! empty( $extraFields ) ) {
				foreach ( $extraFields as $bundle_name => $extra_fields ) {
					foreach ( $extra_fields as $field_name => $column ) {
						if ( $extra_field = $this->_application->Entity_Field( $bundle_name, $field_name ) ) {
							if ( $bundle_name !== $bundle->name ) {
								$query->fieldContains( $extra_field, $keyword, $column, null, 'entity_id = entity_reference.value AND %1$s.bundle_name = ' . $this->_application->getDB()->escapeString( $bundle_name ) );
							} else {
								$query->fieldContains( $extra_field, $keyword, $column );
							}
						}
					}
				}
			}
			if ( ! empty( $taxonomies ) ) {
				foreach ( $taxonomies as $taxonomy ) {
					if ( ! $taxonomy_bundle = $this->_application->Entity_Bundle( $taxonomy ) ) {
						continue;
					}

					$query->taxonomyTermTitleContains( $taxonomy_bundle->name, $taxonomy_bundle->type, $keyword, $taxonomy_bundle->type . '_search_term_title' );
				}
			}
			$query->finishCriteriaGroup();
		}
	}

	public function searchFieldLabels( Bundle $bundle, array $settings, $value ) {
		$labels         = empty( $value['keywords'] ) ? array( $value['text'] ) : $value['keywords'];
		$ignored_labels = empty( $value['keywords_ignored'] ) ? null : $value['keywords_ignored'];
		return array( $labels, $ignored_labels );
	}

	public function searchFieldUnsearchableLabel( Bundle $bundle, array $settings, $value ) {
		return is_string( $value ) ? $value : ( isset( $value['text'] ) ? $value['text'] : null );
	}

	public function preRenderCallback( $form ) {
		$this->_application->Form_Scripts_typeahead();
	}
}
