<?php
/**
 * OpenRouter Provider Class
 *
 * OpenAI-compatible integration with OpenRouter aggregator.
 *
 * @package SmartyPress_AI_Engine
 * @since 1.1.0
 */

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

/**
 * OpenRouter Provider class.
 */
class SmartyPress_AI_OpenRouter_Provider extends SmartyPress_AI_AI_Provider {

	/**
	 * Long-running image models can take several minutes on OpenRouter.
	 */
	const IMAGE_GENERATION_TIMEOUT = 600;

	/**
	 * Number of retries after the first failed OpenRouter request.
	 */
	const RATE_LIMIT_MAX_RETRIES = 3;

	/**
	 * Maximum retry delay in seconds.
	 */
	const RATE_LIMIT_MAX_RETRY_DELAY = 10;

	/**
	 * Default image model.
	 *
	 * @var string
	 */
	private $image_model;

	/**
	 * Enabled text models. Empty means all models.
	 *
	 * @var array
	 */
	private $enabled_text_models;

	/**
	 * Whether text model allow-list should be applied.
	 *
	 * @var bool
	 */
	private $limit_text_models;

	/**
	 * Enabled image models. Empty means all models.
	 *
	 * @var array
	 */
	private $enabled_image_models;

	/**
	 * Whether image model allow-list should be applied.
	 *
	 * @var bool
	 */
	private $limit_image_models;

	/**
	 * Constructor.
	 *
	 * @param string $api_key              API key.
	 * @param string $model                Text model name.
	 * @param string $image_model          Image model name.
	 * @param array  $enabled_text_models  Enabled text models.
	 * @param array  $enabled_image_models Enabled image models.
	 * @param bool   $limit_text_models    Whether to restrict text models to the enabled list.
	 * @param bool   $limit_image_models   Whether to restrict image models to the enabled list.
	 */
	public function __construct( $api_key, $model = 'openai/gpt-4o-mini', $image_model = 'google/gemini-2.5-flash-image', $enabled_text_models = array(), $enabled_image_models = array(), $limit_text_models = false, $limit_image_models = false ) {
		$this->enabled_text_models  = is_array( $enabled_text_models ) ? array_values( $enabled_text_models ) : array();
		$this->enabled_image_models = is_array( $enabled_image_models ) ? array_values( $enabled_image_models ) : array();
		$this->limit_text_models    = (bool) $limit_text_models;
		$this->limit_image_models   = (bool) $limit_image_models;

		parent::__construct( $api_key, $this->get_effective_text_model( $model ) );
		$this->api_endpoint = 'https://openrouter.ai/api/v1/chat/completions';
		$this->image_model  = $this->get_effective_image_model( $image_model );
	}

	/**
	 * Generate content through OpenRouter Chat Completions.
	 *
	 * @param string $prompt The prompt to send to AI.
	 * @param array  $params Additional parameters.
	 * @return array Response with content or error.
	 */
	public function generate_content( $prompt, $params = array() ) {
		$system_prompt = $params['system_prompt'] ?? __( 'You are a helpful assistant for content generation.', 'smartypress-ai-engine' );
		$messages      = $this->build_messages( $system_prompt, $prompt );
		$model         = $this->get_effective_text_model( ! empty( $params['model'] ) ? $params['model'] : $this->model );
		$timeout       = ! empty( $params['request_timeout'] ) ? absint( $params['request_timeout'] ) : 60;

		$api_params = array(
			'model'       => $model,
			'messages'    => $messages,
			'temperature' => $params['temperature'] ?? 0.7,
			'max_tokens'  => $params['max_tokens'] ?? 2000,
		);

		if ( ! empty( $params['response_format'] ) && is_array( $params['response_format'] ) ) {
			$api_params['response_format'] = $params['response_format'];
		}

		if ( ! empty( $params['web_search'] ) ) {
			$api_params = array_merge(
				$api_params,
				apply_filters( 'smartypress_ai_openrouter_web_search_params', array(), $model, $params )
			);
		}

		$api_params = apply_filters( 'smartypress_ai_openrouter_request_params', $api_params, $model, $params );
		$api_params = SmartyPress_AI_Model_Map::filter_request_params( 'openrouter', $model, $api_params );

		return $this->make_openrouter_request( $api_params, $timeout );
	}

	/**
	 * Describe image with a custom vision prompt.
	 *
	 * @param string $image_path Path to the image file.
	 * @param string $prompt     Prompt for the vision model.
	 * @param array  $params     Additional params.
	 * @return array Response with content or error.
	 */
	public function describe_image_with_prompt( $image_path, $prompt, $params = array() ) {
		if ( ! file_exists( $image_path ) || ! is_readable( $image_path ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Image file not found or not readable.', 'smartypress-ai-engine' ),
			);
		}

		$image_data = file_get_contents( $image_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		if ( false === $image_data ) {
			return array(
				'success' => false,
				'error'   => __( 'Failed to read image file.', 'smartypress-ai-engine' ),
			);
		}

		$mime = $this->detect_image_mime_type( $image_path );
		$url  = 'data:' . $mime . ';base64,' . base64_encode( $image_data ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode

		$model = $this->get_effective_text_model( ! empty( $params['model'] ) ? $params['model'] : $this->model );

		$api_params = array(
			'model'       => $model,
			'messages'    => array(
				array(
					'role'    => 'system',
					'content' => __( 'You generate accurate WordPress image metadata from visual content and SEO context.', 'smartypress-ai-engine' ),
				),
				array(
					'role'    => 'user',
					'content' => array(
						array(
							'type' => 'text',
							'text' => $prompt,
						),
						array(
							'type'      => 'image_url',
							'image_url' => array(
								'url' => $url,
							),
						),
					),
				),
			),
			'temperature' => $params['temperature'] ?? 0.2,
			'max_tokens'  => $params['max_tokens'] ?? 500,
		);

		$api_params = SmartyPress_AI_Model_Map::filter_request_params( 'openrouter', $model, $api_params );

		return $this->make_openrouter_request( $api_params, 120 );
	}

	/**
	 * Validate API key.
	 *
	 * @return bool True if valid, false otherwise.
	 */
	public function validate_api_key() {
		$result = $this->generate_content(
			__( 'Say "Hello"', 'smartypress-ai-engine' ),
			array(
				'max_tokens' => 10,
			)
		);

		return $result['success'] ?? false;
	}

	/**
	 * Generate image through OpenRouter image-capable models.
	 *
	 * @param string $prompt Image generation prompt.
	 * @param array  $params Additional parameters.
	 * @return array Response with image URL/b64 or error.
	 */
	public function generate_image( $prompt, $params = array() ) {
		$model      = $this->get_effective_image_model( $params['model'] ?? $this->image_model );
		$resolution = $params['resolution'] ?? '1024x1024';
		$model_data = SmartyPress_AI_Model_Map::get_model( 'openrouter', $model );

		if ( $model_data && empty( $model_data['capabilities']['image_output'] ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Selected OpenRouter model does not support image output.', 'smartypress-ai-engine' ),
			);
		}

		$modalities = array( 'image' );
		if ( ! $model_data || ! empty( $model_data['capabilities']['text_output'] ) ) {
			$modalities[] = 'text';
		}

		$api_params = array(
			'model'        => $model,
			'messages'     => array(
				array(
					'role'    => 'user',
					'content' => $prompt,
				),
			),
			'modalities'   => $modalities,
			'image_config' => array(
				'aspect_ratio' => $this->resolution_to_aspect_ratio( $resolution, $model ),
			),
		);

		$api_params = SmartyPress_AI_Model_Map::filter_request_params( 'openrouter', $model, $api_params );

		if ( empty( $api_params['modalities'] ) || ! in_array( 'image', $api_params['modalities'], true ) ) {
			$api_params['modalities'] = array( 'image' );
		}

		$result = $this->make_openrouter_request( $api_params, self::IMAGE_GENERATION_TIMEOUT, true );

		if ( ! $result['success'] ) {
			return $result;
		}

		$image_url = $result['image_url'] ?? '';

		if ( empty( $image_url ) ) {
			return $this->build_missing_image_error( $result['content'] ?? '' );
		}

		if ( preg_match( '/^data:image\/[a-zA-Z0-9.+-]+;base64,(.+)$/', $image_url, $matches ) ) {
			return array(
				'success'  => true,
				'b64_json' => $matches[1],
				'model'    => $model,
				'usage'    => $result['usage'] ?? array(),
			);
		}

		return array(
			'success' => true,
			'url'     => $image_url,
			'model'   => $model,
			'usage'   => $result['usage'] ?? array(),
		);
	}

	/**
	 * Edit image through OpenRouter image-to-image models.
	 *
	 * @param string $image_path Path to source image.
	 * @param string $prompt     Edit instruction prompt.
	 * @param array  $params     Additional parameters.
	 * @return array Response with image URL/b64 or error.
	 */
	public function edit_image( $image_path, $prompt, $params = array() ) {
		if ( ! file_exists( $image_path ) || ! is_readable( $image_path ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Image file not found or not readable.', 'smartypress-ai-engine' ),
			);
		}

		$edit_models = $this->get_available_image_edit_models();
		$model       = ! empty( $params['model'] ) ? sanitize_text_field( $params['model'] ) : ( array_key_first( $edit_models ) ?: '' );

		if ( empty( $model ) || empty( $edit_models[ $model ] ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Selected OpenRouter model does not support image editing.', 'smartypress-ai-engine' ),
			);
		}

		$data_url = $this->image_file_to_data_url( $image_path );
		if ( empty( $data_url ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Failed to read image file for OpenRouter edit request.', 'smartypress-ai-engine' ),
			);
		}

		$resolution = $params['size'] ?? $params['resolution'] ?? '1024x1024';
		$model_data = $edit_models[ $model ];
		$modalities = array( 'image' );

		if ( ! empty( $model_data['capabilities']['text_output'] ) ) {
			$modalities[] = 'text';
		}

		$api_params = array(
			'model'        => $model,
			'messages'     => array(
				array(
					'role'    => 'user',
					'content' => array(
						array(
							'type' => 'text',
							'text' => $prompt,
						),
						array(
							'type'      => 'image_url',
							'image_url' => array(
								'url' => $data_url,
							),
						),
					),
				),
			),
			'modalities'   => $modalities,
			'image_config' => array(
				'aspect_ratio' => $this->resolution_to_aspect_ratio( $resolution, $model ),
			),
		);

		$api_params = SmartyPress_AI_Model_Map::filter_request_params( 'openrouter', $model, $api_params );

		if ( empty( $api_params['modalities'] ) || ! in_array( 'image', $api_params['modalities'], true ) ) {
			$api_params['modalities'] = array( 'image' );
		}

		$result = $this->make_openrouter_request( $api_params, self::IMAGE_GENERATION_TIMEOUT, true );

		if ( ! $result['success'] ) {
			return $result;
		}

		$image_url = $result['image_url'] ?? '';

		if ( empty( $image_url ) ) {
			return $this->build_missing_image_error( $result['content'] ?? '', true );
		}

		if ( preg_match( '/^data:image\/[a-zA-Z0-9.+-]+;base64,(.+)$/', $image_url, $matches ) ) {
			return array(
				'success'  => true,
				'b64_json' => $matches[1],
				'model'    => $model,
				'usage'    => $result['usage'] ?? array(),
			);
		}

		return array(
			'success' => true,
			'url'     => $image_url,
			'model'   => $model,
			'usage'   => $result['usage'] ?? array(),
		);
	}

	/**
	 * Get available text models.
	 *
	 * @return array
	 */
	public function get_available_models() {
		$models = SmartyPress_AI_Model_Map::get_text_models( 'openrouter' );

		return $this->limit_text_models
			? SmartyPress_AI_Model_Map::filter_models_by_selection( $models, $this->enabled_text_models )
			: $models;
	}

	/**
	 * Get available image models.
	 *
	 * @return array
	 */
	public function get_available_image_models() {
		$models = SmartyPress_AI_Model_Map::get_image_models( 'openrouter' );

		return $this->limit_image_models
			? SmartyPress_AI_Model_Map::filter_models_by_selection( $models, $this->enabled_image_models )
			: $models;
	}

	/**
	 * Get available image edit models.
	 *
	 * @return array
	 */
	public function get_available_image_edit_models() {
		$models = SmartyPress_AI_Model_Map::get_image_edit_models( 'openrouter' );

		return $this->limit_image_models
			? SmartyPress_AI_Model_Map::filter_models_by_selection( $models, $this->enabled_image_models )
			: $models;
	}

	/**
	 * Get first allowed text model when configured model is not allowed.
	 *
	 * @param string $model Configured model.
	 * @return string
	 */
	private function get_effective_text_model( $model ) {
		if ( ! $this->limit_text_models || empty( $this->enabled_text_models ) || in_array( $model, $this->enabled_text_models, true ) ) {
			return $model;
		}

		return $this->enabled_text_models[0] ?? $model;
	}

	/**
	 * Get first allowed image model when configured model is not allowed.
	 *
	 * @param string $model Configured model.
	 * @return string
	 */
	private function get_effective_image_model( $model ) {
		if ( ! $this->limit_image_models || empty( $this->enabled_image_models ) || in_array( $model, $this->enabled_image_models, true ) ) {
			return $model;
		}

		return $this->enabled_image_models[0] ?? $model;
	}

	/**
	 * Make OpenRouter API request.
	 *
	 * @param array $body         Request body.
	 * @param int   $timeout      Timeout.
	 * @param bool  $expect_image Whether image output is expected.
	 * @return array
	 */
	private function make_openrouter_request( $body, $timeout = 60, $expect_image = false ) {
		$request_args = array(
			'headers' => array(
				'Content-Type'       => 'application/json',
				'Authorization'      => 'Bearer ' . $this->api_key,
				'HTTP-Referer'       => home_url(),
				'X-OpenRouter-Title' => get_bloginfo( 'name' ) ?: 'SmartyPress AI',
			),
			'body'    => wp_json_encode( $body ),
			'timeout' => $timeout,
		);

		$attempt        = 0;
		$max_attempts   = self::RATE_LIMIT_MAX_RETRIES + 1;
		$last_response  = null;
		$last_error_msg = '';

		while ( $attempt < $max_attempts ) {
			$attempt++;
			$response = wp_remote_post( $this->api_endpoint, $request_args );

			if ( is_wp_error( $response ) ) {
				return array(
					'success' => false,
					'error'   => $response->get_error_message(),
				);
			}

			$response_code = wp_remote_retrieve_response_code( $response );
			$response_body = wp_remote_retrieve_body( $response );
			$data          = json_decode( $response_body, true );
			$data          = is_array( $data ) ? $data : array();

			if ( 200 === $response_code ) {
				break;
			}

			$last_response  = $response;
			$last_error_msg = $data['error']['message'] ?? __( 'Unknown OpenRouter API error', 'smartypress-ai-engine' );

			if ( $attempt >= $max_attempts || ! $this->is_retryable_openrouter_error( $response_code, $data ) ) {
				return $this->format_openrouter_error( $response, $body, $data );
			}

			$retry_delay = $this->get_openrouter_retry_delay( $response, $attempt );
			$this->log_openrouter_retry( $body, $response_code, $last_error_msg, $attempt, $retry_delay, $data );
			sleep( $retry_delay );
		}

		if ( ! isset( $response ) || is_wp_error( $response ) ) {
			return array(
				'success' => false,
				'error'   => $last_error_msg ?: __( 'Unknown OpenRouter API error', 'smartypress-ai-engine' ),
			);
		}

		$response_code = wp_remote_retrieve_response_code( $response );
		$response_body = wp_remote_retrieve_body( $response );
		$data          = json_decode( $response_body, true );
		$data          = is_array( $data ) ? $data : array();

		if ( 200 !== $response_code ) {
			return $this->format_openrouter_error( $last_response ?: $response, $body, $data );
		}

		$message = $data['choices'][0]['message'] ?? array();
		$content = $message['content'] ?? '';
		$images  = $message['images'] ?? array();
		$choice  = $data['choices'][0] ?? array();
		$finish_reason = is_array( $choice ) ? sanitize_text_field( $choice['finish_reason'] ?? '' ) : '';
		$response_model = sanitize_text_field( $data['model'] ?? ( $body['model'] ?? '' ) );

		if ( class_exists( 'SmartyPress_AI_Logger' ) ) {
			$image_count = is_array( $images ) ? count( $images ) : 0;
			$log_content = $expect_image && $image_count > 0 ? '[image]' : $content;

			SmartyPress_AI_Logger::log_api_success(
				$this->get_provider_name(),
				$this->api_endpoint,
				$body,
				array(
					'code'    => $response_code,
					'content' => $log_content,
					'usage'   => $data['usage'] ?? array(),
				)
			);
		}

		$usage = $data['usage'] ?? array();

		// Record token usage for statistics.
		if ( class_exists( 'SmartyPress_AI_Token_Tracker' ) ) {
			SmartyPress_AI_Token_Tracker::record( array(
				'provider' => $this->get_provider_name(),
				'model'    => $body['model'] ?? '',
				'usage'    => $usage,
			) );
		}

		if ( $expect_image ) {
			$image_url = $this->extract_image_url( $images );

			return array(
				'success'       => true,
				'image_url'     => $image_url,
				'content'       => $content,
				'usage'         => $usage,
				'finish_reason' => $finish_reason,
				'model'         => $response_model,
			);
		}

		return array(
			'success'       => true,
			'content'       => $content,
			'usage'         => $usage,
			'finish_reason' => $finish_reason,
			'model'         => $response_model,
		);
	}

	/**
	 * Check if OpenRouter error is safe to retry.
	 *
	 * @param int   $response_code HTTP response code.
	 * @param array $data          Decoded response body.
	 * @return bool
	 */
	private function is_retryable_openrouter_error( $response_code, $data ) {
		if ( in_array( $response_code, array( 401, 403 ), true ) ) {
			return false;
		}

		$data          = is_array( $data ) ? $data : array();
		$error_message = $data['error']['message'] ?? '';
		$metadata      = $data['error']['metadata'] ?? array();
		$metadata      = is_array( $metadata ) ? $metadata : array();
		$raw_message   = $metadata['raw'] ?? '';
		$error_text    = strtolower( $error_message . ' ' . $raw_message );

		if ( 429 === (int) $response_code ) {
			return true;
		}

		if ( in_array( (int) $response_code, array( 503, 529 ), true ) ) {
			return true;
		}

		return false !== strpos( $error_text, 'rate-limited' )
			|| false !== strpos( $error_text, 'rate limit' )
			|| false !== strpos( $error_text, 'retry shortly' );
	}

	/**
	 * Get retry delay from OpenRouter headers or exponential backoff.
	 *
	 * @param array $response WP HTTP response.
	 * @param int   $attempt  Current attempt number.
	 * @return int
	 */
	private function get_openrouter_retry_delay( $response, $attempt ) {
		$retry_after = wp_remote_retrieve_header( $response, 'retry-after' );
		if ( '' !== $retry_after ) {
			if ( is_numeric( $retry_after ) ) {
				return min( self::RATE_LIMIT_MAX_RETRY_DELAY, max( 1, (int) ceil( (float) $retry_after ) ) );
			}

			$retry_timestamp = strtotime( $retry_after );
			if ( false !== $retry_timestamp ) {
				return min( self::RATE_LIMIT_MAX_RETRY_DELAY, max( 1, $retry_timestamp - time() ) );
			}
		}

		$rate_limit_reset = wp_remote_retrieve_header( $response, 'x-ratelimit-reset' );
		if ( '' !== $rate_limit_reset && is_numeric( $rate_limit_reset ) ) {
			$reset_value = (float) $rate_limit_reset;
			$delay       = $reset_value > time() ? (int) ceil( $reset_value - time() ) : (int) ceil( $reset_value );

			if ( $delay > 0 ) {
				return min( self::RATE_LIMIT_MAX_RETRY_DELAY, $delay );
			}
		}

		return min( self::RATE_LIMIT_MAX_RETRY_DELAY, (int) pow( 2, max( 0, $attempt - 1 ) ) );
	}

	/**
	 * Log intermediate OpenRouter retry event.
	 *
	 * @param array  $body          Request body.
	 * @param int    $response_code HTTP response code.
	 * @param string $error_message Error message.
	 * @param int    $attempt       Current attempt.
	 * @param int    $retry_delay   Retry delay in seconds.
	 * @param array  $data          Decoded response body.
	 */
	private function log_openrouter_retry( $body, $response_code, $error_message, $attempt, $retry_delay, $data ) {
		if ( ! class_exists( 'SmartyPress_AI_Logger' ) ) {
			return;
		}

		$metadata = $data['error']['metadata'] ?? array();
		$metadata = is_array( $metadata ) ? $metadata : array();

		SmartyPress_AI_Logger::log_module_event(
			'openrouter',
			'rate_limit_retry',
			array(
				'model'          => $body['model'] ?? '',
				'response_code'  => $response_code,
				'error'          => $error_message,
				'attempt'        => $attempt,
				'max_retries'    => self::RATE_LIMIT_MAX_RETRIES,
				'retry_delay'    => $retry_delay,
				'provider_name'  => $metadata['provider_name'] ?? '',
				'is_byok'        => isset( $metadata['is_byok'] ) ? (bool) $metadata['is_byok'] : null,
			),
			false
		);
	}

	/**
	 * Format final OpenRouter error response and log it once.
	 *
	 * @param array $response WP HTTP response.
	 * @param array $body     Request body.
	 * @param array $data     Decoded response body.
	 * @return array
	 */
	private function format_openrouter_error( $response, $body, $data ) {
		$data          = is_array( $data ) ? $data : array();
		$response_code = wp_remote_retrieve_response_code( $response );
		$response_body = wp_remote_retrieve_body( $response );
		$error_message = $data['error']['message'] ?? __( 'Unknown OpenRouter API error', 'smartypress-ai-engine' );
		$metadata      = $data['error']['metadata'] ?? array();
		$metadata      = is_array( $metadata ) ? $metadata : array();
		$error_code    = $this->get_openrouter_error_code( $response_code, $error_message, $metadata );

		if ( 'rate_limit' === $error_code && isset( $metadata['is_byok'] ) ) {
			$error_message = ! empty( $metadata['is_byok'] )
				? $error_message . ' ' . __( 'The limit may be coming from your connected provider key in OpenRouter integrations.', 'smartypress-ai-engine' )
				: $error_message . ' ' . __( 'OpenRouter shared provider capacity is temporarily rate-limited. Please retry shortly.', 'smartypress-ai-engine' );
		}

		if ( class_exists( 'SmartyPress_AI_Logger' ) ) {
			SmartyPress_AI_Logger::log_api_error(
				$this->get_provider_name(),
				$this->api_endpoint,
				$body,
				array(
					'code' => $response_code,
					'body' => $response_body,
				),
				$error_message,
				$error_code
			);
		}

		$error_response = array(
			'success'     => false,
			'error'       => $error_message,
			'error_code'  => $error_code,
		);

		if ( 'rate_limit' === $error_code ) {
			$error_response['retry_after'] = $this->get_openrouter_retry_delay( $response, 1 );
		}

		if ( ! empty( $metadata['provider_name'] ) ) {
			$error_response['provider_name'] = sanitize_text_field( $metadata['provider_name'] );
		}

		if ( isset( $metadata['is_byok'] ) ) {
			$error_response['is_byok'] = (bool) $metadata['is_byok'];
		}

		return $error_response;
	}

	/**
	 * Get normalized OpenRouter error code.
	 *
	 * @param int    $response_code HTTP response code.
	 * @param string $error_message Error message.
	 * @param array  $metadata      Error metadata.
	 * @return string
	 */
	private function get_openrouter_error_code( $response_code, $error_message, $metadata ) {
		if ( in_array( (int) $response_code, array( 401, 403 ), true ) ) {
			return 'api_key_invalid';
		}

		$raw_message = $metadata['raw'] ?? '';
		$error_text  = strtolower( $error_message . ' ' . $raw_message );

		if ( 429 === (int) $response_code
			|| false !== strpos( $error_text, 'rate-limited' )
			|| false !== strpos( $error_text, 'rate limit' )
			|| false !== strpos( $error_text, 'retry shortly' )
		) {
			return 'rate_limit';
		}

		if ( false !== strpos( $error_text, 'quota' ) || false !== strpos( $error_text, 'billing' ) ) {
			return 'quota_exceeded';
		}

		return '';
	}

	/**
	 * Extract image URL from OpenRouter response images.
	 *
	 * @param array $images Images response.
	 * @return string
	 */
	private function extract_image_url( $images ) {
		if ( ! is_array( $images ) || empty( $images ) ) {
			return '';
		}

		$image = $images[0];

		if ( isset( $image['image_url']['url'] ) ) {
			return $image['image_url']['url'];
		}

		if ( isset( $image['imageUrl']['url'] ) ) {
			return $image['imageUrl']['url'];
		}

		if ( isset( $image['url'] ) ) {
			return $image['url'];
		}

		return '';
	}

	/**
	 * Build a helpful error when OpenRouter returns no image payload.
	 *
	 * @param string $content Response text from the model.
	 * @param bool   $is_edit Whether this was an edit request.
	 * @return array
	 */
	private function build_missing_image_error( $content, $is_edit = false ) {
		$content = trim( wp_strip_all_tags( (string) $content ) );

		if ( '' !== $content ) {
			return array(
				'success'    => false,
				'error'      => sprintf(
					/* translators: %s: model response preview. */
					__( 'OpenRouter did not return image data. Model response: %s', 'smartypress-ai-engine' ),
					mb_substr( $content, 0, 500 )
				),
				'error_code' => 'missing_image_data',
			);
		}

		return array(
			'success'    => false,
			'error'      => $is_edit
				? __( 'OpenRouter returned an empty image edit response. The upstream provider may have blocked the prompt because of its safety policy, or returned an unsupported response format. Try changing the prompt and check the logs for details.', 'smartypress-ai-engine' )
				: __( 'OpenRouter returned an empty image response. The upstream provider may have blocked the prompt because of its safety policy, or returned an unsupported response format. Try changing the prompt and check the logs for details.', 'smartypress-ai-engine' ),
			'error_code' => 'missing_image_data',
		);
	}

	/**
	 * Convert local image file to data URL for OpenRouter image input.
	 *
	 * @param string $image_path Image path.
	 * @return string
	 */
	private function image_file_to_data_url( $image_path ) {
		$content = file_get_contents( $image_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents

		if ( false === $content ) {
			return '';
		}

		$mime = $this->detect_image_mime_type( $image_path );

		return 'data:' . $mime . ';base64,' . base64_encode( $content ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
	}

	/**
	 * Convert resolution to OpenRouter aspect ratio.
	 *
	 * @param string $resolution Resolution.
	 * @param string $model      Model ID.
	 * @return string
	 */
	private function resolution_to_aspect_ratio( $resolution, $model = '' ) {
		$model_data = SmartyPress_AI_Model_Map::get_model( 'openrouter', $model );

		if ( $model_data && empty( $model_data['aspect_ratios'] ) ) {
			$image_models = SmartyPress_AI_Model_Map::get_image_models( 'openrouter' );
			if ( isset( $image_models[ $model ] ) ) {
				$model_data = $image_models[ $model ];
			}
		}

		if ( $model_data && ! empty( $model_data['aspect_ratios'] ) ) {
			$aspect_ratios = (array) $model_data['aspect_ratios'];
			if ( in_array( $resolution, $aspect_ratios, true ) ) {
				return $resolution;
			}

			$resolutions = (array) ( $model_data['resolutions'] ?? array() );
			$index       = array_search( $resolution, $resolutions, true );
			if ( false !== $index && ! empty( $aspect_ratios[ $index ] ) ) {
				return $aspect_ratios[ $index ];
			}

			return $aspect_ratios[0] ?? '1:1';
		}

		$legacy_map = array(
			'1024x1024' => '1:1',
			'1024x1536' => '2:3',
			'1536x1024' => '3:2',
			'832x1248'  => '2:3',
			'1248x832'  => '3:2',
			'864x1184'  => '3:4',
			'1184x864'  => '4:3',
			'896x1152'  => '4:5',
			'1152x896'  => '5:4',
			'768x1344'  => '9:16',
			'1344x768'  => '16:9',
			'1536x672'  => '21:9',
			'1024x1792' => '9:16',
			'1792x1024' => '16:9',
		);

		if ( preg_match( '/^\d+:\d+$/', (string) $resolution ) ) {
			return $resolution;
		}

		return $legacy_map[ $resolution ] ?? '1:1';
	}
}
