<?php
/**
 * Image Generator Class
 *
 * Handles image generation, metadata creation, and saving to media library.
 *
 * @package SmartyPress_AI_Engine
 * @since 1.1.0
 */

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

/**
 * Image Generator class
 */
class SmartyPress_AI_Image_Generator {

	/**
	 * AI Provider instance (for image generation)
	 *
	 * @var SmartyPress_AI_AI_Provider
	 */
	private $provider;

	/**
	 * Text provider instance (for metadata generation, falls back to image provider if null)
	 *
	 * @var SmartyPress_AI_AI_Provider|null
	 */
	private $text_provider;

	/**
	 * Plugin settings
	 *
	 * @var array
	 */
	private $settings;

	/**
	 * Language policy service for image metadata.
	 *
	 * @var SmartyPress_AI_Content_Generator_Generation_Language_Service
	 */
	private $language_service;

	/**
	 * Constructor
	 *
	 * @param SmartyPress_AI_AI_Provider      $provider      AI provider instance (for images).
	 * @param array                           $settings      Plugin settings.
	 * @param SmartyPress_AI_AI_Provider|null $text_provider Provider for metadata (optional, falls back to image provider).
	 */
	public function __construct( $provider, $settings = array(), $text_provider = null ) {
		$this->provider      = $provider;
		$this->text_provider = $text_provider ?? $provider;
		$this->settings      = $settings;
		$this->language_service = class_exists( 'SmartyPress_AI_Content_Generator_Generation_Language_Service' )
			? new SmartyPress_AI_Content_Generator_Generation_Language_Service()
			: null;
	}

	/**
	 * Generate an image from prompt
	 *
	 * 1. Generates metadata (title, alt, caption, description, filename) using text model.
	 * 2. Generates image using image model.
	 * 3. Saves image to WordPress media library with metadata.
	 *
	 * @param string $prompt    Image generation prompt.
	 * @param string $model     Image model name.
	 * @param string $resolution Image resolution (e.g., '1024x1024').
	 * @param string $text_model Text model for metadata (title, alt, caption, description).
	 * @param array  $metadata_context SEO context for metadata generation.
	 * @return array Result with image data or error.
	 */
	public function generate( $prompt, $model = 'gpt-image-1', $resolution = '1024x1024', $text_model = 'gpt-4o-mini', $metadata_context = array() ) {
		if ( class_exists( 'SmartyPress_AI_Token_Tracker' ) ) {
			SmartyPress_AI_Token_Tracker::set_module( 'image-generation' );
		}

		// Step 1: Generate metadata from prompt using text model.
		$metadata         = $this->generate_metadata( $prompt, $text_model, $metadata_context );
		$metadata_warning = array();

		if ( ! $metadata['success'] ) {
			$this->log_metadata_fallback( $metadata, $text_model );
			$metadata_warning = $this->build_metadata_warning( $metadata, $text_model );

			$metadata = $this->generate_fallback_metadata( $prompt, $metadata_context );
		}

		$metadata = $this->ensure_focus_keyword_in_metadata( $metadata, $metadata_context['focus_keyword'] ?? '' );
		$metadata = $this->normalize_metadata_capitalization( $metadata );

		// Step 2: Generate image.
		$image_result = $this->provider->generate_image( $prompt, array(
			'model'      => $model,
			'resolution' => $resolution,
		) );

		if ( ! $image_result['success'] ) {
			if ( class_exists( 'SmartyPress_AI_Logger' ) ) {
				SmartyPress_AI_Logger::log_module_event( 'image_generator', 'image_generation_failed', array(
					'error'  => $image_result['error'] ?? '',
					'prompt' => $prompt,
					'model'  => $model,
				), false );
			}
			return $image_result;
		}

		// Step 3: Save to media library.
		$attachment_id = $this->save_to_media_library( $image_result, $metadata, $prompt, $model, $resolution, $metadata_warning );

		if ( is_wp_error( $attachment_id ) ) {
			if ( class_exists( 'SmartyPress_AI_Logger' ) ) {
				SmartyPress_AI_Logger::log_module_event( 'image_generator', 'image_generation_failed', array(
					'error'  => $attachment_id->get_error_message(),
					'prompt' => $prompt,
					'model'  => $model,
				), false );
			}
			return array(
				'success' => false,
				'error'   => $attachment_id->get_error_message(),
			);
		}

		// Build response.
		$attachment_url   = wp_get_attachment_url( $attachment_id );
		$thumbnail_url    = wp_get_attachment_image_url( $attachment_id, 'thumbnail' );
		$attached_file    = get_attached_file( $attachment_id );
		$filesize         = $attached_file && file_exists( $attached_file ) ? filesize( $attached_file ) : 0;

		if ( class_exists( 'SmartyPress_AI_Logger' ) ) {
			SmartyPress_AI_Logger::log_module_event( 'image_generator', 'image_generated', array(
				'attachment_id' => $attachment_id,
				'model'         => $model,
				'resolution'    => $resolution,
				'filesize'      => $filesize,
			), true );
		}

		$response = array(
			'success'       => true,
			'attachment_id' => $attachment_id,
			'url'           => $attachment_url,
			'thumbnail_url' => $thumbnail_url ?: $attachment_url,
			'title'         => $metadata['title'],
			'filename'      => basename( $attached_file ?: '' ) ?: ( $metadata['filename'] . '.png' ),
			'description'   => $metadata['description'],
			'alt'           => $metadata['alt'],
			'caption'       => $metadata['caption'] ?? '',
			'resolution'    => $resolution,
			'model'         => $model,
			'filesize'      => $filesize,
		);

		if ( ! empty( $metadata_warning ) ) {
			$response['metadata_warning'] = $metadata_warning;
		}

		return $response;
	}

	/**
	 * Generate metadata from prompt using text model
	 *
	 * Calls the text AI model to analyze the prompt and create SEO-friendly
	 * title, alt text, caption, description, and filename.
	 *
	 * @param string $prompt      Image generation prompt.
	 * @param string $text_model Text model for metadata (default gpt-4o-mini).
	 * @param array  $metadata_context SEO context for metadata.
	 * @return array Metadata array with title, alt, caption, description, filename, or error.
	 */
	public function generate_metadata( $prompt, $text_model = 'gpt-4o-mini', $metadata_context = array() ) {
		$focus_keyword = sanitize_text_field( $metadata_context['focus_keyword'] ?? '' );
		$language      = sanitize_text_field( $metadata_context['language'] ?? '' );
		$language_rule = $this->get_image_metadata_language_instruction( $language );
		$context_json  = wp_json_encode(
			array(
				'focus_keyword' => $focus_keyword,
				'language'      => $language,
			),
			JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
		);
		$system_prompt = 'You are a metadata generator for AI-generated images. '
			. 'Analyze the image generation prompt and create SEO-friendly metadata. '
			. 'Return ONLY a valid JSON object. Do not use markdown, explanations, or code blocks. '
			. 'The JSON object MUST contain exactly these keys: '
			. '"title" (short descriptive title, max 60 chars), '
			. '"alt" (accessibility alt text, max 125 chars), '
			. '"caption" (short public image caption), '
			. '"description" (1-2 sentence description for media library), '
			. '"filename" (lowercase ASCII slug, use only a-z, 0-9 and hyphens, no extension, max 50 chars). '
			. $language_rule . ' '
			. 'If METADATA_CONTEXT.focus_keyword is provided, include it naturally in alt, caption, and description. '
			. 'The filename must remain English ASCII even when the prompt is in another language.';
		$metadata_prompt = $prompt . "\n\nMETADATA_CONTEXT:\n" . $context_json;

		$result = $this->text_provider->generate_content( $metadata_prompt, array(
			'system_prompt'   => $system_prompt,
			'model'           => $text_model,
			'temperature'     => 0.5,
			'max_tokens'      => 400,
			'response_format' => array(
				'type' => 'json_object',
			),
		) );

		if ( ! $result['success'] ) {
			return array(
				'success'       => false,
				'error'         => $result['error'] ?? __( 'Failed to generate metadata.', 'smartypress-ai-engine' ),
				'error_code'    => $result['error_code'] ?? 'api_error',
				'retry_after'   => $result['retry_after'] ?? 0,
				'provider_name' => $result['provider_name'] ?? '',
				'is_byok'       => isset( $result['is_byok'] ) ? (bool) $result['is_byok'] : null,
			);
		}

		$content = $result['content'] ?? '';

		// Try to parse JSON from response.
		$json = $this->parse_json_response( $content );

		if ( ! $json || ! isset( $json['title'] ) ) {
			return array(
				'success'         => false,
				'error'           => __( 'Failed to parse metadata response.', 'smartypress-ai-engine' ),
				'error_code'      => 'parse_failed',
				'content_preview' => mb_substr( trim( wp_strip_all_tags( $content ) ), 0, 300 ),
			);
		}

		$metadata = array(
			'success'     => true,
			'title'       => sanitize_text_field( $json['title'] ?? '' ),
			'alt'         => sanitize_text_field( $json['alt'] ?? '' ),
			'caption'     => sanitize_text_field( $json['caption'] ?? '' ),
			'description' => sanitize_textarea_field( $json['description'] ?? '' ),
			'filename'    => $this->normalize_metadata_filename( $json['filename'] ?? '', $prompt ),
		);

		return $this->ensure_focus_keyword_in_metadata( $metadata, $focus_keyword );
	}

	/**
	 * Normalize visible metadata fields so they start with an uppercase letter.
	 *
	 * @param array $metadata Generated metadata.
	 * @return array
	 */
	private function normalize_metadata_capitalization( $metadata ) {
		foreach ( array( 'title', 'alt', 'caption' ) as $field ) {
			if ( isset( $metadata[ $field ] ) ) {
				$metadata[ $field ] = $this->uppercase_first_letter( $metadata[ $field ] );
			}
		}

		return $metadata;
	}

	/**
	 * Uppercase the first letter in a UTF-8 string.
	 *
	 * @param string $text Text.
	 * @return string
	 */
	private function uppercase_first_letter( $text ) {
		$text = sanitize_text_field( trim( (string) $text ) );
		if ( '' === $text ) {
			return '';
		}

		$normalized = preg_replace_callback(
			'/\p{L}/u',
			function ( $matches ) {
				return function_exists( 'mb_strtoupper' ) ? mb_strtoupper( $matches[0], 'UTF-8' ) : strtoupper( $matches[0] );
			},
			$text,
			1
		);

		return is_string( $normalized ) ? $normalized : $text;
	}

	/**
	 * Ensure SEO metadata contains the primary focus keyword.
	 *
	 * AI providers can ignore keyword instructions, so enforce this invariant
	 * before saving attachment metadata.
	 *
	 * @param array  $metadata      Generated metadata.
	 * @param string $focus_keyword Primary focus keyword.
	 * @return array
	 */
	private function ensure_focus_keyword_in_metadata( $metadata, $focus_keyword ) {
		$focus_keyword = $this->sanitize_focus_keyword( $focus_keyword );
		if ( '' === $focus_keyword ) {
			return $metadata;
		}

		$metadata['alt']         = $this->prepend_keyword_when_missing( $metadata['alt'] ?? '', $focus_keyword, 125 );
		$metadata['caption']     = $this->prepend_keyword_when_missing( $metadata['caption'] ?? '', $focus_keyword, 160 );
		$metadata['description'] = $this->prepend_keyword_when_missing( $metadata['description'] ?? '', $focus_keyword, 300 );

		return $metadata;
	}

	/**
	 * Prepend focus keyword to short metadata fields when it is missing.
	 *
	 * @param string $text          Metadata text.
	 * @param string $focus_keyword Primary focus keyword.
	 * @param int    $limit         Max characters.
	 * @return string
	 */
	private function prepend_keyword_when_missing( $text, $focus_keyword, $limit ) {
		$text = sanitize_text_field( trim( (string) $text ) );
		if ( $this->text_contains_keyword( $text, $focus_keyword ) ) {
			return $text;
		}

		$value = '' === $text ? $focus_keyword : sprintf( '%s: %s', $focus_keyword, $text );

		return sanitize_text_field( mb_substr( $value, 0, $limit ) );
	}

	/**
	 * Check if text contains keyword with Unicode-safe case folding.
	 *
	 * @param string $text          Text.
	 * @param string $focus_keyword Primary focus keyword.
	 * @return bool
	 */
	private function text_contains_keyword( $text, $focus_keyword ) {
		if ( '' === $text || '' === $focus_keyword ) {
			return false;
		}

		return false !== mb_stripos( $text, $focus_keyword, 0, 'UTF-8' );
	}

	/**
	 * Sanitize focus keyword and ignore service-like values from editor controls.
	 *
	 * @param mixed $keyword Raw keyword.
	 * @return string
	 */
	private function sanitize_focus_keyword( $keyword ) {
		if ( ! is_scalar( $keyword ) ) {
			return '';
		}

		$keyword = preg_replace( '/\s+/u', ' ', sanitize_text_field( trim( (string) $keyword ) ) );
		$invalid = array(
			'0',
			'1',
			'on',
			'off',
			'yes',
			'no',
			'true',
			'false',
			'enabled',
			'disabled',
			'checked',
			'unchecked',
			'null',
			'none',
		);

		if ( '' === $keyword || mb_strlen( $keyword, 'UTF-8' ) < 3 || in_array( strtolower( $keyword ), $invalid, true ) ) {
			return '';
		}

		return $keyword;
	}

	/**
	 * Save image to WordPress media library
	 *
	 * Downloads image from URL or decodes b64, creates attachment with metadata.
	 *
	 * @param array  $image_result Image generation result (url or b64_json).
	 * @param array  $metadata     Generated metadata (title, alt, description, filename).
	 * @param string $prompt       Original prompt.
	 * @param string $model        Model used.
	 * @param string $resolution   Resolution used.
	 * @param array  $metadata_warning Metadata fallback warning.
	 * @return int|WP_Error Attachment ID or WP_Error on failure.
	 */
	private function save_to_media_library( $image_result, $metadata, $prompt, $model, $resolution, $metadata_warning = array() ) {
		require_once ABSPATH . 'wp-admin/includes/image.php';

		$upload_dir = wp_upload_dir();
		$filename   = $metadata['filename'] . '.png';
		$filename   = wp_unique_filename( $upload_dir['path'], $filename );
		$file_path  = $upload_dir['path'] . '/' . $filename;

		// Get image data.
		if ( ! empty( $image_result['b64_json'] ) ) {
			$image_data = base64_decode( $image_result['b64_json'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
		} elseif ( ! empty( $image_result['url'] ) ) {
			$image_data = $this->download_image( $image_result['url'] );
		} else {
			return new \WP_Error( 'no_image_data', __( 'No image data available.', 'smartypress-ai-engine' ) );
		}

		if ( ! $image_data || strlen( $image_data ) < 100 ) {
			return new \WP_Error( 'download_failed', __( 'Failed to download image.', 'smartypress-ai-engine' ) );
		}

		// Write file.
		$written = file_put_contents( $file_path, $image_data ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents

		if ( false === $written ) {
			return new \WP_Error( 'write_failed', __( 'Failed to write image file.', 'smartypress-ai-engine' ) );
		}

		// Create attachment.
		$wp_filetype = wp_check_filetype( $filename, null );
		$attachment  = array(
			'post_mime_type' => $wp_filetype['type'] ?: 'image/png',
			'post_title'     => $metadata['title'],
			'post_content'   => $metadata['description'],
			'post_excerpt'   => $metadata['caption'] ?? '',
			'post_status'    => 'inherit',
		);

		$attach_id = wp_insert_attachment( $attachment, $file_path );

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

		// Generate attachment metadata (thumbnails, etc.).
		$attach_data = wp_generate_attachment_metadata( $attach_id, $file_path );
		wp_update_attachment_metadata( $attach_id, $attach_data );

		// Set alt text.
		if ( ! empty( $metadata['alt'] ) ) {
			update_post_meta( $attach_id, '_wp_attachment_image_alt', $metadata['alt'] );
		}

		// Store AI-specific metadata.
		update_post_meta( $attach_id, 'smartypress_ai_prompt', $prompt );
		update_post_meta( $attach_id, 'smartypress_ai_model', $model );
		update_post_meta( $attach_id, 'smartypress_ai_resolution', $resolution );
		update_post_meta( $attach_id, 'smartypress_ai_generated', true );

		if ( ! empty( $metadata_warning ) ) {
			update_post_meta( $attach_id, 'smartypress_ai_metadata_warning', $metadata_warning );
		}

		return $attach_id;
	}

	/**
	 * Download image from URL
	 *
	 * @param string $url Image URL.
	 * @return string|false Image binary data or false on failure.
	 */
	private function download_image( $url ) {
		$response = wp_remote_get( $url, array(
			'timeout' => 60,
		) );

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

		$response_code = wp_remote_retrieve_response_code( $response );
		if ( 200 !== $response_code ) {
			return false;
		}

		return wp_remote_retrieve_body( $response );
	}

	/**
	 * Parse JSON from AI response
	 *
	 * Handles cases where the response contains markdown code blocks.
	 *
	 * @param string $content AI response content.
	 * @return array|null Parsed JSON or null on failure.
	 */
	private function parse_json_response( $content ) {
		$content = trim( $content );

		// Remove markdown code block wrappers if present.
		if ( preg_match( '/```(?:json)?\s*\n?(.*?)\n?\s*```/s', $content, $matches ) ) {
			$content = $matches[1];
		}

		$data = json_decode( $content, true );

		if ( json_last_error() === JSON_ERROR_NONE && is_array( $data ) ) {
			return $data;
		}

		return null;
	}

	/**
	 * Normalize metadata filename to a safe readable ASCII slug.
	 *
	 * @param string $filename Filename returned by AI.
	 * @param string $prompt   Original prompt for deterministic fallback.
	 * @return string
	 */
	private function normalize_metadata_filename( $filename, $prompt ) {
		$filename = pathinfo( (string) $filename, PATHINFO_FILENAME );
		$filename = strtolower( remove_accents( $filename ) );
		$filename = preg_replace( '/[^a-z0-9]+/', '-', $filename );
		$filename = trim( preg_replace( '/-+/', '-', $filename ), '-' );

		if ( empty( $filename ) || strlen( $filename ) < 3 || $this->is_hex_like_filename( $filename ) ) {
			return $this->generate_fallback_filename( $prompt );
		}

		return substr( $filename, 0, 50 );
	}

	/**
	 * Check if filename looks like encoded bytes rather than readable words.
	 *
	 * @param string $filename Filename slug.
	 * @return bool
	 */
	private function is_hex_like_filename( $filename ) {
		$compact = str_replace( '-', '', strtolower( $filename ) );

		return strlen( $compact ) >= 16 && 1 === preg_match( '/^[a-f0-9]+$/', $compact );
	}

	/**
	 * Build a user-facing warning for metadata fallback.
	 *
	 * @param array  $metadata_error Metadata error payload.
	 * @param string $text_model     Text model used for metadata.
	 * @return array
	 */
	private function build_metadata_warning( $metadata_error, $text_model ) {
		return array(
			'reason'        => sanitize_key( $metadata_error['error_code'] ?? 'metadata_fallback' ),
			'error'         => sanitize_text_field( $metadata_error['error'] ?? '' ),
			'provider'      => method_exists( $this->text_provider, 'get_provider_name' ) ? sanitize_text_field( $this->text_provider->get_provider_name() ) : '',
			'model'         => sanitize_text_field( $text_model ),
			'provider_name' => sanitize_text_field( $metadata_error['provider_name'] ?? '' ),
			'is_byok'       => isset( $metadata_error['is_byok'] ) ? (bool) $metadata_error['is_byok'] : null,
			'retry_after'   => absint( $metadata_error['retry_after'] ?? 0 ),
		);
	}

	/**
	 * Log metadata fallback details for debugging.
	 *
	 * @param array  $metadata_error Metadata error payload.
	 * @param string $text_model     Text model used for metadata.
	 */
	private function log_metadata_fallback( $metadata_error, $text_model ) {
		if ( ! class_exists( 'SmartyPress_AI_Logger' ) ) {
			return;
		}

		SmartyPress_AI_Logger::log_module_event(
			'image_generator',
			'metadata_fallback',
			array(
				'provider'        => method_exists( $this->text_provider, 'get_provider_name' ) ? $this->text_provider->get_provider_name() : '',
				'model'           => $text_model,
				'reason'          => $metadata_error['error_code'] ?? 'unknown',
				'error'           => $metadata_error['error'] ?? '',
				'content_preview' => $metadata_error['content_preview'] ?? '',
			),
			false
		);
	}

	/**
	 * Build language instruction for image metadata.
	 *
	 * @param string $language Target metadata language.
	 * @return string
	 */
	private function get_image_metadata_language_instruction( $language ) {
		if ( class_exists( 'SmartyPress_AI_Content_Generator_Generation_Language_Service' ) && $this->language_service instanceof SmartyPress_AI_Content_Generator_Generation_Language_Service ) {
			return $this->language_service->get_output_language_instruction( $language, 'image_metadata' );
		}

		$language = trim( sanitize_text_field( $language ) );
		if ( '' === $language ) {
			$language = function_exists( 'smartypress_ai_get_language_by_locale' ) ? smartypress_ai_get_language_by_locale() : get_locale();
		}

		return sprintf(
			'TARGET OUTPUT LANGUAGE: %s. The image prompt may be written in any language. Generate title, alt text, caption, and description only in the target language.',
			$language
		);
	}

	/**
	 * Generate safe fallback metadata without copying the source prompt into visible fields.
	 *
	 * @param string $prompt Original image prompt.
	 * @param array  $metadata_context Metadata context.
	 * @return array
	 */
	private function generate_fallback_metadata( $prompt, array $metadata_context ) {
		$focus_keyword = $this->sanitize_focus_keyword( $metadata_context['focus_keyword'] ?? '' );
		$title         = '' !== $focus_keyword ? $focus_keyword : __( 'AI generated image', 'smartypress-ai-engine' );

		return array(
			'success'     => true,
			'title'       => $title,
			'alt'         => $focus_keyword,
			'caption'     => $focus_keyword,
			'description' => $focus_keyword,
			'filename'    => $this->generate_fallback_filename( $prompt ),
		);
	}

	/**
	 * Generate fallback title from prompt
	 *
	 * @param string $prompt Image generation prompt.
	 * @return string Short title derived from prompt.
	 */
	private function generate_fallback_title( $prompt ) {
		return wp_trim_words( $prompt, 8, '...' );
	}

	/**
	 * Generate fallback filename from prompt
	 *
	 * @param string $prompt Image generation prompt.
	 * @return string Sanitized filename.
	 */
	private function generate_fallback_filename( $prompt ) {
		$words = wp_trim_words( $prompt, 6, '' );
		$slug  = strtolower( remove_accents( $words ) );
		$slug  = preg_replace( '/[^a-z0-9]+/', '-', $slug );
		$slug  = trim( preg_replace( '/-+/', '-', $slug ), '-' );

		if ( empty( $slug ) || strlen( $slug ) < 3 || $this->is_hex_like_filename( $slug ) ) {
			$slug = 'ai-image-' . substr( md5( $prompt ), 0, 10 );
		}

		return substr( $slug, 0, 50 );
	}

	/**
	 * Regenerate (edit) an existing image
	 *
	 * Uses an image edit provider/model with the source image + correction prompt.
	 * Replaces the file in the same attachment.
	 *
	 * @param int    $attachment_id     Attachment ID.
	 * @param string $correction_prompt User's correction prompt.
	 * @param string $model             Image model (from attachment meta).
	 * @param string $resolution        Resolution (from attachment meta).
	 * @return array Result with updated image data or error.
	 */
	public function regenerate( $attachment_id, $correction_prompt, $model = 'gpt-image-1', $resolution = '1024x1024' ) {
		$file_path = get_attached_file( $attachment_id );
		if ( ! $file_path || ! file_exists( $file_path ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Attachment file not found.', 'smartypress-ai-engine' ),
			);
		}

		$original_prompt = get_post_meta( $attachment_id, 'smartypress_ai_prompt', true );
		$metadata       = array(
			'title'       => get_the_title( $attachment_id ),
			'alt'         => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
			'description' => get_post( $attachment_id )->post_content ?? '',
			'filename'    => pathinfo( $file_path, PATHINFO_FILENAME ),
		);

		if ( class_exists( 'SmartyPress_AI_Logger' ) ) {
			SmartyPress_AI_Logger::log_module_event( 'image_generator', 'regeneration_request', array(
				'attachment_id'     => $attachment_id,
				'model'             => $model,
				'correction_prompt' => $correction_prompt,
				'original_prompt'   => $original_prompt,
			), true );
		}

		$image_result = null;

		if ( ! method_exists( $this->provider, 'edit_image' ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Selected edit provider does not support image editing. Please choose an image edit model in Image Generation settings.', 'smartypress-ai-engine' ),
			);
		}

		$system_prompt = __( 'You are editing an existing image. Apply the user\'s requested changes precisely. Keep the style and composition similar unless explicitly asked to change.', 'smartypress-ai-engine' );
		$full_prompt   = $system_prompt . "\n\n" . __( 'Original context:', 'smartypress-ai-engine' ) . ' ' . $original_prompt . "\n\n" . __( 'Requested changes:', 'smartypress-ai-engine' ) . ' ' . $correction_prompt;

		$image_result = $this->provider->edit_image( $file_path, $full_prompt, array(
			'model'      => $model,
			'size'       => $resolution,
			'resolution' => $resolution,
		) );

		if ( ! $image_result['success'] ) {
			if ( class_exists( 'SmartyPress_AI_Logger' ) ) {
				SmartyPress_AI_Logger::log_module_event( 'image_generator', 'regeneration_failed', array(
					'error'             => $image_result['error'] ?? '',
					'attachment_id'     => $attachment_id,
					'correction_prompt' => $correction_prompt,
				), false );
			}
			return $image_result;
		}

		// Get image data from result.
		$image_data = null;
		if ( ! empty( $image_result['b64_json'] ) ) {
			$image_data = base64_decode( $image_result['b64_json'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
		} elseif ( ! empty( $image_result['url'] ) ) {
			$image_data = $this->download_image( $image_result['url'] );
		}

		if ( ! $image_data || strlen( $image_data ) < 100 ) {
			return array(
				'success' => false,
				'error'   => __( 'Failed to get edited image data.', 'smartypress-ai-engine' ),
			);
		}

		$updated_prompt = $original_prompt . ' [Edit: ' . $correction_prompt . ']';
		$replace_result = $this->replace_attachment_file( $attachment_id, $image_data, $metadata, $updated_prompt );

		if ( is_wp_error( $replace_result ) ) {
			if ( class_exists( 'SmartyPress_AI_Logger' ) ) {
				SmartyPress_AI_Logger::log_module_event( 'image_generator', 'regeneration_failed', array(
					'error'             => $replace_result->get_error_message(),
					'attachment_id'     => $attachment_id,
					'correction_prompt' => $correction_prompt,
				), false );
			}
			return array(
				'success' => false,
				'error'   => $replace_result->get_error_message(),
			);
		}

		$attached_file = get_attached_file( $attachment_id );
		$filesize      = $attached_file && file_exists( $attached_file ) ? filesize( $attached_file ) : 0;

		if ( class_exists( 'SmartyPress_AI_Logger' ) ) {
			SmartyPress_AI_Logger::log_module_event( 'image_generator', 'image_regenerated', array(
				'attachment_id' => $attachment_id,
				'model'         => $model,
				'filesize'      => $filesize,
			), true );
		}

		return array(
			'success'       => true,
			'attachment_id' => $attachment_id,
			'id'            => $attachment_id,
			'url'           => wp_get_attachment_url( $attachment_id ),
			'thumbnail_url' => wp_get_attachment_image_url( $attachment_id, 'thumbnail' ) ?: wp_get_attachment_url( $attachment_id ),
			'title'         => $metadata['title'],
			'filename'      => basename( $attached_file ?: '' ),
			'description'   => $metadata['description'],
			'alt'           => $metadata['alt'],
			'resolution'    => $resolution,
			'model'         => $model,
			'filesize'      => $filesize,
		);
	}

	/**
	 * Replace attachment file with new image data
	 *
	 * @param int    $attachment_id Attachment ID.
	 * @param string $image_data    New image binary data.
	 * @param array  $metadata      Title, alt, description, filename.
	 * @param string $prompt        Updated prompt for meta.
	 * @return true|WP_Error True on success.
	 */
	private function replace_attachment_file( $attachment_id, $image_data, $metadata, $prompt ) {
		require_once ABSPATH . 'wp-admin/includes/image.php';

		$file_path = get_attached_file( $attachment_id );
		if ( ! $file_path ) {
			return new \WP_Error( 'no_file', __( 'Attachment file path not found.', 'smartypress-ai-engine' ) );
		}

		// Delete old thumbnails before overwriting.
		$meta = wp_get_attachment_metadata( $attachment_id );
		if ( ! empty( $meta['sizes'] ) ) {
			$base_dir = dirname( $file_path );
			foreach ( $meta['sizes'] as $size ) {
				if ( ! empty( $size['file'] ) ) {
					$thumb_path = $base_dir . '/' . $size['file'];
					if ( file_exists( $thumb_path ) ) {
						wp_delete_file( $thumb_path );
					}
				}
			}
		}

		// Overwrite existing file (preserves URL).
		$written = file_put_contents( $file_path, $image_data ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		if ( false === $written ) {
			return new \WP_Error( 'write_failed', __( 'Failed to write image file.', 'smartypress-ai-engine' ) );
		}

		// Regenerate thumbnails.
		$attach_data = wp_generate_attachment_metadata( $attachment_id, $file_path );
		wp_update_attachment_metadata( $attachment_id, $attach_data );

		// Update prompt meta.
		update_post_meta( $attachment_id, 'smartypress_ai_prompt', $prompt );

		return true;
	}
}
