<?php
/**
 * ============================================================
 * Front-End AI Chat Agent Class
 * ------------------------------------------------------------
 * Responsibilities:
 *  - Render floating chat bubble + chat window on front-end
 *  - Enqueue JS/CSS assets and localize runtime config
 *  - Handle AJAX chat requests (logged-in + public)
 *  - RAG (Retrieval-Augmented Generation) on top of
 *    wp_wordai_agent_embeddings table with strict provider
 *    separation (OpenAI vs Google Gemini).
 *
 * NEW (Dec 2025):
 *  - RAG answer mode: "strict" vs "soft" (radio in settings).
 *  - Context is ALWAYS passed to providers as an ASSISTANT role
 *    message (for OpenAI) via a dedicated parameter, instead of
 *    mixing context + question into one user message.
 * ============================================================
 */

if ( ! defined( 'ABSPATH' ) ) exit;

class SftcyWordaiFrontendAgentChat {

	/** @var bool */
	private static $initiated = false;
	private static $last_rag_sources = array();

	public function __construct() {
		if ( ! self::$initiated ) {
			self::initiate_hooks();
		}
	}

	/**
	 * ----------------------------------------------------------
	 * Initialize All Hooks
	 * ----------------------------------------------------------
	 * - Assets enqueue on front-end
	 * - Floating widget rendered in footer
	 * - Shortcode registration
	 * - AJAX handlers (logged-in + public)
	 * ----------------------------------------------------------
	 */
	private static function initiate_hooks() {

		add_action( 'wp_enqueue_scripts', [ __CLASS__, 'enqueue_assets' ] );
		add_action( 'wp_footer',          [ __CLASS__, 'render_widget' ] );
		add_action( 'init',               [ __CLASS__, 'register_shortcode' ] );

		// ✅ AJAX Endpoints (both logged + public)
		add_action( 'wp_ajax_sc_wordai_agent_chat_request',        [ __CLASS__, 'sc_wordai_agent_chat_request' ] );
		add_action( 'wp_ajax_nopriv_sc_wordai_agent_chat_request', [ __CLASS__, 'sc_wordai_agent_chat_request' ] );
		add_action( 'wp_ajax_sc_wordai_agent_chat_feedback',        [ __CLASS__, 'sc_wordai_agent_chat_feedback' ] );
		add_action( 'wp_ajax_nopriv_sc_wordai_agent_chat_feedback', [ __CLASS__, 'sc_wordai_agent_chat_feedback' ] );
		add_action( 'wp_ajax_sc_wordai_agent_save_lead',        [ __CLASS__, 'sc_wordai_agent_save_lead' ] );
		add_action( 'wp_ajax_nopriv_sc_wordai_agent_save_lead', [ __CLASS__, 'sc_wordai_agent_save_lead' ] );

		self::$initiated = true;
	}

	/**
	 * ----------------------------------------------------------
	 * Enqueue Front-End JS/CSS + Localized Config
	 * ----------------------------------------------------------
	 * - Only enqueues if chat is enabled in settings.
	 * - Respects "visibility" (public|loggedin).
	 * ----------------------------------------------------------
	 */
	public static function enqueue_assets() {
		$chat = (array) get_option( 'sc_wordai_agent_chat_settings', [] );

		// Merge with centralized defaults
		$defaults = self::wordai_chat_default_settings();
		$chat     = wp_parse_args( $chat, $defaults );

		// Chat disabled → no assets
		if ( empty( $chat['enabled'] ) ) {
			return;
		}

		// Restrict to logged-in users if configured
		if ( $chat['visibility'] === 'loggedin' && ! is_user_logged_in() ) {
			return;
		}

		$chat_widget_css_path    = SFTCY_WORDAI_PLUGIN_DIR . 'public/css/wordai-chat-widget.css';
		$chat_widget_css_version = file_exists( $chat_widget_css_path ) ? (string) filemtime( $chat_widget_css_path ) : SFTCY_WORDAI_VERSION;
		$chat_widget_js_path     = SFTCY_WORDAI_PLUGIN_DIR . 'public/js/wordai-chat-widget.js';
		$chat_widget_js_version  = file_exists( $chat_widget_js_path ) ? (string) filemtime( $chat_widget_js_path ) : SFTCY_WORDAI_VERSION;

		// CSS
		wp_enqueue_style(
			'sc-wordai-chat-widget',
			SFTCY_WORDAI_PLUGIN_URL . 'public/css/wordai-chat-widget.css',
			[],
			$chat_widget_css_version
		);

		// JS
		wp_enqueue_script(
			'sc-wordai-chat-widget',
			SFTCY_WORDAI_PLUGIN_URL . 'public/js/wordai-chat-widget.js',
			[ 'jquery' ],
			$chat_widget_js_version,
			true
		);

		// Localized configuration for front-end JS
		$is_basic_tier        = class_exists( 'SftcyWordaiFeatures' ) && SftcyWordaiFeatures::wordai_is_basic_tier();
		$citations_rule       = class_exists( 'SftcyWordaiFeatures' ) ? SftcyWordaiFeatures::wordai_get_feature_rule( 'chat_source_citations' ) : array( 'status' => 'full' );
		$show_source_frontend = ! $is_basic_tier && ! empty( $chat['show_source_citations'] ) && ( $citations_rule['status'] ?? 'full' ) === 'full';
		$localized_data = [
			'ajax_url'        => admin_url( 'admin-ajax.php' ),
			'nonce'           => wp_create_nonce( 'scwordai_wpnonce' ),
			'model'           => class_exists( 'SftcyWordaiOpenai' ) ? SftcyWordaiOpenai::normalize_openai_model( $chat['model'] ?? '' ) : 'gpt-5.5',
			'temperature'     => floatval( $chat['temperature'] ?? 0.7 ),
			'assistant_name'  => $chat['assistant_name']  ?? 'WordAI Support',
			'welcome_message' => $chat['welcome_message'] ?? 'Hello 👋 How can I help you?',
			'bubble_color'    => $chat['bubble_color']    ?? '#0073aa',
			'bubble_label'    => $chat['bubble_label']    ?? 'Chat',
			'theme'           => $chat['theme']           ?? 'light',
			'position'        => $chat['position']        ?? 'bottom-right',
			'reply_mode'      => $chat['reply_mode']      ?? 'nonstream',
			'prefix'          => $chat['prompt_prefix']   ?? '',
			'avatar_style'    => $chat['avatar_style']    ?? 'none',
			'avatar_color'    => $chat['avatar_color']    ?? '#ffffff',
			'chat_font_size'  => max( 13, min( 18, absint( $chat['chat_font_size'] ?? $defaults['chat_font_size'] ?? 16 ) ) ),
			'auto_display'    => isset( $chat['auto_display'] ) ? (int) $chat['auto_display'] : 1, // default ON
			'feedback_enabled' => ! empty( $chat['feedback_enabled'] ) ? 1 : 0,
			'show_sources'     => $show_source_frontend ? 1 : 0,
			'free_branding'    => $is_basic_tier ? 1 : 0,
			'softcoy_url'      => 'https://softcoy.com',
			'handoff_enabled'  => ! $is_basic_tier && ! empty( $chat['handoff_enabled'] ) ? 1 : 0,
			'handoff_email'    => ! $is_basic_tier && ! empty( $chat['handoff_enabled'] ) ? sanitize_email( $chat['handoff_email'] ?? get_option( 'admin_email' ) ) : '',
			'handoff_label'    => $chat['handoff_label'] ?? __( 'Contact support', 'wordai-pro' ),
			'lead_capture'     => self::get_public_lead_capture_config( $chat ),
		];

		wp_localize_script( 'sc-wordai-chat-widget', 'scwordai_frontchat', $localized_data );
	}

	/**
	 * ----------------------------------------------------------
	 * Render Chat Bubble + Container in Footer
	 * ----------------------------------------------------------
	 * - Simple static HTML container; behavior driven by JS.
	 * - Respects same visibility logic as enqueue_assets().
	 * ----------------------------------------------------------
	 */
	public static function render_widget() {
		$chat = (array) get_option( 'sc_wordai_agent_chat_settings', [] );
		if ( empty( $chat['enabled'] ) ) {
			return;
		}

		if ( isset( $chat['visibility'] ) && $chat['visibility'] === 'loggedin' && ! is_user_logged_in() ) {
			return;
		}

		echo '<div id="wordai-chat-bubble" class="wordai-chat-bubble"></div>';

		echo '<div id="wordai-chat-window" class="wordai-chat-window" style="display:none;">
				<div class="wordai-chat-header">
					<span class="assistant-name">'
						. esc_html( $chat['assistant_name'] ?? 'WordAI Support' ) .
					'</span>
					<button class="chat-close">×</button>
				</div>
				<div class="wordai-chat-body">
					<div class="chat-messages"></div>
				</div>
				<div class="wordai-chat-footer">
					<textarea placeholder="Type your question..." class="chat-input"></textarea>
					<button class="chat-send">Send</button>
				</div>
			  </div>';
	}

	/**
	 * ================================================================
	 * AJAX: Frontend Chat Request (OpenAI + Gemini, Streaming + Non)
	 * ---------------------------------------------------------------
	 * RAG Flow (Embeddings-only, strict provider separation):
	 *  1) Detect active provider (openai|google|…)
	 *  2) Embed user question with that provider's embedding model
	 *  3) Pull similar chunks from wp_wordai_agent_embeddings
	 *     filtered by provider (+ optionally model)
	 *  4) Build three pieces:
	 *       - $user_prompt_final  (user's question)
	 *       - $system_message     (system instructions)
	 *       - $assistant_context  (context string to send as an
	 *                              assistant-role message)
	 *  5) Send to provider create_chat_reply(), which now has a
	 *     dedicated $assistant_context parameter.
	 * ================================================================
	 */
	public static function sc_wordai_agent_chat_request() {
		$request_started = microtime( true );

		check_ajax_referer( 'scwordai_wpnonce', 'security' );

		/*--------------------------------------------------------------
		| 1. Sanitize user prompt
		--------------------------------------------------------------*/
		$prompt = isset( $_POST['prompt'] )
			? sanitize_textarea_field( wp_unslash( $_POST['prompt'] ) )
			: '';

		if ( empty( $prompt ) ) {
			wp_send_json( [
				'status' => 'error',
				'reply'  => __( 'Empty prompt.', 'wordai-pro' ),
			] );
		}

		$conversation_key = isset( $_POST['conversation_key'] ) ? sanitize_text_field( wp_unslash( $_POST['conversation_key'] ) ) : '';
		$page_url         = isset( $_POST['page_url'] ) ? esc_url_raw( wp_unslash( $_POST['page_url'] ) ) : '';
		$page_title       = isset( $_POST['page_title'] ) ? sanitize_text_field( wp_unslash( $_POST['page_title'] ) ) : '';

		/*--------------------------------------------------------------
		| 2. Load chat settings + defaults
		--------------------------------------------------------------*/
		$settings = (array) get_option( 'sc_wordai_agent_chat_settings', [] );
		$defaults = self::wordai_chat_default_settings();
		$settings = wp_parse_args( $settings, $defaults );

		// $model  = sanitize_text_field( $settings['model'] ?? 'gpt-4o-mini' );
		$temp   = floatval( $settings['temperature'] ?? 0.7 );
		$prefix = wp_kses_post(
			$settings['prompt_prefix'] ?? 'You are a helpful assistant for this site.'
		);

		// Chat replies intentionally use the stable non-streaming path for all providers.
		$reply_mode = 'nonstream';
		$streaming  = false;

		// RAG settings
		$rag_enabled     = isset( $settings['rag_enabled'] ) ? (bool) $settings['rag_enabled'] : true;
		$rag_mode        = $settings['rag_mode']        ?? 'embeddings'; // embeddings | hybrid (future)
		$rag_answer_mode = $settings['rag_answer_mode'] ?? 'soft';       // strict | soft

		// Clamp temperature to [0,1] safely
		$temp = max( 0.0, min( 1.0, $temp ) );

		/*--------------------------------------------------------------
		| 3. Detect active provider (openai|google|…)
		--------------------------------------------------------------*/
		$provider = 'openai';
		if (
			class_exists( 'SftcyWordaiAiProviderRouter' )
			&& method_exists( 'SftcyWordaiAiProviderRouter', 'get_active_provider' )
		) {
			$provider = SftcyWordaiAiProviderRouter::get_active_provider() ?: 'openai';
		}


		// Get Active provider model
		if ( $provider === 'google' ) {
			$model = sanitize_text_field( $settings['google_model'] ?? ( class_exists( 'SftcyWordaiGoogleGeminiApi' ) ? SftcyWordaiGoogleGeminiApi::$MODEL : 'gemini-3.5-flash' ) );
		} elseif ( $provider === 'anthropic' ) {
			$model = sanitize_text_field( $settings['anthropic_model'] ?? ( class_exists( 'SftcyWordaiAnthropicApi' ) ? SftcyWordaiAnthropicApi::$MODEL : 'claude-sonnet-5' ) );
		} elseif ( $provider === 'xai' ) {
			$model = class_exists( 'SftcyWordaiXaiSettings' )
				? SftcyWordaiXaiSettings::normalize_xai_model_slug( $settings['xai_model'] ?? '' )
				: sanitize_text_field( $settings['xai_model'] ?? 'grok-4.5' );
		} else {
			$model = class_exists( 'SftcyWordaiOpenai' )
				? SftcyWordaiOpenai::normalize_openai_model( $settings['model'] ?? '' )
				: 'gpt-5.5';
		}		

		$analytics_enabled = ! empty( $settings['analytics_enabled'] );
		$conversation      = $analytics_enabled ? self::get_or_create_conversation( $conversation_key, $provider, $model, $page_url, $page_title ) : array( 'id' => 0, 'session_key' => self::normalize_session_key( $conversation_key ) );
		$conversation_id   = (int) ( $conversation['id'] ?? 0 );
		$conversation_key  = (string) ( $conversation['session_key'] ?? $conversation_key );
		$lead_result       = self::maybe_capture_lead_profile( $conversation_id, $conversation_key, $prompt, $page_url, $page_title, $settings );
		if ( is_wp_error( $lead_result ) ) {
			wp_send_json( array(
				'status'        => 'error',
				'reply'         => $lead_result->get_error_message(),
				'lead_required' => 1,
			) );
		}
		if ( $analytics_enabled && $conversation_id > 0 ) {
			self::log_chat_message( $conversation_id, 'user', $prompt, array(
				'provider' => $provider,
				'model'    => $model,
				'page_url' => $page_url,
			) );
		}

		// Get Active provider fallback message
		$fallback_msg = "I’m not sure from the available information.";

		if ( $provider === 'google' ) {
			$fallback_msg = trim( (string) ( $settings['fallback_google'] ?? $defaults['fallback_google'] ?? $fallback_msg ) );
		} elseif ( $provider === 'anthropic' ) {
			$fallback_msg = trim( (string) ( $settings['fallback_anthropic'] ?? $defaults['fallback_anthropic'] ?? $fallback_msg ) );
		} elseif ( $provider === 'xai' ) {
			$fallback_msg = trim( (string) ( $settings['fallback_xai'] ?? $defaults['fallback_xai'] ?? $fallback_msg ) );
		} else {
			$fallback_msg = trim( (string) ( $settings['fallback_openai'] ?? $defaults['fallback_openai'] ?? $fallback_msg ) );
		}

		if ( $fallback_msg === '' ) {
			$fallback_msg = "I’m not sure from the available information.";
		}


		/*--------------------------------------------------------------
		| 4. Build final messages (RAG-aware)
		|    - $user_prompt_final  = what we send as user message
		|    - $system_message     = system instruction
		|    - $assistant_context  = RAG context (assistant role)
		--------------------------------------------------------------*/
		$rag_provider = self::resolve_rag_source_provider( $provider, $settings );

		list( $user_prompt_final, $system_message, $assistant_context ) = self::build_rag_aware_prompt(
			$prompt,
			$prefix,
			$rag_provider,
			$rag_enabled,
			$rag_mode,
			$rag_answer_mode,
			$fallback_msg
		);

		if ( $user_prompt_final === '__WORDAI_DIRECT_FALLBACK__' ) {
			$latency_ms = self::elapsed_ms( $request_started );
			$message_id = self::log_chat_message( $conversation_id, 'assistant', $assistant_context, array(
				'provider'      => $provider,
				'model'         => $model,
				'page_url'      => $page_url,
				'source_count'  => 0,
				'fallback_used' => 1,
				'latency_ms'    => $latency_ms,
			) );
			$meta = self::build_response_meta( $conversation_id, $conversation_key, $message_id, array(), true, $latency_ms, $settings );
			if ( $streaming ) {
				self::send_stream_headers();
				echo "data: " . wp_json_encode( [ 'replyChunk' => wp_kses_post( $assistant_context ) ] ) . "\n\n";
				echo "data: " . wp_json_encode( [ 'wordaiMeta' => $meta ] ) . "\n\n";
				echo "data: [DONE]\n\n";
				exit;
			}

			wp_send_json( [
				'status' => 'success',
				'reply'  => wp_kses_post( $assistant_context ),
				'meta'   => $meta,
			] );
		}

		/**
		 * ================================================================
		 * 5. GOOGLE GEMINI CHAT HANDLING
		 * ================================================================
		 */
		if ( $provider === 'google' && class_exists( 'SftcyWordaiGoogleGeminiApi' ) ) {

		// 🔥 DEBUG: log full provider call parameters
		self::debug_log( 'PROVIDER CALL (Gemini): Prepared arguments', [
			'user_prompt_final' => $user_prompt_final,
			'system_message'    => $system_message,
			'assistant_context' => mb_substr($assistant_context, 0, 500),
			'model'             => $model,
			'temperature'       => $temp,
			'streaming'         => $streaming,
		]);			

			try {
				// STREAMING MODE
				if ( $streaming ) {
					self::send_stream_headers();
					$stream_result = SftcyWordaiGoogleGeminiApi::create_chat_reply(
						$user_prompt_final,
						$model,
						$temp,
						$system_message,
						false,
						$assistant_context  // context as a separate argument
					);
					if ( is_array( $stream_result ) ) {
						if ( ( $stream_result['status'] ?? '' ) === 'success' ) {
							$reply_text = (string) ( $stream_result['reply'] ?? '' );
							$latency_ms = self::elapsed_ms( $request_started );
							$message_id = self::log_chat_message( $conversation_id, 'assistant', $reply_text, array(
								'provider'      => $provider,
								'model'         => $model,
								'page_url'      => $page_url,
								'source_count'  => count( self::$last_rag_sources ),
								'fallback_used' => 0,
								'latency_ms'    => $latency_ms,
							) );
							self::store_message_sources( $message_id, $conversation_id, self::$last_rag_sources );
							echo "data: " . wp_json_encode( [ 'replyChunk' => $reply_text ] ) . "\n\n";
							echo "data: " . wp_json_encode( [ 'wordaiMeta' => self::build_response_meta( $conversation_id, $conversation_key, $message_id, self::$last_rag_sources, false, $latency_ms, $settings ) ] ) . "\n\n";
						} else {
							$error_msg = $stream_result['reply'] ?? __( 'Gemini request failed.', 'wordai-pro' );
							self::log_chat_failure( 'google', $error_msg, array( 'streaming' => true, 'model' => $model ) );
							self::mark_conversation_error( $conversation_id, $error_msg );
							echo "data: " . wp_json_encode( [ 'error' => [ 'message' => wp_strip_all_tags( $error_msg ) ] ] ) . "\n\n";
						}
						echo "data: [DONE]\n\n";
					}
					// Gemini streaming prints SSE & exits
					exit;
				}

				// NON-STREAM MODE
				$reply = SftcyWordaiGoogleGeminiApi::create_chat_reply(
					$user_prompt_final,
					$model,
					$temp,
					$system_message,
					false,
					$assistant_context
				);

				if ( empty( $reply ) || ! isset( $reply['status'] ) || $reply['status'] !== 'success' ) {
					$error_msg = $reply['reply'] ?? __( 'Gemini request failed.', 'wordai-pro' );
					self::log_chat_failure( 'google', $error_msg, array( 'streaming' => false, 'model' => $model ) );
					self::mark_conversation_error( $conversation_id, $error_msg );
					wp_send_json( [
						'status' => 'error',
						'reply'  => wp_kses_post( $error_msg ),
					] );
				}

				$latency_ms = self::elapsed_ms( $request_started );
				$message_id = self::log_chat_message( $conversation_id, 'assistant', $reply['reply'], array(
					'provider'      => $provider,
					'model'         => $model,
					'page_url'      => $page_url,
					'source_count'  => count( self::$last_rag_sources ),
					'fallback_used' => 0,
					'latency_ms'    => $latency_ms,
				) );
				self::store_message_sources( $message_id, $conversation_id, self::$last_rag_sources );
				wp_send_json( [
					'status' => 'success',
					'reply'  => wp_kses_post( $reply['reply'] ),
					'meta'   => self::build_response_meta( $conversation_id, $conversation_key, $message_id, self::$last_rag_sources, false, $latency_ms, $settings ),
				] );

			} catch ( Exception $e ) {
				self::mark_conversation_error( $conversation_id, $e->getMessage() );
				wp_send_json( [
					'status' => 'error',
					'reply'  => sprintf(
						__( 'Gemini request failed: %s', 'wordai-pro' ),
						esc_html( $e->getMessage() )
					),
				] );
			}

			return; // safety
		}

		/**
		 * ================================================================
		 * 6. ANTHROPIC CLAUDE CHAT HANDLING
		 * ================================================================
		 */
		if ( $provider === 'anthropic' && class_exists( 'SftcyWordaiAnthropicApi' ) ) {

			self::debug_log( 'PROVIDER CALL (Anthropic): Prepared arguments', [
				'user_prompt_final' => $user_prompt_final,
				'system_message'    => $system_message,
				'assistant_context' => mb_substr( $assistant_context, 0, 500 ),
				'model'             => $model,
				'temperature'       => $temp,
				'streaming'         => $streaming,
			] );

			try {
				if ( $streaming ) {
					self::send_stream_headers();
					$stream_result = SftcyWordaiAnthropicApi::create_chat_reply(
						$user_prompt_final,
						$model,
						$temp,
						$system_message,
						true,
						$assistant_context
					);
					if ( is_array( $stream_result ) ) {
						if ( ( $stream_result['status'] ?? '' ) === 'success' ) {
							$reply_text = (string) ( $stream_result['reply'] ?? '' );
							$latency_ms = self::elapsed_ms( $request_started );
							$message_id = self::log_chat_message( $conversation_id, 'assistant', $reply_text, array(
								'provider'      => $provider,
								'model'         => $model,
								'page_url'      => $page_url,
								'source_count'  => count( self::$last_rag_sources ),
								'fallback_used' => 0,
								'latency_ms'    => $latency_ms,
							) );
							self::store_message_sources( $message_id, $conversation_id, self::$last_rag_sources );
							echo "data: " . wp_json_encode( [ 'replyChunk' => $reply_text ] ) . "\n\n";
							echo "data: " . wp_json_encode( [ 'wordaiMeta' => self::build_response_meta( $conversation_id, $conversation_key, $message_id, self::$last_rag_sources, false, $latency_ms, $settings ) ] ) . "\n\n";
						} else {
							$error_msg = $stream_result['reply'] ?? __( 'Anthropic request failed.', 'wordai-pro' );
							self::log_chat_failure( 'anthropic', $error_msg, array( 'streaming' => true, 'model' => $model ) );
							self::mark_conversation_error( $conversation_id, $error_msg );
							echo "data: " . wp_json_encode( [ 'error' => [ 'message' => wp_strip_all_tags( $error_msg ) ] ] ) . "\n\n";
						}
						echo "data: [DONE]\n\n";
					}
					exit;
				}

				$reply = SftcyWordaiAnthropicApi::create_chat_reply(
					$user_prompt_final,
					$model,
					$temp,
					$system_message,
					false,
					$assistant_context
				);

				if ( ! isset( $reply['status'] ) || $reply['status'] !== 'success' ) {
					self::log_chat_failure( 'anthropic', $reply['reply'] ?? __( 'Anthropic request failed.', 'wordai-pro' ), array( 'streaming' => false, 'model' => $model ) );
					self::mark_conversation_error( $conversation_id, $reply['reply'] ?? __( 'Anthropic request failed.', 'wordai-pro' ) );
					wp_send_json( [
						'status' => 'error',
						'reply'  => $reply['reply'] ?? __( 'Anthropic request failed.', 'wordai-pro' ),
					] );
				}

				$latency_ms = self::elapsed_ms( $request_started );
				$message_id = self::log_chat_message( $conversation_id, 'assistant', $reply['reply'], array(
					'provider'      => $provider,
					'model'         => $model,
					'page_url'      => $page_url,
					'source_count'  => count( self::$last_rag_sources ),
					'fallback_used' => 0,
					'latency_ms'    => $latency_ms,
				) );
				self::store_message_sources( $message_id, $conversation_id, self::$last_rag_sources );
				wp_send_json( [
					'status' => 'success',
					'reply'  => wp_kses_post( $reply['reply'] ),
					'meta'   => self::build_response_meta( $conversation_id, $conversation_key, $message_id, self::$last_rag_sources, false, $latency_ms, $settings ),
				] );

			} catch ( Exception $e ) {
				self::log_chat_failure( 'google', $e->getMessage(), array( 'streaming' => (bool) $streaming, 'model' => $model ) );
				self::mark_conversation_error( $conversation_id, $e->getMessage() );
				wp_send_json( [
					'status' => 'error',
					'reply'  => sprintf(
						__( 'Anthropic request failed: %s', 'wordai-pro' ),
						esc_html( $e->getMessage() )
					),
				] );
			}

			return;
		}

		/**
		 * ================================================================
		 * 7. xAI GROK CHAT HANDLING
		 * ================================================================
		 */
		if ( $provider === 'xai' && class_exists( 'SftcyWordaiXaiApi' ) ) {

			self::debug_log( 'PROVIDER CALL (xAI Grok): Prepared arguments', [
				'user_prompt_final' => $user_prompt_final,
				'system_message'    => $system_message,
				'assistant_context' => mb_substr( $assistant_context, 0, 500 ),
				'model'             => $model,
				'temperature'       => $temp,
				'streaming'         => $streaming,
			] );

			try {
				if ( $streaming ) {
					self::send_stream_headers();
					$stream_result = SftcyWordaiXaiApi::create_chat_reply(
						$user_prompt_final,
						$model,
						$temp,
						$system_message,
						true,
						$assistant_context
					);
					if ( is_array( $stream_result ) ) {
						if ( ( $stream_result['status'] ?? '' ) === 'success' ) {
							$reply_text = (string) ( $stream_result['reply'] ?? '' );
							$latency_ms = self::elapsed_ms( $request_started );
							$message_id = self::log_chat_message( $conversation_id, 'assistant', $reply_text, array(
								'provider'      => $provider,
								'model'         => $model,
								'page_url'      => $page_url,
								'source_count'  => count( self::$last_rag_sources ),
								'fallback_used' => 0,
								'latency_ms'    => $latency_ms,
							) );
							self::store_message_sources( $message_id, $conversation_id, self::$last_rag_sources );
							echo "data: " . wp_json_encode( [ 'replyChunk' => $reply_text ] ) . "\n\n";
							echo "data: " . wp_json_encode( [ 'wordaiMeta' => self::build_response_meta( $conversation_id, $conversation_key, $message_id, self::$last_rag_sources, false, $latency_ms, $settings ) ] ) . "\n\n";
						} else {
							$error_msg = $stream_result['reply'] ?? __( 'xAI Grok request failed.', 'wordai-pro' );
							self::log_chat_failure( 'xai', $error_msg, array( 'streaming' => true, 'model' => $model ) );
							self::mark_conversation_error( $conversation_id, $error_msg );
							echo "data: " . wp_json_encode( [ 'error' => [ 'message' => wp_strip_all_tags( $error_msg ) ] ] ) . "\n\n";
						}
						echo "data: [DONE]\n\n";
					}
					exit;
				}

				$reply = SftcyWordaiXaiApi::create_chat_reply(
					$user_prompt_final,
					$model,
					$temp,
					$system_message,
					false,
					$assistant_context
				);

				if ( ! isset( $reply['status'] ) || $reply['status'] !== 'success' ) {
					self::log_chat_failure( 'xai', $reply['reply'] ?? __( 'xAI Grok request failed.', 'wordai-pro' ), array( 'streaming' => false, 'model' => $model ) );
					self::mark_conversation_error( $conversation_id, $reply['reply'] ?? __( 'xAI Grok request failed.', 'wordai-pro' ) );
					wp_send_json( [
						'status' => 'error',
						'reply'  => $reply['reply'] ?? __( 'xAI Grok request failed.', 'wordai-pro' ),
					] );
				}

				$latency_ms = self::elapsed_ms( $request_started );
				$message_id = self::log_chat_message( $conversation_id, 'assistant', $reply['reply'], array(
					'provider'      => $provider,
					'model'         => $model,
					'page_url'      => $page_url,
					'source_count'  => count( self::$last_rag_sources ),
					'fallback_used' => 0,
					'latency_ms'    => $latency_ms,
				) );
				self::store_message_sources( $message_id, $conversation_id, self::$last_rag_sources );
				wp_send_json( [
					'status' => 'success',
					'reply'  => wp_kses_post( $reply['reply'] ),
					'meta'   => self::build_response_meta( $conversation_id, $conversation_key, $message_id, self::$last_rag_sources, false, $latency_ms, $settings ),
				] );

			} catch ( Exception $e ) {
				self::log_chat_failure( 'xai', $e->getMessage(), array( 'streaming' => (bool) $streaming, 'model' => $model ) );
				self::mark_conversation_error( $conversation_id, $e->getMessage() );
				wp_send_json( [
					'status' => 'error',
					'reply'  => sprintf(
						__( 'xAI Grok request failed: %s', 'wordai-pro' ),
						esc_html( $e->getMessage() )
					),
				] );
			}

			return;
		}

		/**
		 * ================================================================
		 * 8. OPENAI CHAT HANDLING  (DEFAULT PROVIDER)
		 * ================================================================
		 */
		if ( class_exists( 'SftcyWordaiOpenai' ) ) {

			// 🔥 DEBUG: log full provider call parameters
			self::debug_log( 'PROVIDER CALL (OpenAI): Prepared arguments', [
				'user_prompt_final' => $user_prompt_final,
				'system_message'    => $system_message,
				'assistant_context' => mb_substr($assistant_context, 0, 500),
				'model'             => $model,
				'temperature'       => $temp,
				'streaming'         => $streaming,
			]);

			try {
				// STREAMING
				if ( $streaming ) {
					self::send_stream_headers();
					$stream_result = SftcyWordaiOpenai::create_chat_reply(
						$user_prompt_final,
						$model,
						$temp,
						$system_message,
						true,
						$assistant_context
					);
					if ( is_array( $stream_result ) ) {
						if ( ( $stream_result['status'] ?? '' ) === 'success' ) {
							$reply_text = (string) ( $stream_result['reply'] ?? '' );
							$latency_ms = self::elapsed_ms( $request_started );
							$message_id = self::log_chat_message( $conversation_id, 'assistant', $reply_text, array(
								'provider'      => $provider,
								'model'         => $model,
								'page_url'      => $page_url,
								'source_count'  => count( self::$last_rag_sources ),
								'fallback_used' => 0,
								'latency_ms'    => $latency_ms,
							) );
							self::store_message_sources( $message_id, $conversation_id, self::$last_rag_sources );
							echo "data: " . wp_json_encode( [ 'replyChunk' => $reply_text ] ) . "\n\n";
							echo "data: " . wp_json_encode( [ 'wordaiMeta' => self::build_response_meta( $conversation_id, $conversation_key, $message_id, self::$last_rag_sources, false, $latency_ms, $settings ) ] ) . "\n\n";
						} else {
							$error_msg = $stream_result['reply'] ?? __( 'OpenAI request failed.', 'wordai-pro' );
							self::log_chat_failure( 'openai', $error_msg, array( 'streaming' => true, 'model' => $model ) );
							self::mark_conversation_error( $conversation_id, $error_msg );
							echo "data: " . wp_json_encode( [ 'error' => [ 'message' => wp_strip_all_tags( $error_msg ) ] ] ) . "\n\n";
						}
						echo "data: [DONE]\n\n";
					}
					// OpenAI streaming prints SSE & exits
					exit;
				}

				// NON-STREAM
				$reply = SftcyWordaiOpenai::create_chat_reply(
					$user_prompt_final,
					$model,
					$temp,
					$system_message,
					false,
					$assistant_context
				);

				if ( ! isset( $reply['status'] ) || $reply['status'] !== 'success' ) {
					self::log_chat_failure( 'openai', $reply['reply'] ?? __( 'OpenAI request failed.', 'wordai-pro' ), array( 'streaming' => false, 'model' => $model ) );
					self::mark_conversation_error( $conversation_id, $reply['reply'] ?? __( 'OpenAI request failed.', 'wordai-pro' ) );
					wp_send_json( [
						'status' => 'error',
						'reply'  => $reply['reply'] ?? __( 'OpenAI request failed.', 'wordai-pro' ),
					] );
				}

				$latency_ms = self::elapsed_ms( $request_started );
				$message_id = self::log_chat_message( $conversation_id, 'assistant', $reply['reply'], array(
					'provider'      => $provider,
					'model'         => $model,
					'page_url'      => $page_url,
					'source_count'  => count( self::$last_rag_sources ),
					'fallback_used' => 0,
					'latency_ms'    => $latency_ms,
				) );
				self::store_message_sources( $message_id, $conversation_id, self::$last_rag_sources );
				wp_send_json( [
					'status' => 'success',
					'reply'  => wp_kses_post( $reply['reply'] ),
					'meta'   => self::build_response_meta( $conversation_id, $conversation_key, $message_id, self::$last_rag_sources, false, $latency_ms, $settings ),
				] );

			} catch ( Exception $e ) {
				self::log_chat_failure( 'openai', $e->getMessage(), array( 'streaming' => (bool) $streaming, 'model' => $model ) );
				self::mark_conversation_error( $conversation_id, $e->getMessage() );
				wp_send_json( [
					'status' => 'error',
					'reply'  => sprintf(
						__( 'OpenAI request failed: %s', 'wordai-pro' ),
						esc_html( $e->getMessage() )
					),
				] );
			}

			return;
		}

		/*--------------------------------------------------------------
		| 7. NO PROVIDER — final safety fallback
		--------------------------------------------------------------*/
		wp_send_json( [
			'status' => 'error',
			'reply'  => __( 'No AI provider available.', 'wordai-pro' ),
		] );
	}

	/**
	 * ============================================================
	 * Build RAG-aware Prompt
	 * ------------------------------------------------------------
	 * - Builds a context string from stored embeddings
	 * - Strict Provider Separation:
	 *      only rows where provider = current provider
	 * - Returns three pieces:
	 *      [ $user_prompt_final, $system_message, $assistant_context ]
	 *
	 * @param string $user_prompt     Raw user question.
	 * @param string $base_system     System/prefix from settings.
	 * @param string $provider        'openai' | 'google' | other.
	 * @param bool   $rag_enabled     Global RAG toggle.
	 * @param string $rag_mode        'embeddings' | 'hybrid' (future).
	 * @param string $answer_mode     'strict' | 'soft' (RAG answer mode).
	 *
	 * @return array
	 * ============================================================
	 */
	private static function build_rag_aware_prompt(
		$user_prompt,
		$base_system,
		$provider,
		$rag_enabled = true,
		$rag_mode = 'embeddings',
		$answer_mode = 'soft',
		$fallback_msg = ''
	) {
		self::$last_rag_sources = array();
		// Normalize answer mode
		$answer_mode = ( $answer_mode === 'strict' ) ? 'strict' : 'soft';

		// Allow complete RAG disable via filter or settings
		$rag_enabled = apply_filters( 'sc_wordai_agent_rag_enabled', $rag_enabled );

		// Basic system message if empty
		$base_system = $base_system ?: 'You are a helpful assistant for this site.';

		// If RAG disabled → behave like normal chat, no context
		if ( ! $rag_enabled ) {
			$system_message    = $base_system;
			$user_prompt_final = trim( $user_prompt );
			self::debug_log( 'RAG disabled; using plain system + user only.' );
			return [ $user_prompt_final, $system_message, '' ];
		}

		// -----------------------------------------------------------------
		// 1️⃣ Build embedding-based context (strict provider separation)
		// -----------------------------------------------------------------
		$context = '';
		if ( $rag_mode === 'embeddings' || $rag_mode === 'hybrid' ) {
			$context = self::build_rag_context( $user_prompt, $provider );
		}

		// -----------------------------------------------------------------
		// 2️⃣ (Future) "Hybrid" Native Search context
		// -----------------------------------------------------------------
		if ( $rag_mode === 'hybrid' ) {
			$native = self::build_native_search_context( $user_prompt );
			if ( ! empty( $native ) ) {
				if ( ! empty( $context ) ) {
					$context .= "\n\n---\n\n" . $native;
				} else {
					$context = $native;
				}
			}
		}

		$is_general = self::is_general_assistant_prompt( $user_prompt );

		// If no context at all → fallback to classic prompt (no RAG)
		if ( empty( $context ) ) {
			if ( ! $is_general ) {
				$not_sure = trim( (string) $fallback_msg );
				if ( $not_sure === '' ) {
					$not_sure = "I’m not sure from the available information.";
				}
				self::debug_log( 'No RAG context built for non-general question; returning direct fallback.' );
				return [ '__WORDAI_DIRECT_FALLBACK__', $base_system, $not_sure ];
			}

			$system_message    = $base_system;
			$user_prompt_final = trim( $user_prompt );
			self::debug_log( 'No RAG context built; using classic chat (no fallback phrase).' );
			return [ $user_prompt_final, $system_message, '' ];
		}

		// Default fallback phrase when answer not found in context
		$not_sure = trim((string)$fallback_msg);
		if ($not_sure === '') $not_sure = "I’m not sure from the available information.";


		if ( $answer_mode === 'soft' && $is_general ) {
			$system_message    = $base_system; // keep your normal prefix only
			$user_prompt_final = trim( $user_prompt );

			self::debug_log( 'General question detected; bypassing RAG context.' );

			return [ $user_prompt_final, $system_message, '' ];
		}		

		// -------------------------------------------------------------
		// System message depends on answer mode (strict vs soft)
		// -------------------------------------------------------------
		if ( $answer_mode === 'strict' ) {
			// STRICT: Only use site context; no hallucination allowed.
			$system_message = $base_system
				. ' You must answer ONLY using the website context that will be provided to you as previous assistant messages.'
				. ' Do not use outside knowledge. If the answer is not present in that context, reply exactly with: "'
				. $not_sure . '" and do not invent information.';
		} else {
			// SOFT:
			// - Prefer website context when it helps
			// - But allow normal helpful answers for general questions (capabilities, greetings, etc.)
			$system_message = $base_system
				. ' Use the website context that will be provided to you as previous assistant messages as the primary source for your answer when it is relevant.'
				. ' You may add short general explanations if helpful, but never contradict the context.';

			// Only enforce fallback for non-general questions
			if ( ! $is_general ) {
				$system_message .= ' If NONE of the context is relevant to the question, reply with: "'
					. $not_sure . '" rather than guessing.';
			}
		}


		$user_prompt_final = trim( $user_prompt );
		$assistant_context = $context;

		self::debug_log( 'RAG context attached to assistant role.', [
			'provider'    => $provider,
			'answer_mode' => $answer_mode,
			'ctx_len'     => strlen( $assistant_context ),
		] );

		return [ $user_prompt_final, $system_message, $assistant_context ];
	}

	/**
	 * Build RAG Context from embeddings (Embeddings-only)
	 * ------------------------------------------------------------
	 * Strict Provider Separation:
	 *   - Only rows where provider = current provider are used.
	 *   - Primary filter: provider + model (same embedding model)
	 *   - Fallback: provider-only if model-based query returns none
	 *
	 * Steps:
	 *  1. Determine embedding model for this provider
	 *  2. Embed user question with same provider + model
	 *  3. Load recent embeddings from wp_wordai_agent_embeddings
	 *  4. Compute cosine similarity
	 *  5. Keep top-K snippets above a similarity threshold
	 *  6. Return a plain-text context block
	 *
	 * @param string $user_prompt
	 * @param string $provider   'openai' or 'google'
	 *
	 * @return string Context block or empty string
	 * ============================================================
	 */
	private static function build_rag_context( $user_prompt, $provider ) {
		global $wpdb;

		$user_prompt = trim( wp_strip_all_tags( $user_prompt ) );
		if ( $user_prompt === '' ) {
			return '';
		}

		// Decide which embedding model to use for this provider
		$embedding_model = self::get_embedding_model_for_provider( $provider );

		// Debug Logging
		self::debug_log( 'RAG using provider/model', [
			'provider' => $provider,
			'model'    => $embedding_model,
		] );

		// Get query embedding vector (strictly via same provider + model)
		$query_vec = self::embed_query_text_for_rag( $user_prompt, $provider, $embedding_model );

		// Debug Logging
		self::debug_log( 'Query embedding created', [
			'length'   => is_array( $query_vec ) ? count( $query_vec ) : 0,
			'is_empty' => empty( $query_vec ),
		] );

		if ( empty( $query_vec ) || ! is_array( $query_vec ) ) {
			return '';
		}

		$table           = $wpdb->prefix . 'wordai_agent_embeddings';
		$sources_table   = $wpdb->prefix . 'wordai_agent_sources';
		$selection_table = $wpdb->prefix . 'wordai_agent_source_selection';

		// How many rows to scan at most (most recent first)
		$scan_limit = (int) apply_filters( 'sc_wordai_agent_rag_scan_limit', 400 );
		if ( $scan_limit < 1 ) {
			$scan_limit = 200;
		}

		// -------------------------------------------------------------
		// 1️⃣ Primary candidate fetch: provider + same model
		// -------------------------------------------------------------
		$rows = [];

		if ( ! empty( $embedding_model ) ) {
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT DISTINCT e.id, e.source_id, e.chunk_text, e.embedding, e.model, e.provider
					FROM {$table} e
					INNER JOIN {$sources_table} s ON s.source_id = e.source_id
					INNER JOIN {$selection_table} sel ON sel.source_row_id = s.id AND sel.selected = 1
					WHERE e.provider = %s AND e.model = %s
					ORDER BY e.id DESC
					LIMIT %d",
					$provider,
					$embedding_model,
					$scan_limit
				),
				ARRAY_A
			);
		}

		// Debug Logging
		self::debug_log( 'Embedding rows fetched for provider/model: ', count( $rows ) );
		if ( empty( $rows ) ) {
			self::debug_log( 'Fallback: No rows for provider/model, using provider-only: ', $provider );
		}

		// -------------------------------------------------------------
		// 2️⃣ Fallback candidate fetch: provider-only
		// -------------------------------------------------------------
		if ( empty( $rows ) ) {
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT DISTINCT e.id, e.source_id, e.chunk_text, e.embedding, e.model, e.provider
					FROM {$table} e
					INNER JOIN {$sources_table} s ON s.source_id = e.source_id
					INNER JOIN {$selection_table} sel ON sel.source_row_id = s.id AND sel.selected = 1
					WHERE e.provider = %s
					ORDER BY e.id DESC
					LIMIT %d",
					$provider,
					$scan_limit
				),
				ARRAY_A
			);
		}

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

		$scored = [];

		foreach ( $rows as $row ) {

			// 🔹 Decode embedding payload (supports JSON or serialized PHP array)
			$emb_raw = $row['embedding'];
			$emb     = null;

			if ( is_string( $emb_raw ) ) {
				// Try JSON first
				$emb = json_decode( $emb_raw, true );
				if ( ! is_array( $emb ) ) {
					// Fallback to maybe_unserialize (for old storage format)
					$emb = maybe_unserialize( $emb_raw );
				}
			} elseif ( is_array( $emb_raw ) ) {
				$emb = $emb_raw;
			}

			if ( ! is_array( $emb ) || empty( $emb ) ) {
				continue;
			}

			// Normalize numeric vector
			$emb = array_map( 'floatval', $emb );

			$sim = self::cosine_similarity( $query_vec, $emb );
			if ( $sim <= 0 ) {
				continue;
			}

			// Debug Logging
			self::debug_log( 'Similarity score', [
				'score'     => $sim,
				'source_id' => $row['source_id'],
			] );

			$scored[] = [
				'score'     => $sim,
				'text'      => $row['chunk_text'],
				'source_id' => (int) $row['source_id'],
			];
		}

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

		// Sort by similarity descending
		usort(
			$scored,
			static function ( $a, $b ) {
				if ( $a['score'] === $b['score'] ) {
					return 0;
				}
				return ( $a['score'] > $b['score'] ) ? -1 : 1;
			}
		);

		$top_k     = (int) apply_filters( 'sc_wordai_agent_rag_top_k', 3 );
		$threshold = (float) apply_filters( 'sc_wordai_agent_rag_min_similarity', 0.30 );

		$top_chunks = array_slice( $scored, 0, $top_k );
		$parts      = [];
		$sources    = [];
		$idx        = 1;

		foreach ( $top_chunks as $row ) {
			// ✅ Only accept snippets that CLEARLY pass the similarity threshold
			if ( $row['score'] < $threshold ) {
				continue;
			}

			$snippet = trim( wp_strip_all_tags( (string) $row['text'] ) );
			if ( $snippet === '' ) {
				continue;
			}

			// Hard cap per snippet to avoid huge prompts
			if ( function_exists( 'mb_substr' ) ) {
				$snippet = mb_substr( $snippet, 0, 1200 );
			} else {
				$snippet = substr( $snippet, 0, 1200 );
			}

			$parts[] = sprintf(
				"Snippet %d (score: %.3f):\n%s",
				$idx++,
				$row['score'],
				$snippet
			);

			$source_id = (int) $row['source_id'];
			if ( $source_id > 0 && ! isset( $sources[ $source_id ] ) ) {
				$sources[ $source_id ] = array(
					'source_id'   => $source_id,
					'source_type' => get_post_type( $source_id ) ?: '',
					'title'       => get_the_title( $source_id ),
					'url'         => get_permalink( $source_id ),
					'score'       => (float) $row['score'],
				);
			}
		}

		// ❌ If NOTHING passed the threshold, we return an empty context.
		//    This means the chat will behave like normal non-RAG chat.
		if ( empty( $parts ) ) {
			self::debug_log( 'No chunks above similarity threshold; skipping RAG context.' );
			return '';
		}

		$context = implode( "\n\n---\n\n", $parts );

		// Global cap for context size
		if ( function_exists( 'mb_substr' ) ) {
			$context = mb_substr( $context, 0, 4000 );
		} else {
			$context = substr( $context, 0, 4000 );
		}

		// Debug Logging
		self::debug_log( 'Top chunks selected', [
			'chunks' => count( $parts ),
		] );

		self::$last_rag_sources = array_values( $sources );

		return $context;
	}

	/**
	 * Determine which embedding model to use for RAG for a given provider.
	 *
	 * Priority:
	 *  1) Most recent model actually stored in wp_wordai_agent_embeddings
	 *     for this provider (keeps RAG aligned with real data).
	 *  2) Provider-specific default constant (AgentOpenai/AgentGemini).
	 *  3) Hard fallback to 'text-embedding-3-small'.
	 *
	 * Filter: 'sc_wordai_agent_rag_embedding_model'
	 *
	 * @param string $provider
	 * @return string
	 */
	private static function get_embedding_model_for_provider( $provider ) {
		global $wpdb;

		$provider = $provider ?: 'openai';
		$table    = $wpdb->prefix . 'wordai_agent_embeddings';

		// 1️⃣ Try most recent model actually stored for this provider
		$model = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT model
				 FROM {$table}
				 WHERE provider = %s AND model <> ''
				 ORDER BY id DESC
				 LIMIT 1",
				$provider
			)
		);

		if ( ! empty( $model ) ) {
			$model = (string) $model;
		} else {
			// 2️⃣ Provider-specific defaults from agent classes
			if ( $provider === 'google'
				&& class_exists( 'SftcyWordaiAgentGemini' )
				&& defined( 'SftcyWordaiAgentGemini::DEFAULT_EMBEDDING_MODEL' )
			) {
				$model = SftcyWordaiAgentGemini::DEFAULT_EMBEDDING_MODEL;
			} elseif ( $provider === 'anthropic' ) {
				$model = class_exists( 'SftcyWordaiAgentAnthropic' )
					&& defined( 'SftcyWordaiAgentAnthropic::DEFAULT_EMBEDDING_MODEL' )
					? SftcyWordaiAgentAnthropic::DEFAULT_EMBEDDING_MODEL
					: '';
			} elseif ( $provider === 'xai' ) {
				$model = class_exists( 'SftcyWordaiAgentXai' )
					&& defined( 'SftcyWordaiAgentXai::DEFAULT_EMBEDDING_MODEL' )
					? SftcyWordaiAgentXai::DEFAULT_EMBEDDING_MODEL
					: 'grok-embedding-small';
			} elseif ( $provider === 'openai'
				&& class_exists( 'SftcyWordaiAgentOpenai' )
				&& defined( 'SftcyWordaiAgentOpenai::DEFAULT_EMBEDDING_MODEL' )
			) {
				$model = SftcyWordaiAgentOpenai::DEFAULT_EMBEDDING_MODEL;
			} else {
				// 3️⃣ Hard fallback
				$model = 'text-embedding-3-small';
			}
		}

		/**
		 * Allow external override of the chosen embedding model.
		 */
		$model = apply_filters( 'sc_wordai_agent_rag_embedding_model', $model, $provider );

		return $model;
	}

	/**
	 * Embed a single user question using the same provider + model as indexing.
	 *
	 * @param string $text
	 * @param string $provider
	 * @param string $embedding_model
	 * @return array Numeric vector or empty array on failure
	 */
	private static function embed_query_text_for_rag( $text, $provider, $embedding_model ) {

		$text = trim( wp_strip_all_tags( $text ) );
		if ( $text === '' ) {
			return [];
		}

		$response = null;

		if ( $provider === 'google' && class_exists( 'SftcyWordaiAgentGemini' ) ) {
			$response = SftcyWordaiAgentGemini::create_embeddings( [ $text ], $embedding_model );
		} elseif ( $provider === 'anthropic' && class_exists( 'SftcyWordaiAgentAnthropic' ) ) {
			$response = SftcyWordaiAgentAnthropic::create_embeddings( [ $text ], $embedding_model, 'query' );
		} elseif ( $provider === 'xai' && class_exists( 'SftcyWordaiAgentXai' ) ) {
			$response = SftcyWordaiAgentXai::create_embeddings( [ $text ], $embedding_model );
		} elseif ( $provider === 'openai' && class_exists( 'SftcyWordaiAgentOpenai' ) ) {
			// Default: OpenAI path
			$response = SftcyWordaiAgentOpenai::create_embeddings( [ $text ], $embedding_model );
		} else {
			return [];
		}

		if ( ! is_array( $response ) || ( $response['status'] ?? 'fail' ) !== 'success' ) {
			return [];
		}

		if ( empty( $response['embeddings'][0] ) || ! is_array( $response['embeddings'][0] ) ) {
			return [];
		}

		// Normalize to float vector
		return array_map( 'floatval', $response['embeddings'][0] );
	}

	private static function resolve_rag_source_provider( $chat_provider, array $settings ) {
		$chat_provider = sanitize_key( (string) $chat_provider );

		$key    = $chat_provider . '_embedding_source';
		$source = sanitize_key( (string) ( $settings[ $key ] ?? $chat_provider ) );
		if ( ! in_array( $source, [ 'anthropic', 'openai', 'google', 'xai' ], true ) ) {
			$source = $chat_provider ?: 'openai';
		}

		return $source;
	}

	private static function send_stream_headers() {
		if ( headers_sent() ) {
			return;
		}

		nocache_headers();
		header( 'Content-Type: text/event-stream; charset=UTF-8' );
		header( 'Cache-Control: no-cache, no-transform' );
		header( 'X-Accel-Buffering: no' );
	}

	private static function elapsed_ms( $started ) {
		return max( 0, (int) round( ( microtime( true ) - (float) $started ) * 1000 ) );
	}

	private static function normalize_session_key( $session_key ) {
		$session_key = sanitize_text_field( (string) $session_key );
		if ( ! preg_match( '/^[a-f0-9-]{20,64}$/i', $session_key ) ) {
			$session_key = wp_generate_uuid4();
		}
		return $session_key;
	}

	private static function visitor_hash_from_session( $session_key ) {
		$salt = defined( 'AUTH_SALT' ) ? AUTH_SALT : wp_salt( 'auth' );
		return hash( 'sha256', $session_key . '|' . $salt );
	}

	private static function get_or_create_conversation( $session_key, $provider, $model, $page_url = '', $page_title = '' ) {
		global $wpdb;

		$session_key  = self::normalize_session_key( $session_key );
		$visitor_hash = self::visitor_hash_from_session( $session_key );
		$table        = $wpdb->prefix . 'wordai_agent_conversations';
		$user_email   = '';

		if ( is_user_logged_in() ) {
			$user = wp_get_current_user();
			if ( $user && ! empty( $user->user_email ) ) {
				$user_email = sanitize_email( $user->user_email );
			}
		}

		$id = (int) $wpdb->get_var(
			$wpdb->prepare( "SELECT id FROM {$table} WHERE session_key = %s", $session_key )
		);

		$data = array(
			'user_email'   => $user_email,
			'visitor_hash' => $visitor_hash,
			'page_url'     => $page_url,
			'page_title'   => $page_title,
			'provider'     => sanitize_key( $provider ),
			'model'        => sanitize_text_field( $model ),
			'status'       => 'open',
			'updated_at'   => current_time( 'mysql' ),
		);

		if ( $id > 0 ) {
			$wpdb->update( $table, $data, array( 'id' => $id ) );
		} else {
			$data['session_key'] = $session_key;
			$data['created_at']  = current_time( 'mysql' );
			$wpdb->insert( $table, $data );
			$id = (int) $wpdb->insert_id;
		}

		return array(
			'id'          => $id,
			'session_key' => $session_key,
			'visitor_hash' => $visitor_hash,
		);
	}

	private static function log_chat_message( $conversation_id, $role, $content, array $meta = array() ) {
		global $wpdb;

		$conversation_id = (int) $conversation_id;
		if ( $conversation_id <= 0 ) {
			return 0;
		}

		$table = $wpdb->prefix . 'wordai_agent_messages';
		$wpdb->insert(
			$table,
			array(
				'conversation_id' => $conversation_id,
				'message_uuid'    => wp_generate_uuid4(),
				'role'            => in_array( $role, array( 'user', 'assistant' ), true ) ? $role : 'assistant',
				'content'         => wp_kses_post( (string) $content ),
				'provider'        => sanitize_key( $meta['provider'] ?? 'openai' ),
				'model'           => sanitize_text_field( $meta['model'] ?? '' ),
				'page_url'        => esc_url_raw( $meta['page_url'] ?? '' ),
				'source_count'    => max( 0, (int) ( $meta['source_count'] ?? 0 ) ),
				'fallback_used'   => ! empty( $meta['fallback_used'] ) ? 1 : 0,
				'latency_ms'      => max( 0, (int) ( $meta['latency_ms'] ?? 0 ) ),
				'created_at'      => current_time( 'mysql' ),
			)
		);

		$message_id = (int) $wpdb->insert_id;
		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$wpdb->prefix}wordai_agent_conversations
				 SET message_count = message_count + 1,
				     last_source_count = %d,
				     status = %s,
				     updated_at = %s
				 WHERE id = %d",
				max( 0, (int) ( $meta['source_count'] ?? 0 ) ),
				! empty( $meta['fallback_used'] ) ? 'needs_review' : 'open',
				current_time( 'mysql' ),
				$conversation_id
			)
		);

		return $message_id;
	}

	private static function store_message_sources( $message_id, $conversation_id, array $sources ) {
		global $wpdb;

		$message_id      = (int) $message_id;
		$conversation_id = (int) $conversation_id;
		if ( $message_id <= 0 || $conversation_id <= 0 || empty( $sources ) ) {
			return;
		}

		$table = $wpdb->prefix . 'wordai_agent_message_sources';
		foreach ( $sources as $source ) {
			$source_id = (int) ( $source['source_id'] ?? 0 );
			if ( $source_id <= 0 ) {
				continue;
			}
			$wpdb->insert(
				$table,
				array(
					'message_id'      => $message_id,
					'conversation_id' => $conversation_id,
					'source_id'       => $source_id,
					'source_type'     => sanitize_key( $source['source_type'] ?? get_post_type( $source_id ) ),
					'source_title'    => sanitize_text_field( $source['title'] ?? get_the_title( $source_id ) ),
					'source_url'      => esc_url_raw( $source['url'] ?? get_permalink( $source_id ) ),
					'similarity'      => (float) ( $source['score'] ?? 0 ),
					'created_at'      => current_time( 'mysql' ),
				)
			);
		}
	}

	private static function get_public_lead_capture_config( array $settings ) {
		$is_basic_tier = class_exists( 'SftcyWordaiFeatures' ) && SftcyWordaiFeatures::wordai_is_basic_tier();
		$mode          = sanitize_key( (string) ( $settings['lead_capture_mode'] ?? 'off' ) );
		$allowed_modes = array( 'off', 'optional_before_chat', 'required_before_chat', 'after_first_reply', 'fallback_handoff' );
		if ( ! in_array( $mode, $allowed_modes, true ) ) {
			$mode = 'off';
		}

		$fields = array(
			'name'    => array(
				'enabled'  => ! empty( $settings['lead_name_enabled'] ) ? 1 : 0,
				'required' => ! empty( $settings['lead_name_required'] ) ? 1 : 0,
				'label'    => __( 'Name', 'wordai-pro' ),
				'type'     => 'text',
			),
			'email'   => array(
				'enabled'  => ! $is_basic_tier && ! empty( $settings['lead_email_enabled'] ) ? 1 : 0,
				'required' => ! $is_basic_tier && ! empty( $settings['lead_email_required'] ) ? 1 : 0,
				'label'    => __( 'Email', 'wordai-pro' ),
				'type'     => 'email',
			),
			'phone'   => array(
				'enabled'  => ! $is_basic_tier && ! empty( $settings['lead_phone_enabled'] ) ? 1 : 0,
				'required' => ! $is_basic_tier && ! empty( $settings['lead_phone_required'] ) ? 1 : 0,
				'label'    => __( 'Phone', 'wordai-pro' ),
				'type'     => 'tel',
			),
			'company' => array(
				'enabled'  => ! $is_basic_tier && ! empty( $settings['lead_company_enabled'] ) ? 1 : 0,
				'required' => ! $is_basic_tier && ! empty( $settings['lead_company_required'] ) ? 1 : 0,
				'label'    => __( 'Company', 'wordai-pro' ),
				'type'     => 'text',
			),
			'message' => array(
				'enabled'  => ! $is_basic_tier && ! empty( $settings['lead_message_enabled'] ) ? 1 : 0,
				'required' => ! $is_basic_tier && ! empty( $settings['lead_message_required'] ) ? 1 : 0,
				'label'    => __( 'What are you interested in?', 'wordai-pro' ),
				'type'     => 'textarea',
			),
		);

		$has_enabled_field = false;
		foreach ( $fields as $field ) {
			if ( ! empty( $field['enabled'] ) ) {
				$has_enabled_field = true;
				break;
			}
		}

		if ( ! $has_enabled_field ) {
			$mode = 'off';
		}

		return array(
			'mode'             => $mode,
			'is_basic'         => $is_basic_tier ? 1 : 0,
			'intro_text'       => sanitize_textarea_field( $settings['lead_intro_text'] ?? __( 'Leave your details and we can follow up with a better answer.', 'wordai-pro' ) ),
			'consent_text'     => sanitize_text_field( $settings['lead_consent_text'] ?? __( 'I agree to be contacted about my request.', 'wordai-pro' ) ),
			'privacy_url'      => esc_url_raw( $settings['lead_privacy_url'] ?? home_url( '/privacy-policy/' ) ),
			'consent_required' => ! empty( $settings['lead_consent_required'] ) ? 1 : 0,
			'fields'           => $fields,
			'labels'           => array(
				'submit'   => __( 'Save details', 'wordai-pro' ),
				'skip'     => __( 'Skip', 'wordai-pro' ),
				'required' => __( 'Please complete the required contact fields before asking a question.', 'wordai-pro' ),
				'saved'    => __( 'Thanks. Your details were saved for follow-up.', 'wordai-pro' ),
			),
		);
	}

	public static function sc_wordai_agent_save_lead() {
		check_ajax_referer( 'scwordai_wpnonce', 'security' );

		$settings = wp_parse_args( (array) get_option( 'sc_wordai_agent_chat_settings', array() ), self::wordai_chat_default_settings() );
		$config   = self::get_public_lead_capture_config( $settings );

		if ( 'off' === ( $config['mode'] ?? 'off' ) ) {
			wp_send_json_error( array( 'message' => __( 'Lead capture is not enabled for this chat assistant.', 'wordai-pro' ) ), 400 );
		}

		$conversation_key = isset( $_POST['conversation_key'] ) ? sanitize_text_field( wp_unslash( $_POST['conversation_key'] ) ) : '';
		$page_url         = isset( $_POST['page_url'] ) ? esc_url_raw( wp_unslash( $_POST['page_url'] ) ) : '';
		$page_title       = isset( $_POST['page_title'] ) ? sanitize_text_field( wp_unslash( $_POST['page_title'] ) ) : '';
		$provider         = class_exists( 'SftcyWordaiBase' ) ? SftcyWordaiBase::sc_wordai_get_current_ai_provider() : 'openai';
		$model            = sanitize_text_field( $settings['model'] ?? '' );
		$conversation     = self::get_or_create_conversation( $conversation_key, $provider, $model, $page_url, $page_title );
		$conversation_id  = isset( $conversation['id'] ) ? (int) $conversation['id'] : 0;

		$result = self::maybe_capture_lead_profile(
			$conversation_id,
			$conversation_key,
			__( 'Visitor submitted contact details through the chat assistant lead form.', 'wordai-pro' ),
			$page_url,
			$page_title,
			$settings
		);

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( array( 'message' => $result->get_error_message() ), 400 );
		}

		wp_send_json_success( array( 'message' => __( 'Thanks. Your details were saved for follow-up.', 'wordai-pro' ) ) );
	}
	private static function maybe_capture_lead_profile( $conversation_id, $conversation_key, $prompt, $page_url, $page_title, array $settings ) {
		$conversation_id = (int) $conversation_id;
		if ( $conversation_id <= 0 ) {
			return true;
		}

		$config = self::get_public_lead_capture_config( $settings );
		if ( 'off' === ( $config['mode'] ?? 'off' ) ) {
			return true;
		}

		$raw_email = isset( $_POST['lead_email'] ) ? trim( (string) wp_unslash( $_POST['lead_email'] ) ) : '';
		if ( '' !== $raw_email && ! is_email( $raw_email ) ) {
			return new WP_Error( 'wordai_lead_invalid_email', __( 'Please enter a valid email address.', 'wordai-pro' ) );
		}

		$lead = array(
			'name'          => isset( $_POST['lead_name'] ) ? sanitize_text_field( wp_unslash( $_POST['lead_name'] ) ) : '',
			'email'         => '' !== $raw_email ? sanitize_email( $raw_email ) : '',
			'phone'         => isset( $_POST['lead_phone'] ) ? sanitize_text_field( wp_unslash( $_POST['lead_phone'] ) ) : '',
			'company'       => isset( $_POST['lead_company'] ) ? sanitize_text_field( wp_unslash( $_POST['lead_company'] ) ) : '',
			'message'       => isset( $_POST['lead_message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['lead_message'] ) ) : '',
			'consent_given' => isset( $_POST['lead_consent'] ) ? (int) $_POST['lead_consent'] : 0,
		);

		foreach ( $config['fields'] as $field_key => $field ) {
			if ( empty( $field['enabled'] ) ) {
				$lead[ $field_key ] = '';
			}
		}

		$missing_required = false;
		if ( 'required_before_chat' === $config['mode'] ) {
			foreach ( $config['fields'] as $field_key => $field ) {
				if ( ! empty( $field['enabled'] ) && ! empty( $field['required'] ) && '' === trim( (string) ( $lead[ $field_key ] ?? '' ) ) ) {
					$missing_required = true;
				}
			}
			if ( ! empty( $config['consent_required'] ) && empty( $lead['consent_given'] ) ) {
				$missing_required = true;
			}
		}

		if ( $missing_required ) {
			return new WP_Error( 'wordai_lead_required', $config['labels']['required'] );
		}

		$has_lead_data = ! empty( $lead['name'] ) || ! empty( $lead['email'] ) || ! empty( $lead['phone'] ) || ! empty( $lead['company'] ) || ! empty( $lead['message'] ) || ! empty( $lead['consent_given'] );
		if ( ! $has_lead_data ) {
			return true;
		}

		global $wpdb;

		$table        = $wpdb->prefix . 'wordai_agent_leads';
		$visitor_hash = self::visitor_hash_from_session( self::normalize_session_key( $conversation_key ) );
		$existing     = $wpdb->get_row(
			$wpdb->prepare( "SELECT id, first_question, name, email, phone, company, message FROM {$table} WHERE conversation_id = %d", $conversation_id ),
			ARRAY_A
		);

		$data = array(
			'conversation_id'    => $conversation_id,
			'visitor_hash'       => $visitor_hash,
			'name'               => $lead['name'],
			'email'              => $lead['email'],
			'phone'              => $lead['phone'],
			'company'            => $lead['company'],
			'message'            => $lead['message'],
			'consent_given'      => ! empty( $lead['consent_given'] ) ? 1 : 0,
			'source_page_url'    => esc_url_raw( $page_url ),
			'source_page_title'  => sanitize_text_field( $page_title ),
			'last_question'      => sanitize_textarea_field( $prompt ),
			'updated_at'         => current_time( 'mysql' ),
		);

		if ( empty( $existing ) ) {
			$data['first_question'] = sanitize_textarea_field( $prompt );
			$data['status']         = 'new';
			$data['created_at']     = current_time( 'mysql' );
			$wpdb->insert( $table, $data );
			self::maybe_notify_admin_about_lead( $data, $settings );
			return true;
		}

		foreach ( array( 'name', 'email', 'phone', 'company', 'message' ) as $key ) {
			if ( '' === $data[ $key ] && ! empty( $existing[ $key ] ) ) {
				$data[ $key ] = $existing[ $key ];
			}
		}

		if ( empty( $existing['first_question'] ) ) {
			$data['first_question'] = sanitize_textarea_field( $prompt );
		}

		$wpdb->update( $table, $data, array( 'id' => (int) $existing['id'] ) );
		return true;
	}

	private static function maybe_notify_admin_about_lead( array $lead, array $settings ) {
		if ( empty( $settings['lead_notify_admin'] ) || ( class_exists( 'SftcyWordaiFeatures' ) && SftcyWordaiFeatures::wordai_is_basic_tier() ) ) {
			return;
		}

		$admin_email = get_option( 'admin_email' );
		if ( ! is_email( $admin_email ) ) {
			return;
		}

		$subject = sprintf( __( 'New WordAI chat lead from %s', 'wordai-pro' ), wp_parse_url( home_url(), PHP_URL_HOST ) );
		$body    = sprintf(
			"Name: %s\nEmail: %s\nPhone: %s\nCompany: %s\nPage: %s\n\nQuestion:\n%s\n\nMessage:\n%s",
			$lead['name'] ?: '-',
			$lead['email'] ?: '-',
			$lead['phone'] ?: '-',
			$lead['company'] ?: '-',
			$lead['source_page_url'] ?: '-',
			$lead['last_question'] ?: '-',
			$lead['message'] ?: '-'
		);

		wp_mail( $admin_email, $subject, $body );
	}

	private static function build_response_meta( $conversation_id, $conversation_key, $message_id, array $sources, $fallback_used, $latency_ms, array $settings ) {
		$is_basic_tier        = class_exists( 'SftcyWordaiFeatures' ) && SftcyWordaiFeatures::wordai_is_basic_tier();
		$show_source_frontend = ! $is_basic_tier && ! empty( $settings['show_source_citations'] );
		$handoff_email        = sanitize_email( $settings['handoff_email'] ?? get_option( 'admin_email' ) );
		$handoff_available    = ! $is_basic_tier && $fallback_used && ! empty( $settings['handoff_enabled'] ) && $handoff_email;

		$public_sources = array();
		if ( $show_source_frontend ) {
			foreach ( $sources as $source ) {
				$public_sources[] = array(
					'id'    => (int) ( $source['source_id'] ?? 0 ),
					'title' => sanitize_text_field( $source['title'] ?? '' ),
					'url'   => esc_url_raw( $source['url'] ?? '' ),
				);
			}
		}

		return array(
			'conversation_id'  => (int) $conversation_id,
			'conversation_key' => (string) $conversation_key,
			'message_id'       => (int) $message_id,
			'source_count'     => count( $sources ),
			'sources'          => $public_sources,
			'fallback_used'    => $fallback_used ? 1 : 0,
			'handoff_available' => $handoff_available ? 1 : 0,
			'handoff_email'    => $handoff_available ? $handoff_email : '',
			'handoff_label'    => $handoff_available ? sanitize_text_field( $settings['handoff_label'] ?? __( 'Contact support', 'wordai-pro' ) ) : '',
			'latency_ms'       => max( 0, (int) $latency_ms ),
		);
	}

	private static function mark_conversation_error( $conversation_id, $message ) {
		global $wpdb;

		$conversation_id = (int) $conversation_id;
		if ( $conversation_id <= 0 ) {
			return;
		}

		$wpdb->update(
			$wpdb->prefix . 'wordai_agent_conversations',
			array(
				'status'     => 'error',
				'last_error' => wp_strip_all_tags( (string) $message ),
				'updated_at' => current_time( 'mysql' ),
			),
			array( 'id' => $conversation_id )
		);
	}

	public static function sc_wordai_agent_chat_feedback() {
		check_ajax_referer( 'scwordai_wpnonce', 'security' );

		global $wpdb;

		$message_id       = isset( $_POST['message_id'] ) ? absint( $_POST['message_id'] ) : 0;
		$conversation_key = isset( $_POST['conversation_key'] ) ? sanitize_text_field( wp_unslash( $_POST['conversation_key'] ) ) : '';
		$rating           = isset( $_POST['rating'] ) ? (int) $_POST['rating'] : 0;
		$comment          = isset( $_POST['comment'] ) ? sanitize_textarea_field( wp_unslash( $_POST['comment'] ) ) : '';
		$page_url         = isset( $_POST['page_url'] ) ? esc_url_raw( wp_unslash( $_POST['page_url'] ) ) : '';

		$rating = $rating > 0 ? 1 : -1;

		if ( $message_id <= 0 ) {
			wp_send_json( array( 'status' => 'error', 'reply' => __( 'Invalid message feedback target.', 'wordai-pro' ) ) );
		}

		$messages_table = $wpdb->prefix . 'wordai_agent_messages';
		$message        = $wpdb->get_row(
			$wpdb->prepare( "SELECT id, conversation_id FROM {$messages_table} WHERE id = %d AND role = 'assistant'", $message_id ),
			ARRAY_A
		);

		if ( empty( $message ) ) {
			wp_send_json( array( 'status' => 'error', 'reply' => __( 'Message not found for feedback.', 'wordai-pro' ) ) );
		}

		$conversation_id = (int) $message['conversation_id'];
		$visitor_hash    = self::visitor_hash_from_session( self::normalize_session_key( $conversation_key ) );
		$table           = $wpdb->prefix . 'wordai_agent_feedback';

		$existing_id = (int) $wpdb->get_var(
			$wpdb->prepare( "SELECT id FROM {$table} WHERE message_id = %d", $message_id )
		);

		$data = array(
			'message_id'      => $message_id,
			'conversation_id' => $conversation_id,
			'rating'          => $rating,
			'comment'         => $comment,
			'visitor_hash'    => $visitor_hash,
			'page_url'        => $page_url,
			'created_at'      => current_time( 'mysql' ),
		);

		if ( $existing_id > 0 ) {
			$wpdb->update( $table, $data, array( 'id' => $existing_id ) );
		} else {
			$wpdb->insert( $table, $data );
		}

		$wpdb->update( $messages_table, array( 'feedback_score' => $rating ), array( 'id' => $message_id ) );
		$wpdb->update(
			$wpdb->prefix . 'wordai_agent_conversations',
			array( 'last_feedback' => $rating, 'updated_at' => current_time( 'mysql' ) ),
			array( 'id' => $conversation_id )
		);

		wp_send_json( array(
			'status'      => 'success',
			'feedbackMsg' => __( 'Thanks for the feedback.', 'wordai-pro' ),
			'rating'      => $rating,
		) );
	}

	private static function is_general_assistant_prompt( $user_prompt ) {
		$prompt_lc = strtolower( preg_replace( '/\s+/', ' ', trim( wp_strip_all_tags( $user_prompt ) ) ) );

		return (bool) preg_match(
			'/^(hi|hello|hey|yo|assalam|assalamu alaikum|salam|good morning|good afternoon|good evening)\b/i',
			$prompt_lc
		) || (bool) preg_match(
			'/\b(what can you do|what do you do|how can you help|what kind of help|help me with|your capabilities|can you help|how do i use you)\b/i',
			$prompt_lc
		);
	}

	/**
	 * Placeholder for future "hybrid" RAG native search.
	 * ---------------------------------------------------
	 * For now this just exposes a filter hook so advanced users
	 * can plug in their own WP_Query or custom search logic.
	 *
	 * Filter: 'sc_wordai_agent_rag_native_context'
	 *
	 * @param string $user_prompt
	 * @return string
	 */
	private static function build_native_search_context( $user_prompt ) {
		$native = apply_filters( 'sc_wordai_agent_rag_native_context', '', $user_prompt );
		if ( ! is_string( $native ) ) {
			return '';
		}
		$native = trim( $native );
		return $native;
	}

	/**
	 * Compute cosine similarity between two numeric vectors.
	 *
	 * @param array $a
	 * @param array $b
	 * @return float  -1.0 .. 1.0
	 */
	private static function cosine_similarity( array $a, array $b ) {
		$len_a = count( $a );
		$len_b = count( $b );
		$len   = min( $len_a, $len_b );

		if ( $len === 0 ) {
			return 0.0;
		}

		$dot = 0.0;
		$na  = 0.0;
		$nb  = 0.0;

		for ( $i = 0; $i < $len; $i++ ) {
			$x = (float) $a[ $i ];
			$y = (float) $b[ $i ];
			$dot += $x * $y;
			$na  += $x * $x;
			$nb  += $y * $y;
		}

		if ( $na <= 0 || $nb <= 0 ) {
			return 0.0;
		}

		return $dot / ( sqrt( $na ) * sqrt( $nb ) );
	}

	/**
	 * ----------------------------------------------------------
	 * Shortcode: [wordai_chatbot]
	 * ----------------------------------------------------------
	 * Allows manual embedding of chat widget inside posts/pages.
	 * Respects all admin settings and visibility logic.
	 * ----------------------------------------------------------
	 */
	public static function register_shortcode() {
		add_shortcode( 'wordai_chatbot', [ __CLASS__, 'render_shortcode' ] );
	}

	public static function render_shortcode( $atts = [] ) {
		$chat = (array) get_option( 'sc_wordai_agent_chat_settings', [] );
		if ( empty( $chat['enabled'] ) ) {
			return '<div class="wordai-chat-shortcode-disabled">' . esc_html__( 'AI Chat is disabled.', 'wordai-pro' ) . '</div>';
		}
		if ( isset( $chat['visibility'] ) && $chat['visibility'] === 'loggedin' && ! is_user_logged_in() ) {
			return '<div class="wordai-chat-shortcode-restricted">' . esc_html__( 'Chat available for logged-in users only.', 'wordai-pro' ) . '</div>';
		}

		ob_start(); ?>
		<div class="wordai-chat-shortcode-embed">
			<div id="wordai-chat-window" class="wordai-chat-window">
				<div class="wordai-chat-header">
					<span class="assistant-name"><?php echo esc_html( $chat['assistant_name'] ?? 'WordAI Support' ); ?></span>
				</div>
				<div class="wordai-chat-body">
					<div class="chat-messages"></div>
				</div>
				<div class="wordai-chat-footer">
					<textarea placeholder="<?php esc_attr_e( 'Type your question…', 'wordai-pro' ); ?>" class="chat-input"></textarea>
					<button class="chat-send"><?php esc_html_e( 'Send', 'wordai-pro' ); ?></button>
				</div>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
	 * ============================================================
	 * Centralized Chat Defaults Provider
	 * ------------------------------------------------------------
	 * Returns the default configuration for the AI Chat Widget.
	 * Used globally for:
	 *  - Settings Page ($def)
	 *  - Enqueue Localized Script
	 *  - Reset to Default Logic
	 *
	 * NOTE: RAG keys exist here but can be surfaced in UI later.
	 * ============================================================
	 */
	// public static function wordai_chat_default_settings() {
	// 	return [
	// 		'enabled'         => 1,                  // Chat enabled as default
	// 		'assistant_name'  => 'WordAI Support',
	// 		'welcome_message' => 'Hello 👋 How can I help you today?',
	// 		'theme'           => 'light',           // light|dark|auto
	// 		'bubble_color'    => '#0073aa',
	// 		'bubble_label'    => 'Chat',            // Bubble text on the button
	// 		'model'           => 'gpt-4o-mini',
	// 		'google_model'    => 'gemini-2.5-flash',
	// 		'temperature'     => 0.7,
	// 		'auto_display'    => 1,
	// 		'position'        => 'bottom-right',    // bottom-right|bottom-left
	// 		'prompt_prefix'   => 'You are a helpful assistant for this site.',
	// 		'reply_mode'      => 'nonstream',       // stream|nonstream
	// 		'visibility'      => 'public',          // public|loggedin

	// 		// Avatar options
	// 		// none|male|female|bot ; color used when avatar != none
	// 		'avatar_style'    => 'none',
	// 		'avatar_color'    => '#ffffff',         // default white

	// 		// RAG-related defaults (Embeddings-only, provider-separated)
	// 		'rag_enabled'     => 1,                 // Global RAG toggle
	// 		'rag_mode'        => 'embeddings',      // embeddings|hybrid (future)
	// 		'rag_answer_mode' => 'soft',            // strict|soft (radio in settings)

	// 		// Fallback message when info not found in embedded content
	// 		'fallback_openai' => "I’m not sure from the available information.",
	// 		'fallback_google' => "I’m not sure from the available information.",

	// 	];
	// }


	/**
	 * ============================================================
	 * Centralized Chat Defaults Provider
	 * ------------------------------------------------------------
	 * Used by:
	 *  - Chat Settings UI
	 *  - Front-end script localization
	 *  - Reset-to-Defaults logic
	 *
	 * All provider-specific values are stored separately
	 * to support Option B (active provider only updates).
	 * ============================================================
	 */
	public static function wordai_chat_default_settings() {

		return [

			/* --------------------------------------------------------
			* Core Chat Settings
			* ------------------------------------------------------ */
			'enabled'         => 1, // Chat enabled by default
			'assistant_name'  => 'WordAI Support',
			'welcome_message' => 'Hello 👋 How can I help you today?',
			'theme'           => 'light',          // light | dark | auto
			'bubble_color'    => '#0073aa',
			'bubble_label'    => 'Chat',

			/* --------------------------------------------------------
			* AI Models (Provider-specific)
			* ------------------------------------------------------ */
			'model'           => class_exists( 'SftcyWordaiOpenai' ) ? SftcyWordaiOpenai::$MODEL : 'gpt-5.5',
			'google_model'    => class_exists( 'SftcyWordaiGoogleGeminiApi' ) ? SftcyWordaiGoogleGeminiApi::$MODEL : 'gemini-3.5-flash',
			'anthropic_model' => class_exists( 'SftcyWordaiAnthropicApi' ) ? SftcyWordaiAnthropicApi::$MODEL : 'claude-sonnet-5',
			'xai_model'       => class_exists( 'SftcyWordaiXaiApi' ) ? SftcyWordaiXaiApi::$MODEL : 'grok-4.5',

			/* --------------------------------------------------------
			* Generation Controls
			* ------------------------------------------------------ */
			'temperature'     => 0.7,
			'reply_mode'      => 'nonstream',       // stream | nonstream

			/* --------------------------------------------------------
			* Display / Visibility
			* ------------------------------------------------------ */
			'auto_display'    => 1,
			'position'        => 'bottom-right',    // bottom-right | bottom-left
			'visibility'      => 'public',          // public | loggedin

			/* --------------------------------------------------------
			* System Prompt
			* ------------------------------------------------------ */
			'prompt_prefix'   => 'You are a helpful assistant for this site.',

			/* --------------------------------------------------------
			* Avatar Settings
			* ------------------------------------------------------ */
			'avatar_style'    => 'none',             // none | male | female | bot
			'avatar_color'    => '#ffffff',
			'chat_font_size'  => 16,

			/* --------------------------------------------------------
			* RAG (Retrieval Augmented Generation)
			* ------------------------------------------------------ */
			'rag_enabled'     => 1,
			'rag_mode'        => 'embeddings',       // embeddings | hybrid (future)
			'rag_answer_mode' => 'soft',             // strict | soft

			/* --------------------------------------------------------
			* Analytics, Trust, and Handoff
			* ------------------------------------------------------ */
			'analytics_enabled'      => 1,
			'feedback_enabled'       => 1,
			'show_source_citations'  => 1,
			'handoff_enabled'        => 0,
			'handoff_email'          => get_option( 'admin_email' ),
			'handoff_label'          => 'Contact support',
			'retention_days'         => 90,

			/* --------------------------------------------------------
			* Visitor Lead Capture
			* ------------------------------------------------------ */
			'lead_capture_mode'      => 'off',
			'lead_intro_text'        => 'Leave your details and we can follow up with a better answer.',
			'lead_consent_text'      => 'I agree to be contacted about my request.',
			'lead_privacy_url'       => home_url( '/privacy-policy/' ),
			'lead_notify_admin'      => 0,
			'lead_name_enabled'      => 1,
			'lead_name_required'     => 0,
			'lead_email_enabled'     => 1,
			'lead_email_required'    => 0,
			'lead_phone_enabled'     => 0,
			'lead_phone_required'    => 0,
			'lead_company_enabled'   => 0,
			'lead_company_required'  => 0,
			'lead_message_enabled'   => 0,
			'lead_message_required'  => 0,
			'lead_consent_required'  => 1,

			/* --------------------------------------------------------
			* Provider-specific Fallback Messages
			* Used when no relevant embedded context is found
			* ------------------------------------------------------ */
			'fallback_openai'    => "I’m not sure from the available information.",
			'fallback_google'    => "I’m not sure from the available information.",
			'fallback_anthropic' => "I’m not sure from the available information.",
			'fallback_xai'       => "I’m not sure from the available information.",
			'openai_embedding_source' => 'openai',
			'google_embedding_source' => 'google',
			'anthropic_embedding_source' => 'anthropic',
			'xai_embedding_source' => 'xai',
		];
	}


	/**
	 * Simple debug logger (only logs when WP_DEBUG is true).
	 *
	 * @param string     $msg
	 * @param mixed|null $data
	 */
	private static function debug_log( $msg, $data = null ) {
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			$out = "\n[WORDIAI-RAG] " . $msg;
			if ( $data !== null ) {
				$out .= ' => ' . print_r( $data, true );
			}
			error_log( $out );
		}
	}

	private static function log_chat_failure( $provider, $message, $context = array() ) {
		if ( class_exists( 'SftcyWordaiSupport' ) ) {
			SftcyWordaiSupport::log_event( 'chat', 'error', 'frontend_chat_failed', (string) $message, $context, $provider, 'chat', 0 );
		}
	}

} // Class
