<?php
/**
 * Class Loco_Automatic_Translate_Addon_Pro\AI_Translate\Services\Decorators\AI_Service_Decorator
 *
 * @package ai-translate
 */

namespace Loco_Automatic_Translate_Addon_Pro\AI_Translate\Services\Decorators;

use Loco_Automatic_Translate_Addon_Pro\AI_Translate\Services\API\Types\Content;
use Loco_Automatic_Translate_Addon_Pro\AI_Translate\Services\API\Types\Generation_Config;
use Loco_Automatic_Translate_Addon_Pro\AI_Translate\Services\API\Types\Parts;
use Loco_Automatic_Translate_Addon_Pro\AI_Translate\Services\Cache\Service_Request_Cache;
use Loco_Automatic_Translate_Addon_Pro\AI_Translate\Services\Contracts\Generative_AI_Model;
use Loco_Automatic_Translate_Addon_Pro\AI_Translate\Services\Contracts\Generative_AI_Service;
use Loco_Automatic_Translate_Addon_Pro\AI_Translate\Services\Exception\Generative_AI_Exception;
use Loco_Automatic_Translate_Addon_Pro\AI_Translate\Services\Util\AI_Capabilities;
use InvalidArgumentException;

/**
 * Class for an AI service that wraps another AI service through a decorator pattern.
 *
 * This class effectively acts as middleware for the underlying AI service, allowing for additional functionality to be
 * centrally provided.
 */
class AI_Service_Decorator implements Generative_AI_Service {

	/**
	 * The underlying AI service to wrap.
	 *
	 * @var Generative_AI_Service
	 */
	private $service;

	/**
	 * Constructor.
	 *
	 * @param Generative_AI_Service $service The underlying AI service to wrap.
	 */
	public function __construct( Generative_AI_Service $service ) {
		$this->service = $service;
	}

	/**
	 * Gets the service slug.
	 *
	 * @return string The service slug.
	 */
	public function get_service_slug(): string {
		return $this->service->get_service_slug();
	}

	/**
	 * Gets the list of AI capabilities that the service and its models support.
	 *
	 * @see AI_Capabilities
	 *
	 * @return string[] The list of AI capabilities.
	 */
	public function get_capabilities(): array {
		return $this->service->get_capabilities();
	}

	/**
	 * Checks whether the service is connected.
	 *
	 * This is typically used to check whether the current service credentials are valid.
	 *
	 * @return bool True if the service is connected, false otherwise.
	 */
	public function is_connected(): bool {
		return Service_Request_Cache::wrap_transient(
			$this->get_service_slug(),
			array( $this->service, 'is_connected' )
		);
	}

	/**
	 * Lists the available generative model slugs and their capabilities.
	 *
	 * @param array<string, mixed> $request_options Optional. The request options. Default empty array.
	 * @return array<string, string[]> Map of the available model slugs and their capabilities.
	 *
	 * @throws Generative_AI_Exception Thrown if the request fails or the response is invalid.
	 */
	public function list_models( array $request_options = array() ): array {
		return Service_Request_Cache::wrap_transient(
			$this->get_service_slug(),
			array( $this->service, 'list_models' ),
			array( $request_options )
		);
	}

	/**
	 * Gets a generative model instance for the provided model parameters.
	 *
	 * @param array<string, mixed> $model_params    {
	 *     Optional. Model parameters. Default empty array.
	 *
	 *     @type string               $feature           Required. Unique identifier of the feature that the model
	 *                                                   will be used for. Must only contain lowercase letters,
	 *                                                   numbers, hyphens.
	 *     @type string               $model             The model slug. By default, the model will be determined
	 *                                                   based on heuristics such as the requested capabilities.
	 *     @type string[]             $capabilities      Capabilities requested for the model to support. It is
	 *                                                   recommended to specify this if you do not explicitly specify a
	 *                                                   model slug.
	 *     @type Generation_Config?   $generationConfig  Model generation configuration options. Default none.
	 *     @type string|Parts|Content $systemInstruction The system instruction for the model. Default none.
	 * }
	 * @param array<string, mixed> $request_options Optional. The request options. Default empty array.
	 * @return Generative_AI_Model The generative model.
	 *
	 * @throws InvalidArgumentException Thrown if the model slug or parameters are invalid.
	 */
	public function get_model( array $model_params = array(), array $request_options = array() ): Generative_AI_Model {
		if ( ! isset( $model_params['feature'] ) || ! preg_match( '/^[a-z0-9-]+$/', $model_params['feature'] ) ) {
			throw new InvalidArgumentException(
				esc_html__( 'You must provide a "feature" identifier as part of the model parameters, which only contains lowercase letters, numbers, and hyphens.', 'ai-services' )
			);
		}

		/**
		 * Filters the AI service model parameters before retrieving the model with them.
		 *
		 * This can be used, for example, to inject additional parameters via server-side logic based on the given
		 * feature identifier.
		 *
		 * @param array<string, mixed> $model_params The model parameters. Commonly supports at least the parameters
		 *                                           'feature', 'capabilities', 'generationConfig' and
		 *                                           'systemInstruction'.
		 * @return array<string, mixed> The processed model parameters.
		 */
		$filtered_model_params = (array) apply_filters( 'ai_services_model_params', $model_params );

		// Ensure that the feature identifier cannot be changed.
		$filtered_model_params['feature'] = $model_params['feature'];
		$model_params                     = $filtered_model_params;

		return $this->service->get_model( $model_params, $request_options );
	}
}
