<?php

namespace Limb_Chatbot\Includes\Services\Knowledge;

use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Utilities\Chatbot_Utility;
use Limb_Chatbot\Includes\Utilities\Copilot_Utility;

use function Symfony\Component\Translation\t;

/**
 * Query_Intelligence_Service
 *
 * Lightweight, multilingual-safe preprocessing layer for user queries
 * before knowledge base retrieval and vector search.
 *
 * Responsibilities:
 * - Pure PHP normalization (trim, whitespace, length limit)
 * - Statistical keyword extraction (language-neutral)
 * - Optional LLM-assisted query rewrite via Copilot_Utility
 * - Simple caching by normalized query hash
 *
 * @since 1.0.0
 */
class Query_Intelligence_Service {

	/**
	 * Chatbot utility used for resolving AI provider, model and config.
	 *
	 * @var Chatbot_Utility
	 */
	private Chatbot_Utility $chatbot_utility;

	/**
	 * Optional Copilot utility instance for small LLM calls.
	 *
	 * @var Copilot_Utility|null
	 */
	private ?Copilot_Utility $copilot_utility = null;

	/**
	 * Maximum length for normalized queries.
	 *
	 * @var int
	 */
	private int $max_query_length = 500;

	/**
	 * Maximum number of keywords to extract.
	 *
	 * @var int
	 */
	private int $max_keywords = 8;


	/**
	 * Query_Intelligence_Service constructor.
	 *
	 * @param  Chatbot_Utility  $chatbot_utility  Chatbot utility instance.
	 *
	 * @since 1.0.0
	 */
	public function __construct( Chatbot_Utility $chatbot_utility ) {
		$this->chatbot_utility = $chatbot_utility;
	}

	/**
	 * Perform full query intelligence pipeline for a raw user query.
	 *
	 * @param  string  $raw_query  Raw user input.
	 *
	 * @return array{
	 *     normalized: string,
	 *     keywords: string[],
	 *     language: string|null,
	 *     rewritten: string|null
	 * }
	 *
	 * @since 1.0.0
	 */
	public function analyze( string $raw_query ): array {
		$normalized = $this->normalize_query( $raw_query );

		if ( $normalized === '' ) {
			return [
				'normalized' => '',
				'keywords'   => [],
				'rewritten'  => null,
			];
		}

		$keywords = $this->extract_keywords( $normalized, $this->max_keywords );

		// If no meaningful keywords, skip LLM rewrite completely.
		if ( empty( $keywords ) ) {
			return [
				'normalized' => $normalized,
				'keywords'   => [],
				'rewritten'  => null,
			];
		}

		$rewritten = null;

		// 3. Optional lightweight LLM-assisted rewrite for short, keyword-rich queries.
		if ( $this->should_rewrite( $normalized, $keywords ) ) {
			try {
				$copilot              = $this->get_copilot_utility();
				$conversation_context = $this->build_rewrite_conversation_context( $normalized );
				$rewritten            = $copilot->rewrite_query( $conversation_context );
			} catch ( \Throwable $e ) {
				// Fail gracefully to keep retrieval path robust.
			}
		}

		return [
			'normalized' => $normalized,
			'keywords'   => $keywords,
			'rewritten'  => $rewritten ?: null,
		];
	}

	/**
	 * Normalize a raw query (multilingual-safe).
	 *
	 * - Trims noise
	 * - Normalizes whitespace
	 * - Keeps accents and UTF-8 intact
	 * - Does not translate or replace words
	 *
	 * @param  string  $query  Raw query string.
	 *
	 * @return string Normalized query.
	 *
	 * @since 1.0.0
	 */
	public function normalize_query( string $query ): string {
		$query = trim( $query );
		$query = preg_replace( '/\s+/u', ' ', $query );

		return substr( $query, 0, $this->max_query_length );
	}

	/**
	 * Extract statistically relevant keywords from a query (language-neutral).
	 *
	 * Uses simple frequency + length thresholds over whitespace-delimited tokens.
	 *
	 * @param  string  $query  Query to analyze.
	 * @param  int  $max  Maximum number of keywords.
	 *
	 * @return string[] List of top keywords.
	 *
	 * @since 1.0.0
	 */
	public function extract_keywords( string $query, int $max = 8 ): array {
		$tokens = preg_split( '/\s+/u', mb_strtolower( $query ) );
		$freq   = [];

		foreach ( $tokens as $token ) {
			if ( ! is_string( $token ) || $token === '' ) {
				continue;
			}
			// Skip tiny tokens; tends to be stopwords or noise across languages.
			if ( mb_strlen( $token ) < 3 ) {
				continue;
			}
			$freq[ $token ] = ( $freq[ $token ] ?? 0 ) + 1;
		}

		if ( empty( $freq ) ) {
			return [];
		}

		arsort( $freq );

		return array_slice( array_keys( $freq ), 0, max( 1, $max ) );
	}

	/**
	 * Determine whether the current query is a good candidate for rewrite.
	 *
	 * Heuristics:
	 * - Short enough (to avoid wasting tokens on long, detailed queries)
	 * - Has at least one keyword
	 *
	 * @param  string  $normalized  Normalized query text.
	 * @param  string[]  $keywords  Extracted keywords.
	 *
	 * @return bool
	 *
	 * @since 1.0.0
	 */
	private function should_rewrite( string $normalized, array $keywords ): bool {
		return true;
	}

	/**
	 * Lazily instantiate Copilot_Utility using the chatbot's AI model and config.
	 *
	 * @return Copilot_Utility
	 *
	 * @throws \Exception
	 *
	 * @since 1.0.0
	 */
	private function get_copilot_utility(): Copilot_Utility {
		if ( $this->copilot_utility instanceof Copilot_Utility ) {
			return $this->copilot_utility;
		}

		$ai_model       = $this->chatbot_utility->get_ai_model();
		$config         = $this->chatbot_utility->get_config();
		$ai_provider_id = $this->chatbot_utility->get_ai_provider_id();

		$this->copilot_utility = new Copilot_Utility( $ai_model, $config, $ai_provider_id );

		return $this->copilot_utility;
	}

	/**
	 * Build a short conversation context for query rewriting.
	 *
	 * This helps the LLM resolve vague references in the query (e.g. "it", "that issue")
	 * based on the recent chat history, without adding new intent.
	 *
	 * @param $current_query
	 *
	 * @return string
	 */
	private function build_rewrite_conversation_context( $current_query ): string {
		$chat = $this->chatbot_utility->get_chat();
		if ( ! $chat ) {
			return '';
		}

		$messages = Message::where(
			[ 'chat_uuid' => $chat->get_uuid() ],
			6,
			1,
			'id',
			'DESC'
		)->get();

		if ( empty( $messages ) ) {
			return '';
		}

		// Ensure chronological order from oldest to newest.
		$messages = array_reverse( $messages );

		$lines = [];
		foreach ( $messages as $message ) {
			if ( ! $message instanceof Message ) {
				continue;
			}
			$role = $message->get_role() ?? 'user';
			$text = trim( $message->extract_text() );
			if ( $text === '' ) {
				continue;
			}
			$lines[] = strtoupper( $role ) . ': ' . $text;
		}

		$conversation = ! empty( $lines ) ? implode( "\n", $lines ) : '';

		$payload = '';

		if ( $conversation ) {
			$payload .= "Conversation context (for terminology alignment and disambiguation only):\n---\n";
			$payload .= $conversation;
			$payload .= "\n---\n\n";
		}

		$payload .= "Current query to rewrite:\n---\n";
		$payload .= $current_query;
		$payload .= "\n---";

		return $payload;
	}
}

