<?php

namespace Limb_Chatbot\Includes\Vector_Dbs\Pinecone\Endpoints;

use Limb_Chatbot\Includes\Data_Objects\Vector;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\Collection;


/**
 * Class Query_Endpoint
 *
 * Handles querying vectors from the Pinecone vector database.
 *
 * @package Limb_Chatbot\Includes\Vector_Dbs\Pinecone\Endpoints
 *
 * @since 1.0.0
 */
class Query_Endpoint extends Pinecone_Endpoint {
	/**
	 * Special role allowing universal access.
	 */
	private const ROLE_EVERYONE = 'everyone';

	/**
	 * @var string Host URL for the Pinecone query endpoint
	 *
	 * @since 1.0.0
	 */
	protected string $host;

	/**
	 * Query Pinecone with a vector and get the closest matches.
	 *
	 * @param  Vector  $vector  The vector object containing values to query with.
	 * @param  int  $count  The number of top results to return. Default is 3.
	 *
	 * @return Collection|null Collection of matched vectors or null on failure.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 * @since 1.0.15 Added dimension support
	 */
	public function query( Vector $vector, int $count = 3 ): ?Collection {
		$values = maybe_unserialize( $vector->get_values() );

		if ( ! empty( $vector->dimension ) ) {
			$this->utility->set_dimension( $vector->dimension );
		} elseif ( is_array( $values ) ) {
			// Fallback: infer dimension from values count if not explicitly set
			$this->utility->set_dimension( count( $values ) );
		}

		$http_client  = $this->http_client_factory();
		$vector_index = $this->utility->get_global_utility()->get_vector_index();
		$this->define_host( $vector_index );
		$body         = [
			'vector' => $values,
			'topK'   => $count ?? 100,
		];
		$excluded_ids = $vector_index->get_inactive_vectors();

		$filter_clauses = [];
		if ( ! empty( $excluded_ids ) ) {
			$filter_clauses[] = [
				'internal_id' => [
					'$nin' => $excluded_ids,
				],
			];
		}

		$query_roles = $this->utility->get_global_utility()->get_query_access_roles();
		$query_roles = array_values( array_filter( array_unique( array_map( static function ( $role ) {
			return is_string( $role ) ? sanitize_key( $role ) : '';
		}, $query_roles ) ) ) );

		$role_or_clauses = [
			[
				'access_everyone' => true,
			],
			[
				'access_roles' => [
					'$exists' => false,
				],
			],
		];
		if ( ! empty( $query_roles ) ) {
			$role_or_clauses[] = [
				'access_roles' => [
					'$in' => $query_roles,
				],
			];
		}
		$filter_clauses[] = [ '$or' => $role_or_clauses ];

		if ( 1 === count( $filter_clauses ) ) {
			$body['filter'] = $filter_clauses[0];
		} elseif ( count( $filter_clauses ) > 1 ) {
			$body['filter'] = [
				'$and' => $filter_clauses,
			];
		}

		$url      = 'https://' . $this->host . '/query';
		$response = $http_client->post( $url,
			array(
				'headers' => $this->get_header(),
				'timeout' => $this->utility->get_timeout(),
				'body'    => json_encode( $body ),
			),
		);

		return ( new Query\Response_Handler( $response, $http_client, $this ) )->get_vectors();
	}
}