<?php
/**
 * Handles frontend filtering for SEO data overrides (title, meta description, alt tags, content URLs).
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

class Rank_Frontend_Filters {

    // Track if content was modified by the standard filters
    private static $content_modified = false;
    
    // Cache for page-specific image alt overrides (per-request cache)
    private static $page_image_overrides_cache = null;

    /**
     * Initialize frontend filter hooks.
     */
    public static function init() {
        // Image Alt Tags
        add_filter( 'wp_get_attachment_image_attributes', array( __CLASS__, 'filter_attachment_alt' ), 10, 2 );

        // Title Tags
        add_filter( 'pre_get_document_title', array( __CLASS__, 'filter_fallback_title' ), 5, 1 ); // Fallback
        add_filter( 'wpseo_title', array( __CLASS__, 'filter_seo_plugin_title' ), 15, 1 ); // Yoast
        add_filter( 'rank_math/frontend/title', array( __CLASS__, 'filter_seo_plugin_title' ), 15, 1 ); // Rank Math

        // Meta Descriptions
        add_filter( 'wpseo_metadesc', array( __CLASS__, 'filter_meta_description' ), 10, 1 ); // Yoast
        add_filter( 'rank_math/frontend/description', array( __CLASS__, 'filter_meta_description' ), 10, 1 ); // Rank Math
        add_action( 'wp_head', array( __CLASS__, 'add_meta_description_tag_fallback' ), 5 ); // Fallback output

        // --- Content URL Replacement Filters ---
        // Apply to standard content filters
        add_filter( 'the_content', array( __CLASS__, 'filter_content_urls' ), 10, 1 );
        add_filter( 'the_excerpt', array( __CLASS__, 'filter_content_urls' ), 10, 1 );
        
        // Use output buffering as a fallback for content not caught by filters
        add_action( 'template_redirect', array( __CLASS__, 'maybe_start_output_buffer' ) );
        
        // Add hooks for JavaScript-based alt tag handling
        add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_alt_tag_script' ), 1 );
        add_action( 'wp_ajax_get_bulk_alt_tags', array( __CLASS__, 'handle_bulk_alt_tags_request' ) );
        add_action( 'wp_ajax_nopriv_get_bulk_alt_tags', array( __CLASS__, 'handle_bulk_alt_tags_request' ) );
    }
    
    /**
     * Start output buffering only if needed
     */
    public static function maybe_start_output_buffer() {
        // Only apply on frontend
        if (is_admin()) {
            return;
        }
        
        // Start the output buffer - we'll check if it's needed in the callback
        ob_start(array(__CLASS__, 'process_output_buffer'));
    }
    
    /**
     * Process the output buffer to replace URLs in the final HTML
     * Only performs replacements if standard filters didn't catch everything
     */
    public static function process_output_buffer($buffer) {
        // If content was already modified by standard filters, just return the buffer
        if (self::$content_modified) {
            return $buffer;
        }
        
        // Get the current page URL
        $current_url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
        if (empty($current_url)) {
            return $buffer;
        }
        
        // Get the full current URL
        $protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? "https" : "http";
        $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
        $full_current_url = $protocol . "://" . $host . $current_url;
        
        // Get URL replacements for this specific page context
        $replacements = self::get_url_replacements_for_page($full_current_url);
        
        // If no replacements found, return the buffer unchanged
        if (empty($replacements)) {
            return $buffer;
        }
        
        // Perform replacements
        $modified_buffer = $buffer;
        
        foreach ($replacements as $old_url => $new_url) {
            // Try exact string replacement first
            $temp_buffer = str_replace($old_url, $new_url, $modified_buffer);
            
            // If that didn't work, try with href attribute
            if ($temp_buffer === $modified_buffer) {
                $href_pattern = '/href=["\']' . preg_quote($old_url, '/') . '["\']/i';
                $href_replacement = 'href="' . $new_url . '"';
                $temp_buffer = preg_replace($href_pattern, $href_replacement, $modified_buffer);
            }
            
            // If still no match, try URL-decoded version (handles %20 spaces, etc.)
            if ($temp_buffer === $modified_buffer) {
                $decoded_old_url = urldecode($old_url);
                if ($decoded_old_url !== $old_url) {
                    // Try exact string replacement with decoded URL
                    $temp_buffer = str_replace($decoded_old_url, $new_url, $modified_buffer);
                    
                    // If that didn't work, try with href attribute using decoded URL
                    if ($temp_buffer === $modified_buffer) {
                        $href_pattern = '/href=["\']' . preg_quote($decoded_old_url, '/') . '["\']/i';
                        $href_replacement = 'href="' . $new_url . '"';
                        $temp_buffer = preg_replace($href_pattern, $href_replacement, $modified_buffer);
                    }
                }
            }
            
            $modified_buffer = $temp_buffer;
        }
        
        return $modified_buffer;
    }
    
    /**
     * Get URL replacements specific to the current page
     * This optimizes performance by only loading relevant replacements
     */
    private static function get_url_replacements_for_page($page_url) {
        $replacements = array();
        
        // Get internal URL replacements
        $internal_replacements = get_option('rank_internal_url_replacements', []);
        $internal_count = is_array($internal_replacements) ? count($internal_replacements) : 0;
        
        if (is_array($internal_replacements)) {
            // First, quickly check if this page has any replacements
            $has_replacements_for_page = false;
            $quick_check_count = 0;
            foreach ($internal_replacements as $old_url => $data) {
                $quick_check_count++;
                if (is_array($data) && isset($data['context']) && $data['context'] === $page_url) {
                    $has_replacements_for_page = true;
                    break;
                }
            }
            
            // Only process if this page has replacements
            if ($has_replacements_for_page) {
                foreach ($internal_replacements as $old_url => $data) {
                    // Only include replacements relevant to this page
                    if (is_array($data) && isset($data['new_url']) && isset($data['context'])) {
                        // If this replacement is for the current page or has no specific context
                        if ($data['context'] === $page_url || empty($data['context'])) {
                            // Extract the actual broken URL from composite key or use broken_url field
                            $broken_url = isset($data['broken_url']) ? $data['broken_url'] : $old_url;
                            
                            // If it's a composite key, extract the broken URL part
                            if (strpos($broken_url, '::context::') !== false) {
                                $broken_url = explode('::context::', $broken_url)[0];
                            }
                            
                            $replacements[$broken_url] = $data['new_url'];
                        }
                    }
                }
            }
        }
        
        // Get external URL replacements (same optimization)
        $external_replacements = get_option('rank_external_url_replacements', []);
        $external_count = is_array($external_replacements) ? count($external_replacements) : 0;
        
        if (is_array($external_replacements)) {
            // First, quickly check if this page has any replacements
            $has_replacements_for_page = false;
            $quick_check_count = 0;
            foreach ($external_replacements as $old_url => $data) {
                $quick_check_count++;
                if (is_array($data) && isset($data['context']) && $data['context'] === $page_url) {
                    $has_replacements_for_page = true;
                    break;
                }
            }
            
            // Only process if this page has replacements
            if ($has_replacements_for_page) {
                foreach ($external_replacements as $old_url => $data) {
                    // Only include replacements relevant to this page
                    if (is_array($data) && isset($data['new_url']) && isset($data['context'])) {
                        // If this replacement is for the current page or has no specific context
                        if ($data['context'] === $page_url || empty($data['context'])) {
                            // Extract the actual broken URL from composite key or use broken_url field
                            $broken_url = isset($data['broken_url']) ? $data['broken_url'] : $old_url;
                            
                            // If it's a composite key, extract the broken URL part
                            if (strpos($broken_url, '::context::') !== false) {
                                $broken_url = explode('::context::', $broken_url)[0];
                            }
                            
                            $replacements[$broken_url] = $data['new_url'];
                        }
                    }
                }
            }
        }
        
        // Only log when there are actual replacements to avoid log spam
        if (!empty($replacements)) {
            Rank_Processor::add_debug_log("URL Replacements: Found " . count($replacements) . " for page (from " . ($internal_count + $external_count) . " total in DB)");
        }
        
        return $replacements;
    }

    /**
     * Load all image alt overrides for the current page into cache.
     * This runs only once per page request to eliminate N+1 queries.
     */
    private static function load_page_image_overrides() {
        // Return early if cache is already loaded
        if ( self::$page_image_overrides_cache !== null ) {
            return;
        }
        
        global $wpdb;
        
        // Ensure RANK_TABLE_NAME is defined before using it
        if ( ! defined('RANK_TABLE_NAME') ) {
            error_log('RANK Plugin Error: RANK_TABLE_NAME constant not defined in Rank_Frontend_Filters.');
            self::$page_image_overrides_cache = array(); // Set empty cache to prevent repeated attempts
            return;
        }
        
        $table_name = $wpdb->prefix . RANK_TABLE_NAME;
        $post_id = get_queried_object_id();
        
        // Get current URL for fallback lookup
        $protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? "https" : "http";
        $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
        $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
        $current_url = $protocol . "://" . $host . $uri;
        
        // Initialize cache as empty array
        self::$page_image_overrides_cache = array();
        
        // URL-First Lookup Strategy for image alt tags
        
        // Step 1: Get all URL-specific overrides first (highest priority)
        $sql = $wpdb->prepare(
            "SELECT object_id, override_value
             FROM $table_name
             WHERE original_page_url = %s
             AND override_type = 'image_alt'
             ORDER BY override_id DESC",
            $current_url
        );
        
        $url_results = $wpdb->get_results( $sql );
        
        // Cache URL-specific results with priority 1 (highest)
        foreach ( $url_results as $row ) {
            $attachment_id = absint( $row->object_id );
            self::$page_image_overrides_cache[$attachment_id] = array(
                'value' => $row->override_value,
                'priority' => 1
            );
        }
        
        // Step 2: Get post_id-specific overrides as fallback (only for uncached items)
        if ( $post_id > 0 ) {
            $sql = $wpdb->prepare(
                "SELECT object_id, override_value
                 FROM $table_name
                 WHERE post_id = %d
                 AND override_type = 'image_alt'
                 ORDER BY override_id DESC",
                absint( $post_id )
            );
            
            $post_results = $wpdb->get_results( $sql );
            
            // Cache post-specific results with priority 2 (lower), only if not already cached
            foreach ( $post_results as $row ) {
                $attachment_id = absint( $row->object_id );
                
                // Only cache if not already set by URL-specific override
                if ( ! isset( self::$page_image_overrides_cache[$attachment_id] ) ) {
                    self::$page_image_overrides_cache[$attachment_id] = array(
                        'value' => $row->override_value,
                        'priority' => 2
                    );
                }
            }
        }
    }

    /**
     * Get override value from the database.
     * For image_alt overrides, this now uses the cached data to avoid N+1 queries.
     *
     * @param int    $post_id       Post ID context.
     * @param int    $object_id     Specific object ID (e.g., attachment ID), 0 for post-level.
     * @param string $override_type The type of override ('title', 'meta_description', 'image_alt').
     * @return string|null The override value or null if not found.
     */
    public static function get_override_value( $post_id, $object_id, $override_type ) {
        // For image_alt overrides, use the cached approach
        if ( $override_type === 'image_alt' ) {
            self::load_page_image_overrides();
            
            $attachment_id = absint( $object_id );
            if ( isset( self::$page_image_overrides_cache[$attachment_id] ) ) {
                return self::$page_image_overrides_cache[$attachment_id]['value'];
            }
            
            return null;
        }
        
        // For other override types (title, meta_description), use the original method
        global $wpdb;

        // Ensure RANK_TABLE_NAME is defined before using it
        if ( ! defined('RANK_TABLE_NAME') ) {
             error_log('RANK Plugin Error: RANK_TABLE_NAME constant not defined in Rank_Frontend_Filters.');
             return null;
        }
        $table_name = $wpdb->prefix . RANK_TABLE_NAME;

        // Get current URL for fallback lookup
        $protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? "https" : "http";
        $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
        $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
        $current_url = $protocol . "://" . $host . $uri;
        
        // URL-First Lookup Strategy: First try URL match, then fall back to post_id
        
        // Step 1: Try to find override by URL first (highest priority for multilingual sites)
        $sql = $wpdb->prepare(
            "SELECT override_value
             FROM $table_name
             WHERE original_page_url = %s
             AND object_id = %d
             AND override_type = %s
             ORDER BY override_id DESC
             LIMIT 1",
            $current_url,
            absint( $object_id ),
            sanitize_key( $override_type )
        );
        $override = $wpdb->get_var( $sql );
        
        // Step 2: If no URL match found, fall back to post_id match
        if ( $override === null && $post_id > 0 ) {
            $sql = $wpdb->prepare(
                "SELECT override_value
                 FROM $table_name
                 WHERE post_id = %d
                 AND object_id = %d
                 AND override_type = %s
                 ORDER BY override_id DESC
                 LIMIT 1",
                absint( $post_id ),
                absint( $object_id ),
                sanitize_key( $override_type )
            );
            $override = $wpdb->get_var( $sql );
        }

        return $override; // Returns null if no row found
    }

    /**
     * Filter post content to replace specific URLs.
     *
     * @param string $content The original post content.
     * @return string The modified post content.
     */
    public static function filter_content_urls($content) {
        // Skip empty content
        if (empty($content)) {
            return $content;
        }
        
        // Get the current page URL
        $protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? "https" : "http";
        $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
        $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
        $current_url = $protocol . "://" . $host . $uri;
        
        // Get URL replacements for this specific page
        $replacements = self::get_url_replacements_for_page($current_url);
        
        // If no replacements found, return content unchanged
        if (empty($replacements)) {
            return $content;
        }
        
        $updated_content = $content;
        $content_changed = false;
        $total_replacements_made = 0;
        
        // Pre-compute all URL variations for efficient batch processing
        $url_variations = array();
        foreach ($replacements as $old_url => $new_url) {
            $variations = array($old_url); // Start with original
            
            // Add decoded variations if different
            $decoded = urldecode($old_url);
            if ($decoded !== $old_url) {
                $variations[] = $decoded;
            }
            
            $raw_decoded = rawurldecode($old_url);
            if ($raw_decoded !== $old_url && $raw_decoded !== $decoded) {
                $variations[] = $raw_decoded;
            }
            
            // Store all variations pointing to the same replacement
            foreach ($variations as $variation) {
                $url_variations[$variation] = array(
                    'new_url' => $new_url,
                    'original' => $old_url,
                    'type' => ($variation === $old_url) ? 'original' : 'decoded'
                );
            }
        }
        
        // Perform efficient batch replacements
        foreach ($url_variations as $search_url => $data) {
            $new_url = $data['new_url'];
            $original_url = $data['original'];
            $type = $data['type'];
            
            // Try exact string replacement
            $temp_content = str_replace($search_url, $new_url, $updated_content);
            if ($temp_content !== $updated_content) {
                $updated_content = $temp_content;
                $content_changed = true;
                $total_replacements_made++;
                continue;
            }
            
            // Try href pattern replacement
            $href_pattern = '/href=["\']' . preg_quote($search_url, '/') . '["\']/i';
            $href_replacement = 'href="' . $new_url . '"';
            $temp_content = preg_replace($href_pattern, $href_replacement, $updated_content);
            if ($temp_content !== $updated_content) {
                $updated_content = $temp_content;
                $content_changed = true;
                $total_replacements_made++;
                continue;
            }
        }
        
        // Only try expensive flexible matching if no replacements were made
        if ($total_replacements_made === 0 && !empty($replacements)) {
            foreach ($replacements as $old_url => $new_url) {
                $url_parts = parse_url($old_url);
                if (isset($url_parts['path']) && !empty($url_parts['path'])) {
                    $path_part = ltrim($url_parts['path'], '/');
                    
                    // Create a flexible pattern that handles mixed encoding
                    $flexible_pattern = str_replace(['%20', '+'], ['\s+', '\s+'], preg_quote($path_part, '/'));
                    $href_pattern = '/href=["\'][^"\']*' . $flexible_pattern . '[^"\']*["\']/i';
                    
                    if (preg_match($href_pattern, $updated_content)) {
                        $temp_content = preg_replace($href_pattern, 'href="' . $new_url . '"', $updated_content);
                        if ($temp_content !== $updated_content) {
                            $updated_content = $temp_content;
                            $content_changed = true;
                            $total_replacements_made++;
                            break; // Stop after first flexible match to avoid over-replacement
                        }
                    }
                }
            }
        }
        
        // Only log when replacements are actually made or when debugging specific issues
        if ($total_replacements_made > 0) {
            Rank_Processor::add_debug_log("URL Replacements: " . $total_replacements_made . " successful replacements made in content");
        } else if (!empty($replacements)) {
            // Only log failures when there were expected replacements but none worked
            Rank_Processor::add_debug_log("URL Replacements: 0 matches found for " . count($replacements) . " expected replacements");
        }
        
        // If content was changed, mark it so output buffer knows not to process it again
        if ($content_changed) {
            self::$content_modified = true;
        }
        
        return $updated_content;
    }

    /**
     * Filter image attributes to add alt text override if empty.
     * Now uses cached data to avoid N+1 queries.
     *
     * @param array $attr Attributes for the image tag.
     * @param WP_Post $attachment Attachment object.
     * @return array Modified attributes.
     */
    public static function filter_attachment_alt( $attr, $attachment ) {
        if ( empty( $attr['alt'] ) && is_object($attachment) && isset($attachment->ID) ) {
            $attachment_id = absint( $attachment->ID );
            
            // Load the cache if not already loaded (this happens only once per page request)
            self::load_page_image_overrides();
            
            // Simple cache lookup - no database query needed
            if ( isset( self::$page_image_overrides_cache[$attachment_id] ) ) {
                $attr['alt'] = self::$page_image_overrides_cache[$attachment_id]['value'];
            }
        }
        return $attr;
    }

    /**
     * Fallback function to filter the document title if major SEO plugins are inactive.
     *
     * @param string $title The original title.
     * @return string The original or overridden title.
     */
    public static function filter_fallback_title( $title ) {
        if ( defined('WPSEO_VERSION') || defined('RANK_MATH_VERSION') ) {
            return $title;
        }

        $post_id = get_queried_object_id();
        if ( $post_id > 0 ) {
            $title_override = self::get_override_value( $post_id, 0, 'title' );
            if ( $title_override !== null ) {
                return esc_html( $title_override );
            }
        }
        return $title;
    }

    /**
     * Callback function to potentially override the title generated by SEO plugins.
     *
     * @param string $title The original title generated by the SEO plugin.
     * @return string The original or overridden title.
     */
    public static function filter_seo_plugin_title( $title ) {
        $post_id = get_queried_object_id();
        if ( $post_id > 0 ) {
            $title_override = self::get_override_value( $post_id, 0, 'title' );
            if ( $title_override !== null ) {
                return esc_html( $title_override );
            }
        }
        return $title;
    }

    /**
     * Callback function to potentially override the meta description generated by SEO plugins.
     *
     * @param string $description The original description.
     * @return string The original or overridden description.
     */
    public static function filter_meta_description( $description ) {
        $post_id = get_queried_object_id();
        if ( $post_id > 0 ) {
            $desc_override = self::get_override_value( $post_id, 0, 'meta_description' );
            if ( $desc_override !== null ) {
                // Use the override value directly (sanitized on save)
                return $desc_override;
            }
        }
        return $description;
    }

    /**
     * Fallback function to output the meta description tag directly if no known SEO plugin is active.
     */
    public static function add_meta_description_tag_fallback() {
        if ( defined('WPSEO_VERSION') || defined('RANK_MATH_VERSION') || defined('AIOSEO_VERSION') || defined('SEOPRESS_VERSION') ) {
            return;
        }
        
        $post_id = get_queried_object_id();
        if ( ! $post_id ) {
            return;
        }

        $desc_override = self::get_override_value( $post_id, 0, 'meta_description' );

        if ( $desc_override !== null ) {
            echo '<meta name="description" content="' . esc_attr( $desc_override ) . '" />' . "\n";
        }
    }
    
    /**
     * Enqueue the JavaScript for handling alt tags.
     * Script is set to load in the head with defer attribute for faster execution.
     */
    public static function enqueue_alt_tag_script() {
        // Only enqueue on frontend
        if ( is_admin() ) {
            return;
        }
        
        // Path to the JavaScript file
        $js_file = plugin_dir_url( dirname( __FILE__ ) ) . 'assets/js/rank-alt.js';
        
        // Register and enqueue the script in head with defer
        wp_register_script(
            'rank-alt-tags',
            $js_file,
            array(),
            '1.4.1', // Updated version - performance fix for forced reflows
            false // Load in head instead of footer
        );
        
        // Add defer attribute for non-blocking execution
        add_filter( 'script_loader_tag', function( $tag, $handle ) {
            if ( 'rank-alt-tags' === $handle ) {
                return str_replace( ' src', ' defer src', $tag );
            }
            return $tag;
        }, 10, 2 );
        
        wp_enqueue_script( 'rank-alt-tags' );
        
        // Get the current post ID
        $post_id = get_queried_object_id();
        
        // Add the AJAX URL, post ID, and current URL to the script
        wp_localize_script( 'rank-alt-tags', 'rankAltTags', array(
            'ajaxUrl' => admin_url( 'admin-ajax.php' ),
            'postId' => $post_id,
            // Current URL is obtained directly in JavaScript using window.location.href
        ));
    }
    
    /**
     * Handle AJAX request for bulk alt tags.
     */
    public static function handle_bulk_alt_tags_request() {
        // Get the array of image URLs
        $urls = isset($_POST['urls']) ? json_decode(stripslashes($_POST['urls']), true) : [];
        
        if (empty($urls) || !is_array($urls)) {
            wp_send_json(array(
                'success' => false,
                'message' => 'No URLs provided'
            ));
            return;
        }
        
        $alt_tags = array();
        
        // Get post ID from the request, fallback to get_queried_object_id() if not provided
        $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : get_queried_object_id();
        
        // Get current page URL from the request
        $current_page_url = isset($_POST['current_url']) ? esc_url_raw($_POST['current_url']) : '';
        
        // If not provided, try to construct it (fallback)
        if (empty($current_page_url)) {
            $protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? "https" : "http";
            $current_page_url = $protocol . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
        }
        
        global $wpdb;
        
        // Check if RANK_TABLE_NAME is defined
        if (!defined('RANK_TABLE_NAME')) {
            wp_send_json(array(
                'success' => false,
                'message' => 'Configuration error: Table name not defined'
            ));
            return;
        }
        
        $table_name = $wpdb->prefix . RANK_TABLE_NAME;
        
        // No debug info initialization needed
        
        // Process all URLs in a single database query
        if (!empty($urls)) {
            // Create placeholders for the IN clause
            $placeholders = implode(',', array_fill(0, count($urls), '%s'));
            
            // URL-First Lookup Strategy: Step 1 - Try with original_page_url matching current page
            if (!empty($current_page_url)) {
                // Prepare values for the query (all URLs + current page URL)
                $query_values = $urls;
                $query_values[] = $current_page_url;
                
                // Query by original_page_url (highest priority)
                $sql = $wpdb->prepare(
                    "SELECT image_url, override_value
                     FROM $table_name
                     WHERE image_url IN ($placeholders)
                     AND override_type = 'image_alt'
                     AND original_page_url = %s
                     ORDER BY override_id DESC",
                    ...$query_values
                );
                
                $results = $wpdb->get_results($sql);
                
                // Process URL-specific results (highest priority)
                foreach ($results as $row) {
                    $alt_tags[$row->image_url] = $row->override_value;
                }
            }
            
            // URL-First Lookup Strategy: Step 2 - For remaining URLs, fall back to post_id
            $remaining_urls = array_diff($urls, array_keys($alt_tags));
            
            if (!empty($remaining_urls) && $post_id > 0) {
                // Create new placeholders for the remaining URLs
                $placeholders_remaining = implode(',', array_fill(0, count($remaining_urls), '%s'));
                
                // Prepare values for the query (remaining URLs + post_id)
                $query_values = $remaining_urls;
                $query_values[] = $post_id;
                
                // Look for post-specific alt tags (fallback priority)
                $sql = $wpdb->prepare(
                    "SELECT image_url, override_value
                     FROM $table_name
                     WHERE image_url IN ($placeholders_remaining)
                     AND override_type = 'image_alt'
                     AND post_id = %d
                     ORDER BY override_id DESC",
                    ...$query_values
                );
                
                $results = $wpdb->get_results($sql);
                
                // Process post-specific results (fallback priority)
                foreach ($results as $row) {
                    $alt_tags[$row->image_url] = $row->override_value;
                }
            }
        }
        
        wp_send_json(array(
            'success' => true,
            'alt_tags' => $alt_tags
        ));
    }
    
}