<?php

namespace wpsolr\core\classes\engines\opensearch_vector_php;

use wpsolr\core\classes\engines\opensearch_php\WPSOLR_SearchOpenSearchClient;
use wpsolr\core\classes\hosting_api\WPSOLR_Hosting_Api_Abstract;
use wpsolr\core\classes\utilities\WPSOLR_Option;

class WPSOLR_SearchOpenSearch_Vector_Client extends WPSOLR_SearchOpenSearchClient {
	use WPSOLR_OpenSearch_Vector_Client;

	protected string $search_pipeline;

	protected function _build_query_keywords( array $query_string, array $query_filters ): array {

		$keywords = $query_string['query'];
		$keywords = preg_replace( '/(.*):/', '', $keywords ); // keyword => keyword, text:keyword => keyword
		$keywords = trim( $keywords, '(' );
		$keywords = trim( $keywords, ')' );
		if ( empty( $keywords ) || ( false !== strpos( $keywords, '*' ) ) ) {
			// Do not use vectors on empty keywords
			$this->filter = WPSOLR_Option::OPTION_SEARCH_ITEM_FILTER_BM25;
		}

		switch ( $this->filter ?? '' ) {
			case WPSOLR_Option::OPTION_SEARCH_ITEM_FILTER_BM25:
				return parent::_build_query_keywords( $query_string, $query_filters );

			case WPSOLR_Option::OPTION_SEARCH_ITEM_FILTER_NEAR_TEXT:
				return [ "must" => $this->_build_query_keywords_vector( $keywords, $query_filters ) ];

			case WPSOLR_Option::OPTION_SEARCH_ITEM_FILTER_HYBRID_RRF:
				return $this->_build_query_keywords_hybrid_rff( $query_string, $keywords, $query_filters );

			default:
				throw new \Exception( esc_html( "Unknown query type '{$this->filter}'." ) );
		}

	}

	protected function _before_search( array $the_query ): array {
		if ( ! empty( $this->search_pipeline ) ) {
			$the_query['search_pipeline'] = $this->search_pipeline;
		}

		return $the_query;
	}

	protected function _build_query_keywords_vector( string $keywords, array $query_filters ): array {
		# https://docs.opensearch.org/latest/vector-search/ai-search/semantic-search/#using-a-semantic-field
		return [
			"neural" => [
				$this->_FIELD_NAME_TEXT_CONCATENATED_FIELDS_EMBEDDING => [
					"query_text" => $keywords,
					"k"          => 200,
					#"num_candidates" => 50000,
					"model_id"   => $this->_get_model_id(),
					'filter'     => [ 'bool' => $query_filters ],
					// pre-filtering before ANN search for better recall
				]
			]
		];

	}

	protected function _build_query_near_object( string $object_id, array $query_filters ): array {
		// Opensearch only use the object vector for similarity (not doc id)
		$response = $this->search_engine_client->get( [
			'index'   => $this->get_index_label(),
			'id'      => $object_id,
			'_source' => [ $this->_FIELD_NAME_TEXT_CONCATENATED_FIELDS_EMBEDDING ]
			// only fetch the vector
		] );

		if ( empty( $object_embedding = $response['_source'][ $this->_FIELD_NAME_TEXT_CONCATENATED_FIELDS_EMBEDDING ] ?? null ) ) {
			throw new \Exception( esc_html( "Object id '{$object_id}' for recommendation not found." ) );
		}

		return [
			'must' => [
				"knn" => [
					$this->_FIELD_NAME_TEXT_CONCATENATED_FIELDS_EMBEDDING => [
						"vector" => $object_embedding,
						"k"      => 200,
						'filter' => [ 'bool' => $query_filters ], // pre-filtering before ANN search for better recall
					]
				]
			]
		];

	}

	protected function _build_query_keywords_hybrid_rff( array $query_string, string $keywords, array $query_filters ): array {
		# https://docs.opensearch.org/latest/vector-search/ai-search/hybrid-search/index/#example-combining-a-neural-query-and-a-match-query

		// Add the RFF search pipeline to the query
		$this->search_pipeline = $this->get_index_label();

		// Make an hybrid query
		return [
			"must" => [
				'hybrid' => [
					'queries' => [
						[ 'query_string' => $query_string ],
						$this->_build_query_keywords_vector( $keywords, $query_filters )
					],
					'filter'  => [ 'bool' => $query_filters ],
					// post filtering to also filter merged results, because keywords results are not filtered
				]
			]
		];
	}

	public function admin_delete_index() {
		// Delete all related structures first
		$this->_clean_before_delete_index();

		parent::admin_delete_index();
	}

	/**
	 * Add a configuration to the index if missing.
	 */
	protected
	function admin_index_update(
		&$index_parameters
	) {
		// Replace  the external connector, as it contains the API keys that could have changed
		if ( $this->_is_external_model() ) {
			$this->_clean_before_delete_index();
			$this->_register_model();
			$model_id                                                                                                         = $this->_register_model();
			$index_parameters[ WPSOLR_Hosting_Api_Abstract::FIELD_NAME_FIELDS_INDEX_WEAVIATE_HUGGINGFACE_CONFIG_MODEL_QUERY ] = $model_id;
		}

		parent::admin_index_update( $index_parameters );
	}
}
