<?php

namespace Limb_Chatbot\Includes\AI_Providers\Claude\Utilities;

use Limb_Chatbot\Includes\AI_Providers\Claude\Claude;
use Limb_Chatbot\Includes\AI_Providers\Claude\Endpoints\Chat_Completion\Chat_Completion_Endpoint;
use Limb_Chatbot\Includes\AI_Providers\Claude\Endpoints\Chat_Completion\Tool_Calls_Message;
use Limb_Chatbot\Includes\AI_Providers\Claude\Endpoints\Chat_Completion\Tool_Result_Message;
use Limb_Chatbot\Includes\Chatbot_Tools\Chatbot_Tools;
use Limb_Chatbot\Includes\Chatbot_Tools\Tools\Live_Agent_Connection;
use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Data_Objects\Setting;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Traits\Json_Serializable_Trait;
use Limb_Chatbot\Includes\Utilities\Chatbot_Utility as Global_Utility;
use ReflectionClass;

/**
 * Class Chatbot_Utility
 *
 * Utility class to handle chatbot-related functionalities for Claude AI Provider.
 *
 * @package Limb_Chatbot\Includes\AI_Providers\Claude\Utilities
 * @since 1.0.9
 */
class Chatbot_Utility {

	use Json_Serializable_Trait;

	/**
	 * Global chatbot utility instance.
	 *
	 * @var Global_Utility
	 * @since 1.0.9
	 */
	public Global_Utility $global_utility;

	/**
	 * AI Model ID used for requests.
	 *
	 * @var int|null
	 * @since 1.0.9
	 */
	public ?int $ai_model_id = null;

	/**
	 * Config ID for the Claude configuration.
	 *
	 * @var int|null
	 * @since 1.0.9
	 */
	public ?int $config_id = null;

	/**
	 * Default timeout for requests in seconds.
	 *
	 * @var int
	 * @since 1.0.9
	 */
	public int $timeout = 120;

	/**
	 * List of properties excluded from JSON serialization.
	 *
	 * @var array
	 * @since 1.0.9
	 */
	protected array $excluded_json_properties = array(
		'global_utility',
		'config_id',
		'ai_model_id',
	);

	/**
	 * Constructor.
	 *
	 * @param  Global_Utility  $global_utility  The global utility instance.
	 *
	 * @since 1.0.9
	 */
	public function __construct( Global_Utility $global_utility ) {
		$this->global_utility = $global_utility;
		$this->populate_properties( ! empty( $this->global_utility->get_chatbot()->get_id() ) );
	}

	/**
	 * Populate object properties either from the CPT meta or Settings.
	 *
	 * @param  bool  $is_cpt  Whether the chatbot is a CPT (custom post type).
	 *
	 * @return void
	 * @since 1.0.9
	 */
	public function populate_properties( $is_cpt ) {
		$reflection = new ReflectionClass( $this );
		$properties = $reflection->getProperties();

		foreach ( $properties as $property ) {
			$property_name = $property->getName();

			if ( in_array( $property_name, $this->excluded_json_properties, true ) ) {
				continue;
			}

			if ( empty( $this->{$property_name} ) && ! $property->isStatic() ) {
				$key = self::class . '_' . $property_name;

				if ( $is_cpt ) {
					$value = $this->global_utility->get_chatbot()->get_meta( $property_name );
				} else {
					$value = Setting::find( $key )->get_value();
				}

				if ( $value !== null ) {
					$this->{$property_name} = $value;
				}
			}
		}
	}

	/**
	 * Generate content by delegating to the endpoint.
	 *
	 * @return Message|Tool_Calls_Message|null Generated message result.
	 * @throws Exception If generation fails.
	 * @since 1.0.9
	 */
	public function generate() {
		return $this->get_endpoint_instance()->generate();
	}

	/**
	 * Finalize and generate response with optional streaming.
	 *
	 * @return Message|Tool_Calls_Message|null Generated message result.
	 * @throws Exception If generation fails.
	 * @since 1.0.9
	 */
	public function finalize() {
		return $this->get_endpoint_instance()->finalize();
	}

	/**
	 * Get instance of the Chat Completion Endpoint.
	 *
	 * @return Chat_Completion_Endpoint
	 * @since 1.0.9
	 */
	public function get_endpoint_instance() {
		return new Chat_Completion_Endpoint( $this );
	}

	/**
	 * Get the AI model instance based on the AI model ID.
	 *
	 * @return AI_Model|null
	 * @since 1.0.9
	 */
	public function get_ai_model() {
		return $this->ai_model_id ? AI_Model::find( $this->ai_model_id ) : null;
	}

	/**
	 * Process a tool call message by invoking the respective functions.
	 *
	 * @param  Tool_Calls_Message  $tool_calls_message  The tool calls message.
	 *
	 * @return array Array of Tool_Result_Message objects.
	 * @throws Exception If the function call is invalid.
	 * @since 1.0.9
	 */
	public function tool_call( Tool_Calls_Message $tool_calls_message ) {
		$tool_call_responses = array();

		foreach ( $tool_calls_message->get_tool_calls() as $tool_call ) {
			if ( isset( $tool_call->type ) && $tool_call->type === 'function' ) {
				$result = $this->function_tool_call( $tool_call, $tool_calls_message );
				if ( $result ) {
					$tool_call_responses[] = $result;
				}
			}
		}

		return $tool_call_responses;
	}

	/**
	 * Check if the tool call is a live agent connection.
	 *
	 * @param  Tool_Calls_Message  $tool_calls_message  The tool calls message.
	 *
	 * @return array|false Arguments array if live agent tool, false otherwise.
	 * @throws Exception If tool call is invalid.
	 * @since 1.0.9
	 */
	public function is_live_agent_tool_call( Tool_Calls_Message $tool_calls_message ) {
		foreach ( $tool_calls_message->get_tool_calls() as $tool_call ) {
			if ( isset( $tool_call->type ) && $tool_call->type === 'function' ) {
				if ( empty( $tool_call->function->name ) ) {
					continue;
				}

				$tool_name = $tool_call->function->name;
				$tool      = Chatbot_Tools::instance()->get_tool( $tool_name );

				if ( $tool instanceof Live_Agent_Connection ) {
					$arguments = $tool_call->function->arguments ?? '{}';

					return json_decode( $arguments, true );
				}
			}
		}

		return false;
	}

	/**
	 * Processes a single function tool call.
	 *
	 * @param  object  $tool_call  The tool call object.
	 * @param  Tool_Calls_Message  $tool_calls_message  The parent tool calls message.
	 *
	 * @return Tool_Result_Message|null The result message or null.
	 * @throws Exception If tool call is invalid.
	 * @since 1.0.9
	 */
	public function function_tool_call( $tool_call, $tool_calls_message ) {
		if ( empty( $tool_call->function->name ) ) {
			throw new Exception(
				Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'Invalid params for tool_calls', 'limb-chatbot' )
			);
		}

		$tool_name = $tool_call->function->name;
		$tool      = Chatbot_Tools::instance()->get_tool( $tool_name );

		if ( ! $tool ) {
			return null;
		}

		$arguments = $tool_call->function->arguments ?? '{}';
		$arguments = json_decode( $arguments, true );

		if ( json_last_error() !== JSON_ERROR_NONE ) {
			throw new Exception(
				Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'Failed to decode tool arguments', 'limb-chatbot' )
			);
		}

		$result = $tool->execute( $arguments, $tool_calls_message, $this->global_utility );

		return Tool_Result_Message::from_tool_call( $tool_call->id, $result );
	}

	/**
	 * Get system instructions from messages.
	 *
	 * @return string|false The system instructions string or false if not available.
	 * @since 1.0.9
	 */
	public function get_system_instructions() {
		$messages = $this->global_utility->get_messages();

		if ( ! empty( $messages[0] ) && $messages[0]->get_role() === 'system' ) {
			$content = $messages[0]->get_content();
			if ( is_array( $content ) && isset( $content[0]['text']['value'] ) ) {
				return $content[0]['text']['value'];
			}
			if ( is_string( $content ) ) {
				return $content;
			}
		}

		return false;
	}

	/**
	 * Get the config instance if config_id is set.
	 *
	 * @return Config|null
	 * @since 1.0.9
	 */
	public function get_config() {
		return ! empty( $this->config_id ) ? Config::find( $this->config_id ) : null;
	}

	/**
	 * Get timeout value in seconds.
	 *
	 * @return int
	 * @since 1.0.9
	 */
	public function get_timeout() {
		return $this->timeout;
	}

	/**
	 * Get the tools in the format required for Claude API.
	 *
	 * Claude uses 'input_schema' instead of 'parameters'.
	 *
	 * @return array|null
	 * @since 1.0.9
	 */
	public function get_tools() {
		$chatbot_tools = $this->global_utility->get_tools();

		if ( empty( $chatbot_tools ) ) {
			return null;
		}

		$tools = array();

		foreach ( $chatbot_tools as $tool ) {
			$parameters = $tool->get_parameters_schema();

			// Build Claude-compatible tool definition
			$tool_def = array(
				'name'         => $tool->get_name(),
				'description'  => $tool->get_description(),
				'input_schema' => array(
					'type' => 'object',
				),
			);

			// Add properties if available
			if ( ! empty( $parameters['properties'] ) ) {
				$tool_def['input_schema']['properties'] = $parameters['properties'];
			} else {
				$tool_def['input_schema']['properties'] = new \stdClass();
			}

			// Add required fields if available
			if ( ! empty( $parameters['required'] ) ) {
				$tool_def['input_schema']['required'] = $parameters['required'];
			}

			$tools[] = $tool_def;
		}

		return $tools;
	}

	/**
	 * Returns the settings prefix used to locate related values.
	 *
	 * @return string
	 * @since 1.0.9
	 */
	public function get_settings_prefix() {
		return 'ai_providers.' . Claude::$id . '.' . Chatbot::SETTING_PREFIX;
	}
}

