<?php

namespace Limb_Chatbot\Includes\AI_Providers\Open_Router\Endpoints;

use Limb_Chatbot\Includes\AI_Providers\Open_Router\Services\Message_Service;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\AI_Providers\Open_Router\Endpoints\Chat_Completion\Tool_Calls_Message;
use Limb_Chatbot\Includes\AI_Providers\Open_Router\Endpoints\Chat_Completion\Tool_Result_Message;
use Limb_Chatbot\Includes\AI_Providers\Open_Router\Endpoints\Chat_Completion\Handlers\Chat_Completion_Response_Handler;
use Limb_Chatbot\Includes\AI_Providers\Open_Router\Endpoints\Chat_Completion\Stream_Parser;
use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\Collection;

/**
 * Class Chat_Completion_Endpoint
 *
 * Handles OpenRouter Chat Completion API interactions for assistant responses.
 *
 * @package Limb_Chatbot\Includes\AI_Providers\Open_Router\Endpoints
 * @since 1.0.14
 */
class Chat_Completion_Endpoint extends Open_Router_Endpoint {

	/**
	 * Sends a chat completion request to the OpenRouter API.
	 *
	 * Prepares the message body, attaches tools and streaming configs,
	 * and returns the AI-generated message.
	 *
	 * @return Message|Tool_Calls_Message The generated message from OpenRouter.
	 * @throws Exception If messages are empty or invalid.
	 * @since 1.0.14
	 */
	public function generate() {
		$model       = $this->utility->global_utility->get_ai_model();

		$is_stream   = $this->utility->global_utility->get_stream();
		$messages    = $this->prepare_messages( $model );
		$http_client = $this->http_client_factory( $is_stream );
		$body        = array(
			'model'       => $model->get_name(),
			'messages'    => $messages,
			'tools'       => $this->utility->get_tools(),
			'temperature' => 0,
		);
		if ( $max_completion_tokens = $this->utility->global_utility->get_max_completion_tokens() ) {
			$body['max_completion_tokens'] = $max_completion_tokens;
		}
		if ( $is_stream ) {
			$body['stream']         = $is_stream;
			$body['stream_options'] = array( 'include_usage' => true );
		}
		$args = [
			'body'    => json_encode( $body ),
			'headers' => $this->get_header(),
			'timeout' => $this->utility->get_timeout() ?? 60
		];
		if ( $is_stream ) {
			$stream_parser         = new Stream_Parser();
			$args['stream_parser'] = array( $stream_parser, 'parser' );
		}
		$response = $http_client->post( self::API_BASE_URL . '/chat/completions', $args );

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

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

		return $messages;
	}

	/**
	 * Prepares a single message based on its type.
	 *
	 * @param  mixed  $message  A message object (Message, Tool_Calls_Message, or Tool_Result_Message).
	 * @param  AI_Model  $model  The current model for compatibility checks.
	 *
	 * @return array The API-ready message structure.
	 * @throws Exception If message type is unsupported.
	 * @since 1.0.14
	 */
	protected function prepare_individual_message( $message, AI_Model $model ) {
		if ( $message instanceof Tool_Calls_Message ) {
			$individual_message = array(
				'role'       => $message->get_role(),
				'tool_calls' => $message->get_tool_calls(),
			);
		} elseif ( $message instanceof Tool_Result_Message ) {
			$individual_message = array(
				'role'         => $message->get_role(),
				'content'      => is_string( $message->get_content() ) ? $message->get_content() : json_encode( $message->get_content() ),
				'tool_call_id' => $message->get_tool_call_id(),
			);
		} elseif ( $message instanceof Message ) {
			$individual_message = ( new Message_Service() )->prepare_message( $message, $model );
		} else {
			throw new Exception( Error_Codes::NOT_SUPPORTED, __( 'Not supported message !', 'limb-chatbot' ) );
		}

		return $individual_message;
	}

	/**
	 * Generate a search query using AI chat completions.
	 *
	 * Sends a collection of messages to the AI model to produce a
	 * keyword-rich search query suitable for knowledge retrieval.
	 *
	 * @param  mixed  $messages  Collection of messages to process.
	 *
	 * @return Message The AI-generated response message containing the search query.
	 *
	 * @throws Exception
	 * @since 1.0.14
	 */
	public function is_vector_search_applicable( $messages ) {
		return $this->make_request( $messages, false, [ 'temperature' => 0.3 ] )->get_message();
	}

	/**
	 * Send a request to the AI endpoint.
	 *
	 * @param  Collection|array  $messages  Messages to send.
	 * @param  bool  $stream  Whether to use streaming.
	 * @param  array  $params  Optional generation parameters.
	 *
	 * @return Chat_Completion_Response_Handler Response handler.
	 * @throws Exception
	 * @since 1.0.14
	 */
	public function make_request( $messages, $stream = false, array $params = [] ) {
		$http_client = $this->http_client_factory( $stream );
		$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();
		}
		$body = array_merge( array(
			'model'    => $model->get_name(),
			'messages' => $messages->map( function ( $message ) use ( $model ) {
				return $this->prepare_individual_message( $message, $model );
			} )->items,
		), $params );
		if ( $stream ) {
			$body['stream_options'] = array( 'include_usage' => true );
			$body['stream']         = $stream;
		}
		$args = [
			'body'    => json_encode( $body ),
			'headers' => $this->get_header(),
			'timeout' => $this->utility->get_timeout() ?? 60
		];
		if ( $stream ) {
			$stream_parser         = new Stream_Parser();
			$args['stream_parser'] = array( $stream_parser, 'parser' );
		}
		$response = $http_client->post( self::API_BASE_URL . '/chat/completions', $args );

		return ( new Chat_Completion_Response_Handler( $response, $http_client, $this, $stream_parser ?? null ) );
	}

	/**
	 * 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.14
	 */
	public function analyze_turn( $messages ) {
		return $this->make_request( $messages, false, [ 'temperature' => 0.1 ] )->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.14
	 */
	public function generate_data_collect_question( $messages, $stream = false ) {
		return $this->make_request( $messages, $stream, [ 'temperature' => 0.7 ] )->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|array  $messages  Messages for success message generation (system + user prompt)
	 *
	 * @return Message The generated success message text
	 * @throws Exception
	 * @since 1.0.14
	 */
	public function generate_action_success_message( $messages, $stream = false ) {
		return $this->make_request( $messages, $stream, [ 'temperature' => 0.7 ] )->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|array  $messages  Messages for success message generation (system + user prompt + extracted variables)
	 *
	 * @return Message The generated success message text with extracted variables context
	 * @throws Exception
	 * @since 1.0.14
	 */
	public function rerank( $messages ) {
		return $this->make_request( $messages, false, [ 'temperature' => 0 ] )->get_message();
	}
}
