<?php
namespace AIFI;

/**
 * Handles AI image generation and media library integration.
 *
 * @package AIFI
 */
class Generator {
    /**
     * Settings instance.
     *
     * @var Settings
     */
    private $settings;

    /**
     * Initialize generator.
     */
    public function __construct($settings) {
        $this->settings = $settings;
    }

    /**
     * Generate image for a post.
     *
     * @param int    $post_id Post ID.
     * @param string $prompt  Optional custom prompt.
     * @param string $style   Optional style override.
     * @param string $title   Optional custom title.
     * @param string $custom_text Optional custom text for image.
     * @param int    $quality Optional quality override.
     * @return int|\WP_Error Attachment ID on success, WP_Error on failure.
     */
    public function generate_image($post_id, $prompt = '', $style = '', $title = '', $custom_text = '', $quality = null) {
        $settings = $this->settings->get_settings();

        $post = get_post($post_id);

        if (!$post) {
            return new \WP_Error(
                'invalid_post',
                __('Invalid post ID.', 'ai-featured-image-generator')
            );
        }

        // Build prompt
        $base_prompt = !empty($title) ? $title : $post->post_title;
        if (!empty($prompt)) {
            $base_prompt .= ' - ' . $prompt;
        }

        // Apply style
        $style = !empty($style) ? $style : $settings['default_style'];
        $final_prompt = $base_prompt;

        // Stability presets (sp_ prefix) are sent as API-level style_preset, not prompt text
        if (strpos($style, 'sp_') === 0) {
            $settings['stability_style_preset'] = substr($style, 3);
        } else {
            $style_prompt = $this->get_style_prompt($style);
            // Only add style prompt if style is not 'none' and prompt is not empty
            if ($style !== 'none' && $style_prompt !== '') {
                $final_prompt .= ' ' . $style_prompt;
            }
        }

        // Handle allow_text setting
        if (empty($settings['allow_text']) || $settings['allow_text'] === false) {
            $final_prompt .= ' no text, no captions, no words, no letters, no writing';
        } else {
            // Use custom text if provided, otherwise use title
            $text_to_add = !empty($custom_text) ? $custom_text : $title;
            $text_to_add = explode('·', $text_to_add)[0]; // Remove any WordPress admin suffixes
            $final_prompt .= 'please add the "' . $text_to_add . '" text on the image ';
        }
    
        // Allow filtering the prompt
        $final_prompt = apply_filters('aifi_build_prompt', $final_prompt, $post, $style);
   
        // Capture old featured image alt text before generating (for preserve option)
        $old_alt_text = '';
        $old_thumb_id = get_post_thumbnail_id($post_id);
        if ($old_thumb_id) {
            $old_alt_text = get_post_meta($old_thumb_id, '_wp_attachment_image_alt', true);
        }

        // Call AI API
        $image_data = $this->call_ai_api($final_prompt, $settings, $quality);
        if (is_wp_error($image_data)) {
            return $image_data;
        }

        // Download and save image
        $attachment_id = $this->save_image($image_data, $post_id, $title);
        if (is_wp_error($attachment_id)) {
            return $attachment_id;
        }

        // Auto alt text -- prefer the frontend-provided $title over $post->post_title
        // because the post may not have been saved yet (e.g. still "Auto Draft")
        $effective_title = !empty($title) ? $title : $post->post_title;
        $this->apply_auto_alt_text($attachment_id, array(
            'title'  => $effective_title,
            'prompt' => !empty($prompt) ? $prompt : $effective_title,
            'style'  => $style,
        ), $old_alt_text);

        // Set as featured image
        set_post_thumbnail($post_id, $attachment_id);

        // Fire action hook
        do_action('aifi_after_generate', $attachment_id, $post_id);

        return $attachment_id;
    }

    /**
     * Get style-specific prompt.
     *
     * @param string $style Style name.
     * @return string
     */
    private function get_style_prompt($style) {
        // Handle custom styles (Pro users)
        if ( strpos($style, 'custom_') === 0 ) {
            $settings = $this->settings->get_settings();
            $custom_styles = isset($settings['custom_styles']) ? (array) $settings['custom_styles'] : array();
            $idx = (int) substr($style, 7);
            return isset($custom_styles[$idx]['prompt']) ? $custom_styles[$idx]['prompt'] : '';
        }

        $styles = array(
            'realistic' => 'in a realistic, photographic style',
            'artistic' => 'in an artistic, painterly style',
            'cartoon' => 'in a cartoon, animated style',
            'sketch' => 'in a detailed sketch style',
            'watercolor' => 'in a beautiful watercolor painting style',
            '3d' => 'as a high-quality 3D render',
            'pixel' => 'in retro pixel art style',
            'cyberpunk' => 'in a vibrant cyberpunk style',
            'fantasy' => 'in a magical fantasy art style',
            'anime' => 'in detailed anime style',
            'minimalist' => 'in a clean minimalist style',
            'technicolor' => 'in vivid technicolor style'
        );

        return isset($styles[$style]) ? $styles[$style] : $styles['realistic'];
    }

    /**
     * Generate an image prompt from a post's title and content using the configured text AI.
     *
     * @param int $post_id Post ID.
     * @return string|\WP_Error Generated prompt string on success, WP_Error on failure.
     */
    public function generate_prompt_from_content($post_id) {
        $post = get_post($post_id);
        if (!$post) {
            return new \WP_Error('invalid_post', __('Invalid post ID.', 'ai-featured-image-generator'));
        }

        $settings = $this->settings->get_settings();
        $provider = isset($settings['ai_provider']) ? $settings['ai_provider'] : 'openai';

        // Stability AI has no text generation API
        if ($provider === 'stability') {
            return new \WP_Error(
                'unsupported_provider',
                __('Stability AI does not support text generation. Please switch to OpenAI, Gemini, or Grok to use this feature.', 'ai-featured-image-generator')
            );
        }

        // Gemini requires premium
        if ($provider === 'gemini' && (!function_exists('afig_fs') || !afig_fs()->can_use_premium_code())) {
            return new \WP_Error(
                'premium_required',
                __('Google Gemini is a Pro feature. Please upgrade to use Gemini, or switch to OpenAI or Grok.', 'ai-featured-image-generator')
            );
        }

        // Extract and clean content
        $title   = trim($post->post_title);
        $content = $post->post_content;
        $content = strip_shortcodes($content);
        $content = wp_strip_all_tags($content);
        $content = preg_replace('/\s+/', ' ', $content);
        $content = trim($content);
        $content = mb_substr($content, 0, 2000);

        if (empty($title) && empty($content)) {
            return new \WP_Error('empty_content', __('This post has no title or content to generate a prompt from.', 'ai-featured-image-generator'));
        }

        // Resolve style for prompt context
        $style = isset($settings['default_style']) ? $settings['default_style'] : 'realistic';
        $style_label = $this->get_style_prompt($style);
        if ($style === 'none' || strpos($style, 'sp_') === 0) {
            $style_label = '';
        }
        if (strpos($style, 'custom_') === 0) {
            $custom_styles = isset($settings['custom_styles']) ? (array) $settings['custom_styles'] : array();
            $idx = (int) substr($style, 7);
            $style_label = isset($custom_styles[$idx]['prompt']) ? $custom_styles[$idx]['prompt'] : '';
        }

        // Resolve text-on-image setting
        $allow_text = !empty($settings['allow_text']) && $settings['allow_text'] !== false;

        // Resolve aspect ratio based on provider
        if ($provider === 'gemini') {
            $aspect_ratio = isset($settings['gemini_aspect_ratio']) ? $settings['gemini_aspect_ratio'] : '16:9';
        } elseif ($provider === 'stability') {
            $aspect_ratio = isset($settings['stability_aspect_ratio']) ? $settings['stability_aspect_ratio'] : '16:9';
        } elseif ($provider === 'grok') {
            $aspect_ratio = isset($settings['grok_aspect_ratio']) ? $settings['grok_aspect_ratio'] : '1:1';
        } else {
            $size = isset($settings['default_size']) ? $settings['default_size'] : '1024x1024';
            $size_to_ratio = array(
                '1024x1024' => '1:1',
                '1536x1024' => '3:2',
                '1024x1536' => '2:3',
            );
            $aspect_ratio = isset($size_to_ratio[$size]) ? $size_to_ratio[$size] : '1:1';
        }

        // Map aspect ratio to composition guidance
        $ratio_parts = explode(':', $aspect_ratio);
        $ratio_value = (int) $ratio_parts[0] / max((int) $ratio_parts[1], 1);
        if ($ratio_value > 1.3) {
            $composition_hint = 'wide/panoramic landscape orientation';
        } elseif ($ratio_value < 0.77) {
            $composition_hint = 'tall/vertical portrait orientation';
        } else {
            $composition_hint = 'square format';
        }

        $system_prompt = 'You are a world-class creative director writing image generation prompts for AI. Given an article title and content, create a single image prompt (2-3 sentences) for a premium blog featured image.

Rules:
- Describe a CONCRETE, real-world scene — real people, objects, environments, or nature. Never use abstract shapes, geometric patterns, data visualizations, or floating icons.
- Use objects, settings, and elements that naturally belong to the article\'s subject area. Do not force metaphors from unrelated fields.
- Avoid complex interfaces, dashboards, code snippets, or detailed text-heavy displays as these render poorly in AI-generated images.
- Never include any text, typography, words, letters, labels, captions, or watermarks in the image.
- VARY your visual approach. Do NOT default to "a person sitting at a desk." Consider: action shots, close-ups of hands working, aerial views, environmental wide shots, still life arrangements, or dramatic angles. Choose what best fits THIS specific article.
- VARY lighting. Do NOT always use golden hour or soft diffused light. Consider: dramatic side lighting, cool blue tones, high-contrast chiaroscuro, bright overhead fluorescent, moody backlight, or warm tungsten — choose lighting that matches the article\'s emotional tone.
- The article\'s emotional tone MUST drive the scene. An article about urgency needs tense, dynamic energy — not calm relaxation. An article about trust needs warmth and stability. Read the article\'s argument and reflect its feeling, not just its topic.
- The scene must feel relevant to BOTH the article\'s topic/industry AND its emotional theme. Ground the scene in the article\'s specific domain.
- Focus on ONE primary subject or scene. Do not compose two subjects with contrasting states.
- NEVER use words like "symbolizing", "representing", "evoking", or "conveying" — image generators render pixels, not concepts. Describe only what is literally visible.
- Write 2-3 short, discrete sentences. No semicolons chaining ideas.
- Do NOT include layout instructions like "leaving space for text" or "negative space on the right." Just describe the scene.
- Do NOT include any style directives (e.g. "in watercolor style", "in anime style") — style will be applied separately.';

        $system_prompt .= "\n\nSettings context:";
        $system_prompt .= "\n- Aspect ratio: " . $aspect_ratio . ' (' . $composition_hint . '). Compose the scene to suit this format.';
        if (!empty($style_label)) {
            $system_prompt .= "\n- The image will be rendered " . $style_label . '. Describe a scene that works well in this style, but do NOT include style directives in your prompt.';
        }
        if ($allow_text) {
            $system_prompt .= "\n- Text will be overlaid on this image, so include some open areas in the composition.";
        } else {
            $system_prompt .= "\n- This image must be completely free of any text or writing.";
        }
        $system_prompt .= "\n\nOutput ONLY the prompt, nothing else.";

        $user_message = '';
        if (!empty($title)) {
            $user_message .= 'Article Title: ' . $title . "\n\n";
        }
        if (!empty($content)) {
            $user_message .= 'Article Content: ' . $content;
        }

        if ($provider === 'gemini') {
            return $this->call_gemini_text_api($system_prompt, $user_message, $settings);
        }

        // OpenAI and Grok share the same API format
        $api_url  = ($provider === 'grok') ? 'https://api.x.ai/v1/chat/completions' : 'https://api.openai.com/v1/chat/completions';
        $api_key  = ($provider === 'grok') ? $settings['grok_api_key'] : (
            !empty($settings['openai_api_key']) ? $settings['openai_api_key'] : (isset($settings['api_key']) ? $settings['api_key'] : '')
        );
        $model    = ($provider === 'grok') ? 'grok-3-mini' : 'gpt-4o-mini';

        return $this->call_openai_compatible_text_api($api_url, $api_key, $model, $system_prompt, $user_message);
    }

    /**
     * Call an OpenAI-compatible text completion API (works for OpenAI and Grok).
     *
     * @param string $api_url       API endpoint URL.
     * @param string $api_key       API key.
     * @param string $model         Model name.
     * @param string $system_prompt System prompt.
     * @param string $user_message  User message.
     * @return string|\WP_Error
     */
    private function call_openai_compatible_text_api($api_url, $api_key, $model, $system_prompt, $user_message) {
        if (empty($api_key)) {
            return new \WP_Error('no_api_key', __('API key is not configured.', 'ai-featured-image-generator'));
        }

        $response = wp_remote_post($api_url, array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type'  => 'application/json',
            ),
            'body'    => wp_json_encode(array(
                'model'      => $model,
                'messages'   => array(
                    array('role' => 'system', 'content' => $system_prompt),
                    array('role' => 'user',   'content' => $user_message),
                ),
                'max_tokens' => 300,
                'temperature' => 0.7,
            )),
            'timeout' => 30,
        ));

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

        $code = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);

        if ($code !== 200) {
            return $this->handle_api_error($code, 'Text API error', $body);
        }

        $data = json_decode($body, true);
        $text = isset($data['choices'][0]['message']['content']) ? trim($data['choices'][0]['message']['content']) : '';

        if (empty($text)) {
            return new \WP_Error('empty_response', __('The AI returned an empty prompt. Please try again.', 'ai-featured-image-generator'));
        }

        return $text;
    }

    /**
     * Call Gemini text generation API.
     *
     * @param string $system_prompt System prompt.
     * @param string $user_message  User message.
     * @param array  $settings      Plugin settings.
     * @return string|\WP_Error
     */
    private function call_gemini_text_api($system_prompt, $user_message, $settings) {
        $api_key = isset($settings['gemini_api_key']) ? $settings['gemini_api_key'] : '';
        if (empty($api_key)) {
            return new \WP_Error('no_api_key', __('Gemini API key is not configured.', 'ai-featured-image-generator'));
        }

        // Always use gemini-2.5-flash-image for text generation, regardless of
        // which image model the user selected. This mirrors OpenAI (gpt-4o-mini)
        // and Grok (grok-3-mini) which also hardcode their text models.
        // Some image models (e.g. gemini-3-pro-image-preview) don't reliably
        // return text-only responses even with responseModalities: ["TEXT"].
        $text_models = array(
            'gemini-2.5-flash-image',
            'gemini-2.5-flash-preview-05-20',
            'gemini-2.0-flash',
            'gemini-1.5-flash',
        );

        $payload = wp_json_encode(array(
            'system_instruction' => array(
                'parts' => array(array('text' => $system_prompt)),
            ),
            'contents' => array(
                array('parts' => array(array('text' => $user_message))),
            ),
            'generationConfig' => array(
                'maxOutputTokens'    => 300,
                'temperature'        => 0.7,
                'responseModalities' => array('TEXT'),
            ),
        ));

        $code = 0;
        $body = '';
        $response = null;

        foreach ($text_models as $text_model) {
            $url = 'https://generativelanguage.googleapis.com/v1beta/models/' . $text_model . ':generateContent';

            $response = wp_remote_post($url, array(
                'headers' => array(
                    'x-goog-api-key' => $api_key,
                    'Content-Type'   => 'application/json',
                ),
                'body'        => $payload,
                'timeout'     => 30,
                'httpversion' => '1.1',
                'sslverify'   => true,
            ));

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

            $code = wp_remote_retrieve_response_code($response);
            $body = wp_remote_retrieve_body($response);

            if ($code === 200) {
                break;
            }

            // 404 means model not found — try the next one
            if ($code === 404) {
                continue;
            }

            // Any other error — stop trying and report it
            break;
        }

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

        if ($code !== 200) {
            return $this->handle_api_error($code, 'Gemini text API error', $body);
        }

        $data = json_decode($body, true);
        $text = isset($data['candidates'][0]['content']['parts'][0]['text'])
            ? trim($data['candidates'][0]['content']['parts'][0]['text'])
            : '';

        if (empty($text)) {
            return new \WP_Error('empty_response', __('The AI returned an empty prompt. Please try again.', 'ai-featured-image-generator'));
        }

        return $text;
    }

    /**
     * Handle API errors with human-readable messages.
     *
     * @param int    $response_code HTTP response code.
     * @param string $error_message Original error message from API.
     * @param string $body          Response body.
     * @return \WP_Error
     */
    private function handle_api_error($response_code, $error_message, $body = '') {
        $error_data = json_decode($body, true);
        
        // DEBUG: Log the actual error response for troubleshooting
        error_log('=== AIFI Debug - API Error ===');
        error_log('Response Code: ' . $response_code);
        error_log('Response Body: ' . substr($body, 0, 500)); // First 500 chars
        error_log('Parsed Error Data: ' . print_r($error_data, true));
        error_log('==============================');
        
        // Try multiple paths for error messages (OpenAI / Gemini / Stability structures)
        $api_error_message = $error_message;
        if (isset($error_data['error']['message'])) {
            $api_error_message = $error_data['error']['message'];
        } elseif (isset($error_data['error']) && is_string($error_data['error'])) {
            $api_error_message = $error_data['error'];
        } elseif (!empty($error_data['errors']) && is_array($error_data['errors'])) {
            // Stability AI returns {name, errors: [string, ...]}
            $strings = array_filter($error_data['errors'], 'is_string');
            if (!empty($strings)) {
                $api_error_message = implode('; ', $strings);
            }
        } elseif (isset($error_data['message']) && is_string($error_data['message'])) {
            $api_error_message = $error_data['message'];
        }
        
        error_log('Extracted Error Message: ' . $api_error_message);
        
        // Common API error patterns and their human-readable equivalents
        $error_mappings = array(
            // Authentication errors
            'invalid_api_key' => __('Your API key is invalid or expired. Please check your API key in the plugin settings.', 'ai-featured-image-generator'),
            'insufficient_quota' => __('You have exceeded your API usage quota. Please check your account billing.', 'ai-featured-image-generator'),
            'billing_not_active' => __('Your account billing is not active. Please add a payment method to your account.', 'ai-featured-image-generator'),
            
            // Content policy errors
            'content_policy' => __('Your prompt contains content that violates the AI provider\'s usage policies. Please modify your prompt and try again.', 'ai-featured-image-generator'),
            'safety_system' => __('Your prompt was rejected by the safety system. Please modify your prompt to avoid potentially harmful content.', 'ai-featured-image-generator'),
            'blocked' => __('Your request was blocked. This may be due to content policy violations. Please modify your prompt and try again.', 'ai-featured-image-generator'),
            
            // Rate limiting
            'rate_limit' => __('You are making requests too quickly. Please wait a moment before trying again.', 'ai-featured-image-generator'),
            'resource_exhausted' => __('API quota exceeded. Please wait or upgrade your account plan.', 'ai-featured-image-generator'),
            
            // Server errors
            'server_error' => __('The AI service is experiencing issues. Please try again in a few minutes.', 'ai-featured-image-generator'),
            'service_unavailable' => __('The image generation service is temporarily unavailable. Please try again later.', 'ai-featured-image-generator'),
        );
        
        // Check for specific error patterns
        $lower_error = strtolower($api_error_message);
        
        // Authentication and billing errors
        if (strpos($lower_error, 'invalid api key') !== false || strpos($lower_error, 'incorrect api key') !== false) {
            return new \WP_Error('api_auth_error', $error_mappings['invalid_api_key'], array('status' => $response_code));
        }
        
        if (strpos($lower_error, 'quota') !== false || strpos($lower_error, 'billing') !== false) {
            return new \WP_Error('api_quota_error', $error_mappings['insufficient_quota'], array('status' => $response_code));
        }
        
        if (strpos($lower_error, 'billing_not_active') !== false) {
            return new \WP_Error('api_billing_error', $error_mappings['billing_not_active'], array('status' => $response_code));
        }
        
        // Content policy errors
        if (strpos($lower_error, 'content policy') !== false || strpos($lower_error, 'usage policy') !== false) {
            return new \WP_Error('api_content_error', $error_mappings['content_policy'], array('status' => $response_code));
        }
        
        if (strpos($lower_error, 'safety system') !== false || strpos($lower_error, 'safety') !== false) {
            return new \WP_Error('api_safety_error', $error_mappings['safety_system'], array('status' => $response_code));
        }
        
        // Rate limiting
        if (strpos($lower_error, 'rate limit') !== false || strpos($lower_error, 'too many requests') !== false || strpos($lower_error, 'resource_exhausted') !== false) {
            return new \WP_Error('api_rate_limit', $error_mappings['rate_limit'], array('status' => $response_code));
        }
        
        // Gemini-specific errors
        if (strpos($lower_error, 'blocked') !== false) {
            return new \WP_Error('api_blocked', $error_mappings['blocked'], array('status' => $response_code));
        }
        
        if (strpos($lower_error, 'api_key_invalid') !== false || strpos($lower_error, 'invalid_argument') !== false) {
            return new \WP_Error('api_auth_error', $error_mappings['invalid_api_key'], array('status' => $response_code));
        }
        
        // Server errors
        if ($response_code >= 500) {
            return new \WP_Error('api_server_error', $error_mappings['server_error'], array('status' => $response_code));
        }
        
        if ($response_code === 503) {
            return new \WP_Error('api_unavailable', $error_mappings['service_unavailable'], array('status' => $response_code));
        }
        
        // Generic error handling based on HTTP status codes
        switch ($response_code) {
            case 400:
                return new \WP_Error('api_bad_request',
                    sprintf(
                        /* translators: %s: underlying error message from the API */
                        __('Invalid request: %s', 'ai-featured-image-generator'),
                        $api_error_message
                    ),
                    array('status' => $response_code));
            case 401:
                return new \WP_Error('api_unauthorized', 
                    __('Authentication failed. Please check your API key.', 'ai-featured-image-generator'), 
                    array('status' => $response_code));
            case 403:
                return new \WP_Error('api_forbidden', 
                    __('Access denied. Please check your API key permissions.', 'ai-featured-image-generator'), 
                    array('status' => $response_code));
            case 404:
                return new \WP_Error('api_not_found', 
                    __('The requested service was not found. Please try again later.', 'ai-featured-image-generator'), 
                    array('status' => $response_code));
            case 402:
                return new \WP_Error('api_payment_required',
                    __('Insufficient credits. Please add credits to your Stability AI account to use this feature.', 'ai-featured-image-generator'),
                    array('status' => $response_code));
            case 429:
                return new \WP_Error('api_rate_limit',
                    __('Too many requests. Please wait a moment before trying again.', 'ai-featured-image-generator'),
                    array('status' => $response_code));
            default:
                // Fallback to original error message if no specific mapping found
                return new \WP_Error('api_error', 
                    sprintf(__('API error (HTTP %d): %s', 'ai-featured-image-generator'), $response_code, $api_error_message), 
                    array('status' => $response_code));
        }
    }

    /**
     * Call AI image generation API.
     *
     * @param string $prompt   Image prompt.
     * @param array  $settings Plugin settings.
     * @param int    $quality  Optional quality override.
     * @return string|\WP_Error Image data on success, WP_Error on failure.
     */
    private function call_ai_api($prompt, $settings, $quality = null) {
        require_once(ABSPATH . 'wp-admin/includes/file.php');
        
        $provider = isset($settings['ai_provider']) ? $settings['ai_provider'] : 'openai';
        
        // Route to provider-specific method
        if ($provider === 'gemini') {
            // Premium check: Gemini is only available for premium users
            if ( ! function_exists('afig_fs') || ! afig_fs()->can_use_premium_code() ) {
                return new \WP_Error(
                    'premium_required',
                    __('Google Gemini is a Pro feature. Please upgrade to use Gemini for image generation, or switch to OpenAI in the plugin settings.', 'ai-featured-image-generator')
                );
            }
            if (afig_fs()->is__premium_only()) {
              return $this->call_gemini_api($prompt, $settings, $quality);
            }
        } elseif ($provider === 'stability') {
            return $this->call_stability_api($prompt, $settings, $quality);
        } elseif ($provider === 'grok') {
            return $this->call_grok_api($prompt, $settings, $quality);
        } else {
            return $this->call_openai_api($prompt, $settings, $quality);
        }
    }

    /**
     * Call OpenAI image generation API.
     *
     * @param string $prompt   Image prompt.
     * @param array  $settings Plugin settings.
     * @param int    $quality  Optional quality override.
     * @return string|\WP_Error Image data on success, WP_Error on failure.
     */
    private function call_openai_api($prompt, $settings, $quality = null) {
        $api_url = 'https://api.openai.com/v1/images/generations';
        $size = isset($settings['default_size']) ? $settings['default_size'] : '1024x1024';

        $output_format = isset($settings['output_format']) ? $settings['output_format'] : 'webp';
        $quality = $quality !== null ? intval($quality) : (isset($settings['image_quality']) ? intval($settings['image_quality']) : 90);
        
        $model = isset($settings['ai_model']) ? $settings['ai_model'] : 'chatgpt-image-latest';
        
        // Get OpenAI API key (support legacy 'api_key' field)
        $api_key = isset($settings['openai_api_key']) ? $settings['openai_api_key'] : (isset($settings['api_key']) ? $settings['api_key'] : '');
        
        // Generation quality tier (low / medium / high) — supported by all GPT Image models.
        $generation_quality = isset($settings['openai_quality']) ? $settings['openai_quality'] : 'high';

        // GPT Image 2 exclusive params.
        $is_gpt2 = ( $model === 'gpt-image-2' );
        $background = $is_gpt2 && isset($settings['openai_background']) ? $settings['openai_background'] : 'auto';
        $moderation = $is_gpt2 && isset($settings['openai_moderation']) ? $settings['openai_moderation'] : 'auto';

        // Transparent background requires PNG — override the format for this request only.
        if ( $is_gpt2 && $background === 'transparent' && $output_format !== 'png' ) {
            $output_format = 'png';
        }

        // Build the API request body
        $body = array(
            'model'          => $model,
            'prompt'         => $prompt,
            'n'              => 1,
            'size'           => $size,
            'output_format'  => $output_format,
            'quality'        => $generation_quality,
        );

        // Only include output_compression for lossy formats (webp, jpeg)
        // PNG is lossless and doesn't support compression parameter
        if ($output_format !== 'png') {
            $body['output_compression'] = $quality;
        }

        // GPT Image 2 exclusive: background and moderation (only send non-default values).
        if ( $is_gpt2 ) {
            if ( $background !== 'auto' ) {
                $body['background'] = $background;
            }
            if ( $moderation !== 'auto' ) {
                $body['moderation'] = $moderation;
            }
        }

        $args = array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'application/json'
            ),
            'body' => json_encode($body),
            'timeout' => 240,
            'httpversion' => '1.1',
            'sslverify' => true,
            'blocking' => true
        );

        // Allow filtering API request arguments
        $args = apply_filters('aifi_api_request_args', $args);

        $response = wp_remote_post($api_url, $args);

        if (is_wp_error($response)) {
            $error_message = $response->get_error_message();
            
            // Handle timeout specifically
            if (strpos($error_message, 'timed out') !== false) {
                return new \WP_Error(
                    'api_timeout',
                    __('The request timed out. The image generation is taking longer than expected. Please try again.', 'ai-featured-image-generator')
                );
            }
            
            // Handle connection errors
            if (strpos($error_message, 'could not resolve host') !== false) {
                return new \WP_Error(
                    'api_connection_error',
                    __('Unable to connect to OpenAI servers. Please check your internet connection and try again.', 'ai-featured-image-generator')
                );
            }
            
            // Handle SSL errors
            if (strpos($error_message, 'ssl') !== false || strpos($error_message, 'certificate') !== false) {
                return new \WP_Error(
                    'api_ssl_error',
                    __('SSL connection error. Please try again or contact your hosting provider.', 'ai-featured-image-generator')
                );
            }
            
            // Generic connection error
            return new \WP_Error(
                'api_connection_error',
                sprintf(
                    __('Connection failed: %s', 'ai-featured-image-generator'),
                    $error_message
                )
            );
        }

        $response_code = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);

        if ($response_code !== 200) {
            $error_message = __('Unknown API error occurred.', 'ai-featured-image-generator');
            return $this->handle_api_error($response_code, $error_message, $body);
        }

        // Parse the JSON response
        $response_data = json_decode($body, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            return new \WP_Error(
                'api_parse_error',
                __('Invalid response from OpenAI API. Please try again.', 'ai-featured-image-generator')
            );
        }
        
        if (!isset($response_data['data'][0]['b64_json'])) {
            return new \WP_Error(
                'api_data_error',
                __('No image data found in API response. The image generation may have failed.', 'ai-featured-image-generator')
            );
        }

        // Decode the base64 image data
        $image_data = base64_decode($response_data['data'][0]['b64_json']);
        if ($image_data === false) {
            return new \WP_Error(
                'api_decode_error',
                __('Failed to process the generated image data. Please try again.', 'ai-featured-image-generator')
            );
        }

        // Save the image data to a temporary file
        $temp_file = wp_tempnam('aifi-');
        if (!$temp_file) {
            return new \WP_Error(
                'api_temp_file_error',
                __('Unable to create temporary file for the image. Please check your server permissions.', 'ai-featured-image-generator')
            );
        }

        file_put_contents($temp_file, $image_data);

        return $temp_file;
    }

    /**
     * Call Google Gemini image generation API.
     *
     * @param string $prompt   Image prompt.
     * @param array  $settings Plugin settings.
     * @param int    $quality  Optional quality override (not used by Gemini).
     * @return string|\WP_Error Image data on success, WP_Error on failure.
     */
    private function call_gemini_api($prompt, $settings, $quality = null) {
        // Double-check premium status (defense in depth)
        if ( ! function_exists('afig_fs') || ! afig_fs()->can_use_premium_code() ) {
            return new \WP_Error(
                'premium_required',
                __('Google Gemini is a Pro feature. Please upgrade to use Gemini for image generation.', 'ai-featured-image-generator')
            );
        }

        if ( afig_fs()->is__premium_only() ) {
        
            $model = isset($settings['ai_model']) ? $settings['ai_model'] : 'gemini-2.5-flash-image';
            $api_key = isset($settings['gemini_api_key']) ? $settings['gemini_api_key'] : '';
            
            // Build API URL with model
            $api_url = 'https://generativelanguage.googleapis.com/v1beta/models/' . $model . ':generateContent';
            
            $aspect_ratio = isset($settings['gemini_aspect_ratio']) ? $settings['gemini_aspect_ratio'] : '16:9';
            $image_size = isset($settings['gemini_image_size']) ? $settings['gemini_image_size'] : '1K';

            // If the model is gemini-2.5-flash-image, omit 'imageSize' from the payload.
            if ($model === 'gemini-2.5-flash-image') {
                $payload = array(
                    'contents' => array(
                        array(
                            'parts' => array(
                                array('text' => $prompt)
                            )
                        )
                    ),
                    'generationConfig' => array(
                        'imageConfig' => array(
                            'aspectRatio' => $aspect_ratio
                        )
                    )
                );
            } else {
                $payload = array(
                    'contents' => array(
                        array(
                            'parts' => array(
                                array('text' => $prompt)
                            )
                        )
                    ),
                    'generationConfig' => array(
                        'imageConfig' => array(
                            'aspectRatio' => $aspect_ratio,
                            'imageSize' => $image_size
                        )
                    )
                );
            }
            
            // DEBUG: Log the API request
            error_log('=== AIFI Debug - Gemini API Request ===');
            error_log('URL: ' . $api_url);
            error_log('Model: ' . $model);
            error_log('Has API Key: ' . (!empty($api_key) ? 'Yes' : 'No'));
            error_log('Payload: ' . json_encode($payload, JSON_PRETTY_PRINT));
            error_log('======================================');
            
            $args = array(
                'headers' => array(
                    'x-goog-api-key' => $api_key,
                    'Content-Type' => 'application/json'
                ),
                'body' => json_encode($payload),
                'timeout' => 240,
                'httpversion' => '1.1',
                'sslverify' => true,
                'blocking' => true
            );

            // Allow filtering API request arguments
            $args = apply_filters('aifi_api_request_args', $args);

            $response = wp_remote_post($api_url, $args);
            if (is_wp_error($response)) {
                $error_message = $response->get_error_message();
                
                // Handle timeout specifically
                if (strpos($error_message, 'timed out') !== false) {
                    return new \WP_Error(
                        'api_timeout',
                        __('The request timed out. The image generation is taking longer than expected. Please try again.', 'ai-featured-image-generator')
                    );
                }
                
                // Handle connection errors
                if (strpos($error_message, 'could not resolve host') !== false) {
                    return new \WP_Error(
                        'api_connection_error',
                        __('Unable to connect to Google Gemini servers. Please check your internet connection and try again.', 'ai-featured-image-generator')
                    );
                }
                
                // Handle SSL errors
                if (strpos($error_message, 'ssl') !== false || strpos($error_message, 'certificate') !== false) {
                    return new \WP_Error(
                        'api_ssl_error',
                        __('SSL connection error. Please try again or contact your hosting provider.', 'ai-featured-image-generator')
                    );
                }
                
                // Generic connection error
                return new \WP_Error(
                    'api_connection_error',
                    sprintf(
                        __('Connection failed: %s', 'ai-featured-image-generator'),
                        $error_message
                    )
                );
            }

            $response_code = wp_remote_retrieve_response_code($response);
            $body = wp_remote_retrieve_body($response);

            if ($response_code !== 200) {
                $error_message = __('Unknown API error occurred.', 'ai-featured-image-generator');
                return $this->handle_api_error($response_code, $error_message, $body);
            }

            // Parse the JSON response
            $response_data = json_decode($body, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                return new \WP_Error(
                    'api_parse_error',
                    __('Invalid response from Google Gemini API. Please try again.', 'ai-featured-image-generator')
                );
            }
            
            // Extract base64 image data from Gemini response
            // The response structure should be: candidates[0].content.parts[0].inlineData.data
            if (!isset($response_data['candidates'][0]['content']['parts'][0]['inlineData']['data']) AND !isset($response_data['candidates'][0]['content']['parts'][1]['inlineData']['data'])) {
                return new \WP_Error(
                    'api_data_error',
                    __('No image data found in API response. The image generation may have failed.', 'ai-featured-image-generator')
                );
            }

            if (isset($response_data['candidates'][0]['content']['parts'][0]['inlineData']['data'])) {
                $image_data = base64_decode($response_data['candidates'][0]['content']['parts'][0]['inlineData']['data']);
            } else {
                $image_data = base64_decode($response_data['candidates'][0]['content']['parts'][1]['inlineData']['data']);
            }

            // Decode the base64 image data
            if ($image_data === false) {
                return new \WP_Error(
                    'api_decode_error',
                    __('Failed to process the generated image data. Please try again.', 'ai-featured-image-generator')
                );
            }

            // Save the image data to a temporary file
            $temp_file = wp_tempnam('aifi-');
            if (!$temp_file) {
                return new \WP_Error(
                    'api_temp_file_error',
                    __('Unable to create temporary file for the image. Please check your server permissions.', 'ai-featured-image-generator')
                );
            }

            file_put_contents($temp_file, $image_data);

            return $temp_file;
        }else {
            return new \WP_Error(
                'premium_required',
                __('Google Gemini is a Pro feature. Please upgrade to use Gemini for image generation.', 'ai-featured-image-generator')
            );
        }
    }

    /**
     * Call Stability AI image generation API.
     *
     * @param string $prompt   Image prompt.
     * @param array  $settings Plugin settings.
     * @param int    $quality  Optional quality override (not used by Stability).
     * @return string|\WP_Error Temp file path on success, WP_Error on failure.
     */
    private function call_stability_api($prompt, $settings, $quality = null) {
        $model = isset($settings['ai_model']) ? $settings['ai_model'] : 'core';
        $api_key = isset($settings['stability_api_key']) ? $settings['stability_api_key'] : '';
        $aspect_ratio = isset($settings['stability_aspect_ratio']) ? $settings['stability_aspect_ratio'] : '16:9';
        $style_preset = isset($settings['stability_style_preset']) ? $settings['stability_style_preset'] : '';

        $can_use_premium = function_exists('afig_fs') && afig_fs()->can_use_premium_code();
        $output_format = ($can_use_premium && isset($settings['stability_output_format'])) ? $settings['stability_output_format'] : 'png';
        $negative_prompt = ($can_use_premium && isset($settings['stability_negative_prompt'])) ? $settings['stability_negative_prompt'] : '';

        // Free users can only use 'core' model
        if ( ! $can_use_premium && $model !== 'core' ) {
            $model = 'core';
        }

        // Determine endpoint based on model
        if ($model === 'core') {
            $api_url = 'https://api.stability.ai/v2beta/stable-image/generate/core';
        } elseif ($model === 'ultra') {
            $api_url = 'https://api.stability.ai/v2beta/stable-image/generate/ultra';
        } else {
            $api_url = 'https://api.stability.ai/v2beta/stable-image/generate/sd3';
        }

        // Build multipart form data
        $boundary = wp_generate_password(24, false);
        $body = '';

        // Add prompt
        $body .= '--' . $boundary . "\r\n";
        $body .= 'Content-Disposition: form-data; name="prompt"' . "\r\n\r\n";
        $body .= $prompt . "\r\n";

        // Add model (only for sd3 endpoint)
        if ($model !== 'core' && $model !== 'ultra') {
            $body .= '--' . $boundary . "\r\n";
            $body .= 'Content-Disposition: form-data; name="model"' . "\r\n\r\n";
            $body .= $model . "\r\n";
        }

        // Add aspect ratio
        $body .= '--' . $boundary . "\r\n";
        $body .= 'Content-Disposition: form-data; name="aspect_ratio"' . "\r\n\r\n";
        $body .= $aspect_ratio . "\r\n";

        // Add output format
        $body .= '--' . $boundary . "\r\n";
        $body .= 'Content-Disposition: form-data; name="output_format"' . "\r\n\r\n";
        $body .= $output_format . "\r\n";

        // Add style preset if set
        if (!empty($style_preset)) {
            $body .= '--' . $boundary . "\r\n";
            $body .= 'Content-Disposition: form-data; name="style_preset"' . "\r\n\r\n";
            $body .= $style_preset . "\r\n";
        }

        // Add negative prompt if set (Pro only)
        if (!empty($negative_prompt)) {
            $body .= '--' . $boundary . "\r\n";
            $body .= 'Content-Disposition: form-data; name="negative_prompt"' . "\r\n\r\n";
            $body .= $negative_prompt . "\r\n";
        }

        $body .= '--' . $boundary . '--' . "\r\n";

        $args = array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
                'Accept' => 'application/json',
            ),
            'body' => $body,
            'timeout' => 240,
            'httpversion' => '1.1',
            'sslverify' => true,
            'blocking' => true,
        );

        $args = apply_filters('aifi_api_request_args', $args);
        $response = wp_remote_post($api_url, $args);

        if (is_wp_error($response)) {
            $error_message = $response->get_error_message();
            if (strpos($error_message, 'timed out') !== false) {
                return new \WP_Error('api_timeout', __('The request timed out. Please try again.', 'ai-featured-image-generator'));
            }
            return new \WP_Error('api_connection_error', sprintf(__('Connection failed: %s', 'ai-featured-image-generator'), $error_message));
        }

        $response_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);

        if ($response_code !== 200) {
            return $this->handle_api_error($response_code, __('Unknown API error occurred.', 'ai-featured-image-generator'), $response_body);
        }

        $response_data = json_decode($response_body, true);
        if (json_last_error() !== JSON_ERROR_NONE || !isset($response_data['image'])) {
            return new \WP_Error('api_data_error', __('No image data found in Stability AI response. Please try again.', 'ai-featured-image-generator'));
        }

        $image_data = base64_decode($response_data['image']);
        if ($image_data === false) {
            return new \WP_Error('api_decode_error', __('Failed to process the generated image data. Please try again.', 'ai-featured-image-generator'));
        }

        $temp_file = wp_tempnam('aifi-');
        if (!$temp_file) {
            return new \WP_Error('api_temp_file_error', __('Unable to create temporary file. Please check server permissions.', 'ai-featured-image-generator'));
        }

        file_put_contents($temp_file, $image_data);
        return $temp_file;
    }

    /**
     * Call Grok / xAI image generation API.
     *
     * @param string $prompt   Image prompt.
     * @param array  $settings Plugin settings.
     * @param int    $quality  Optional quality override (not used by Grok).
     * @return string|\WP_Error Temp file path on success, WP_Error on failure.
     */
    private function call_grok_api($prompt, $settings, $quality = null) {
        $api_url = 'https://api.x.ai/v1/images/generations';
        $api_key = isset($settings['grok_api_key']) ? $settings['grok_api_key'] : '';
        $model = isset($settings['ai_model']) ? $settings['ai_model'] : 'grok-imagine-image';

        $can_use_premium = function_exists('afig_fs') && afig_fs()->can_use_premium_code();

        // Free users can only use standard model
        if ( ! $can_use_premium && $model !== 'grok-imagine-image' ) {
            $model = 'grok-imagine-image';
        }

        $aspect_ratio = isset($settings['grok_aspect_ratio']) ? $settings['grok_aspect_ratio'] : '1:1';
        $resolution = isset($settings['grok_resolution']) ? $settings['grok_resolution'] : '1k';

        // Free users can only use 1K resolution
        if ( ! $can_use_premium ) {
            $resolution = '1k';
        }

        $body = array(
            'model'           => $model,
            'prompt'          => $prompt,
            'n'               => 1,
            'response_format' => 'b64_json',
            'aspect_ratio'    => $aspect_ratio,
            'resolution'      => $resolution,
        );

        $args = array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'application/json',
            ),
            'body' => json_encode($body),
            'timeout' => 240,
            'httpversion' => '1.1',
            'sslverify' => true,
            'blocking' => true,
        );

        $args = apply_filters('aifi_api_request_args', $args);
        $response = wp_remote_post($api_url, $args);

        if (is_wp_error($response)) {
            $error_message = $response->get_error_message();
            if (strpos($error_message, 'timed out') !== false) {
                return new \WP_Error('api_timeout', __('The request timed out. Please try again.', 'ai-featured-image-generator'));
            }
            return new \WP_Error('api_connection_error', sprintf(__('Connection failed: %s', 'ai-featured-image-generator'), $error_message));
        }

        $response_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);

        if ($response_code !== 200) {
            return $this->handle_api_error($response_code, __('Unknown API error occurred.', 'ai-featured-image-generator'), $response_body);
        }

        $response_data = json_decode($response_body, true);
        if (json_last_error() !== JSON_ERROR_NONE || !isset($response_data['data'][0]['b64_json'])) {
            return new \WP_Error('api_data_error', __('No image data found in Grok API response. Please try again.', 'ai-featured-image-generator'));
        }

        $image_data = base64_decode($response_data['data'][0]['b64_json']);
        if ($image_data === false) {
            return new \WP_Error('api_decode_error', __('Failed to process the generated image data. Please try again.', 'ai-featured-image-generator'));
        }

        $temp_file = wp_tempnam('aifi-');
        if (!$temp_file) {
            return new \WP_Error('api_temp_file_error', __('Unable to create temporary file. Please check server permissions.', 'ai-featured-image-generator'));
        }

        file_put_contents($temp_file, $image_data);
        return $temp_file;
    }

    /**
     * Upscale an existing featured image using Stability AI.
     *
     * @param int    $post_id Post ID.
     * @param string $mode    Upscale mode: 'conservative', 'creative', or 'fast'.
     * @return int|\WP_Error New attachment ID on success, WP_Error on failure.
     */
    public function upscale_image($post_id, $mode = 'conservative') {
        require_once(ABSPATH . 'wp-admin/includes/file.php');
        require_once(ABSPATH . 'wp-admin/includes/media.php');
        require_once(ABSPATH . 'wp-admin/includes/image.php');

        $settings = $this->settings->get_settings();
        $api_key = isset($settings['stability_api_key']) ? $settings['stability_api_key'] : '';

        if (empty($api_key)) {
            return new \WP_Error('no_api_key', __('Stability AI API key is required for upscaling.', 'ai-featured-image-generator'));
        }

        // Get current featured image
        $thumbnail_id = get_post_thumbnail_id($post_id);
        if (!$thumbnail_id) {
            return new \WP_Error('no_featured_image', __('No featured image found to upscale.', 'ai-featured-image-generator'));
        }

        $image_path = get_attached_file($thumbnail_id);
        if (!$image_path || !file_exists($image_path)) {
            return new \WP_Error('image_not_found', __('Featured image file not found.', 'ai-featured-image-generator'));
        }

        // Validate mode
        $allowed_modes = array('conservative', 'creative', 'fast');
        if (!in_array($mode, $allowed_modes, true)) {
            $mode = 'conservative';
        }

        // Preprocess: Stability's upscale modes have strict input size limits.
        // Conservative/Creative: max 1 MP. Fast: max 1536x1536 (~2.36 MP).
        // If the source is larger, resize it down so the API accepts it.
        $prepared = $this->prepare_upscale_source($image_path, $mode);
        if (is_wp_error($prepared)) {
            return $prepared;
        }
        $upload_path = $prepared['path'];
        $resized_temp = $prepared['temp'];

        $api_url = 'https://api.stability.ai/v2beta/stable-image/upscale/' . $mode;

        // Build multipart form data with image file
        $boundary = wp_generate_password(24, false);
        $body = '';

        // Add image file
        $image_content = file_get_contents($upload_path);
        $mime = mime_content_type($upload_path);
        $filename = basename($upload_path);
        $body .= '--' . $boundary . "\r\n";
        $body .= 'Content-Disposition: form-data; name="image"; filename="' . $filename . '"' . "\r\n";
        $body .= 'Content-Type: ' . $mime . "\r\n\r\n";
        $body .= $image_content . "\r\n";

        // Resized source already buffered into $body — remove the temp file now
        // so we don't need to track cleanup across every downstream exit path.
        if ($resized_temp && file_exists($resized_temp)) {
            wp_delete_file($resized_temp);
        }
        unset($image_content);

        // Conservative and Creative require a prompt parameter
        if ($mode === 'conservative' || $mode === 'creative') {
            $post = get_post($post_id);
            $prompt = $post ? $post->post_title : 'Upscale this image';
            $body .= '--' . $boundary . "\r\n";
            $body .= 'Content-Disposition: form-data; name="prompt"' . "\r\n\r\n";
            $body .= $prompt . "\r\n";
        }

        // Add output_format for conservative and creative
        if ($mode === 'conservative' || $mode === 'creative') {
            $body .= '--' . $boundary . "\r\n";
            $body .= 'Content-Disposition: form-data; name="output_format"' . "\r\n\r\n";
            $body .= 'png' . "\r\n";
        }

        $body .= '--' . $boundary . '--' . "\r\n";

        $args = array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
                'Accept' => 'application/json',
            ),
            'body' => $body,
            'timeout' => 300,
            'httpversion' => '1.1',
            'sslverify' => true,
            'blocking' => true,
        );

        $response = wp_remote_post($api_url, $args);

        if (is_wp_error($response)) {
            return new \WP_Error('api_connection_error', sprintf(__('Connection failed: %s', 'ai-featured-image-generator'), $response->get_error_message()));
        }

        $response_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);

        // Conservative and Creative return 202 with a generation ID for async polling
        if ($response_code === 202) {
            $response_data = json_decode($response_body, true);
            $generation_id = isset($response_data['id']) ? $response_data['id'] : '';

            if (empty($generation_id)) {
                return new \WP_Error('api_data_error', __('No generation ID received from upscale request.', 'ai-featured-image-generator'));
            }

            // Poll for the result (max ~3 minutes with 10s intervals)
            $result_url = 'https://api.stability.ai/v2beta/stable-image/upscale/' . $mode . '/result/' . $generation_id;
            $max_attempts = 18;

            for ($i = 0; $i < $max_attempts; $i++) {
                sleep(10);

                $poll_response = wp_remote_get($result_url, array(
                    'headers' => array(
                        'Authorization' => 'Bearer ' . $api_key,
                        'Accept' => 'application/json',
                    ),
                    'timeout' => 30,
                    'httpversion' => '1.1',
                    'sslverify' => true,
                ));

                if (is_wp_error($poll_response)) {
                    continue;
                }

                $poll_code = wp_remote_retrieve_response_code($poll_response);

                if ($poll_code === 200) {
                    $response_body = wp_remote_retrieve_body($poll_response);
                    $response_code = 200;
                    break;
                }

                // 202 means still processing
                if ($poll_code !== 202) {
                    $poll_body = wp_remote_retrieve_body($poll_response);
                    return $this->handle_api_error($poll_code, __('Upscaling failed during processing.', 'ai-featured-image-generator'), $poll_body);
                }
            }

            if ($response_code !== 200) {
                return new \WP_Error('api_timeout', __('Upscaling timed out. The image may be too large or the server is busy. Please try again.', 'ai-featured-image-generator'));
            }
        }

        if ($response_code !== 200) {
            return $this->handle_api_error($response_code, __('Upscaling failed.', 'ai-featured-image-generator'), $response_body);
        }

        $response_data = json_decode($response_body, true);
        if (json_last_error() !== JSON_ERROR_NONE || !isset($response_data['image'])) {
            return new \WP_Error('api_data_error', __('No image data found in upscale response.', 'ai-featured-image-generator'));
        }

        $upscaled_data = base64_decode($response_data['image']);
        if ($upscaled_data === false) {
            return new \WP_Error('api_decode_error', __('Failed to process upscaled image data.', 'ai-featured-image-generator'));
        }

        // Save upscaled image to temp file
        $temp_file = wp_tempnam('aifi-upscale-');
        file_put_contents($temp_file, $upscaled_data);

        // Get post title for filename
        if (!isset($post)) {
            $post = get_post($post_id);
        }
        $clean_title = $post ? explode('·', $post->post_title)[0] : 'upscaled';
        $clean_title = trim($clean_title);
        $filename_base = sanitize_file_name($clean_title . '-upscaled');

        $file_array = array(
            'name' => $filename_base . '.png',
            'tmp_name' => $temp_file,
        );

        $attachment_id = media_handle_sideload($file_array, $post_id);

        if (file_exists($temp_file)) {
            wp_delete_file($temp_file);
        }

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

        // Set as new featured image
        set_post_thumbnail($post_id, $attachment_id);

        return $attachment_id;
    }

    /**
     * Validate and, if necessary, downscale the source image so Stability AI
     * will accept it as upscale input.
     *
     * Stability limits (per mode):
     *   conservative / creative: 64x64 min, 1,048,576 total pixels max
     *   fast:                    32x32 min, 1536x1536 max
     * Aspect ratio (all modes): between 1:2.5 and 2.5:1.
     *
     * @param string $image_path Absolute path to source image.
     * @param string $mode       Upscale mode.
     * @return array|\WP_Error {path: string, temp: string|null} on success.
     */
    private function prepare_upscale_source($image_path, $mode) {
        $limits = array(
            'conservative' => array('min' => 64, 'max_pixels' => 1048576),
            'creative'     => array('min' => 64, 'max_pixels' => 1048576),
            'fast'         => array('min' => 32, 'max_w' => 1536, 'max_h' => 1536),
        );
        $limit = isset($limits[$mode]) ? $limits[$mode] : $limits['conservative'];

        $dims = @getimagesize($image_path);
        if (!$dims) {
            return new \WP_Error('image_read_error', __('Could not read the featured image. It may be corrupted or in an unsupported format.', 'ai-featured-image-generator'));
        }
        list($width, $height) = $dims;

        if ($width < $limit['min'] || $height < $limit['min']) {
            return new \WP_Error(
                'image_too_small',
                sprintf(
                    /* translators: 1: current width, 2: current height, 3: minimum dimension */
                    __('This image is too small to upscale (%1$d×%2$d). Please use an image at least %3$d×%3$d pixels.', 'ai-featured-image-generator'),
                    $width, $height, $limit['min']
                )
            );
        }

        $ratio = $width / $height;
        if ($ratio > 2.5) {
            return new \WP_Error(
                'invalid_aspect_ratio',
                sprintf(
                    /* translators: 1: width, 2: height */
                    __('This image is too wide to upscale (%1$d×%2$d). Try an image that is closer to square — the width cannot be more than 2.5× the height.', 'ai-featured-image-generator'),
                    $width, $height
                )
            );
        }
        if ($ratio < 0.4) {
            return new \WP_Error(
                'invalid_aspect_ratio',
                sprintf(
                    /* translators: 1: width, 2: height */
                    __('This image is too tall to upscale (%1$d×%2$d). Try an image that is closer to square — the height cannot be more than 2.5× the width.', 'ai-featured-image-generator'),
                    $width, $height
                )
            );
        }

        // Figure out target dimensions when the source exceeds the mode's limits.
        $new_w = $width;
        $new_h = $height;

        if ($mode === 'fast') {
            if ($width > $limit['max_w'] || $height > $limit['max_h']) {
                $scale = min($limit['max_w'] / $width, $limit['max_h'] / $height);
                $new_w = (int) floor($width * $scale);
                $new_h = (int) floor($height * $scale);
            }
        } else {
            if (($width * $height) > $limit['max_pixels']) {
                // 0.99 safety margin — floor() rounding can otherwise land one pixel over.
                $scale = sqrt($limit['max_pixels'] / ($width * $height)) * 0.99;
                $new_w = (int) floor($width * $scale);
                $new_h = (int) floor($height * $scale);
            }
        }

        if ($new_w === $width && $new_h === $height) {
            return array('path' => $image_path, 'temp' => null);
        }

        $editor = wp_get_image_editor($image_path);
        if (is_wp_error($editor)) {
            return $editor;
        }

        $resized = $editor->resize($new_w, $new_h, false);
        if (is_wp_error($resized)) {
            return $resized;
        }

        $temp_path = wp_tempnam('aifi-upscale-src-' . $mode . '.png');
        $saved = $editor->save($temp_path, 'image/png');
        if (is_wp_error($saved)) {
            if (file_exists($temp_path)) {
                wp_delete_file($temp_path);
            }
            return $saved;
        }

        return array('path' => $saved['path'], 'temp' => $saved['path']);
    }

    /**
     * Remove background from an existing featured image using Stability AI.
     *
     * @param int $post_id Post ID.
     * @return int|\WP_Error New attachment ID on success, WP_Error on failure.
     */
    public function remove_background($post_id) {
        require_once(ABSPATH . 'wp-admin/includes/file.php');
        require_once(ABSPATH . 'wp-admin/includes/media.php');
        require_once(ABSPATH . 'wp-admin/includes/image.php');

        $settings = $this->settings->get_settings();
        $api_key = isset($settings['stability_api_key']) ? $settings['stability_api_key'] : '';

        if (empty($api_key)) {
            return new \WP_Error('no_api_key', __('Stability AI API key is required for background removal.', 'ai-featured-image-generator'));
        }

        // Get current featured image
        $thumbnail_id = get_post_thumbnail_id($post_id);
        if (!$thumbnail_id) {
            return new \WP_Error('no_featured_image', __('No featured image found.', 'ai-featured-image-generator'));
        }

        $image_path = get_attached_file($thumbnail_id);
        if (!$image_path || !file_exists($image_path)) {
            return new \WP_Error('image_not_found', __('Featured image file not found.', 'ai-featured-image-generator'));
        }

        $api_url = 'https://api.stability.ai/v2beta/stable-image/edit/remove-background';

        // Build multipart form data with image file
        $boundary = wp_generate_password(24, false);
        $body = '';

        $image_content = file_get_contents($image_path);
        $filename = basename($image_path);
        $body .= '--' . $boundary . "\r\n";
        $body .= 'Content-Disposition: form-data; name="image"; filename="' . $filename . '"' . "\r\n";
        $body .= 'Content-Type: ' . mime_content_type($image_path) . "\r\n\r\n";
        $body .= $image_content . "\r\n";

        $body .= '--' . $boundary . '--' . "\r\n";

        $args = array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
                'Accept' => 'application/json',
            ),
            'body' => $body,
            'timeout' => 300,
            'httpversion' => '1.1',
            'sslverify' => true,
            'blocking' => true,
        );

        $response = wp_remote_post($api_url, $args);

        if (is_wp_error($response)) {
            return new \WP_Error('api_connection_error', sprintf(__('Connection failed: %s', 'ai-featured-image-generator'), $response->get_error_message()));
        }

        $response_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);

        if ($response_code !== 200) {
            return $this->handle_api_error($response_code, __('Background removal failed.', 'ai-featured-image-generator'), $response_body);
        }

        $response_data = json_decode($response_body, true);
        if (json_last_error() !== JSON_ERROR_NONE || !isset($response_data['image'])) {
            return new \WP_Error('api_data_error', __('No image data found in background removal response.', 'ai-featured-image-generator'));
        }

        $result_data = base64_decode($response_data['image']);
        if ($result_data === false) {
            return new \WP_Error('api_decode_error', __('Failed to process image data.', 'ai-featured-image-generator'));
        }

        // Save result to temp file
        $temp_file = wp_tempnam('aifi-rmbg-');
        file_put_contents($temp_file, $result_data);

        // Get post title for filename
        $post = get_post($post_id);
        $clean_title = $post ? explode('·', $post->post_title)[0] : 'no-bg';
        $clean_title = trim($clean_title);
        $filename_base = sanitize_file_name($clean_title . '-no-bg');

        $file_array = array(
            'name' => $filename_base . '.png',
            'tmp_name' => $temp_file,
        );

        $attachment_id = media_handle_sideload($file_array, $post_id);

        if (file_exists($temp_file)) {
            wp_delete_file($temp_file);
        }

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

        // Set as new featured image
        set_post_thumbnail($post_id, $attachment_id);

        return $attachment_id;
    }

    /**
     * Save image to media library.
     *
     * @param string $image_data Image data or temporary file path.
     * @param int    $post_id   Post ID.
     * @return int|\WP_Error Attachment ID on success, WP_Error on failure.
     */
    private function save_image($image_data, $post_id, $title) {
        require_once(ABSPATH . 'wp-admin/includes/media.php');
        require_once(ABSPATH . 'wp-admin/includes/file.php');
        require_once(ABSPATH . 'wp-admin/includes/image.php');

        // If we received a temporary file path, use it directly
        if (file_exists($image_data)) {
            $temp_file = $image_data;
        } else {
            // Download file to temp dir if we received a URL
            $temp_file = download_url($image_data);
            if (is_wp_error($temp_file)) {
                return $temp_file;
            }
        }

        // Get the output format from settings to determine file extension
        $settings = $this->settings->get_settings();
        $provider = isset($settings['ai_provider']) ? $settings['ai_provider'] : 'openai';
        
        // Check premium status for Gemini
        $can_use_premium = function_exists('afig_fs') && afig_fs()->can_use_premium_code();
        
        // Determine file extension based on provider
        if ($provider === 'gemini' && $can_use_premium) {
            // Gemini always returns PNG format (cannot be customized)
            $extension = 'png';
        } elseif ($provider === 'stability') {
            // Stability: Pro users choose format, free users get PNG
            $output_format = ($can_use_premium && isset($settings['stability_output_format'])) ? $settings['stability_output_format'] : 'png';
            $extension = $output_format === 'jpeg' ? 'jpg' : $output_format;
        } elseif ($provider === 'grok') {
            // Grok always returns JPG
            $extension = 'jpg';
        } else {
            // OpenAI supports custom formats (default for all non-premium users)
            $output_format = isset($settings['output_format']) ? $settings['output_format'] : 'webp';
            $extension = $output_format === 'jpeg' ? 'jpg' : $output_format;
        }
        
        // Get post title and sanitize it for use as filename
        // Remove any "· PostType" suffix that might come from WordPress admin (e.g., "My Title · Movie")
        $clean_title = explode('·', $title)[0];
        $clean_title = trim($clean_title);
        $filename = sanitize_file_name($clean_title);
        
        $file_array = array(
            'name' => $filename . '.' . $extension,
            'tmp_name' => $temp_file
        );

        // Move the temporary file into the uploads directory
        $attachment_id = media_handle_sideload($file_array, $post_id);

        // Clean up the temporary file
        if (file_exists($temp_file)) {
            wp_delete_file($temp_file);
        }

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

        return $attachment_id;
    }

    /**
     * Generate an inline image (not set as featured image).
     *
     * Used by the Gutenberg block to generate images that are inserted
     * directly into post content rather than as featured images.
     *
     * @param string $prompt        Image prompt.
     * @param string $style         Style preset.
     * @param string $aspect_ratio  Aspect ratio (e.g., '16:9').
     * @param int    $post_id       Optional post ID for attachment.
     * @param int    $display_width Display width in pixels for size optimization.
     * @param string $custom_text   Optional custom text to overlay on the image.
     * @return array|\WP_Error      Attachment data or error.
     */
    public function generate_inline_image($prompt, $style = '', $aspect_ratio = '', $post_id = 0, $display_width = 1200, $custom_text = '') {
        $settings = $this->settings->get_settings();

        // Build prompt with style
        $style = !empty($style) ? $style : ($settings['default_style'] ?? 'realistic');
        $final_prompt = $prompt;

        // Stability presets (sp_ prefix) are sent as API-level style_preset, not prompt text
        if (strpos($style, 'sp_') === 0) {
            $settings['stability_style_preset'] = substr($style, 3);
        } else {
            $style_prompt = $this->get_style_prompt($style);
            // Only add style prompt if style is not 'none' and prompt is not empty
            if ($style !== 'none' && $style_prompt !== '') {
                $final_prompt .= ' ' . $style_prompt;
            }
        }

        // Handle text on image - block custom_text is independent from global allow_text setting
        if (!empty($custom_text)) {
            // Custom text provided from block - always add it regardless of global setting
            $final_prompt .= ' please add the "' . $custom_text . '" text on the image ';
        } elseif (empty($settings['allow_text']) || $settings['allow_text'] === false) {
            // No custom text and global setting disables text
            $final_prompt .= ' no text, no captions, no words, no letters, no writing';
        }

        // Override aspect ratio if provided from block attribute
        if (!empty($aspect_ratio)) {
            $settings['gemini_aspect_ratio'] = $aspect_ratio;
            $settings['default_size'] = $this->map_aspect_to_openai_size($aspect_ratio, $settings['ai_model'] ?? 'gpt-image-2');
            $settings['stability_aspect_ratio'] = $aspect_ratio;
            $settings['grok_aspect_ratio'] = $aspect_ratio;
        } else {
            // Use inline-specific defaults per provider
            $provider = $settings['ai_provider'] ?? 'openai';
            if ($provider === 'openai' && isset($settings['inline_default_size'])) {
                $settings['default_size'] = $settings['inline_default_size'];
            } elseif ($provider === 'gemini' && isset($settings['inline_gemini_aspect_ratio'])) {
                $settings['gemini_aspect_ratio'] = $settings['inline_gemini_aspect_ratio'];
            } elseif ($provider === 'stability' && isset($settings['inline_stability_aspect_ratio'])) {
                $settings['stability_aspect_ratio'] = $settings['inline_stability_aspect_ratio'];
            } elseif ($provider === 'grok' && isset($settings['inline_grok_aspect_ratio'])) {
                $settings['grok_aspect_ratio'] = $settings['inline_grok_aspect_ratio'];
            }
        }

        // Optimize quality based on display width
        $optimized_quality = $this->get_optimized_quality($display_width);
        $settings['image_quality'] = $optimized_quality['quality'];

        // For Gemini, optimize image size based on display width
        if (($settings['ai_provider'] ?? 'openai') === 'gemini') {
            $settings['gemini_image_size'] = $optimized_quality['gemini_size'];
        }

        // Use inline-specific output format for OpenAI
        if (($settings['ai_provider'] ?? 'openai') === 'openai' && isset($settings['inline_output_format'])) {
            $settings['output_format'] = $settings['inline_output_format'];
        }

        // Use inline-specific settings for Stability
        if (($settings['ai_provider'] ?? 'openai') === 'stability') {
            if (isset($settings['inline_stability_output_format'])) {
                $settings['stability_output_format'] = $settings['inline_stability_output_format'];
            }
            if (isset($settings['inline_stability_negative_prompt'])) {
                $settings['stability_negative_prompt'] = $settings['inline_stability_negative_prompt'];
            }
        }

        // Allow filtering the prompt
        $final_prompt = apply_filters('aifi_build_inline_prompt', $final_prompt, $style);

        // Call AI API
        $image_data = $this->call_ai_api($final_prompt, $settings, null);
        if (is_wp_error($image_data)) {
            return $image_data;
        }

        // Save image (does NOT set as featured image)
        $title = sanitize_title(substr($prompt, 0, 50));
        $attachment_id = $this->save_image($image_data, $post_id, $title);
        if (is_wp_error($attachment_id)) {
            return $attachment_id;
        }

        // Get attachment data
        $attachment_url = wp_get_attachment_url($attachment_id);
        $attachment_meta = wp_get_attachment_metadata($attachment_id);

        // Auto alt text (for inline, title = prompt since there's no post title context)
        $alt_text = $this->apply_auto_alt_text($attachment_id, array(
            'title'  => $prompt,
            'prompt' => $prompt,
            'style'  => $style,
        ));

        // Fire action hook for inline image generation
        do_action('aifi_after_generate_inline', $attachment_id, $post_id);

        return array(
            'attachment_id' => $attachment_id,
            'url' => $attachment_url,
            'alt' => $alt_text ? $alt_text : $prompt,
            'width' => $attachment_meta['width'] ?? 0,
            'height' => $attachment_meta['height'] ?? 0,
        );
    }

    /**
     * Map aspect ratio to OpenAI image size.
     *
     * OpenAI only supports 3 native sizes:
     * - 1024x1024 (1:1 square)
     * - 1536x1024 (3:2 landscape, used for 4:3, 16:9)
     * - 1024x1536 (2:3 portrait, used for 3:4, 9:16)
     *
     * @param string $aspect_ratio Aspect ratio string (e.g., '16:9').
     * @return string OpenAI size string (e.g., '1536x1024').
     */
    private function map_aspect_to_openai_size($aspect_ratio, $model = 'gpt-image-2') {
        $is_v2 = ( $model === 'gpt-image-2' || $model === 'chatgpt-image-latest' );

        if ( $is_v2 ) {
            $mapping = array(
                '1:1'  => '1024x1024',
                '3:2'  => '1536x1024',
                '2:3'  => '1024x1536',
                '16:9' => '1792x1024',
                '9:16' => '1024x1792',
                '3:1'  => '1920x640',
                '1:3'  => '640x1920',
                '4:3'  => '1536x1024', // closest native
                '3:4'  => '1024x1536', // closest native
            );
        } else {
            $mapping = array(
                '1:1'  => '1024x1024',
                '4:3'  => '1536x1024',
                '3:2'  => '1536x1024',
                '16:9' => '1536x1024',
                '3:4'  => '1024x1536',
                '2:3'  => '1024x1536',
                '9:16' => '1024x1536',
            );
        }

        return $mapping[$aspect_ratio] ?? '1024x1024';
    }

    /**
     * Get optimized quality and size settings based on display width.
     *
     * Optimizes image generation to match the display context:
     * - Small displays (< 600px): Higher compression, smaller Gemini size
     * - Medium displays (600-1000px): Balanced quality
     * - Large displays (> 1000px): Full quality, larger Gemini size
     *
     * @param int $display_width Display width in pixels.
     * @return array Array with 'quality' (1-100) and 'gemini_size' ('1K', '2K', '4K').
     */
    private function get_optimized_quality($display_width) {
        // Small display: mobile, sidebar, small columns
        if ($display_width < 600) {
            return array(
                'quality'     => 75,  // Good compression for small displays
                'gemini_size' => '1K', // 1K is sufficient for small displays
            );
        }

        // Medium display: typical content width
        if ($display_width < 1000) {
            return array(
                'quality'     => 85,  // Balanced quality
                'gemini_size' => '1K', // 1K still good for medium displays
            );
        }

        // Large display: wide/full layouts, high-res displays
        if ($display_width < 1600) {
            return array(
                'quality'     => 90,  // High quality
                'gemini_size' => '2K', // 2K for larger displays
            );
        }

        // Extra large display: 4K/retina, full-width layouts
        return array(
            'quality'     => 95,  // Maximum quality
            'gemini_size' => '4K', // 4K for ultra-wide displays
        );
    }

    /**
     * Apply auto alt text to an attachment based on plugin settings.
     *
     * @param int    $attachment_id Attachment ID.
     * @param array  $context       Context array with 'title', 'prompt', and 'style' keys.
     * @param string $old_alt_text  Optional existing alt text from previous featured image.
     * @return string|false The alt text that was set, or false if disabled.
     */
    private function apply_auto_alt_text($attachment_id, $context, $old_alt_text = '') {
        $settings = $this->settings->get_settings();

        if (empty($settings['auto_alt_text'])) {
            return false;
        }

        // If overwrite is disabled and we have old alt text, preserve it
        $overwrite = isset($settings['alt_text_overwrite']) ? (bool) $settings['alt_text_overwrite'] : true;
        if (!$overwrite && !empty($old_alt_text)) {
            $alt_text = sanitize_text_field(trim($old_alt_text));
            update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
            return $alt_text;
        }

        $source = isset($settings['alt_text_source']) ? $settings['alt_text_source'] : 'post_title';
        $title  = isset($context['title']) ? $context['title'] : '';
        $prompt = isset($context['prompt']) ? $context['prompt'] : $title;
        $style  = isset($context['style']) ? $this->get_style_label($context['style']) : '';

        switch ($source) {
            case 'prompt':
                $alt_text = $prompt;
                break;

            case 'custom':
                $format = isset($settings['alt_text_custom_format']) ? $settings['alt_text_custom_format'] : '{title}';
                $alt_text = str_replace(
                    array('{title}', '{prompt}', '{style}', '{site_name}'),
                    array($title, $prompt, $style, get_bloginfo('name')),
                    $format
                );
                break;

            case 'post_title':
            default:
                $alt_text = $title;
                break;
        }

        $alt_text = sanitize_text_field(trim($alt_text));

        if (!empty($alt_text)) {
            update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
        }

        return $alt_text;
    }

    /**
     * Get a human-readable label for a style key.
     *
     * @param string $style Style key (e.g. 'realistic', 'custom_0').
     * @return string Human-readable label (e.g. 'Realistic').
     */
    private function get_style_label($style) {
        if (strpos($style, 'custom_') === 0) {
            $settings = $this->settings->get_settings();
            $custom_styles = isset($settings['custom_styles']) ? (array) $settings['custom_styles'] : array();
            $idx = (int) substr($style, 7);
            return isset($custom_styles[$idx]['name']) ? $custom_styles[$idx]['name'] : $style;
        }

        $labels = array(
            'none'        => 'None',
            'realistic'   => 'Realistic',
            'artistic'    => 'Artistic',
            'cartoon'     => 'Cartoon',
            'sketch'      => 'Sketch',
            'watercolor'  => 'Watercolor',
            '3d'          => '3D Render',
            'pixel'       => 'Pixel Art',
            'cyberpunk'   => 'Cyberpunk',
            'fantasy'     => 'Fantasy',
            'anime'       => 'Anime',
            'minimalist'  => 'Minimalist',
            'technicolor' => 'Technicolor',
        );

        return isset($labels[$style]) ? $labels[$style] : $style;
    }
}