<?php
/**
 * Google Gemini API integration core for WordAI.
 *
 * This class contains ONLY low-level API logic:
 *  - Text / content generation
 *  - Image generation
 *  - Streaming handling (cURL hook)
 *  - Embeddings
 *
 * It is intentionally provider-only and has NO admin UI / AJAX handlers.
 *
 * Typical usage:
 *   - SftcyWordaiGoogleGeminiApi::send_chat_request( $args );
 *   - SftcyWordaiGoogleGeminiApi::send_image_request( $args );
 *   - SftcyWordaiGoogleGeminiApi::embed_texts( $chunks, 'text-embedding-004' );
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

if ( ! class_exists( 'SftcyWordaiGoogleGeminiApi' ) ) :

class SftcyWordaiGoogleGeminiApi {

	/**
	 * Google Gemini API key, loaded lazily from WordAI settings.
	 *
	 * @var string|null
	 */
	protected static $API_KEY = null;

	/**
	 * Base endpoint for text / multimodal content.
	 *
	 * @var string
	 */
	public static $AI_TEXT_EP_BASE = 'https://generativelanguage.googleapis.com/v1beta/models/';

	/**
	 * URL suffix for non-streaming content generation.
	 *
	 * @var string
	 */
	public static $AI_TEXT_EP_SUFFIX = ':generateContent';

	/**
	 * Endpoint for Gemini 3 image models.
	 *
	 * @var string
	 */
	public static $AI_INTERACTIONS_EP = 'https://generativelanguage.googleapis.com/v1beta/interactions';

	/**
	 * URL suffix for streaming content generation.
	 *
	 * @var string
	 */
	public static $AI_STREAM_EP_SUFFIX = ':streamGenerateContent';

	/**
	 * Default Gemini content model.
	 *
	 * @var string
	 */
	public static $MODEL = 'gemini-3.5-flash';

	/**
	 * Default Gemini "image model" (logically same as text model, used with responseMimeType).
	 *
	 * @var string
	 */
	public static $IMAGE_MODEL = 'gemini-3.1-flash-image';

	/**
	 * Default image MIME type.
	 *
	 * @var string
	 */
	public static $IMAGE_MIME = 'image/png';

	/**
	 * Streaming flag for current request.
	 *
	 * @var bool|null
	 */
	public static $STREAM = null;

	/**
	 * Stream output mode:
	 * - 'raw' = output raw Gemini stream chunks (best for existing content-generation parsers)
	 * - 'sse' = output WordAI SSE wrapper: data: {"replyChunk": "..."} + data: [DONE]
	 */
	private static $STREAM_MODE = 'raw';

	/**
	 * Public output container, mirroring SftcyWordaiOpenai::$output format.
	 *
	 * @var array
	 */
	public static $output = [];

	/**
	 * Ensure API key is loaded from current settings.
	 *
	 * Called internally before any HTTP request.
	 *
	 * @return void
	 */
	protected static function ensure_api_key() {
		if ( ! is_null( self::$API_KEY ) ) {
			return;
		}

		if ( ! class_exists( 'SftcyWordaiBase' ) ) {
			return;
		}

		$data = SftcyWordaiBase::sc_wordai_get_current_api_settings();

		self::$API_KEY = ( isset( $data['wordai-google-gemini-api-key-data'] ) && ! empty( $data['wordai-google-gemini-api-key-data'] ) )
			? $data['wordai-google-gemini-api-key-data']
			: null;
	}

	/*───────────────────────────────────────────────────────────────
	| STREAMING SUPPORT (OPTIONAL, SIMILAR TO OPENAI)
	───────────────────────────────────────────────────────────────*/

	/**
	 * raw for content generation streaming, and sse for chat streaming
	 *
	 * @param string $mode
	 * @return void
	 */
	public static function set_stream_mode( $mode ) {
		$mode = strtolower( (string) $mode );
		self::$STREAM_MODE = in_array( $mode, [ 'raw', 'sse' ], true ) ? $mode : 'raw';
	}	

	/**
	 * Register cURL streaming hook for Gemini calls.
	 *
	 * Attaches the writefunction handler to output real-time chunks.
	 * This mirrors OpenAI streaming behavior.
	 *
	 * @return void
	 */
	public static function sc_curl_add_streaming_hook() {
		add_action( 'http_api_curl', array( __CLASS__, 'sc_wordai_gemini_stream_response' ), 10, 3 );
	}


	/**
	 * Remove cURL streaming hook for Gemini calls.
	 *
	 * @return void
	 */
	public static function sc_curl_remove_streaming_hook() {
		remove_action( 'http_api_curl', array( __CLASS__, 'sc_wordai_gemini_stream_response' ), 10 );
	}


	/**
	 * Check whether streaming is enabled for the current provider (Gemini).
	 *
	 * Uses the shared 'sc-wordai-streaming' flag in settings.
	 *
	 * @return bool
	 */
	public static function sc_gemini_streaming_status() {		
		if ( ! class_exists( 'SftcyWordaiBase' ) ) {
			return false;
		}
		$data = SftcyWordaiBase::sc_wordai_get_current_api_settings();

		return ( isset( $data['sc-wordai-streaming'] ) && intval( $data['sc-wordai-streaming'] ) === 1 );
	}

	/**
	 * Worker function for Gemini streaming responses.
	 * cURL write callback used for Gemini streaming responses.
	 *
	 * @param resource $ch cURL handler (provided by WP HTTP API).
	 * @return void
	 */
	// public static function sc_wordai_gemini_stream_response( $ch ) {
	// 	if ( self::$STREAM !== true ) {
	// 		return;
	// 	}

	// 	@ob_end_clean();
	// 	@ob_start();

	// 	curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
	// 	curl_setopt(
	// 		$ch,
	// 		CURLOPT_WRITEFUNCTION,
	// 		function ( $curl_info, $data ) {
	// 			echo self::wordai_raw_contents( $data );
	// 			@ob_end_flush();
	// 			flush();

	// 			return strlen( $data );
	// 		}
	// 	);
	// }


	/**
	 * cURL write callback used for Gemini streaming responses.
	 * Supports two modes:
	 *  - raw: echoes Gemini "data: {...}" chunks unchanged (best for content generation)
	 *  - sse: converts Gemini chunks into "data: {replyChunk: متن}" SSE for chat widget
	 */
	// public static function sc_wordai_gemini_stream_response( $ch ) {
   	public static function sc_wordai_gemini_stream_response( $ch, $r = array(), $url = '' ) {	

		if ( self::$STREAM !== true ) {
			return;
		}

		@ob_end_clean();
		@ob_start();

		curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );

		curl_setopt(
			$ch,
			CURLOPT_WRITEFUNCTION,
			function( $curl, $data ) {

				// MODE 1: RAW passthrough (your old working version for content generation)
				if ( self::$STREAM_MODE === 'raw' ) {
					echo self::wordai_raw_contents( $data );
					@ob_end_flush();
					flush();
					return strlen( $data );
				}

				// MODE 2: SSE wrapper (chat widget-friendly)
				$line = trim( (string) $data );

				// Gemini streaming typically sends: "data: {...}\n\n" and ends with "data: [DONE]"
				$clean = $line;

				// Normalize "data:" prefix
				if ( stripos( $clean, 'data:' ) === 0 ) {
					$clean = trim( substr( $clean, 5 ) );
				}

				// End
				if ( $clean === '[DONE]' ) {
					echo "data: [DONE]\n\n";
					@ob_end_flush();
					flush();
					return strlen( $data );
				}

				// Decode Gemini JSON chunk
				$json = json_decode( $clean, true );
				$text = '';

				// Extract chunk text (handle parts array)
				if ( isset( $json['candidates'][0]['content']['parts'] ) && is_array( $json['candidates'][0]['content']['parts'] ) ) {
					foreach ( $json['candidates'][0]['content']['parts'] as $part ) {
						if ( isset( $part['text'] ) ) {
							$text .= $part['text'];
						}
					}
				} elseif ( isset( $json['candidates'][0]['content']['parts'][0]['text'] ) ) {
					$text = (string) $json['candidates'][0]['content']['parts'][0]['text'];
				} elseif ( isset( $json['candidates'][0]['text'] ) ) {
					$text = (string) $json['candidates'][0]['text'];
				}

				// Emit SSE chunk for chat UI
				if ( $text !== '' ) {
					echo "data: " . wp_json_encode( [ 'replyChunk' => $text ] ) . "\n\n";
					@ob_end_flush();
					flush();
				}

				return strlen( $data );
			}
		);
	}


	// public static function sc_wordai_gemini_stream_response( $ch ) {

	// 	if ( self::$STREAM !== true ) {
	// 		return;
	// 	}

	// 	@ob_end_clean();
	// 	@ob_start();

	// 	curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );

	// 	curl_setopt(
	// 		$ch,
	// 		CURLOPT_WRITEFUNCTION,
	// 		function( $curl, $data ) {

	// 			// Remove "data: " from Gemini format
	// 			// $clean = trim( str_replace('data:', '', $data) );
				
	// 			$clean = trim( str_replace('data: ', '', $data) );
	// 			$clean = trim( str_replace('data:', '', $clean) ); // fallback


	// 			if ( $clean === '[DONE]' ) {
	// 				echo "data: [DONE]\n\n";
	// 				@ob_end_flush();
	// 				flush();
	// 				return strlen($data);
	// 			}

	// 			$json = json_decode( $clean, true );
	// 			$text = '';

	// 			// Extract streaming chunk from Gemini structure
	// 			if ( isset($json['candidates'][0]['content']['parts'][0]['text']) ) {
	// 				$text = $json['candidates'][0]['content']['parts'][0]['text'];
	// 			}

	// 			if ( $text !== '' ) {
	// 				echo "data: " . json_encode(['replyChunk' => $text]) . "\n\n";
	// 				@ob_end_flush();
	// 				flush();
	// 			}

	// 			return strlen($data);
	// 		}
	// 	);
	// }


	/**
	 * Worker function to strip "data: " prefix from streamed chunks.
	 * Strip "data: " prefix from streamed chunks for easier JSON parsing.
	 *
	 * @param string $data
	 * @return string
	 */
	// public static function wordai_raw_contents( $data = '' ) {
	// 	return str_replace( 'data: ', '', $data );
	// }

	/**
	 * Return raw Gemini chunk text for "raw" streaming mode.
	 * We only strip the leading "data:" prefix when it's at the beginning
	 * of the chunk (Gemini SSE format), and keep everything else unchanged.
	 */
	public static function wordai_raw_contents( $data = '' ) {
		return (string) $data;
	}



	/**
	 * Default Gemini safety settings (BLOCK_NONE for all categories).
	 *
	 * @return array[]
	 */
    public static function get_default_safety_settings() {

        // Allowed categories for Gemini 2.0 / 2.5
        $categories = [
            'HARM_CATEGORY_HARASSMENT',
            'HARM_CATEGORY_HATE_SPEECH',
            'HARM_CATEGORY_SEXUALLY_EXPLICIT',
            'HARM_CATEGORY_DANGEROUS_CONTENT',
            'HARM_CATEGORY_CIVIC_INTEGRITY',
        ];

        $settings = [];

        foreach ($categories as $cat) {
            $settings[] = [
                'category'  => $cat,
                'threshold' => 'BLOCK_NONE'
            ];
        }

        return $settings;
    }

	/*───────────────────────────────────────────────────────────────
	| PARAM BUILDERS (CONTENT & IMAGE)
	───────────────────────────────────────────────────────────────*/

	/**
	 * Build Gemini request params in a clean, Gemini-native format.
	 *
	 * - Keeps system instruction separate (systemInstruction)
	 * - Keeps prompt as user content
	 * - Keeps generationConfig centralized here (no duplication in router)
	 * - Returns model + body only (stream decision should be made by caller/router)
	 */
	private static function get_adaptive_content_max_tokens( $prompt = '' ) {
		$adaptive_tokens = 8192;
		$content_settings = class_exists( 'SftcyWordaiBase' )
			? SftcyWordaiBase::sc_wordai_get_current_content_settings()
			: [];

		$paragraphs_key = isset( $content_settings['sc-wordai-content-paragraphs'] )
			? (string) $content_settings['sc-wordai-content-paragraphs']
			: '3';
		$paragraph_count = intval( preg_replace( '/\D+/', '', $paragraphs_key ) );
		if ( $paragraph_count <= 0 ) {
			$paragraph_count = 3;
		}

		if ( $paragraph_count >= 6 ) {
			$adaptive_tokens = 12288;
		}
		if ( $paragraph_count >= 10 ) {
			$adaptive_tokens = 16384;
		}

		$prompt_length = function_exists( 'mb_strlen' ) ? mb_strlen( (string) $prompt ) : strlen( (string) $prompt );
		if ( $prompt_length > 2400 ) {
			$adaptive_tokens += 2048;
		}

		return min( 24576, $adaptive_tokens );
	}

	private static function is_gemini_3_text_model( $model ) {
		return 0 === strpos( (string) $model, 'gemini-3' );
	}

	public static function set_gemini_params( $prompt, $system_instruction = '', $overrides = [] ) {

		$data = class_exists( 'SftcyWordaiBase' )
			? SftcyWordaiBase::sc_wordai_get_current_api_settings()
			: [];

		$model = ! empty( $data['sc-wordai-gemini-model-slug'] )
			? trim( (string) $data['sc-wordai-gemini-model-slug'] )
			: self::$MODEL;

		if ( class_exists( 'SftcyWordaiGoogleGeminiSettings' ) && method_exists( 'SftcyWordaiGoogleGeminiSettings', 'normalize_gemini_model_slug' ) ) {
			$model = SftcyWordaiGoogleGeminiSettings::normalize_gemini_model_slug( $model );
		}

		$temperature = isset( $data['sc-wordai-temperature'] ) ? floatval( $data['sc-wordai-temperature'] ) : 0.4;
		$top_p       = isset( $data['sc-wordai-top-p'] ) ? floatval( $data['sc-wordai-top-p'] ) : 0.9;
		$adaptive_max_tokens = self::get_adaptive_content_max_tokens( $prompt );
		$saved_max_tokens = ( isset( $data['sc-wordai-max-tokens'] ) && ! empty( $data['sc-wordai-max-tokens'] ) )
			? intval( $data['sc-wordai-max-tokens'] )
			: 0;
		$max_tokens = $saved_max_tokens > 0 ? max( $adaptive_max_tokens, $saved_max_tokens ) : $adaptive_max_tokens;

		// Apply overrides (optional)
		if ( isset( $overrides['temperature'] ) ) $temperature = floatval( $overrides['temperature'] );
		if ( isset( $overrides['top_p'] ) )       $top_p       = floatval( $overrides['top_p'] );
		if ( isset( $overrides['max_tokens'] ) )  $max_tokens  = max( $adaptive_max_tokens, intval( $overrides['max_tokens'] ) );

		// Clamp
		$temperature = max( 0.0, min( 2.0, $temperature ) ); // Gemini allows >1
		$top_p       = max( 0.0, min( 1.0, $top_p ) );

		$prompt = trim( (string) $prompt );
		$system_instruction = trim( (string) $system_instruction );
		if ( $system_instruction === '' ) {
			$system_instruction = 'You are a helpful AI writing assistant.';
		}

		$generation_config = [
			'maxOutputTokens' => $max_tokens,
		];

		if ( ! self::is_gemini_3_text_model( $model ) ) {
			$generation_config['temperature'] = $temperature;
			$generation_config['topP']        = $top_p;
		}

		$body = [
			// ✅ Gemini-native system instruction
			'systemInstruction' => [
				'parts' => [
					[ 'text' => $system_instruction ],
				],
			],

			// ✅ User prompt as user turn
			'contents' => [
				[
					'role'  => 'user',
					'parts' => [
						[ 'text' => $prompt ],
					],
				],
			],

			// ✅ Centralized generation config
			'generationConfig' => $generation_config,

			'safetySettings' => self::get_default_safety_settings(),
		];

		return [
			'model' => $model,
			'body'  => $body,
		];
	}



	/**
	 * ================================================================
	 *  Gemini Image Generation Parameters (FINAL VERSION)
	 * ---------------------------------------------------------------
	 *  - Reads ALL Gemini image settings (size, style, quality, mime)
	 *  - Injects them into the prompt in a structured way
	 *  - Does NOT use generationConfig (invalid for image generation)
	 *  - Returns: { model, body, mime }
	 * ================================================================
	 */
	/**
	 * Build Gemini image generation parameters.
	 *
	 * @param array $api_image_params { prompt, mime?, size?, style?, quality? }
	 * @return array { model, body, mime }
	 */
	// public static function set_gemini_image_params( $api_image_params = array() ) {

	// 	// Final prompt already built by generate_prompt('image')
	// 	$prompt = isset( $api_image_params['prompt'] )
	// 		? (string) $api_image_params['prompt']
	// 		: '';

	// 	// Load IMAGE settings (not API settings!)
	// 	$data = SftcyWordaiBase::sc_wordai_get_current_image_settings();

	// 	// Model
	// 	$model = ! empty( $data['sc-wordai-gemini-image-model-slug'] )
	// 		? trim( $data['sc-wordai-gemini-image-model-slug'] )
	// 		: self::$IMAGE_MODEL;

	// 	// Mime type from settings, allow override from $api_image_params
	// 	$mime = ! empty( $api_image_params['mime'] )
	// 		? $api_image_params['mime']
	// 		: ( $data['sc-wordai-gemini-image-mime'] ?? self::$IMAGE_MIME );

	// 	// Size / style / quality from settings
	// 	$size    = $data['sc-wordai-gemini-image-size']    ?? '1024x1024';
	// 	$style   = $data['sc-wordai-gemini-image-style']   ?? 'photorealistic';
	// 	$quality = $data['sc-wordai-gemini-image-quality'] ?? 'standard';

	// 	// Enrich prompt with metadata – but DO NOT rebuild the semantic part
	// 	$final_prompt =
	// 		"Image configuration:\n" .
	// 		"- Size: {$size}\n" .
	// 		"- Style: {$style}\n" .
	// 		"- Quality: {$quality}\n\n" .
	// 		"Generate the following image:\n{$prompt}";

	// 	return array(
	// 		'model' => $model,
	// 		'mime'  => $mime,
	// 		'body'  => array(
	// 			'contents' => array(
	// 				array(
	// 					'role'  => 'user',
	// 					'parts' => array(
	// 						array( 'text' => $final_prompt ),
	// 					),
	// 				),
	// 			),
	// 			'safetySettings' => self::get_default_safety_settings(),
	// 		),
	// 	);
	// }


	/**
	 * Build Gemini image generation parameters.
	 *
	 * Accepts either:
	 *  - string $prompt
	 *  - array  $api_image_params { prompt, mime?, size?, style?, quality? }
	 *
	 * @param mixed $api_image_params
	 * @return array { model, body, mime }
	 */
	public static function set_gemini_image_params( $api_image_params = array() ) {

		// -----------------------------
		// 1) Normalize input (string OR array)
		// -----------------------------
		$prompt = '';
		$overrides = [];

		if ( is_string( $api_image_params ) ) {
			// Backward-compat: called as set_gemini_image_params($prompt_string)
			$prompt = (string) $api_image_params;
		} elseif ( is_array( $api_image_params ) ) {
			$overrides = $api_image_params;
			$prompt = isset( $api_image_params['prompt'] ) ? (string) $api_image_params['prompt'] : '';
		}

		$prompt = trim( $prompt );

		// -----------------------------
		// 2) Load IMAGE settings (provider-aware)
		// -----------------------------
		$data = SftcyWordaiBase::sc_wordai_get_current_image_settings();

		$model = ! empty( $data['sc-wordai-gemini-image-model-slug'] )
			? trim( $data['sc-wordai-gemini-image-model-slug'] )
			: self::$IMAGE_MODEL;

		if ( class_exists( 'SftcyWordaiGoogleGeminiSettings' ) && method_exists( 'SftcyWordaiGoogleGeminiSettings', 'normalize_gemini_image_model_slug' ) ) {
			$model = SftcyWordaiGoogleGeminiSettings::normalize_gemini_image_model_slug( $model );
		}

		// allow override from $overrides (rare, but supported)
		$mime = ! empty( $overrides['mime'] )
			? (string) $overrides['mime']
			: ( $data['sc-wordai-gemini-image-mime'] ?? self::$IMAGE_MIME );

		// allow override from $overrides, else settings
		$size    = ! empty( $overrides['size'] )    ? (string) $overrides['size']    : ( $data['sc-wordai-gemini-image-size']    ?? '1024x1024' );
		$style   = ! empty( $overrides['style'] )   ? (string) $overrides['style']   : ( $data['sc-wordai-gemini-image-style']   ?? 'photorealistic' );
		$quality = ! empty( $overrides['quality'] ) ? (string) $overrides['quality'] : ( $data['sc-wordai-gemini-image-quality'] ?? 'standard' );

		// -----------------------------
		// 3) Build final prompt
		// -----------------------------
		$final_prompt =
			"Image configuration:\n" .
			"- Size: {$size}\n" .
			"- Style: {$style}\n" .
			"- Quality: {$quality}\n\n" .
			"Generate the following image:\n{$prompt}";

		return array(
			'model' => $model,
			'mime'  => $mime,
			'size'  => $size,
			'style' => $style,
			'quality' => $quality,
			'body'  => array(
				'contents' => array(
					array(
						'role'  => 'user',
						'parts' => array(
							array( 'text' => $final_prompt ),
						),
					),
				),
				'safetySettings' => self::get_default_safety_settings(),
			),
		);
	}

	/**
	 * Gemini 3 image models use the Interactions API.
	 *
	 * @param string $model
	 * @return bool
	 */
	private static function is_gemini_interactions_image_model( $model ) {
		return in_array(
			(string) $model,
			array(
				'gemini-3.1-flash-image',
				'gemini-3.1-flash-lite-image',
				'gemini-3-pro-image',
			),
			true
		);
	}

	/**
	 * Extract the final image prompt from the existing Gemini image body.
	 *
	 * @param array $body
	 * @return string
	 */
	private static function extract_interactions_image_prompt( $body ) {
		if ( isset( $body['contents'][0]['parts'][0]['text'] ) ) {
			return (string) $body['contents'][0]['parts'][0]['text'];
		}

		return '';
	}

	/**
	 * Convert existing WordAI resolution settings to Gemini response_format.
	 *
	 * @param string $mime
	 * @return array
	 */
	private static function build_gemini_interactions_response_format( $mime, $model = '' ) {
		$data = class_exists( 'SftcyWordaiBase' ) ? SftcyWordaiBase::sc_wordai_get_current_image_settings() : array();
		$size = isset( $data['sc-wordai-gemini-image-size'] ) ? (string) $data['sc-wordai-gemini-image-size'] : '1024x1024';
		$format = array(
			'type'       => 'image',
			'mime_type'  => 'image/jpeg',
			'image_size' => 'gemini-3.1-flash-lite-image' === $model ? '1K' : self::gemini_interactions_image_size_from_dimensions( $size ),
		);

		$aspect_ratio = self::gemini_interactions_aspect_ratio_from_dimensions( $size );
		if ( '' !== $aspect_ratio ) {
			$format['aspect_ratio'] = $aspect_ratio;
		}

		return $format;
	}

	/**
	 * Map a WIDTHxHEIGHT setting to Gemini image_size.
	 *
	 * @param string $size
	 * @return string
	 */
	private static function gemini_interactions_image_size_from_dimensions( $size ) {
		if ( preg_match( '/^(\d+)x(\d+)$/', (string) $size, $matches ) ) {
			$max_edge = max( absint( $matches[1] ), absint( $matches[2] ) );
			if ( $max_edge > 2048 ) {
				return '4K';
			}
			if ( $max_edge > 1024 ) {
				return '2K';
			}
		}

		return '1K';
	}

	/**
	 * Map a WIDTHxHEIGHT setting to Gemini aspect_ratio.
	 *
	 * @param string $size
	 * @return string
	 */
	private static function gemini_interactions_aspect_ratio_from_dimensions( $size ) {
		if ( ! preg_match( '/^(\d+)x(\d+)$/', (string) $size, $matches ) ) {
			return '1:1';
		}

		$width  = max( 1, absint( $matches[1] ) );
		$height = max( 1, absint( $matches[2] ) );
		$gcd    = self::gcd( $width, $height );

		return ( $width / $gcd ) . ':' . ( $height / $gcd );
	}

	/**
	 * Greatest common divisor.
	 *
	 * @param int $a
	 * @param int $b
	 * @return int
	 */
	private static function gcd( $a, $b ) {
		$a = absint( $a );
		$b = absint( $b );

		while ( $b ) {
			$tmp = $b;
			$b   = $a % $b;
			$a   = $tmp;
		}

		return max( 1, $a );
	}

	/**
	 * Generate images through Gemini Interactions API.
	 *
	 * @param string $model
	 * @param array  $body
	 * @param string $mime
	 * @return array
	 */
	private static function generate_interactions_image( $model, $body, $mime ) {
		$prompt            = self::extract_interactions_image_prompt( is_array( $body ) ? $body : array() );
		$interactions_mime = 'image/jpeg';

		$request_body = array(
			'model'           => $model,
			'input'           => $prompt,
			'response_format' => self::build_gemini_interactions_response_format( $interactions_mime, $model ),
		);

		self::$output['wordAIRequestParams'] = array(
			'model' => $model,
			'body'  => $request_body,
			'mime'  => $interactions_mime,
		);

		$args = array(
			'headers'     => array(
				'Content-Type'   => 'application/json; charset=UTF-8',
				'x-goog-api-key' => self::$API_KEY,
			),
			'timeout'     => 120,
			'httpversion' => '1.0',
			'sslverify'   => false,
			'body'        => wp_json_encode( $request_body ),
		);

		$response = wp_remote_post( self::$AI_INTERACTIONS_EP, $args );

		if ( is_wp_error( $response ) ) {
			self::$output['status']       = 'fail';
			self::$output['errorMessage'] = $response->get_error_message();
			return self::$output;
		}

		$raw     = wp_remote_retrieve_body( $response );
		$decoded = json_decode( $raw, true );

		self::$output['responseBody']       = $decoded;
		self::$output['model']              = $model;
		self::$output['selectedImageModel'] = $model;

		$gemini_error = self::handle_gemini_error( $decoded, 'image' );
		if ( $gemini_error ) {
			self::$output['status']             = $gemini_error['status'] ?? 'fail';
			self::$output['errorType']          = $gemini_error['errorType'] ?? 'gemini_error';
			self::$output['errorMessage']       = $gemini_error['errorMessage'] ?? __( 'Google Gemini image generation failed.', 'wordai-pro' );
			self::$output['model']              = $model;
			self::$output['selectedImageModel'] = $model;
			return self::$output;
		}

		$images = self::collect_interactions_images( $decoded, $interactions_mime );

		if ( ! empty( $images ) ) {
			self::$output['status']            = 'success';
			self::$output['responseImageData'] = $images;
			return self::$output;
		}

		self::$output['status']       = 'fail';
		self::$output['errorType']    = 'gemini_unexpected_image_format';
		self::$output['errorMessage'] = __( 'No image data returned from Google Gemini. Please try again, reduce prompt complexity, or check Gemini limits/policy blocks.', 'wordai-pro' );

		return self::$output;
	}

	/**
	 * Collect image data from Gemini Interactions responses.
	 *
	 * @param mixed  $node
	 * @param string $default_mime
	 * @return array
	 */
	private static function collect_interactions_images( $node, $default_mime ) {
		$images = array();
		self::walk_interactions_images( $node, $images, $default_mime );
		return $images;
	}

	/**
	 * Recursive Interactions image walker.
	 *
	 * @param mixed $node
	 * @param array $images
	 * @param string $default_mime
	 * @return void
	 */
	private static function walk_interactions_images( $node, &$images, $default_mime ) {
		if ( ! is_array( $node ) ) {
			return;
		}

		$type = isset( $node['type'] ) ? (string) $node['type'] : '';
		$data = isset( $node['data'] ) ? (string) $node['data'] : '';
		$mime = isset( $node['mime_type'] ) ? (string) $node['mime_type'] : ( $node['mimeType'] ?? $default_mime );

		if ( '' !== $data && ( 'image' === $type || isset( $node['mime_type'] ) || isset( $node['mimeType'] ) ) ) {
			$images[] = array(
				'b64_json' => $data,
				'mimeType' => $mime ? $mime : $default_mime,
			);
			return;
		}

		foreach ( $node as $child ) {
			self::walk_interactions_images( $child, $images, $default_mime );
		}
	}


	/*───────────────────────────────────────────────────────────────
	| CORE HTTP CALLS (TEXT & IMAGES)
	───────────────────────────────────────────────────────────────*/

    /**
     * Low-level Gemini content request.
     *
     * @param string $model  Gemini model ID.
     * @param array  $body   Request JSON body.
     * @param bool   $stream Whether to call streaming endpoint.
     * @return array Normalized output (like OpenAI create_content()).
     */
	public static function send_text_request( $model, $body, $stream = false ) {

		self::$output = array();
		self::$output['wordAIRequestParams'] = array(
			'model' => $model,
			'body'  => $body,
		);

		self::ensure_api_key();

		if ( empty( self::$API_KEY ) ) {
			self::$output['status']              = 'fail';
			self::$output['comment']             = 'Valid API Key missing.';
			self::$output['wordAIAPIKeyMissing'] = __( 'Valid Google Gemini API Key required.', 'wordai-pro' );
			return self::$output;
		}

		/*----------------------------------------
		| Decide if we REALLY stream
		----------------------------------------*/
		$use_stream = (bool) $stream && self::sc_gemini_streaming_status();

		/*----------------------------------------
		| Build endpoint URL (based on REAL stream)
		----------------------------------------*/
		$base = rtrim( self::$AI_TEXT_EP_BASE, '/' ) . '/' . $model;
		$url  = $base . ( $use_stream ? self::$AI_STREAM_EP_SUFFIX : self::$AI_TEXT_EP_SUFFIX );

		/*----------------------------------------
		| Handle streaming hook toggle
		----------------------------------------*/
		if ( $use_stream ) {
			self::$STREAM = true;
			self::sc_curl_add_streaming_hook();
		} else {
			self::$STREAM = false;
		}

		$args = array(
			'headers'     => array(
				'Content-Type'   => 'application/json; charset=UTF-8',
				'x-goog-api-key' => self::$API_KEY,
			),
			'timeout'     => 90,
			'httpversion' => '1.0',
			'sslverify'   => false,
			'data_format' => 'body',
			'body'        => wp_json_encode( $body ),
		);

		$response = wp_remote_post( $url, $args );

		// If true streaming, output is already flushed via CURLOPT_WRITEFUNCTION
		if ( $use_stream ) {
			self::sc_curl_remove_streaming_hook();
			exit;
		}

		if ( is_wp_error( $response ) ) {
			self::$output['status']       = 'fail';
			self::$output['errorMessage'] = $response->get_error_message();
			return self::$output;
		}

		/*----------------------------------------
		| Decode body
		----------------------------------------*/
		$code         = wp_remote_retrieve_response_code( $response );
		$raw          = wp_remote_retrieve_body( $response );
		$body_decoded = json_decode( $raw, true );

		self::$output['responseBody'] = $body_decoded;

		/*----------------------------------------
		| FAIL CASE
		----------------------------------------*/
		if ( $code !== 200 ) {
			$error_msg = ! empty( $body_decoded['error']['message'] )
				? $body_decoded['error']['message']
				: __( 'Failed to get response from Google Gemini API.', 'wordai-pro' );

			self::$output['status']       = 'fail';
			self::$output['errorMessage'] = $error_msg;

			return self::$output;
		}

		/*----------------------------------------
		| SUCCESS CASE — Extract text robustly
		----------------------------------------*/
		$text = '';

		if ( isset( $body_decoded['candidates'][0]['content']['parts'] ) && is_array( $body_decoded['candidates'][0]['content']['parts'] ) ) {
			foreach ( $body_decoded['candidates'][0]['content']['parts'] as $part ) {
				if ( isset( $part['text'] ) ) {
					$text .= $part['text'];
				}
			}
		} elseif ( isset( $body_decoded['candidates'][0]['text'] ) ) {
			$text = $body_decoded['candidates'][0]['text'];
		}

		self::$output['status']       = 'success';
		self::$output['responseText'] = wp_kses_post( $text );
		self::$output['model']        = esc_html( $model );

		return self::$output;
	}


	/**
	 * High-level helper that mirrors a "chat" request for the router.
	 *
	 * Expected $args keys:
	 *  - 'prompt'             => string (required)
	 *  - 'system_instruction' => string (optional)
	 *  - 'stream'             => bool   (optional)
	 *  - 'overrides'          => array  (optional settings overrides)
	 *
	 * @param array $args
	 * @return array
	 */
	public static function send_chat_request( $args = array() ) {

		$prompt             = isset( $args['prompt'] ) ? (string) $args['prompt'] : '';
		$system_instruction = isset( $args['system_instruction'] ) ? (string) $args['system_instruction'] : '';
		$stream             = isset( $args['stream'] ) ? (bool) $args['stream'] : false;
		$overrides          = isset( $args['overrides'] ) && is_array( $args['overrides'] ) ? $args['overrides'] : array();

		$params = self::set_gemini_params( $prompt, $system_instruction, $overrides );

		return self::send_text_request( $params['model'], $params['body'], $stream );
	}


	/**
	 * =====================================================================
	 *  Google Gemini :: create_chat_reply()
	 *  ---------------------------------------------------------------------
	 *  Unified helper for Gemini chat API.
	 *  - Signature matches OpenAI helper:
	 *       ($user_prompt, $model, $temperature, $system_message, $stream, $assistant_context)
	 *  - $assistant_context is the RAG block from your embeddings.
	 *  - Must return ['status' => 'success', 'reply' => '...'] on success.
	 * =====================================================================
	 *
	 * @param string $user_prompt        Final user question.
	 * @param string $model              Gemini model id (e.g. gemini-1.5-flash).
	 * @param float  $temperature        Creativity (0–1).
	 * @param string $system_message     System / instructions.
	 * @param bool   $stream             Whether to stream (SSE).
	 * @param string $assistant_context  RAG context string.
	 *
	 * @return array|string
	 */
	public static function create_chat_reply(
		$user_prompt,
		$model,
		$temperature,
		$system_message = '',
		$stream = false,
		$assistant_context = ''
	) {
		self::ensure_api_key();
		if ( empty( self::$API_KEY ) ) {
			return [
				'status' => 'error',
				'reply'  => __( 'Valid Google Gemini API key required.', 'wordai-pro' ),
			];
		}

		$user_prompt = trim( (string) $user_prompt );
		if ( $user_prompt === '' ) {
			return [
				'status' => 'error',
				'reply'  => __( 'Empty message.', 'wordai-pro' ),
			];
		}

		$model = class_exists( 'SftcyWordaiGoogleGeminiSettings' )
			? SftcyWordaiGoogleGeminiSettings::normalize_gemini_model_slug( $model )
			: self::$MODEL;
		$temperature = max( 0.0, min( 1.0, floatval( $temperature ) ) );

		// ------------------------------------------------------------
		// 1) Make Gemini more flexible for greetings / small talk
		// ------------------------------------------------------------
		$system_message = $system_message ?: 'You are a helpful assistant for this site.';

		$p = strtolower( preg_replace('/\s+/', ' ', $user_prompt ) );
		$is_smalltalk = (bool) preg_match(
			'/^(hi|hello|hey|yo|assalam|assalamu alaikum|salam|thanks|thank you|thx|good morning|good afternoon|good evening)\b/i',
			$p
		);

		// If your system prompt includes the strict fallback line, soften it for smalltalk.
		// This prevents Gemini from refusing normal greetings.
		if ( $is_smalltalk ) {
			$system_message .= "\n\n"
				. "If the user greets you or asks small talk (like 'Hi', 'Hello', 'Thanks'), reply naturally and politely even if the website context is not relevant. "
				. "Only use the fallback phrase when the user asks for specific facts that require website context and the context is not relevant.";
		}

		// ------------------------------------------------------------
		// 2) Prepare RAG context (decode entities + trim)
		// ------------------------------------------------------------
		$assistant_context = (string) $assistant_context;
		if ( $assistant_context !== '' ) {
			// Decode things like pi&#241;a → piña for cleaner context
			$assistant_context = html_entity_decode( $assistant_context, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
			$assistant_context = trim( $assistant_context );
		}

		// ------------------------------------------------------------
		// 3) Build Gemini body using systemInstruction (recommended)
		// ------------------------------------------------------------
		$contents = [];

		// Attach RAG context as a prior "model" message (closest equivalent to assistant role)
		if ( $assistant_context !== '' ) {
			$contents[] = [
				'role'  => 'model',
				'parts' => [
					[ 'text' => "Website context (may be relevant):\n" . $assistant_context ],
				],
			];
		}

		// Final user turn
		$contents[] = [
			'role'  => 'user',
			'parts' => [
				[ 'text' => $user_prompt ],
			],
		];

		$generation_config = [];
		if ( ! self::is_gemini_3_text_model( $model ) ) {
			$generation_config['temperature'] = $temperature;
		}

		$body = [
			// ✅ Best practice: systemInstruction instead of faking it as a user message
			'systemInstruction' => [
				'parts' => [
					[ 'text' => $system_message ],
				],
			],
			'contents' => $contents,

			// Keep your existing safety policy (BLOCK_NONE)
			'safetySettings' => self::get_default_safety_settings(),
		];

		if ( ! empty( $generation_config ) ) {
			$body['generationConfig'] = $generation_config;
		}

		// ------------------------------------------------------------
		// 4) Use your existing request pipeline (supports streaming hook)
		// ------------------------------------------------------------

		// // Chat widget streaming expects SSE wrapper chunks {replyChunk: "..."}
		// self::set_stream_mode( $stream ? 'sse' : 'raw' );

		// $resp = self::send_text_request( $model, $body, (bool) $stream );

		// // STREAM: send_text_request() exits; this is just safety
		// if ( $stream ) {
		// 	exit;
		// }

		// ------------------------------------------------------------
		// 4) Use your existing request pipeline (supports streaming hook)
		// ------------------------------------------------------------

		// Only stream if requested AND Gemini streaming is enabled in settings
		$do_stream = (bool) $stream && self::sc_gemini_streaming_status();

		// Chat widget streaming expects SSE wrapper chunks {replyChunk: "..."}
		self::set_stream_mode( $do_stream ? 'sse' : 'raw' );

		$resp = self::send_text_request( $model, $body, $do_stream );

		// STREAM: send_text_request() exits; this is just safety
		if ( $do_stream ) {
			exit;
		}


		if ( ! is_array( $resp ) || ( $resp['status'] ?? '' ) !== 'success' ) {
			return [
				'status' => 'error',
				'reply'  => $resp['errorMessage'] ?? __( 'Gemini request failed.', 'wordai-pro' ),
			];
		}

		$text = trim( (string) ( $resp['responseText'] ?? '' ) );
		if ( $text === '' ) {
			return [
				'status' => 'error',
				'reply'  => __( 'No response received from Gemini.', 'wordai-pro' ),
			];
		}

		return [
			'status' => 'success',
			'reply'  => $text,
		];
	}
	
	/**
	 * ======================================================================
	 * Google Gemini — create_content()
	 * Unified output identical to OpenAI::create_content()
	 * ======================================================================
	 *
	 * @param array $api_params  ( prompt, system_instruction, stream )
	 * @return array identical OpenAI output structure
	 */
	public static function create_content($api_params = [], $stream = false)
	{		
		self::$output = [];
		self::$output['wordAIRequestParams'] = $api_params;		
		self::ensure_api_key();

		if (empty( self::$API_KEY )) {
			self::$output['status']              = 'fail';
			self::$output['comment']             = 'Valid API Key missing.';
			self::$output['wordAIAPIKeyMissing'] = __('Valid Google Gemini API Key required.', 'wordai-pro');
			return self::$output;
		}
		

		// Validate structure
		if (!isset($api_params['model']) || !isset($api_params['body'])) {
			return [
				'status'      => 'fail',
				'errorMessage'=> 'Invalid Gemini parameters received.',
				'errorType'   => 'gemini_params_error'
			];
		}

		$model = $api_params['model'];
		$body  = $api_params['body'];

		// Content generation streaming should stay RAW (existing parsers expect Gemini raw chunks)
		$stream = (bool) $stream && self::sc_gemini_streaming_status();
		self::set_stream_mode('raw');
		// API call
		$response = self::send_text_request($model, $body, $stream);


		/**
		 * ---------------------------------------------------------
		 * CENTRALIZED GEMINI ERROR HANDLING
		 * ---------------------------------------------------------
		 */
		$gemini_error = self::handle_gemini_error( $response, 'content' );
		if ( $gemini_error ) {
			$gemini_error['model'] = $model;
			$gemini_error['selectedImageModel'] = $model;			
			return $gemini_error;
		}

		// ❗ SECOND: general failure
		if (!isset($response['status']) || $response['status'] !== 'success') {
			return [
				'status'      => 'fail',
				'errorMessage'=> $response['errorMessage'] ?? 'Gemini request failed.',
				'errorType'   => $response['errorType'] ?? 'gemini_error',
			];
		}

		// SUCCESS — normalize output
		$final_text = $response['responseText'] ?? '';

		self::$output['status']              = 'success';
		self::$output['responseText']        = $final_text;
		self::$output['responseBody']        = $response;
		self::$output['model']               = $model;
		self::$output['responseTextWithBR']  = preg_replace("/\n\n/", "<br/><br/>", $final_text);

		return self::$output;
	}


	/**
	 * Gemini image generation core.
	 *
	 * Returns output compatible with OpenAI image behavior:
	 *  - status
	 *  - responseImageData => [ [ 'b64_json' => ..., 'mimeType' => ... ], ... ]
	 *  - responseBody
	 *  - wordAIRequestParams
	 *
	 * @param string $prompt
	 * @param array  $overrides
	 * @return array
	 */
	public static function generate_image_from_prompt( $prompt, $overrides = array() ) {

		self::$output = array();

		$params = self::set_gemini_image_params( $prompt, $overrides );
		self::$output['wordAIRequestParams'] = $params;

		self::ensure_api_key();

		if ( empty( self::$API_KEY ) ) {
			self::$output['status']              = 'fail';
			self::$output['comment']             = 'Valid API Key missing.';
			self::$output['wordAIAPIKeyMissing'] = __( 'Valid Google Gemini API Key required.', 'wordai-pro' );
			return self::$output;
		}

		$model = $params['model'];
		$body  = $params['body'];
		self::$output['model']              = $model;
		self::$output['selectedImageModel'] = $model;

		if ( self::is_gemini_interactions_image_model( $model ) ) {
			return self::generate_interactions_image( $model, $body, $params['mime'] ?? self::$IMAGE_MIME );
		}

		$base = rtrim( self::$AI_TEXT_EP_BASE, '/' ) . '/' . $model;
		$url  = $base . self::$AI_TEXT_EP_SUFFIX;

		$args = array(
			'headers'   => array(
				'Content-Type'   => 'application/json; charset=UTF-8',
				'x-goog-api-key' => self::$API_KEY,
			),
			'timeout'   => 90,
			'httpversion' => '1.0',
			'sslverify' => false,
			'body'      => wp_json_encode( $body ),
		);

		$response = wp_remote_post( $url, $args );

		if ( is_wp_error( $response ) ) {
			self::$output['errorMessage'] = $response->get_error_message();
			self::$output['status']       = 'fail';
			return self::$output;
		}

		$raw     = wp_remote_retrieve_body( $response );
		$decoded = json_decode( $raw, true );
		self::$output['responseBody'] = $decoded;


		$responseImages = array();

		// Gemini inline image data: candidates[0].content.parts[].inlineData.data
		if ( ! empty( $decoded['candidates'][0]['content']['parts'] ) ) {
			foreach ( $decoded['candidates'][0]['content']['parts'] as $part ) {
				if ( isset( $part['inlineData']['data'] ) ) {
					$responseImages[] = array(
						'b64_json' => $part['inlineData']['data'],
						'mimeType' => isset( $part['inlineData']['mimeType'] ) ? $part['inlineData']['mimeType'] : self::$IMAGE_MIME,
					);
				}
			}
		}

		if ( ! empty( $responseImages ) ) {
			self::$output['responseImageData'] = $responseImages;
			self::$output['status']            = 'success';
			return self::$output;
		}

		if ( isset( $decoded['error']['message'] ) ) {
			$normalized_error = self::handle_gemini_error( $decoded, 'image' );
			self::$output['status']       = $normalized_error['status'] ?? 'fail';
			self::$output['errorType']    = $normalized_error['errorType'] ?? 'gemini_error';
			self::$output['errorMessage'] = $normalized_error['errorMessage'] ?? $decoded['error']['message'];
			return self::$output;
		}

		self::$output['errorMessage'] = __( 'Unexpected Google Gemini image response format.', 'wordai-pro' );
		self::$output['status']       = 'fail';
		return self::$output;
	}

	/**
	 * Router-facing wrapper: unified "send_image_request" method.
	 *
	 * Expected $args keys:
	 *  - 'prompt' => string (required)
	 *
	 * @param array $args
	 * @return array
	 */
	public static function send_image_request( $args = array() ) {
		$prompt    = isset( $args['prompt'] ) ? (string) $args['prompt'] : '';
		$overrides = isset( $args['overrides'] ) && is_array( $args['overrides'] ) ? $args['overrides'] : array();

		return self::generate_image_from_prompt( $prompt, $overrides );
	}

	/**
	 * ================================================================
	 *  Google Gemini Image Generation (FINAL VERSION)
	 * ---------------------------------------------------------------
	 *  Compatible with OpenAI structure for frontend:
	 *  - status
	 *  - responseImageData[] { b64_json, mimeType }
	 *  - responseBody
	 *  - wordAIRequestParams
	 *  - model
	 *  - selectedImageModel
	 * ================================================================
	 *
	 * @param array $api_params
	 *   Either:
	 *     - ['model' => '...', 'body' => [...], 'mime' => 'image/png']  (from build_image_params)
	 *     - OR legacy ['prompt' => '...'] (fallback)
	 *
	 * @return array
	 */
	// public static function generate_image( $api_params = array() ) {

	// 	self::$output = array();

	// 	/**
	// 	 * ---------------------------------------------------------
	// 	 * 1️⃣ Normalize params
	// 	 *    - Preferred: already built by set_gemini_image_params()
	// 	 * ---------------------------------------------------------
	// 	 */
	// 	if ( isset( $api_params['model'], $api_params['body'] ) ) {

	// 		$model = $api_params['model'];
	// 		$body  = $api_params['body'];
	// 		$mime  = $api_params['mime'] ?? self::$IMAGE_MIME;

	// 		self::$output['wordAIRequestParams'] = $api_params;

	// 	} else {
	// 		// Legacy / safety fallback if only prompt is passed
	// 		$prompt = isset( $api_params['prompt'] ) ? (string) $api_params['prompt'] : '';

	// 		$params = self::set_gemini_image_params( $prompt, $api_params );

	// 		$model = $params['model'];
	// 		$body  = $params['body'];
	// 		$mime  = $params['mime'];

	// 		self::$output['wordAIRequestParams'] = $params;
	// 	}

	// 	/**
	// 	 * ---------------------------------------------------------
	// 	 * 2️⃣ Verify API key
	// 	 * ---------------------------------------------------------
	// 	 */
	// 	self::ensure_api_key();

	// 	if ( empty( self::$API_KEY ) ) {
	// 		self::$output['status']              = 'fail';
	// 		self::$output['wordAIAPIKeyMissing'] = __( 'Valid Google Gemini API Key required.', 'wordai-pro' );
	// 		return self::$output;
	// 	}

	// 	/**
	// 	 * ---------------------------------------------------------
	// 	 * 3️⃣ Perform HTTP request
	// 	 * ---------------------------------------------------------
	// 	 */
	// 	$url = rtrim( self::$AI_TEXT_EP_BASE, '/' ) . '/' . $model . self::$AI_TEXT_EP_SUFFIX;
		

	// 	$args = array(
	// 		'headers' => array(
	// 			'Content-Type'   => 'application/json; charset=UTF-8',
	// 			'x-goog-api-key' => self::$API_KEY,
	// 		),
	// 		'timeout'     => 90,
	// 		'httpversion' => '1.0',
	// 		'sslverify'   => false,
	// 		'body'        => wp_json_encode( $body ),
	// 	);

	// 	$response = wp_remote_post( $url, $args );

	// 	if ( is_wp_error( $response ) ) {
	// 		self::$output['status']       = 'fail';
	// 		self::$output['errorMessage'] = $response->get_error_message();
	// 		return self::$output;
	// 	}

	// 	$raw     = wp_remote_retrieve_body( $response );
	// 	$decoded = json_decode( $raw, true );
	// 	self::$output['responseBody'] = $decoded;

	// 	/**
	// 	 * 🔥 TEMPORARY DEBUG: Log raw Gemini response to debug.log
	// 	 * Remove after debugging.
	// 	 */
	// 	if ( function_exists( 'error_log' ) ) {
	// 		error_log( "GEMINI RAW IMAGE RESPONSE:\n" . print_r( $decoded, true ) );
	// 	}


	// 	/**
	// 	 * ---------------------------------------------------------
	// 	 * 4️⃣ Parse inline image blobs
	// 	 * ---------------------------------------------------------
	// 	 */
	// 	$images = array();

	// 	if ( ! empty( $decoded['candidates'][0]['content']['parts'] ) ) {
	// 		foreach ( $decoded['candidates'][0]['content']['parts'] as $part ) {

	// 			if ( isset( $part['inlineData']['data'] ) ) {
	// 				$images[] = array(
	// 					'b64_json' => $part['inlineData']['data'],
	// 					'mimeType' => $part['inlineData']['mimeType'] ?? self::$IMAGE_MIME,
	// 				);
	// 			}
	// 		}
	// 	}

	// 	if ( ! empty( $images ) ) {
	// 		self::$output['status']            = 'success';
	// 		self::$output['responseImageData'] = $images;
	// 		// 👇 expose model names so JS → upload flow works
	// 		self::$output['model']             = $model;
	// 		self::$output['selectedImageModel']= $model;
	// 		return self::$output;
	// 	}

	// 	/**
	// 	 * ---------------------------------------------------------
	// 	 * 5️⃣ Gemini error handling (correct priority)
	// 	 * ---------------------------------------------------------
	// 	 */
	// 	if ( isset($decoded['error']) ) {

	// 		$errStatus  = $decoded['error']['status']  ?? '';
	// 		$errMessage = $decoded['error']['message'] ?? '';

	// 		/**
	// 		 * 1️⃣ Invalid or unsupported model
	// 		 */
	// 		if ( $errStatus === 'NOT_FOUND' ) {
	// 			self::$output['status']       = 'fail';
	// 			self::$output['errorType']    = 'gemini_invalid_model';
	// 			self::$output['errorMessage'] =
	// 				__('The selected Google Gemini image model is invalid or not supported. Please choose a supported model.', 'wordai-pro');
	// 			return self::$output;
	// 		}

	// 		/**
	// 		 * 2️⃣ Free-tier image block (0 quota)
	// 		 */
	// 		if (
	// 			$errStatus === 'RESOURCE_EXHAUSTED'
	// 			&& strpos($errMessage, 'limit: 0') !== false
	// 		) {
	// 			self::$output['status']       = 'fail';
	// 			self::$output['errorType']    = 'gemini_free_tier_image_blocked';
	// 			self::$output['errorMessage'] =
	// 				__('Google Gemini free plan does not support image generation. Please upgrade your Google API plan or switch provider.', 'wordai-pro');
	// 			return self::$output;
	// 		}

	// 		/**
	// 		 * 3️⃣ Generic Gemini error
	// 		 */
	// 		self::$output['status']       = 'fail';
	// 		self::$output['errorType']    = strtolower($errStatus);
	// 		self::$output['errorMessage'] = $errMessage;
	// 		return self::$output;
	// 	}


	// 	self::$output['status']       = 'fail';
	// 	self::$output['errorMessage'] = __( 'Unexpected Google Gemini image response format.', 'wordai-pro' );

		
	// 	return self::$output;
	// }

	/**
	 * ================================================================
	 *  Google Gemini Image Generation (FINAL VERSION)
	 * ---------------------------------------------------------------
	 *  Compatible with OpenAI structure for frontend:
	 *  - status
	 *  - responseImageData[] { b64_json, mimeType }
	 *  - responseBody
	 *  - wordAIRequestParams
	 *  - model
	 *  - selectedImageModel
	 * ================================================================
	 *
	 * @param array $api_params
	 * @return array
	 */
	public static function generate_image( $api_params = array() ) {

		self::$output = array();

		/**
		 * ---------------------------------------------------------
		 * 1️⃣ Normalize params
		 * ---------------------------------------------------------
		 */
		if ( isset( $api_params['model'], $api_params['body'] ) ) {

			$model = $api_params['model'];
			$body  = $api_params['body'];
			$mime  = $api_params['mime'] ?? self::$IMAGE_MIME;

			self::$output['wordAIRequestParams'] = $api_params;

		} else {

			$prompt = isset( $api_params['prompt'] ) ? (string) $api_params['prompt'] : '';
			$params = self::set_gemini_image_params( $prompt, $api_params );

			$model = $params['model'];
			$body  = $params['body'];
			$mime  = $params['mime'];

			self::$output['wordAIRequestParams'] = $params;
		}

		/**
		 * ---------------------------------------------------------
		 * 2️⃣ Verify API key
		 * ---------------------------------------------------------
		 */
		self::ensure_api_key();

		if ( empty( self::$API_KEY ) ) {
			self::$output['status']              = 'fail';
			self::$output['wordAIAPIKeyMissing'] = __( 'Valid Google Gemini API Key required.', 'wordai-pro' );
			return self::$output;
		}

		/**
		 * ---------------------------------------------------------
		 * 3️⃣ Perform HTTP request
		 * ---------------------------------------------------------
		 */
		if ( self::is_gemini_interactions_image_model( $model ) ) {
			return self::generate_interactions_image( $model, $body, $mime );
		}

		$url = rtrim( self::$AI_TEXT_EP_BASE, '/' ) . '/' . $model . self::$AI_TEXT_EP_SUFFIX;

		$args = array(
			'headers' => array(
				'Content-Type'   => 'application/json; charset=UTF-8',
				'x-goog-api-key' => self::$API_KEY,
			),
			'timeout'     => 90,
			'httpversion' => '1.0',
			'sslverify'   => false,
			'body'        => wp_json_encode( $body ),
		);

		$response = wp_remote_post( $url, $args );

		if ( is_wp_error( $response ) ) {
			self::$output['status']       = 'fail';
			self::$output['errorMessage'] = $response->get_error_message();
			return self::$output;
		}

		$raw     = wp_remote_retrieve_body( $response );
		$decoded = json_decode( $raw, true );
		self::$output['responseBody'] = $decoded;
		// ✅ Always expose model in output (success OR fail)
		self::$output['model']              = $model;
		self::$output['selectedImageModel'] = $model;


		/**
		 * 🔥 TEMPORARY DEBUG LOG
		 */
		// if ( function_exists( 'error_log' ) ) {
		// 	error_log( "GEMINI RAW IMAGE RESPONSE:\n" . print_r( $decoded, true ) );
		// }

		/**
		 * ---------------------------------------------------------
		 * CENTRALIZED GEMINI ERROR HANDLER
		 * ---------------------------------------------------------
		 */
		$gemini_error = self::handle_gemini_error( $decoded, 'image' );
		if ( $gemini_error ) {
			$gemini_error['model'] = $model;
			$gemini_error['selectedImageModel'] = $model;			
			return $gemini_error;
		}

		/**
		 * ---------------------------------------------------------
		 * 4️⃣ Parse inline image blobs
		 * ---------------------------------------------------------
		 */
		$images = array();

		if ( ! empty( $decoded['candidates'][0]['content']['parts'] ) ) {
			foreach ( $decoded['candidates'][0]['content']['parts'] as $part ) {
				if ( isset( $part['inlineData']['data'] ) ) {
					$images[] = array(
						'b64_json' => $part['inlineData']['data'],
						'mimeType' => $part['inlineData']['mimeType'] ?? self::$IMAGE_MIME,
					);
				}
			}
		}

		if ( ! empty( $images ) ) {
			self::$output['status']            = 'success';
			self::$output['responseImageData'] = $images;
			self::$output['model']             = $model;
			self::$output['selectedImageModel']= $model;
			return self::$output;
		}

		/**
		 * ---------------------------------------------------------
		 * 5️⃣ If no image + no Gemini error → fallback
		 * ---------------------------------------------------------
		 */
		if ( isset($decoded['error']) ) {
			self::$output['status']       = 'fail';
			self::$output['errorType']    = strtolower($decoded['error']['status'] ?? 'gemini_error');
			self::$output['errorMessage'] = $decoded['error']['message'] ?? 'Unknown Gemini error.';
			return self::$output;
		}

		self::$output['status']       = 'fail';
		self::$output['errorType']    = 'gemini_unexpected_image_format';
		self::$output['errorMessage'] = __( 'No image data returned from Google Gemini. Please try again, reduce prompt complexity, or check Gemini limits/policy blocks.', 'wordai-pro' );
		

		return self::$output;
	}


	/**
	 * Helper
	 *
	 * @param [type] $mime
	 * @return string - image extension
	 */
	public static function mime_to_extension( $mime ) {
		switch ( strtolower( $mime ) ) {
			case 'image/png':
				return 'png';
			case 'image/jpeg':
			case 'image/jpg':
				return 'jpg';
			case 'image/webp':
				return 'webp';
			default:
				return 'png'; // safe fallback
		}
	}


	/**
	 * ======================================================================
	 * Central Google Gemini Error Normalizer
	 * ----------------------------------------------------------------------
	 * Returns a UNIFIED structure:
	 *  [
	 *      'status'      => 'fail',
	 *      'errorType'   => '...',
	 *      'errorMessage'=> '...'
	 *  ]
	 * ======================================================================
	 *
	 * @param array $decoded Gemini decoded API response
	 * @param string $mode   'content' or 'image'
	 *
	 * @return array|false   normalized array if error detected, or false if clean
	 */
	// public static function handle_gemini_error( $decoded, $mode = 'content' ) {

	// 	if ( empty( $decoded ) || ! isset( $decoded['error'] ) ) {
	// 		return false;
	// 	}

	// 	$errStatus  = strtoupper( $decoded['error']['status']  ?? '' );
	// 	$errMessage = $decoded['error']['message'] ?? '';

	// 	// ----------------------------------------
	// 	// 1️⃣ Invalid / unsupported model
	// 	// ----------------------------------------
	// 	if ( $errStatus === 'NOT_FOUND' ) {
	// 		return [
	// 			'status'      => 'fail',
	// 			'errorType'   => 'gemini_invalid_model',
	// 			'errorMessage'=> __(
	// 				'The selected Google Gemini model is invalid or not supported. Please choose a supported model.',
	// 				'wordai-pro'
	// 			),
	// 		];
	// 	}

	// 	// ----------------------------------------
	// 	// 2️⃣ FREE TIER (0 quota) — content + images
	// 	// ----------------------------------------
	// 	if (
	// 		$errStatus === 'RESOURCE_EXHAUSTED' &&
	// 		strpos( $errMessage, 'limit: 0' ) !== false
	// 	) {
	// 		if ( $mode === 'image' ) {
	// 			return [
	// 				'status'      => 'fail',
	// 				'errorType'   => 'gemini_free_tier_image_blocked',
	// 				'errorMessage'=> __(
	// 					'Google Gemini free plan does not support image generation. Please upgrade your Google API plan or switch provider.',
	// 					'wordai-pro'
	// 				),
	// 			];
	// 		} else {
	// 			return [
	// 				'status'      => 'fail',
	// 				'errorType'   => 'gemini_free_tier_content_blocked',
	// 				'errorMessage'=> __(
	// 					'Google Gemini free plan does not support content generation. Please upgrade your Google API plan or switch provider.',
	// 					'wordai-pro'
	// 				),
	// 			];
	// 		}
	// 	}

	// 	// ----------------------------------------
	// 	// 3️⃣ Free Tier Policy Error (Gemini 3 style)
	// 	// ----------------------------------------
	// 	if (
	// 		$errStatus === 'PERMISSION_DENIED' &&
	// 		stripos( $errMessage, 'free tier' ) !== false
	// 	) {
	// 		return [
	// 			'status'      => 'fail',
	// 			'errorType'   => 'gemini_free_tier_restricted',
	// 			'errorMessage'=> __(
	// 				'Your Google Gemini API Key is on the Free Tier and cannot access this feature.',
	// 				'wordai-pro'
	// 			),
	// 		];
	// 	}

	// 	// ----------------------------------------
	// 	// 4️⃣ General Gemini Error
	// 	// ----------------------------------------
	// 	return [
	// 		'status'      => 'fail',
	// 		'errorType'   => strtolower( $errStatus ?: 'gemini_error' ),
	// 		'errorMessage'=> $errMessage ?: __( 'Unknown Google Gemini error occurred.', 'wordai-pro' ),
	// 	];
	// }

	/**
	 * ======================================================================
	 * Central Google Gemini Error Normalizer
	 * ----------------------------------------------------------------------
	 * Supports BOTH inputs:
	 *  A) Raw Gemini decoded JSON (has top-level: error / candidates / promptFeedback)
	 *  B) Wrapper from send_text_request() (has: status, errorMessage, responseBody)
	 *
	 * Returns unified FAIL structure:
	 *  [
	 *      'status'       => 'fail',
	 *      'errorType'    => '...',
	 *      'errorMessage' => '...'
	 *  ]
	 * ======================================================================
	 *
	 * @param array  $decoded Either wrapper or raw Gemini decoded response
	 * @param string $mode    'content' or 'image'
	 *
	 * @return array|false normalized array if error detected, or false if clean
	 */
	public static function handle_gemini_error( $decoded, $mode = 'content' ) {

		// Must be array
		if ( empty( $decoded ) || ! is_array( $decoded ) ) {
			return false;
		}

		$mode = ( $mode === 'image' ) ? 'image' : 'content';

		/**
		 * ---------------------------------------------------------
		 * 0) If this is our wrapper output (send_text_request),
		 *    unwrap the real Gemini payload from responseBody.
		 * ---------------------------------------------------------
		 */
		$payload = $decoded;

		if ( isset( $decoded['responseBody'] ) && is_array( $decoded['responseBody'] ) ) {
			$payload = $decoded['responseBody'];
		}

		/**
		 * ---------------------------------------------------------
		 * 1) If wrapper already says FAIL and has a message,
		 *    return it (but try to improve using payload if possible).
		 * ---------------------------------------------------------
		 */
		if ( isset( $decoded['status'] ) && $decoded['status'] === 'fail' ) {

			// If payload contains Gemini error details, normalize from payload
			if ( is_array( $payload ) && isset( $payload['error'] ) ) {
				// fall through to section 3 below
			} else {
				return [
					'status'       => 'fail',
					'errorType'    => ! empty( $decoded['errorType'] ) ? (string) $decoded['errorType'] : 'gemini_error',
					'errorMessage' => ! empty( $decoded['errorMessage'] )
						? (string) $decoded['errorMessage']
						: __( 'Google Gemini request failed.', 'wordai-pro' ),
				];
			}
		}

		/**
		 * ---------------------------------------------------------
		 * 2) SOFT BLOCKS (often no "error" key)
		 * ---------------------------------------------------------
		 * Gemini may return no inlineData image + no error, but still
		 * provide promptFeedback.blockReason or candidates.finishReason.
		 */
		$block_reason = $payload['promptFeedback']['blockReason'] ?? '';
		if ( ! empty( $block_reason ) ) {

			return [
				'status'       => 'fail',
				'errorType'    => 'gemini_blocked',
				'errorMessage' => sprintf(
					/* translators: %s: Gemini block reason */
					__( 'Google Gemini blocked this request: %s', 'wordai-pro' ),
					sanitize_text_field( $block_reason )
				),
			];
		}

		$finish_reason = $payload['candidates'][0]['finishReason'] ?? '';
		if ( ! empty( $finish_reason ) ) {

			$finish_reason_upper = strtoupper( (string) $finish_reason );

			// Common finish reasons that mean refusal/block
			if ( in_array( $finish_reason_upper, [ 'SAFETY', 'RECITATION' ], true ) ) {
				return [
					'status'       => 'fail',
					'errorType'    => 'gemini_finish_reason',
					'errorMessage' => sprintf(
						/* translators: %s: finish reason */
						__( 'Google Gemini could not complete the request (finishReason: %s). Please try a different prompt or provider.', 'wordai-pro' ),
						sanitize_text_field( $finish_reason_upper )
					),
				];
			}

			if ( $finish_reason_upper === 'MAX_TOKENS' ) {
				return [
					'status'       => 'fail',
					'errorType'    => 'gemini_max_tokens',
					'errorMessage' => __( 'Google Gemini returned an incomplete response because the selected output limit was reached. Increase the Gemini Max Tokens value in WordAI API Settings or reduce the content paragraph/depth settings, then try again.', 'wordai-pro' ),
				];
			}
		}

		/**
		 * ---------------------------------------------------------
		 * 3) HARD API ERRORS (payload['error'])
		 * ---------------------------------------------------------
		 */
		if ( empty( $payload['error'] ) || ! is_array( $payload['error'] ) ) {
			return false; // no detectable Gemini error
		}

		$errStatus  = strtoupper( (string) ( $payload['error']['status'] ?? '' ) );
		$errMessage = (string) ( $payload['error']['message'] ?? '' );

		/**
		 * 3A) Auth / API key issues
		 */
		if ( $errStatus === 'UNAUTHENTICATED' ) {
			return [
				'status'       => 'fail',
				'errorType'    => 'gemini_unauthenticated',
				'errorMessage' => __(
					'Google Gemini authentication failed. Please verify your API key is valid and has access to the selected API.',
					'wordai-pro'
				),
			];
		}

		if ( $errStatus === 'PERMISSION_DENIED' ) {

			// Free tier restriction text check (your existing case)
			if ( stripos( $errMessage, 'free tier' ) !== false ) {
				return [
					'status'       => 'fail',
					'errorType'    => 'gemini_free_tier_restricted',
					'errorMessage' => __(
						'Your Google Gemini API Key is on the Free Tier and cannot access this feature.',
						'wordai-pro'
					),
				];
			}

			return [
				'status'       => 'fail',
				'errorType'    => 'gemini_permission_denied',
				'errorMessage' => $errMessage ?: __(
					'Google Gemini permission denied. Please check API key permissions and billing/project access.',
					'wordai-pro'
				),
			];
		}

		/**
		 * 3B) Invalid / unsupported model
		 */
		if ( $errStatus === 'NOT_FOUND' ) {
			return [
				'status'       => 'fail',
				'errorType'    => 'gemini_invalid_model',
				'errorMessage' => __(
					'The selected Google Gemini model is invalid or not supported. Please choose a supported model.',
					'wordai-pro'
				),
			];
		}

		/**
		 * 3C) Quota / rate limiting / resource exhausted
		 */
		if ( $errStatus === 'RESOURCE_EXHAUSTED' ) {

			// Preserve provider-specific quota text instead of mislabeling support.
			if ( strpos( strtolower( $errMessage ), 'limit: 0' ) !== false ) {
				if ( $mode === 'image' ) {
					return [
						'status'       => 'fail',
						'errorType'    => 'gemini_image_quota_unavailable',
						'errorMessage' => $errMessage ?: __(
							'Google Gemini rejected image generation for the selected model on the current plan or quota. Check Gemini rate limits, billing, or try another Gemini image model.',
							'wordai-pro'
						),
					];
				}

				return [
					'status'       => 'fail',
					'errorType'    => 'gemini_content_quota_unavailable',
					'errorMessage' => $errMessage ?: __(
						'Google Gemini rejected content generation for the selected model on the current plan or quota. Check Gemini rate limits or billing before retrying.',
						'wordai-pro'
					),
				];
			}

			// Otherwise quota/rate limit
			return [
				'status'       => 'fail',
				'errorType'    => 'gemini_rate_limited',
				'errorMessage' => $errMessage ?: __(
					'Google Gemini quota/rate limit reached. Please wait a moment and try again, or upgrade your plan.',
					'wordai-pro'
				),
			];
		}

		/**
		 * 3D) Invalid argument / malformed request
		 */
		if ( $errStatus === 'INVALID_ARGUMENT' ) {
			return [
				'status'       => 'fail',
				'errorType'    => 'gemini_invalid_argument',
				'errorMessage' => $errMessage ?: __(
					'Invalid request parameters sent to Google Gemini. Please check the request payload.',
					'wordai-pro'
				),
			];
		}

		if ( $errStatus === 'UNAVAILABLE' ) {
			return [
				'status'       => 'fail',
				'errorType'    => 'gemini_unavailable',
				'errorMessage' => $errMessage ?: __(
					'Google Gemini is currently experiencing high demand or a temporary service issue. Please try again in a moment.',
					'wordai-pro'
				),
			];
		}

		/**
		 * 4) General Gemini Error (fallback)
		 */
		return [
			'status'       => 'fail',
			'errorType'    => strtolower( $errStatus ?: 'gemini_error' ),
			'errorMessage' => $errMessage ?: __( 'Unknown Google Gemini error occurred.', 'wordai-pro' ),
		];
	}



	/*───────────────────────────────────────────────────────────────
	| EMBEDDINGS
	───────────────────────────────────────────────────────────────*/

	/**
	 * Create embeddings for an array of text chunks using Gemini.
	 *
	 * @param array  $texts Array of text chunks.
	 * @param string $model Embedding model (e.g., text-embedding-004).
	 * @return array
	 * [
	 *   'status'      => 'success' | 'fail',
	 *   'feedbackMsg' => string,
	 *   'embeddings'  => [ [floats...], [floats...] ],
	 * ]
	 */
	public static function embed_texts( array $texts, $model = 'text-embedding-004' ) {

		self::ensure_api_key();

		if ( empty( self::$API_KEY ) ) {
			return array(
				'status'      => 'fail',
				'feedbackMsg' => __( 'Google Gemini API key is missing.', 'wordai-pro' ),
			);
		}

		if ( empty( $texts ) ) {
			return array(
				'status'      => 'fail',
				'feedbackMsg' => __( 'No text provided for embedding.', 'wordai-pro' ),
			);
		}

		$endpoint = sprintf(
			'https://generativelanguage.googleapis.com/v1beta/models/%s:embedContent',
			$model
		);

		$embeddings = array();

		// Note: currently Gemini embeddings are one-at-a-time.
		foreach ( $texts as $chunk ) {

			$body = array(
				'content' => array(
					'parts' => array(
						array(
							'text' => (string) $chunk,
						),
					),
				),
			);

			$args = array(
				'headers'  => array(
					'Content-Type'   => 'application/json',
					'x-goog-api-key' => self::$API_KEY,
				),
				'body'     => wp_json_encode( $body ),
				'timeout'  => 40,
				'sslverify'=> false,
			);

			$response = wp_remote_post( $endpoint, $args );

			if ( is_wp_error( $response ) ) {
				return array(
					'status'      => 'fail',
					'feedbackMsg' => __( 'Embedding request failed: ', 'wordai-pro' ) . $response->get_error_message(),
				);
			}

			$code = wp_remote_retrieve_response_code( $response );
			$raw  = wp_remote_retrieve_body( $response );
			$json = json_decode( $raw, true );

			if ( $code !== 200 || empty( $json['embedding']['values'] ) ) {
				$err = isset( $json['error']['message'] ) ? $json['error']['message'] : 'Unknown API error';
				return array(
					'status'      => 'fail',
					'feedbackMsg' => __( 'Embedding error: ', 'wordai-pro' ) . $err,
				);
			}

			$embeddings[] = $json['embedding']['values'];
		}

		return array(
			'status'      => 'success',
			'feedbackMsg' => __( 'Gemini embeddings created successfully.', 'wordai-pro' ),
			'embeddings'  => $embeddings,
		);
	}
}

endif;
