<?php

namespace Limb_Chatbot\Includes\Utilities;

use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Data_Objects\Vector;
use Limb_Chatbot\Includes\Data_Objects\Vector_Index;
use Limb_Chatbot\Includes\Data_Objects\Vector_Index_Vector;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\User_Roles;
use Limb_Chatbot\Includes\Interfaces\Utility_Interface;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Vector_Dbs\Local\Local;
use Limb_Chatbot\Includes\Vector_Dbs\Pinecone\Pinecone;
use Limb_Chatbot\Includes\Vector_Dbs\Vector_Db;

/**
 * Class Vector_Db_Utility
 *
 * Wrapper utility for vector database operations, supporting various backends (e.g., Pinecone).
 *
 * @package Limb_Chatbot\Includes\Utilities
 *
 * @since 1.0.0
 */
class Vector_Db_Utility extends Utility implements Utility_Interface {

	/**
	 * The vector index associated with this utility.
	 *
	 * @var Vector_Index
	 * @since 1.0.0
	 */
	public Vector_Index $vector_index;

	/**
	 * Meta data array for the vector index.
	 *
	 * @var array
	 * @since 1.0.0
	 */
	public array $vector_index_metas = [];
	/**
	 * Role slugs used to filter knowledge vectors at query time.
	 *
	 * @var string[]
	 * @since 2.0.0
	 */
	public array $query_access_roles = [];

	/**
	 * The ID of the vector DB.
	 *
	 * @var string|null
	 * @since 1.0.0
	 */
	public ?string $id = null;

	/**
	 * Static utility name.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public static string $name = 'Vector DB Utility';

	/**
	 * Vector_Db_Utility constructor.
	 *
	 * @param Vector_Index $vector_index Vector index instance.
	 * @param array        $vector_index_metas Optional array of vector index metas.
	 *
	 * @since 1.0.0
	 */
	public function __construct( Vector_Index $vector_index, array $vector_index_metas = [] ) {
		$this->vector_index = $vector_index;
		$this->vector_index_metas = $vector_index_metas;
		if ( $vector_index->get_config_id() ) {
			$config = Config::find( $vector_index->get_config_id() );
			if ( $config instanceof Config && $config->get_related_to_instance() instanceof Vector_Db ) {
				$this->id = $config->get_related_to_instance()->get_id();
			}
		} else {
			$this->id = Local::$id;
		}
	}

	/**
	 * Get the specific vector DB utility instance for the configured vector DB.
	 *
	 * @throws Exception If the vector DB is not supported or missing.
	 *
	 * @return mixed The backend-specific vector DB utility.
	 *
	 * @since 1.0.0
	 */
	public function get_vector_db_utility() {
		if ( $this->id == Pinecone::$id ) {
			$vector_db = new Pinecone();
		} elseif( $this->id == Local::$id || empty($this->id) ) {
			$vector_db = new Local();
		} else {
			throw new Exception( Error_Codes::NOT_SUPPORTED, __( 'Not supported vector db', 'limb-chatbot' ) );
		}
		$vector_db_utility = $vector_db->get_utility( $this );
		if ( is_null( $vector_db_utility ) ) {
			throw new Exception( Error_Codes::NOT_SUPPORTED, __( 'Vector DB utility is missing.', 'limb-chatbot' ) );
		}

		return $vector_db_utility;
	}

	/**
	 * Get the vector DB ID.
	 *
	 * @return string|null Vector DB ID or null if not set.
	 *
	 * @since 1.0.0
	 */
	public function get_id(): ?string {
		return $this->id;
	}

	/**
	 * Get the vector index instance.
	 *
	 * @return Vector_Index The vector index.
	 *
	 * @since 1.0.0
	 */
	public function get_vector_index(): Vector_Index {
		return $this->vector_index;
	}

	/**
	 * Get array of vector index metas.
	 *
	 * @return array Vector index metas.
	 *
	 * @since 1.0.0
	 */
	public function get_vector_index_metas(): array {
		return $this->vector_index_metas;
	}

	/**
	 * Get role slugs used for query-time access filtering.
	 *
	 * @return string[]
	 * @since 2.0.0
	 */
	public function get_query_access_roles(): array {
		return $this->query_access_roles;
	}

	/**
	 * Set role slugs used for query-time access filtering.
	 *
	 * @param  array  $roles
	 *
	 * @return void
	 * @since 2.0.0
	 */
	public function set_query_access_roles( array $roles ): void {
		$this->query_access_roles = User_Roles::expand_roles_for_matching( $roles );
	}

	/**
	 * Upsert (insert or update) a vector into the vector DB.
	 *
	 * @param  Vector  $vector  Vector instance to upsert.
	 *
	 * @return mixed Result from the vector DB upsert operation.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function upsert( Vector $vector ) {
		return $this->get_vector_db_utility()->upsert( $vector );
	}

	/**
	 * Batch upsert multiple vectors into the vector DB.
	 *
	 * @param  array  $vectors  Array of Vector instances.
	 *
	 * @return mixed Result of batch upsert.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function upsert_batch( $vectors ) {
		return $this->get_vector_db_utility()->upsert_batch( $vectors );
	}

	/**
	 * Delete a vector from the vector DB.
	 *
	 * @param  Vector  $vector  Vector instance to delete.
	 *
	 * @return mixed Result of delete operation.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function delete_vector( Vector $vector ) {
		return $this->get_vector_db_utility()->delete_vector( $vector );
	}

	/**
	 * Batch delete multiple vectors from the vector DB.
	 *
	 * @param  array  $vectors  Array of Vector instances.
	 *
	 * @return mixed Result of batch delete operation.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function delete_vector_batch( array $vectors ) {
		return $this->get_vector_db_utility()->delete_vector_batch( $vectors );
	}

	/**
	 * Query the vector DB for similar vectors.
	 *
	 * @param  Vector  $vector  Vector instance to query with.
	 * @param  int  $count  Number of results to return. Default is 3.
	 *
	 * @return mixed Query results from the vector DB.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function query( Vector $vector, int $count = 3 ) {
		return $this->get_vector_db_utility()->query( $vector, $count );
	}

	/**
	 * Create the vector index in the backend vector DB.
	 *
	 * @return Collection The created index representation.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function create_index(): Collection {
		// TODO rename to some general get_related_child_utility() method to be used by ai_ai_providers, vector_dbs, etc ...
		return $this->get_vector_db_utility()->create();
	}

	/**
	 * Delete the vector index from the backend vector DB.
	 *
	 * @return mixed Result of delete index operation.
	 *
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function delete_index() {
		return $this->get_vector_db_utility()->delete();
	}

	/**
	 * Get the currently set vector.
	 *
	 * @return Vector|null Currently set Vector instance or null.
	 *
	 * @since 1.0.0
	 */
	public function get_vector(): ?Vector {
		return $this->vector;
	}

	/**
	 * Set the current Vector instance.
	 *
	 * @param Vector|null $vector Vector instance or null.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_vector( ?Vector $vector ): void {
		$this->vector = $vector;
	}

	/**
	 * Get the associated Vector_Index_Vector instance.
	 *
	 * @return Vector_Index_Vector|null The vector index vector or null.
	 *
	 * @since 1.0.0
	 */
	public function get_vector_index_vector(): ?Vector_Index_Vector {
		return $this->vector_index_vector;
	}

	/**
	 * Set the associated Vector_Index_Vector instance.
	 *
	 * @param Vector_Index_Vector|null $vector_index_vector Vector index vector or null.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 */
	public function set_vector_index_vector( ?Vector_Index_Vector $vector_index_vector ): void {
		$this->vector_index_vector = $vector_index_vector;
	}
}