<?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(
            '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'      => __( '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_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_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' ));
            }
        } 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' ));
        }
        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',
                '3:4'  => '1024x1536',
            );
        } 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,
                'gemini_size' => '1K',
            );
        }
        // Medium display: typical content width
        if ( $display_width < 1000 ) {
            return array(
                'quality'     => 85,
                'gemini_size' => '1K',
            );
        }
        // Large display: wide/full layouts, high-res displays
        if ( $display_width < 1600 ) {
            return array(
                'quality'     => 90,
                'gemini_size' => '2K',
            );
        }
        // Extra large display: 4K/retina, full-width layouts
        return array(
            'quality'     => 95,
            'gemini_size' => '4K',
        );
    }

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

}
