<?php
/**
 * OpenAI vision model registry for AI Alt Text Generator.
 *
 * @package AIAltTextGenerator
 * @since 1.3.0
 */

namespace AIAltTextGenerator;

use WP_PowerSuite\AI\OpenAiCompatibleClient;

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

require_once __DIR__ . '/dependencies.php';

/**
 * Vision-capable OpenAI models for alt text generation.
 *
 * @since 1.3.0
 */
class Models {

	/**
	 * Default model ID.
	 */
	public const DEFAULT = 'gpt-4o-mini';

	/**
	 * Retired model IDs mapped to the closest current replacement.
	 *
	 * @var array<string, string>
	 */
	private const RETIRED_MAP = [
		'gpt-4-turbo'            => 'gpt-4.1',
		'gpt-4-turbo-2024-04-09' => 'gpt-4.1',
		'gpt-4o-2024-05-13'      => 'gpt-4o',
		'gpt-4-vision-preview'   => 'gpt-4o-mini',
		'gpt-5-nano'             => 'gpt-4.1-nano',
		'gpt-5'                  => 'gpt-5.4',
	];

	/**
	 * Grouped models for the settings dropdown.
	 *
	 * @param array<string, mixed> $settings Optional module settings for context-aware ordering.
	 * @return array<string, array{label: string, models: array<string, string>}>
	 */
	public static function getGroups( array $settings = [] ): array {
		$groups = [
			'budget' => [
				'label'  => __( 'Budget (bulk uploads)', 'wp-powersuite' ),
				'models' => [
					'gpt-4o-mini'  => __( 'GPT-4o Mini — best default for concise alt text', 'wp-powersuite' ),
					'gpt-4.1-nano' => __( 'GPT-4.1 Nano — lowest cost for large libraries', 'wp-powersuite' ),
				],
			],
			'balanced' => [
				'label'  => __( 'Balanced', 'wp-powersuite' ),
				'models' => [
					'gpt-4.1-mini' => __( 'GPT-4.1 Mini — sharper descriptions, still efficient', 'wp-powersuite' ),
					'gpt-5-mini'   => __( 'GPT-5 Mini — strong quality for WooCommerce and SEO context', 'wp-powersuite' ),
					'gpt-5.4-mini' => __( 'GPT-5.4 Mini — fast with improved vision accuracy', 'wp-powersuite' ),
				],
			],
			'premium' => [
				'label'  => __( 'Premium (complex images)', 'wp-powersuite' ),
				'models' => [
					'gpt-4.1' => __( 'GPT-4.1 — detailed or SEO-focused alt text', 'wp-powersuite' ),
					'gpt-4o'  => __( 'GPT-4o — highest fidelity for busy scenes', 'wp-powersuite' ),
					'gpt-5.4' => __( 'GPT-5.4 — advanced reasoning for complex images', 'wp-powersuite' ),
					'gpt-5.5' => __( 'GPT-5.5 — highest quality descriptions', 'wp-powersuite' ),
					'o4-mini' => __( 'o4-mini — screenshots and images with small text', 'wp-powersuite' ),
				],
			],
		];

		/**
		 * Filter grouped OpenAI vision models shown in AI Alt Text settings.
		 *
		 * @param array<string, array{label: string, models: array<string, string>}> $groups   Model groups.
		 * @param array<string, mixed>                                              $settings Module settings context.
		 */
		$groups = apply_filters( 'wp_powersuite_ai_alt_text_model_groups', $groups, $settings );

		return self::orderGroupsForSettings( $groups, $settings );
	}

	/**
	 * Flat list of all known model IDs => labels.
	 *
	 * @param array<string, mixed> $settings Optional module settings.
	 * @return array<string, string>
	 */
	public static function getList( array $settings = [] ): array {
		$list = [];
		foreach ( self::getGroups( $settings ) as $group ) {
			if ( ! isset( $group['models'] ) || ! is_array( $group['models'] ) ) {
				continue;
			}
			foreach ( $group['models'] as $id => $label ) {
				$list[ (string) $id ] = (string) $label;
			}
		}

		/**
		 * Filter flat OpenAI vision model list for AI Alt Text.
		 *
		 * @param array<string, string> $list     Model ID => label.
		 * @param array<string, mixed>  $settings Module settings context.
		 */
		return apply_filters( 'wp_powersuite_ai_alt_text_models', $list, $settings );
	}

	/**
	 * Settings-aware helper text for the model field.
	 *
	 * @param array<string, mixed> $settings Module settings.
	 */
	public static function getSettingsHint( array $settings ): string {
		$auto_on_upload = ! empty( $settings['auto_on_upload'] );
		$prompt_style   = PromptStyles::sanitize( (string) ( $settings['prompt_style'] ?? 'concise' ) );
		$woo_or_seo     = ! empty( $settings['use_woo_context'] ) || ! empty( $settings['use_seo_keyphrase'] );

		if ( $auto_on_upload ) {
			return __( 'Auto-generate on upload works best with Budget models. GPT-4o Mini is the recommended default.', 'wp-powersuite' );
		}

		if ( PromptStyles::needsQualityModel( $prompt_style ) || $woo_or_seo ) {
			return __( 'Detailed, SEO, marketing, or context-rich styles benefit from Balanced or Premium models.', 'wp-powersuite' );
		}

		return __( 'GPT-4o Mini is ideal for everyday concise alt text. Upgrade to Balanced or Premium when images are complex or keyword-rich.', 'wp-powersuite' );
	}

	/**
	 * Sanitize a stored model ID; maps retired models and falls back to default when unknown.
	 *
	 * Model IDs contain dots and digits (e.g. gpt-4.1-nano) and must not pass through sanitize_key().
	 */
	public static function sanitize( string $model, array $settings = [] ): string {
		$model = trim( sanitize_text_field( $model ) );
		if ( $model === '' ) {
			return self::DEFAULT;
		}

		if ( isset( self::RETIRED_MAP[ $model ] ) ) {
			$model = self::RETIRED_MAP[ $model ];
		}

		$allowed = array_keys( self::getList( $settings ) );
		if ( in_array( $model, $allowed, true ) ) {
			return $model;
		}

		foreach ( $allowed as $id ) {
			if ( strcasecmp( $model, $id ) === 0 ) {
				return $id;
			}
		}

		return self::DEFAULT;
	}

	/**
	 * Whether the model expects max_completion_tokens instead of max_tokens.
	 */
	public static function usesMaxCompletionTokens( string $model ): bool {
		$model = self::sanitize( $model );

		/**
		 * Filter whether an AI Alt Text model uses max_completion_tokens.
		 *
		 * @param bool   $uses     Default detection result.
		 * @param string $model_id Sanitized model ID.
		 */
		return (bool) apply_filters(
			'wp_powersuite_ai_alt_text_uses_max_completion_tokens',
			OpenAiCompatibleClient::usesMaxCompletionTokens( $model ),
			$model
		);
	}

	/**
	 * Append the correct token limit key for the Chat Completions API.
	 *
	 * @param array<string, mixed> $body       Request body (must include model).
	 * @param int                  $max_tokens Token budget for the completion.
	 * @return array<string, mixed>
	 */
	public static function withTokenLimit( array $body, int $max_tokens ): array {
		$model = isset( $body['model'] ) ? (string) $body['model'] : self::DEFAULT;
		$key   = self::usesMaxCompletionTokens( $model ) ? 'max_completion_tokens' : 'max_tokens';
		$body[ $key ] = max( 1, $max_tokens );

		return $body;
	}

	/**
	 * Put the most relevant model group first based on module settings.
	 *
	 * @param array<string, array{label: string, models: array<string, string>}> $groups
	 * @param array<string, mixed>                                              $settings
	 * @return array<string, array{label: string, models: array<string, string>}>
	 */
	private static function orderGroupsForSettings( array $groups, array $settings ): array {
		$auto_on_upload = ! empty( $settings['auto_on_upload'] );
		$prompt_style   = PromptStyles::sanitize( (string) ( $settings['prompt_style'] ?? 'concise' ) );
		$needs_quality  = PromptStyles::needsQualityModel( $prompt_style )
			|| ! empty( $settings['use_woo_context'] )
			|| ! empty( $settings['use_seo_keyphrase'] );

		$order = $auto_on_upload
			? [ 'budget', 'balanced', 'premium' ]
			: ( $needs_quality ? [ 'balanced', 'premium', 'budget' ] : [ 'budget', 'balanced', 'premium' ] );

		$ordered = [];
		foreach ( $order as $key ) {
			if ( isset( $groups[ $key ] ) ) {
				$ordered[ $key ] = $groups[ $key ];
			}
		}

		foreach ( $groups as $key => $group ) {
			if ( ! isset( $ordered[ $key ] ) ) {
				$ordered[ $key ] = $group;
			}
		}

		return $ordered;
	}
}
