<?php

/**
 * OpenAI API controller
 */
final class VAPPV_TTW_OpenAI_API_Connector {

	/**
	 * OpenAI API address (endpoint).
	 * Default is: https://api.openai.com
	 */
	private $openai_api_address;

	/**
	 * Account API key
	 */
	private $api_key;

	/**
	 * OpenAI controller constructor
	 */
	public function __construct( $openai_api_address, $api_key ) {
		$this->openai_api_address = $openai_api_address;
		$this->api_key = $api_key;
	}

	/**
	 * Check connection to the OpenAI
	 * @return array
	 * @throws Exception
	 */
	public function test_connection(): array {

		try {

			// Prepare enpoint URL
			$api_url = sprintf( '%1$s/v1/chat/completions', $this->openai_api_address );

			// Prepare arguments
			$args = array(
				'method'    => 'POST',
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json'
				),
				'body'      => wp_json_encode( array(
					'model'    => 'gpt-3.5-turbo',
					'messages' => array(
						array(
							'role' => 'system',
							'content' => 'You are a helpful assistant.'
						),
						array(
							'role' => 'user',
							'content' => 'Say this is a test!'
						)
					),
					'max_tokens'  => 150,
					'temperature' => 0.7,
				))
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to connect to OpenAI via API with specified access key (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);
				return VAPPV_TTW_Core::ajax_pack_error( 'OPENAI_API_ERROR', array(
					'code' => 500,
					'message' => esc_html( $error_message )
				) );

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to connect to OpenAI via API with specified access key (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return VAPPV_TTW_Core::ajax_pack_error( 'OPENAI_API_ERROR', $extracted_error );

			}

			return VAPPV_TTW_Core::ajax_pack_success( $openai_response_raw );

		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					'%1$s // %2$s',
					esc_html( __( 'Failed to connect to OpenAI via API with specified access key (Plugin error).', 'talk-to-website' ) ),
					esc_html( $t->getMessage() )
				)
			);
			return VAPPV_TTW_Core::ajax_pack_error( 'GENERAL_OPENAI_CONNECTION_ERROR', esc_html( $t->getMessage() ) );

		}
	}

	/**
	 * Update OpenAI API key
	 *
	 * @param string $api_key OpenAI API key
	 *
	 * @return array
	 * @throws Exception
	 */
	public function update_api_key( string $api_key ): array {

		// Get short version of the API key. Then, log the data.
		$new_openai_key_substr = substr( $api_key, 0, 10 );
		VAPPV_TTW_Core::add_log_entry(
			'info',
			sprintf(
				esc_html(
					/* translators: 1. shortened API key */
					__( 'Trying to set API key = %1$s***...', 'talk-to-website' )
				),
				esc_html( $new_openai_key_substr )
			)
		);

		// Try to call OpenAI's server via API
		try {

			$api_url = sprintf( '%1$s/v1/chat/completions', $this->openai_api_address );

			// Prepare arguments
			$args = array(
				'method'    => 'POST',
				'headers'   => array(
					'Authorization' => 'Bearer ' . $api_key,
					'Content-Type'  => 'application/json'
				),
				'body'      => wp_json_encode( array(
					'model'    => 'gpt-3.5-turbo',
					'messages' => array(
						array(
							'role' => 'system',
							'content' => 'You are a helpful assistant.'
						),
						array(
							'role' => 'user',
							'content' => 'Say this is a test!'
						)
					),
					'max_tokens'  => 150,
					'temperature' => 0.7,
				))
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to connect to OpenAI via API with specified access key (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);
				return VAPPV_TTW_Core::ajax_pack_error( 'OPENAI_API_ERROR', array(
					'code' => 500,
					'message' => esc_html( $error_message )
				) );

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to connect to OpenAI via API with specified access key (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return VAPPV_TTW_Core::ajax_pack_error( 'OPENAI_API_ERROR', $extracted_error );

			}

			//Set new API key locally
			$this->api_key = $api_key;

			//Update OpenAI API key into DB
			$settings_controller = new VAPPV_TTW_Settings();
			$settings_controller->update_setting( VAPPV_TTW_Settings::$settings_openai_api_key, $api_key );

			//Add log entry
			VAPPV_TTW_Core::add_log_entry(
				'info',
				sprintf(
					esc_html(
						/* translators: 1. OpenAI API key substring */
						__( 'OpenAI API key updated. New key = %1$s***...', 'talk-to-website' )
					),
					esc_html( $new_openai_key_substr )
				)
			);

			return VAPPV_TTW_Core::ajax_pack_success( array(
				'Result'             => 'API_KEY_SAVED',
				'OpenAIResponseText' => $openai_response_raw
			) );

		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					'%1$s // %2$s',
					esc_html( __( 'Failed to connect to OpenAI via API with specified access key (Plugin error).', 'talk-to-website' ) ),
					esc_html( $t->getMessage() )
				)
			);
			return VAPPV_TTW_Core::ajax_pack_error( 'GENERAL_OPENAI_CONNECTION_ERROR', $t->getMessage() );

		}

	}

	/**
	 * Upload training file;
	 * Based on docs: https://platform.openai.com/docs/api-reference/files/create ;
	 * Response format:
	 * {
	 *   "id": "file-abc123",
	 *   "object": "file",
	 *   "bytes": 120000,
	 *   "created_at": 1677610602,
	 *   "filename": "mydata.jsonl",
	 *   "purpose": "fine-tune",
	 * }
	 *
	 * @param string $fileName Training file name
	 *
	 * @return VAPPV_TTW_Common_Error|VAPPV_TTW_OpenAI_API_Error|array|string
	 * @throws Exception
	 */
	public function upload_training_file_for_assistants( string $fileName ) {
		
		VAPPV_TTW_Core::add_log_entry(
			'info',
			sprintf(
				esc_html(
					/* translators: 1. name of the uploading file */
					__( 'Trying to upload a training file to the OpenAI platform via API.. Filename = %1$s', 'talk-to-website' )
				),
				esc_html( $fileName )
			)
		);

		try {

			// Prepare direct link to the file and extract file content
			$file_name_with_full_path = VAPPV_TTW_UPLOADS_DIR['path'] . '/' . $fileName;
			$WP_Filesystem_Direct = new WP_Filesystem_Direct( false );
			$file_content = $WP_Filesystem_Direct->get_contents( $file_name_with_full_path );

			// Create API url
			$api_url = sprintf( '%1$s/v1/files', $this->openai_api_address );

			$boundary = wp_generate_uuid4();

			$body = "--{$boundary}\r\n";
			$body .= "Content-Disposition: form-data; name=\"purpose\"\r\n\r\n";
			$body .= "assistants\r\n";
			$body .= "--{$boundary}\r\n";
			$body .= "Content-Disposition: form-data; name=\"file\"; filename=\"{$fileName}\"\r\n";
			$body .= "Content-Type: application/json\r\n\r\n";
			$body .= $file_content . "\r\n";
			$body .= "--{$boundary}--";

			$args = array(
				'method'    => 'POST',
				'timeout'   => 100,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'multipart/form-data; boundary=' . $boundary,
					'OpenAI-Beta' 	=> 'assistants=v2'
				),
				'body'      => $body,
			);

			// Send request to OpenAI's API
			$response = wp_remote_request( $api_url, $args );

			// Check for exception
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to upload a training file to OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_upload_file_error = new VAPPV_TTW_Common_Error();
				$wp_upload_file_error->message = esc_html( __( 'Failed to upload a training file to OpenAI (WP error).', 'talk-to-website' ) );
				$wp_upload_file_error->exception_data = esc_html( $error_message );
				return $wp_upload_file_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to upload a training file to OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Add log entry
			VAPPV_TTW_Core::add_log_entry(
				'info',
				sprintf(
					esc_html(
						/* translators: 1. OpenAI's API response details */
						__( 'The training file with your website data has been successfully uploaded to OpenAI. API response: %1$s.', 'talk-to-website' )
					),
					esc_html( $openai_response_raw )
				)
			);

			//Unpack results
			$results_unpacked = json_decode( $openai_response_raw, true );
			return $results_unpacked;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to upload a training file to OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error = new VAPPV_TTW_Common_Error();
			$common_error->message = esc_html( __( 'Failed to upload a training file to OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * Remove file;
	 * Based on docs:
	 * https://platform.openai.com/docs/api-reference/files/delete
	 *
	 * @param string $file_id Vector store identifier
	 *
	 * @return mixed
	 * @throws Exception
	 */
	public function delete_file( string $file_id ) {
		
		VAPPV_TTW_Core::add_log_entry(
			'info',
			sprintf(
				esc_html(
					/* translators: 1. deleting file name */
					__( 'Trying to delete a training file with ID = %1$s on the OpenAI platform...', 'talk-to-website' )
				),
				esc_html( $file_id )
			)
		);

		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/files/%2$s', $this->openai_api_address, $file_id );

			// Prepare args
			$args = array(
				'method'    => 'DELETE',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type' 	=> 'application/json',
					'OpenAI-Beta' 	=> 'assistants=v2'
				)
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for exception
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to delete training file in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_delete_file_error = new VAPPV_TTW_Common_Error();
				$wp_delete_file_error->message = esc_html( __( 'Failed to delete training file in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_delete_file_error->exception_data = esc_html( $error_message );
				return $wp_delete_file_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to delete training file in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			VAPPV_TTW_Core::add_log_entry(
				'info',
				sprintf(
					esc_html(
						/* translators: 1. OpenAI's API raw response */
						__( 'Training file deleted. API response: %1$s.', 'talk-to-website' )
					),
					esc_html( $openai_response_raw )
				)
			);

			//Unpack results
			$results_unpacked = json_decode( $openai_response_raw, true );
			return $results_unpacked;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to delete training file in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error                 = new VAPPV_TTW_Common_Error();
			$common_error->message        = esc_html( __( 'Failed to delete training file in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * Create new vector store
	 * Based on docs:
	 * https://platform.openai.com/docs/api-reference/vector-stores/create
	 * Response format:
	 * {
	 *	"id": "vs_abc123",
	 *	"object": "vector_store",
	 *	"created_at": 1699061776,
	 *	"name": "Support FAQ",
	 *	"bytes": 139920,
	 *	"file_counts": {
	 *	"in_progress": 0,
	 *	"completed": 3,
	 *	"failed": 0,
	 *	"cancelled": 0,
	 *	"total": 3
	 *  }
	 *
	 * @param string $name
	 * @param array $file_ids
	 *
	 * @return mixed
	 * @throws Exception
	 */
	public function create_vector_store( string $name, array $file_ids ) {
		
		VAPPV_TTW_Core::add_log_entry(
			'info',
			sprintf(
				esc_html(
					/* translators: 1. vector store name */
					__( 'Trying to create new vector store in OpenAI via API.. Store name = %1$s.', 'talk-to-website' )
				),
				esc_html( $name )
			)
		);

		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/vector_stores', $this->openai_api_address );

			$body = array(
				'name'     => $name,
				'file_ids' => $file_ids
			);

			$args = array(
				'method'    => 'POST',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type' 	=> 'application/json',
					'OpenAI-Beta' 	=> 'assistants=v2'
				),
				'body'      => wp_json_encode( $body )
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to create a vector store in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_create_vector_store_error = new VAPPV_TTW_Common_Error();
				$wp_create_vector_store_error->message = esc_html( __( 'Failed to create a vector store in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_create_vector_store_error->exception_data = esc_html( $error_message );
				return $wp_create_vector_store_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to create a vector store in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Unpack results
			VAPPV_TTW_Core::add_log_entry(
				'info',
				sprintf(
					esc_html(
						/* translators: 1. OpenAI's API raw response */
						__( 'Vector Store has been created and linked to the training file. API response: %1$s.', 'talk-to-website' )
					),
					esc_html( $openai_response_raw )
				)
			);

			$results_unpacked = json_decode( $openai_response_raw, true );
			return $results_unpacked;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to create a vector store in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error                 = new VAPPV_TTW_Common_Error();
			$common_error->message        = esc_html( __( 'Failed to create a vector store in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * Remove vector store;
	 * Based on docs:
	 * https://platform.openai.com/docs/api-reference/vector-stores/delete
	 *
	 * @param string $vector_store_id Vector store identifier
	 *
	 * @return mixed
	 * @throws Exception
	 */
	public function delete_vector_store( string $vector_store_id ) {
		
		VAPPV_TTW_Core::add_log_entry(
			'info',
			sprintf(
				esc_html(
					/* translators: 1. vector store identifier */
					__( 'Trying to delete a vector store from the OpenAI platform via API.. Store ID = %1$s.', 'talk-to-website' )
				),
				esc_html( $vector_store_id )
			)
		);

		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/vector_stores/%2$s', $this->openai_api_address, $vector_store_id );

			$args = array(
				'method'    => 'DELETE',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json',
					'OpenAI-Beta'   => 'assistants=v2'
				)
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to delete a vector store in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_delete_vector_store_error = new VAPPV_TTW_Common_Error();
				$wp_delete_vector_store_error->message = esc_html( __( 'Failed to delete a vector store in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_delete_vector_store_error->exception_data = esc_html( $error_message );
				return $wp_delete_vector_store_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to delete a vector store in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Unpack results
			VAPPV_TTW_Core::add_log_entry(
				'info',
				sprintf(
					esc_html(
						/* translators: 1. OpenAI's API raw response */
						__( 'Vector store deleted. API response: %1$s.', 'talk-to-website' )
					),
					esc_html( $openai_response_raw )
				)
			);

			$results_unpacked = json_decode( $openai_response_raw, true );
			return $results_unpacked;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to delete a vector store in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error                 = new VAPPV_TTW_Common_Error();
			$common_error->message        = esc_html( __( 'Failed to delete a vector store in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * Create new assistant;
	 * Based on docs:
	 * https://platform.openai.com/docs/api-reference/assistants/createAssistant ;
	 * https://platform.openai.com/docs/assistants/tools/knowledge-retrieval ;
	 * Response format:
	 * {
	 *	    "id": "asst_abc123",
	 *	    "object": "assistant",
	 *	    "created_at": 1698984975,
	 *	    "name": "Math Tutor",
	 *	    "description": null,
	 *	    "model": "gpt-4-turbo",
	 *	    "instructions": "You are a personal math tutor. When asked a question, write and run Python code to answer the question.",
	 *	    "tools": [{ "type": "file_search" }],
	 *	    "metadata": {},
	 *	    "top_p": 1.0,
	 *	    "temperature": 1.0,
	 *	    "response_format": "auto"
	 *	}
	 *
	 * @param array $assistantObject Assistant object
	 *
	 * @return VAPPV_TTW_Common_Error|VAPPV_TTW_OpenAI_API_Error|array
	 * @throws Exception
	 */
	public function create_assistant( array $assistantObject ) {
		
		VAPPV_TTW_Core::add_log_entry(
			'info',
			sprintf(
				esc_html(
					/* translators: 1. assistant name */
					__( 'Trying to create a new assistant in OpenAI via API.. Name = %1$s.', 'talk-to-website' )
				),
				esc_html( $assistantObject['name'] )
			)
		);

		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/assistants', $this->openai_api_address );

			$args = array(
				'method'    => 'POST',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json',
					'OpenAI-Beta'   => 'assistants=v2'
				),
				'body'      => wp_json_encode( $assistantObject )
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to create an AI assistant in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_create_ai_assistant_error = new VAPPV_TTW_Common_Error();
				$wp_create_ai_assistant_error->message = esc_html( __( 'Failed to create an AI assistant in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_create_ai_assistant_error->exception_data = esc_html( $error_message );
				return $wp_create_ai_assistant_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to create an AI assistant in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Unpack results
			VAPPV_TTW_Core::add_log_entry(
				'info',
				sprintf(
					esc_html(
						/* translators: 1. OpenAI's API raw response */
						__( 'An AI assistant has been created and linked to the training file. API response: %1$s.', 'talk-to-website' )
					),
					esc_html( $openai_response_raw )
				)
			);

			$results_unpacked = json_decode( $openai_response_raw, true );
			return $results_unpacked;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to create an AI assistant in OpenAI. Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error                 = new VAPPV_TTW_Common_Error();
			$common_error->message        = esc_html( __( 'Failed to create an AI assistant in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * Remove assistant;
	 * Based on docs:
	 * https://platform.openai.com/docs/api-reference/assistants/deleteAssistant
	 *
	 * @param string $assistant_id Assistant identifier
	 *
	 * @return mixed
	 * @throws Exception
	 */
	public function delete_assistant( string $assistant_id ) {
		
		VAPPV_TTW_Core::add_log_entry(
			'info',
			sprintf(
				esc_html(
					/* translators: 1. assistant identifier */
					__( 'Trying to delete an AI assistant in OpenAI via API.. ID = %1$s.', 'talk-to-website' )
				),
				esc_html( $assistant_id )
			)
		);

		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/assistants/%2$s', $this->openai_api_address, $assistant_id );

			$args = array(
				'method'    => 'DELETE',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json',
					'OpenAI-Beta'   => 'assistants=v2'
				)
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to delete an AI assistant in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_delete_ai_assistant_error = new VAPPV_TTW_Common_Error();
				$wp_delete_ai_assistant_error->message = esc_html( __( 'Cannot delete AI assistant (WP error).', 'talk-to-website' ) );
				$wp_delete_ai_assistant_error->exception_data = esc_html( $error_message );
				return $wp_delete_ai_assistant_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to delete an AI assistant in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Unpack results
			VAPPV_TTW_Core::add_log_entry(
				'info',
				sprintf(
					esc_html(
						/* translators: 1. OpenAI's raw response */
						__( 'AI assistant deleted. API response: %1$s.', 'talk-to-website' )
					),
					esc_html( $openai_response_raw )
				)
			);

			$results_unpacked = json_decode( $openai_response_raw, true );
			return $results_unpacked;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to delete an AI assistant in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error                 = new VAPPV_TTW_Common_Error();
			$common_error->message        = esc_html( __( 'Failed to delete an AI assistant in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * Update OpenAI assistant primary params (model and description );
	 * Based on docs:
	 * https://platform.openai.com/docs/api-reference/assistants/modifyAssistant ;
	 * Response format:
	 * {
	 *   "id": "asst_abc123",
	 *   "object": "assistant",
	 *   "created_at": 1699009709,
	 *   "name": "HR Helper",
	 *   "description": null,
	 *   "model": "gpt-4",
	 *   "instructions": "You are an HR bot, and you have access to files to answer employee questions about company policies. Always response with info from either of the files.",
	 *   "tools": [{ "type": "retrieval" }],
	 *   "file_ids": ["file-abc123", "file-abc456"],
	 *   "metadata": {}
	 * }
	 *
	 * @param string $assistant_id Assistant identifier
	 * @param string $assistant_new_gpt_model Assistant new GPT model
	 * @param string $assistant_new_magic_instructions Assistant magic instructions
	 *
	 * @return VAPPV_TTW_Common_Error|VAPPV_TTW_OpenAI_API_Error|array|string
	 * @throws Exception
	 */
	public function modify_assistant_primary_params(
		string $assistant_id,
		string $assistant_new_gpt_model,
		string $assistant_new_magic_instructions,
		float $temperature,
		array $file_store_ids
	) {
		
		VAPPV_TTW_Core::add_log_entry(
			'info',
			sprintf(
				esc_html(
					/* translators: 1. assistant identifier */
					__( 'Trying to update an existing AI assistant in OpenAI via API.. ID = %1$s.', 'talk-to-website' )
				),
				esc_html( $assistant_id )
			)
		);

		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/assistants/%2$s', $this->openai_api_address, $assistant_id );

			$assistant_new_params = array(
				'model'        => $assistant_new_gpt_model,
				'instructions' => $assistant_new_magic_instructions,
				'temperature'  => $temperature
			);
			if ( ! is_null( $file_store_ids ) && count( $file_store_ids ) > 0 ) {
				$assistant_new_params['tool_resources'] = array(
					'file_search' => array(
						'vector_store_ids' => $file_store_ids
					)
				);
			}

			$args = array(
				'method'    => 'POST',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json',
					'OpenAI-Beta'   => 'assistants=v2'
				),
				'body'      => wp_json_encode( $assistant_new_params )
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to mofidy an AI assistant data in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_delete_vector_store_error = new VAPPV_TTW_Common_Error();
				$wp_delete_vector_store_error->message = esc_html( __( 'Failed to mofidy an AI assistant data in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_delete_vector_store_error->exception_data = esc_html( $error_message );
				return $wp_delete_vector_store_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to mofidy an AI assistant data in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Unpack results
			VAPPV_TTW_Core::add_log_entry(
				'info',
				sprintf(
					esc_html(
						/* translators: 1. OpenAI's API raw response */
						__( 'An assistant has been updated. API response: %1$s.', 'talk-to-website' )
					),
					esc_html( $openai_response_raw )
				)
			);

			$results_unpacked = json_decode( $openai_response_raw, true );
			return $results_unpacked;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to mofidy an AI assistant data in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error = new VAPPV_TTW_Common_Error();
			$common_error->message = esc_html( __( 'Failed to mofidy an AI assistant data in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * Create conversation thread ;
	 * Based on docs: https://platform.openai.com/docs/api-reference/threads/createThread ;
	 * Response format:
	 * {
	 *   "id": "thread_abc123",
	 *   "object": "thread",
	 *   "created_at": 1699012949,
	 *   "metadata": {}
	 * }
	 * @return VAPPV_TTW_Common_Error|VAPPV_TTW_OpenAI_API_Error|array|string
	 * @throws Exception
	 */
	public function create_thread() {
		
		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/threads', $this->openai_api_address );

			$args = array(
				'method'    => 'POST',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json',
					'OpenAI-Beta'   => 'assistants=v2'
				)
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to create a thread for conversation with AI in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_cannot_create_thread_error = new VAPPV_TTW_Common_Error();
				$wp_cannot_create_thread_error->message = esc_html( __( 'Failed to create a thread for conversation with AI in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_cannot_create_thread_error->exception_data = esc_html( $error_message );
				return $wp_cannot_create_thread_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to create a thread for conversation with AI in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Unpack results
			$thread_object = json_decode( $openai_response_raw, true );
			return $thread_object;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to create a thread for conversation with AI in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error                 = new VAPPV_TTW_Common_Error();
			$common_error->message        = esc_html( __( 'Failed to create a thread for conversation with AI in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * Create message addressed to the specified thread ;
	 * Based on docs:
	 * https://platform.openai.com/docs/api-reference/messages/createMessage ;
	 * Response format:
	 * {
	 *   "id": "msg_abc123",
	 *   "object": "thread.message",
	 *   "created_at": 1699017614,
	 *   "thread_id": "thread_abc123",
	 *   "role": "user",
	 *   "content": [{ "type": "text", "text": { "value": "How does AI work? Explain it in simple terms.", "annotations": [] }}],
	 *   "file_ids": [],
	 *   "assistant_id": null,
	 *   "run_id": null,
	 *   "metadata": {}
	 * }
	 *
	 * @param string $thread_id OpenAI thread identifier
	 * @param string $message_text Message text
	 *
	 * @return VAPPV_TTW_Common_Error|VAPPV_TTW_OpenAI_API_Error|array|string
	 * @throws Exception
	 */
	public function create_message( string $thread_id, string $message_text ) {
		
		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/threads/%2$s/messages', $this->openai_api_address, $thread_id );

			$args = array(
				'method'    => 'POST',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json',
					'OpenAI-Beta'   => 'assistants=v2'
				),
				'body'      => wp_json_encode( array(
					'role'     => 'user',
					'content'  => $message_text,
					'metadata' => array(
						'is_message_from_user' => '1'
					)
				) )
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to create a message from the user in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_cannot_create_message_error = new VAPPV_TTW_Common_Error();
				$wp_cannot_create_message_error->message = esc_html( __( 'Failed to create a message from the user in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_cannot_create_message_error->exception_data = esc_html( $error_message );
				return $wp_cannot_create_message_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to create a message from the user in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Unpack results
			$results_unpacked = json_decode( $openai_response_raw, true );
			return $results_unpacked;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to create a message from the user in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error = new VAPPV_TTW_Common_Error();
			$common_error->message = esc_html( __( 'Failed to create a message from the user in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * List messages based on specified thread ;
	 * Based on docs: https://platform.openai.com/docs/api-reference/messages/listMessages ;
	 * Response format:
	 * {
	 *   "object": "list",
	 *   "data": [{
	 *       "id": "msg_abc123",
	 *       "object": "thread.message",
	 *       "created_at": 1699016383,
	 *       "thread_id": "thread_abc123",
	 *       "role": "user",
	 *       "content": [{ "type": "text", "text": { "value": "How does AI work? Explain it in simple terms.", "annotations": [] }}],
	 *       "file_ids": [],
	 *       "assistant_id": null,
	 *       "run_id": null,
	 *       "metadata": {}
	 *   }],
	 *   "first_id": "msg_abc123",
	 *   "last_id": "msg_abc456",
	 *   "has_more": false
	 * }
	 *
	 * @param string $thread_id OpenAI thread identifier
	 * @param string $unanswered_message_id Unanswered message ID
	 * @param int $limit Messages limit
	 *
	 * @return VAPPV_TTW_Common_Error|VAPPV_TTW_OpenAI_API_Error|array|string
	 * @throws Exception
	 */
	public function list_messages( string $thread_id, string $unanswered_message_id, int $limit ) {
		
		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/threads/%2$s/messages?after=%3$s&order=asc&limit=%4$s', $this->openai_api_address, $thread_id, $unanswered_message_id, $limit );

			$args = array(
				'method'    => 'GET',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json',
					'OpenAI-Beta'   => 'assistants=v2'
				)
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to list messages inside AI thread in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_cannot_list_messages_error = new VAPPV_TTW_Common_Error();
				$wp_cannot_list_messages_error->message = esc_html( __( 'Failed to list messages inside AI thread in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_cannot_list_messages_error->exception_data = esc_html( $error_message );
				return $wp_cannot_list_messages_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to list messages inside AI thread in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Unpack results
			$results_decoded = json_decode( $openai_response_raw, true );
			return $results_decoded;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to list messages inside AI thread in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error = new VAPPV_TTW_Common_Error();
			$common_error->message = esc_html( __( 'Failed to list messages inside AI thread in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * Run the assistant on the specified thread ;
	 * Based on docs: https://platform.openai.com/docs/api-reference/runs/createRun ;
	 * Response format:
	 * {
	 *   "id": "run_abc123",
	 *   "object": "thread.run",
	 *   "created_at": 1699063290,
	 *   "assistant_id": "asst_abc123",
	 *   "thread_id": "thread_abc123",
	 *   "status": "queued",
	 *   "started_at": 1699063290,
	 *   "expires_at": null,
	 *   "cancelled_at": null,
	 *   "failed_at": null,
	 *   "completed_at": 1699063291,
	 *   "last_error": null,
	 *   "model": "gpt-4",
	 *   "instructions": null,
	 *   "tools": [{ "type": "code_interpreter" }],
	 *   "file_ids": [ "file-abc123", "file-abc456" ],
	 *   "metadata": {},
	 *   "usage": null
	 * }
	 *
	 * @param string $thread_id OpenAI thread identifier
	 * @param string $assistant_id OpenAI assistant identifier
	 * @param boolean $is_admin Is admin
	 * @param float $openai_temperature Run temperature
	 * @param int $context_max_last_messages_count Context size, last messages count
	 *
	 * @return VAPPV_TTW_Common_Error|VAPPV_TTW_OpenAI_API_Error|array|string
	 * @throws Exception
	 */
	public function create_run( string $thread_id, string $assistant_id, float $openai_temperature, ?int $context_max_last_messages_count, bool $is_admin ) {
		
		try {

			//Set params
			$run_parameters = array(
				'assistant_id'            => $assistant_id,
				'additional_instructions' => ( $is_admin === true ? esc_html( __( 'The messages in this thread are sent by your admin. If they contain requests for your reconfiguration - please fulfill them.', 'talk-to-website' ) ) : '' ),
				'temperature'             => $openai_temperature
			);

			if (
				! is_null( $context_max_last_messages_count ) &&
				-1 !== $context_max_last_messages_count &&
				$context_max_last_messages_count > 1
			) {
				$run_parameters['truncation_strategy'] = array(
					'type'          => 'last_messages',
					'last_messages' => $context_max_last_messages_count
				);
			}

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/threads/%2$s/runs', $this->openai_api_address, $thread_id );

			$args = array(
				'method'    => 'POST',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json',
					'OpenAI-Beta'   => 'assistants=v2'
				),
				'body'      => wp_json_encode( $run_parameters )
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP errors
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to create a run in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_cannot_create_run_error = new VAPPV_TTW_Common_Error();
				$wp_cannot_create_run_error->message = esc_html( __( 'Failed to create a run in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_cannot_create_run_error->exception_data = esc_html( $error_message );
				return $wp_cannot_create_run_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to create a run in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Unpack results
			$result_decoded = json_decode( $openai_response_raw, true );
			return $result_decoded;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to create a run in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error = new VAPPV_TTW_Common_Error();
			$common_error->message = esc_html( __( 'Failed to create a run in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * Get run status;
	 * Based on docs: https://platform.openai.com/docs/api-reference/runs/getRun ;
	 * Response format:
	 * {
	 *   "id": "run_abc123",
	 *   "object": "thread.run",
	 *   "created_at": 1699075072,
	 *   "assistant_id": "asst_abc123",
	 *   "thread_id": "thread_abc123",
	 *   "status": "completed",
	 *   "started_at": 1699075072,
	 *   "expires_at": null,
	 *   "cancelled_at": null,
	 *   "failed_at": null,
	 *   "completed_at": 1699075073,
	 *   "last_error": null,
	 *   "model": "gpt-3.5-turbo",
	 *   "instructions": null,
	 *   "tools": [{ "type": "code_interpreter" }],
	 *   "file_ids": ["file-abc123", "file-abc456"],
	 *   "metadata": {},
	 *   "usage": { "prompt_tokens": 123, "completion_tokens": 456, "total_tokens": 579 }
	 * }
	 *
	 * @param string $thread_id OpenAI thread identifier
	 * @param string $run_id OpenAI run identifier
	 *
	 * @return VAPPV_TTW_Common_Error|VAPPV_TTW_OpenAI_API_Error|array|string
	 * @throws Exception
	 */
	public function retrieve_run( string $thread_id, string $run_id ) {
		
		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/threads/%2$s/runs/%3$s', $this->openai_api_address, $thread_id, $run_id );

			$args = array(
				'method'    => 'GET',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json',
					'OpenAI-Beta'   => 'assistants=v2'
				)
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to retrieve a run in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_cannot_retrieve_run_error = new VAPPV_TTW_Common_Error();
				$wp_cannot_retrieve_run_error->message = esc_html( __( 'Failed to retrieve a run in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_cannot_retrieve_run_error->exception_data = esc_html( $error_message );
				return $wp_cannot_retrieve_run_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to retrieve a run in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			//Unpack results
			$result_decoded = json_decode( $openai_response_raw, true );
			return $result_decoded;
			
		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to retrieve a run in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error = new VAPPV_TTW_Common_Error();
			$common_error->message = esc_html( __( 'Failed to retrieve a run in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}

	/**
	 * List OpenAI available models
	 * @return VAPPV_TTW_Common_Error|VAPPV_TTW_OpenAI_API_Error|array|string
	 * @throws Exception
	 */
	public function list_available_models() {

		try {

			// Prepare API url
			$api_url = sprintf( '%1$s/v1/models', $this->openai_api_address );

			$args = array(
				'method'    => 'GET',
				'timeout'   => 20,
				'headers'   => array(
					'Authorization' => 'Bearer ' . $this->api_key,
					'Content-Type'  => 'application/json',
					'OpenAI-Beta'   => 'assistants=v2'
				)
			);

			// Send request to the OpenAI's server
			$response = wp_remote_request( $api_url, $args );

			// Check for WP error
			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s',
						esc_html( __( 'Failed to list available models in OpenAI (WP error).', 'talk-to-website' ) ),
						esc_html( $error_message )
					)
				);

				$wp_cannot_list_models_error = new VAPPV_TTW_Common_Error();
				$wp_cannot_list_models_error->message = esc_html( __( 'Failed to list available models in OpenAI (WP error).', 'talk-to-website' ) );
				$wp_cannot_list_models_error->exception_data = esc_html( $error_message );
				return $wp_cannot_list_models_error;

			}

			// Get response body
			$openai_response_raw = wp_remote_retrieve_body( $response );

			// Get response code
			$openai_response_code = wp_remote_retrieve_response_code( $response );

			//Check response for error
			if ( 200 !== $openai_response_code ) {

				$extracted_error = VAPPV_TTW_Exceptions_Environment::extract_error_from_openai_response( $openai_response_code, $openai_response_raw );
				VAPPV_TTW_Core::add_log_entry(
					'error',
					sprintf(
						'%1$s // %2$s // StatusCode = %3$s',
						esc_html( __( 'Failed to list available models in OpenAI (Server responded with an error).', 'talk-to-website' ) ),
						$extracted_error->message,
						$openai_response_code
					)
				);
				return $extracted_error;

			}

			return json_decode( $openai_response_raw, true );

		} catch ( Throwable $t ) {

			// Executed only in PHP 7, will not match in PHP 5
			VAPPV_TTW_Core::add_log_entry(
				'error',
				sprintf(
					esc_html(
						/* translators: 1. throwable details */
						__( 'Failed to list available models in OpenAI (Plugin error). Details: %1$s.', 'talk-to-website' )
					),
					esc_html( $t->getMessage() )
				)
			);

			$common_error = new VAPPV_TTW_Common_Error();
			$common_error->message = esc_html( __( 'Failed to list available models in OpenAI (Plugin error).', 'talk-to-website' ) );
			$common_error->exception_data = esc_html( $t->getMessage() );
			return $common_error;

		}
	}
}

/*
	© 2024 Copyright Vitalii A. Popov. All rights reserved.
*/

?>