<?php

namespace Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints;

use Limb_Chatbot\Includes\AI_Providers\Gemini\Utilities\Chatbot_Utility;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Services\Message_Service;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Utilities\Copilot_Utility;
use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints\Generate_Content\Tool_Calls_Message;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints\Generate_Content\Tool_Result_Message;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints\Generate_Content\Handlers\Generate_Content_Response_Handler;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints\Generate_Content\Stream_Parser;
use Limb_Chatbot\Includes\AI_Providers\Gemini\Gemini;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\Collection;

/**
 * Class Generate_Content_Endpoint
 *
 * Handles content generation using the Gemini AI model.
 *
 * @package Limb_Chatbot\Includes\AI_Providers\Gemini\Endpoints
 * @since 1.0.0
 */
class Generate_Content_Endpoint extends Gemini_Endpoint {

	/**
	 * Generate_Content_Endpoint constructor.
	 *
	 * @param  Chatbot_Utility|Copilot_Utility  $utility  Utility instance for configuration.
	 *
	 * @since 1.0.0
	 */
	public function __construct( $utility ) {
		parent::__construct( $utility );
	}

	/**
	 * Generates content based on the provided messages and model.
	 *
	 * @return Message|null The generated response message.
	 * @throws Exception If generation fails.
	 * @since 1.0.0
	 */
	public function generate() {
		$is_stream = (bool) $this->utility->global_utility->get_stream();
		$model = $this->utility->global_utility->get_ai_model();
		$body  = array(
			'contents' => $this->prepare_messages( $model ),
			'tools'    => $this->utility->get_tools(),
		);
		if ( $tokens = $this->utility->global_utility->get_max_completion_tokens() ) {
			$body['generationConfig']['maxOutputTokens'] = $tokens;
		}
		$body['generationConfig']['temperature'] = 0; // Make this dynamic
		if ( $system_instructions = $this->utility->get_system_instructions() ) {
			$body['systemInstruction'] = array( 'parts' => array( 'text' => $system_instructions ) );
		}
		$args        = [
			'body'    => wp_json_encode( $body ),
			'headers' => $this->get_header(),
			'timeout' => $this->utility->get_timeout() ?? 60,
		];
		$http_client = $this->http_client_factory( $is_stream );
		if ( $is_stream ) {
			$stream_parser         = new Stream_Parser();
			$args['stream_parser'] = array( $stream_parser, 'parser' );
		}
		$response = $http_client->post( $this->generate_endpoint( $is_stream, $model ), $args );

		return ( new Generate_Content_Response_Handler( $response, $http_client, $this, $stream_parser ?? null ) )->get_message();
	}

	/**
	 * Prepares an array of messages for the API request body.
	 *
	 * @param  mixed  $model  The AI model being used.
	 *
	 * @return array List of formatted messages.
	 * @throws Exception If messages array is empty.
	 * @since 1.0.0
	 */
	protected function prepare_messages( $model ) {
		foreach ( $this->utility->global_utility->get_messages() as $message ) {
			if ( $message = $this->prepare_individual_message( $message, $model ) ) {
				$messages[] = $message;
			}
		}
		if ( empty( $messages ) ) {
			throw new Exception( Error_Codes::CHATBOT_NO_MESSAGES, __( 'Messages are empty', 'limb-chatbot' ) );
		}

		return $messages;
	}

	/**
	 * Prepares a single message for API consumption.
	 *
	 * @param  mixed  $message  The message instance (Message, Tool_Calls_Message, or Tool_Result_Message).
	 * @param  mixed  $model  The AI model instance.
	 *
	 * @return array|null The formatted message or null.
	 * @throws Exception If unsupported message type is given.
	 * @since 1.0.0
	 */
	protected function prepare_individual_message( $message, $model ) {
		if ( $message instanceof Tool_Calls_Message || $message instanceof Tool_Result_Message ) {
			return array(
				'role'  => Gemini::ROLE_ASSISTANT,
				'parts' => $message->get_parts(),
			);
		} elseif ( $message instanceof Message ) {
			return ( new Message_Service() )->prepare_message( $message );
		} else {
			throw new Exception( Error_Codes::NOT_SUPPORTED, __( 'Not supported message !', 'limb-chatbot' ) );
		}
	}

	/**
	 * Generates the API endpoint URL based on stream and model.
	 *
	 * @param  bool  $is_stream  Whether streaming is enabled.
	 * @param  mixed  $model  The AI model instance.
	 *
	 * @return string Full endpoint URL.
	 * @throws Exception
	 * @since 1.0.0
	 */
	protected function generate_endpoint( $is_stream, $model ) {
		return self::API_BASE_URL . $this->utility->get_version() . '/models/' . $model->get_name() . ':' . ( $is_stream ? 'streamGenerateContent' : 'generateContent' ) . '?key=' . $this->get_auth_key();
	}

	/**
	 * Send a request to the AI endpoint.
	 *
	 * @param  Collection  $messages  Messages to send.
	 * @param  bool  $stream  Whether to stream the response.
	 * @param  object  $http_client  HTTP client instance.
	 * @param  array  $args  Optional generation parameters (e.g. temperature).
	 * @param  Stream_Parser|null  $stream_parser  When streaming, use this parser so the same instance is fed by the client and passed to the response handler. If null and $stream is true, a new parser is created.
	 *
	 * @return object Response from HTTP client.
	 * @throws Exception
	 * @since 1.0.0
	 */
	private function make_request( $messages, $stream, $http_client, $args = [], $stream_parser = null ) {
		$model       = null;
		if ( property_exists( $this->utility, 'ai_model' ) && ! empty( $this->utility->ai_model ) ) {
			$model = $this->utility->ai_model;
		}
		if ( empty( $model ) || ( ! $model instanceof AI_Model ) ) {
			$model = $this->utility->global_utility->get_ai_model();
		}
		$system_message = $messages->shift();
		$body           = array(
			'contents' => $messages->map( function ( $message ) use ( $model ) {
				return $this->prepare_individual_message( $message, $model );
			} )->items
		);
		if ( ! empty( $args ) ) {
			$body['generationConfig'] = $args;
		}
		if ( $system_message && $system_message->get_role() === 'system' ) {
			$body['systemInstruction'] = array( 'parts' => array( 'text' => $system_message->get_content()[0]['text']['value'] ) );
		}
		$request_args = [
			'headers' => $this->get_header(),
			'body'    => wp_json_encode( $body ),
			'timeout' => $this->utility->get_timeout() ?? 60,
		];
		if ( $stream ) {
			if ( $stream_parser === null ) {
				$stream_parser = new Stream_Parser();
			}
			$request_args['stream_parser'] = array( $stream_parser, 'parser' );
		}

		return $http_client->post( $this->generate_endpoint( $stream, $model ), $request_args );
	}

	/**
	 * Determine if vector search is applicable for the given messages.
	 *
	 * @param  Collection  $messages  Collection of Message objects.
	 *
	 * @return Message Response from the handler indicating applicability.
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function is_vector_search_applicable( Collection $messages ) {
		$http_client = $this->http_client_factory();
		$response    = $this->make_request( $messages, false, $http_client, [ 'temperature' => '0.2' ] );

		return ( new Generate_Content_Response_Handler( $response,
			$http_client,
			$this,
			null ) )->get_response_message();
	}

	/**
	 * Analyze a single turn of conversation.
	 *
	 * @param  Collection|array  $messages  Messages for analysis.
	 *
	 * @return array JSON-decoded response from AI endpoint.
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function analyze_turn( $messages ) {
		$http_client = $this->http_client_factory();
		$response    = $this->make_request( $messages,false,
			$http_client,
			[ 'temperature' => 0.1, 'responseMimeType' => 'application/json' ] );

		return ( new Generate_Content_Response_Handler( $response, $http_client, $this, null ) )->get_json();
	}

	/**
	 * Generate simple text response from AI.
	 *
	 * This method generates a straightforward text response from the AI model
	 * and returns just the text content without any additional structure.
	 *
	 * @param  Collection  $messages  Collection of Message objects for the AI.
	 *
	 * @return Message The generated text response.
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function generate_data_collect_question( Collection $messages, $stream = false ) {
		$http_client = $this->http_client_factory( $stream );
		$parser      = $stream ? new Stream_Parser() : null;
		$response    = $this->make_request( $messages, $stream, $http_client, [ 'temperature' => 0.7 ], $parser );

		return ( new Generate_Content_Response_Handler( $response,
			$http_client,
			$this,
			$parser ) )->get_message();
	}

	/**
	 * Generate a beautiful success message for action completion
	 *
	 * Creates a warm, celebratory message for users after they complete an action.
	 * Uses slightly higher temperature (0.7) for more creative, engaging responses.
	 *
	 * @param  Collection  $messages  Collection of Message objects with system and user prompts
	 *
	 * @return Message The generated success message
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function generate_action_success_message( Collection $messages, $stream = false ): Message {
		$http_client = $this->http_client_factory( $stream );
		$parser      = $stream ? new Stream_Parser() : null;
		$response    = $this->make_request( $messages, $stream, $http_client, [ 'temperature' => 0.7 ], $parser );

		// Extract and return text from the message
		return ( new Generate_Content_Response_Handler( $response, $http_client, $this, $parser ) )->get_message();
	}

	/**
	 * Generate action success message with extracted variables context
	 *
	 * Creates a beautiful success message that includes information from extracted variables.
	 * Uses slightly higher temperature (0.7) for more creative, engaging responses.
	 *
	 * @param  Collection  $messages  Collection of Message objects with context and variables
	 *
	 * @return Message The generated success message with extracted variables context
	 * @throws Exception If message generation fails
	 * @since 1.0.0
	 */
	public function rerank( $messages ) {
		$http_client = $this->http_client_factory();
		$response    = $this->make_request( $messages, false, $http_client, [ 'temperature' => 0 ] );

		// Extract and return text from the message
		return ( new Generate_Content_Response_Handler( $response, $http_client, $this, null ) )->get_message();
	}
}