<?php
/**
 * Shared vision provider utilities.
 *
 * @package AIAltTextGenerator
 * @since 1.6.0
 */

namespace AIAltTextGenerator\Providers;

use AIAltTextGenerator\Languages;
use AIAltTextGenerator\PromptStyles;

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

/**
 * @since 1.6.0
 */
class VisionHelper {

	/** @var list<string> */
	public const SUPPORTED_MIMES = [
		'image/jpeg',
		'image/jpg',
		'image/png',
		'image/gif',
		'image/webp',
		'image/avif',
		'image/bmp',
		'image/tiff',
	];

	public const SVG_MIME       = 'image/svg+xml';
	public const MAX_FILE_SIZE  = 20971520;
	public const API_MAX_SIZE   = 4194304;
	public const MAX_DIMENSION  = 2048;
	/** Faster/safer ceiling for shared-host bulk AJAX vision calls. */
	public const BULK_MAX_DIMENSION = 768;
	/** Resize when originals are still large even under API_MAX_SIZE. */
	public const PREFERRED_MAX_BYTES = 750000;

	/** MIME types accepted by OpenAI, Claude, and most vision APIs for inline images. */
	public const API_VISION_MIMES = [
		'image/jpeg',
		'image/png',
		'image/gif',
		'image/webp',
	];

	/** @var array<string, string> */
	private const EXTENSION_MIMES = [
		'jpg'   => 'image/jpeg',
		'jpeg'  => 'image/jpeg',
		'png'   => 'image/png',
		'gif'   => 'image/gif',
		'webp'  => 'image/webp',
		'avif'  => 'image/avif',
		'bmp'   => 'image/bmp',
		'tiff'  => 'image/tiff',
		'tif'   => 'image/tiff',
		'svg'   => 'image/svg+xml',
		'svgz'  => 'image/svg+xml',
	];

	public static function detectMime( string $file_path, int $attachment_id = 0 ): string {
		$from_file = wp_check_filetype( $file_path, null )['type'] ?? '';
		if ( is_string( $from_file ) && $from_file !== '' ) {
			return self::normalizeMime( $from_file );
		}

		if ( $attachment_id > 0 ) {
			$post_mime = get_post_mime_type( $attachment_id );
			if ( is_string( $post_mime ) && $post_mime !== '' ) {
				return self::normalizeMime( $post_mime );
			}
		}

		$extension = strtolower( pathinfo( $file_path, PATHINFO_EXTENSION ) );

		return self::normalizeMime( self::EXTENSION_MIMES[ $extension ] ?? '' );
	}

	public static function isProcessableMime( string $mime ): bool {
		$mime = self::normalizeMime( $mime );

		return $mime === self::SVG_MIME || in_array( $mime, self::SUPPORTED_MIMES, true );
	}

	public static function normalizeMime( string $mime ): string {
		if ( $mime === 'image/jpg' || $mime === 'image/pjpeg' ) {
			return 'image/jpeg';
		}

		return $mime;
	}

	public static function normalizeMimeForDataUrl( string $mime ): string {
		return self::normalizeMime( $mime );
	}

	/**
	 * Prefer a WordPress intermediate size so bulk AJAX avoids loading multi‑MB originals.
	 */
	public static function resolveFileForVision( int $attachment_id, string $fallback_path ): string {
		if ( $attachment_id <= 0 || $fallback_path === '' || ! is_file( $fallback_path ) ) {
			return $fallback_path;
		}

		$meta = wp_get_attachment_metadata( $attachment_id );
		if ( ! is_array( $meta ) || empty( $meta['sizes'] ) || ! is_array( $meta['sizes'] ) ) {
			return $fallback_path;
		}

		$dir = dirname( $fallback_path );
		foreach ( [ 'large', 'medium_large', 'medium' ] as $size ) {
			$file = isset( $meta['sizes'][ $size ]['file'] ) ? (string) $meta['sizes'][ $size ]['file'] : '';
			if ( $file === '' ) {
				continue;
			}
			$candidate = path_join( $dir, $file );
			if ( is_readable( $candidate ) ) {
				return $candidate;
			}
		}

		return $fallback_path;
	}

	/**
	 * @param list<string>|null $api_mimes
	 * @return array{data: string, mime: string}|\WP_Error
	 */
	public static function getApiImagePayload( string $file_path, string $mime, ?array $api_mimes = null, int $max_dimension = 0 ): array|\WP_Error {
		$api_mimes     = $api_mimes ?? self::API_VISION_MIMES;
		$mime          = self::normalizeMime( $mime );
		$max_dimension = $max_dimension > 0 ? $max_dimension : self::MAX_DIMENSION;

		if ( in_array( $mime, $api_mimes, true ) ) {
			$payload = self::getImagePayload( $file_path, $mime, $max_dimension );
			if ( is_wp_error( $payload ) ) {
				return $payload;
			}

			$payload['mime'] = self::normalizeMimeForDataUrl( (string) $payload['mime'] );

			return $payload;
		}

		return self::convertToApiPayload( $file_path, 'image/jpeg', $max_dimension );
	}

	/**
	 * @return array{data: string, mime: string}|\WP_Error
	 */
	public static function convertToApiPayload( string $file_path, string $target_mime = 'image/jpeg', int $max_dimension = 0 ): array|\WP_Error {
		$max_dimension = $max_dimension > 0 ? $max_dimension : self::MAX_DIMENSION;

		if ( ! function_exists( 'wp_get_image_editor' ) ) {
			require_once ABSPATH . 'wp-admin/includes/image.php';
		}

		$editor = wp_get_image_editor( $file_path );
		if ( ! $editor || is_wp_error( $editor ) ) {
			return new \WP_Error(
				'no_editor',
				__( 'Could not open this image for conversion. AVIF and similar formats need GD or ImageMagick with format support on the server.', 'wp-powersuite' )
			);
		}

		if ( is_wp_error( $editor->load() ) ) {
			return new \WP_Error( 'load_failed', __( 'Could not load image for conversion.', 'wp-powersuite' ) );
		}

		$size = $editor->get_size();
		if ( $size && ! empty( $size['width'] ) && ! empty( $size['height'] ) ) {
			$scale = min( $max_dimension / $size['width'], $max_dimension / $size['height'], 1.0 );
			if ( $scale < 1.0 ) {
				$editor->resize(
					max( 1, (int) round( $size['width'] * $scale ) ),
					max( 1, (int) round( $size['height'] * $scale ) ),
					false
				);
			}
		}

		$extension = $target_mime === 'image/webp' ? 'webp' : 'jpg';
		$saved     = $editor->save( null, $extension );
		if ( is_wp_error( $saved ) || ! isset( $saved['path'] ) || ! is_file( $saved['path'] ) ) {
			return new \WP_Error( 'convert_failed', __( 'Could not convert image to a supported format.', 'wp-powersuite' ) );
		}

		$data = file_get_contents( $saved['path'] );
		@unlink( $saved['path'] );

		if ( $data === false ) {
			return new \WP_Error( 'read_failed', __( 'Could not read converted image.', 'wp-powersuite' ) );
		}

		$encoded = base64_encode( $data );
		if ( $encoded === false ) {
			return new \WP_Error( 'encode_failed', __( 'Could not encode image.', 'wp-powersuite' ) );
		}

		$out_mime = isset( $saved['mime-type'] ) ? (string) $saved['mime-type'] : $target_mime;

		return [
			'data' => $encoded,
			'mime' => self::normalizeMimeForDataUrl( $out_mime ),
		];
	}

	public static function readSvgUserContent( string $file_path ): string|\WP_Error {
		$svg_content = file_get_contents( $file_path );
		if ( $svg_content === false || strlen( $svg_content ) > 50000 ) {
			return new \WP_Error( 'svg_read_failed', __( 'Could not read SVG or file too large.', 'wp-powersuite' ) );
		}

		$hints = [];
		if ( preg_match( '/<title[^>]*>([^<]+)<\/title>/i', $svg_content, $matches ) ) {
			$hints[] = 'Title: ' . trim( $matches[1] );
		}
		if ( preg_match( '/<desc[^>]*>([^<]+)<\/desc>/i', $svg_content, $matches ) ) {
			$hints[] = 'Description: ' . trim( $matches[1] );
		}
		if ( preg_match( '/aria-label=["\']([^"\']+)["\']/i', $svg_content, $matches ) ) {
			$hints[] = 'Aria-label: ' . trim( $matches[1] );
		}

		if ( ! empty( $hints ) ) {
			return implode( '. ', $hints );
		}

		return substr( strip_tags( $svg_content ), 0, 3000 );
	}

	public static function svgPromptSuffix(): string {
		return ' ' . __( 'The user will provide SVG markup or text. Generate concise alt text based on it.', 'wp-powersuite' );
	}

	/**
	 * @param array<string, mixed> $context
	 */
	public static function buildSystemPrompt( int $max_chars, string $prompt_style, string $language, string $custom_prompt, array $context ): string {
		if ( $custom_prompt !== '' ) {
			$base = $custom_prompt;
		} else {
			$base = __(
				'You write image alt text for the web. Describe only what is visibly present. Prioritize accuracy and accessibility for screen readers over SEO. Be clear, specific, and concise. Do not start with "Image of", "Picture of", or "Photo of". Do not invent brands, people, places, or marketing claims that are not visible. Output only the alt text, nothing else.',
				'wp-powersuite'
			);

			$style_instructions = PromptStyles::instruction( $prompt_style );
			$base              .= ' ' . $style_instructions;
		}

		$parts = [ $base ];

		if ( $custom_prompt !== '' ) {
			$parts[] = __(
				'Output only the alt text, nothing else. Do not start with "Image of" or "Picture of".',
				'wp-powersuite'
			);
		}

		$parts[] = __(
			'Context priority: (1) visible image content, (2) SEO focus keywords / bulk keywords when provided (weave them in naturally), (3) parent page/product context, (4) attachment title/caption, (5) filename only as a last-resort hint. Never let a filename override what you see. Never stuff keywords.',
			'wp-powersuite'
		);

		if ( $max_chars > 0 ) {
			$parts[] = sprintf( __( 'Keep the alt text under %d characters.', 'wp-powersuite' ), $max_chars );
		}

		if ( trim( $language ) !== '' ) {
			$parts[] = sprintf( __( 'Write the alt text in %s.', 'wp-powersuite' ), Languages::getLabel( $language ) );
		} elseif ( ! empty( $context['language'] ) ) {
			$parts[] = sprintf( __( 'Write the alt text in %s.', 'wp-powersuite' ), $context['language'] );
		}

		if ( ! empty( $context['product_title'] ) ) {
			$parts[] = sprintf(
				__( 'Product context (use only if visible or clearly relevant): Product name: %s.', 'wp-powersuite' ),
				$context['product_title']
			);
		}
		if ( ! empty( $context['product_category'] ) ) {
			$parts[] = sprintf( __( 'Product categories: %s.', 'wp-powersuite' ), $context['product_category'] );
		}
		if ( ! empty( $context['product_short_description'] ) ) {
			$parts[] = sprintf( __( 'Product summary: %s.', 'wp-powersuite' ), \AIAltTextGenerator\Text::slice( (string) $context['product_short_description'], 0, 200 ) );
		}
		if ( ! empty( $context['variation_attributes'] ) ) {
			$parts[] = sprintf( __( 'Variation details: %s.', 'wp-powersuite' ), $context['variation_attributes'] );
		}
		if ( ! empty( $context['parent_title'] ) ) {
			$parts[] = sprintf(
				__( 'Parent page/post title (context only): %s.', 'wp-powersuite' ),
				$context['parent_title']
			);
		}
		if ( ! empty( $context['attachment_title'] ) ) {
			$parts[] = sprintf(
				__( 'Attachment title (context only, do not copy verbatim unless accurate): %s.', 'wp-powersuite' ),
				$context['attachment_title']
			);
		}
		if ( ! empty( $context['caption'] ) ) {
			$parts[] = sprintf(
				__( 'Existing caption (context only): %s.', 'wp-powersuite' ),
				$context['caption']
			);
		}
		if ( ! empty( $context['description'] ) ) {
			$parts[] = sprintf(
				__( 'Existing description (context only): %s.', 'wp-powersuite' ),
				$context['description']
			);
		}
		if ( ! empty( $context['filename_hint'] ) ) {
			$parts[] = sprintf(
				__( 'Filename hint (lowest priority, ignore if it conflicts with the image): %s.', 'wp-powersuite' ),
				$context['filename_hint']
			);
		}
		if ( ! empty( $context['keyphrase'] ) ) {
			$parts[] = sprintf(
				__( 'Required SEO focus keyphrase: "%s". Naturally include this keyphrase (or a close grammatical form) in the alt text. Keep the description accurate to the image — do not invent objects just to fit the phrase, and do not keyword-stuff.', 'wp-powersuite' ),
				$context['keyphrase']
			);
		}
		if ( ! empty( $context['bulk_keywords'] ) ) {
			$parts[] = sprintf(
				__( 'Required SEO keywords for this bulk run: %s. Include at least one of these keywords or a close natural variant in the alt text when it can fit the visible image. Prefer variety across images when several keywords are listed. Do not invent details solely to force a keyword, and do not list keywords.', 'wp-powersuite' ),
				$context['bulk_keywords']
			);
		}
		if ( ! empty( $context['negative_keywords'] ) ) {
			$parts[] = sprintf( __( 'Do not include these words or phrases: %s', 'wp-powersuite' ), $context['negative_keywords'] );
		}
		if ( ! empty( $context['avoid_alt'] ) ) {
			$parts[] = sprintf(
				/* translators: 1: number of images, 2: duplicate alt text */
				__( 'This alt text is duplicated on %1$d other images. Write a UNIQUE, image-specific description. Do not reuse: "%2$s".', 'wp-powersuite' ),
				max( 1, (int) ( $context['duplicate_count'] ?? 1 ) - 1 ),
				$context['avoid_alt']
			);
		}

		return implode( ' ', array_filter( $parts ) );
	}

	/**
	 * @return array{data: string, mime: string}|\WP_Error
	 */
	public static function getImagePayload( string $file_path, string $mime, int $max_dimension = 0 ): array|\WP_Error {
		$max_dimension = $max_dimension > 0 ? $max_dimension : self::MAX_DIMENSION;
		$size          = filesize( $file_path );
		if ( $size === false ) {
			return new \WP_Error( 'read_failed', __( 'Could not read image file.', 'wp-powersuite' ) );
		}

		$needs_resize = $size > self::API_MAX_SIZE || $size > self::PREFERRED_MAX_BYTES;
		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		$dims = @getimagesize( $file_path );
		if ( is_array( $dims ) && ! empty( $dims[0] ) && ! empty( $dims[1] ) ) {
			if ( (int) $dims[0] > $max_dimension || (int) $dims[1] > $max_dimension ) {
				$needs_resize = true;
			}
		}

		if ( ! $needs_resize ) {
			$data = file_get_contents( $file_path );
			if ( $data === false ) {
				return new \WP_Error( 'read_failed', __( 'Could not read image file.', 'wp-powersuite' ) );
			}
			$encoded = base64_encode( $data );
			return $encoded !== false
				? [ 'data' => $encoded, 'mime' => $mime ]
				: new \WP_Error( 'encode_failed', __( 'Could not encode image.', 'wp-powersuite' ) );
		}

		return self::resizeImage( $file_path, $mime, $max_dimension );
	}

	/**
	 * @return array{data: string, mime: string}|\WP_Error
	 */
	public static function resizeImage( string $file_path, string $mime, int $max_dimension = 0 ): array|\WP_Error {
		$max_dimension = $max_dimension > 0 ? $max_dimension : self::MAX_DIMENSION;

		if ( ! function_exists( 'wp_get_image_editor' ) ) {
			require_once ABSPATH . 'wp-admin/includes/image.php';
		}

		$editor = wp_get_image_editor( $file_path );
		if ( ! $editor || is_wp_error( $editor ) ) {
			return new \WP_Error( 'no_editor', __( 'Image editor not available.', 'wp-powersuite' ) );
		}

		if ( is_wp_error( $editor->load() ) ) {
			return new \WP_Error( 'load_failed', __( 'Could not load image for resize.', 'wp-powersuite' ) );
		}

		$size = $editor->get_size();
		if ( ! $size || empty( $size['width'] ) || empty( $size['height'] ) ) {
			return new \WP_Error( 'invalid_size', __( 'Could not get image dimensions.', 'wp-powersuite' ) );
		}

		$scale    = min( $max_dimension / $size['width'], $max_dimension / $size['height'], 1.0 );
		$target_w = max( 1, (int) round( $size['width'] * $scale ) );
		$target_h = max( 1, (int) round( $size['height'] * $scale ) );

		$editor->resize( $target_w, $target_h, false );
		$saved = $editor->save();
		if ( is_wp_error( $saved ) || ! isset( $saved['path'] ) || ! is_file( $saved['path'] ) ) {
			return new \WP_Error( 'resize_failed', __( 'Could not resize image.', 'wp-powersuite' ) );
		}

		$data     = file_get_contents( $saved['path'] );
		$out_mime = $saved['mime-type'] ?? $mime;
		@unlink( $saved['path'] );

		if ( $data === false ) {
			return new \WP_Error( 'read_failed', __( 'Could not read resized image.', 'wp-powersuite' ) );
		}

		$encoded = base64_encode( $data );
		return $encoded !== false
			? [ 'data' => $encoded, 'mime' => $out_mime ]
			: new \WP_Error( 'encode_failed', __( 'Could not encode image.', 'wp-powersuite' ) );
	}

	public static function normalizeAltText( string $content, int $max_chars ): string {
		$content = trim( $content );
		if ( $content === '' ) {
			return '';
		}

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

		return sanitize_text_field( $content );
	}
}
