<?php

namespace Limb_Chatbot\Includes\AI_Providers\Open_Ai\Utilities;

use Limb_Chatbot\Includes\AI_Providers\Open_Ai\Open_Ai;
use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Data_Objects\Vector;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Utilities\Embedding_Utility as Global_Embedding_Utility;
use Limb_Chatbot\Includes\Data_Objects\Setting;
use Limb_Chatbot\Includes\AI_Providers\Open_Ai\Endpoints\Embedding_Endpoint;
use ReflectionClass;

/**
 * Utility class for generating and managing embeddings using the OpenAI provider.
 *
 * @package Limb_Chatbot\Includes\AI_Providers\Open_Ai\Utilities
 * @since 1.0.0
 */
class Embedding_Utility {

	/**
	 * Global embedding utility used to retrieve vectors and shared functionality.
	 *
	 * @since 1.0.0
	 * @var Global_Embedding_Utility
	 */
	public Global_Embedding_Utility $global_utility;

	/**
	 * Configuration ID used to retrieve specific config.
	 *
	 * @since 1.0.0
	 * @var int|null
	 */
	public ?int $config_id = null;

	/**
	 * AI model ID used for embedding.
	 *
	 * @since 1.0.0
	 * @var int|null
	 */
	public ?int $ai_model_id = null;

	/**
	 * Dimension of the embedding vector.
	 *
	 * @since 1.0.0
	 * @var int|null
	 */
	public ?int $dimension = null;

	/**
	 * Encoding format for the embedding.
	 *
	 * @since 1.0.0
	 * @var string|null
	 */
	public ?string $encoding_format = 'float';

	/**
	 * Timeout duration in seconds for the embedding request.
	 *
	 * @since 1.0.0
	 * @var int|null
	 */
	public ?int $timeout = 60;

	/**
	 * Constructor.
	 *
	 * Initializes the embedding utility with the global utility and populates internal properties from settings.
	 *
	 * @since 1.0.0
	 *
	 * @param Global_Embedding_Utility $global_utility Global utility instance.
	 */
	public function __construct( Global_Embedding_Utility $global_utility ) {
		$this->global_utility = $global_utility;
		$this->populate_properties( 'ai_providers.' . Open_Ai::$id . '.utilities.embedding.' );
	}

	/**
	 * Populates class properties using settings prefixed by the given string.
	 *
	 * @since 1.0.0
	 *
	 * @param string $setting_prefix Prefix for the setting keys.
	 * @return void
	 */
	public function populate_properties( $setting_prefix ) {
		foreach ( ( new ReflectionClass( $this ) )->getProperties() as $property ) {
			$value = Setting::find( $setting_prefix . $property->getName() )->get_value();
			if ( $property->getType() && $property->getType()->isBuiltin() && ! empty( $value ) ) {
				$property->setValue( $this, $value );
			}
		}
	}

	/**
	 * Generates an embedding vector using the configured embedding endpoint.
	 *
	 * @return Vector|null Generated embedding vector or null on failure.
	 * @throws Exception
	 * @since 1.0.0
	 *
	 */
	public function generate(): ?Vector {
		return ( new Embedding_Endpoint( $this ) )->generate( $this->global_utility->get_vector() );
	}

	/**
	 * Returns the configured AI model ID.
	 *
	 * @since 1.0.0
	 *
	 * @return int|null AI model ID.
	 */
	public function get_ai_model_id(): ?int {
		return $this->ai_model_id;
	}

	/**
	 * Returns the configured encoding format.
	 *
	 * @since 1.0.0
	 *
	 * @return string|null Encoding format.
	 */
	public function get_encoding_format(): ?string {
		return $this->encoding_format;
	}

	/**
	 * Returns the configured dimension of the embedding.
	 *
	 * @since 1.0.0
	 *
	 * @return int|null Embedding dimension.
	 */
	public function get_dimension(): ?int {
		return $this->dimension;
	}

	/**
	 * Returns the timeout value for the embedding request.
	 *
	 * @since 1.0.0
	 *
	 * @return int|null Timeout in seconds.
	 */
	public function get_timeout(): ?int {
		return $this->timeout;
	}

	/**
	 * Returns the configuration ID used in this utility.
	 *
	 * @since 1.0.0
	 *
	 * @return int|null Config ID.
	 */
	public function get_config_id(): ?int {
		return $this->config_id;
	}

	/**
	 * Retrieves the associated configuration object based on the config ID.
	 *
	 * @since 1.0.0
	 *
	 * @return Config|null Configuration object or null if not set.
	 */
	public function get_config(): ?Config {
		return $this->config_id ? Config::find( $this->config_id ) : null;
	}
}