<?php
/**
 * Image ALT Generator module.
 *
 * @package SmartyPress_AI_Engine
 */

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

/**
 * Registers REST endpoints and media hooks for Image ALT generation.
 */
class SmartyPress_AI_Image_ALT_Generator_Module extends SmartyPress_AI_Module_Base {

	const SETTINGS_OPTION  = 'smartypress_ai_image_alt_settings';
	const AUTO_CRON_HOOK   = 'smartypress_ai_image_alt_auto_generate';
	const DEFAULT_MODEL    = 'gpt-4o';
	const DEFAULT_PROVIDER = 'openai';

	/**
	 * Service.
	 *
	 * @var SmartyPress_AI_Image_ALT_Service
	 */
	private $service;

	/**
	 * Job manager.
	 *
	 * @var SmartyPress_AI_Image_ALT_Job_Manager
	 */
	private $job_manager;

	/**
	 * Index manager.
	 *
	 * @var SmartyPress_AI_Image_ALT_Index_Manager
	 */
	private $index_manager;

	/**
	 * Constructor.
	 *
	 * @param array $providers Providers.
	 * @param array $settings  Plugin settings.
	 */
	public function __construct( $providers = array(), $settings = array() ) {
		$this->module_id      = 'image_alt_generator';
		$this->module_name    = 'Image ALT Generation';
		$this->module_version = '1.0.0';
		$this->service        = new SmartyPress_AI_Image_ALT_Service();
		$this->index_manager  = new SmartyPress_AI_Image_ALT_Index_Manager();
		$this->job_manager    = new SmartyPress_AI_Image_ALT_Job_Manager( $this->service, $this->index_manager );

		parent::__construct( $providers, $settings );
	}

	/**
	 * Initialize module.
	 *
	 * @return void
	 */
	public function init() {
		if ( ! $this->is_enabled() ) {
			return;
		}

		$this->register_settings();
		$this->register_hooks();

		add_action( 'init', function () {
			add_action( 'rest_api_init', array( $this, 'register_module_rest_routes' ), 20 );
		}, 20 );
	}

	/**
	 * Register module settings.
	 *
	 * @return void
	 */
	public function register_settings() {
		if ( false === get_option( self::SETTINGS_OPTION, false ) ) {
			add_option( self::SETTINGS_OPTION, $this->get_default_settings(), '', false );
		}
	}

	/**
	 * Get module settings.
	 *
	 * @return array
	 */
	public function get_module_settings() {
		return $this->get_settings();
	}

	/**
	 * Register hooks.
	 *
	 * @return void
	 */
	protected function register_hooks() {
		add_action( 'add_attachment', array( $this, 'queue_auto_generation' ) );
		add_action( 'add_attachment', array( $this, 'mark_index_stale_for_attachment' ) );
		add_action( 'delete_attachment', array( $this, 'mark_index_stale_for_attachment' ) );
		add_action( 'save_post_attachment', array( $this, 'mark_index_stale_for_attachment' ) );
		add_action( 'added_post_meta', array( $this, 'mark_index_stale_for_alt_meta' ), 10, 4 );
		add_action( 'updated_post_meta', array( $this, 'mark_index_stale_for_alt_meta' ), 10, 4 );
		add_action( 'deleted_post_meta', array( $this, 'mark_index_stale_for_alt_meta' ), 10, 4 );
		add_action( self::AUTO_CRON_HOOK, array( $this, 'process_auto_generation' ) );
	}

	/**
	 * Register REST API routes.
	 *
	 * @return void
	 */
	public function register_module_rest_routes() {
		register_rest_route(
			'smartypress/v1',
			'/image-alt/attachments',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'get_attachments_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/stats',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'get_stats_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/index/start',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'index_start_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/index/next',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'index_next_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/index/cancel',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'index_cancel_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/index',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'index_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/generate',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'generate_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/bulk/start',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'bulk_start_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/bulk/next',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'bulk_next_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/bulk/cancel',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'bulk_cancel_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/job',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'job_endpoint' ),
				'permission_callback' => array( $this, 'check_upload_permissions' ),
			)
		);

		register_rest_route(
			'smartypress/v1',
			'/image-alt/settings',
			array(
				array(
					'methods'             => 'GET',
					'callback'            => array( $this, 'get_settings_endpoint' ),
					'permission_callback' => array( $this, 'check_upload_permissions' ),
				),
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'save_settings_endpoint' ),
					'permission_callback' => array( $this, 'check_manage_permissions' ),
				),
			)
		);
	}

	/**
	 * Get attachments endpoint.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function get_attachments_endpoint( $request ) {
		return new WP_REST_Response(
			array_merge(
				array( 'success' => true ),
				$this->service->get_attachments(
					array(
						'page'     => $request->get_param( 'page' ),
						'per_page' => $request->get_param( 'per_page' ),
						'filter'   => $request->get_param( 'filter' ),
						'search'   => $request->get_param( 'search' ),
					)
				)
			),
			200
		);
	}

	/**
	 * Stats endpoint.
	 *
	 * @return WP_REST_Response
	 */
	public function get_stats_endpoint() {
		$index = $this->index_manager->get();

		return new WP_REST_Response(
			array(
				'success' => true,
				'stats'   => $index['stats'] ?? array(
					'total'       => 0,
					'with_alt'    => 0,
					'without_alt' => 0,
					'generated'   => 0,
					'failed'      => 0,
				),
				'job'     => $this->job_manager->get(),
				'index'   => $index,
			),
			200
		);
	}

	/**
	 * Start image indexing endpoint.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function index_start_endpoint( $request ) {
		$state = $this->index_manager->start(
			array(
				'filter'     => $request->get_param( 'filter' ) ?: 'missing_alt',
				'search'     => $request->get_param( 'search' ) ?: '',
				'post_types' => $request->get_param( 'post_types' ) ?: array(),
			)
		);

		return new WP_REST_Response(
			array(
				'success' => true,
				'index'   => $state,
			),
			200
		);
	}

	/**
	 * Process next image indexing batch endpoint.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function index_next_endpoint( $request ) {
		$result = $this->index_manager->process_next( $request->get_param( 'batch_size' ) ?: 250 );

		return new WP_REST_Response( $result, ! empty( $result['success'] ) ? 200 : 500 );
	}

	/**
	 * Cancel image indexing endpoint.
	 *
	 * @return WP_REST_Response
	 */
	public function index_cancel_endpoint() {
		return new WP_REST_Response(
			array(
				'success' => true,
				'index'   => $this->index_manager->cancel(),
			),
			200
		);
	}

	/**
	 * Current index endpoint.
	 *
	 * @return WP_REST_Response
	 */
	public function index_endpoint() {
		return new WP_REST_Response(
			array(
				'success'    => true,
				'index'      => $this->index_manager->get(),
				'post_types' => $this->index_manager->get_post_type_options(),
				'default_post_types' => $this->index_manager->get_default_post_types(),
			),
			200
		);
	}

	/**
	 * Generate one image ALT endpoint.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function generate_endpoint( $request ) {
		$attachment_id = absint( $request->get_param( 'attachment_id' ) );
		$settings      = $this->merge_request_settings( $request );
		$vision_provider = 'context' === ( $settings['vision_mode'] ?? 'vision' ) ? null : $this->resolve_vision_provider( $settings );
		$text_provider   = $this->resolve_text_provider( $settings );

		if ( ! $text_provider || ( 'context' !== ( $settings['vision_mode'] ?? 'vision' ) && ! $vision_provider ) ) {
			return $this->rest_error( $this->get_missing_provider_message( $settings ), 400 );
		}

		$result = $this->service->generate_for_attachment( $attachment_id, $vision_provider, $settings, $text_provider );
		$this->index_manager->mark_stale();

		return new WP_REST_Response( $result, ! empty( $result['success'] ) ? 200 : 500 );
	}

	/**
	 * Bulk start endpoint.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function bulk_start_endpoint( $request ) {
		$result = $this->job_manager->start(
			array(
				'filter'     => $request->get_param( 'filter' ) ?: 'missing_alt',
				'search'     => $request->get_param( 'search' ) ?: '',
				'post_types' => $request->get_param( 'post_types' ) ?: array(),
			)
		);

		return new WP_REST_Response( $result, ! empty( $result['success'] ) ? 200 : 400 );
	}

	/**
	 * Bulk process next endpoint.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function bulk_next_endpoint( $request ) {
		$settings = $this->merge_request_settings( $request );
		$vision_provider = 'context' === ( $settings['vision_mode'] ?? 'vision' ) ? null : $this->resolve_vision_provider( $settings );
		$text_provider   = $this->resolve_text_provider( $settings );

		if ( ! $text_provider || ( 'context' !== ( $settings['vision_mode'] ?? 'vision' ) && ! $vision_provider ) ) {
			return $this->rest_error( $this->get_missing_provider_message( $settings ), 400 );
		}

		$result = $this->job_manager->process_next( $vision_provider, $settings, $settings['batch_size'] ?? 1, $text_provider );
		if ( ! empty( $result['results'] ) ) {
			$this->index_manager->mark_stale();
		}

		return new WP_REST_Response( $result, ! empty( $result['success'] ) ? 200 : 500 );
	}

	/**
	 * Bulk cancel endpoint.
	 *
	 * @return WP_REST_Response
	 */
	public function bulk_cancel_endpoint() {
		return new WP_REST_Response(
			array(
				'success' => true,
				'job'     => $this->job_manager->cancel(),
			),
			200
		);
	}

	/**
	 * Current job endpoint.
	 *
	 * @return WP_REST_Response
	 */
	public function job_endpoint() {
		return new WP_REST_Response(
			array(
				'success' => true,
				'job'     => $this->job_manager->get(),
			),
			200
		);
	}

	/**
	 * Get settings endpoint.
	 *
	 * @return WP_REST_Response
	 */
	public function get_settings_endpoint() {
		return new WP_REST_Response(
			array(
				'success'            => true,
				'settings'           => $this->get_settings(),
				'post_types'         => $this->index_manager->get_post_type_options(),
				'default_post_types' => $this->index_manager->get_default_post_types(),
			),
			200
		);
	}

	/**
	 * Save settings endpoint.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response
	 */
	public function save_settings_endpoint( $request ) {
		$settings = $this->sanitize_settings( (array) $request->get_json_params() );
		update_option( self::SETTINGS_OPTION, $settings, false );

		return new WP_REST_Response(
			array(
				'success'  => true,
				'settings' => $settings,
			),
			200
		);
	}

	/**
	 * Queue automatic generation for a new attachment.
	 *
	 * @param int $attachment_id Attachment ID.
	 * @return void
	 */
	public function queue_auto_generation( $attachment_id ) {
		$settings = $this->get_settings();
		if ( empty( $settings['auto_generate_on_upload'] ) || ! wp_attachment_is_image( $attachment_id ) ) {
			return;
		}

		if ( ! wp_next_scheduled( self::AUTO_CRON_HOOK, array( absint( $attachment_id ) ) ) ) {
			wp_schedule_single_event( time() + 30, self::AUTO_CRON_HOOK, array( absint( $attachment_id ) ) );
		}
	}

	/**
	 * Process automatic generation from cron.
	 *
	 * @param int $attachment_id Attachment ID.
	 * @return void
	 */
	public function process_auto_generation( $attachment_id ) {
		$settings = $this->get_settings();
		if ( empty( $settings['auto_generate_on_upload'] ) ) {
			return;
		}

		$vision_provider = 'context' === ( $settings['vision_mode'] ?? 'vision' ) ? null : $this->resolve_vision_provider( $settings );
		$text_provider   = $this->resolve_text_provider( $settings );
		if ( ! $text_provider || ( 'context' !== ( $settings['vision_mode'] ?? 'vision' ) && ! $vision_provider ) ) {
			return;
		}

		$this->service->generate_for_attachment( $attachment_id, $vision_provider, $settings, $text_provider );
		$this->index_manager->mark_stale();
	}

	/**
	 * Mark index stale when attachments are added, changed, or deleted.
	 *
	 * @param int $attachment_id Attachment ID.
	 * @return void
	 */
	public function mark_index_stale_for_attachment( $attachment_id ) {
		if ( $attachment_id && 'attachment' === get_post_type( $attachment_id ) ) {
			$this->index_manager->mark_stale();
		}
	}

	/**
	 * Mark index stale when ALT or module meta changes.
	 *
	 * @param int    $meta_id    Meta ID.
	 * @param int    $object_id  Object ID.
	 * @param string $meta_key   Meta key.
	 * @param mixed  $meta_value Meta value.
	 * @return void
	 */
	public function mark_index_stale_for_alt_meta( $meta_id, $object_id, $meta_key, $meta_value ) {
		unset( $meta_id, $meta_value );

		if ( '_wp_attachment_image_alt' !== $meta_key && 0 !== strpos( (string) $meta_key, 'smartypress_ai_alt_' ) ) {
			return;
		}

		if ( $object_id && 'attachment' === get_post_type( $object_id ) ) {
			$this->index_manager->mark_stale();
		}
	}

	/**
	 * Check media permissions.
	 *
	 * @return bool
	 */
	public function check_upload_permissions() {
		return current_user_can( 'upload_files' );
	}

	/**
	 * Check manage settings permissions.
	 *
	 * @return bool
	 */
	public function check_manage_permissions() {
		return current_user_can( 'manage_options' );
	}

	/**
	 * Resolve provider from settings.
	 *
	 * @param array $settings Settings.
	 * @return SmartyPress_AI_AI_Provider|null
	 */
	private function resolve_vision_provider( array $settings ) {
		$provider_name = $settings['provider'] ?? self::DEFAULT_PROVIDER;
		$provider      = $this->get_provider( $provider_name );

		if ( ! $provider ) {
			return null;
		}

		if ( ! method_exists( $provider, 'describe_image_with_prompt' ) ) {
			return null;
		}

		return $provider;
	}

	/**
	 * Resolve text provider from settings.
	 *
	 * @param array $settings Settings.
	 * @return SmartyPress_AI_AI_Provider|null
	 */
	private function resolve_text_provider( array $settings ) {
		$provider_name = $settings['text_provider'] ?? self::DEFAULT_PROVIDER;
		$provider      = $this->get_provider( $provider_name );

		if ( ! $provider || ! method_exists( $provider, 'generate_content' ) ) {
			return null;
		}

		return $provider;
	}

	/**
	 * Return provider configuration error for current generation mode.
	 *
	 * @param array $settings Settings.
	 * @return string
	 */
	private function get_missing_provider_message( array $settings ) {
		if ( ! $this->resolve_text_provider( $settings ) ) {
			return __( 'No text AI provider is available for context-only ALT generation. Please configure a text provider in Settings.', 'smartypress-ai-engine' );
		}

		return __( 'No vision-capable AI provider is available. Please configure OpenAI, OpenRouter, or Claude in Settings.', 'smartypress-ai-engine' );
	}

	/**
	 * Merge saved settings with request overrides.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return array
	 */
	private function merge_request_settings( $request ) {
		$settings = $this->get_settings();
		$params   = (array) $request->get_json_params();

		if ( empty( $params ) ) {
			$params = $request->get_params();
		}

		return $this->sanitize_settings( array_merge( $settings, $params['settings'] ?? $params ) );
	}

	/**
	 * Get saved settings with defaults.
	 *
	 * @return array
	 */
	private function get_settings() {
		$settings = get_option( self::SETTINGS_OPTION, array() );
		return $this->sanitize_settings( is_array( $settings ) ? $settings : array() );
	}

	/**
	 * Default settings.
	 *
	 * @return array
	 */
	private function get_default_settings() {
		$provider      = $this->get_default_supported_provider_name();
		$text_provider = $this->get_default_text_provider_name();

		return array(
			'vision_mode'             => 'vision',
			'provider'                => $provider,
			'model'                   => $this->get_default_model_for_provider( $provider ),
			'text_provider'           => $text_provider,
			'text_model'              => $this->get_default_text_model_for_provider( $text_provider ),
			'language'                => function_exists( 'smartypress_ai_get_language_by_locale' ) ? smartypress_ai_get_language_by_locale() : get_locale(),
			'customLanguage'          => '',
			'generate_alt'            => true,
			'generate_caption'        => true,
			'generate_title'          => false,
			'generate_description'    => false,
			'preserve_existing_alt'   => true,
			'include_seo_keyword'     => true,
			'manual_focus_keyword'    => '',
			'poor_context_prompt'     => 'Generate cautious metadata for images with weak context. Use only the available filename, attachment title, site name and language. Keep wording generic when the image content is unclear.',
			'max_alt_length'          => 125,
			'batch_size'              => 1,
			'auto_generate_on_upload' => false,
		);
	}

	/**
	 * Sanitize settings.
	 *
	 * @param array $settings Raw settings.
	 * @return array
	 */
	private function sanitize_settings( array $settings ) {
		$defaults = $this->get_default_settings();
		$settings = wp_parse_args( $settings, $defaults );
		$vision_mode   = 'context' === sanitize_key( $settings['vision_mode'] ) ? 'context' : 'vision';
		$provider      = sanitize_key( $settings['provider'] );
		$model         = sanitize_text_field( $settings['model'] );
		$text_provider = sanitize_key( $settings['text_provider'] );
		$text_model    = sanitize_text_field( $settings['text_model'] );

		if ( ! $this->is_supported_provider( $provider ) ) {
			$provider = $this->get_default_supported_provider_name();
			$model    = $this->get_default_model_for_provider( $provider );
		} elseif ( ! $this->is_supported_model( $provider, $model ) ) {
			$model = $this->get_default_model_for_provider( $provider );
		}

		if ( ! $this->is_text_provider( $text_provider ) ) {
			$text_provider = $this->get_default_text_provider_name();
			$text_model    = $this->get_default_text_model_for_provider( $text_provider );
		} elseif ( ! $this->is_text_model( $text_provider, $text_model ) ) {
			$text_model = $this->get_default_text_model_for_provider( $text_provider );
		}

		return array(
			'vision_mode'             => $vision_mode,
			'provider'                => $provider,
			'model'                   => $model,
			'text_provider'           => $text_provider,
			'text_model'              => $text_model,
			'language'                => sanitize_text_field( $settings['language'] ),
			'customLanguage'          => sanitize_text_field( $settings['customLanguage'] ?? '' ),
			'generate_alt'            => (bool) $settings['generate_alt'],
			'generate_caption'        => (bool) $settings['generate_caption'],
			'generate_title'          => (bool) $settings['generate_title'],
			'generate_description'    => (bool) $settings['generate_description'],
			'preserve_existing_alt'   => (bool) $settings['preserve_existing_alt'],
			'include_seo_keyword'     => (bool) $settings['include_seo_keyword'],
			'manual_focus_keyword'    => sanitize_text_field( $settings['manual_focus_keyword'] ?? '' ),
			'poor_context_prompt'     => sanitize_textarea_field( $settings['poor_context_prompt'] ?? '' ),
			'max_alt_length'          => max( 40, min( 300, absint( $settings['max_alt_length'] ) ) ),
			'batch_size'              => max( 1, min( 5, absint( $settings['batch_size'] ) ) ),
			'auto_generate_on_upload' => (bool) $settings['auto_generate_on_upload'],
		);
	}

	/**
	 * Check whether provider supports image analysis.
	 *
	 * @param string $provider_name Provider name.
	 * @return bool
	 */
	private function is_supported_provider( $provider_name ) {
		$provider = SmartyPress_AI_Providers::get_provider( $provider_name );
		return $provider && method_exists( $provider, 'describe_image_with_prompt' );
	}

	/**
	 * Get first configured provider that supports image analysis.
	 *
	 * @return string
	 */
	private function get_default_supported_provider_name() {
		$providers = SmartyPress_AI_Providers::get_all();

		if ( isset( $providers[ self::DEFAULT_PROVIDER ] ) && method_exists( $providers[ self::DEFAULT_PROVIDER ], 'describe_image_with_prompt' ) ) {
			return self::DEFAULT_PROVIDER;
		}

		foreach ( $providers as $provider_name => $provider ) {
			if ( method_exists( $provider, 'describe_image_with_prompt' ) ) {
				return $provider_name;
			}
		}

		return self::DEFAULT_PROVIDER;
	}

	/**
	 * Get first configured text provider.
	 *
	 * @return string
	 */
	private function get_default_text_provider_name() {
		$providers = SmartyPress_AI_Providers::get_all();

		if ( isset( $providers[ self::DEFAULT_PROVIDER ] ) && method_exists( $providers[ self::DEFAULT_PROVIDER ], 'generate_content' ) ) {
			return self::DEFAULT_PROVIDER;
		}

		foreach ( $providers as $provider_name => $provider ) {
			if ( method_exists( $provider, 'generate_content' ) ) {
				return $provider_name;
			}
		}

		return self::DEFAULT_PROVIDER;
	}

	/**
	 * Check whether provider can generate text.
	 *
	 * @param string $provider_name Provider name.
	 * @return bool
	 */
	private function is_text_provider( $provider_name ) {
		$provider = SmartyPress_AI_Providers::get_provider( $provider_name );
		return $provider && method_exists( $provider, 'generate_content' ) && method_exists( $provider, 'get_available_models' );
	}

	/**
	 * Get default text model for provider.
	 *
	 * @param string $provider_name Provider name.
	 * @return string
	 */
	private function get_default_text_model_for_provider( $provider_name ) {
		$models = $this->get_text_models_for_provider( $provider_name );
		if ( ! empty( $models ) ) {
			return (string) array_key_first( $models );
		}

		return self::DEFAULT_MODEL;
	}

	/**
	 * Check whether text model exists for provider.
	 *
	 * @param string $provider_name Provider name.
	 * @param string $model         Model ID.
	 * @return bool
	 */
	private function is_text_model( $provider_name, $model ) {
		$models = $this->get_text_models_for_provider( $provider_name );
		return isset( $models[ $model ] );
	}

	/**
	 * Get text models for provider.
	 *
	 * @param string $provider_name Provider name.
	 * @return array
	 */
	private function get_text_models_for_provider( $provider_name ) {
		$provider = SmartyPress_AI_Providers::get_provider( $provider_name );
		if ( ! $provider || ! method_exists( $provider, 'get_available_models' ) ) {
			return array();
		}

		return $provider->get_available_models();
	}

	/**
	 * Get default vision-capable model for provider.
	 *
	 * @param string $provider_name Provider name.
	 * @return string
	 */
	private function get_default_model_for_provider( $provider_name ) {
		$models = $this->get_supported_models_for_provider( $provider_name );
		if ( ! empty( $models ) ) {
			return (string) array_key_first( $models );
		}

		return self::DEFAULT_MODEL;
	}

	/**
	 * Check whether model can analyze images and return text.
	 *
	 * @param string $provider_name Provider name.
	 * @param string $model         Model ID.
	 * @return bool
	 */
	private function is_supported_model( $provider_name, $model ) {
		$models = $this->get_supported_models_for_provider( $provider_name );
		return isset( $models[ $model ] );
	}

	/**
	 * Get image-input text models for the provider.
	 *
	 * @param string $provider_name Provider name.
	 * @return array
	 */
	private function get_supported_models_for_provider( $provider_name ) {
		$provider = SmartyPress_AI_Providers::get_provider( $provider_name );
		if ( ! $provider || ! method_exists( $provider, 'get_available_models' ) ) {
			return array();
		}

		$models = $provider->get_available_models();

		if ( 'openai' === $provider_name ) {
			$vision_models = array( 'gpt-5.3-codex', 'gpt-5.2', 'gpt-5.1', 'gpt-5', 'gpt-4.1', 'gpt-4o', 'gpt-4o-mini', 'gpt-4o-2024-11-20' );
			return array_intersect_key( $models, array_flip( $vision_models ) );
		}

		if ( 'openrouter' === $provider_name ) {
			return array_filter(
				$models,
				function( $model ) {
					$capabilities = is_array( $model['capabilities'] ?? null ) ? $model['capabilities'] : array();
					return ! empty( $capabilities['image_input'] ) && ! empty( $capabilities['text_output'] );
				}
			);
		}

		if ( 'anthropic' === $provider_name ) {
			return array_filter(
				$models,
				function( $model ) {
					$capabilities = is_array( $model['capabilities'] ?? null ) ? $model['capabilities'] : array();
					return ! empty( $capabilities['image_input'] ) && ! empty( $capabilities['text_output'] );
				}
			);
		}

		return array();
	}

	/**
	 * REST error helper.
	 *
	 * @param string $message Message.
	 * @param int    $status  HTTP status.
	 * @return WP_REST_Response
	 */
	private function rest_error( $message, $status ) {
		return new WP_REST_Response(
			array(
				'success' => false,
				'error'   => $message,
			),
			$status
		);
	}
}
