<?php

namespace Limb_Chatbot\Includes\Services;

use Exception;
use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Conversation_State;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Repositories\Conversation_State_Repository;
use Limb_Chatbot\Includes\Services\Actions\Action_Service;
use Limb_Chatbot\Includes\Services\Chat_Language_Service;
use Limb_Chatbot\Includes\Utilities\Chatbot_Utility;
use Limb_Chatbot\Includes\Utilities\Copilot_Utility;

/**
 * Class Conversation_State_Analyzer
 *
 * Analyzes conversation turns intelligently using AI while maintaining context,
 * user intent history, topic continuity, and other essential conversation data.
 *
 * @package Limb_Chatbot\Includes\Services
 * @since 1.0.0
 */
class Conversation_State_Analyzer {

	/**
	 * Maximum number of previous messages to include in context for AI analysis.
	 *
	 * @var int
	 * @since 1.0.0
	 */
	const MAX_MESSAGES_COUNT = 6;

	/**
	 * Conversation state repository instance.
	 *
	 * @var Conversation_State_Repository
	 * @since 1.0.0
	 */
	private Conversation_State_Repository $repository;

	/**
	 * Copilot AI utility instance.
	 *
	 * @var Copilot_Utility
	 * @since 1.0.0
	 */
	private Copilot_Utility $copilot_utility;

	/**
	 * Conversation_State_Analyzer constructor.
	 *
	 * @param  Chatbot_Utility  $utility  Chatbot utility instance with AI model and config.
	 *
	 * @since 1.0.0
	 */
	public function __construct( Chatbot_Utility $utility ) {
		$this->copilot_utility = new Copilot_Utility( $utility->get_ai_model(), $utility->get_config() );
		$this->repository      = new Conversation_State_Repository();
	}

	/**
	 * Run intelligent analysis for a single conversation turn.
	 *
	 * @param  string  $chat_uuid  Chat session UUID.
	 *
	 * @return Conversation_State
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function analyze_turn( string $chat_uuid ): ?Conversation_State {
		$chat = Chat::find_by_uuid( $chat_uuid );
		if ( ! $chat->get_chatbot()->get_parameter( 'chat_analyzes' ) ) {
			return null;
		}
		if ( ! $chat instanceof Chat || $chat->is_live_agent_active() || $chat->has_action() ) {
			return null;
		}
		$messages = Message::where( [ 'chat_uuid' => $chat_uuid ], null, null, 'id', 'DESC' )->get();
		// Reverse to start from the earlier one
		$messages = array_reverse( $messages );

		$prev_state = $this->repository->get_by_chat_uuid( $chat_uuid );

		// Build enhanced AI payload
		$payload = $this->build_enhanced_payload( $messages, $prev_state );

		// Call AI for analysis
		$raw = $this->copilot_utility->analyze_turn( $payload );

		// Process AI response
		$data = $this->get_enhanced_response_data( $raw );

		// Update chat name if topic is present
		$this->maybe_update_chat_name( $data, $chat_uuid );

		// Update confidence in assistant message
		$this->update_response_confidence( $data, $messages );

		// Build conversation state object
		$data['chat_uuid'] = $chat_uuid;
		$incoming          = Conversation_State::make( $data );

		// Merge with previous state if exists
		$final = $prev_state ? $this->repository->merge( $prev_state, $incoming ) : $incoming;

		$this->repository->upsert( $final );

		return $final;
	}

	/**
	 * Build enhanced payload for AI conversation analysis.
	 *
	 * @param  Message[]  $messages
	 * @param  Conversation_State|null  $prev_state
	 *
	 * @return string JSON-encoded payload
	 * @since 1.0.0
	 */
	private function build_enhanced_payload( array $messages, ?Conversation_State $prev_state ): string {
		$payload = [
			'conversation'   => $this->get_conversation_context( $messages ),
			'previous_state' => $prev_state ? $prev_state->to_array() : null,
			'analysis_focus' => $this->get_analysis_focus( $messages, $prev_state ),
		];

		return wp_json_encode( $payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
	}

	/**
	 * Get conversation context as an array of role:text strings for AI analysis.
	 *
	 * @param  Message[]  $messages
	 *
	 * @return array
	 * @since 1.0.0
	 */
	private function get_conversation_context( array $messages ): array {
		$context = [];
		$to      = count( $messages ) - 1;
		$from    = $to > self::MAX_MESSAGES_COUNT ? $to - self::MAX_MESSAGES_COUNT : 0;

		for ( $i = $from; $i <= $to; $i ++ ) {
			$context[] = "{$messages[$i]->get_role()}: {$messages[$i]->extract_text()}";
		}

		return $context;
	}

	/**
	 * Determine focused analysis instructions based on previous state and messages.
	 *
	 * @param  Message[]  $messages
	 * @param  Conversation_State|null  $prev_state
	 *
	 * @return array
	 * @since 1.0.0
	 */
	private function get_analysis_focus( array $messages, ?Conversation_State $prev_state ): array {
		$focus = [];

		if ( $prev_state ) {
			$focus[] = "Maintain topic continuity: '{$prev_state->get_topic()}'";

			if ( $prev_state->get_summary() ) {
				$focus[] = "Evolve summary from: '{$prev_state->get_summary()}'";
			}

			if ( $prev_state->get_entities() ) {
				$focus[] = "Merge with existing entities: " . json_encode( $prev_state->get_entities() );
			}

			if ( $prev_state->get_user_intent_history() ) {
				$focus[] = "Continue intent history: " . implode( ' → ', $prev_state->get_user_intent_history() );
			}
		} else {
			$focus[] = "Establish initial conversation state";
			$focus[] = "Identify primary topic and intent";
		}

		$last_user_message = $this->get_last_user_message( $messages );
		if ( $last_user_message ) {
			$focus[] = "Analyze user message: '{$last_user_message->extract_text()}'";
		}

		return $focus;
	}

	/**
	 * Get the last user message in the conversation.
	 *
	 * @param  Message[]  $messages
	 *
	 * @return Message|null
	 * @since 1.0.0
	 */
	private function get_last_user_message( array $messages ): ?Message {
		for ( $i = count( $messages ) - 1; $i >= 0; $i -- ) {
			if ( $messages[ $i ]->get_role() === Message::ROLE_USER ) {
				return $messages[ $i ];
			}
		}

		return null;
	}

	/**
	 * Normalize and process AI-enhanced response data.
	 *
	 * @param  array  $data  Raw AI response.
	 *
	 * @return array
	 * @since 1.0.0
	 */
	private function get_enhanced_response_data( array $data ): array {
		// Validate and structure entities
		$data['entities'] = $this->validate_entities( $data['entities'] ?? [] );

		// Validate and structure user preferences
		$data['user_preferences'] = $this->validate_user_preferences( $data['user_preferences'] ?? [] );

		$data['topic']               = isset( $data['topic'] ) ? (string) $data['topic'] : null;
		$data['summary']             = isset( $data['summary'] ) ? (string) $data['summary'] : null;
		$data['confidence']          = isset( $data['confidence'] ) ? (float) $data['confidence'] : Conversation_State::DEFAULT_CONFIDENCE;
		$data['sentiment']           = isset( $data['sentiment'] ) ? (string) $data['sentiment'] : null;
		$data['user_intent_history'] = isset( $data['user_intent_history'] ) ? (array) $data['user_intent_history'] : [];

		return $data;
	}

	/**
	 * Validate and structure entities to ensure proper format.
	 *
	 * @param  mixed  $entities  Raw entities data.
	 *
	 * @return array Structured entities.
	 * @since 1.0.0
	 */
	private function validate_entities( $entities ): array {
		// Default structure
		$default_structure = [
			'people'    => [],
			'orgs'      => [],
			'locations' => [],
			'dates'     => [],
			'products'  => [],
			'services'  => [],
			'misc'      => []
		];

		// If entities is not an array, return default structure
		if ( ! is_array( $entities ) ) {
			return $default_structure;
		}

		// If it's a flat array, try to categorize items
		if ( $this->is_flat_array( $entities ) ) {
			return $this->categorize_flat_entities( $entities );
		}

		// If it's already structured, validate and clean it
		$structured = [];
		foreach ( $default_structure as $key => $default_value ) {
			$structured[ $key ] = isset( $entities[ $key ] ) && is_array( $entities[ $key ] )
				? array_filter( $entities[ $key ], fn( $v ) => ! empty( $v ) )
				: $default_value;
		}

		return $structured;
	}

	/**
	 * Check if an array is flat (has only numeric keys).
	 *
	 * @param  array  $array  Array to check.
	 *
	 * @return bool True if flat array, false otherwise.
	 * @since 1.0.0
	 */
	private function is_flat_array( array $array ): bool {
		if ( empty( $array ) ) {
			return false;
		}

		foreach ( array_keys( $array ) as $key ) {
			if ( ! is_int( $key ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Convert flat entity array to structured format (AI should handle categorization).
	 *
	 * @param  array  $entities  Flat array of entities.
	 *
	 * @return array Structured entities.
	 * @since 1.0.0
	 */
	private function categorize_flat_entities( array $entities ): array {
		// If we get flat arrays, just put everything in misc and let AI handle proper categorization
		// This is a fallback - AI should be providing structured entities
		return [
			'people'    => [],
			'orgs'      => [],
			'locations' => [],
			'dates'     => [],
			'products'  => [],
			'services'  => [],
			'misc'      => array_filter( $entities, fn( $v ) => ! empty( $v ) )
		];
	}

	/**
	 * Validate and structure user preferences to ensure proper format.
	 *
	 * @param  mixed  $preferences  Raw preferences data.
	 *
	 * @return array Structured preferences.
	 * @since 1.0.0
	 */
	private function validate_user_preferences( $preferences ): array {
		// If preferences is not an array, return empty object
		if ( ! is_array( $preferences ) ) {
			return [];
		}

		// If it's a flat array, try to convert to key-value pairs
		if ( $this->is_flat_array( $preferences ) ) {
			return [];
		}

		// If it's already structured, clean it
		$structured = [];
		foreach ( $preferences as $key => $value ) {
			if ( is_string( $key ) && ! empty( $value ) ) {
				$structured[ $key ] = $value;
			}
		}

		return $structured;
	}

	/**
	 * Update chat name based on AI-determined topic.
	 *
	 * @param  array  $data
	 * @param  string  $uuid
	 *
	 * @since 1.0.0
	 */
	private function maybe_update_chat_name( array $data, string $uuid ): void {
		if ( ! empty( $data['topic'] ) ) {
			Chat::update( [ 'uuid' => $uuid ], [ 'name' => ucfirst( $data['topic'] ) ] );
		}
	}

	/**
	 * Update confidence metadata in the last assistant message.
	 *
	 * @param  array  $data
	 * @param  Message[]  $messages
	 *
	 * @since 1.0.0
	 */
	private function update_response_confidence( array $data, array $messages ): void {
		for ( $i = count( $messages ) - 1; $i >= 0; $i -- ) {
			if ( $messages[ $i ]->get_role() === Message::ROLE_ASSISTANT ) {
				$messages[ $i ]->update_meta( 'confidence',
					$data['confidence'] ?? Conversation_State::DEFAULT_CONFIDENCE );
				break;
			}
		}
	}

}
