<?php
if ( ! defined( 'ABSPATH' ) ) exit;

if ( ! class_exists( 'ALLSI_Image_Source' ) ) {
    require_once plugin_dir_path( __FILE__ ) . 'sources/class-allsi-image-source.php';
}

if ( ! class_exists( 'ALLSI_Source_Youtube' ) ) {
    $allsi_youtube_source_path = plugin_dir_path( __FILE__ ) . 'sources/class-allsi-source-youtube.php';
    if ( file_exists( $allsi_youtube_source_path ) ) {
        require_once $allsi_youtube_source_path;
    }
}

if ( ! class_exists( 'ALLSI_Source_Google_Image' ) ) {
    $allsi_google_image_source_path = plugin_dir_path( __FILE__ ) . 'sources/class-allsi-source-google-image.php';
    if ( file_exists( $allsi_google_image_source_path ) ) {
        require_once $allsi_google_image_source_path;
    }
}

if ( ! class_exists( 'ALLSI_Source_Dallev1' ) ) {
    $allsi_dalle_source_path = plugin_dir_path( __FILE__ ) . 'sources/class-allsi-source-dallev1.php';
    if ( file_exists( $allsi_dalle_source_path ) ) {
        require_once $allsi_dalle_source_path;
    }
}

if ( ! class_exists( 'ALLSI_Source_Stability' ) ) {
    $allsi_stability_source_path = plugin_dir_path( __FILE__ ) . 'sources/class-allsi-source-stability.php';
    if ( file_exists( $allsi_stability_source_path ) ) {
        require_once $allsi_stability_source_path;
    }
}

if ( ! class_exists( 'ALLSI_Source_Replicate' ) ) {
    $allsi_replicate_source_path = plugin_dir_path( __FILE__ ) . 'sources/class-allsi-source-replicate.php';
    if ( file_exists( $allsi_replicate_source_path ) ) {
        require_once $allsi_replicate_source_path;
    }
}

/**
 * The admin-specific functionality of the plugin.
 *
 * @link       https://github.com/yourusername/all-sources-images
 * @since      1.0.0
 *
 * @package    All_Sources_Images
 * @subpackage All_Sources_Images/admin
 */
/**
 * The admin-specific functionality of the plugin.
 *
 * Defines the plugin name, version, and two examples hooks for how to
 * enqueue the admin-specific stylesheet and JavaScript.
 *
 * @package    All_Sources_Images
 * @subpackage All_Sources_Images/admin
 * @author     Your Name <your@email.com>
 */
class All_Sources_Images_Admin {
    /**
     * The ID of this plugin.
     *
     * @since    4.0.0
     * @access   private
     * @var      string    $plugin_name    The ID of this plugin.
     */
    private $plugin_name;

    /**
     * The version of this plugin.
     *
     * @since    4.0.0
     * @access   private
     * @var      string    $version    The current version of this plugin.
     */
    private $version;

    /**
     * Hook suffix for the Media submenu page.
     *
     * @var string
     */
    private $media_picker_hook = '';

    /**
     * Hook suffix for the top-level media browser page.
     *
     * @var string
     */
    private $media_picker_top_level_hook = '';

    /**
     * Hook suffix for the Search submenu under the plugin menu.
     *
     * @var string
     */
    private $media_picker_search_submenu_hook = '';

    /**
     * Default post ID for media picker.
     *
     * @since    6.1.8
     * @var      int
     */
    private $media_picker_default_post_id = 0;

    /**
     * Initialize the class and set its properties.
     *
     * @since    4.0.0
     * @param      string    $plugin_name       The name of this plugin.
     * @param      string    $version    The version of this plugin.
     */
    public function __construct( $plugin_name, $version ) {
        $this->plugin_name = $plugin_name;
        $this->version = $version;
        $ALLSI_generation = $this->ALLSI_generation_features();

        // Cron settings for scheduled generation
        $cron_options = wp_parse_args( get_option( 'ALLSI_plugin_cron_settings' ) );
        
        // Register cron hook for automated image generation
        add_action( 'ALLSI_cron_image_generation', array(&$this, 'ALLSI_execute_cron_generation') );
        
        // Register hook for scheduled image generation (used by plugin integrations)
        add_action( 'ALLSI_generate_scheduled_image', array(&$this, 'ALLSI_generate_scheduled_image') );

        // Robust publish hooks for automatic image generation on publish.
        add_action( 'transition_post_status', array( $this, 'ALLSI_auto_image_transition_post_status' ), 20, 3 );
        add_action( 'wp_after_insert_post', array( $this, 'ALLSI_auto_image_after_insert_post' ), 20, 4 );
        
        // Testing APIs function with Ajax call
        add_action( 'wp_ajax_allsi_test_apis', array(&$this, 'ALLSI_test_apis') );
        /* Gutenberg Block */
        // Scripts for Block & Translations
        add_action( 'init', array(&$this, 'ALLSI_register_mpt_block') );
        add_action( 'enqueue_block_editor_assets', array(&$this, 'ALLSI_enqueue_style_block') );
        // Register media picker page only once (avoid duplicates from multiple class instances)
        static $media_picker_registered = false;
        if ( ! $media_picker_registered ) {
            add_action( 'admin_menu', array( $this, 'ALLSI_register_media_picker_page' ) );
            $media_picker_registered = true;
        }
        add_action( 'admin_enqueue_scripts', array( $this, 'ALLSI_enqueue_media_picker_assets' ) );
        add_action( 'admin_enqueue_scripts', array( $this, 'ALLSI_enqueue_media_modal_assets' ) );
        add_action( 'enqueue_block_editor_assets', array( $this, 'ALLSI_enqueue_media_modal_assets' ) );
        add_action( 'wp_enqueue_scripts', array( $this, 'ALLSI_enqueue_front_media_modal_assets' ) );
        // Gutenberg Block : Translate search term (called once before searching multiple banks)
        add_action( 'wp_ajax_allsi_translate_search', array(&$this, 'ALLSI_translate_search_ajax') );
        // Gutenberg Block : Searching images with APIs
        add_action( 'wp_ajax_allsi_block_searching_images', array(&$this, 'ALLSI_block_searching_images') );
        // Gutenberg Block : Download images from APIs
        add_action( 'wp_ajax_allsi_block_downloading_image', array(&$this, 'ALLSI_block_downloading_image') );
        // Log viewer actions (Others > Logging)
        add_action( 'wp_ajax_allsi_get_log_contents', array( $this, 'ALLSI_get_log_contents_ajax' ) );
        add_action( 'wp_ajax_allsi_clear_log', array( $this, 'ALLSI_clear_log_ajax' ) );
        // Extend timeout request for wp_remote_request() with dalle
        $options_banks = wp_parse_args( get_option( 'ALLSI_plugin_banks_settings' ), $this->ALLSI_default_options_banks_settings( TRUE ) );
        if ( isset( $options_banks['api_chosen_auto'] ) && true === in_array( 'dallev1', $options_banks['api_chosen_auto'] ) ) {
            add_filter( 'http_request_timeout', array(&$this, 'ALLSI_custom_http_request_timeout') );
        }
        // Migration v5 to v6
        add_action( 'init', array(&$this, 'ALLSI_migration') );
        add_action( 'plugins_loaded', array( $this, 'ALLSI_maybe_boot_elementor' ), 20 );

        if ( did_action( 'elementor/loaded' ) ) {
            $this->ALLSI_maybe_boot_elementor();
        } else {
            add_action( 'elementor/init', array( $this, 'ALLSI_maybe_boot_elementor' ) );
        }
    }

    /**
     * Trigger the automatic image generation process upon saving a post
     *
     * @since    5.0.0
     * @access   public
     */
    public function ALLSI_trigger_save_post() {
        $log = $this->ALLSI_monolog_call();
        $log->info( 'Launch Automatic plugin' );
        //$automatic_generation = new All_Sources_Images_Generation($this->plugin_name, $this->version);
        // Add a callback function to the save_post action to handle image generation
        add_action( 'save_post', function ( $post_id ) {
            // Skip if this is an autosave
            if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
                return;
            }
            
            // Skip if this is a revision
            if ( wp_is_post_revision( $post_id ) ) {
                return;
            }
            
            // Check if WPeMatico campaign_fetch class is active (it's instantiated during feed processing)
            if ( class_exists( 'wpematico_campaign_fetch' ) ) {
                return;
            }
            // Check if we're in a WPeMatico cron/ajax context
            if ( defined( 'WPEMATICO_VERSION' ) && ( doing_action( 'wpematico_cron' ) || did_action( 'wpematico_init_fetching' ) || did_action( 'Wpematico_init_fetching' ) ) ) {
                return;
            }
            
            // Skip if triggered by FeedWordPress  
            if ( class_exists( 'FeedWordPress' ) && doing_action( 'syndicated_item' ) ) {
                return;
            }
            
            // Check if we already scheduled generation for this post (prevents duplicates)
            $transient_key = 'ALLSI_scheduled_' . $post_id;
            if ( get_transient( $transient_key ) ) {
                return;
            }
            set_transient( $transient_key, true, 120 );
            
            // Schedule a single event with a delay to avoid conflicts
            wp_schedule_single_event( 
                time() + 5,
                // 5 seconds apart for each block
                'ALLSI_generate_scheduled_image',
                array($post_id)
             );
        } );
    }

    /**
     * Generate images for all blocks when called by the scheduled event
     * Uses post meta flags per image block to prevent duplicates
     *
     * @since    6.0.0
     * @access   public
     */
    public function ALLSI_generate_scheduled_image( $post_id ) {
        $post_id = absint( $post_id );
        $log = $this->ALLSI_monolog_call();
        $log->info( '=== SCHEDULED GENERATION START for post ID: ' . $post_id . ' ===' );

        if ( 0 === $post_id ) {
            $log->info( 'Invalid post ID, exiting scheduled generation' );
            return;
        }

        // Always clear queue lock for this post when the scheduled task starts.
        delete_transient( 'ALLSI_auto_image_queued_' . $post_id );

        if ( ! $this->ALLSI_auto_image_should_process_post( $post_id ) ) {
            $log->info( 'Post does not match Auto Image filters, exiting' );
            return;
        }
        
        // Retrieve main settings from the plugin options
        $main_settings = get_option( 'ALLSI_plugin_main_settings' );
        $img_blocks = isset( $main_settings['image_block'] ) ? $main_settings['image_block'] : array();
        
        if ( empty( $img_blocks ) ) {
            $log->info( 'No image blocks configured, exiting' );
            return;
        }
        
        // Check rewrite_featured setting
        $rewrite_featured = isset( $main_settings['rewrite_featured'] ) && $main_settings['rewrite_featured'] === 'true';
        $log->info( 'Rewrite featured setting: ' . ( $rewrite_featured ? 'enabled' : 'disabled' ) );
        
        $automatic_generation = new All_Sources_Images_Generation($this->plugin_name, $this->version);
        
        // Iterate through each image block
        foreach ( $img_blocks as $key_img_block => $img_block ) {
            $meta_key = '_ALLSI_block_' . $key_img_block . '_status';
            $current_status = get_post_meta( $post_id, $meta_key, true );
            
            $log->info( 'Block ' . $key_img_block . ' status: ' . ( $current_status ?: 'empty' ) );
            
            // Skip if already completed or in progress
            if ( $current_status === 'completed' ) {
                $log->info( 'Block ' . $key_img_block . ' already completed, skipping' );
                continue;
            }
            
            if ( $current_status === 'processing' ) {
                // Check if processing started more than 2 minutes ago (stuck process)
                $started_at = get_post_meta( $post_id, '_ALLSI_block_' . $key_img_block . '_started', true );
                if ( $started_at && ( time() - intval( $started_at ) ) < 120 ) {
                    $log->info( 'Block ' . $key_img_block . ' is being processed by another instance, skipping' );
                    continue;
                }
                $log->info( 'Block ' . $key_img_block . ' was stuck, retrying' );
            }
            
            // Mark as processing with timestamp
            update_post_meta( $post_id, $meta_key, 'processing' );
            update_post_meta( $post_id, '_ALLSI_block_' . $key_img_block . '_started', time() );
            
            $log->info( 'Starting generation for block ' . $key_img_block );
            
            try {
                // Generate the image for this specific block
                $automatic_generation->ALLSI_create_thumb(
                    $post_id,
                    0,                      // $check_value_enable
                    1,                      // $check_post_type
                    1,                      // $check_category
                    $rewrite_featured,      // $rewrite_featured - pass the setting!
                    false,
                    null,
                    null,
                    $key_img_block,
                    true
                );
                
                // Mark as completed
                update_post_meta( $post_id, $meta_key, 'completed' );
                delete_post_meta( $post_id, '_ALLSI_block_' . $key_img_block . '_started' );
                $log->info( 'Block ' . $key_img_block . ' completed successfully' );
                
            } catch ( Exception $e ) {
                // Mark as failed so it can be retried
                update_post_meta( $post_id, $meta_key, 'failed' );
                $log->error( 'Block ' . $key_img_block . ' failed: ' . $e->getMessage() );
            }
        }
        
        $log->info( '=== SCHEDULED GENERATION END for post ID: ' . $post_id . ' ===' );
    }

    /**
     * Schedule auto image generation when a post transitions to publish.
     *
     * @param string  $new_status New post status.
     * @param string  $old_status Old post status.
     * @param WP_Post $post       Post object.
     */
    public function ALLSI_auto_image_transition_post_status( $new_status, $old_status, $post ) {
        if ( 'publish' !== $new_status || 'publish' === $old_status ) {
            return;
        }

        if ( ! ( $post instanceof WP_Post ) ) {
            return;
        }

        $this->ALLSI_auto_image_schedule_post( $post->ID, 'transition_post_status' );
    }

    /**
     * Fallback publish trigger for importers and integrations that bypass the classic editor flow.
     *
     * @param int          $post_id     Post ID.
     * @param WP_Post      $post        Post object.
     * @param bool         $update      Whether this is an existing post being updated.
     * @param WP_Post|null $post_before Previous post object.
     */
    public function ALLSI_auto_image_after_insert_post( $post_id, $post, $update, $post_before ) {
        if ( ! ( $post instanceof WP_Post ) ) {
            return;
        }

        if ( 'publish' !== $post->post_status ) {
            return;
        }

        // Skip normal content updates that were already published before.
        if ( true === (bool) $update && ( $post_before instanceof WP_Post ) && 'publish' === $post_before->post_status ) {
            return;
        }

        $this->ALLSI_auto_image_schedule_post( $post_id, 'wp_after_insert_post' );
    }

    /**
     * Queue a single delayed event for image generation.
     *
     * @param int    $post_id Post ID.
     * @param string $reason  Hook name used for logging.
     */
    private function ALLSI_auto_image_schedule_post( $post_id, $reason = '' ) {
        $post_id = absint( $post_id );
        if ( 0 === $post_id ) {
            return;
        }

        if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
            return;
        }

        if ( ! $this->ALLSI_auto_image_should_process_post( $post_id ) ) {
            return;
        }

        $transient_key = 'ALLSI_auto_image_queued_' . $post_id;
        if ( get_transient( $transient_key ) ) {
            return;
        }

        if ( wp_next_scheduled( 'ALLSI_generate_scheduled_image', array( $post_id ) ) ) {
            return;
        }

        set_transient( $transient_key, true, 120 );
        wp_schedule_single_event( time() + 5, 'ALLSI_generate_scheduled_image', array( $post_id ) );

        $log = $this->ALLSI_monolog_call();
        $log->info( 'Auto image scheduled', array(
            'post_id' => $post_id,
            'reason'  => sanitize_text_field( (string) $reason ),
        ) );
    }

    /**
     * Validate if a post matches Auto Image publish filters.
     *
     * @param int|WP_Post $post Post object or post ID.
     * @return bool
     */
    private function ALLSI_auto_image_should_process_post( $post ) {
        if ( ! ( $post instanceof WP_Post ) ) {
            $post = get_post( $post );
        }

        if ( ! ( $post instanceof WP_Post ) ) {
            return false;
        }

        if ( wp_is_post_revision( $post->ID ) || wp_is_post_autosave( $post->ID ) ) {
            return false;
        }

        if ( 'publish' !== $post->post_status ) {
            return false;
        }

        $options = wp_parse_args(
            get_option( 'ALLSI_plugin_auto_image_settings' ),
            $this->ALLSI_default_options_auto_image_settings( true )
        );

        if ( empty( $options['enabled'] ) || 'enable' !== $options['enabled'] ) {
            return false;
        }

        $allowed_post_types = array();
        if ( ! empty( $options['post_types'] ) && is_array( $options['post_types'] ) ) {
            $allowed_post_types = array_values( array_unique( array_filter( array_map( 'sanitize_key', $options['post_types'] ) ) ) );
        }

        if ( ! empty( $allowed_post_types ) && ! in_array( $post->post_type, $allowed_post_types, true ) ) {
            return false;
        }

        $excluded_ids = $this->ALLSI_auto_image_parse_ids(
            isset( $options['exclude_post_ids'] ) ? $options['exclude_post_ids'] : ''
        );
        if ( in_array( $post->ID, $excluded_ids, true ) ) {
            return false;
        }

        $selected_term_ids = array();
        if ( ! empty( $options['term_ids'] ) && is_array( $options['term_ids'] ) ) {
            $selected_term_ids = array_values( array_unique( array_filter( array_map( 'absint', $options['term_ids'] ) ) ) );
        }

        if ( ! empty( $selected_term_ids ) ) {
            $taxonomies = get_object_taxonomies( $post->post_type, 'names' );
            if ( empty( $taxonomies ) ) {
                return false;
            }

            $post_term_ids = wp_get_post_terms( $post->ID, $taxonomies, array( 'fields' => 'ids' ) );
            if ( is_wp_error( $post_term_ids ) || empty( $post_term_ids ) ) {
                return false;
            }

            $post_term_ids = array_map( 'absint', $post_term_ids );
            if ( empty( array_intersect( $selected_term_ids, $post_term_ids ) ) ) {
                return false;
            }
        }

        return true;
    }

    /**
     * Parse a comma-separated list of IDs and return unique integers.
     *
     * @param string $ids_raw Raw CSV string.
     * @return array
     */
    private function ALLSI_auto_image_parse_ids( $ids_raw ) {
        if ( ! is_string( $ids_raw ) || '' === trim( $ids_raw ) ) {
            return array();
        }

        preg_match_all( '/\d+/', $ids_raw, $matches );
        if ( empty( $matches[0] ) ) {
            return array();
        }

        return array_values( array_unique( array_filter( array_map( 'absint', $matches[0] ) ) ) );
    }

    /**
     * Clear ASI block status meta for a post (useful for re-generation)
     *
     * @param int $post_id The post ID.
     */
    public function clear_block_status( $post_id ) {
        $post_id = absint( $post_id );
        if ( 0 === $post_id ) {
            return;
        }

        $all_meta = get_post_meta( $post_id );
        if ( empty( $all_meta ) || ! is_array( $all_meta ) ) {
            return;
        }

        foreach ( array_keys( $all_meta ) as $meta_key ) {
            if ( 0 === strpos( (string) $meta_key, '_ALLSI_block_' ) ) {
                delete_post_meta( $post_id, $meta_key );
            }
        }
    }

    /**
     * Execute cron-based automated image generation
     * Called by WordPress cron hook 'ALLSI_cron_image_generation'
     *
     * @since    6.1.7
     * @access   public
     */
    public function ALLSI_execute_cron_generation() {
        $log = $this->ALLSI_monolog_call();
        $log->info( 'Starting cron image generation' );
        
        // Get cron settings
        $cron_options = get_option( 'ALLSI_plugin_cron_settings' );
        
        // Verify cron is enabled
        if ( !isset( $cron_options['enable_cron'] ) || $cron_options['enable_cron'] !== 'enable' ) {
            $log->info( 'Cron is disabled, exiting' );
            return;
        }
        
        // Get post types to process
        $post_types = isset( $cron_options['cron_post_types'] ) ? $cron_options['cron_post_types'] : array( 'post' );
        
        // Get posts per run
        $posts_per_run = isset( $cron_options['posts_per_run'] ) ? absint( $cron_options['posts_per_run'] ) : 5;
        
        // Build date query
        $date_query = array();
        if ( isset( $cron_options['posts_date_mode'] ) && $cron_options['posts_date_mode'] === 'relative' ) {
            $date_value = isset( $cron_options['posts_date_value'] ) ? absint( $cron_options['posts_date_value'] ) : 5;
            $date_unit = isset( $cron_options['posts_date_unit'] ) ? $cron_options['posts_date_unit'] : 'days';
            
            $date_query = array(
                array(
                    'after' => $date_value . ' ' . $date_unit . ' ago',
                    'inclusive' => true,
                ),
            );
        }
        
        // Query posts without featured images
        $args = array(
            'post_type' => $post_types,
            'posts_per_page' => $posts_per_run,
            'post_status' => 'publish',
            'meta_query' => array(
                array(
                    'key' => '_thumbnail_id',
                    'compare' => 'NOT EXISTS',
                ),
            ),
            'orderby' => 'date',
            'order' => 'DESC',
        );
        
        if ( !empty( $date_query ) ) {
            $args['date_query'] = $date_query;
        }
        
        $query = new WP_Query( $args );
        
        $log->info( 'Found ' . $query->found_posts . ' posts without featured images' );
        
        if ( $query->have_posts() ) {
            $automatic_generation = new All_Sources_Images_Generation( $this->plugin_name, $this->version );
            $main_settings = get_option( 'ALLSI_plugin_main_settings' );
            $img_blocks = isset( $main_settings['image_block'] ) ? $main_settings['image_block'] : array();
            
            while ( $query->have_posts() ) {
                $query->the_post();
                $post_id = get_the_ID();
                
                $log->info( 'Processing post ID: ' . $post_id );
                
                // Process each image block for this post
                foreach ( $img_blocks as $key_img_block => $img_block ) {
                    $automatic_generation->ALLSI_create_thumb(
                        $post_id,
                        0,
                        1,
                        1,
                        0,
                        false,
                        null,
                        null,
                        $key_img_block,
                        true
                    );
                }
            }
            
            wp_reset_postdata();
        }
        
        $log->info( 'Cron image generation completed' );
    }

    /**
     * Trigger when hook wp_insert_post is fired
     *
     * @since    5.0.3
     * @access   public
     */
    public function ALLSI_trigger_wp_insert_post( $post_ID ) {
    }

    public function ALLSI_trigger_wp_automatic( $post_data ) {
        $log = $this->ALLSI_monolog_call();
        $log->info( 'Launch WordPress Automatic Plugin' );
        $automatic_generation = new All_Sources_Images_Generation($this->plugin_name, $this->version);
        $main_settings = get_option( 'ALLSI_plugin_main_settings' );
        $img_blocks = $main_settings['image_block'];
        foreach ( $img_blocks as $key_img_block => $img_block ) {
            $automatic_generation->ALLSI_create_thumb(
                $post_data['post_id'],
                '0',
                '1',
                '1',
                '0',
                false,
                null,
                null,
                $key_img_block,
                false
            );
        }
    }

    /**
     * Register all of the hooks related to the admin area functionality
     * of the plugin.
     *
     * @since    4.0.0
     * @access   private
     */
    private function ALLSI_generation_features() {
        $ALLSI_generation = new All_Sources_Images_Generation($this->plugin_name, $this->version);
        return $ALLSI_generation;
    }

    /**
     * Get proxy configuration for HTTP requests
     * Returns proxy args to merge with wp_remote_get/wp_remote_post args
     *
     * @since    6.1.7
     * @return   array    Proxy configuration array
     */
    
    // Cloudflare Worker constants (hardcoded for automatic fallback)
    const ALLSI_CLOUDFLARE_WORKER_URL = 'https://noisy-heart-6060.estebandezafra.workers.dev';
    const ALLSI_CLOUDFLARE_WORKER_TOKEN = 'TTSHFHRwllzM1RDctruMT0daz4sYmTKAj1V3y8VFlL6mVwdt4oYWWolwYrvLz1PS';
    
    // Sources that support automatic Cloudflare fallback when no API key is configured
    const ALLSI_CLOUDFLARE_FALLBACK_SOURCES = array( 'pixabay', 'pexels', 'unsplash', 'flickr', 'giphy' );
    
    public function ALLSI_get_proxy_args() {
        $proxy_options = $this->ALLSI_get_proxy_settings();
        
        // Only return proxy args if legacy proxy is enabled and configured
        if ( empty( $proxy_options['enable_proxy'] ) || 'enable' !== $proxy_options['enable_proxy'] ) {
            return array();
        }

        if ( empty( $proxy_options['proxy_address'] ) ) {
            return array();
        }

        $proxy_host = preg_replace( '#^https?://#i', '', trim( $proxy_options['proxy_address'] ) );
        $proxy_port = ! empty( $proxy_options['proxy_port'] ) ? $proxy_options['proxy_port'] : '80';
        $proxy_url  = $proxy_host . ':' . $proxy_port;

        if ( ! empty( $proxy_options['proxy_username'] ) && ! empty( $proxy_options['proxy_password'] ) ) {
            $proxy_url = $proxy_options['proxy_username'] . ':' . $proxy_options['proxy_password'] . '@' . $proxy_url;
        }

        return array(
            'proxy' => 'https://' . $proxy_url,
        );
    }

    private function ALLSI_get_proxy_settings() {
        static $cache = null;
        if ( null === $cache ) {
            $cache = wp_parse_args( get_option( 'ALLSI_plugin_proxy_settings' ), $this->ALLSI_default_options_proxy_settings( false ) );
        }
        return $cache;
    }

    /**
     * Check if a source supports Cloudflare fallback
     */
    public function ALLSI_source_supports_cloudflare_fallback( $service ) {
        return in_array( $service, self::ALLSI_CLOUDFLARE_FALLBACK_SOURCES, true );
    }

    /**
     * Check if legacy proxy is enabled by user
     */
    public function ALLSI_is_legacy_proxy_enabled() {
        $options = $this->ALLSI_get_proxy_settings();
        return ! empty( $options['enable_proxy'] ) && 'enable' === $options['enable_proxy'] && ! empty( $options['proxy_address'] );
    }

    public function ALLSI_current_proxy_mode() {
        // For backward compatibility - now only returns 'legacy' or 'disabled'
        $options = $this->ALLSI_get_proxy_settings();
        if ( ! empty( $options['enable_proxy'] ) && 'enable' === $options['enable_proxy'] ) {
            return 'legacy';
        }
        return 'disabled';
    }

    /**
     * Get Cloudflare Worker bootstrap configuration (uses hardcoded constants)
     */
    private function ALLSI_get_cloudflare_bootstrap() {
        return array(
            'worker_url' => self::ALLSI_CLOUDFLARE_WORKER_URL,
            'token'      => self::ALLSI_CLOUDFLARE_WORKER_TOKEN,
        );
    }

    private function ALLSI_get_cloudflare_service_map() {
        return array(
            'pixabay'        => array( 'key_location' => 'query',  'key_name' => 'key' ),
            'pexels'         => array( 'key_location' => 'header', 'key_name' => 'Authorization' ),
            'unsplash'       => array( 'key_location' => 'header', 'key_name' => 'Authorization' ),
            'giphy'          => array( 'key_location' => 'query',  'key_name' => 'api_key' ),
            'flickr'         => array( 'key_location' => 'query',  'key_name' => 'api_key' ),
            'openverse'      => array( 'key_location' => 'none' ),
            'cc_search'      => array( 'key_location' => 'none' ),
            'youtube'        => array( 'key_location' => 'query',  'key_name' => 'key' ),
            'google_image'   => array( 'key_location' => 'query',  'key_name' => 'key' ),
            'pixabay_video'  => array( 'key_location' => 'query',  'key_name' => 'key' ),
            'dallev1'        => array( 'key_location' => 'header', 'key_name' => 'Authorization' ),
            'stability'      => array( 'key_location' => 'header', 'key_name' => 'Authorization' ),
            'replicate'      => array( 'key_location' => 'header', 'key_name' => 'Authorization' ),
            'gemini'         => array( 'key_location' => 'query',  'key_name' => 'key' ),
            'workers_ai'     => array( 'key_location' => 'header', 'key_name' => 'Authorization' ),
            'google_translate'=> array( 'key_location' => 'query', 'key_name' => 'key' ),
            'cloudflare_ai'  => array( 'key_location' => 'header', 'key_name' => 'Authorization' ),
        );
    }

    private function ALLSI_strip_credentials_from_request( $service, &$url, array &$args ) {
        $map = $this->ALLSI_get_cloudflare_service_map();
        if ( empty( $map[ $service ] ) ) {
            return;
        }

        $config = $map[ $service ];
        if ( 'header' === $config['key_location'] && ! empty( $config['key_name'] ) ) {
            if ( isset( $args['headers'][ $config['key_name'] ] ) ) {
                unset( $args['headers'][ $config['key_name'] ] );
            }
        }

        if ( 'query' === $config['key_location'] && ! empty( $config['key_name'] ) ) {
            $url = remove_query_arg( $config['key_name'], $url );
        }
    }

    private function ALLSI_merge_http_args( array $base, array $extra ) {
        if ( empty( $extra ) ) {
            return $base;
        }

        if ( isset( $extra['headers'] ) ) {
            if ( isset( $base['headers'] ) && is_array( $base['headers'] ) ) {
                $base['headers'] = array_merge( $base['headers'], $extra['headers'] );
            } else {
                $base['headers'] = $extra['headers'];
            }
            unset( $extra['headers'] );
        }

        return array_merge( $base, $extra );
    }

    private function ALLSI_prepare_http_args( array $args ) {
        if ( empty( $args['method'] ) ) {
            $args['method'] = 'GET';
        }
        $args['method'] = strtoupper( $args['method'] );
        return $args;
    }

    private function ALLSI_remote_request_via_cloudflare( $service, $url, array $args ) {
        $bootstrap = $this->ALLSI_get_cloudflare_bootstrap();

        $worker_url      = untrailingslashit( $bootstrap['worker_url'] );
        // Use URL-safe base64 encoding: replace + with -, / with _, remove =
        $encoded_url_b64 = rtrim( strtr( base64_encode( $url ), '+/', '-_' ), '=' );
        $final_url       = add_query_arg( array(
            'servicio' => $service,
            'token'    => $bootstrap['token'],
            'url_b64'  => $encoded_url_b64,
        ), $worker_url );

        unset( $args['proxy'] );

        return wp_remote_request( $final_url, $args );
    }

    /**
     * Main remote request function
     * 
     * Logic:
     * 1. If source supports Cloudflare fallback AND use_cloudflare_fallback is true -> use Cloudflare
     * 2. If legacy proxy is enabled -> use legacy proxy
     * 3. Otherwise -> direct request
     */
    public function ALLSI_remote_request( $service, $url, array $args = array(), $use_cloudflare_fallback = false ) {
        $args = $this->ALLSI_prepare_http_args( $args );

        // Check if we should use Cloudflare fallback (source has no API key configured)
        if ( $use_cloudflare_fallback && $this->ALLSI_source_supports_cloudflare_fallback( $service ) ) {
            return $this->ALLSI_remote_request_via_cloudflare( $service, $url, $args );
        }

        // Apply legacy proxy if enabled
        $proxy_args = $this->ALLSI_get_proxy_args();
        $args = $this->ALLSI_merge_http_args( $args, $proxy_args );

        return wp_remote_request( $url, $args );
    }

    public function ALLSI_remote_get( $service, $url, array $args = array(), $use_cloudflare_fallback = false ) {
        $args['method'] = 'GET';
        return $this->ALLSI_remote_request( $service, $url, $args, $use_cloudflare_fallback );
    }

    public function ALLSI_remote_post( $service, $url, array $args = array(), $use_cloudflare_fallback = false ) {
        $args['method'] = 'POST';
        return $this->ALLSI_remote_request( $service, $url, $args, $use_cloudflare_fallback );
    }

    /**
     * Register the stylesheets for the admin area.
     *
     * @since    4.0.0
     */
    public function enqueue_styles( $hook ) {
        // Post Editor
        if ( $hook == 'post.php' || $hook == 'post-new.php' ) {
            wp_enqueue_style(
                'mpt-post',
                plugin_dir_url( __FILE__ ) . 'css/all-sources-images-post.css',
                array(),
                $this->version,
                'all'
            );
        }
        // ASI Admin Pages (new UI)
        $ALLSI_pages = array(
            'toplevel_page_allsi-new-settings',
            'all-sources-images_page_allsi-new-settings',
            'all-sources-images_page_allsi-new-automatic',
            'all-sources-images_page_allsi-new-bulk-generation'
        );
        if ( in_array( $hook, $ALLSI_pages ) ) {
            wp_enqueue_style(
                $this->plugin_name,
                plugin_dir_url( __FILE__ ) . 'css/all-sources-images-admin.css',
                array(),
                $this->version,
                'all'
            );
        }
    }

    /**
     * Register the JavaScript for the admin area.
     *
     * @since    4.0.0
     */
    public function enqueue_scripts( $hook ) {
        global $pagenow;
        $post_types_default = $this->ALLSI_default_posts_types();
        $block = wp_parse_args( get_option( 'ALLSI_plugin_block_settings' ), $this->ALLSI_default_options_block_settings( TRUE ) );
        $options_banks = wp_parse_args( get_option( 'ALLSI_plugin_banks_settings' ), $this->ALLSI_default_options_banks_settings( TRUE ) );
        $options_auto = wp_parse_args( get_option( 'ALLSI_plugin_main_settings' ), $this->ALLSI_default_options_main_settings( TRUE ) );
        // Scripts for new UI pages
        $ALLSI_new_pages = array(
            'toplevel_page_allsi-new-settings',
            'all-sources-images_page_allsi-new-settings',
            'all-sources-images_page_allsi-new-automatic',
            'all-sources-images_page_allsi-new-bulk-generation'
        );
        if ( in_array( $hook, $ALLSI_new_pages ) ) {
            // Use WordPress Core jQuery UI
            wp_enqueue_script( 'jquery-ui-core' );
            wp_enqueue_script( 'jquery-ui-tabs' );
            wp_enqueue_script( 'jquery-ui-sortable' );
            wp_enqueue_script( 'jquery-ui-draggable' );
            wp_enqueue_script( 'jquery-ui-droppable' );
        }
        wp_enqueue_script(
            'mpt-rating',
            plugin_dir_url( __FILE__ ) . 'js/rating-admin.js',
            array('jquery'),
            $this->version,
            false
        );
        // Old bulk generation scripts removed - now using bulk-generation.js via new-ui-assets.php
        // Source/Settings scripts (new page)
        if ( in_array( $hook, array( 'toplevel_page_allsi-new-settings', 'all-sources-images_page_allsi-new-settings' ), true ) ) {
            wp_enqueue_script( 'allsi-source', plugins_url( 'js/source.js', __FILE__ ), array('jquery', 'jquery-ui-core') );
            wp_localize_script( 'allsi-source', 'allsiApisTestingAjax', array(
                'ajaxurl'            => admin_url( 'admin-ajax.php' ),
                'nonce'              => wp_create_nonce( 'api_testing_nonce' ),
                'successful_testing' => esc_html__( 'Your API key works!', 'all-sources-images' ),
                'error_key'          => esc_html__( "Your API key doesn't work.", 'all-sources-images' ),
                'error_testing'      => esc_html__( 'An error has occurred with the remote API.', 'all-sources-images' ),
            ) );
        }
        // JavaScript Variables for nonce, admin-jax.php path and translations
        $js_vars = array(
            'wp_ajax_url'  => admin_url( 'admin-ajax.php' ),
            'translations' => array(
                'successful'       => esc_html__( 'Successful generation !!', 'all-sources-images' ),
                'error_generation' => esc_html__( 'Error with images generation', 'all-sources-images' ),
                'error_plugin'     => esc_html__( 'Error with the plugin', 'all-sources-images' ),
                'search_terms'     => esc_html__( 'Search Terms', 'all-sources-images' ),
                'img_resolution'   => esc_html__( 'Image Resolution', 'all-sources-images' ),
                'img_size'         => esc_html__( 'Image Size', 'all-sources-images' ),
                'img_bank'         => esc_html__( 'Image Bank', 'all-sources-images' ),
            ),
        );

        $bulk_generation_flag = filter_input( INPUT_POST, 'all-sources-images', FILTER_UNSAFE_RAW );
        $ids_mpt_generation_raw = filter_input( INPUT_GET, 'ids_mpt_generation', FILTER_UNSAFE_RAW );
        $cats_raw = filter_input( INPUT_GET, 'cats', FILTER_UNSAFE_RAW );

        if ( ! empty( $bulk_generation_flag ) || ! empty( $ids_mpt_generation_raw ) || ! empty( $cats_raw ) ) {
            $ids_from_cats = '';

            if ( ! empty( $cats_raw ) ) {
                $taxo_term = get_term( absint( $cats_raw ) );
                if ( empty( $taxo_term ) ) {
                    return false;
                }
                $cpts = get_post_types( array(
                    'public' => true,
                ), 'names' );
                $post_ids = get_posts( array(
                    'numberposts' => -1,
                    'tax_query'   => array(array(
                        'taxonomy' => $taxo_term->taxonomy,
                        'field'    => 'slug',
                        'terms'    => $taxo_term->slug,
                    )),
                    'post_type'   => array(),
                    'post_status' => array(
                        'publish',
                        'draft',
                        'pending',
                        'future',
                        'private'
                    ),
                    'fields'      => 'ids',
                ) );
                $ids = '';
                foreach ( $post_ids as $post_id ) {
                    $ids .= $post_id . ',';
                }
                $ids_from_cats = substr_replace( $ids, '', -1 );
            }

            $ids_param = is_string( $ids_mpt_generation_raw ) ? sanitize_text_field( $ids_mpt_generation_raw ) : '';
            // Use IDs from category if available, otherwise from URL parameter
            $ids = ! empty( $ids_from_cats ) ? $ids_from_cats : $ids_param;
            $ids = array_map( 'intval', explode( ',', trim( $ids, ',' ) ) );
            $count = count( $ids );
            $ids = json_encode( $ids );
            $ajax_nonce = wp_create_nonce( 'ajax_nonce_All_Sources_Images' );
            $image_blocks = isset( $options_auto['image_block'] ) && is_array( $options_auto['image_block'] ) ? $options_auto['image_block'] : array();
            $counter_image_block = ! empty( $image_blocks ) ? count( $image_blocks ) : 1;
            if ( isset( $options_auto['bulk_generation_interval'] ) && (int) $options_auto['bulk_generation_interval'] !== 0 ) {
                $remaining_seconds = $this->cron_scheduled();
            } else {
                $remaining_seconds = 0;
            }
            $js_vars['sendposts'] = array(
                'posts'         => $ids,
                'count'         => $count,
                'block_counter' => $counter_image_block,
                'interval'      => $remaining_seconds,
                'nonce'         => $ajax_nonce,
            );
        }
        //Include Main dashboard Js
        if ( in_array( $hook, $ALLSI_new_pages ) || ($pagenow == 'index.php' || $pagenow == 'post.php' || $pagenow == 'post-new.php') && in_array( get_post_type( get_the_ID() ), $post_types_default['choosed_post_type'] ) ) {
            wp_enqueue_script(
                $this->plugin_name,
                plugin_dir_url( __FILE__ ) . 'js/all-sources-images-admin.js',
                array('jquery'),
                $this->version,
                array(
                    'strategy' => 'defer',
                )
            );
        }
        if ( $hook == 'all-sources-images_page_allsi-new-automatic' ) {
            $image_blocks = ( isset( $options_auto['image_block'] ) ? $options_auto['image_block'] : array() );
            // Calculating the current block index based on existing blocks
            $blockIndex = count( $image_blocks ) + 1;
            // Starts after the existing blocks
            wp_localize_script( $this->plugin_name, 'allsiAutomaticSettings', array(
                'blockIndex' => $blockIndex,
            ) );
        }
        $current_post_ID = json_encode( array_map( 'intval', array(get_the_ID()) ) );
        $post_nounce = wp_create_nonce( 'ajax_nonce_All_Sources_Images' );
        // Check if dalle is the first chosen image bank
        if ( isset( $options_banks['api_chosen_auto'] ) && true === in_array( 'dallev1', $options_banks['api_chosen_auto'] ) && 'dallev1' === reset( $options_banks['api_chosen_auto'] ) ) {
            $dalle = "true";
        } else {
            $dalle = "false";
        }
        if ( TRUE === $block['enable_manual_search'] ) {
            $block['enable_manual_search'] = "true";
        }
        /* General settings */
        $post_vars['postgeneration'] = array(
            'wp_ajax_url'       => admin_url( 'admin-ajax.php' ),
            'postID'            => $current_post_ID,
            'generateImg'       => plugin_dir_url( __FILE__ ) . 'img/generate.png',
            'strGenerate'       => esc_html__( 'Generation', 'all-sources-images' ),
            'strNoGenerate'     => esc_html__( 'Generate Automatically', 'all-sources-images' ),
            'strDalleGenerate'  => esc_html__( 'Dall-e v3 Generation may take 20 to 40 seconds. Please be patient', 'all-sources-images' ),
            'strManualGenerate' => esc_html__( 'Generate Manually', 'all-sources-images' ),
            'strNoRewrite'      => esc_html__( 'Edit your overwrite settings if you want a new image', 'all-sources-images' ),
            'strConfirmReplace' => esc_html__( 'A featured image already exists. Do you want to replace it with a new one?', 'all-sources-images' ),
            'manual_search'     => $block['enable_manual_search'],
            'dalle'             => $dalle,
            'nonce'             => $post_nounce,
        );
        // Do not include this file into unselected posts types
        if ( ($pagenow == 'index.php' || $pagenow == 'post.php' || $pagenow == 'post-new.php') && in_array( get_post_type( get_the_ID() ), $post_types_default['choosed_post_type'] ) ) {
            wp_localize_script( $this->plugin_name, 'allsiGenerationSpecificPostJsVars', $post_vars );
        }
        /* Translation for JS file */
        $translations_var['translations'] = array(
            'only_one_featured' => esc_html__( 'Only one featured image per post is possible', 'all-sources-images' ),
            'delete_logs'       => esc_html__( 'Are you sure to delete all logs ?', 'all-sources-images' ),
            'no_interval'       => esc_html__( 'No interval', 'all-sources-images' ),
            'per_minute'        => esc_html__( 'per minute', 'all-sources-images' ),
            'per_hour'          => esc_html__( 'per hour', 'all-sources-images' ),
        );
        wp_localize_script( $this->plugin_name, 'allsiTranslationsJsVars', $translations_var );
        wp_localize_script( 'common-mpt', 'allsiTranslationsJsVars', $translations_var );
        wp_localize_script( 'mpt-rating', 'allsiTranslationsJsVars', $translations_var );
        wp_localize_script( 'images-generation', 'allsiGenerationVars', $js_vars );
    }

    public function cron_scheduled() {
        // interval delay
        $options = wp_parse_args( get_option( 'ALLSI_plugin_main_settings' ) );
        $options_interval = $options['bulk_generation_interval'];
        // Switch interval options into seconds
        switch ( $options_interval ) {
            case '0':
                return false;
            case '1':
                $interval_seconds = 20;
                break;
            case '2':
                $interval_seconds = 30;
                break;
            case '3':
                $interval_seconds = 60;
                break;
            case '4':
                $interval_seconds = 120;
                break;
            case '5':
                $interval_seconds = 240;
                break;
            case '6':
                $interval_seconds = 600;
                break;
            case '7':
                $interval_seconds = 3600;
                break;
            default:
                return false;
        }
        return $interval_seconds;
    }

    /**
     * Show main menu item
     *
     * @since    4.0.0
     * @since    6.2.0 Simplified to only use new UI pages
     */
    public function ALLSI_main_settings() {
        // Main menu now opens Search directly.
        $this->media_picker_top_level_hook = add_menu_page(
            __( 'All Sources Images Options', 'all-sources-images' ),
            'All Sources Images',
            'ALLSI_manage',
            'allsi-media-browser',
            array( $this, 'ALLSI_render_media_picker_page' ),
            'dashicons-images-alt2',
            81
        );

        // First child: Search (same page as parent).
        $this->media_picker_search_submenu_hook = add_submenu_page(
            'allsi-media-browser',
            __( 'Search', 'all-sources-images' ),
            __( 'Search', 'all-sources-images' ),
            'ALLSI_manage',
            'allsi-media-browser',
            array( $this, 'ALLSI_render_media_picker_page' )
        );
        
        // Include new UI menus (Settings, Automatic, Bulk Generation)
        include_once plugin_dir_path( __FILE__ ) . 'partials/new-ui/new-ui-menus.php';
        ALLSI_set_new_ui_admin( $this );
        ALLSI_register_new_ui_menus();
        
        /* Bulk Generation link for posts & custom post type */
        $post_type_availables = get_option( 'ALLSI_plugin_posts_settings' );
        if ( isset( $post_type_availables['choosed_post_type'] ) ) {
            if ( false == $post_type_availables['choosed_post_type'] ) {
                $post_type_availables['choosed_post_type'] = array();
            }
        } else {
            $post_types_default = get_post_types( '', 'objects' );
            unset(
                $post_types_default['attachment'],
                $post_types_default['revision'],
                $post_types_default['nav_menu_item'],
                $post_types_default['custom_css'],
                $post_types_default['customize_changeset'],
                $post_types_default['oembed_cache'],
                $post_types_default['user_request'],
                $post_types_default['wp_block'],
                $post_types_default['wp_template'],
                $post_types_default['wp_template_part'],
                $post_types_default['wp_global_styles'],
                $post_types_default['wp_navigation']
            );
            $post_type_availables = array();
            $post_type_availables['choosed_post_type'] = array();
            foreach ( $post_types_default as $post_type ) {
                $post_type_availables['choosed_post_type'][$post_type->name] = $post_type->name;
            }
        }
        foreach ( $post_type_availables['choosed_post_type'] as $screen ) {
            add_filter( 'bulk_actions-edit-' . $screen, array(&$this, 'ALLSI_add_bulk_actions') );
            // Text on dropdown
            add_action(
                'handle_bulk_actions-edit-' . $screen,
                array(&$this, 'ALLSI_bulk_action_handler'),
                10,
                3
            );
            // Redirection
        }
        // Genererate link for Categories
        add_filter(
            'category_row_actions',
            array(&$this, 'ALLSI_add_bulk_action_category'),
            10,
            2
        );
        // Loop with each taxo for Genrate link
        $args_taxo = array(
            'public' => true,
        );
        $taxonomies = get_taxonomies( $args_taxo );
        $taxonomies = array_diff( $taxonomies, ['post_tag', 'post_format'] );
        foreach ( $taxonomies as $taxonomy ) {
            add_filter(
                $taxonomy . '_row_actions',
                array(&$this, 'ALLSI_add_bulk_action_category'),
                10,
                2
            );
        }
    }

    /**
     * Main actions
     *
     * @since    4.0.0
     */
    public function ALLSI_main_actions() {
        if ( !current_user_can( 'ALLSI_manage' ) ) {
            wp_die( esc_html__( 'You do not have sufficient permissions.', 'all-sources-images' ) );
        }
        //register_setting('ASI-plugin-posts-settings', 'ALLSI_plugin_posts_settings');
        register_setting( 'ASI-plugin-block-settings', 'ALLSI_plugin_block_settings', array(
            'sanitize_callback' => array( $this, 'ALLSI_sanitize_block_settings' ),
        ) );
        register_setting( 'ASI-plugin-main-settings', 'ALLSI_plugin_main_settings', array(
            'sanitize_callback' => array($this, 'ALLSI_sanitize_main_settings'),
        ) );
        register_setting( 'ASI-plugin-auto-image-settings', 'ALLSI_plugin_auto_image_settings', array(
            'sanitize_callback' => array( $this, 'ALLSI_sanitize_auto_image_settings' ),
        ) );
        register_setting( 'ASI-plugin-banks-settings', 'ALLSI_plugin_banks_settings', array(
            'sanitize_callback' => array($this, 'ALLSI_sanitize_banks_settings'),
        ) );
        register_setting( 'ASI-plugin-interval-settings', 'ALLSI_plugin_interval_settings', array(
            'sanitize_callback' => array( $this, 'ALLSI_sanitize_interval_settings' ),
        ) );
        register_setting( 'ASI-plugin-cron-settings', 'ALLSI_plugin_cron_settings', array(
            'sanitize_callback' => array($this, 'ALLSI_sanitize_cron_settings'),
        ) );
        register_setting( 'ASI-plugin-rights-settings', 'ALLSI_plugin_rights_settings', array(
            'sanitize_callback' => array( $this, 'ALLSI_sanitize_rights_settings' ),
        ) );
        register_setting( 'ASI-plugin-proxy-settings', 'ALLSI_plugin_proxy_settings', array(
            'sanitize_callback' => array( $this, 'ALLSI_sanitize_proxy_settings' ),
        ) );
        register_setting( 'ASI-plugin-logs-settings', 'ALLSI_plugin_logs_settings', array(
            'sanitize_callback' => array( $this, 'ALLSI_sanitize_logs_settings' ),
        ) );
        require_once dirname( __FILE__ ) . '/partials/delete_log.php';
        add_filter(
            'map_meta_cap',
            array(&$this, 'ALLSI_map_manage_options_capability'),
            10,
            4
        );
    }

    /**
     * Change to "ALLSI_manage" rights for all plugin forms
     *
     * @since    5.2.11
     */
    public function ALLSI_map_manage_options_capability(
        $caps,
        $cap,
        $user_id,
        $args
    ) {
        // Check if the capability being checked is 'manage_options'
        if ( $cap === 'manage_options' ) {
            // Check if the form is submitting a specific option related to your plugin
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified by WordPress Settings API in options.php
            $option_page = isset( $_POST['option_page'] ) ? sanitize_text_field( wp_unslash( $_POST['option_page'] ) ) : '';
            if ( $option_page && in_array( $option_page, array(
                'ASI-plugin-proxy-settings',
                'ASI-plugin-main-settings',
                'ASI-plugin-auto-image-settings',
                'ASI-plugin-block-settings',
                'ASI-plugin-cron-settings',
                'ASI-plugin-logs-settings',
                'ASI-plugin-rights-settings',
                'ASI-plugin-banks-settings'
            ) ) ) {
                // If the user has the 'ALLSI_manage' capability, grant access to manage the options
                if ( current_user_can( 'ALLSI_manage' ) ) {
                    $caps = array('ALLSI_manage');
                }
            }
        }
        return $caps;
    }

    /**
     * Get current url
     *
     * @since    4.0.0
     */
    public function ALLSI_current_url() {
        $requested_url = ( is_ssl() ? 'https://' : 'http://' );
        $requested_url .= isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
        $requested_url .= isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
        return $requested_url;
    }

    /**
     * Default banks name
     *
     * @since    6.0.0
     */
    public function ALLSI_banks_name_auto() {
        /* Banks for Automatic Bulk */
        $list_api_auto = array(
            esc_html__( 'Google Image (API)', 'all-sources-images' )      => array('google_image', true),
            esc_html__( 'DALL·E (v3)', 'all-sources-images' )            => array('dallev1', true),
            esc_html__( 'Openverse', 'all-sources-images' )               => array('cc_search', true),
            esc_html__( 'Flickr', 'all-sources-images' )                  => array('flickr', true),
            esc_html__( 'Pixabay', 'all-sources-images' )                 => array('pixabay', true),
            esc_html__( 'GIPHY', 'all-sources-images' )                   => array('giphy', true),
            esc_html__( 'Youtube', 'all-sources-images' )                 => array('youtube', true),
            esc_html__( 'Unsplash', 'all-sources-images' )                => array('unsplash', true),
            esc_html__( 'Pexels', 'all-sources-images' )                  => array('pexels', true),
            esc_html__( 'Stable Diffusion', 'all-sources-images' )        => array('stability', true),
            esc_html__( 'Replicate', 'all-sources-images' )               => array('replicate', true),
            esc_html__( 'Gemini (Google AI)', 'all-sources-images' )      => array('gemini', true),
            esc_html__( 'Cloudflare Workers AI', 'all-sources-images' )   => array('workers_ai', true),
            esc_html__( 'Google Translate', 'all-sources-images' )        => array('google_translate', true),
        );
        return $list_api_auto;
    }

    public function ALLSI_banks_name_manual() {
        $list_api_manual = array(
            esc_html__( 'Google Image (API)', 'all-sources-images' )      => array('google_image', true),
            esc_html__( 'DALL·E (v3)', 'all-sources-images' )            => array('dallev1', true),
            esc_html__( 'Openverse', 'all-sources-images' )               => array('cc_search', true),
            esc_html__( 'Flickr', 'all-sources-images' )                  => array('flickr', true),
            esc_html__( 'Pixabay', 'all-sources-images' )                 => array('pixabay', true),
            esc_html__( 'GIPHY', 'all-sources-images' )                   => array('giphy', true),
            esc_html__( 'Youtube', 'all-sources-images' )                 => array('youtube', true),
            esc_html__( 'Unsplash', 'all-sources-images' )                => array('unsplash', true),
            esc_html__( 'Pexels', 'all-sources-images' )                  => array('pexels', true),
            esc_html__( 'Stable Diffusion', 'all-sources-images' )        => array('stability', true),
            esc_html__( 'Replicate', 'all-sources-images' )               => array('replicate', true),
            esc_html__( 'Gemini (Google AI)', 'all-sources-images' )      => array('gemini', true),
            esc_html__( 'Cloudflare Workers AI', 'all-sources-images' )   => array('workers_ai', true),
        );
        return $list_api_manual;
    }

    /**
     * Codes for AI-based generators
     *
     * @since    6.1.8
     */
    public function ALLSI_ai_source_codes() {
        return array(
            'dallev1',
            'stability',
            'replicate',
            'gemini',
            'workers_ai',
        );
    }

    /**
     * Get default posts types & categories
     *
     * @since    5.0.0
     */
    public function ALLSI_default_posts_types() {
        $post_types_default['choosed_post_type'] = get_post_types();
        unset(
            $post_types_default['choosed_post_type']['attachment'],
            $post_types_default['choosed_post_type']['revision'],
            $post_types_default['choosed_post_type']['nav_menu_item'],
            $post_types_default['choosed_post_type']['custom_css'],
            $post_types_default['choosed_post_type']['customize_changeset'],
            $post_types_default['choosed_post_type']['oembed_cache'],
            $post_types_default['choosed_post_type']['user_request'],
            $post_types_default['choosed_post_type']['wp_block'],
            $post_types_default['choosed_post_type']['wp_template'],
            $post_types_default['choosed_post_type']['wp_template_part'],
            $post_types_default['choosed_post_type']['wp_global_styles'],
            $post_types_default['choosed_post_type']['wp_navigation']
        );
        $categories_default = get_terms( array(
            'taxonomy'   => 'category',
            'hide_empty' => false,
        ) );
        foreach ( $categories_default as $category ) {
            $post_types_default['choosed_categories'][$category->slug] = $category->name;
        }
        return $post_types_default;
    }

    /**
     * Default values for Image admin tabs
     *
     * @since    4.0.0
     */
    public function ALLSI_default_options_main_settings( $never_set = FALSE ) {
        $default_options = array(
            'image_block'              => array(
                1 => array(
                    'image_location'  => 'featured',
                    'api_chosen'      => 'pixabay',
                    'based_on'        => 'title',
                    'translation_EN'  => 'true',
                    'title_selection' => 'full_title',
                    'selected_image'  => 'first_result',
                ),
            ),
            'image_filename'           => 'title',
            'rewrite_featured'         => '',
            'image_reuse'              => '',
            'image_flip'               => '',
            'image_crop'               => '',
            'bulk_generation_interval' => 0,
        );
        return $default_options;
    }

    /**
     * Default values for Auto Image tab
     *
     * @since    6.2.0
     */
    public function ALLSI_default_options_auto_image_settings( $never_set = FALSE ) {
        $default_options = array(
            'enabled'          => 'disable',
            'post_types'       => array(),
            'term_ids'         => array(),
            'exclude_post_ids' => '',
        );

        return $default_options;
    }

    /**
     * Default values for "Source" (Banks) admin tabs
     *
     * @since    4.0.0
     */
    public function ALLSI_default_options_banks_settings( $never_set = FALSE ) {
        // Migration from v4 to v5
        $options_banks = get_option( 'ALLSI_plugin_banks_settings' );
        if ( isset( $options_banks['api_chosen'] ) && !isset( $options_banks['api_chosen_auto'] ) ) {
            // Already chosen bank as selected
            $ar_bank_auto = array($options_banks['api_chosen']);
        } else {
            // Default banks - Pixabay as primary
            $ar_bank_auto = array('pixabay', 'openverse');
        }
        // Default manual banks: stock image sources selected by default (as shown in settings UI)
        // Order: Pixabay, Flickr, Openverse (cc_search), Unsplash, GIPHY, Pexels
        // AI sources and YouTube/Google Image excluded by default
        $default_options = array(
            'api_chosen_manual' => array(
                'pixabay',
                'flickr',
                'cc_search',
                'unsplash',
                'giphy',
                'pexels',
            ),
            'api_chosen_auto'   => $ar_bank_auto,
            'googleimage'       => array(
                'cxid'           => '',
                'apikey'         => '',
                'search_country' => 'en',
                'img_color'      => '',
                'filetype'       => '',
                'imgsz'          => 'large',
                'imgtype'        => '',
                'safe'           => 'moderate',
                'rights'         => array(),
            ),
            'dallev1'           => array(
                'apikey'  => '',
                'imgsize' => '1024x1024',
            ),
            'stability'         => array(
                'apikey'              => '',
                'model'               => 'sd3-large',
                'aspect_ratio'        => '16:9',
                'output_format'       => 'jpeg',
                'use_negative_prompt' => '',
            ),
            'replicate'         => array(
                'apikey'        => '',
                'model'         => 'black-forest-labs/flux-schnell',
                'output_format' => 'webp',
                'aspect_ratio'  => '16:9',
            ),
            'gemini'            => array(
                'apikey'       => '',
                'model'        => 'gemini-2.5-flash-image',
                'aspect_ratio' => '1:1',
                'image_size'   => '',
            ),
            'workers_ai'        => array(
                'account_id'      => '',
                'api_token'       => '',
                'model'           => '@cf/black-forest-labs/flux-1-schnell',
                'steps'           => 4,
                'negative_prompt' => '',
            ),
            'cc_search'         => array(
                'source'       => 1,
                'rights'       => '',
                'imgtype'      => '',
                'aspect_ratio' => 'tall',
            ),
            'flickr'            => array(
                'apikey'  => '',
                'rights'  => '',
                'imgtype' => 7,
            ),
            'pixabay'           => array(
                'username'       => '',
                'apikey'         => '',
                'imgtype'        => 'all',
                'search_country' => 'en',
                'orientation'    => 'all',
                'min_width'      => 0,
                'min_height'     => 0,
                'safesearch'     => 'false',
            ),
            'pexels'            => array(
                'apikey'         => '',
                'orientation'    => '',
                'size'           => 'large',
                'color'          => '',
                'locale'         => 'en-US',
            ),
            'giphy'             => array(
                'apikey'    => '',
                'rating'    => 'g',
                'lang'      => 'en',
                'limit'     => 25,
                'media_type'=> 'gifs',
                'bundle'    => '',
            ),
            'unsplash'          => array(
                'apikey'         => '',
                'orientation'    => '',
                'content_filter' => 'low',
                'color'          => '',
            ),
            'youtube'           => array(
                'apikey'            => '',
                'thumbnail_quality' => 'high',
                'search_order'      => 'relevance',
                'video_duration'    => 'any',
                'safe_search'       => 'moderate',
                'max_results'       => 5,
                'region_code'       => '',
            ),
            'google_translate'  => array(
                'apikey' => '',
            ),
        );
        return $default_options;
    }

    /**
     * Default values for Interval admin tabs
     *
     * @since    4.0.0
     */
    /*
    	public function ALLSI_default_options_interval_settings( $never_set = FALSE ) {
    
    	    $default_options = array(
    	      // Interval
    	      'bulk_generation_interval'     => 0
    	    );
    
    	    return $default_options;
    	}*/
    /**
     * Default values for Gutenberg Block
     *
     * @since    4.0.0
     */
    public function ALLSI_default_options_block_settings( $never_set = FALSE ) {
        $default_options = array(
            'enable_manual_search'    => true,
            'translation_EN'          => 'true',
            'source_lang'             => '', // Empty = auto-detect from WordPress language
            'translate_alt'           => '',
            'translate_alt_lang'      => '',
            'google_translate_apikey' => '',
        );
        return $default_options;
    }

    /**
     * Default values for Cron admin tabs
     *
     * @since    4.0.0
     */
    public function ALLSI_default_options_cron_settings( $never_set = FALSE ) {
        $default_options = array(
            'enable_cron'        => 'disable',
            'cron_post_types'    => array('post', 'page'),
            'cron_interval_value'=> 5,
            'cron_interval_unit' => 'minutes',
            'posts_date_mode'    => 'all',
            'posts_date_value'   => 5,
            'posts_date_unit'    => 'days',
            'posts_per_run'      => 5,
        );
        return $default_options;
    }

    /**
     * Default values for Rights admin tabs
     *
     * @since    5.2.11
     */
    public function ALLSI_default_options_rights_settings( $never_set = FALSE ) {
        $default_options = array(
            'rights_editor'      => '',
            'rights_author'      => '',
            'rights_contributor' => '',
            'rights_subscriber'  => '',
        );
        return $default_options;
    }

    /**
     * Default values for Proxy admin tabs
     *
     * @since    4.0.0
     */
    public function ALLSI_default_options_proxy_settings( $never_set = FALSE ) {
        $default_options = array(
            'enable_proxy'    => 'disable',
            'proxy_mode'      => 'legacy',
            'proxy_address'   => '',
            'proxy_port'      => '80',
            'proxy_username'  => '',
            'proxy_password'  => '',
            'cloudflare_worker_url' => '',
            'cloudflare_token'      => '',
        );
        return $default_options;
    }

    /**
     * Default values for logs admin tabs
     *
     * @since    4.0.0
     */
    public function ALLSI_default_options_logs_settings( $never_set = FALSE ) {
        $default_options = array(
            'logs' => '',
        );
        return $default_options;
    }

    /**
     * Create file for logs
     *
     * @since    4.0.0
     */
    private function ALLSI_log_file( $check = false ) {
        $logs_dir = ALLSI_ensure_logs_dir();
        if ( false === $logs_dir ) {
            if ( true === $check ) {
                return false;
            }
            return false;
        }

        $files = @scandir( $logs_dir );
        $result = '';
        if ( !empty( $files ) ) {
            foreach ( $files as $key => $value ) {
                if ( !in_array( $value, array('.', '..'), true ) ) {
                    if ( !is_dir( $logs_dir . $value ) && strstr( $value, '.log' ) ) {
                        $result = $value;
                    }
                }
            }
        }
        if ( true == $check && empty( $result ) ) {
            return false;
        }
        if ( empty( $result ) ) {
            $result = 'mpt-' . wp_generate_password( 14, false, false ) . '.log';
        }
        return $result;
    }

    /**
     * Get the absolute path to the current log file.
     *
     * @return string|false
     */
    private function ALLSI_get_log_file_path() {
        $logs_dir = ALLSI_ensure_logs_dir();
        if ( false === $logs_dir ) {
            return false;
        }

        $log_file = $this->ALLSI_log_file( true );
        if ( empty( $log_file ) ) {
            return false;
        }

        $log_path = $logs_dir . $log_file;
        if ( ! is_file( $log_path ) || ! is_readable( $log_path ) ) {
            return false;
        }

        return $log_path;
    }

    /**
     * Read log contents, optionally tailing large files for performance.
     *
     * @param int $max_bytes Maximum bytes to read from the end of the file.
     *
     * @return array
     */
    private function ALLSI_read_log_contents( $max_bytes = 200000 ) {
        $log_path = $this->ALLSI_get_log_file_path();
        if ( false === $log_path ) {
            return array(
                'content'   => '',
                'filename'  => '',
                'truncated' => false,
                'size'      => 0,
            );
        }

        $file_size = filesize( $log_path );
        if ( false === $file_size || $file_size <= 0 ) {
            return array(
                'content'   => '',
                'filename'  => basename( $log_path ),
                'truncated' => false,
                'size'      => 0,
            );
        }

        $truncated = false;
        $content   = '';
        $max_bytes = max( 1024, absint( $max_bytes ) );

        if ( $file_size > $max_bytes ) {
            $truncated = true;
            $handle    = fopen( $log_path, 'rb' );
            if ( false !== $handle ) {
                fseek( $handle, -$max_bytes, SEEK_END );
                $content = fread( $handle, $max_bytes );
                fclose( $handle );
            }
            if ( false === $content ) {
                $content = '';
            }
            $content = ltrim( (string) $content );
        } else {
            $content = file_get_contents( $log_path );
            if ( false === $content ) {
                $content = '';
            }
        }

        return array(
            'content'   => (string) $content,
            'filename'  => basename( $log_path ),
            'truncated' => $truncated,
            'size'      => (int) $file_size,
        );
    }

    /**
     * Build log viewer data for rendering and AJAX responses.
     *
     * @param int $max_bytes Maximum bytes to read from log file.
     *
     * @return array
     */
    public function ALLSI_get_log_view_data( $max_bytes = 200000 ) {
        $log_data = $this->ALLSI_read_log_contents( $max_bytes );

        if ( empty( $log_data['filename'] ) ) {
            return array(
                'content'   => '',
                'filename'  => '',
                'truncated' => false,
                'size'      => 0,
                'message'   => __( 'No log file found.', 'all-sources-images' ),
            );
        }

        $message = sprintf(
            /* translators: %s: log file name */
            __( 'Viewing log file: %s', 'all-sources-images' ),
            $log_data['filename']
        );

        if ( ! empty( $log_data['truncated'] ) ) {
            $message .= ' ' . __( '(showing latest entries)', 'all-sources-images' );
        }

        $log_data['message'] = $message;
        return $log_data;
    }

    /**
     * AJAX: return log contents for the Logging viewer.
     */
    public function ALLSI_get_log_contents_ajax() {
        check_ajax_referer( 'allsi_log_viewer_nonce', 'nonce' );

        if ( ! current_user_can( 'ALLSI_manage' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'all-sources-images' ) ), 403 );
        }

        $log_data = $this->ALLSI_get_log_view_data();
        wp_send_json_success( $log_data );
    }

    /**
     * AJAX: clear the active log file.
     */
    public function ALLSI_clear_log_ajax() {
        check_ajax_referer( 'allsi_log_viewer_nonce', 'nonce' );

        if ( ! current_user_can( 'ALLSI_manage' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'all-sources-images' ) ), 403 );
        }

        $log_path = $this->ALLSI_get_log_file_path();
        if ( false === $log_path ) {
            wp_send_json_success( array(
                'content'   => '',
                'filename'  => '',
                'truncated' => false,
                'size'      => 0,
                'message'   => __( 'No log file found.', 'all-sources-images' ),
            ) );
        }

        $deleted = wp_delete_file( $log_path );
        if ( false === $deleted && file_exists( $log_path ) ) {
            wp_send_json_error( array( 'message' => __( 'Unable to clear log file.', 'all-sources-images' ) ) );
        }

        wp_send_json_success( array(
            'content'   => '',
            'filename'  => '',
            'truncated' => false,
            'size'      => 0,
            'message'   => __( 'Log file cleared.', 'all-sources-images' ),
        ) );
    }

    /**
     * Create file for logs
     *
     * @since    4.0.0
     */
    public function ALLSI_monolog_call() {
        $main_settings = get_option( 'ALLSI_plugin_logs_settings' );
        
        // Ensure the logger class is loaded
        if ( ! class_exists( 'ALLSI_Logger' ) ) {
            require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-allsi-logger.php';
        }
        
        // Check if logs enabled
        if ( ! empty( $main_settings['logs'] ) && true == $main_settings['logs'] ) {
            $logs_dir = ALLSI_ensure_logs_dir();
            if ( false === $logs_dir ) {
                return new ALLSI_Nolog();
            }
            $logfile = $this->ALLSI_log_file();
            $log = new ALLSI_Logger( 'ALLSI_logger', $logs_dir . $logfile, true );
        } else {
            $log = new ALLSI_Nolog();
        }
        return $log;
    }

    /**
     * Check if interval generation is enabled
     *
     * @since    4.0.0
     */
    public function ALLSI_check_interval() {
        $options = wp_parse_args( get_option( 'ALLSI_plugin_main_settings' ), $this->ALLSI_default_options_main_settings( TRUE ) );
        $value_bulk_generation_interval = ( isset( $options['bulk_generation_interval'] ) ? (int) $options['bulk_generation_interval'] : 0 );
        if ( 0 == $value_bulk_generation_interval ) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Check if interval generation is enabled
     *
     * @since    4.0.0
     */
    public function ALLSI_do_interval_cron( $new_ids_to_add = false ) {
        // Get processing  ids
        $interval_posts_to_generate = get_transient( 'ALLSI_interval_generation' );
        if ( ! is_array( $interval_posts_to_generate ) ) {
            $interval_posts_to_generate = array();
        }

        // Default status to "generation done". The loop below sets it to false when it finds work.
        $no_more_post_to_generate = true;

        $new_ids_to_add_array    = is_array( $new_ids_to_add ) ? $new_ids_to_add : array();
        $has_new_ids_to_add      = ! empty( $new_ids_to_add_array );
        $new_ids_to_add_is_false = ( false === $new_ids_to_add );

        foreach ( $interval_posts_to_generate as $post => $post_val ) {
            $continue_loop = false;
            // Get the first post not already generated
            if ( FALSE == $interval_posts_to_generate[$post]['processed'] ) {
                // Change default status to "generation processing"
                $no_more_post_to_generate = false;
                // Generation
                $launch_MPT = new All_Sources_Images_Generation($this->plugin_name, $this->version);
                $ALLSI_return = $launch_MPT->ALLSI_create_thumb(
                    $interval_posts_to_generate[$post]['id'],
                    '0',
                    '0',
                    '0',
                    '0'
                );
                // Get the return status
                if ( $ALLSI_return == null ) {
                    // Settings
                    $main_settings = get_option( 'ALLSI_plugin_main_settings' );
                    // Image location
                    $image_location = ( !empty( $main_settings['image_location'] ) ? $main_settings['image_location'] : 'featured' );
                    if ( has_post_thumbnail( $interval_posts_to_generate[$post]['id'] ) && "featured" === $image_location ) {
                        $interval_posts_to_generate[$post]['processed'] = 'already-exist';
                        $continue_loop = true;
                    } else {
                        // Add the status to this speficic post : Problem
                        $interval_posts_to_generate[$post]['processed'] = 'error';
                    }
                } else {
                    // Add the status to theis speficic post : ok
                    $interval_posts_to_generate[$post]['processed'] = true;
                }
                // Limit only to the first post
                if ( TRUE !== $continue_loop ) {
                    break;
                }
            }
        }
        // Generation done and new ids to generate
        if ( TRUE == $no_more_post_to_generate && $has_new_ids_to_add ) {
            // Delete old posts
            delete_transient( 'ALLSI_interval_generation' );
            $new_posts_to_generate = array();
            foreach ( $new_ids_to_add_array as $id ) {
                $new_posts_to_generate[] = array(
                    'id'        => (int) $id,
                    'processed' => false,
                );
            }
            // Add news posts
            set_transient( 'ALLSI_interval_generation', $new_posts_to_generate );
        } elseif ( TRUE == $no_more_post_to_generate && $new_ids_to_add_is_false ) {
            // Generation done
            // Nothing to add/do
        } elseif ( FALSE == $no_more_post_to_generate && $has_new_ids_to_add ) {
            // Update with new ids (added at the end)
            foreach ( $new_ids_to_add_array as $id ) {
                $interval_posts_to_generate[] = array(
                    'id'        => (int) $id,
                    'processed' => false,
                );
            }
            // Allow ids into the array only once. Avoid duplicate ids (Remove the last ones)
            $temp_generate_posts = array();
            foreach ( $interval_posts_to_generate as &$v ) {
                if ( !isset( $temp_generate_posts[$v['id']] ) ) {
                    $temp_generate_posts[$v['id']] =& $v;
                }
            }
            $interval_posts_to_generate = array_values( $temp_generate_posts );
            // Add news posts
            set_transient( 'ALLSI_interval_generation', $interval_posts_to_generate );
        } else {
            // Generation not finished : Updating transient
            set_transient( 'ALLSI_interval_generation', $interval_posts_to_generate );
        }
    }

    /**
     * Add "Generate featured images" into bulk menu for categories
     *
     * @since    4.0.0
     */
    public function ALLSI_add_bulk_action_category( $actions, $tag ) {
        $actions['atp'] = '<a href="admin.php?page=allsi-new-bulk-generation&cats=' . $tag->term_id . '" class="aria-button-if-js">' . esc_html__( 'Generate featured images', 'all-sources-images' ) . '</a>';
        return $actions;
    }

    /**
     * Redirection for bulk action
     *
     * @since    4.0.0
     */
    public function ALLSI_bulk_action_handler( $redirect_to, $action_name, $post_ids ) {
        if ( 'bulk_regenerate_thumbnails' === $action_name ) {
            $ids = implode( ',', array_map( 'intval', $post_ids ) );
            wp_safe_redirect( admin_url( 'admin.php?page=allsi-new-bulk-generation&auto_generate_ids=' . $ids ) );
            exit;
        }
        return $redirect_to;
    }

    /**
     * Add "Generate featured images" into bulk menu for posts
     *
     * @since    4.0.0
     */
    public function ALLSI_add_bulk_actions( $actions ) {
        // Enqueue the bulk actions script with localized text
        wp_enqueue_script(
            'allsi-bulk-actions',
            plugin_dir_url( __FILE__ ) . 'js/bulk-actions.js',
            array( 'jquery' ),
            $this->version,
            true
        );
        
        wp_localize_script( 'allsi-bulk-actions', 'allsiBulkActions', array(
            'optionText' => esc_html__( 'Generate Images (ASI)', 'all-sources-images' ),
        ) );
        
        return $actions;
    }

    /**
     * Testing if APIs works into settings page
     *
     * @since    5.0.0
     */
    public function ALLSI_test_apis() {
        check_ajax_referer( 'api_testing_nonce', 'nonce' );
        
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( __( 'Permission denied.', 'all-sources-images' ) );
        }
        
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above with check_ajax_referer().
        $apiKey = isset( $_POST['apikey'] ) ? sanitize_text_field( wp_unslash( $_POST['apikey'] ) ) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above with check_ajax_referer().
        $cxId = isset( $_POST['cxid'] ) ? sanitize_text_field( wp_unslash( $_POST['cxid'] ) ) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above with check_ajax_referer().
        $apiBank = isset( $_POST['apibank'] ) ? sanitize_text_field( wp_unslash( $_POST['apibank'] ) ) : '';
        $response = '';
        if ( 'pixabay' === $apiBank ) {
            $apiUrl = "https://pixabay.com/api/?key=" . $apiKey . "&q=exemple";
            $response = $this->ALLSI_remote_get( 'pixabay', $apiUrl );
        } elseif ( 'google_image' === $apiBank ) {
            if ( ! class_exists( 'ALLSI_Source_Google_Image' ) ) {
                wp_send_json_error( __( 'Google Image source is unavailable.', 'all-sources-images' ) );
            }

            if ( empty( $cxId ) ) {
                wp_send_json_error( __( 'CX ID is required for Google Custom Search.', 'all-sources-images' ) );
            }

            $default_options   = $this->ALLSI_default_options_banks_settings( true );
            $google_defaults   = isset( $default_options['googleimage'] ) ? $default_options['googleimage'] : array();
            $google_options    = array_merge( $google_defaults, array(
                'apikey' => $apiKey,
                'cxid'   => $cxId,
                'imgsz'  => isset( $google_defaults['imgsz'] ) ? $google_defaults['imgsz'] : 'large',
            ) );
            $proxy_args        = $this->ALLSI_get_proxy_args();

            $source  = new ALLSI_Source_Google_Image();
            $context = array(
                'options'        => array( 'googleimage' => $google_options ),
                'search'         => 'asi test',
                'proxy_args'     => $proxy_args,
                'generation'     => $this,
                'get_only_thumb' => true,
            );

            $result = $source->generate( $context );

            if ( is_wp_error( $result ) ) {
                wp_send_json_error( $result->get_error_message() );
            }

            if ( isset( $result['items'] ) && ! empty( $result['items'] ) ) {
                wp_send_json_success( wp_json_encode( $result ) );
            }

            wp_send_json_error( __( 'No results found or there was an error.', 'all-sources-images' ) );
        } elseif ( 'dalle' === $apiBank ) {
            if ( ! class_exists( 'ALLSI_Source_Dallev1' ) ) {
                wp_send_json_error( __( 'DALL·E source is unavailable.', 'all-sources-images' ) );
            }

            if ( empty( $apiKey ) ) {
                wp_send_json_error( __( 'API key is required.', 'all-sources-images' ) );
            }

            $default_options = $this->ALLSI_default_options_banks_settings( true );
            $dalle_defaults  = isset( $default_options['dallev1'] ) ? $default_options['dallev1'] : array();
            $dalle_options   = array_merge( $dalle_defaults, array( 'apikey' => $apiKey ) );
            $proxy_args      = $this->ALLSI_get_proxy_args();

            $source  = new ALLSI_Source_Dallev1();
            $context = array(
                'options'        => array( 'dallev1' => $dalle_options ),
                'search'         => 'asi dalle test',
                'proxy_args'     => $proxy_args,
                'get_only_thumb' => true,
            );

            $result = $source->generate( $context );

            if ( is_wp_error( $result ) ) {
                wp_send_json_error( $result->get_error_message() );
            }

            if ( isset( $result['data'] ) && ! empty( $result['data'] ) ) {
                wp_send_json_success( wp_json_encode( $result ) );
            }

            wp_send_json_error( __( 'No results found or there was an error.', 'all-sources-images' ) );
        } elseif ( 'youtube' === $apiBank ) {
            if ( ! class_exists( 'ALLSI_Source_Youtube' ) ) {
                wp_send_json_error( __( 'YouTube source is unavailable.', 'all-sources-images' ) );
            }

            $default_options  = $this->ALLSI_default_options_banks_settings( true );
            $youtube_defaults = isset( $default_options['youtube'] ) ? $default_options['youtube'] : array();
            $youtube_options  = array_merge( $youtube_defaults, array( 'apikey' => $apiKey, 'max_results' => 1 ) );
            $proxy_args       = $this->ALLSI_get_proxy_args();

            $source  = new ALLSI_Source_Youtube();
            $context = array(
                'options'        => array( 'youtube' => $youtube_options ),
                'search'         => 'asi test',
                'proxy_args'     => $proxy_args,
                'generation'     => $this,
                'get_only_thumb' => true,
            );

            $result = $source->generate( $context );

            if ( is_wp_error( $result ) ) {
                wp_send_json_error( $result->get_error_message() );
            }

            if ( isset( $result['items'] ) && ! empty( $result['items'] ) ) {
                wp_send_json_success( wp_json_encode( $result ) );
            }

            wp_send_json_error( __( 'No results found or there was an error.', 'all-sources-images' ) );
        } elseif ( 'unsplash' === $apiBank ) {
            $apiUrl = "https://api.unsplash.com/search/photos?query=test&client_id={$apiKey}";
            $response = $this->ALLSI_remote_get( 'unsplash', $apiUrl );
        } elseif ( 'giphy' === $apiBank ) {
            if ( empty( $apiKey ) ) {
                wp_send_json_error( __( 'API key is required.', 'all-sources-images' ) );
            }
            $query_args = array(
                'api_key' => $apiKey,
                'q'       => 'asi test',
                'limit'   => 1,
                'rating'  => 'pg',
                'lang'    => 'en',
            );
            $apiUrl = add_query_arg( $query_args, 'https://api.giphy.com/v1/gifs/search' );
            $response = $this->ALLSI_remote_get( 'giphy', $apiUrl );
            if ( is_wp_error( $response ) ) {
                wp_send_json_error( __( 'Error connecting to the API.', 'all-sources-images' ) );
            }
            $status_code = wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            if ( 200 !== $status_code ) {
                $decoded = json_decode( $body, true );
                $message = isset( $decoded['meta']['msg'] ) ? $decoded['meta']['msg'] : __( 'Unexpected GIPHY API response.', 'all-sources-images' );
                wp_send_json_error( $message, $status_code );
            }
            wp_send_json_success( $body );
            return;
        } elseif ( 'pexels' === $apiBank ) {
            $apiUrl = "https://api.pexels.com/v1/search?query=nature&per_page=1";
            $response = $this->ALLSI_remote_get( 'pexels', $apiUrl, array(
                'headers' => array(
                    'Authorization' => $apiKey,
                ),
            ) );
        } elseif ( 'stability' === $apiBank ) {
            if ( empty( $apiKey ) ) {
                wp_send_json_error( __( 'API key is required.', 'all-sources-images' ) );
            }

            $request_args  = array(
                'headers' => array(
                    'Authorization' => 'Bearer ' . $apiKey,
                    'Accept'        => 'application/json',
                ),
                'timeout' => 20,
            );
            $response      = $this->ALLSI_remote_get( 'stability', 'https://api.stability.ai/v1/user/account', $request_args );

            if ( is_wp_error( $response ) ) {
                wp_send_json_error( __( 'Error connecting to Stability AI.', 'all-sources-images' ) );
            }

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

            if ( 200 === $code ) {
                wp_send_json_success( $body );
            }

            $decoded = json_decode( $body, true );
            $message = isset( $decoded['message'] ) ? $decoded['message'] : __( 'Unexpected Stability AI response.', 'all-sources-images' );
            wp_send_json_error( $message, $code );
        } elseif ( 'replicate' === $apiBank ) {
            // test Replicate API key by listing models
            $apiUrl = 'https://api.replicate.com/v1/models';
            $response = $this->ALLSI_remote_get( 'replicate', $apiUrl, array(
                'headers' => array(
                    'Authorization' => 'Token ' . $apiKey,
                    'Content-Type'  => 'application/json',
                ),
                'timeout' => 10,
            ) );
            // Handle HTTP error
            if ( is_wp_error( $response ) ) {
                wp_send_json_error( 'Error connecting to Replicate API.' );
            }
            // Check status code for unauthorized vs ok
            $code = wp_remote_retrieve_response_code( $response );
            if ( 200 === $code ) {
                wp_send_json_success( wp_remote_retrieve_body( $response ) );
            } elseif ( 401 === $code ) {
                wp_send_json_error( 'unauthorized', 401 );
            } else {
                wp_send_json_error( 'Unexpected status: ' . $code, $code );
            }
        } elseif ( 'gemini' === $apiBank ) {
            if ( empty( $apiKey ) ) {
                wp_send_json_error( __( 'API key is required.', 'all-sources-images' ) );
            }
            $apiUrl = add_query_arg( array(
                'key'      => $apiKey,
                'pageSize' => 1,
            ), 'https://generativelanguage.googleapis.com/v1beta/models' );
            $response = $this->ALLSI_remote_get( 'gemini', $apiUrl, array(
                'timeout' => 15,
            ) );
            if ( is_wp_error( $response ) ) {
                wp_send_json_error( 'Error connecting to Gemini API.' );
            }
            $code = wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            if ( 200 === $code ) {
                wp_send_json_success( $body );
            }
            $decoded = json_decode( $body, true );
            $message = isset( $decoded['error']['message'] ) ? $decoded['error']['message'] : __( 'Unexpected Gemini API response.', 'all-sources-images' );
            wp_send_json_error( $message, $code );
        } elseif ( 'workers_ai' === $apiBank ) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified at function start with check_ajax_referer().
            $account_id = isset( $_POST['account_id'] ) ? sanitize_text_field( wp_unslash( $_POST['account_id'] ) ) : '';
            if ( empty( $account_id ) ) {
                wp_send_json_error( __( 'Account ID is required.', 'all-sources-images' ) );
            }
            if ( empty( $apiKey ) ) {
                wp_send_json_error( __( 'API token is required.', 'all-sources-images' ) );
            }
            // Use the AI finetunes endpoint to verify credentials (lightweight, read-only)
            $apiUrl = sprintf( 'https://api.cloudflare.com/client/v4/accounts/%s/ai/finetunes', rawurlencode( $account_id ) );
            $request_args = array(
                'headers' => array(
                    'Authorization' => 'Bearer ' . $apiKey,
                    'Content-Type'  => 'application/json',
                ),
                'timeout' => 15,
            );
            $response = $this->ALLSI_remote_get( 'workers_ai', $apiUrl, $request_args );
            if ( is_wp_error( $response ) ) {
                wp_send_json_error( __( 'Error connecting to Cloudflare Workers AI.', 'all-sources-images' ) );
            }
            $code = wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            $decoded = json_decode( $body, true );
            
            // Check if response indicates success (even empty results mean valid credentials)
            if ( 200 === $code && isset( $decoded['success'] ) && true === $decoded['success'] ) {
                wp_send_json_success( $body );
            }
            
            // Handle specific error cases
            $message = '';
            if ( isset( $decoded['errors'][0]['message'] ) ) {
                $message = $decoded['errors'][0]['message'];
            } elseif ( isset( $decoded['messages'][0] ) ) {
                $message = $decoded['messages'][0];
            } elseif ( 401 === $code || 403 === $code ) {
                $message = __( 'Invalid API token or insufficient permissions. Ensure your token has Workers AI:Read permission.', 'all-sources-images' );
            } elseif ( 404 === $code ) {
                $message = __( 'Account not found. Please verify your Account ID.', 'all-sources-images' );
            } else {
                $message = __( 'Unexpected Workers AI response.', 'all-sources-images' );
            }
            wp_send_json_error( $message, $code );
        } else {
            $response = '';
            wp_send_json_error( 'Unknown API bank: ' . $apiBank );
        }
        if ( is_wp_error( $response ) ) {
            wp_send_json_error( 'Error connecting to the API.' );
        } else {
            wp_send_json_success( $response['body'] );
        }
    }

    /**
     * Register MPT Gutenberg Block
     *
     * @since    5.0.0
     */
    public function ALLSI_register_mpt_block() {
        $banks = wp_parse_args( get_option( 'ALLSI_plugin_banks_settings' ), $this->ALLSI_default_options_banks_settings( TRUE ) );
        if ( isset( $banks['api_chosen_manual']['envato'] ) ) {
            unset($banks['api_chosen_manual']['envato']);
        }

        $manual_bank_labels = array();
        $manual_banks = $this->ALLSI_banks_name_manual();
        foreach ( $manual_banks as $label => $data ) {
            $manual_bank_labels[$data[0]] = $label;
        }

        $asset_file = (include plugin_dir_path( __FILE__ ) . 'blocks/allsi-images/build/index.asset.php');

        wp_register_script(
            'allsi-minimasonry',
            plugins_url( 'blocks/allsi-images/build/minimasonry.min.js', __FILE__ ),
            array(),
            filemtime( plugin_dir_path( __FILE__ ) . 'blocks/allsi-images/build/minimasonry.min.js' ),
            true
        );

        $script_dependencies = $asset_file['dependencies'];
        $script_dependencies[] = 'allsi-minimasonry';

        wp_register_style(
            'allsi-images-editor-style',
            plugins_url( 'blocks/allsi-images/build/allsi-images-editor.css', __FILE__ ),
            array(),
            filemtime( plugin_dir_path( __FILE__ ) . 'blocks/allsi-images/build/allsi-images-editor.css' )
        );

        wp_register_script(
            'allsi-images-script',
            plugins_url( 'blocks/allsi-images/build/index.js', __FILE__ ),
            $script_dependencies,
            $asset_file['version']
        );
        register_block_type( 'allsi/allsi-images', array(
            'editor_script' => 'allsi-images-script',
        ) );
        $default_post_id = apply_filters( 'ALLSI_media_picker_default_post_id', 0 );
        $this->media_picker_default_post_id = $default_post_id;
        $block_settings = wp_parse_args( get_option( 'ALLSI_plugin_block_settings' ), $this->ALLSI_default_options_block_settings( TRUE ) );
        $translation_en_active = ( ! empty( $block_settings['translation_EN'] ) && $block_settings['translation_EN'] == 'true' );
        $translate_alt_active = ( ! empty( $block_settings['translate_alt'] ) && $block_settings['translate_alt'] == 'true' );
        $translate_alt_lang = ( ! empty( $block_settings['translate_alt_lang'] ) ? $block_settings['translate_alt_lang'] : '' );
        wp_localize_script( 'allsi-images-script', 'allsiAjax', array(
            'ajax_url'           => admin_url( 'admin-ajax.php' ),
            'admin_url'          => admin_url(),
            'nonce'              => wp_create_nonce( 'ALLSI_gutenberg_block' ),
            'choosed_banks'      => $banks['api_chosen_manual'],
            'available_banks'    => $manual_bank_labels,
            'licensing_data'     => '1', // All features available
            'path_default_img'   => plugins_url( '/blocks/allsi-images/img/', __FILE__ ),
            'ai_sources'         => $this->ALLSI_ai_source_codes(),
            'default_post_id'    => $default_post_id,
            'translation_en'     => $translation_en_active,
            'translate_alt'      => $translate_alt_active,
            'translate_alt_lang' => $translate_alt_lang,
        ) );
        // Locate character strings
        wp_set_script_translations( 'allsi-images-script', 'all-sources-images', plugin_dir_path( __DIR__ ) . 'languages' );
        add_action( 'admin_enqueue_scripts', function ( $hook ) {
            // Checks whether you are on an editing page
            if ( 'post.php' === $hook || 'post-new.php' === $hook ) {
                wp_enqueue_script(
                    'manual-search',
                    plugins_url( 'js/manual_search.js', __FILE__ ),
                    array('wp-blocks', 'wp-data'),
                    $this->version,
                    true
                );
            }
        } );
        // Note: Translations are loaded automatically by WordPress.org for plugins hosted there.
    }

    /**
     * Enqueue style for MPT Gutenberg Block
     *
     * @since    5.0.0
     */
    public function ALLSI_enqueue_style_block() {
        wp_enqueue_style( 'allsi-images-editor-style' );
    }

    /**
     * Register Media submenu page for standalone image explorer.
     */
    public function ALLSI_register_media_picker_page() {
        $this->media_picker_hook = add_submenu_page(
            'upload.php',
            __( 'All Sources Images', 'all-sources-images' ),
            __( 'All Sources Images', 'all-sources-images' ),
            'upload_files',
            'allsi-media-browser',
            array( $this, 'ALLSI_render_media_picker_page' )
        );
    }

    /**
     * Render container for the standalone explorer.
     */
    public function ALLSI_render_media_picker_page() {
        echo '<div class="wrap allsi-media-browser-wrap">';
        echo '<h1>' . esc_html__( 'All Sources Images', 'all-sources-images' ) . '</h1>';
        echo '<p class="description">' . esc_html__( 'Search and download media from your connected sources without leaving the Media Library.', 'all-sources-images' ) . '</p>';
        echo '<div id="allsi-media-picker-root"></div>';
        echo '</div>';
    }

    /**
     * Enqueue assets when viewing the standalone explorer page.
     */
    public function ALLSI_enqueue_media_picker_assets( $hook ) {
        $allowed_hooks = array_filter( array(
            $this->media_picker_hook,
            $this->media_picker_top_level_hook,
            $this->media_picker_search_submenu_hook,
        ) );

        if ( empty( $allowed_hooks ) || ! in_array( $hook, $allowed_hooks, true ) ) {
            return;
        }

        wp_enqueue_style( 'allsi-images-editor-style' );
        wp_enqueue_script( 'allsi-images-script' );

        // Enqueue the media picker initialization script
        wp_enqueue_script(
            'allsi-media-picker-init',
            plugin_dir_url( __FILE__ ) . 'js/media-picker-init.js',
            array( 'allsi-images-script' ),
            $this->version,
            true
        );
    }

    /**
     * Enqueue integration script for the WordPress media modal so we can inject the explorer tab.
     */
    public function ALLSI_enqueue_media_modal_assets( $hook = null ) {
        if ( ! current_user_can( 'upload_files' ) ) {
            return;
        }

        if ( function_exists( 'wp_enqueue_media' ) ) {
            wp_enqueue_media();
        }

        $this->ALLSI_prepare_media_modal_scripts();
    }

    /**
     * Ensure the media modal integration is also loaded on the front-end (Elementor, etc.).
     */
    public function ALLSI_enqueue_front_media_modal_assets() {
        if ( ! is_user_logged_in() || ! current_user_can( 'upload_files' ) ) {
            return;
        }

        if ( function_exists( 'wp_enqueue_media' ) ) {
            wp_enqueue_media();
        }

        $this->ALLSI_prepare_media_modal_scripts();
    }

    /**
     * Shared loader for styles and scripts needed by the media modal tab.
     */
    private function ALLSI_prepare_media_modal_scripts() {
        wp_enqueue_style( 'allsi-images-editor-style' );
        wp_enqueue_script( 'allsi-images-script' );

        if ( ! wp_script_is( 'allsi-media-modal', 'registered' ) ) {
            wp_register_script(
                'allsi-media-modal',
                plugins_url( 'js/allsi-media-modal.js', __FILE__ ),
                array( 'jquery', 'media-views', 'allsi-images-script' ),
                $this->version,
                true
            );
        }

        wp_localize_script( 'allsi-media-modal', 'allsiMediaModal', array(
            'tabLabel' => __( 'All Sources Images', 'all-sources-images' ),
            'fallbackPostId' => isset( $this->media_picker_default_post_id ) ? intval( $this->media_picker_default_post_id ) : 0,
            'tabId' => 'allsi-media-tab',
        ) );

        wp_enqueue_script( 'allsi-media-modal' );
    }

    /**
     * Initialize the Elementor integration if necessary.
     */
    public function ALLSI_maybe_boot_elementor() {
        if ( ! class_exists( '\\Elementor\\Plugin' ) ) {
            return;
        }

        if ( class_exists( 'ALLSI_Elementor_Integration' ) ) {
            return;
        }

        require_once plugin_dir_path( __FILE__ ) . 'elementor/class-allsi-elementor-integration.php';
        new ALLSI_Elementor_Integration( $this->version );
    }

    /**
     * Search MPT Gutenberg Block
     *
     * @since    5.0.0
     */
    public function ALLSI_block_searching_images() {
        // Security check first
        check_ajax_referer( 'ALLSI_gutenberg_block', 'nonce' );
        
        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( __( 'Permission denied.', 'all-sources-images' ) );
        }
        
        if ( defined( 'ALLSI_DEBUG' ) && ALLSI_DEBUG ) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging only when ALLSI_DEBUG is true.
            error_log( '[All Sources Images] ALLSI_block_searching_images CALLED' );
        }
        
        $search_raw = filter_input( INPUT_GET, 'search', FILTER_UNSAFE_RAW );
        $bank_raw   = filter_input( INPUT_GET, 'bank', FILTER_UNSAFE_RAW );
        $id_raw     = filter_input( INPUT_GET, 'id', FILTER_UNSAFE_RAW );

        if ( ! is_string( $search_raw ) || ! is_string( $bank_raw ) || ! is_string( $id_raw ) || '' === $search_raw || '' === $bank_raw || '' === $id_raw ) {
            wp_send_json_error( 'Missing required parameters' );
            return;
        }

        $search_term = sanitize_text_field( $search_raw );
        $bank        = sanitize_text_field( strtolower( $bank_raw ) );
        $index_raw   = filter_input( INPUT_GET, 'index', FILTER_UNSAFE_RAW );
        $page_raw    = filter_input( INPUT_GET, 'page', FILTER_UNSAFE_RAW );
        $skip_translation_raw = filter_input( INPUT_GET, 'skip_translation', FILTER_UNSAFE_RAW );

        $index = is_string( $index_raw ) ? absint( $index_raw ) : 0;
        $id    = absint( $id_raw );
        $page  = is_string( $page_raw ) ? max( 1, absint( $page_raw ) ) : 1;

        // Skip translation if already translated by frontend (skip_translation=1)
        $skip_translation = is_string( $skip_translation_raw ) && absint( $skip_translation_raw ) === 1;
        
        // Translate search term to English if translation_EN is enabled AND not already translated
        if ( ! $skip_translation ) {
            $block_settings = wp_parse_args( get_option( 'ALLSI_plugin_block_settings' ), $this->ALLSI_default_options_block_settings( TRUE ) );
            if ( ! empty( $block_settings['translation_EN'] ) && $block_settings['translation_EN'] == 'true' ) {
                // Use configured source language or auto-detect from WordPress
                if ( ! empty( $block_settings['source_lang'] ) ) {
                    $source_lang = $block_settings['source_lang'];
                } else {
                    $wp_lang = get_bloginfo( 'language' );
                    $source_lang = substr( $wp_lang, 0, 2 );
                }
                if ( $source_lang !== 'en' && ! empty( $search_term ) ) {
                    $REST_generation = new All_Sources_Images_Generation( $this->plugin_name, $this->version );
                    $translated_term = $REST_generation->ALLSI_translate_text( $search_term, $source_lang, 'en' );
                    if ( $translated_term !== false && ! empty( $translated_term ) ) {
                        ALLSI_log( array(
                            'original_search' => $search_term,
                            'translated_search' => $translated_term,
                            'source_lang' => $source_lang,
                        ), 'GUTENBERG_BLOCK_TRANSLATION' );
                        $search_term = $translated_term;
                    }
                }
            }
        }
        
        ALLSI_log( array(
            'search_term' => $search_term,
            'bank' => $bank,
            'post_id' => $id,
            'index' => $index
        ), 'GUTENBERG_BLOCK_SEARCH' );
        if ( 0 !== $id && FALSE === get_post_status( $id ) ) {
            wp_send_json_error( 'Error with post ID.' );
        }
        $REST_generation = new All_Sources_Images_Generation($this->plugin_name, $this->version);
        $results_thumbs = $REST_generation->ALLSI_create_thumb(
            $id,
            '0',
            '1',
            '1',
            '1',
            TRUE,
            $search_term,
            $bank,
            null,
            null,
            null,
            false,
            array( 'page' => $page )
        );
        if ( $results_thumbs !== false ) {
            $response_preview = json_encode( $results_thumbs );
            if ( strlen( $response_preview ) > 1200 ) {
                $response_preview = substr( $response_preview, 0, 1200 ) . '...';
            }
            ALLSI_log( array(
                'bank'             => $bank,
                'response_preview' => $response_preview,
            ), 'GUTENBERG_BLOCK_RESPONSE' );
        }
        if ( $results_thumbs === false ) {
            ALLSI_log( 'No results returned from ALLSI_create_thumb', 'GUTENBERG_BLOCK_ERROR' );
            wp_send_json_error( 'Error connecting to the API.' );
        } else {
            // Normalize API response to universal format: always use 'images' array
            $normalized_images = array();
            $bank_options = wp_parse_args( get_option( 'ALLSI_plugin_banks_settings' ), $this->ALLSI_default_options_banks_settings( TRUE ) );
            $yt_options = isset( $bank_options['youtube'] ) ? $bank_options['youtube'] : array();
            $yt_quality = isset( $yt_options['thumbnail_quality'] ) ? $yt_options['thumbnail_quality'] : 'high';
            $yt_quality_fallback = array_unique( array(
                $yt_quality,
                'maxresdefault',
                'standard',
                'high',
                'medium',
                'default'
            ) );
            
            $pagination_data = array(
                'page'        => $page,
                'has_more'    => false,
                'total_pages' => null,
                'total'       => null,
            );
            // Detect bank and extract images + normalize structure
            if ( 'giphy' === $bank && isset( $results_thumbs['data'] ) && is_array( $results_thumbs['data'] ) ) {
                if ( isset( $results_thumbs['pagination'] ) && is_array( $results_thumbs['pagination'] ) ) {
                    $giphy_pagination = $results_thumbs['pagination'];
                    $limit_option = isset( $bank_options['giphy']['limit'] ) ? intval( $bank_options['giphy']['limit'] ) : 25;
                    $limit_option = max( 1, min( 50, $limit_option ) );
                    $offset = isset( $giphy_pagination['offset'] ) ? max( 0, intval( $giphy_pagination['offset'] ) ) : ( ( $page - 1 ) * $limit_option );
                    $batch_count = isset( $giphy_pagination['count'] ) ? max( 0, intval( $giphy_pagination['count'] ) ) : count( $results_thumbs['data'] );
                    $pagination_data['page'] = max( 1, intval( floor( $offset / $limit_option ) + 1 ) );
                    if ( isset( $giphy_pagination['total_count'] ) ) {
                        $total_count = max( 0, intval( $giphy_pagination['total_count'] ) );
                        $pagination_data['total'] = $total_count;
                        $pagination_data['total_pages'] = (int) ceil( $total_count / $limit_option );
                        $pagination_data['has_more'] = ( $offset + $batch_count ) < $total_count;
                    } else {
                        $pagination_data['has_more'] = ( $batch_count >= $limit_option );
                    }
                }
                foreach ( $results_thumbs['data'] as $item ) {
                    if ( empty( $item ) || ! is_array( $item ) ) {
                        continue;
                    }

                    $images      = isset( $item['images'] ) && is_array( $item['images'] ) ? $item['images'] : array();
                    $large_url   = '';
                    $thumb_url   = '';
                    $large_keys  = array( 'downsized_large', 'original', 'downsized', 'fixed_height', 'fixed_width' );
                    $thumb_keys  = array( 'fixed_height_small', 'fixed_width_small', 'fixed_height', 'fixed_width', 'downsized_still', 'downsized_small' );

                    foreach ( $large_keys as $key ) {
                        if ( isset( $images[ $key ]['url'] ) && '' !== $images[ $key ]['url'] ) {
                            $large_url = $images[ $key ]['url'];
                            break;
                        }
                    }

                    foreach ( $thumb_keys as $key ) {
                        if ( isset( $images[ $key ]['url'] ) && '' !== $images[ $key ]['url'] ) {
                            $thumb_url = $images[ $key ]['url'];
                            break;
                        }
                    }

                    if ( empty( $large_url ) && isset( $item['url'] ) ) {
                        $large_url = $item['url'];
                    }

                    if ( empty( $large_url ) ) {
                        continue;
                    }

                    if ( empty( $thumb_url ) ) {
                        $thumb_url = $large_url;
                    }

                    $caption = '';
                    if ( isset( $item['user']['display_name'] ) && '' !== $item['user']['display_name'] ) {
                        $caption = $item['user']['display_name'];
                    } elseif ( isset( $item['username'] ) ) {
                        $caption = $item['username'];
                    }

                    $normalized_images[] = array(
                        'url'      => $large_url,
                        'thumb'    => $thumb_url,
                        'title'    => isset( $item['title'] ) ? $item['title'] : '',
                        'alt'      => isset( $item['title'] ) ? $item['title'] : '',
                        'caption'  => $caption,
                        'source'   => isset( $item['url'] ) ? $item['url'] : '',
                        'rating'   => isset( $item['rating'] ) ? $item['rating'] : '',
                        'media_id' => isset( $item['id'] ) ? $item['id'] : '',
                    );
                }
            } elseif (isset($results_thumbs['data']) && is_array($results_thumbs['data'])) {
                // DALL-E format: { data: [{url, revised_prompt}] }
                foreach ($results_thumbs['data'] as $item) {
                    $normalized_images[] = array(
                        'url' => isset($item['url']) ? $item['url'] : '',
                        'thumb' => isset($item['url']) ? $item['url'] : '',
                        'title' => isset($item['revised_prompt']) ? $item['revised_prompt'] : '',
                        'alt' => isset($item['revised_prompt']) ? $item['revised_prompt'] : '',
                        'caption' => 'DALL-E Generated'
                    );
                }
            } elseif ( 'youtube' === $bank && isset($results_thumbs['items']) && is_array($results_thumbs['items']) ) {
                foreach ( $results_thumbs['items'] as $item ) {
                    $thumbnails = isset( $item['snippet']['thumbnails'] ) ? $item['snippet']['thumbnails'] : array();
                    $chosen_large = '';
                    foreach ( $yt_quality_fallback as $quality_key ) {
                        if ( isset( $thumbnails[ $quality_key ]['url'] ) ) {
                            $chosen_large = $thumbnails[ $quality_key ]['url'];
                            break;
                        }
                    }
                    $thumb_medium = isset( $thumbnails['medium']['url'] ) ? $thumbnails['medium']['url'] : ( isset( $thumbnails['default']['url'] ) ? $thumbnails['default']['url'] : $chosen_large );
                    $title = isset( $item['snippet']['title'] ) ? $item['snippet']['title'] : '';
                    $channel = isset( $item['snippet']['channelTitle'] ) ? $item['snippet']['channelTitle'] : '';
                    $video_id = isset( $item['id']['videoId'] ) ? $item['id']['videoId'] : '';

                    $normalized_images[] = array(
                        'url' => $chosen_large,
                        'thumb' => $thumb_medium,
                        'title' => $title,
                        'alt' => $title,
                        'caption' => $channel,
                        'video_id' => $video_id,
                    );
                }
            } elseif ( 'flickr' === $bank ) {
                $photos = array();
                if ( isset( $results_thumbs['photos']['photo'] ) && is_array( $results_thumbs['photos']['photo'] ) ) {
                    $photos = $results_thumbs['photos']['photo'];
                } elseif ( isset( $results_thumbs['photos'] ) && is_array( $results_thumbs['photos'] ) ) {
                    $photos = $results_thumbs['photos'];
                }

                if ( isset( $results_thumbs['photos'] ) && is_array( $results_thumbs['photos'] ) ) {
                    if ( isset( $results_thumbs['photos']['page'] ) ) {
                        $pagination_data['page'] = max( 1, intval( $results_thumbs['photos']['page'] ) );
                    }
                    if ( isset( $results_thumbs['photos']['pages'] ) ) {
                        $pagination_data['total_pages'] = intval( $results_thumbs['photos']['pages'] );
                        $pagination_data['has_more'] = ( $pagination_data['page'] < $pagination_data['total_pages'] );
                    }
                    if ( isset( $results_thumbs['photos']['total'] ) ) {
                        $pagination_data['total'] = intval( $results_thumbs['photos']['total'] );
                    }
                }

                foreach ( $photos as $item ) {
                    $server = isset( $item['server'] ) ? $item['server'] : '';
                    $id = isset( $item['id'] ) ? $item['id'] : '';
                    $secret = isset( $item['secret'] ) ? $item['secret'] : '';

                    $large_url = '';
                    $thumb_url = '';
                    if ( $server && $id && $secret ) {
                        $base_url = 'https://live.staticflickr.com/' . $server . '/' . $id . '_' . $secret;
                        $large_url = $base_url . '_b.jpg';
                        $thumb_url = $base_url . '_q.jpg';
                    }

                    if ( empty( $large_url ) && isset( $item['url'] ) ) {
                        $large_url = $item['url'];
                    }

                    if ( empty( $thumb_url ) ) {
                        $thumb_url = $large_url;
                    }

                    $normalized_images[] = array(
                        'url' => $large_url,
                        'thumb' => $thumb_url,
                        'title' => isset( $item['title'] ) ? $item['title'] : '',
                        'alt' => isset( $item['title'] ) ? $item['title'] : '',
                        'caption' => isset( $item['owner'] ) ? $item['owner'] : '',
                        'photo_id' => $id,
                    );
                }
            } elseif (isset($results_thumbs['items']) && is_array($results_thumbs['items'])) {
                // Google Custom Search format
                if ( 'google_image' === $bank ) {
                    $request_info = array();
                    if ( isset( $results_thumbs['queries']['request'][0] ) ) {
                        $request_info = $results_thumbs['queries']['request'][0];
                    }
                    $start_index = isset( $request_info['startIndex'] ) ? intval( $request_info['startIndex'] ) : 1;
                    $count_per_page = isset( $request_info['count'] ) ? intval( $request_info['count'] ) : count( $results_thumbs['items'] );
                    if ( $count_per_page <= 0 ) {
                        $count_per_page = max( 1, count( $results_thumbs['items'] ) );
                    }
                    $current_page = ( $count_per_page > 0 ) ? intval( floor( max( 0, $start_index - 1 ) / $count_per_page ) + 1 ) : $page;
                    $pagination_data['page'] = max( 1, $current_page );
                    $total_results = null;
                    if ( isset( $results_thumbs['searchInformation']['totalResults'] ) ) {
                        $total_results = intval( $results_thumbs['searchInformation']['totalResults'] );
                        $pagination_data['total'] = $total_results;
                    }
                    if ( null !== $total_results && $count_per_page > 0 ) {
                        $max_considered = min( $total_results, 100 );
                        $pagination_data['total_pages'] = (int) ceil( $max_considered / $count_per_page );
                    }
                    $pagination_data['has_more'] = ! empty( $results_thumbs['queries']['nextPage'] );
                }
                foreach ($results_thumbs['items'] as $item) {
                    $large_url = '';
                    if (isset($item['pagemap']['cse_image'][0]['src']) && '' !== $item['pagemap']['cse_image'][0]['src']) {
                        $large_url = $item['pagemap']['cse_image'][0]['src'];
                    } elseif (isset($item['link']) && '' !== $item['link']) {
                        $large_url = $item['link'];
                    }

                    if ('' === $large_url) {
                        continue;
                    }

                    $thumb_url = '';
                    if (isset($item['pagemap']['cse_thumbnail'][0]['src']) && '' !== $item['pagemap']['cse_thumbnail'][0]['src']) {
                        $thumb_url = $item['pagemap']['cse_thumbnail'][0]['src'];
                    } elseif (isset($item['image']['thumbnailLink']) && '' !== $item['image']['thumbnailLink']) {
                        $thumb_url = $item['image']['thumbnailLink'];
                    } else {
                        $thumb_url = $large_url;
                    }

                    $title = isset($item['title']) ? $item['title'] : '';
                    $caption = isset($item['displayLink']) ? $item['displayLink'] : '';

                    $normalized_images[] = array(
                        'url' => $large_url,
                        'thumb' => $thumb_url,
                        'title' => $title,
                        'alt' => $title,
                        'caption' => $caption,
                        'source' => isset($item['link']) ? $item['link'] : ''
                    );
                }
            } elseif (isset($results_thumbs['hits']) && is_array($results_thumbs['hits'])) {
                // Pixabay format
                if ( 'pixabay' === $bank ) {
                    $total_hits = isset( $results_thumbs['totalHits'] ) ? intval( $results_thumbs['totalHits'] ) : null;
                    // Pixabay API does not return a per-page value in responses.
                    // Keep this in sync with the request (ALLSI_Source_Pixabay::build_query_args uses per_page=20).
                    $requested_per_page = 20;
                    if ( isset( $results_thumbs['per_page'] ) ) {
                        $requested_per_page = intval( $results_thumbs['per_page'] );
                    }
                    if ( $requested_per_page <= 0 ) {
                        $requested_per_page = 20;
                    }
                    $pagination_data['page'] = $page;
                    if ( null !== $total_hits ) {
                        $pagination_data['total'] = $total_hits;
                        if ( $requested_per_page > 0 ) {
                            $total_pages = (int) ceil( $total_hits / $requested_per_page );
                            $pagination_data['total_pages'] = $total_pages;
                            $pagination_data['has_more'] = ( $page < $total_pages );
                        }
                    } else {
                        $pagination_data['has_more'] = ( count( $results_thumbs['hits'] ) >= $requested_per_page );
                    }
                }
                foreach ($results_thumbs['hits'] as $item) {
                    $normalized_images[] = array(
                        'url' => isset($item['largeImageURL']) ? $item['largeImageURL'] : '',
                        'thumb' => isset($item['webformatURL']) ? $item['webformatURL'] : '',
                        'title' => isset($item['tags']) ? $item['tags'] : '',
                        'alt' => isset($item['tags']) ? $item['tags'] : '',
                        'caption' => isset($item['user']) ? $item['user'] : ''
                    );
                }
            } elseif (isset($results_thumbs['results']) && is_array($results_thumbs['results'])) {
                // Openverse, Unsplash results format
                if ( 'unsplash' === $bank ) {
                    if ( isset( $results_thumbs['total'] ) ) {
                        $pagination_data['total'] = intval( $results_thumbs['total'] );
                    }
                    if ( isset( $results_thumbs['total_pages'] ) ) {
                        $total_pages = intval( $results_thumbs['total_pages'] );
                        $pagination_data['total_pages'] = $total_pages;
                        $pagination_data['has_more'] = ( $page < $total_pages );
                    }
                } elseif ( in_array( $bank, array( 'cc_search', 'openverse' ), true ) ) {
                    if ( isset( $results_thumbs['page'] ) ) {
                        $pagination_data['page'] = max( 1, intval( $results_thumbs['page'] ) );
                    }
                    $total_results = null;
                    if ( isset( $results_thumbs['result_count'] ) ) {
                        $total_results = intval( $results_thumbs['result_count'] );
                    } elseif ( isset( $results_thumbs['count'] ) ) {
                        $total_results = intval( $results_thumbs['count'] );
                    }
                    if ( null !== $total_results ) {
                        $pagination_data['total'] = $total_results;
                    }
                    if ( isset( $results_thumbs['page_count'] ) ) {
                        $pagination_data['total_pages'] = intval( $results_thumbs['page_count'] );
                        $pagination_data['has_more'] = ( $pagination_data['page'] < $pagination_data['total_pages'] );
                    } else {
                        $pagination_data['has_more'] = ! empty( $results_thumbs['next'] );
                    }
                }
                foreach ($results_thumbs['results'] as $item) {
                    $normalized_images[] = array(
                        'url' => isset($item['urls']['regular']) ? $item['urls']['regular'] : (isset($item['url']) ? $item['url'] : ''),
                        'thumb' => isset($item['urls']['small']) ? $item['urls']['small'] : (isset($item['url']) ? $item['url'] : ''),
                        'title' => isset($item['alt_description']) ? $item['alt_description'] : (isset($item['title']) ? $item['title'] : ''),
                        'alt' => isset($item['alt_description']) ? $item['alt_description'] : (isset($item['title']) ? $item['title'] : ''),
                        'caption' => isset($item['user']['name']) ? $item['user']['name'] : (isset($item['creator']) ? $item['creator'] : '')
                    );
                }
            } elseif (isset($results_thumbs['photos']) && is_array($results_thumbs['photos'])) {
                // Pexels format
                if ( 'pexels' === $bank ) {
                    $current_page = isset( $results_thumbs['page'] ) ? max( 1, intval( $results_thumbs['page'] ) ) : $page;
                    $per_page = isset( $results_thumbs['per_page'] ) ? intval( $results_thumbs['per_page'] ) : count( $results_thumbs['photos'] );
                    $total_results = isset( $results_thumbs['total_results'] ) ? intval( $results_thumbs['total_results'] ) : null;
                    $pagination_data['page'] = $current_page;
                    if ( null !== $total_results ) {
                        $pagination_data['total'] = $total_results;
                        if ( $per_page > 0 ) {
                            $pagination_data['total_pages'] = (int) ceil( $total_results / $per_page );
                        }
                    }
                    if ( ! empty( $results_thumbs['next_page'] ) ) {
                        $pagination_data['has_more'] = true;
                    } elseif ( isset( $pagination_data['total_pages'] ) ) {
                        $pagination_data['has_more'] = ( $current_page < $pagination_data['total_pages'] );
                    } else {
                        $pagination_data['has_more'] = ! empty( $results_thumbs['photos'] );
                    }
                }
                foreach ($results_thumbs['photos'] as $item) {
                    $normalized_images[] = array(
                        'url' => isset($item['src']['large2x']) ? $item['src']['large2x'] : '',
                        'thumb' => isset($item['src']['tiny']) ? $item['src']['tiny'] : '',
                        'title' => isset($item['alt']) ? $item['alt'] : '',
                        'alt' => isset($item['alt']) ? $item['alt'] : '',
                        'caption' => isset($item['photographer']) ? $item['photographer'] : ''
                    );
                }
            } elseif ( 'stability' === $bank && isset( $results_thumbs['image'] ) ) {
                $stability_options = isset( $bank_options['stability'] ) ? $bank_options['stability'] : array();
                $format = isset( $stability_options['output_format'] ) ? $stability_options['output_format'] : 'jpeg';
                $mime_map = array(
                    'jpeg' => 'image/jpeg',
                    'jpg'  => 'image/jpeg',
                    'png'  => 'image/png',
                    'webp' => 'image/webp',
                );
                $mime = isset( $mime_map[ $format ] ) ? $mime_map[ $format ] : 'image/jpeg';
                $data_uri = 'data:' . $mime . ';base64,' . $results_thumbs['image'];
                $normalized_images[] = array(
                    'url'      => $data_uri,
                    'thumb'    => $data_uri,
                    'title'    => $search_term,
                    'alt'      => $search_term,
                    'caption'  => __( 'Generated with Stability AI', 'all-sources-images' ),
                    'mime'     => $mime,
                    'is_data'  => true,
                );
            } elseif ( 'gemini' === $bank && isset( $results_thumbs['candidates'] ) && is_array( $results_thumbs['candidates'] ) ) {
                foreach ( $results_thumbs['candidates'] as $candidate ) {
                    if ( empty( $candidate['content']['parts'] ) || ! is_array( $candidate['content']['parts'] ) ) {
                        continue;
                    }
                    foreach ( $candidate['content']['parts'] as $part ) {
                        $inline_data = array();
                        if ( isset( $part['inline_data'] ) && is_array( $part['inline_data'] ) ) {
                            $inline_data = $part['inline_data'];
                        } elseif ( isset( $part['inlineData'] ) && is_array( $part['inlineData'] ) ) {
                            $inline_data = $part['inlineData'];
                        }

                        if ( empty( $inline_data['data'] ) ) {
                            continue;
                        }

                        $mime = 'image/png';
                        if ( ! empty( $inline_data['mime_type'] ) ) {
                            $mime = $inline_data['mime_type'];
                        } elseif ( ! empty( $inline_data['mimeType'] ) ) {
                            $mime = $inline_data['mimeType'];
                        }

                        $data_uri = 'data:' . $mime . ';base64,' . $inline_data['data'];
                        $normalized_images[] = array(
                            'url'      => $data_uri,
                            'thumb'    => $data_uri,
                            'title'    => $search_term,
                            'alt'      => $search_term,
                            'caption'  => __( 'Generated with Google Gemini', 'all-sources-images' ),
                            'mime'     => $mime,
                            'is_data'  => true,
                        );
                    }
                }
            } elseif ( 'workers_ai' === $bank && is_array( $results_thumbs ) ) {
                $image_payload = '';
                if ( isset( $results_thumbs['result']['image'] ) ) {
                    $image_payload = $results_thumbs['result']['image'];
                } elseif ( isset( $results_thumbs['result']['images'][0] ) ) {
                    $image_payload = $results_thumbs['result']['images'][0];
                } elseif ( isset( $results_thumbs['result']['output'][0] ) ) {
                    $image_payload = $results_thumbs['result']['output'][0];
                }

                if ( is_string( $image_payload ) && $image_payload !== '' ) {
                    $mime    = 'image/png';
                    $data_uri = $image_payload;
                    if ( 0 !== strpos( $image_payload, 'data:' ) ) {
                        $data_uri = 'data:' . $mime . ';base64,' . $image_payload;
                    } else {
                        if ( preg_match( '/^data:([^;]+);base64,/', $image_payload, $matches ) ) {
                            $mime = $matches[1];
                        }
                    }

                    $normalized_images[] = array(
                        'url'      => $data_uri,
                        'thumb'    => $data_uri,
                        'title'    => $search_term,
                        'alt'      => $search_term,
                        'caption'  => __( 'Generated with Cloudflare Workers AI', 'all-sources-images' ),
                        'mime'     => $mime,
                        'is_data'  => true,
                    );
                }
            } elseif ( 'replicate' === $bank && isset( $results_thumbs['output'] ) ) {
                // Replicate format: {"output": ["url1", "url2", ...]} or {"output": "url"}
                $outputs = is_array( $results_thumbs['output'] ) ? $results_thumbs['output'] : array( $results_thumbs['output'] );
                $model_name = isset( $results_thumbs['model'] ) ? $results_thumbs['model'] : 'Replicate';
                
                foreach ( $outputs as $output_url ) {
                    if ( ! is_string( $output_url ) || '' === $output_url ) {
                        continue;
                    }
                    // translators: %s is the AI model name used on Replicate.
                    $caption = sprintf( __( 'Generated with %s on Replicate', 'all-sources-images' ), $model_name );
                    $normalized_images[] = array(
                        'url'     => $output_url,
                        'thumb'   => $output_url,
                        'title'   => $search_term,
                        'alt'     => $search_term,
                        'caption' => $caption,
                    );
                }
            } elseif ( 'dallev1' === $bank && isset( $results_thumbs['data'] ) && is_array( $results_thumbs['data'] ) ) {
                // DALL-E format: {"data": [{"url": "...", "revised_prompt": "..."}]}
                foreach ( $results_thumbs['data'] as $dalle_item ) {
                    if ( empty( $dalle_item['url'] ) ) {
                        continue;
                    }
                    $alt_text = isset( $dalle_item['revised_prompt'] ) ? $dalle_item['revised_prompt'] : $search_term;
                    $normalized_images[] = array(
                        'url'     => $dalle_item['url'],
                        'thumb'   => $dalle_item['url'],
                        'title'   => $search_term,
                        'alt'     => $alt_text,
                        'caption' => __( 'Generated with DALL-E', 'all-sources-images' ),
                    );
                }
            }

            if ( 'gemini' === $bank && ! empty( $results_thumbs['candidates'] ) && empty( $normalized_images ) ) {
                $first_part_keys = array();
                if ( isset( $results_thumbs['candidates'][0]['content']['parts'][0] ) && is_array( $results_thumbs['candidates'][0]['content']['parts'][0] ) ) {
                    $first_part_keys = array_keys( $results_thumbs['candidates'][0]['content']['parts'][0] );
                }

                ALLSI_log(
                    array(
                        'candidate_count' => is_array( $results_thumbs['candidates'] ) ? count( $results_thumbs['candidates'] ) : 0,
                        'first_part_keys' => $first_part_keys,
                    ),
                    'GUTENBERG_BLOCK_GEMINI_EMPTY_NORMALIZATION'
                );
            }
            
            // Universal response format
            $response_data = array(
                'images'     => $normalized_images,
                'index'      => $index,
                'count'      => count($normalized_images),
                'pagination' => $pagination_data,
                'bank'       => $bank,
            );
            
            ALLSI_log( 'Search successful, returning ' . count($normalized_images) . ' normalized images', 'GUTENBERG_BLOCK_SUCCESS' );
            wp_send_json_success( $response_data );
        }
    }

    /**
     * Translate search term AJAX endpoint
     * Called once by frontend before searching multiple image banks
     *
     * @since    6.2.0
     */
    public function ALLSI_translate_search_ajax() {
        check_ajax_referer( 'ALLSI_gutenberg_block', 'nonce' );
        
        if ( ! current_user_can( 'edit_posts' ) ) {
            wp_send_json_error( __( 'Permission denied.', 'all-sources-images' ) );
        }
        
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified above with check_ajax_referer
        if ( ! isset( $_GET['search'] ) ) {
            wp_send_json_error( 'Missing search parameter' );
            return;
        }
        
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified above with check_ajax_referer
        $search_term = sanitize_text_field( wp_unslash( $_GET['search'] ) );
        $original_term = $search_term;
        $was_translated = false;
        
        // Check if translation is enabled
        $block_settings = wp_parse_args( get_option( 'ALLSI_plugin_block_settings' ), $this->ALLSI_default_options_block_settings( TRUE ) );
        
        if ( ! empty( $block_settings['translation_EN'] ) && $block_settings['translation_EN'] == 'true' ) {
            // Use configured source language or auto-detect from WordPress
            if ( ! empty( $block_settings['source_lang'] ) ) {
                $source_lang = $block_settings['source_lang'];
            } else {
                $wp_lang = get_bloginfo( 'language' );
                $source_lang = substr( $wp_lang, 0, 2 );
            }
            
            if ( $source_lang !== 'en' && ! empty( $search_term ) ) {
                $REST_generation = new All_Sources_Images_Generation( $this->plugin_name, $this->version );
                $translated_term = $REST_generation->ALLSI_translate_text( $search_term, $source_lang, 'en' );
                
                if ( $translated_term !== false && ! empty( $translated_term ) ) {
                    ALLSI_log( array(
                        'original_search' => $search_term,
                        'translated_search' => $translated_term,
                        'source_lang' => $source_lang,
                    ), 'TRANSLATE_SEARCH_AJAX' );
                    $search_term = $translated_term;
                    $was_translated = true;
                }
            }
        }
        
        wp_send_json_success( array(
            'original' => $original_term,
            'translated' => $search_term,
            'was_translated' => $was_translated,
        ) );
    }

    /**
     * Downloading image for MPT Gutenberg Block
     *
     * @since    5.0.0
     */
    public function ALLSI_block_downloading_image() {
        // Check the nonce
        check_ajax_referer( 'ALLSI_gutenberg_block', 'nonce' );
        
        if ( ! current_user_can( 'upload_files' ) ) {
            wp_send_json_error( __( 'Permission denied.', 'all-sources-images' ) );
        }
        
        $raw_url_image = filter_input( INPUT_POST, 'url_image', FILTER_UNSAFE_RAW );
        $raw_url_image = is_string( $raw_url_image ) ? $raw_url_image : '';
        if ( 0 === strpos( $raw_url_image, 'data:image' ) ) {
            $url_image = $raw_url_image;
        } else {
            $url_image = esc_url_raw( $raw_url_image );
        }

        $search_term_raw = filter_input( INPUT_POST, 'search_term', FILTER_UNSAFE_RAW );
        $bank_raw        = filter_input( INPUT_POST, 'bank', FILTER_UNSAFE_RAW );
        $alt_raw         = filter_input( INPUT_POST, 'alt_image', FILTER_UNSAFE_RAW );
        $raw_caption     = filter_input( INPUT_POST, 'caption_image', FILTER_UNSAFE_RAW );
        $title_image_raw = filter_input( INPUT_POST, 'title_image', FILTER_UNSAFE_RAW );
        $file_name_raw   = filter_input( INPUT_POST, 'file_name', FILTER_UNSAFE_RAW );

        $search_term = is_string( $search_term_raw ) ? sanitize_text_field( $search_term_raw ) : 'image';
        $bank        = is_string( $bank_raw ) ? sanitize_text_field( $bank_raw ) : '';
        $alt         = is_string( $alt_raw ) ? sanitize_text_field( $alt_raw ) : '';
        $caption     = is_string( $raw_caption ) ? wp_kses_post( $raw_caption ) : '';
        $title_image = is_string( $title_image_raw ) ? sanitize_text_field( $title_image_raw ) : '';
        $file_name_raw = is_string( $file_name_raw ) ? $file_name_raw : '';

        $file_name_base = sanitize_file_name( $file_name_raw );
        if ( '' !== $file_name_base && false !== strpos( $file_name_base, '.' ) ) {
            $file_name_base = sanitize_file_name( pathinfo( $file_name_base, PATHINFO_FILENAME ) );
        }

        $post_id_raw = filter_input( INPUT_POST, 'post_id', FILTER_UNSAFE_RAW );
        $id_fallback_raw = filter_input( INPUT_POST, 'id', FILTER_UNSAFE_RAW );

        $post_id = is_string( $post_id_raw ) ? intval( $post_id_raw ) : 0;
        if ( ! $post_id && is_string( $id_fallback_raw ) ) {
            $post_id = intval( $id_fallback_raw );
        }

        ALLSI_log( array(
            'url'      => $url_image,
            'post_id'  => $post_id,
            'bank'     => $bank,
            'search'   => $search_term,
        ), 'GUTENBERG_DOWNLOAD_START' );
        $file_array = array();
        $is_data_uri = ( 0 === strpos( $url_image, 'data:image' ) );
        $tmp = '';
        if ( $is_data_uri ) {
            if ( preg_match( '/^data:(image\/[a-zA-Z0-9.+-]+);base64,(.*)$/', $url_image, $matches ) ) {
                $mime_type = $matches[1];
                $base64_data = str_replace( ' ', '+', $matches[2] );
                $decoded = base64_decode( $base64_data );
                if ( false === $decoded ) {
                    wp_send_json_error( array( 'erreur' => 'Invalid image data.' ) );
                    return;
                }
                $tmp = wp_tempnam( 'stability_block' );
                if ( false === $tmp ) {
                    wp_send_json_error( array( 'erreur' => 'Unable to create temporary file.' ) );
                    return;
                }
                // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Saving decoded base64 image to temp file.
                file_put_contents( $tmp, $decoded );
            } else {
                wp_send_json_error( array( 'erreur' => 'Invalid data URI.' ) );
                return;
            }
        } else {
            $tmp = download_url( $url_image );
            // Check for error
            if ( is_wp_error( $tmp ) ) {
                ALLSI_log( array(
                    'message' => $tmp->get_error_message(),
                    'url'     => $url_image,
                ), 'GUTENBERG_DOWNLOAD_ERROR' );
                wp_send_json_error( array(
                    'erreur' => $tmp->get_error_message(),
                ) );
                return;
            }
            $mime_type = '';
        }
        
        $allowed_extensions = array( 'jpg', 'jpeg', 'png', 'gif', 'webp' );
        $mime_map = array(
            'image/jpeg' => 'jpg',
            'image/jpg'  => 'jpg',
            'image/pjpeg'=> 'jpg',
            'image/png'  => 'png',
            'image/gif'  => 'gif',
            'image/webp' => 'webp',
        );

        $extension = '';
        $detected_mime = wp_get_image_mime( $tmp );
        if ( $detected_mime && isset( $mime_map[ $detected_mime ] ) ) {
            $mime_type = $detected_mime;
            $extension = $mime_map[ $detected_mime ];
        }

        if ( empty( $extension ) ) {
            $parsed_path = wp_parse_url( $url_image, PHP_URL_PATH );
            if ( $parsed_path ) {
                $path_info = pathinfo( $parsed_path );
                if ( isset( $path_info['extension'] ) ) {
                    $maybe_ext = strtolower( $path_info['extension'] );
                    if ( in_array( $maybe_ext, $allowed_extensions, true ) ) {
                        $extension = $maybe_ext;
                    }
                }
            }
        }

        if ( empty( $extension ) ) {
            $extension = 'jpg';
        }

        $sanitized_search = sanitize_file_name( $search_term );
        if ( empty( $sanitized_search ) ) {
            $sanitized_search = 'allsi-image';
        }
        $final_file_base = ! empty( $file_name_base ) ? $file_name_base : $sanitized_search;
        $file_array['name'] = $final_file_base . '.' . $extension;
        // Filename
        $file_array['tmp_name'] = $tmp;
        if ( $mime_type ) {
            $file_array['type'] = $mime_type;
        }
        // Check to ensure the image is valid
        $check = wp_check_filetype_and_ext( $file_array['tmp_name'], $file_array['name'] );
        if ( $check["ext"] == "" ) {
            wp_delete_file( $file_array['tmp_name'] );
            ALLSI_log( array(
                'reason' => 'Invalid image after download',
                'mime'   => $mime_type,
                'url'    => $url_image,
            ), 'GUTENBERG_DOWNLOAD_ERROR' );
            wp_send_json_error( array(
                'erreur' => 'Invalid Image',
            ) );
            return;
        }
        // wp_handle_sideload to download the image.
        $uploaded_file = wp_handle_sideload( $file_array, array(
            'test_form' => false,
        ) );
        if ( isset( $uploaded_file['error'] ) ) {
            wp_delete_file( $file_array['tmp_name'] );
            ALLSI_log( array(
                'reason' => $uploaded_file['error'],
                'file'   => $file_array['name'],
            ), 'GUTENBERG_DOWNLOAD_ERROR' );
            wp_send_json_error( array(
                'error' => $uploaded_file['error'],
            ) );
            return;
        }
        // Insert image into the media library.
        $wp_upload_dir = wp_upload_dir();
        $default_title = preg_replace( '/\.[^.]+$/', '', basename( $uploaded_file['file'] ) );
        $attachment = array(
            'guid'           => $wp_upload_dir['url'] . '/' . basename( $uploaded_file['file'] ),
            'post_mime_type' => $check['type'],
            'post_title'     => ( ! empty( $title_image ) ? $title_image : $default_title ),
            'post_content'   => '',
            'post_status'    => 'inherit',
        );
        $attach_id = wp_insert_attachment( $attachment, $uploaded_file['file'], $post_id );
        
        // Translate ALT text if enabled in block settings
        $block_settings = wp_parse_args( get_option( 'ALLSI_plugin_block_settings' ), $this->ALLSI_default_options_block_settings( TRUE ) );
        if ( ! empty( $alt ) && ! empty( $block_settings['translate_alt'] ) && $block_settings['translate_alt'] == 'true' ) {
            $target_lang = ( ! empty( $block_settings['translate_alt_lang'] ) ? $block_settings['translate_alt_lang'] : '' );
            // Only translate if target language is set and not English (ALT usually comes in English from image banks)
            if ( ! empty( $target_lang ) && $target_lang !== 'en' ) {
                $REST_generation = new All_Sources_Images_Generation( $this->plugin_name, $this->version );
                $translated_alt = $REST_generation->ALLSI_translate_text( $alt, 'en', $target_lang );
                if ( $translated_alt !== false && ! empty( $translated_alt ) ) {
                    ALLSI_log( array(
                        'original_alt'   => $alt,
                        'translated_alt' => $translated_alt,
                        'target_lang'    => $target_lang,
                    ), 'GUTENBERG_DOWNLOAD_ALT_TRANSLATED' );
                    $alt = $translated_alt;
                }
            }
        }
        
        // Add alt text for image
        update_post_meta( $attach_id, '_wp_attachment_image_alt', $alt );
        // Add caption text for image
        if ( !empty( $caption ) ) {
            wp_update_post( array(
                'ID'           => $attach_id,
                'post_excerpt' => $caption,
            ) );
        }
        $attach_data = wp_generate_attachment_metadata( $attach_id, $uploaded_file['file'] );
        wp_update_attachment_metadata( $attach_id, $attach_data );
        $url_media = wp_get_attachment_url( $attach_id );

        ALLSI_log( array(
            'attachment_id' => $attach_id,
            'url'           => $url_media,
            'post_id'       => $post_id,
        ), 'GUTENBERG_DOWNLOAD_SUCCESS' );

        wp_send_json_success( array(
            'url_media'     => $url_media,
            'alt_image'     => $alt,
            'caption_image' => $caption,
            'id_media'      => $attach_id,
        ) );
    }

    /**
     * Extending http timeout for wp_remote_request()
     *
     * @since    5.0.0
     */
    public function ALLSI_custom_http_request_timeout() {
        return 40;
        // 40 seconds
    }

    /**
     * Adds a settings link to the plugins page
     *
     * @since    2.0.5
     */
    public function ALLSI_add_link_parameters( $links ) {
        $settings_link = '<a href="admin.php?page=allsi-new-settings">' . esc_html__( 'Settings', 'all-sources-images' ) . '</a>';
        array_push( $links, $settings_link );
        return $links;
    }

    /**
     * Adds notice to ask for a review
     *
     * @since    5.2.6
     */






    /**
     * Upgrade plugin : options updated
     *
     * @since    6.0.0
     */
    public function ALLSI_migration() {
        // Retrieve existing options
        $optionstomove = get_option( 'ALLSI_plugin_main_settings' );
        $options_banks = get_option( 'ALLSI_plugin_banks_settings' );
        // Check if old values exist (migration needed)
        if ( $optionstomove && !isset( $optionstomove['image_block'][1] ) ) {
            // Fix 6.0.1 : Add image bank in options
            if ( isset( $options_banks['api_chosen_auto'] ) ) {
                $ar_bank_auto = array($options_banks['api_chosen_auto']);
                $default_bank = reset( $ar_bank_auto[0] );
            } else {
                $default_bank = 'pixabay';
            }
            // Move old values to the new structure
            $optionstomove['image_block'][1] = array(
                'image_location'                  => ( isset( $optionstomove['image_location'] ) ? $optionstomove['image_location'] : 'featured' ),
                'image_custom_location_placement' => ( isset( $optionstomove['image_custom_location_placement'] ) ? $optionstomove['image_custom_location_placement'] : '' ),
                'image_custom_location_position'  => ( isset( $optionstomove['image_custom_location_position'] ) ? $optionstomove['image_custom_location_position'] : '' ),
                'image_custom_location_tag'       => ( isset( $optionstomove['image_custom_location_tag'] ) ? $optionstomove['image_custom_location_tag'] : '' ),
                'image_custom_image_size'         => ( isset( $optionstomove['image_custom_image_size'] ) ? $optionstomove['image_custom_image_size'] : '' ),
                'api_chosen'                      => $default_bank,
                'based_on'                        => ( isset( $optionstomove['based_on'] ) ? $optionstomove['based_on'] : 'title' ),
                'title_selection'                 => ( isset( $optionstomove['title_selection'] ) ? $optionstomove['title_selection'] : 'full_title' ),
                'title_length'                    => ( isset( $optionstomove['title_length'] ) ? $optionstomove['title_length'] : '' ),
                'text_analyser_lang'              => ( isset( $optionstomove['text_analyser_lang'] ) ? $optionstomove['text_analyser_lang'] : '' ),
                'tags'                            => ( isset( $optionstomove['tags'] ) ? $optionstomove['tags'] : '' ),
                'categories'                      => ( isset( $optionstomove['categories'] ) ? $optionstomove['categories'] : '' ),
                'custom_field'                    => ( isset( $optionstomove['custom_field'] ) ? $optionstomove['custom_field'] : '' ),
                'custom_request'                  => ( isset( $optionstomove['custom_request'] ) ? $optionstomove['custom_request'] : '' ),
                'openai_extractor_apikey'         => ( isset( $optionstomove['openai_extractor_apikey'] ) ? $optionstomove['openai_extractor_apikey'] : '' ),
                'openai_number_of_keywords'       => ( isset( $optionstomove['openai_number_of_keywords'] ) ? $optionstomove['openai_number_of_keywords'] : '' ),
                'translation_EN'                  => ( isset( $optionstomove['translation_EN'] ) ? $optionstomove['translation_EN'] : '' ),
                'selected_image'                  => ( isset( $optionstomove['selected_image'] ) ? $optionstomove['selected_image'] : 'first_result' ),
            );
            // Remove old keys to prevent redundancy (optional)
            $keys_to_remove = array(
                'image_location',
                'image_custom_location_placement',
                'image_custom_location_position',
                'image_custom_location_tag',
                'image_custom_image_size',
                'based_on',
                'title_selection',
                'title_length',
                'text_analyser_lang',
                'tags',
                'categories',
                'custom_field',
                'custom_request',
                'openai_extractor_apikey',
                'openai_number_of_keywords',
                'translation_EN',
                'selected_image'
            );
            foreach ( $keys_to_remove as $key ) {
                unset($optionstomove[$key]);
            }
            // Save updated options
            update_option( 'ALLSI_plugin_main_settings', $optionstomove );
        } elseif ( isset( $optionstomove['image_block'][1] ) && $optionstomove && !isset( $optionstomove['image_block'][1]['api_chosen'] ) ) {
            // Fix 6.0.1 : Add image bank in options
            if ( isset( $options_banks['api_chosen_auto'] ) ) {
                $ar_bank_auto = array($options_banks['api_chosen_auto']);
                $default_bank = reset( $ar_bank_auto[0] );
            } else {
                $default_bank = 'pixabay';
            }
            // Retrieve current options
            $current_options = get_option( 'ALLSI_plugin_main_settings', array() );
            // Update only the necessary values
            $existing_block = isset( $current_options['image_block'][1] ) ? $current_options['image_block'][1] : array();
            $current_options['image_block'][1] = array_merge( 
                $existing_block,
                // Initialize as an empty array if not set
                array(
                    'api_chosen' => $default_bank,
                )
             );
            // Save the updated options
            update_option( 'ALLSI_plugin_main_settings', $current_options );
        } else {
        }
    }

    /**
     * Sanitize main settings - Merge with existing values to preserve settings from other tabs
     *
     * When saving from Image Placement tab, we don't want to lose Post-Processing settings and vice versa.
     * This callback merges the new values with existing ones based on the _saving_tab field.
     *
     * @since    6.2.0
     * @param    array    $input    Raw input from settings form
     * @return   array              Merged settings
     */
    public function ALLSI_sanitize_main_settings( $input ) {
        // Get existing settings
        $existing = get_option( 'ALLSI_plugin_main_settings', array() );
        
        // If input is empty or not an array, return existing
        if ( empty( $input ) || ! is_array( $input ) ) {
            return $existing;
        }
        
        // First, sanitize all input values
        $input = $this->ALLSI_sanitize_main_settings_values( $input );
        
        // Fields that belong to Post-Processing tab
        $post_processing_fields = array(
            'image_filename',
            'rewrite_featured',
            'image_reuse',
            'image_flip',
            'image_crop',
            'enable_alt',
            'alt_from',
            'translate_alt',
            'translate_alt_lang',
            'enable_caption',
            'caption_from',
        );
        
        // Fields that belong to Image Placement tab
        $image_placement_fields = array(
            'image_block',
            'image_custom_post_type',
        );
        
        // Check for explicit tab indicator (added as hidden field in forms)
        $saving_tab = isset( $input['_saving_tab'] ) ? $input['_saving_tab'] : null;
        
        // Remove the _saving_tab field as it's not a real setting
        unset( $input['_saving_tab'] );
        
        // Merge strategy based on which tab is being saved
        if ( $saving_tab === 'post_processing' ) {
            // Saving Post-Processing: preserve ALL Image Placement fields from existing
            foreach ( $image_placement_fields as $field ) {
                if ( isset( $existing[ $field ] ) ) {
                    $input[ $field ] = $existing[ $field ];
                }
            }
        } elseif ( $saving_tab === 'image_placement' ) {
            // Saving Image Placement: preserve ALL Post-Processing fields from existing
            foreach ( $post_processing_fields as $field ) {
                if ( isset( $existing[ $field ] ) ) {
                    $input[ $field ] = $existing[ $field ];
                }
            }
        } else {
            // Fallback: try to detect which tab based on fields present (for legacy/other forms)
            $saving_post_processing = false;
            $saving_image_placement = false;
            
            foreach ( $post_processing_fields as $field ) {
                if ( isset( $input[ $field ] ) ) {
                    $saving_post_processing = true;
                    break;
                }
            }
            
            foreach ( $image_placement_fields as $field ) {
                if ( isset( $input[ $field ] ) ) {
                    $saving_image_placement = true;
                    break;
                }
            }
            
            if ( $saving_post_processing && ! $saving_image_placement ) {
                foreach ( $image_placement_fields as $field ) {
                    if ( isset( $existing[ $field ] ) && ! isset( $input[ $field ] ) ) {
                        $input[ $field ] = $existing[ $field ];
                    }
                }
            } elseif ( $saving_image_placement && ! $saving_post_processing ) {
                foreach ( $post_processing_fields as $field ) {
                    if ( isset( $existing[ $field ] ) && ! isset( $input[ $field ] ) ) {
                        $input[ $field ] = $existing[ $field ];
                    }
                }
            }
        }
        
        // Also preserve any other fields that might exist but weren't in the form
        foreach ( $existing as $key => $value ) {
            if ( ! isset( $input[ $key ] ) ) {
                $input[ $key ] = $value;
            }
        }
        
        return $input;
    }
    
    /**
     * Sanitize main settings values recursively
     *
     * @since    6.2.0
     * @param    array    $input    Raw input array
     * @return   array              Sanitized array
     */
    private function ALLSI_sanitize_main_settings_values( $input ) {
        if ( ! is_array( $input ) ) {
            return sanitize_text_field( $input );
        }
        
        $sanitized = array();
        foreach ( $input as $key => $value ) {
            $clean_key = sanitize_key( $key );
            
            if ( is_array( $value ) ) {
                // Recursively sanitize nested arrays (like image_block)
                $sanitized[ $clean_key ] = $this->ALLSI_sanitize_main_settings_values( $value );
            } else {
                // Sanitize based on field type
                switch ( $clean_key ) {
                    case 'image_filename':
                    case 'alt_from':
                    case 'caption_from':
                    case 'image_location':
                    case 'based_on':
                    case 'selected_image':
                    case 'title_selection':
                    case 'tags':
                    case 'categories':
                    case 'api_chosen':
                    case 'api_chosen_2':
                    case 'text_analyser_lang':
                    case 'translate_alt_lang':
                    case 'ai_prompt_style':
                    case 'image_custom_location_placement':
                    case 'image_custom_location_position':
                    case 'image_custom_location_tag':
                    case 'image_custom_image_size':
                    case 'categories_level':
                        $sanitized[ $clean_key ] = sanitize_text_field( $value );
                        break;
                        
                    case 'title_length':
                    case 'openai_number_of_keywords':
                        $sanitized[ $clean_key ] = absint( $value );
                        break;
                        
                    case 'rewrite_featured':
                    case 'image_reuse':
                    case 'image_flip':
                    case 'image_crop':
                    case 'enable_alt':
                    case 'translate_alt':
                    case 'enable_caption':
                    case 'translation_en':
                        // Boolean-like values
                        $sanitized[ $clean_key ] = sanitize_text_field( $value );
                        break;
                        
                    case 'custom_field':
                    case 'custom_request':
                    case 'ai_prompt_custom_instructions':
                        $sanitized[ $clean_key ] = sanitize_text_field( $value );
                        break;
                        
                    case 'openai_extractor_apikey':
                    case 'ai_prompt_apikey':
                        // API keys - sanitize but preserve
                        $sanitized[ $clean_key ] = sanitize_text_field( $value );
                        break;
                        
                    case 'image_custom_post_type':
                        // Array of post types
                        if ( is_array( $value ) ) {
                            $sanitized[ $clean_key ] = array_map( 'sanitize_key', $value );
                        } else {
                            $sanitized[ $clean_key ] = sanitize_key( $value );
                        }
                        break;
                        
                    default:
                        $sanitized[ $clean_key ] = sanitize_text_field( $value );
                        break;
                }
            }
        }
        
        return $sanitized;
    }

    /**
     * Sanitize auto image settings.
     *
     * @since    6.2.0
     * @param    array $input Raw input from settings form.
     * @return   array
     */
    public function ALLSI_sanitize_auto_image_settings( $input ) {
        $defaults = $this->ALLSI_default_options_auto_image_settings( true );

        if ( ! is_array( $input ) ) {
            return $defaults;
        }

        $sanitized = $defaults;

        $sanitized['enabled'] = ( isset( $input['enabled'] ) && 'enable' === sanitize_text_field( $input['enabled'] ) )
            ? 'enable'
            : 'disable';

        $post_types_input = array();
        if ( isset( $input['post_types'] ) ) {
            $post_types_input = is_array( $input['post_types'] ) ? $input['post_types'] : array( $input['post_types'] );
        }

        $post_types = array_values( array_unique( array_filter( array_map( 'sanitize_key', $post_types_input ) ) ) );
        if ( ! empty( $post_types ) ) {
            $public_post_types = get_post_types( array( 'public' => true ), 'names' );
            $post_types = array_values( array_intersect( $post_types, $public_post_types ) );
        }
        $sanitized['post_types'] = $post_types;

        $term_ids_input = array();
        if ( isset( $input['term_ids'] ) ) {
            $term_ids_input = is_array( $input['term_ids'] ) ? $input['term_ids'] : array( $input['term_ids'] );
        }
        $sanitized['term_ids'] = array_values( array_unique( array_filter( array_map( 'absint', $term_ids_input ) ) ) );

        $exclude_ids_raw = isset( $input['exclude_post_ids'] ) ? sanitize_text_field( $input['exclude_post_ids'] ) : '';
        $exclude_ids = $this->ALLSI_auto_image_parse_ids( $exclude_ids_raw );
        $sanitized['exclude_post_ids'] = ! empty( $exclude_ids ) ? implode( ',', $exclude_ids ) : '';

        return $sanitized;
    }

    /**
     * Sanitize banks settings - Sanitize all fields and remove 'envato' if present (no longer working)
     *
     * @since    4.2.0
     */
    public function ALLSI_sanitize_banks_settings( $input ) {
        if ( ! is_array( $input ) ) {
            return array();
        }
        
        $sanitized = array();
        
        // Recursively sanitize the input array
        foreach ( $input as $key => $value ) {
            $clean_key = sanitize_key( $key );
            if ( is_array( $value ) ) {
                // For nested arrays (like bank-specific settings)
                $sanitized[ $clean_key ] = $this->ALLSI_sanitize_banks_array_recursive( $value );
            } else {
                // Sanitize based on expected field types
                $sanitized[ $clean_key ] = $this->ALLSI_sanitize_bank_field( $clean_key, $value );
            }
        }
        
        // Remove 'envato' from api_chosen_auto if present
        if ( isset( $sanitized['api_chosen_auto'] ) && is_array( $sanitized['api_chosen_auto'] ) ) {
            unset( $sanitized['api_chosen_auto']['envato'] );
        }
        // Remove 'envato' from api_chosen_manual if present
        if ( isset( $sanitized['api_chosen_manual'] ) && is_array( $sanitized['api_chosen_manual'] ) ) {
            unset( $sanitized['api_chosen_manual']['envato'] );
        }
        // Clear api_chosen if it's 'envato'
        if ( isset( $sanitized['api_chosen'] ) && 'envato' === $sanitized['api_chosen'] ) {
            $sanitized['api_chosen'] = '';
        }
        
        return $sanitized;
    }
    
    /**
     * Recursively sanitize banks array
     *
     * @since    6.2.0
     * @param    array    $array    Array to sanitize
     * @return   array              Sanitized array
     */
    private function ALLSI_sanitize_banks_array_recursive( $array ) {
        if ( ! is_array( $array ) ) {
            return sanitize_text_field( $array );
        }
        
        $sanitized = array();
        foreach ( $array as $key => $value ) {
            $clean_key = sanitize_key( $key );
            if ( is_array( $value ) ) {
                $sanitized[ $clean_key ] = $this->ALLSI_sanitize_banks_array_recursive( $value );
            } else {
                $sanitized[ $clean_key ] = $this->ALLSI_sanitize_bank_field( $clean_key, $value );
            }
        }
        return $sanitized;
    }
    
    /**
     * Sanitize individual bank field based on field name
     *
     * @since    6.2.0
     * @param    string   $key     Field key
     * @param    mixed    $value   Field value
     * @return   mixed             Sanitized value
     */
    private function ALLSI_sanitize_bank_field( $key, $value ) {
        // API keys, tokens, and account IDs - sanitize as text but preserve case
        if ( strpos( $key, 'apikey' ) !== false || strpos( $key, 'api_key' ) !== false || 
             strpos( $key, 'token' ) !== false || strpos( $key, 'secret' ) !== false ||
             strpos( $key, 'account_id' ) !== false || strpos( $key, 'cxid' ) !== false ) {
            return sanitize_text_field( $value );
        }
        
        // URLs
        if ( strpos( $key, 'url' ) !== false || strpos( $key, 'endpoint' ) !== false ) {
            return esc_url_raw( $value );
        }
        
        // Specific image size fields that should be text (like 1024x1024)
        if ( 'imgsize' === $key || 'image_size' === $key ) {
            return sanitize_text_field( $value );
        }
        
        // Numeric fields (but exclude imgsize which is handled above)
        if ( strpos( $key, 'max_results' ) !== false || strpos( $key, 'count' ) !== false ||
             strpos( $key, 'width' ) !== false || strpos( $key, 'height' ) !== false ||
             strpos( $key, 'min_width' ) !== false || strpos( $key, 'min_height' ) !== false ||
             strpos( $key, 'steps' ) !== false || strpos( $key, 'seed' ) !== false ||
             strpos( $key, 'limit' ) !== false ) {
            return absint( $value );
        }
        
        // Boolean-like fields
        if ( in_array( $value, array( 'true', 'false', '1', '0', 'yes', 'no', 'on', 'off' ), true ) ) {
            return sanitize_text_field( $value );
        }
        
        // Default: sanitize as text field
        return sanitize_text_field( $value );
    }

    /**
     * Sanitize cron settings and schedule/unschedule WordPress cron events
     *
     * @since    6.1.7
     * @param    array    $input    Raw input from settings form
     * @return   array             Sanitized settings
     */
    public function ALLSI_sanitize_cron_settings( $input ) {
        if ( ! is_array( $input ) ) {
            return array();
        }
        
        // Sanitize input values first
        $sanitized = array();
        foreach ( $input as $key => $value ) {
            $clean_key = sanitize_key( $key );
            switch ( $clean_key ) {
                case 'enable_cron':
                    $allowed = array( 'enable', 'disable' );
                    $sanitized[ $clean_key ] = in_array( $value, $allowed, true ) ? $value : 'disable';
                    break;
                case 'cron_interval_value':
                    $sanitized[ $clean_key ] = absint( $value );
                    break;
                case 'cron_interval_unit':
                    $allowed = array( 'minutes', 'hours', 'days' );
                    $sanitized[ $clean_key ] = in_array( $value, $allowed, true ) ? $value : 'minutes';
                    break;
                case 'cron_post_types':
                    if ( is_array( $value ) ) {
                        $sanitized[ $clean_key ] = array_map( 'sanitize_key', $value );
                    } else {
                        $sanitized[ $clean_key ] = array();
                    }
                    break;
                default:
                    $sanitized[ $clean_key ] = sanitize_text_field( $value );
                    break;
            }
        }
        
        // Clear existing scheduled event
        $timestamp = wp_next_scheduled( 'ALLSI_cron_image_generation' );
        if ( $timestamp ) {
            wp_unschedule_event( $timestamp, 'ALLSI_cron_image_generation' );
        }

        // If cron is enabled, schedule new event
        if ( isset( $sanitized['enable_cron'] ) && $sanitized['enable_cron'] === 'enable' ) {
            // Convert interval to seconds
            $interval_value = isset( $sanitized['cron_interval_value'] ) ? $sanitized['cron_interval_value'] : 5;
            $interval_unit = isset( $sanitized['cron_interval_unit'] ) ? $sanitized['cron_interval_unit'] : 'minutes';
            
            $seconds = 0;
            switch ( $interval_unit ) {
                case 'minutes':
                    $seconds = $interval_value * 60;
                    break;
                case 'hours':
                    $seconds = $interval_value * 3600;
                    break;
                case 'days':
                    $seconds = $interval_value * 86400;
                    break;
            }
            
            // Create custom interval if needed
            $recurrence_key = 'ALLSI_cron_' . $interval_value . '_' . $interval_unit;
            
            // Add custom interval to WordPress cron schedules
            add_filter( 'cron_schedules', function( $schedules ) use ( $recurrence_key, $seconds, $interval_value, $interval_unit ) {
                $schedules[$recurrence_key] = array(
                    'interval' => $seconds,
                    /* translators: 1: interval number, 2: interval unit (minutes, hours, days) */
                    'display'  => sprintf( __( 'Every %1$d %2$s', 'all-sources-images' ), $interval_value, $interval_unit ),
                );
                return $schedules;
            } );
            
            // Schedule the event
            if ( ! wp_next_scheduled( 'ALLSI_cron_image_generation' ) ) {
                wp_schedule_event( time(), $recurrence_key, 'ALLSI_cron_image_generation' );
            }
        }
        
        return $sanitized;
    }

    /**
     * Sanitize block settings
     *
     * @since    6.2.0
     * @param    array    $input    Raw input from settings form
     * @return   array             Sanitized settings
     */
    public function ALLSI_sanitize_block_settings( $input ) {
        if ( ! is_array( $input ) ) {
            return array();
        }
        
        $sanitized = array();
        foreach ( $input as $key => $value ) {
            if ( is_array( $value ) ) {
                $sanitized[ sanitize_key( $key ) ] = array_map( 'sanitize_text_field', $value );
            } else {
                $sanitized[ sanitize_key( $key ) ] = sanitize_text_field( $value );
            }
        }
        
        return $sanitized;
    }

    /**
     * Sanitize interval settings
     *
     * @since    6.2.0
     * @param    array    $input    Raw input from settings form
     * @return   array             Sanitized settings
     */
    public function ALLSI_sanitize_interval_settings( $input ) {
        if ( ! is_array( $input ) ) {
            return array();
        }
        
        $sanitized = array();
        foreach ( $input as $key => $value ) {
            $sanitized[ sanitize_key( $key ) ] = is_numeric( $value ) ? absint( $value ) : sanitize_text_field( $value );
        }
        
        return $sanitized;
    }

    /**
     * Sanitize rights/permissions settings
     *
     * @since    6.2.0
     * @param    array    $input    Raw input from settings form
     * @return   array             Sanitized settings
     */
    public function ALLSI_sanitize_rights_settings( $input ) {
        if ( ! is_array( $input ) ) {
            return array();
        }
        
        $sanitized = array();
        foreach ( $input as $key => $value ) {
            if ( is_array( $value ) ) {
                // For role arrays, sanitize each role name
                $sanitized[ sanitize_key( $key ) ] = array_map( 'sanitize_key', $value );
            } else {
                $sanitized[ sanitize_key( $key ) ] = sanitize_text_field( $value );
            }
        }
        
        return $sanitized;
    }

    /**
     * Sanitize proxy settings
     *
     * @since    6.2.0
     * @param    array    $input    Raw input from settings form
     * @return   array             Sanitized settings
     */
    public function ALLSI_sanitize_proxy_settings( $input ) {
        if ( ! is_array( $input ) ) {
            return array();
        }

        $sanitized = array();
        $allowed_proxy_modes = array( 'legacy', 'cloudflare', 'disabled' );
        $allowed_enable_values = array( 'enable', 'disable' );

        foreach ( $input as $key => $value ) {
            switch ( $key ) {
                case 'enable_proxy':
                    $enable_value = sanitize_text_field( $value );
                    $sanitized['enable_proxy'] = in_array( $enable_value, $allowed_enable_values, true ) ? $enable_value : 'disable';
                    break;
                case 'proxy_mode':
                    $mode_value = sanitize_text_field( $value );
                    $sanitized['proxy_mode'] = in_array( $mode_value, $allowed_proxy_modes, true ) ? $mode_value : 'legacy';
                    break;
                case 'proxy_address':
                    $address = sanitize_text_field( $value );
                    $sanitized['proxy_address'] = preg_replace( '#^https?://#i', '', trim( $address ) );
                    break;
                case 'proxy_port':
                    $port = absint( $value );
                    $sanitized['proxy_port'] = ( $port >= 1 && $port <= 65535 ) ? $port : 80;
                    break;
                case 'proxy_username':
                    $sanitized['proxy_username'] = sanitize_text_field( $value );
                    break;
                case 'proxy_password':
                    $sanitized['proxy_password'] = sanitize_text_field( $value );
                    break;
                case 'cloudflare_worker_url':
                    $sanitized['cloudflare_worker_url'] = esc_url_raw( $value );
                    break;
                case 'cloudflare_token':
                    $sanitized['cloudflare_token'] = sanitize_text_field( $value );
                    break;
            }
        }

        return $sanitized;
    }

    /**
     * Sanitize logs settings
     *
     * @since    6.2.0
     * @param    array    $input    Raw input from settings form
     * @return   array             Sanitized settings
     */
    public function ALLSI_sanitize_logs_settings( $input ) {
        if ( ! is_array( $input ) ) {
            return array();
        }
        
        $sanitized = array();
        foreach ( $input as $key => $value ) {
            switch ( $key ) {
                case 'log_level':
                    $allowed_levels = array( 'debug', 'info', 'warning', 'error', 'none' );
                    $sanitized[ $key ] = in_array( $value, $allowed_levels, true ) ? $value : 'info';
                    break;
                case 'log_retention_days':
                    $sanitized[ $key ] = absint( $value );
                    break;
                default:
                    $sanitized[ sanitize_key( $key ) ] = sanitize_text_field( $value );
                    break;
            }
        }
        
        return $sanitized;
    }

}
