<?php
/**
 * OpenAI vision provider.
 *
 * @package AIAltTextGenerator
 * @since 1.4.0
 */

namespace AIAltTextGenerator\Providers;

use AIAltTextGenerator\Models;
use AIAltTextGenerator\Settings;

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

/**
 * @since 1.4.0
 */
class OpenAIProvider implements ProviderInterface {

	private const API_URL = 'https://api.openai.com/v1/chat/completions';

	/** @var list<string> */
	private const SUPPORTED_MIMES = [
		'image/jpeg',
		'image/jpg',
		'image/png',
		'image/gif',
		'image/webp',
		'image/avif',
		'image/bmp',
		'image/tiff',
		'image/x-icon',
		'image/vnd.microsoft.icon',
	];

	private const SVG_MIME = 'image/svg+xml';
	private const MAX_FILE_SIZE = 20971520;
	private const API_MAX_SIZE  = 4194304;
	private const MAX_DIMENSION = 2048;

	public function getId(): string {
		return 'openai';
	}

	public function getLabel(): string {
		return 'OpenAI';
	}

	public function supportsVision(): bool {
		return true;
	}

	/**
	 * @param array<string, mixed> $options
	 * @return array{alt_text: string, tokens: int}|\WP_Error
	 */
	public function generate( string $file_path, array $options = [] ): array|\WP_Error {
		$api_key = (string) ( $options['api_key'] ?? Settings::getApiKey() );
		if ( $api_key === '' ) {
			return new \WP_Error( 'missing_api_key', __( 'OpenAI API key is not configured.', 'wp-powersuite' ) );
		}

		$model        = Models::sanitize( (string) ( $options['model'] ?? Models::DEFAULT ) );
		$max_chars    = (int) ( $options['max_chars'] ?? 125 );
		$prompt_style = (string) ( $options['prompt_style'] ?? 'concise' );
		$language     = (string) ( $options['language'] ?? '' );
		$custom_prompt = trim( (string) ( $options['custom_prompt'] ?? '' ) );
		$context      = is_array( $options['context'] ?? null ) ? $options['context'] : [];

		if ( ! is_file( $file_path ) ) {
			return new \WP_Error( 'file_not_found', __( 'Image file not found.', 'wp-powersuite' ) );
		}

		$attachment_id = max( 0, (int) ( $options['attachment_id'] ?? 0 ) );
		$mime          = VisionHelper::detectMime( $file_path, $attachment_id );

		if ( $mime === VisionHelper::SVG_MIME ) {
			return $this->generateForSvg( $file_path, $api_key, $model, $max_chars, $prompt_style, $custom_prompt, $language, $context, $options );
		}

		if ( ! VisionHelper::isProcessableMime( $mime ) ) {
			return new \WP_Error( 'unsupported_format', __( 'Unsupported format. Use JPG/JPEG, PNG, GIF, WebP, AVIF, BMP, TIFF, or SVG.', 'wp-powersuite' ) );
		}

		$size = filesize( $file_path );
		if ( $size === false || $size > self::MAX_FILE_SIZE ) {
			return new \WP_Error( 'file_too_large', __( 'Image exceeds 20MB limit.', 'wp-powersuite' ) );
		}

		$max_dimension = max( 0, (int) ( $options['max_dimension'] ?? 0 ) );
		$force_jpeg    = ! empty( $options['force_jpeg'] );
		$needs_convert = $force_jpeg && (
			! in_array( $mime, [ 'image/jpeg', 'image/jpg', 'image/png' ], true )
			|| $size > 400000
		);

		if ( $needs_convert ) {
			$image = VisionHelper::convertToApiPayload(
				$file_path,
				'image/jpeg',
				$max_dimension > 0 ? $max_dimension : VisionHelper::BULK_MAX_DIMENSION
			);
		} else {
			$image = VisionHelper::getApiImagePayload( $file_path, $mime, null, $max_dimension );
		}
		if ( is_wp_error( $image ) ) {
			return $image;
		}

		$image_url     = 'data:' . $image['mime'] . ';base64,' . $image['data'];
		$system_prompt = VisionHelper::buildSystemPrompt( $max_chars, $prompt_style, $language, $custom_prompt, $context );

		/**
		 * Filter the system prompt before sending to the AI provider.
		 *
		 * @param string               $system_prompt Prompt text.
		 * @param int                  $attachment_id Attachment ID.
		 * @param array<string, mixed> $context       Generation context.
		 */
		$system_prompt = (string) apply_filters( 'wpps_ai_alt_text_prompt', $system_prompt, $attachment_id, $context );

		$body = Models::withTokenLimit(
			[
				'model'    => $model,
				'messages' => [
					[ 'role' => 'system', 'content' => $system_prompt ],
					[
						'role'    => 'user',
						'content' => [
							[
								'type'      => 'image_url',
								'image_url' => [
									'url'    => $image_url,
									'detail' => 'low',
								],
							],
						],
					],
				],
			],
			150
		);

		return $this->request(
			$api_key,
			$body,
			$max_chars,
			$model,
			max( 10, min( 180, (int) ( $options['timeout'] ?? 90 ) ) )
		);
	}

	/**
	 * @param array<string, mixed> $options
	 * @return true|\WP_Error
	 */
	public function testConnection( array $options = [] ): bool|\WP_Error {
		$api_key = (string) ( $options['api_key'] ?? Settings::getApiKey() );
		if ( $api_key === '' ) {
			return new \WP_Error( 'missing_api_key', __( 'OpenAI API key is not configured.', 'wp-powersuite' ) );
		}

		$response = wp_remote_get(
			'https://api.openai.com/v1/models',
			[
				'timeout' => 20,
				'headers' => [
					'Authorization' => 'Bearer ' . $api_key,
				],
			]
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$code = wp_remote_retrieve_response_code( $response );
		if ( $code !== 200 ) {
			$decoded = json_decode( (string) wp_remote_retrieve_body( $response ), true );
			$message = is_array( $decoded ) ? ( $decoded['error']['message'] ?? __( 'API connection failed.', 'wp-powersuite' ) ) : __( 'API connection failed.', 'wp-powersuite' );
			return new \WP_Error( 'api_error', (string) $message );
		}

		return true;
	}

	/**
	 * @param array<string, mixed> $context
	 * @return array{alt_text: string, tokens: int}|\WP_Error
	 */
	private function generateForSvg( string $file_path, string $api_key, string $model, int $max_chars, string $prompt_style, string $custom_prompt, string $language, array $context, array $options ): array|\WP_Error {
		$user_content = VisionHelper::readSvgUserContent( $file_path );
		if ( is_wp_error( $user_content ) ) {
			return $user_content;
		}

		$system_prompt = VisionHelper::buildSystemPrompt( $max_chars, $prompt_style, $language, $custom_prompt, $context );
		$system_prompt .= VisionHelper::svgPromptSuffix();
		$attachment_id = max( 0, (int) ( $options['attachment_id'] ?? 0 ) );
		$system_prompt = (string) apply_filters( 'wpps_ai_alt_text_prompt', $system_prompt, $attachment_id, $context );

		$body = Models::withTokenLimit(
			[
				'model'    => $model,
				'messages' => [
					[ 'role' => 'system', 'content' => $system_prompt ],
					[ 'role' => 'user', 'content' => $user_content ],
				],
			],
			100
		);

		return $this->request(
			$api_key,
			$body,
			$max_chars,
			$model,
			max( 10, min( 180, (int) ( $options['timeout'] ?? 90 ) ) )
		);
	}

	/**
	 * @param array<string, mixed> $body
	 * @return array{alt_text: string, tokens: int}|\WP_Error
	 */
	private function request( string $api_key, array $body, int $max_chars, string $model, int $timeout = 90 ): array|\WP_Error {
		$response = wp_remote_post(
			self::API_URL,
			[
				'timeout' => max( 10, min( 180, $timeout ) ),
				'headers' => [
					'Authorization' => 'Bearer ' . $api_key,
					'Content-Type'  => 'application/json',
				],
				'body' => wp_json_encode( $body ),
			]
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$code    = wp_remote_retrieve_response_code( $response );
		$decoded = json_decode( (string) wp_remote_retrieve_body( $response ), true );

		if ( $code !== 200 || ! is_array( $decoded ) ) {
			$message = is_array( $decoded ) ? ( $decoded['error']['message'] ?? __( 'Unknown API error.', 'wp-powersuite' ) ) : __( 'Unknown API error.', 'wp-powersuite' );
			$classified = \AIAltTextGenerator\GenerationError::classify( 'api_error', (string) $message, (int) $code );

			return new \WP_Error(
				$classified['code'],
				$classified['message'],
				[ 'status' => (int) $code ]
			);
		}

		$content = trim( (string) ( $decoded['choices'][0]['message']['content'] ?? '' ) );
		if ( $content === '' ) {
			return new \WP_Error( 'empty_response', __( 'AI returned empty alt text.', 'wp-powersuite' ) );
		}

		if ( $max_chars > 0 && \AIAltTextGenerator\Text::length( $content ) > $max_chars ) {
			$content = \AIAltTextGenerator\Text::slice( $content, 0, $max_chars - 1 ) . '…';
		}

		$tokens = (int) ( $decoded['usage']['total_tokens'] ?? 0 );

		return [
			'alt_text' => sanitize_text_field( $content ),
			'tokens'   => $tokens,
		];
	}
}
