<?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;

		$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.', 'wppowersuite-lite' )
			);
		}

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

		$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.', 'wppowersuite-lite' ) );
		}

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

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

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

		$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.', 'wppowersuite-lite' ) );
		}

		$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( wp_strip_all_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.', 'wppowersuite-lite' );
	}

	/**
	 * @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.',
				'wppowersuite-lite'
			);

			$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".',
				'wppowersuite-lite'
			);
		}

		$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.',
			'wppowersuite-lite'
		);

		if ( $max_chars > 0 ) {
			/* translators: %d: number value. */
			$parts[] = sprintf( __( 'Keep the alt text under %d characters.', 'wppowersuite-lite' ), $max_chars );
		}

		if ( trim( $language ) !== '' ) {
			/* translators: %s: string value. */
			$parts[] = sprintf( __( 'Write the alt text in %s.', 'wppowersuite-lite' ), Languages::getLabel( $language ) );
		} elseif ( ! empty( $context['language'] ) ) {
			/* translators: %s: string value. */
			$parts[] = sprintf( __( 'Write the alt text in %s.', 'wppowersuite-lite' ), $context['language'] );
		}

		if ( ! empty( $context['product_title'] ) ) {
			$parts[] = sprintf(
				/* translators: %s: string value. */
				__( 'Product context (use only if visible or clearly relevant): Product name: %s.', 'wppowersuite-lite' ),
				$context['product_title']
			);
		}
		if ( ! empty( $context['product_category'] ) ) {
			/* translators: %s: string value. */
			$parts[] = sprintf( __( 'Product categories: %s.', 'wppowersuite-lite' ), $context['product_category'] );
		}
		if ( ! empty( $context['product_short_description'] ) ) {
			/* translators: %s: string value. */
			$parts[] = sprintf( __( 'Product summary: %s.', 'wppowersuite-lite' ), \AIAltTextGenerator\Text::slice( (string) $context['product_short_description'], 0, 200 ) );
		}
		if ( ! empty( $context['variation_attributes'] ) ) {
			/* translators: %s: string value. */
			$parts[] = sprintf( __( 'Variation details: %s.', 'wppowersuite-lite' ), $context['variation_attributes'] );
		}
		if ( ! empty( $context['parent_title'] ) ) {
			$parts[] = sprintf(
				/* translators: %s: string value. */
				__( 'Parent page/post title (context only): %s.', 'wppowersuite-lite' ),
				$context['parent_title']
			);
		}
		if ( ! empty( $context['attachment_title'] ) ) {
			$parts[] = sprintf(
				/* translators: %s: string value. */
				__( 'Attachment title (context only, do not copy verbatim unless accurate): %s.', 'wppowersuite-lite' ),
				$context['attachment_title']
			);
		}
		if ( ! empty( $context['caption'] ) ) {
			$parts[] = sprintf(
				/* translators: %s: string value. */
				__( 'Existing caption (context only): %s.', 'wppowersuite-lite' ),
				$context['caption']
			);
		}
		if ( ! empty( $context['description'] ) ) {
			$parts[] = sprintf(
				/* translators: %s: string value. */
				__( 'Existing description (context only): %s.', 'wppowersuite-lite' ),
				$context['description']
			);
		}
		if ( ! empty( $context['filename_hint'] ) ) {
			$parts[] = sprintf(
				/* translators: %s: string value. */
				__( 'Filename hint (lowest priority, ignore if it conflicts with the image): %s.', 'wppowersuite-lite' ),
				$context['filename_hint']
			);
		}
		if ( ! empty( $context['keyphrase'] ) ) {
			$parts[] = sprintf(
				/* translators: %s: string value. */
				__( '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.', 'wppowersuite-lite' ),
				$context['keyphrase']
			);
		}
		if ( ! empty( $context['bulk_keywords'] ) ) {
			$parts[] = sprintf(
				/* translators: %s: string value. */
				__( '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.', 'wppowersuite-lite' ),
				$context['bulk_keywords']
			);
		}
		if ( ! empty( $context['negative_keywords'] ) ) {
			/* translators: %s: string value. */
			$parts[] = sprintf( __( 'Do not include these words or phrases: %s', 'wppowersuite-lite' ), $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".', 'wppowersuite-lite' ),
				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.', 'wppowersuite-lite' ) );
		}

		$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.', 'wppowersuite-lite' ) );
			}
			$encoded = base64_encode( $data );
			return $encoded !== false
				? [ 'data' => $encoded, 'mime' => $mime ]
				: new \WP_Error( 'encode_failed', __( 'Could not encode image.', 'wppowersuite-lite' ) );
		}

		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;

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

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

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

		$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.', 'wppowersuite-lite' ) );
		}

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

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

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

	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 );
	}
}

