<?php
/**
 * Name: AI Alt Text Generator
 * Description: Automatically generate alt text using AI (OpenAI or OpenRouter) with vision models
 * Category: Utilities
 * Icon: -
 * UseCases: [
 *   {
 *     "title": "Add Useful Functionality",
 *     "description": "Extend WordPress with helpful tools that make site management easier."
 *   },
 *   {
 *     "title": "Save Time",
 *     "description": "Automate or simplify tasks that would otherwise require manual work or additional tools."
 *   }
 * ]
 * Added: 2025-10-12
 * Beta: true
 * Tags: alt-text, images, accessibility, seo, media, ai, openai, vision
 * WhereToFind: {
 *   "location": "Media Library Upload",
 *   "url": "upload.php",
 *   "instructions": "Upload images - alt text auto-generates from filename",
 *   "newTab": false
 * }
 */

// Prevent direct access
if (!defined('ABSPATH')) exit;

class SwitchboardAutoAltText {

    private $encryption_key = 'switchboard_alt_text_key_v1';

    public function __construct() {
        add_action('add_attachment', [$this, 'auto_generate_alt_text']);
        add_filter('wp_get_attachment_image_attributes', [$this, 'ensure_alt_text'], 10, 2);
        add_filter('attachment_fields_to_edit', [$this, 'add_generate_button'], 10, 2);
        add_action('wp_ajax_generate_ai_alt_text', [$this, 'ajax_generate_ai_alt_text']);
        add_action('wp_ajax_switchboard_find_images_without_alt', [$this, 'ajax_find_images_without_alt']);
        add_action('wp_ajax_switchboard_batch_generate_alt', [$this, 'ajax_batch_generate_alt']);
        add_action('wp_ajax_switchboard_test_alt_text_api', [$this, 'ajax_test_api']);
        add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);
        add_action('admin_footer', [$this, 'add_inline_script']);
        add_filter('switchboard_before_save_settings', [$this, 'encrypt_api_key_before_save'], 10, 2);
    }

    /**
     * Encrypt API keys before saving
     */
    public function encrypt_api_key_before_save($settings, $module) {
        if ($module !== 'auto-alt-text') {
            return $settings;
        }

        $existing_settings = get_option('switchboard_auto-alt-text_settings', []);

        // Handle OpenAI API key
        if (isset($settings['openai_api_key'])) {
            if (empty($settings['openai_api_key'])) {
                // Empty means user cleared it
                $settings['openai_api_key'] = '';
            } elseif (strpos($settings['openai_api_key'], 'ENC:') === 0) {
                // Already encrypted, keep as-is
                // This happens when user doesn't change the key
            } else {
                // Plain text key, encrypt it (new or modified key)
                $settings['openai_api_key'] = $this->encrypt($settings['openai_api_key']);
            }
        }

        // Handle OpenRouter API key
        if (isset($settings['openrouter_api_key'])) {
            if (empty($settings['openrouter_api_key'])) {
                // Empty means user cleared it
                $settings['openrouter_api_key'] = '';
            } elseif (strpos($settings['openrouter_api_key'], 'ENC:') === 0) {
                // Already encrypted, keep as-is
                // This happens when user doesn't change the key
            } else {
                // Plain text key, encrypt it (new or modified key)
                $settings['openrouter_api_key'] = $this->encrypt($settings['openrouter_api_key']);
            }
        }

        return $settings;
    }

    /**
     * Encrypt API key
     */
    private function encrypt($value) {
        if (empty($value)) {
            return '';
        }

        // Use WordPress constants if available, otherwise use ABSPATH as fallback
        $auth_key = defined('AUTH_KEY') && AUTH_KEY ? AUTH_KEY : ABSPATH;
        $secure_auth_key = defined('SECURE_AUTH_KEY') && SECURE_AUTH_KEY ? SECURE_AUTH_KEY : ABSPATH;

        $key = substr(hash('sha256', $this->encryption_key . $auth_key), 0, 32);
        $iv = substr(hash('sha256', $this->encryption_key . $secure_auth_key), 0, 16);

        $encrypted = openssl_encrypt(
            $value,
            'AES-128-CBC',
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );

        if ($encrypted === false) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Switchboard Alt Text: API key encryption failed'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            return '';
        }

        return 'ENC:' . base64_encode($encrypted);
    }

    /**
     * Decrypt API key
     */
    private function decrypt($value) {
        if (empty($value) || strpos($value, 'ENC:') !== 0) {
            return $value;
        }

        $encrypted = substr($value, 4);

        // Use WordPress constants if available, otherwise use ABSPATH as fallback
        $auth_key = defined('AUTH_KEY') && AUTH_KEY ? AUTH_KEY : ABSPATH;
        $secure_auth_key = defined('SECURE_AUTH_KEY') && SECURE_AUTH_KEY ? SECURE_AUTH_KEY : ABSPATH;

        $key = substr(hash('sha256', $this->encryption_key . $auth_key), 0, 32);
        $iv = substr(hash('sha256', $this->encryption_key . $secure_auth_key), 0, 16);

        $decrypted = openssl_decrypt(
            base64_decode($encrypted),
            'AES-128-CBC',
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );

        if ($decrypted === false) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Switchboard Alt Text: API key decryption failed'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            return '';
        }

        return $decrypted;
    }
    
    public function enqueue_scripts($hook) {
        // Allow on all admin pages to ensure it works in media modal
        if (!is_admin()) return;


        // Just enqueue jQuery, script will be added in admin_footer
        wp_enqueue_script('jquery');
    }

    public function add_inline_script() {
        if (!is_admin()) return;

        ?>
        <script type="text/javascript">
        jQuery(document).ready(function($) {
            console.log("Switchboard AI Alt Text: Script loaded via admin_footer");

            // Use event delegation for dynamically loaded content (media modal)
            $(document).on("click", ".generate-ai-alt-btn", function(e) {
                e.preventDefault();
                console.log("Switchboard AI Alt Text: Button clicked");

                var btn = $(this);
                var mediaId = btn.data("id");
                var resultDiv = $("#ai-alt-result-" + mediaId);

                console.log("Switchboard AI Alt Text: Media ID:", mediaId);

                btn.prop("disabled", true).text("Generating...");
                resultDiv.html("<em>Generating alt text with AI...</em>");

                $.post(ajaxurl, {
                    action: "generate_ai_alt_text",
                    media_id: mediaId,
                    nonce: "<?php echo esc_attr(wp_create_nonce('generate_ai_alt')); ?>"
                }, function(response) {
                    console.log("Switchboard AI Alt Text: Response received", response);

                    if (response.success) {
                        resultDiv.empty().append($("<strong>").text("Generated: ")).append(document.createTextNode(response.data.alt_text));

                        // Try multiple selectors to find the alt text input
                        var altInput;
                        if ($("#attachment_alt").length) {
                            altInput = $("#attachment_alt");
                            console.log("Switchboard AI Alt Text: Found via #attachment_alt");
                        } else if ($("textarea[name='_wp_attachment_image_alt']").length) {
                            altInput = $("textarea[name='_wp_attachment_image_alt']");
                            console.log("Switchboard AI Alt Text: Found via textarea[name='_wp_attachment_image_alt']");
                        } else if ($("input[name='attachments[" + mediaId + "][image_alt]']").length) {
                            altInput = $("input[name='attachments[" + mediaId + "][image_alt]']");
                            console.log("Switchboard AI Alt Text: Found via input[name='attachments[" + mediaId + "][image_alt]']");
                        } else if ($("#attachments-" + mediaId + "-image_alt").length) {
                            altInput = $("#attachments-" + mediaId + "-image_alt");
                            console.log("Switchboard AI Alt Text: Found via #attachments-" + mediaId + "-image_alt");
                        }

                        console.log("Switchboard AI Alt Text: Found alt input:", altInput ? altInput.length : 0);

                        if (altInput.length) {
                            altInput.val(response.data.alt_text);
                            console.log("Switchboard AI Alt Text: Updated alt text field");
                        } else {
                            console.log("Switchboard AI Alt Text: Could not find alt text input field");
                        }
                    } else {
                        resultDiv.empty().append($("<span>").css("color", "red").text("Error: " + (response.data ? response.data.message : "Unknown error")));
                    }
                    btn.prop("disabled", false).text("Generate with AI");
                }).fail(function(xhr, status, error) {
                    console.error("Switchboard AI Alt Text: AJAX failed", status, error);
                    resultDiv.empty().append($("<span>").css("color", "red").text("Failed to connect to AI service"));
                    btn.prop("disabled", false).text("Generate with AI");
                });
            });

            // Check if button exists after page load
            setTimeout(function() {
                var btnCount = $(".generate-ai-alt-btn").length;
                console.log("Switchboard AI Alt Text: Found " + btnCount + " button(s)");
            }, 1000);
        });
        </script>
        <?php
    }
    
    public function add_generate_button($fields, $post) {

        if (!wp_attachment_is_image($post->ID)) {
            return $fields;
        }

        $settings = get_option('switchboard_auto-alt-text_settings', []);
        $ai_enabled = isset($settings['ai_enabled']) && $settings['ai_enabled'] === 'yes';


        if (!$ai_enabled) {
            return $fields;
        }


        $fields['ai_alt_generator'] = [
            'label' => 'AI Alt Text',
            'input' => 'html',
            'html' => '<div class="sb-sp-flex sb-sp-items-center sb-sp-gap-2">
                          <button type="button" class="button generate-ai-alt-btn" data-id="' . $post->ID . '">Generate with AI</button>
                          <span style="display: inline-flex; align-items: center; padding: 2px 8px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-size: 11px; font-weight: 600; border-radius: 3px; text-transform: uppercase; letter-spacing: 0.5px;">
                              <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 4px;">
                                  <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
                              </svg>
                              Switchboard
                          </span>
                      </div>
                      <div id="ai-alt-result-' . $post->ID . '" class="sb-sp-mt"></div>',
            'helps' => 'Generate alt text using AI vision models.'
        ];

        return $fields;
    }
    
    public function ajax_generate_ai_alt_text() {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Auto Alt Text AJAX: Request received'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        }

        check_ajax_referer('generate_ai_alt', 'nonce');
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Auto Alt Text AJAX: Nonce verified'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        }

        if (!current_user_can('upload_files')) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Auto Alt Text AJAX: Permission denied'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            wp_send_json_error(['message' => esc_html__('Permission denied', 'wpswitchboard')]);
        }

        $media_id = isset($_POST['media_id']) ? intval($_POST['media_id']) : 0;
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Auto Alt Text AJAX: Media ID: ' . $media_id); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        }

        if (!wp_attachment_is_image($media_id)) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Auto Alt Text AJAX: Not an image'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            wp_send_json_error(['message' => esc_html__('Not an image', 'wpswitchboard')]);
        }

        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Auto Alt Text AJAX: Calling generate_ai_alt_text()'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        }
        $alt_text = $this->generate_ai_alt_text($media_id);

        if ($alt_text) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Auto Alt Text AJAX: Success! Alt text: ' . $alt_text); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            update_post_meta($media_id, '_wp_attachment_image_alt', $alt_text);
            wp_send_json_success(['alt_text' => $alt_text]);
        } else {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Auto Alt Text AJAX: Failed to generate alt text'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            $error_msg = !empty($this->last_generation_error) ? $this->last_generation_error : __('Failed to generate alt text. Check your API settings.', 'wpswitchboard');
            wp_send_json_error(['message' => esc_html($error_msg)]);
        }
    }

    /**
     * AJAX: Find images without alt text
     */
    public function ajax_find_images_without_alt() {
        check_ajax_referer('switchboard_batch_alt', 'nonce');

        if (!current_user_can('upload_files')) {
            wp_send_json_error(['message' => esc_html__('Permission denied', 'wpswitchboard')]);
        }

        // Find all image attachments
        $args = [
            'post_type' => 'attachment',
            'post_mime_type' => 'image',
            'post_status' => 'inherit',
            'posts_per_page' => -1,
            'fields' => 'ids'
        ];

        $images = get_posts($args);
        $images_without_alt = [];

        foreach ($images as $image_id) {
            $alt_text = get_post_meta($image_id, '_wp_attachment_image_alt', true);

            // Check if alt text is missing or is filename-based
            if (empty($alt_text)) {
                $images_without_alt[] = $image_id;
            } else {
                // Check if it's likely filename-based (contains common filename patterns)
                $filename = get_the_title($image_id);
                $generated_from_filename = $this->generate_alt_from_filename($filename);

                // If alt text matches filename-based generation, consider it for regeneration
                if (strtolower(trim($alt_text)) === strtolower(trim($generated_from_filename))) {
                    $images_without_alt[] = $image_id;
                }
            }
        }

        wp_send_json_success(['image_ids' => $images_without_alt]);
    }

    /**
     * AJAX: Batch generate alt text for a single image
     */
    public function ajax_batch_generate_alt() {
        check_ajax_referer('switchboard_batch_alt', 'nonce');

        if (!current_user_can('upload_files')) {
            wp_send_json_error(['message' => esc_html__('Permission denied', 'wpswitchboard')]);
        }

        $image_id = isset($_POST['image_id']) ? intval($_POST['image_id']) : 0;

        if (!wp_attachment_is_image($image_id)) {
            wp_send_json_error(['message' => esc_html__('Not an image', 'wpswitchboard')]);
        }

        $alt_text = $this->generate_ai_alt_text($image_id);

        if ($alt_text) {
            update_post_meta($image_id, '_wp_attachment_image_alt', $alt_text);
            wp_send_json_success(['alt_text' => $alt_text]);
        } else {
            $error_msg = !empty($this->last_generation_error) ? $this->last_generation_error : __('Failed to generate', 'wpswitchboard');
            wp_send_json_error(['message' => esc_html($error_msg)]);
        }
    }

    public function auto_generate_alt_text($attachment_id) {

        if (!wp_attachment_is_image($attachment_id)) {
            return;
        }

        $existing_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
        if (!empty($existing_alt)) {
            return;
        }

        $settings = get_option('switchboard_auto-alt-text_settings', []);
        $ai_enabled = isset($settings['ai_enabled']) && $settings['ai_enabled'] === 'yes';
        $auto_generate = isset($settings['auto_generate']) && $settings['auto_generate'] === 'yes';


        if ($ai_enabled && $auto_generate) {
            $alt_text = $this->generate_ai_alt_text($attachment_id);
            if ($alt_text) {
                update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
                return;
            } else {
            }
        }

        // Fallback to filename-based generation
        $filename = get_the_title($attachment_id);
        $alt_text = $this->generate_alt_from_filename($filename);
        update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
    }
    
    private $last_generation_error = '';

    private function generate_ai_alt_text($attachment_id) {
        $this->last_generation_error = '';
        $settings = get_option('switchboard_auto-alt-text_settings', []);

        $provider = isset($settings['ai_provider']) ? $settings['ai_provider'] : 'openai';

        // Get the correct API key based on provider
        $api_key = '';
        if ($provider === 'openai') {
            $encrypted_key = isset($settings['openai_api_key']) ? $settings['openai_api_key'] : '';
            $api_key = $encrypted_key ? $this->decrypt($encrypted_key) : '';
        } else {
            $encrypted_key = isset($settings['openrouter_api_key']) ? $settings['openrouter_api_key'] : '';
            $api_key = $encrypted_key ? $this->decrypt($encrypted_key) : '';
        }

        $model = isset($settings['model']) ? $settings['model'] : 'gpt-4o-mini';
        $custom_prompt = isset($settings['custom_prompt']) && !empty($settings['custom_prompt'])
            ? $settings['custom_prompt']
            : 'Generate a concise, descriptive alt text for this image. Keep it under 125 characters. Focus on what is visible in the image. Do not include phrases like "image of" or "picture of". Just describe what you see.';


        if (empty($api_key)) {
            $this->last_generation_error = __('API key not configured.', 'wpswitchboard');
            return false;
        }

        $image_url = wp_get_attachment_url($attachment_id);
        if (!$image_url) {
            $this->last_generation_error = __('Could not get image URL.', 'wpswitchboard');
            return false;
        }

        // Check for unsupported image formats (AI vision APIs only support PNG, JPEG, GIF, WebP)
        $mime_type = get_post_mime_type($attachment_id);
        $unsupported_formats = ['image/avif', 'image/bmp', 'image/tiff', 'image/svg+xml'];
        if (in_array($mime_type, $unsupported_formats, true)) {
            $format = strtoupper(str_replace('image/', '', $mime_type));
            /* translators: %s is the image format name */
            $this->last_generation_error = sprintf(__('%s format is not supported by AI vision models. Supported formats: PNG, JPEG, GIF, WebP.', 'wpswitchboard'), $format);
            return false;
        }

        // Check if URL is local/non-public - convert to base64 data URL
        // Matches localhost, 127.0.0.1, ::1, .local, .test, and other common local dev domains
        if (preg_match('/^https?:\/\/(localhost|127\.0\.0\.1|::1)(:|\/)/i', $image_url) ||
            preg_match('/^https?:\/\/[^\/]+\.(local|test|dev|localhost|invalid)(:|\/|$)/i', $image_url)) {
            $image_path = get_attached_file($attachment_id);
            if ($image_path && file_exists($image_path)) {
                $image_data = file_get_contents($image_path); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
                $image_type = wp_check_filetype($image_path);
                $file_mime = $image_type['type'];
                $base64 = base64_encode($image_data); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
                $image_url = 'data:' . $file_mime . ';base64,' . $base64;
            } else {
                $this->last_generation_error = __('Could not read local image file.', 'wpswitchboard');
                return false;
            }
        }


        if ($provider === 'openai') {
            $result = $this->generate_with_openai($image_url, $api_key, $model, $custom_prompt);
        } elseif ($provider === 'openrouter') {
            $result = $this->generate_with_openrouter($image_url, $api_key, $model, $custom_prompt);
        } else {
            $this->last_generation_error = __('Invalid AI provider.', 'wpswitchboard');
            $result = false;
        }

        return $result;
    }

    private function generate_with_openai($image_url, $api_key, $model, $prompt) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Auto Alt Text OpenAI: Making API request with model: ' . $model); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        }

        $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
            'timeout' => 30,
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'application/json'
            ],
            'body' => json_encode([
                'model' => $model,
                'messages' => [
                    [
                        'role' => 'user',
                        'content' => [
                            [
                                'type' => 'text',
                                'text' => $prompt
                            ],
                            [
                                'type' => 'image_url',
                                'image_url' => [
                                    'url' => $image_url
                                ]
                            ]
                        ]
                    ]
                ],
                'max_tokens' => 100
            ])
        ]);

        if (is_wp_error($response)) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Auto Alt Text OpenAI: WP Error: ' . $response->get_error_message()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            $this->last_generation_error = $response->get_error_message();
            return false;
        }

        $status_code = wp_remote_retrieve_response_code($response);
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Auto Alt Text OpenAI: HTTP Status: ' . $status_code); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        }

        $body = json_decode(wp_remote_retrieve_body($response), true);

        if ($status_code !== 200) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Auto Alt Text OpenAI: API Error Response: ' . wp_remote_retrieve_body($response)); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            $this->last_generation_error = isset($body['error']['message']) ? $body['error']['message'] : ('API error: HTTP ' . $status_code);
        }

        if (isset($body['choices'][0]['message']['content'])) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Auto Alt Text OpenAI: Success! Generated alt text'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            return trim($body['choices'][0]['message']['content']);
        }

        if (empty($this->last_generation_error)) {
            $this->last_generation_error = __('No content in API response.', 'wpswitchboard');
        }
        return false;
    }
    
    private function generate_with_openrouter($image_url, $api_key, $model, $prompt) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Auto Alt Text OpenRouter: Making API request with model: ' . $model); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        }

        $response = wp_remote_post('https://openrouter.ai/api/v1/chat/completions', [
            'timeout' => 30,
            'headers' => [
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'application/json',
                'HTTP-Referer' => home_url(),
                'X-Title' => get_bloginfo('name')
            ],
            'body' => json_encode([
                'model' => $model,
                'messages' => [
                    [
                        'role' => 'user',
                        'content' => [
                            [
                                'type' => 'text',
                                'text' => $prompt
                            ],
                            [
                                'type' => 'image_url',
                                'image_url' => [
                                    'url' => $image_url
                                ]
                            ]
                        ]
                    ]
                ]
            ])
        ]);

        if (is_wp_error($response)) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Auto Alt Text OpenRouter: WP Error: ' . $response->get_error_message()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            $this->last_generation_error = $response->get_error_message();
            return false;
        }

        $status_code = wp_remote_retrieve_response_code($response);
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('Auto Alt Text OpenRouter: HTTP Status: ' . $status_code); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        }

        $body = json_decode(wp_remote_retrieve_body($response), true);

        if ($status_code !== 200) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Auto Alt Text OpenRouter: API Error Response: ' . wp_remote_retrieve_body($response)); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            $this->last_generation_error = isset($body['error']['message']) ? $body['error']['message'] : ('API error: HTTP ' . $status_code);
        }

        if (isset($body['choices'][0]['message']['content'])) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('Auto Alt Text OpenRouter: Success! Generated alt text'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }
            return trim($body['choices'][0]['message']['content']);
        }

        if (empty($this->last_generation_error)) {
            $this->last_generation_error = __('No content in API response.', 'wpswitchboard');
        }
        return false;
    }
    
    public function ensure_alt_text($attr, $attachment) {
        if (empty($attr['alt'])) {
            $filename = get_the_title($attachment->ID);
            $attr['alt'] = $this->generate_alt_from_filename($filename);
        }
        
        return $attr;
    }
    
    private function generate_alt_from_filename($filename) {
        $alt = preg_replace('/\.[^.]+$/', '', $filename);
        $alt = str_replace(['-', '_'], ' ', $alt);
        $alt = preg_replace('/^\d+\s*|\s*\d+$/', '', $alt);
        $alt = ucwords(strtolower($alt));
        $alt = preg_replace('/\s+/', ' ', trim($alt));
        
        return $alt;
    }

    /**
     * AJAX: Test API configuration
     */
    public function ajax_test_api() {
        check_ajax_referer('switchboard_test_api', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => esc_html__('Permission denied', 'wpswitchboard')]);
        }

        $provider = isset($_POST['provider']) ? sanitize_text_field(wp_unslash($_POST['provider'])) : '';
        $model = isset($_POST['model']) ? sanitize_text_field(wp_unslash($_POST['model'])) : '';
        $api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : '';

        // Decrypt API key if encrypted
        if (!empty($api_key) && strpos($api_key, 'ENC:') === 0) {
            $api_key = $this->decrypt($api_key);
        }

        // Validate inputs
        if (empty($provider) || empty($model) || empty($api_key)) {
            wp_send_json_error(['message' => esc_html__('Missing required parameters (provider, model, or API key)', 'wpswitchboard')]);
        }

        // Use a simple test image URL (1x1 transparent PNG data URL)
        $test_image_url = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
        $test_prompt = 'Describe this image in one word.';

        // Test the API
        $result = false;
        $error_message = '';

        try {
            if ($provider === 'openai') {
                $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
                    'timeout' => 15,
                    'headers' => [
                        'Authorization' => 'Bearer ' . $api_key,
                        'Content-Type' => 'application/json'
                    ],
                    'body' => json_encode([
                        'model' => $model,
                        'messages' => [
                            [
                                'role' => 'user',
                                'content' => [
                                    ['type' => 'text', 'text' => $test_prompt],
                                    ['type' => 'image_url', 'image_url' => ['url' => $test_image_url]]
                                ]
                            ]
                        ],
                        'max_tokens' => 20
                    ])
                ]);
            } elseif ($provider === 'openrouter') {
                $response = wp_remote_post('https://openrouter.ai/api/v1/chat/completions', [
                    'timeout' => 15,
                    'headers' => [
                        'Authorization' => 'Bearer ' . $api_key,
                        'Content-Type' => 'application/json',
                        'HTTP-Referer' => home_url(),
                        'X-Title' => get_bloginfo('name')
                    ],
                    'body' => json_encode([
                        'model' => $model,
                        'messages' => [
                            [
                                'role' => 'user',
                                'content' => [
                                    ['type' => 'text', 'text' => $test_prompt],
                                    ['type' => 'image_url', 'image_url' => ['url' => $test_image_url]]
                                ]
                            ]
                        ]
                    ])
                ]);
            } else {
                // translators: %s is the provider name
                wp_send_json_error(['message' => sprintf(esc_html__('Invalid provider: %s', 'wpswitchboard'), $provider)]);
            }

            if (is_wp_error($response)) {
                $error_message = $response->get_error_message();
            } else {
                $status_code = wp_remote_retrieve_response_code($response);
                $body = json_decode(wp_remote_retrieve_body($response), true);

                if ($status_code === 200 && isset($body['choices'][0]['message']['content'])) {
                    $result = true;
                } else {
                    // Extract error message from API response
                    if (isset($body['error']['message'])) {
                        $error_message = $body['error']['message'];
                    } elseif (isset($body['error'])) {
                        $error_message = is_string($body['error']) ? $body['error'] : json_encode($body['error']);
                    } else {
                        $error_message = 'HTTP ' . $status_code . ': ' . wp_remote_retrieve_response_message($response);
                    }
                }
            }
        } catch (Exception $e) {
            $error_message = $e->getMessage();
        }

        if ($result) {
            wp_send_json_success([
                'message' => esc_html__('API connection successful! Your configuration is working correctly.', 'wpswitchboard')
            ]);
        } else {
            wp_send_json_error([
                'message' => $error_message ?: esc_html__('Unknown error occurred while testing API', 'wpswitchboard')
            ]);
        }
    }

    /**
     * Render settings panel
     *
     * @param array $settings Current module settings
     * @return void Outputs HTML directly
     */
    public function render_settings($settings = []) {
                    $ai_enabled = isset($settings['ai_enabled']) ? $settings['ai_enabled'] : 'no';
                    $ai_provider = isset($settings['ai_provider']) ? $settings['ai_provider'] : 'openai';

                    // Separate API keys for each provider
                    $openai_api_key = isset($settings['openai_api_key']) ? $settings['openai_api_key'] : '';
                    $openrouter_api_key = isset($settings['openrouter_api_key']) ? $settings['openrouter_api_key'] : '';

                    $model = isset($settings['model']) ? $settings['model'] : 'gpt-4o-mini';
                    $auto_generate = isset($settings['auto_generate']) ? $settings['auto_generate'] : 'no';
                    $custom_prompt = isset($settings['custom_prompt']) ? $settings['custom_prompt'] : '';

                    // Default prompt
                    $default_prompt = 'Generate a concise, descriptive alt text for this image. Keep it under 125 characters. Focus on what is visible in the image. Do not include phrases like "image of" or "picture of". Just describe what you see.';

                    // Mask encrypted API keys
                    $openai_key_display = '';
                    $openai_has_key = false;
                    if (!empty($openai_api_key)) {
                        $openai_has_key = true;
                        if (strpos($openai_api_key, 'ENC:') === 0) {
                            $openai_key_display = '••••••••••••••••';
                        } else {
                            $openai_key_display = $openai_api_key;
                        }
                    }

                    $openrouter_key_display = '';
                    $openrouter_has_key = false;
                    if (!empty($openrouter_api_key)) {
                        $openrouter_has_key = true;
                        if (strpos($openrouter_api_key, 'ENC:') === 0) {
                            $openrouter_key_display = '••••••••••••••••';
                        } else {
                            $openrouter_key_display = $openrouter_api_key;
                        }
                    }

                    $html = '
                        <input type="hidden" name="module" value="auto-alt-text">
                        
                        <div class="sb-sp-group" style="margin-bottom: 24px;">
                            <h3 class="sb-sp-heading">' . esc_html__('AI Alt Text Generator', 'wpswitchboard') . '</h3>
                            <p class="sb-sp-description">' . esc_html__('Automatically generate descriptive alt text for images using AI vision models. Improves accessibility and SEO.', 'wpswitchboard') . '</p>
                        </div>

                        <div class="sb-module-option-item sb-module-toggle-item" style="margin-bottom: 24px;">
                            <div class="sb-module-option-content">
                                <div class="sb-module-toggle-header">
                                    <strong class="sb-module-option-title">' . esc_html__('Enable AI Generation', 'wpswitchboard') . '</strong>
                                    <label class="sb-module-toggle">
                                        <input type="checkbox" name="setting_ai_enabled" value="yes" ' . checked($ai_enabled, 'yes', false) . '>
                                        <span class="sb-module-toggle-slider"></span>
                                    </label>
                                </div>
                                <p class="sb-module-option-text">' . esc_html__('Use AI vision models to generate alt text (requires API key)', 'wpswitchboard') . '</p>
                            </div>
                        </div>
                        
                        <div id="ai-settings-section" style="display: ' . ($ai_enabled === 'yes' ? 'block' : 'none') . ';">
                            <input type="hidden" name="setting_ai_provider" id="setting_ai_provider" value="' . esc_attr($ai_provider) . '">
                            <input type="hidden" name="setting_model" id="setting_model" value="' . esc_attr($model) . '">

                            <!-- Provider Selection Card -->
                            <div class="sb-module-card" style="margin-bottom: 20px;">
                                <div class="sb-module-card-header">
                                    <div class="sb-module-card-header-left">
                                        <h3 class="sb-module-card-title">' . esc_html__('AI Provider', 'wpswitchboard') . '</h3>
                                        <p class="sb-module-card-desc">' . esc_html__('Select which AI service to use', 'wpswitchboard') . '</p>
                                    </div>
                                </div>
                                <div class="sb-module-card-body">
                                    <div class="sb-module-form-grid">
                                        <label class="sb-module-checkbox-item ' . ($ai_provider === 'openai' ? 'active' : '') . '" data-provider="openai" style="cursor: pointer;">
                                            <input type="radio" name="provider_select" value="openai" ' . checked($ai_provider, 'openai', false) . ' style="display: none;">
                                            <span style="flex: 1; font-weight: 600;">OpenAI</span>
                                            <span style="font-size: 12px; color: #64748b;">GPT-4o, GPT-4o-mini</span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($ai_provider === 'openrouter' ? 'active' : '') . '" data-provider="openrouter" style="cursor: pointer;">
                                            <input type="radio" name="provider_select" value="openrouter" ' . checked($ai_provider, 'openrouter', false) . ' style="display: none;">
                                            <span style="flex: 1; font-weight: 600;">OpenRouter</span>
                                            <span style="font-size: 12px; color: #64748b;">Claude, Gemini, GPT</span>
                                        </label>
                                    </div>
                                </div>
                            </div>

                            <!-- Model Selection Card -->
                            <div class="sb-module-card" style="margin-bottom: 20px;">
                                <div class="sb-module-card-header">
                                    <div class="sb-module-card-header-left">
                                        <h3 class="sb-module-card-title">' . esc_html__('Vision Model', 'wpswitchboard') . '</h3>
                                        <p class="sb-module-card-desc">' . esc_html__('Select a vision-enabled model for image analysis', 'wpswitchboard') . '</p>
                                    </div>
                                </div>
                                <div class="sb-module-card-body">
                                    <!-- OpenAI Models -->
                                    <div class="sb-module-checkbox-grid openai-models" style="display: ' . ($ai_provider === 'openai' ? 'grid' : 'none') . ';">
                                        <label class="sb-module-checkbox-item ' . ($model === 'gpt-4o' ? 'active' : '') . '" data-model="gpt-4o" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="gpt-4o" ' . checked($model, 'gpt-4o', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>GPT-4o</strong><br>
                                                <small style="color: #64748b;">Best quality</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'gpt-4o-mini' ? 'active' : '') . '" data-model="gpt-4o-mini" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="gpt-4o-mini" ' . checked($model, 'gpt-4o-mini', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>GPT-4o-mini (Recommended)</strong><br>
                                                <small style="color: #64748b;">Recommended</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'gpt-4-turbo-2024-04-09' ? 'active' : '') . '" data-model="gpt-4-turbo-2024-04-09" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="gpt-4-turbo-2024-04-09" ' . checked($model, 'gpt-4-turbo-2024-04-09', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>GPT-4 Turbo</strong><br>
                                                <small style="color: #64748b;">Legacy</small>
                                            </span>
                                        </label>
                                    </div>

                                    <!-- OpenRouter Models -->
                                    <div class="sb-module-checkbox-grid openrouter-models" style="display: ' . ($ai_provider === 'openrouter' ? 'grid' : 'none') . ';">
                                        <label class="sb-module-checkbox-item ' . ($model === 'openai/gpt-4o-mini' ? 'active' : '') . '" data-model="openai/gpt-4o-mini" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="openai/gpt-4o-mini" ' . checked($model, 'openai/gpt-4o-mini', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>GPT-4o-mini (Recommended)</strong><br>
                                                <small style="color: #64748b;">OpenAI • Recommended</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'openai/gpt-4o' ? 'active' : '') . '" data-model="openai/gpt-4o" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="openai/gpt-4o" ' . checked($model, 'openai/gpt-4o', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>GPT-4o</strong><br>
                                                <small style="color: #64748b;">OpenAI • Best quality</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'anthropic/claude-sonnet-4.5' ? 'active' : '') . '" data-model="anthropic/claude-sonnet-4.5" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="anthropic/claude-sonnet-4.5" ' . checked($model, 'anthropic/claude-sonnet-4.5', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>Claude 4.5</strong><br>
                                                <small style="color: #64748b;">Anthropic • Latest</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'anthropic/claude-3.7-sonnet' ? 'active' : '') . '" data-model="anthropic/claude-3.7-sonnet" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="anthropic/claude-3.7-sonnet" ' . checked($model, 'anthropic/claude-3.7-sonnet', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>Claude 3.7</strong><br>
                                                <small style="color: #64748b;">Anthropic</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'anthropic/claude-3.5-sonnet' ? 'active' : '') . '" data-model="anthropic/claude-3.5-sonnet" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="anthropic/claude-3.5-sonnet" ' . checked($model, 'anthropic/claude-3.5-sonnet', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>Claude 3.5</strong><br>
                                                <small style="color: #64748b;">Anthropic</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'anthropic/claude-3-opus' ? 'active' : '') . '" data-model="anthropic/claude-3-opus" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="anthropic/claude-3-opus" ' . checked($model, 'anthropic/claude-3-opus', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>Claude 3 Opus</strong><br>
                                                <small style="color: #64748b;">Anthropic</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'anthropic/claude-3-haiku' ? 'active' : '') . '" data-model="anthropic/claude-3-haiku" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="anthropic/claude-3-haiku" ' . checked($model, 'anthropic/claude-3-haiku', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>Claude 3 Haiku</strong><br>
                                                <small style="color: #64748b;">Anthropic • Fast & cheap</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'google/gemini-2.5-flash' ? 'active' : '') . '" data-model="google/gemini-2.5-flash" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="google/gemini-2.5-flash" ' . checked($model, 'google/gemini-2.5-flash', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>Gemini 2.5 Flash</strong><br>
                                                <small style="color: #64748b;">Google • Latest</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'google/gemini-pro-1.5' ? 'active' : '') . '" data-model="google/gemini-pro-1.5" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="google/gemini-pro-1.5" ' . checked($model, 'google/gemini-pro-1.5', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>Gemini 1.5 Pro</strong><br>
                                                <small style="color: #64748b;">Google</small>
                                            </span>
                                        </label>
                                        <label class="sb-module-checkbox-item ' . ($model === 'google/gemini-flash-1.5' ? 'active' : '') . '" data-model="google/gemini-flash-1.5" style="cursor: pointer;">
                                            <input type="radio" name="model_select" value="google/gemini-flash-1.5" ' . checked($model, 'google/gemini-flash-1.5', false) . ' style="display: none;">
                                            <span style="flex: 1;">
                                                <strong>Gemini 1.5 Flash</strong><br>
                                                <small style="color: #64748b;">Google</small>
                                            </span>
                                        </label>
                                    </div>
                                </div>
                            </div>

                            <!-- API Key Configuration Card -->
                            <div class="sb-module-card" style="margin-bottom: 20px;">
                                <div class="sb-module-card-header">
                                    <div class="sb-module-card-header-left">
                                        <h3 class="sb-module-card-title" id="api-key-label">
                                            <span id="api-key-label-text">' . ($ai_provider === 'openai' ? esc_html__('OpenAI API Key', 'wpswitchboard') : esc_html__('OpenRouter API Key', 'wpswitchboard')) . '</span>
                                            ' . ($openai_has_key && $ai_provider === 'openai' ? '<span style="margin-left: 8px; color: #16a34a; font-size: 13px;">' . esc_html__('Saved', 'wpswitchboard') . '</span>' : '') . '
                                            ' . ($openrouter_has_key && $ai_provider === 'openrouter' ? '<span style="margin-left: 8px; color: #16a34a; font-size: 13px;">' . esc_html__('Saved', 'wpswitchboard') . '</span>' : '') . '
                                        </h3>
                                        <p class="sb-module-card-desc" id="api-key-help">
                                            Get your API key from <a href="https://platform.openai.com/api-keys" target="_blank" id="api-link-openai" style="' . ($ai_provider === 'openai' ? '' : 'display:none;') . '">OpenAI Platform</a>
                                            <a href="https://openrouter.ai/keys" target="_blank" id="api-link-openrouter" style="' . ($ai_provider === 'openrouter' ? '' : 'display:none;') . '">OpenRouter</a>
                                        </p>
                                    </div>
                                </div>
                                <div class="sb-module-card-body">
                                    <!-- Hidden inputs for both API keys (store actual encrypted values) -->
                                    <input type="hidden" name="setting_openai_api_key" id="setting_openai_api_key" value="' . esc_attr($openai_api_key) . '">
                                    <input type="hidden" name="setting_openrouter_api_key" id="setting_openrouter_api_key" value="' . esc_attr($openrouter_api_key) . '">

                                    <div style="display: flex; gap: 8px; align-items: center;">
                                        <div style="position: relative; flex: 1;">
                                            <input type="password" name="setting_api_key_display" id="setting_api_key_display"
                                                value="' . ($ai_provider === 'openai' ? esc_attr($openai_key_display) : esc_attr($openrouter_key_display)) . '"
                                                placeholder="sk-..."
                                                class="sb-module-input"
                                                style="width: 100%; padding-right: 80px;"
                                                data-has-key="' . ($ai_provider === 'openai' ? ($openai_has_key ? '1' : '0') : ($openrouter_has_key ? '1' : '0')) . '">
                                            <div style="position: absolute; right: 4px; top: 50%; transform: translateY(-50%); display: flex; gap: 2px;">
                                                <button type="button" id="toggle-api-visibility" class="sb-alt-icon-btn" style="' . (($ai_provider === 'openai' && $openai_has_key) || ($ai_provider === 'openrouter' && $openrouter_has_key) ? '' : 'display:none;') . '" title="Show/Hide API key" aria-label="Toggle API key visibility">
                                                    <span class="dashicons dashicons-visibility"></span>
                                                </button>
                                                <button type="button" id="clear-api-btn" class="sb-alt-icon-btn" style="' . (($ai_provider === 'openai' && $openai_has_key) || ($ai_provider === 'openrouter' && $openrouter_has_key) ? '' : 'display:none;') . '" title="Clear API key" aria-label="Clear API key">
                                                    <span class="dashicons dashicons-no"></span>
                                                </button>
                                            </div>
                                        </div>
                                        <button type="button" id="test-api-btn" class="sb-module-button sb-module-button-secondary" style="white-space: nowrap;">
                                            <span class="dashicons dashicons-yes-alt" style="font-size: 16px; width: 16px; height: 16px; vertical-align: middle; margin-right: 4px;"></span> ' . esc_html__('Test API', 'wpswitchboard') . '
                                        </button>
                                    </div>
                                    <style>
                                        .sb-alt-icon-btn {
                                            background: none;
                                            border: 1px solid #e2e8f0;
                                            border-radius: 6px;
                                            cursor: pointer;
                                            padding: 4px 6px;
                                            color: #64748b;
                                            transition: all 0.15s ease;
                                            display: inline-flex;
                                            align-items: center;
                                            justify-content: center;
                                        }
                                        .sb-alt-icon-btn:hover { background: #f1f5f9; color: #334155; }
                                        .sb-alt-icon-btn .dashicons { font-size: 16px; width: 16px; height: 16px; }
                                    </style>
                                    <div id="api-test-result" style="display: none; margin-top: 12px; padding: 12px; border-radius: 8px;"></div>
                                </div>
                            </div>

                            <!-- Custom Prompt Card -->
                            <div class="sb-module-card" style="margin-bottom: 20px;">
                                <div class="sb-module-card-header">
                                    <div class="sb-module-card-header-left">
                                        <h3 class="sb-module-card-title">' . esc_html__('Custom Prompt (Optional)', 'wpswitchboard') . '</h3>
                                        <p class="sb-module-card-desc">' . esc_html__('Customize the prompt sent to the AI model. Leave empty to use the default prompt.', 'wpswitchboard') . '</p>
                                    </div>
                                </div>
                                <div class="sb-module-card-body">
                                    <textarea name="setting_custom_prompt" id="setting_custom_prompt" rows="4" class="sb-module-input" style="width: 100%; font-family: monospace;" placeholder="' . esc_attr($default_prompt) . '">' . esc_textarea($custom_prompt) . '</textarea>
                                </div>
                            </div>
                        </div>

                        <div class="sb-sp-divider" style="margin: 24px 0;"></div>
                        
                        <div class="sb-module-option-item sb-module-toggle-item" style="margin-bottom: 24px;">
                            <div class="sb-module-option-content">
                                <div class="sb-module-toggle-header">
                                    <strong class="sb-module-option-title">' . esc_html__('Auto-generate on Upload', 'wpswitchboard') . '</strong>
                                    <label class="sb-module-toggle">
                                        <input type="checkbox" name="setting_auto_generate" value="yes" ' . checked($auto_generate, 'yes', false) . '>
                                        <span class="sb-module-toggle-slider"></span>
                                    </label>
                                </div>
                                <p class="sb-module-option-text">' . esc_html__('Automatically generate alt text when images are uploaded', 'wpswitchboard') . '</p>
                            </div>
                        </div>

                        <div class="sb-sp-info-box sb-sp-info-box-info" style="margin-bottom: 24px;">
                            <h4 class="sb-sp-heading-sm">' . esc_html__('Supported Image Formats', 'wpswitchboard') . '</h4>
                            <p class="sb-sp-description" style="margin: 0;">' . esc_html__('AI vision models support: PNG, JPEG, GIF, and WebP. AVIF, BMP, TIFF, and SVG are not supported — these will fall back to filename-based alt text.', 'wpswitchboard') . '</p>
                        </div>

                        <div class="sb-sp-info-box sb-sp-info-box-info" style="margin-bottom: 24px;">
                            <h4 class="sb-sp-heading-sm">' . esc_html__('Fallback Behavior', 'wpswitchboard') . '</h4>
                            <p class="sb-sp-description" style="margin: 0;">' . esc_html__('If AI is disabled or fails, alt text is generated from filename. Example: "beach-sunset-2024.jpg" becomes "Beach Sunset"', 'wpswitchboard') . '</p>
                        </div>

                        <div class="sb-sp-divider" style="margin: 24px 0;"></div>

                        <div class="sb-sp-group" style="margin-bottom: 16px;">
                            <h3 class="sb-sp-heading">' . esc_html__('Batch Process Old Images', 'wpswitchboard') . '</h3>
                            <p class="sb-sp-description">' . esc_html__('Generate alt text for images that are missing it or have only filename-based alt text.', 'wpswitchboard') . '</p>
                        </div>

                        <div class="sb-sp-card sb-sp-p-4">
                            <div class="sb-sp-button-group" style="justify-content: flex-start; margin-bottom: 16px;">
                                <button type="button" id="find-images-btn" class="button button-secondary button-small">
                                    <span class="dashicons dashicons-search" style="margin-top: 3px;"></span> ' . esc_html__('Find Images Without Alt Text', 'wpswitchboard') . '
                                </button>
                            </div>

                            <div id="images-found-result" class="sb-sp-info-box sb-sp-info-box-success" style="display: none; margin-bottom: 16px;">
                                <p id="images-count" class="sb-sp-description" style="margin: 0 0 12px 0;"></p>
                                <div class="sb-sp-button-group" style="justify-content: flex-start; margin-top: 0;">
                                    <button type="button" id="process-images-btn" class="button button-primary button-small" disabled>
                                        <span class="dashicons dashicons-update" style="margin-top: 3px;"></span> ' . esc_html__('Generate Alt Text for All', 'wpswitchboard') . '
                                    </button>
                                </div>
                            </div>

                            <div id="batch-progress" style="display: none; margin: 16px 0;">
                                <div style="background: #f3f4f6; border-radius: 8px; height: 8px; overflow: hidden; margin-bottom: 12px;">
                                    <div id="progress-bar" style="background: var(--primary-500); height: 100%; width: 0%; transition: width 0.3s ease; border-radius: 8px;"></div>
                                </div>
                                <p id="progress-text" class="sb-sp-description" style="margin: 0; font-size: 13px; color: #64748b;"></p>
                            </div>

                            <div id="batch-feedback" style="margin-top: 16px;"></div>

                            <div id="batch-results-table" style="display: none; margin-top: 24px;">
                                <div class="sb-sp-card" style="box-shadow: none !important; border: 1px solid #e5e7eb; overflow: hidden;">
                                    <div style="max-height: 500px; overflow-y: auto;">
                                        <table class="widefat" style="margin: 0; border: 0; border-collapse: collapse;">
                                            <thead>
                                                <tr style="background: #f9fafb; border-bottom: 2px solid #e5e7eb;">
                                                    <th style="padding: 12px 16px; text-align: left; font-size: 12px; font-weight: 600; color: #374151; text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap;">' . esc_html__('Image', 'wpswitchboard') . '</th>
                                                    <th style="padding: 12px 16px; text-align: left; font-size: 12px; font-weight: 600; color: #374151; text-transform: uppercase; letter-spacing: 0.5px;">' . esc_html__('Generated Alt Text', 'wpswitchboard') . '</th>
                                                    <th style="padding: 12px 16px; text-align: left; font-size: 12px; font-weight: 600; color: #374151; text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap;">' . esc_html__('Status', 'wpswitchboard') . '</th>
                                                </tr>
                                            </thead>
                                            <tbody id="batch-results-body"></tbody>
                                        </table>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <p class="sb-sp-description" style="margin-top: 16px;">' . esc_html__('You can also generate alt text manually from the Media Library using the "Generate with AI" button.', 'wpswitchboard') . '</p>

                        <style>
                        @keyframes spin {
                            from { transform: rotate(0deg); }
                            to { transform: rotate(360deg); }
                        }
                        </style>

                        <script>
                        jQuery(document).ready(function($) {
                            // Toggle AI settings visibility
                            $(\'input[name="setting_ai_enabled"]\').on(\'change\', function() {
                                if ($(this).is(\':checked\')) {
                                    $(\'#ai-settings-section\').slideDown();
                                } else {
                                    $(\'#ai-settings-section\').slideUp();
                                }
                            });

                            // Provider tag selection
                            $(\'.sb-module-checkbox-item[data-provider]\').on(\'click\', function() {
                                const provider = $(this).data(\'provider\');

                                // Update visual state
                                $(\'.sb-module-checkbox-item[data-provider]\').removeClass(\'active\');
                                $(this).addClass(\'active\');

                                // Update hidden input
                                $(\'#setting_ai_provider\').val(provider);

                                // Switch API key field to show correct provider\'s key
                                const $displayInput = $(\'#setting_api_key_display\');
                                const $clearBtn = $(\'#clear-api-btn\');
                                const $label = $(\'#api-key-label\');

                                if (provider === \'openai\') {
                                    const openaiKey = $(\'#setting_openai_api_key\').val();
                                    const hasKey = openaiKey && openaiKey.length > 0;

                                    // If encrypted, show dots; otherwise show actual value
                                    const isEncrypted = openaiKey && openaiKey.startsWith(\'ENC:\');
                                    const displayValue = isEncrypted ? \'••••••••••••••••\' : openaiKey;

                                    $displayInput.val(displayValue).attr(\'data-has-key\', hasKey ? \'1\' : \'0\');
                                    $label.html(\'<span id="api-key-label-text">OpenAI API Key</span>\' +
                                        (hasKey ? \'<span style="margin-left: 8px; color: #16a34a; font-size: 13px;">[Saved]</span>\' : \'\'));
                                    $clearBtn.toggle(hasKey);

                                    // Hide eye icon for encrypted values, show for plain text
                                    if (!isEncrypted && hasKey) {
                                        $(\'#toggle-api-visibility\').show();
                                    } else {
                                        $(\'#toggle-api-visibility\').hide();
                                    }

                                    $(\'.openai-models\').show();
                                    $(\'.openrouter-models\').hide();
                                    $(\'#api-link-openai\').show();
                                    $(\'#api-link-openrouter\').hide();

                                    // Auto-select default OpenAI model if current is OpenRouter
                                    const currentModel = $(\'#setting_model\').val();
                                    if (currentModel.includes(\'/\')) {
                                        $(\'.openai-models .sb-module-checkbox-item[data-model="gpt-4o-mini"]\').click();
                                    }
                                } else {
                                    const openrouterKey = $(\'#setting_openrouter_api_key\').val();
                                    const hasKey = openrouterKey && openrouterKey.length > 0;

                                    // If encrypted, show dots; otherwise show actual value
                                    const isEncrypted = openrouterKey && openrouterKey.startsWith(\'ENC:\');
                                    const displayValue = isEncrypted ? \'••••••••••••••••\' : openrouterKey;

                                    $displayInput.val(displayValue).attr(\'data-has-key\', hasKey ? \'1\' : \'0\');
                                    $label.html(\'<span id="api-key-label-text">OpenRouter API Key</span>\' +
                                        (hasKey ? \'<span style="margin-left: 8px; color: #16a34a; font-size: 13px;">[Saved]</span>\' : \'\'));
                                    $clearBtn.toggle(hasKey);

                                    // Hide eye icon for encrypted values, show for plain text
                                    if (!isEncrypted && hasKey) {
                                        $(\'#toggle-api-visibility\').show();
                                    } else {
                                        $(\'#toggle-api-visibility\').hide();
                                    }

                                    $(\'.openai-models\').hide();
                                    $(\'.openrouter-models\').show();
                                    $(\'#api-link-openai\').hide();
                                    $(\'#api-link-openrouter\').show();

                                    // Auto-select default OpenRouter model if current is OpenAI
                                    const currentModel = $(\'#setting_model\').val();
                                    if (!currentModel.includes(\'/\')) {
                                        $(\'.openrouter-models .sb-module-checkbox-item[data-model="openai/gpt-4o-mini"]\').click();
                                    }
                                }
                            });

                            // Model tag selection
                            $(\'.sb-module-checkbox-item[data-model]\').on(\'click\', function() {
                                const model = $(this).data(\'model\');
                                const container = $(this).parent();

                                // Update visual state within the same container
                                container.find(\'.sb-module-checkbox-item[data-model]\').removeClass(\'active\');
                                $(this).addClass(\'active\');

                                // Update hidden input
                                $(\'#setting_model\').val(model);
                            });

                            // Toggle API key visibility
                            $(\'#toggle-api-visibility\').on(\'click\', function() {
                                const $input = $(\'#setting_api_key_display\');
                                const $icon = $(this).find(\'.dashicons\');

                                if ($input.attr(\'type\') === \'password\') {
                                    $input.attr(\'type\', \'text\');
                                    $icon.removeClass(\'dashicons-visibility\').addClass(\'dashicons-hidden\');
                                    $(this).attr(\'title\', \'Hide API key\');
                                } else {
                                    $input.attr(\'type\', \'password\');
                                    $icon.removeClass(\'dashicons-hidden\').addClass(\'dashicons-visibility\');
                                    $(this).attr(\'title\', \'Show API key\');
                                }
                            });

                            // Clear API key button
                            $(\'#clear-api-btn\').on(\'click\', function() {
                                const provider = $(\'#setting_ai_provider\').val();
                                const $displayInput = $(\'#setting_api_key_display\');
                                const $label = $(\'#api-key-label\');

                                // Clear the display field
                                $displayInput.val(\'\').attr(\'data-has-key\', \'0\').focus();

                                // Update the appropriate hidden field
                                if (provider === \'openai\') {
                                    $(\'#setting_openai_api_key\').val(\'\');
                                    $label.html(\'<span id="api-key-label-text">OpenAI API Key</span>\');
                                } else {
                                    $(\'#setting_openrouter_api_key\').val(\'\');
                                    $label.html(\'<span id="api-key-label-text">OpenRouter API Key</span>\');
                                }

                                // Hide clear button and eye icon
                                $(this).hide();
                                $(\'#toggle-api-visibility\').hide();
                            });

                            // Update hidden input when typing in display field
                            $(\'#setting_api_key_display\').on(\'input\', function() {
                                const provider = $(\'#setting_ai_provider\').val();
                                const value = $(this).val();

                                // Update the appropriate hidden field
                                if (provider === \'openai\') {
                                    $(\'#setting_openai_api_key\').val(value);
                                } else {
                                    $(\'#setting_openrouter_api_key\').val(value);
                                }

                                // Show/hide clear button based on whether there\'s a value
                                const hasValue = value && value.length > 0;
                                $(\'#clear-api-btn\').toggle(hasValue);

                                // Show/hide eye icon - only show for plain text (not encrypted dots)
                                const isEncrypted = value === \'••••••••••••••••\';
                                if (!isEncrypted && hasValue) {
                                    $(\'#toggle-api-visibility\').show();
                                } else {
                                    $(\'#toggle-api-visibility\').hide();
                                }

                                // Update saved indicator
                                const $label = $(\'#api-key-label\');
                                if (value && value !== \'••••••••••••••••\') {
                                    // User is typing a new key - remove saved indicator
                                    const labelText = provider === \'openai\' ? \'OpenAI API Key\' : \'OpenRouter API Key\';
                                    $label.html(\'<span id="api-key-label-text">\' + labelText + \'</span>\');
                                }
                            });

                            // Hover effects are handled by CSS

                            // Test API configuration
                            $(\'#test-api-btn\').on(\'click\', function() {
                                const $btn = $(this);
                                const $result = $(\'#api-test-result\');
                                const provider = $(\'#setting_ai_provider\').val();
                                const model = $(\'#setting_model\').val();
                                let apiKey = $(\'#setting_api_key_display\').val();

                                // If showing dots, get the actual encrypted key from hidden field
                                if (apiKey === \'••••••••••••••••\') {
                                    if (provider === \'openai\') {
                                        apiKey = $(\'#setting_openai_api_key\').val();
                                    } else {
                                        apiKey = $(\'#setting_openrouter_api_key\').val();
                                    }
                                }

                                // Validate inputs
                                if (!apiKey || apiKey.trim() === \'\') {
                                    $result.css({\'background\': \'#fee2e2\', \'color\': \'#991b1b\', \'border-color\': \'#fca5a5\'})
                                        .html(\'<strong>Error:</strong> Please enter an API key first.\').show();
                                    return;
                                }

                                // Disable button and show loading
                                $btn.prop(\'disabled\', true).html(\'<span class="dashicons dashicons-update dashicons-spin"></span> Testing...\');
                                $result.css({\'background\': \'#dbeafe\', \'color\': \'#1e40af\', \'border-color\': \'#93c5fd\'})
                                    .html(\'<strong>Testing API connection...</strong><br>Provider: \' + (provider === \'openai\' ? \'OpenAI\' : \'OpenRouter\') + \'<br>Model: \' + model).show();

                                // Call test endpoint
                                $.post(ajaxurl, {
                                    action: \'switchboard_test_alt_text_api\',
                                    nonce: \'' . wp_create_nonce('switchboard_test_api') . '\',
                                    provider: provider,
                                    model: model,
                                    api_key: apiKey
                                }, function(response) {
                                    $btn.prop(\'disabled\', false).html(\'<span class="dashicons dashicons-yes-alt" style="font-size: 16px; width: 16px; height: 16px; vertical-align: middle; margin-right: 4px;"></span> Test API\');

                                    if (response.success) {
                                        $result.css({\'background\': \'#dcfce7\', \'color\': \'#166534\', \'border-color\': \'#86efac\'})
                                            .html(\'<strong>API Test Successful!</strong><br>\' + response.data.message).show();
                                    } else {
                                        $result.css({\'background\': \'#fee2e2\', \'color\': \'#991b1b\', \'border-color\': \'#fca5a5\'})
                                            .html(\'<strong>API Test Failed</strong><br>\' + response.data.message).show();
                                    }
                                }).fail(function() {
                                    $btn.prop(\'disabled\', false).html(\'<span class="dashicons dashicons-yes-alt" style="font-size: 16px; width: 16px; height: 16px; vertical-align: middle; margin-right: 4px;"></span> Test API\');
                                    $result.css({\'background\': \'#fee2e2\', \'color\': \'#991b1b\', \'border-color\': \'#fca5a5\'})
                                        .html(\'<strong>Connection Failed</strong><br>Could not connect to server. Please check your internet connection.\').show();
                                });
                            });

                            // Batch processing functionality
                            let foundImages = [];

                            // Find images without alt text
                            $(\'#find-images-btn\').on(\'click\', function() {
                                const $btn = $(this);
                                const $result = $(\'#images-found-result\');
                                const $count = $(\'#images-count\');
                                const $processBtn = $(\'#process-images-btn\');

                                $btn.prop(\'disabled\', true).html(\'<span class="dashicons dashicons-update" style="animation: spin 1s linear infinite; margin-top: 3px;"></span> Searching...\');
                                $result.hide();

                                $.post(ajaxurl, {
                                    action: \'switchboard_find_images_without_alt\',
                                    nonce: \'' . wp_create_nonce('switchboard_batch_alt') . '\'
                                }, function(response) {
                                    $btn.prop(\'disabled\', false).html(\'<span class="dashicons dashicons-search" style="margin-top: 3px;"></span> ' . esc_js(__('Find Images Without Alt Text', 'wpswitchboard')) . '\');

                                    if (response.success) {
                                        foundImages = response.data.image_ids;
                                        const count = foundImages.length;

                                        if (count > 0) {
                                            $count.html(\'<strong>\' + count + \' image\' + (count === 1 ? \'\' : \'s\') + \' found without proper alt text.</strong>\');
                                            $processBtn.prop(\'disabled\', false);
                                            $result.show();
                                        } else {
                                            $count.html(\'<strong>No images found!</strong> All images have alt text.\');
                                            $processBtn.prop(\'disabled\', true);
                                            $result.removeClass(\'sb-sp-info-box-success\').addClass(\'sb-sp-info-box-info\');
                                            $result.show();
                                        }
                                    } else {
                                        alert(\'Error: \' + response.data.message);
                                    }
                                }).fail(function() {
                                    $btn.prop(\'disabled\', false).html(\'<span class="dashicons dashicons-search" style="margin-top: 3px;"></span> ' . esc_js(__('Find Images Without Alt Text', 'wpswitchboard')) . '\');
                                    alert(\'Failed to search for images. Please try again.\');
                                });
                            });

                            // Process images
                            $(\'#process-images-btn\').on(\'click\', function() {
                                if (!foundImages.length) return;

                                // Warn for large batches
                                let confirmMessage = \'Generate alt text for \' + foundImages.length + \' image(s)? This will use your AI credits.\';
                                if (foundImages.length > 50) {
                                    confirmMessage += \'\\n\\nNote: Processing \' + foundImages.length + \' images may take several minutes.\';
                                }

                                if (!confirm(confirmMessage)) return;

                                const $btn = $(this);
                                const $progress = $(\'#batch-progress\');
                                const $progressBar = $(\'#progress-bar\');
                                const $progressText = $(\'#progress-text\');
                                const $feedback = $(\'#batch-feedback\');
                                const $resultsTable = $(\'#batch-results-table\');
                                const $resultsBody = $(\'#batch-results-body\');

                                $btn.prop(\'disabled\', true).html(\'<span class="dashicons dashicons-update" style="animation: spin 1s linear infinite; margin-top: 3px;"></span> Processing...\');
                                $progress.show();
                                $progressBar.css(\'width\', \'0%\');
                                $feedback.empty();
                                $resultsBody.empty();
                                $resultsTable.show();

                                let processed = 0;
                                let succeeded = 0;
                                let failed = 0;

                                function processNext() {
                                    if (processed >= foundImages.length) {
                                        // Done
                                        $progressBar.css(\'width\', \'100%\');
                                        $progressText.html(\'<strong style="color: #166534;">Complete!</strong> \' + succeeded + \' succeeded, \' + failed + \' failed\');
                                        if (succeeded > 0 || failed > 0) {
                                            $feedback.html(\'<div class="sb-sp-info-box sb-sp-info-box-success" style="margin-top: 16px;"><h4 class="sb-sp-heading-sm">Batch Processing Complete</h4><p class="sb-sp-description" style="margin: 0;">Successfully generated: <strong>\' + succeeded + \'</strong><br>Failed: <strong>\' + failed + \'</strong></p></div>\');
                                        }
                                        $btn.prop(\'disabled\', false).html(\'<span class="dashicons dashicons-update" style="margin-top: 3px;"></span> ' . esc_js(__('Generate Alt Text for All', 'wpswitchboard')) . '\');
                                        foundImages = [];
                                        return;
                                    }

                                    const imageId = foundImages[processed];

                                    // Add placeholder row
                                    const isEven = processed % 2 === 0;
                                    const $row = $(\'<tr style="border-bottom: 1px solid #f3f4f6; \' + (isEven ? \'background: #ffffff;\' : \'background: #f9fafb;\') + \' transition: background-color 0.15s ease;"></tr>\');
                                    $row.append(\'<td style="padding: 12px 16px;"><div style="width: 50px; height: 50px; background: #f3f4f6; border-radius: 6px; display: flex; align-items: center; justify-content: center;"><span class="dashicons dashicons-format-image" style="color: #9ca3af; font-size: 24px;"></span></div></td>\');
                                    $row.append(\'<td style="padding: 12px 16px; font-size: 13px; color: #64748b;"><em>Generating...</em></td>\');
                                    $row.append(\'<td style="padding: 12px 16px;"><span class="dashicons dashicons-update" style="animation: spin 1s linear infinite; color: var(--primary-500);"></span></td>\');
                                    $resultsBody.append($row);

                                    $.post(ajaxurl, {
                                        action: \'switchboard_batch_generate_alt\',
                                        image_id: imageId,
                                        nonce: \'' . wp_create_nonce('switchboard_batch_alt') . '\'
                                    }, function(response) {
                                        processed++;

                                        // Get image thumbnail
                                        const imgSrc = response.data && response.data.thumbnail ? response.data.thumbnail : \'\';

                                        if (response.success) {
                                            succeeded++;
                                            const altText = response.data.alt_text || \'Generated\';

                                            $row.empty();
                                            $row.append(\'<td style="padding: 12px 16px;">\' + (imgSrc ? \'<img src="\' + imgSrc + \'" style="width: 50px; height: 50px; object-fit: cover; border-radius: 6px; border: 1px solid #e5e7eb;">\' : \'<div style="width: 50px; height: 50px; background: #f3f4f6; border-radius: 6px;"></div>\') + \'</td>\');
                                            $row.append(\'<td style="padding: 12px 16px; font-size: 13px; color: #111827;">\' + altText + \'</td>\');
                                            $row.append(\'<td style="padding: 12px 16px;"><span class="sb-sp-badge" style="background: #dcfce7; color: #166534; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; display: inline-block;">Success</span></td>\');
                                        } else {
                                            failed++;
                                            const errorMsg = response.data && response.data.message ? response.data.message : \'Failed\';

                                            $row.empty();
                                            $row.append(\'<td style="padding: 12px 16px;">\' + (imgSrc ? \'<img src="\' + imgSrc + \'" style="width: 50px; height: 50px; object-fit: cover; border-radius: 6px; border: 1px solid #e5e7eb;">\' : \'<div style="width: 50px; height: 50px; background: #f3f4f6; border-radius: 6px;"></div>\') + \'</td>\');
                                            $row.append(\'<td style="padding: 12px 16px; font-size: 13px; color: #991b1b;">\' + errorMsg + \'</td>\');
                                            $row.append(\'<td style="padding: 12px 16px;"><span class="sb-sp-badge" style="background: #fee2e2; color: #991b1b; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; display: inline-block;">Failed</span></td>\');
                                        }

                                        const percent = Math.round((processed / foundImages.length) * 100);
                                        $progressBar.css(\'width\', percent + \'%\');
                                        $progressText.html(\'Processing... <strong>\' + processed + \' / \' + foundImages.length + \'</strong> (\' + percent + \'%)\');

                                        // Add small delay to prevent rate limiting
                                        setTimeout(processNext, 500);
                                    }).fail(function() {
                                        processed++;
                                        failed++;

                                        $row.empty();
                                        $row.append(\'<td style="padding: 12px 16px;"><div style="width: 50px; height: 50px; background: #f3f4f6; border-radius: 6px;"></div></td>\');
                                        $row.append(\'<td style="padding: 12px 16px; font-size: 13px; color: #991b1b;">Connection failed</td>\');
                                        $row.append(\'<td style="padding: 12px 16px;"><span class="sb-sp-badge" style="background: #fee2e2; color: #991b1b; padding: 4px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; display: inline-block;">Failed</span></td>\');

                                        const percent = Math.round((processed / foundImages.length) * 100);
                                        $progressBar.css(\'width\', percent + \'%\');
                                        $progressText.html(\'Processing... <strong>\' + processed + \' / \' + foundImages.length + \'</strong> (\' + percent + \'%)\');

                                        // Add small delay before retry
                                        setTimeout(processNext, 500);
                                    });
                                }

                                processNext();
                            });
                        });
                        </script>
                    ';
                echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- HTML is built with escaped values
                
    }

    /**
     * Register data locations for cleanup system
     */
    public function register_data_locations() {
        return [
            'options' => [
                'switchboard_auto-alt-text_settings',
            ],
            'option_patterns' => [],
            'tables' => [],
            'post_meta' => [],
            'user_meta' => [],
        ];
    }
}

$switchboard_instance = new SwitchboardAutoAltText();
Switchboard_Data_Registry::get_instance()->auto_register($switchboard_instance, __FILE__);
