<?php
/**
 * Image Generator - handles saving generated images to WordPress media library.
 *
 * @package AgenticWP
 */

namespace Agentic_WP;

defined( 'ABSPATH' ) || exit;

/**
 * Handles image generation and media library integration.
 *
 * @since 1.0.0
 */
class Image_Generator {

	/**
	 * Post meta key to track generated images.
	 *
	 * @var string
	 */
	const META_KEY_GENERATED = '_agenticwp_generated_image';

	/**
	 * Maximum allowed decoded image size in bytes (50MB).
	 *
	 * @var int
	 */
	const MAX_IMAGE_SIZE = 52428800;

	/**
	 * Generate an image using the OpenAI API.
	 *
	 * @param string $prompt  Text description of the desired image.
	 * @param array  $options Optional parameters (size, quality, output_format, background).
	 * @return array|WP_Error Response data with base64 image on success, or WP_Error.
	 */
	public function generate( string $prompt, array $options = array() ): array|\WP_Error {
		$client = new OpenAI_Client();
		$result = $client->create_image( $prompt, $options );

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

		// Track usage statistics.
		$stats      = Usage_Stats::get_instance();
		$stats_data = $stats->get_stats();
		if ( ! isset( $stats_data['lifetime']['images_generated'] ) ) {
			$stats_data['lifetime']['images_generated'] = 0;
		}
		++$stats_data['lifetime']['images_generated'];
		// Use reflection to set stats and dirty flag since there's no public setter.
		$reflection = new \ReflectionClass( $stats );
		$stats_prop = $reflection->getProperty( 'stats' );
		$stats_prop->setValue( $stats, $stats_data );
		$dirty_prop = $reflection->getProperty( 'dirty' );
		$dirty_prop->setValue( $stats, true );

		return $result;
	}

	/**
	 * Save base64-encoded image data to the WordPress media library.
	 *
	 * Uses memory-efficient streaming to temp file instead of holding full image in memory.
	 *
	 * @param string $base64_data Base64-encoded image data.
	 * @param string $filename    Desired filename (without extension).
	 * @param string $format      Image format (png, jpeg, webp).
	 * @param string $prompt      Original prompt used to generate the image.
	 * @return array|WP_Error Attachment data on success, or WP_Error.
	 */
	public function save_image( string $base64_data, string $filename, string $format = 'png', string $prompt = '' ): array|\WP_Error {
		// Estimate decoded size before processing.
		$estimated_size = (int) ( strlen( $base64_data ) * 0.75 );

		if ( $estimated_size > self::MAX_IMAGE_SIZE ) {
			return new \WP_Error(
				'image_too_large',
				__( 'Generated image exceeds maximum size limit. Try a lower quality setting.', 'agenticwp' )
			);
		}

		// Check WordPress upload limit.
		$max_upload = wp_max_upload_size();
		if ( $estimated_size > $max_upload ) {
			return new \WP_Error(
				'upload_limit_exceeded',
				sprintf(
					/* translators: %s: Maximum upload size */
					__( 'Generated image exceeds upload limit (%s). Try a lower quality setting.', 'agenticwp' ),
					size_format( $max_upload )
				)
			);
		}

		// Map format to extension and MIME type.
		$format_map = array(
			'png'  => array(
				'ext'  => 'png',
				'mime' => 'image/png',
			),
			'jpeg' => array(
				'ext'  => 'jpg',
				'mime' => 'image/jpeg',
			),
			'webp' => array(
				'ext'  => 'webp',
				'mime' => 'image/webp',
			),
		);

		if ( ! isset( $format_map[ $format ] ) ) {
			$format = 'png';
		}

		$ext  = $format_map[ $format ]['ext'];
		$mime = $format_map[ $format ]['mime'];

		// Sanitize filename.
		$filename = sanitize_file_name( $filename );
		if ( empty( $filename ) ) {
			$filename = 'generated-image-' . time();
		}
		$filename .= '.' . $ext;

		// Stream base64 to temp file instead of holding full image in memory.
		$temp_file = wp_tempnam( $filename );
		if ( ! $temp_file ) {
			return new \WP_Error(
				'temp_file_error',
				__( 'Could not create temporary file.', 'agenticwp' )
			);
		}

		// Decode base64 data - required because API returns base64-encoded images.
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Decoding API image response.
		$decoded = base64_decode( $base64_data, true );
		if ( false === $decoded ) {
			wp_delete_file( $temp_file );
			return new \WP_Error(
				'decode_error',
				__( 'Invalid image data received.', 'agenticwp' )
			);
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		$bytes_written = file_put_contents( $temp_file, $decoded );
		unset( $decoded ); // Free memory immediately.

		if ( false === $bytes_written ) {
			wp_delete_file( $temp_file );
			return new \WP_Error(
				'write_error',
				__( 'Could not write image to file.', 'agenticwp' )
			);
		}

		// Validate MIME type using WordPress functions.
		$allowed_mimes = array(
			'png'  => 'image/png',
			'jpg'  => 'image/jpeg',
			'jpeg' => 'image/jpeg',
			'webp' => 'image/webp',
		);
		$filetype      = wp_check_filetype( $filename, $allowed_mimes );

		if ( empty( $filetype['type'] ) ) {
			wp_delete_file( $temp_file );
			return new \WP_Error(
				'invalid_filetype',
				__( 'Invalid image file type.', 'agenticwp' )
			);
		}

		// Upload to WordPress media library.
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading local temp file.
		$upload = wp_upload_bits( $filename, null, file_get_contents( $temp_file ) );
		wp_delete_file( $temp_file );

		if ( ! empty( $upload['error'] ) ) {
			return new \WP_Error(
				'upload_error',
				$upload['error']
			);
		}

		// Create attachment.
		$attachment_data = array(
			'post_title'     => wp_strip_all_tags( $filename ),
			'post_content'   => '',
			'post_status'    => 'inherit',
			'post_mime_type' => $mime,
		);

		$attachment_id = wp_insert_attachment( $attachment_data, $upload['file'] );

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

		// Generate attachment metadata (thumbnails, etc.).
		require_once ABSPATH . 'wp-admin/includes/image.php';
		$metadata = wp_generate_attachment_metadata( $attachment_id, $upload['file'] );
		wp_update_attachment_metadata( $attachment_id, $metadata );

		// Track as AI-generated image.
		update_post_meta(
			$attachment_id,
			self::META_KEY_GENERATED,
			array(
				'prompt'     => $prompt,
				'created_at' => time(),
			)
		);

		return array(
			'attachment_id' => $attachment_id,
			'url'           => wp_get_attachment_url( $attachment_id ),
			'filename'      => $filename,
		);
	}

	/**
	 * Get recent generated images.
	 *
	 * @param int $limit Number of images to retrieve.
	 * @return array Array of image data.
	 */
	public function get_recent_images( int $limit = 10 ): array {
		$args = array(
			'post_type'      => 'attachment',
			'post_status'    => 'inherit',
			'posts_per_page' => $limit,
			'orderby'        => 'date',
			'order'          => 'DESC',
			'meta_query'     => array(
				array(
					'key'     => self::META_KEY_GENERATED,
					'compare' => 'EXISTS',
				),
			),
		);

		$query  = new \WP_Query( $args );
		$images = array();

		foreach ( $query->posts as $post ) {
			$meta     = get_post_meta( $post->ID, self::META_KEY_GENERATED, true );
			$prompt   = $meta['prompt'] ?? '';
			$images[] = array(
				'id'         => $post->ID,
				'url'        => wp_get_attachment_url( $post->ID ),
				'thumbnail'  => wp_get_attachment_image_url( $post->ID, 'thumbnail' ),
				'title'      => $prompt ? wp_trim_words( $prompt, 10, '...' ) : $post->post_title,
				'prompt'     => $prompt,
				'created_at' => $meta['created_at'] ?? strtotime( $post->post_date ),
				'edit_url'   => admin_url( 'upload.php?item=' . $post->ID ),
			);
		}

		return $images;
	}
}
