<?php
/**
 * Config Endpoint for BoostBot REST API.
 *
 * @package BoostBot
 */

// Prevent direct access to the file.
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if (!defined('ABSPATH')) {
    exit;
}

/**
 * BoostBot_Config_Endpoint class.
 *
 * Provides public configuration data for the frontend widget.
 *
 * @since 1.0.0
 */
class BoostBot_Config_Endpoint extends BoostBot_Base_Endpoint
{
    /**
     * The base path for this endpoint.
     *
     * @since 1.0.0
     * @access protected
     * @var string $rest_base
     */
    protected $rest_base = 'config';

    /**
     * Register the routes for this endpoint.
     *
     * @since 1.0.0
     */
    public function register()
    {
        // GET /wp-json/boostbot/v1/config
        $this->register_route(
            '',
            'GET',
            [$this, 'get_public_config'],
            array( $this, 'public_with_api_rate_limit' ) // Passive bootstrap read
        );

        // GET /wp-json/boostbot/v1/config/debug - Debug endpoint (admin only)
        $this->register_route(
            '/debug',
            'GET',
            [$this, 'debug_claude'],
            [$this, 'admin_access']
        );

        // GET /wp-json/boostbot/v1/config/debug-bot - Debug bot resolution (public for testing)
        $this->register_route(
            '/debug-bot',
            'GET',
            [$this, 'debug_bot_resolution'],
            [$this, 'admin_access']
        );

        // GET /wp-json/boostbot/v1/config/debug-widget - Debug widget loading
        $this->register_route(
            '/debug-widget',
            'GET',
            [$this, 'debug_widget_loading'],
            [$this, 'admin_access']
        );

        // POST /wp-json/boostbot/v1/config/fix-defaults - Fix multiple default bots
        $this->register_route(
            '/fix-defaults',
            'POST',
            [$this, 'fix_default_bots'],
            [$this, 'admin_access']
        );

        // POST /wp-json/boostbot/v1/config/create-test-product - Create test headphones product
        $this->register_route(
            '/create-test-product',
            'POST',
            [$this, 'create_test_product'],
            [$this, 'admin_access']
        );

        // GET /wp-json/boostbot/v1/config/products - List products for debugging
        $this->register_route(
            '/products',
            'GET',
            [$this, 'list_products'],
            [$this, 'admin_access']
        );

        // POST /wp-json/boostbot/v1/config/add-product-image - Add image to product
        $this->register_route(
            '/add-product-image',
            'POST',
            [$this, 'add_product_image'],
            [$this, 'admin_access']
        );

        // GET /wp-json/boostbot/v1/config/connection-status - Backend connection health check
        $this->register_route(
            '/connection-status',
            'GET',
            array( $this, 'connection_status' ),
            array( $this, 'admin_access' )
        );

        // POST /wp-json/boostbot/v1/config/repair-connection - Attempt to repair backend connection
        $this->register_route(
            '/repair-connection',
            'POST',
            array( $this, 'repair_connection' ),
            array( $this, 'admin_access' )
        );
    }

    /**
     * Fix multiple default bots - keep only the first one.
     */
    public function fix_default_bots($request)
    {
        global $wpdb;
        $table = self::sanitize_identifier( $wpdb->prefix . 'boostbot_bots' );

        // Get all default bots
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().
        $defaults = $wpdb->get_results( $wpdb->prepare( "SELECT id, bot_name FROM {$table} WHERE is_default = %d ORDER BY id ASC", 1 ) );
        
        if (count($defaults) <= 1) {
            return $this->success([
                'message' => 'No fix needed - only one default bot exists',
                'default_bots' => $defaults,
            ]);
        }

        // Keep only the first one as default
        $keep_id = $defaults[0]->id;
        $fixed = [];

        foreach ($defaults as $bot) {
            if ($bot->id != $keep_id) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier sanitized via sanitize_identifier(); values bind via prepare().
                $wpdb->update($table, ['is_default' => 0], ['id' => $bot->id], ['%d'], ['%d']);
                $fixed[] = $bot->bot_name;
            }
        }

        return $this->success([
            'message' => 'Fixed! Only one default bot now.',
            'kept_default' => $defaults[0]->bot_name,
            'removed_default_from' => $fixed,
        ]);
    }

    /**
     * Debug widget loading issues.
     */
    public function debug_widget_loading($request)
    {
        $license_manager = new BoostBot_License_Manager();
        
        $debug = [
            'is_pro' => $license_manager->is_pro(),
            'dev_mode' => $license_manager->is_dev_mode(),
            'license_status' => $license_manager->get_license_status(),
            'is_active_setting' => BoostBot_Settings::get('is_active', true),
            'boostbot_dev_mode_pro_option' => get_option('boostbot_dev_mode_pro', 'NOT_SET'),
            'show_on_mobile' => BoostBot_Settings::get('show_on_mobile', true),
            'hidden_pages' => BoostBot_Settings::get('hidden_pages', []),
        ];

        return $this->success($debug);
    }

    /* ──────────────────────────────────────────────────────────────────── */
    /*  Backend Connection Health & Repair                                  */
    /* ──────────────────────────────────────────────────────────────────── */

    /**
     * GET /config/connection-status — Check backend connection health.
     *
     * Returns a detailed diagnostic of the backend connection state:
     * - Whether site_id and site_api_key are present
     * - Whether the backend is reachable
     * - Whether authentication succeeds
     * - Current credit balance
     *
     * @since 1.0.4
     */
    public function connection_status( $request ) {
        $result = array(
            'backend_base_url'     => trim( (string) BoostBot_Settings::get( 'backend_base_url', '' ) ),
            'site_id_present'      => '' !== trim( (string) BoostBot_Settings::get( 'backend_site_id', '' ) ),
            'site_id'              => trim( (string) BoostBot_Settings::get( 'backend_site_id', '' ) ),
            'api_key_present'      => '' !== trim( (string) BoostBot_Settings::get( 'backend_site_api_key', '' ) ),
            'backend_reachable'    => false,
            'auth_ok'              => false,
            'credits'              => null,
            'error'                => null,
        );

        if ( empty( $result['backend_base_url'] ) ) {
            $result['error'] = 'Backend base URL is not configured.';
            return $this->success( $result );
        }

        if ( ! $result['site_id_present'] ) {
            $result['error'] = 'Site is not registered with the backend yet.';
            return $this->success( $result );
        }

        if ( ! $result['api_key_present'] ) {
            $result['error'] = 'Site API key is missing. The site is registered but cannot authenticate. Use "Repair Connection" to fix.';
            return $this->success( $result );
        }

        // Try to fetch credits status to verify the full chain works.
        if ( ! class_exists( 'BoostBot_Credits_Backend_Client' ) ) {
            $result['error'] = 'Credits backend client class not available.';
            return $this->success( $result );
        }

        $client  = new BoostBot_Credits_Backend_Client();
        $site_id = $client->ensure_site_id();
        if ( is_wp_error( $site_id ) ) {
            $result['error'] = 'ensure_site_id() failed: ' . $site_id->get_error_message();
            return $this->success( $result );
        }

        $status = $client->get_status( (string) $site_id );
        if ( is_wp_error( $status ) ) {
            $result['backend_reachable'] = true; // Could connect but got an error.
            $result['error'] = 'get_status() failed: ' . $status->get_error_message();
            return $this->success( $result );
        }

        $result['backend_reachable'] = true;
        $result['auth_ok']           = true;
        $result['credits']           = $status['boost_credits'] ?? null;

        // Clear the connection error transient since everything works.
        delete_transient( 'boostbot_backend_key_missing' );

        return $this->success( $result );
    }

    /**
     * POST /config/repair-connection — Attempt to repair the backend connection.
     *
     * Strategy:
     * 1. If no site_id → fresh registration (normal flow).
     * 2. If site_id exists but key is missing → clear site_id and re-register.
     *    The backend will treat this as a new registration if the domain record
     *    was cleaned up, or return the existing site_id without a key if still registered.
     * 3. If both exist → test the connection. If auth fails, clear both and re-register.
     *
     * @since 1.0.4
     */
    public function repair_connection( $request ) {
        $site_id  = trim( (string) BoostBot_Settings::get( 'backend_site_id', '' ) );
        $site_key = trim( (string) BoostBot_Settings::get( 'backend_site_api_key', '' ) );

        // Step 1: If both exist, test if connection actually works.
        if ( '' !== $site_id && '' !== $site_key && class_exists( 'BoostBot_Credits_Backend_Client' ) ) {
            $client = new BoostBot_Credits_Backend_Client();
            $status = $client->get_status( $site_id );
            if ( ! is_wp_error( $status ) ) {
                // Connection works! Clear any error transients.
                delete_transient( 'boostbot_backend_key_missing' );
                return $this->success( array(
                    'repaired' => false,
                    'message'  => 'Connection is already working. No repair needed.',
                    'credits'  => $status['boost_credits'] ?? null,
                ) );
            }
            // Auth failed — clear credentials and re-register.
            BoostBot_Logger::warning( 'Repair: existing credentials failed auth test. Clearing and re-registering.' );
        }

        // Step 2: Clear stored credentials.
        BoostBot_Settings::set( 'backend_site_id', '' );
        BoostBot_Settings::set( 'backend_site_api_key', '' );

        // Step 3: Fresh registration via ensure_site_id().
        if ( ! class_exists( 'BoostBot_Credits_Backend_Client' ) ) {
            return $this->error( 'Credits backend client is not available.', 500 );
        }

        $client      = new BoostBot_Credits_Backend_Client();
        $new_site_id = $client->ensure_site_id();

        if ( is_wp_error( $new_site_id ) ) {
            $error_code = $new_site_id->get_error_code();

            // Special case: domain is already registered on the backend but we can't get a new key.
            if ( 'boostbot_backend_missing_site_key' === $error_code ) {
                return $this->error(
                    'This domain is already registered on the BoostBot backend but the API key cannot be re-issued automatically. '
                    . 'Please contact BoostBot support to rotate the site API key.',
                    503
                );
            }

            return $this->error( 'Repair failed: ' . $new_site_id->get_error_message(), 500 );
        }

        // Verify the repair worked.
        $verify_key = trim( (string) BoostBot_Settings::get( 'backend_site_api_key', '' ) );
        if ( '' === $verify_key ) {
            return $this->error( 'Registration succeeded but API key was not saved. Please check server disk space and database.', 500 );
        }

        // Clear error transients.
        delete_transient( 'boostbot_backend_key_missing' );
        delete_transient( 'boostbot_connection_error_dismissed' );

        return $this->success( array(
            'repaired'    => true,
            'message'     => 'Connection repaired successfully. New site registered.',
            'new_site_id' => $new_site_id,
        ) );
    }

    /**
     * Debug bot resolution for a specific page.
     *
     * @param WP_REST_Request $request The request.
     * @return WP_REST_Response Debug info.
     */
    public function debug_bot_resolution($request)
    {
        $post_id  = $request->get_param('post_id');
        $page_url = $request->get_param('page_url');

        $debug = [
            'request' => [
                'post_id'  => $post_id,
                'page_url' => $page_url,
            ],
        ];

        // Build context
        $resolver = new BoostBot_Bot_Resolver();
        $context = $resolver->build_context_from_request($post_id, $page_url);
        $debug['context'] = $context;

        // Get all active bots
        $model = new BoostBot_Bot_Model();
        $active_bots = $model->get_active();
        $debug['active_bots_count'] = count($active_bots);
        $debug['active_bots'] = array_map(function($bot) {
            return [
                'id' => $bot->id,
                'name' => $bot->bot_name,
                'is_default' => $bot->is_default,
                'priority' => $bot->priority,
                'display_conditions' => $bot->display_conditions,
            ];
        }, $active_bots);

        // Try to resolve
        $resolved_bot = $resolver->resolve($context);
        $debug['resolved_bot'] = $resolved_bot ? [
            'id' => $resolved_bot->id,
            'name' => $resolved_bot->bot_name,
            'is_default' => $resolved_bot->is_default,
        ] : null;

        // Check each bot's conditions
        $debug['condition_checks'] = [];
        foreach ($active_bots as $bot) {
            $matches = $resolver->bot_matches_context($bot, $context);
            $debug['condition_checks'][] = [
                'bot_id' => $bot->id,
                'bot_name' => $bot->bot_name,
                'matches' => $matches,
                'conditions' => $bot->display_conditions,
            ];
        }

        // Check general settings
        $license_manager = new BoostBot_License_Manager();
        $debug['settings'] = [
            'is_active' => BoostBot_Settings::get('is_active'),
            'license_status' => $license_manager->get_license_status(),
            'is_pro' => $license_manager->is_pro(),
            'dev_mode' => $license_manager->is_dev_mode(),
        ];

        return $this->success($debug);
    }

    /**
     * Debug Claude API connection.
     *
     * @param WP_REST_Request $request The request.
     * @return WP_REST_Response Debug info.
     */
    public function debug_claude($request)
    {
        $debug = [];

        // BYOK fields are legacy; keep visibility for troubleshooting migrations.
        $legacy_api_key = (string) BoostBot_Settings::get('api_key');
        $debug['legacy_api_key_exists'] = ! empty( $legacy_api_key );
        $debug['legacy_api_key_length'] = strlen( $legacy_api_key );

        $debug['backend_base_url_configured'] = ! empty( trim( (string) BoostBot_Settings::get( 'backend_base_url', '' ) ) );

        // Try backend AI client (credits-aware) instead of direct BYOK client.
        try {
            if ( ! class_exists( 'BoostBot_Backend_AI_Client' ) ) {
                throw new Exception( 'Backend AI client class not found.' );
            }
            if ( ! class_exists( 'BoostBot_Credits_Backend_Client' ) ) {
                throw new Exception( 'Credits backend client class not found.' );
            }

            $credits_client = new BoostBot_Credits_Backend_Client();
            $site_id = $credits_client->ensure_site_id();
            $debug['site_id_resolved'] = ! is_wp_error( $site_id );
            if ( is_wp_error( $site_id ) ) {
                throw new Exception( $site_id->get_error_message() );
            }
            $debug['site_id'] = (string) $site_id;

            $client = new BoostBot_Backend_AI_Client();
            $debug['client_created'] = true;

            // Try a simple API call
            $response = $client->send_message(
                [['role' => 'user', 'content' => 'Hello']],
                ['max_tokens' => 50]
            );

            $debug['api_call_success'] = true;
            $debug['response_preview'] = substr( (string) ( $response['content'] ?? '' ), 0, 100 );
            $debug['provider'] = $response['provider'] ?? null;
            $debug['model'] = $response['model'] ?? null;

        } catch (Exception $e) {
            $debug['client_created'] = false;
            $debug['error'] = $e->getMessage();
            $debug['error_code'] = $e->getCode();
        }

        return $this->success($debug);
    }

    /**
     * Get public configuration for the chatbot widget.
     *
     * @since 1.0.0
     * @param WP_REST_Request $request The REST request object.
     * @return WP_REST_Response The response with public config.
     */
    public function get_public_config($request)
    {
        BoostBot_Logger::debug('Config endpoint called');

        // Get page context from request
        $post_id  = $request->get_param( 'post_id' );
        $page_url = $request->get_param( 'page_url' );
        
        // Resolve the appropriate bot for this context
        $resolver    = new BoostBot_Bot_Resolver();
        $context     = $resolver->build_context_from_request( $post_id, $page_url );
        $bot         = $resolver->resolve( $context );

        // Get general settings (for fallback)
        $settings = $this->get_public_safe_settings();

        // Get license status
        $license_status = 'free';
        if (class_exists('BoostBot_License_Manager')) {
            $license_manager = new BoostBot_License_Manager();
            $license_status = $license_manager->get_license_status();
        }

        // Build response - use bot config if available, otherwise use general settings
        if ( $bot ) {
            $bot_config = $resolver->get_bot_config( $bot );
            
            // Get bot's actual widget types (NEVER use global settings)
            $bot_widget_types = $this->get_bot_widget_types( $bot );
            
            $config_language = $bot->language ?? ( $context['language'] ?? BoostBot_Language::get_primary() );
            $config_locale   = BoostBot_Language_Manager::lang_to_locale( $config_language, get_locale() );

            $config = [
                'bot_id'          => $bot_config['id'],
                'bot_name'        => $bot_config['name'],
                'show_welcome_message' => (bool) $bot_config['show_welcome_message'],
                'welcome_message' => $bot_config['welcome_message'] ?: ( BoostBot_Settings::get_for_language( 'global_default_welcome_text', $bot->language ?? '', '' ) ?: $settings['welcome_message'] ),
                'appearance'      => [
                    'primary_color' => $bot_config['primary_color'] ?: $settings['primary_color'],
                    'accent_color'  => $settings['accent_color'],
                    'position'      => $bot_config['position'] ?: $settings['position'],
                    'avatar_url'    => $bot_config['bubble_icon'] ?: ($settings['avatar_url'] ?? null),
                ],
                'behavior'        => [
                    'show_on_mobile'  => $settings['show_on_mobile'] ?? true,
                    'auto_open_delay' => $settings['auto_open_delay'] ?? 0,
                    'show_on_pages'   => 'all', // Bot already resolved based on conditions
                ],
                // Mobile layout settings (vw/vh units for responsive chat window)
                // Note: side margin is auto-calculated from width on frontend: (100 - width) / 2
                'mobile_layout'   => [
                    'width_vw'          => $settings['mobile_layout_width_vw'] ?? 94,
                    'height_vh'         => $settings['mobile_layout_height_vh'] ?? 75,
                    'margin_top_vh'     => $settings['mobile_layout_margin_top_vh'] ?? 20,
                    'margin_bottom_vh'  => $settings['mobile_layout_margin_bottom_vh'] ?? 5,
                ],
                'bot_rules'       => $bot_config['rules'],
                'forbidden_topics'=> $bot_config['forbidden_topics'],
                'custom_instructions' => $bot_config['custom_instructions'],
                'widgets_enabled' => $bot_widget_types,  // Use bot's actual widgets, not global
                'language'        => $config_language,
                'is_rtl'          => BoostBot_Language_Manager::is_rtl_language( $config_language ),
                'locale'          => $config_locale,
                'direction_mode'  => $settings['direction_mode'] ?? 'auto',
                'is_active'       => true, // Bot is active if resolved
                'is_test_mode'    => ! empty( $bot->test_mode ) && (int) $bot->test_mode === 1,
                'license_status'  => $license_status,
                'is_pro'          => $license_status === 'pro',
            ];

            // Week 28: caps (global + effective per-bot)
            $input_cap_enabled = (bool) BoostBot_Settings::get( 'visitor_input_char_limit_enabled', true );
            $input_cap         = absint( BoostBot_Settings::get( 'visitor_input_char_limit', 500 ) );
            $output_cap_global = absint( BoostBot_Settings::get( 'bot_output_char_limit_global', 150 ) );
            $output_cap_bot    = isset( $bot_config['bot_output_char_limit'] ) ? absint( $bot_config['bot_output_char_limit'] ) : 0;
            $effective_output  = $output_cap_global;
            if ( $output_cap_bot > 0 ) {
                $effective_output = min( $effective_output, $output_cap_bot );
            }

            $config['visitor_input_char_limit_enabled'] = $input_cap_enabled;
            $config['visitor_input_char_limit']         = $input_cap > 0 ? $input_cap : 500;
            $config['bot_output_char_limit_global']     = $output_cap_global > 0 ? $output_cap_global : 150;
            $config['bot_output_char_limit']            = $effective_output > 0 ? $effective_output : 150;
            
            // Add appearance preset if available
            if ( ! empty( $bot_config['appearance_preset'] ) ) {
                $config['appearance_preset'] = $bot_config['appearance_preset'];
                
                // Get system global values (always needed as fallback for 'inherit' values)
                $system_global_values = get_option( 'boostbot_appearance_global_values', array() );
                
                // Get global values - use preset's global_values if available, otherwise fallback to system global values
                $gv = null;
                if ( ! empty( $bot_config['appearance_preset']['global_values'] ) ) {
                    $gv = $bot_config['appearance_preset']['global_values'];
                } elseif ( ! empty( $system_global_values ) ) {
                    $gv = $system_global_values;
                    // Also add to appearance_preset for frontend consistency
                    $config['appearance_preset']['global_values'] = $system_global_values;
                }
                
                // Merge global values into appearance
                if ( ! empty( $gv ) ) {
                    
                    // Apply colors
                    if ( ! empty( $gv['colors']['primary'] ) ) {
                        $config['appearance']['primary_color'] = $gv['colors']['primary'];
                    }
                    if ( ! empty( $gv['colors']['secondary'] ) ) {
                        $config['appearance']['secondary_color'] = $gv['colors']['secondary'];
                    }
                    if ( ! empty( $gv['colors']['accent'] ) ) {
                        $config['appearance']['accent_color'] = $gv['colors']['accent'];
                    }
                    if ( ! empty( $gv['colors']['background'] ) ) {
                        $config['appearance']['background_color'] = $gv['colors']['background'];
                    }
                    if ( ! empty( $gv['colors']['text'] ) ) {
                        $config['appearance']['text_color'] = $gv['colors']['text'];
                    }
                    
                    // Apply typography - resolve 'inherit' from system global values
                    $font_family = ! empty( $gv['typography']['fontFamily'] ) ? $gv['typography']['fontFamily'] : null;
                    
                    // If fontFamily is 'inherit', get the actual font from system global values
                    if ( $font_family === 'inherit' && ! empty( $system_global_values['typography']['fontFamily'] ) ) {
                        $font_family = $system_global_values['typography']['fontFamily'];
                    }
                    
                    if ( $font_family && $font_family !== 'inherit' ) {
                        $config['appearance']['font_family'] = $font_family;
                        
                        // Check if this is an Elementor custom font and get its CSS
                        $custom_font_css = $this->get_elementor_custom_font_css( $font_family );
                        if ( $custom_font_css ) {
                            $config['custom_font_css'] = $custom_font_css;
                        }
                    }
                }
                
                // Fallback: Check legacy font_family field if no typography in global_values
                if ( empty( $config['appearance']['font_family'] ) && ! empty( $bot_config['appearance_preset']['font_family'] ) ) {
                    $legacy_font = $bot_config['appearance_preset']['font_family'];
                    if ( $legacy_font && $legacy_font !== 'inherit' ) {
                        $config['appearance']['font_family'] = $legacy_font;
                        
                        // Check if this is an Elementor custom font and get its CSS
                        $custom_font_css = $this->get_elementor_custom_font_css( $legacy_font );
                        if ( $custom_font_css ) {
                            $config['custom_font_css'] = $custom_font_css;
                        }
                    }
                }
                
                // Add element settings
                if ( ! empty( $bot_config['appearance_preset']['element_settings'] ) ) {
                    $config['element_settings'] = $bot_config['appearance_preset']['element_settings'];
                    
                    // Override header title with bot name (not preset name)
                    if ( ! empty( $bot_config['name'] ) ) {
                        if ( ! isset( $config['element_settings']['header'] ) ) {
                            $config['element_settings']['header'] = array();
                        }
                        $config['element_settings']['header']['title'] = $bot_config['name'];
                    }
                }
            }
            
            // Apply bot-specific overrides if any
            if ( ! empty( $bot_config['appearance_overrides'] ) ) {
                $config['appearance_overrides'] = $bot_config['appearance_overrides'];
            }

            // Add opening screen data if bot has an opening screen preset
            if ( ! empty( $bot->opening_screen_preset_id ) && class_exists( 'BoostBot_Opening_Screen_Preset_Model' ) ) {
                try {
                    $opening_preset_model = new BoostBot_Opening_Screen_Preset_Model();
                    $opening_preset = $opening_preset_model->get( $bot->opening_screen_preset_id );
                    
                    if ( $opening_preset ) {
                        $opening_preset = $opening_preset_model->parse_json_fields( $opening_preset );

                        // Bot-level overrides (if present) take precedence over preset values.
                        $opening_overrides = isset( $bot->opening_screen_overrides ) ? $bot->opening_screen_overrides : null;
                        if ( is_string( $opening_overrides ) ) {
                            $opening_overrides = json_decode( $opening_overrides, true );
                        }
                        if ( ! is_array( $opening_overrides ) ) {
                            $opening_overrides = array();
                        }

                        $config['opening_screen'] = array(
                            'has_opening_screen' => true,
                            'preset_id'          => $opening_preset->id,
                            'mode'               => isset( $opening_overrides['mode'] ) ? $opening_overrides['mode'] : ( $opening_preset->mode ?? 'questions_ai' ),
                            'welcome_text'       => isset( $opening_overrides['welcome_text'] ) ? $opening_overrides['welcome_text'] : ( $opening_preset->welcome_text ?? '' ),
                            'show_welcome'       => isset( $opening_overrides['show_welcome'] ) ? (bool) $opening_overrides['show_welcome'] : (bool) ( $opening_preset->show_welcome ?? true ),
                            'questions'          => isset( $opening_overrides['questions'] ) ? $opening_overrides['questions'] : ( $opening_preset->questions ?: array() ),
                            'ai_instructions'    => isset( $opening_overrides['ai_instructions'] ) ? $opening_overrides['ai_instructions'] : ( $opening_preset->ai_instructions ?? '' ),
                            'qa_pairs'           => isset( $opening_overrides['qa_pairs'] ) ? $opening_overrides['qa_pairs'] : ( $opening_preset->qa_pairs ?: array() ),
                        );
                    }
                } catch ( Exception $e ) {
                    BoostBot_Logger::error( 'Error loading opening screen preset: ' . $e->getMessage() );
                }
            }

            // Add page context if post_id provided
            if ( $post_id ) {
                $extractor = new BoostBot_Content_Extractor();
                $config['page_context'] = $extractor->get_page_context( absint( $post_id ) );
            }

        } else {
            // No bot resolved for this context (e.g. default bot excluded) => widget should be inactive on this page.
            // Return empty widgets_enabled since no bot is active
            $config_language = $context['language'] ?? BoostBot_Language::get_primary();
            $config_locale   = BoostBot_Language_Manager::lang_to_locale( $config_language, get_locale() );

            $config = [
                'bot_name'        => $settings['bot_name'],
                'welcome_message' => $settings['welcome_message'],
                'appearance'      => [
                    'primary_color' => $settings['primary_color'],
                    'accent_color'  => $settings['accent_color'],
                    'position'      => $settings['position'],
                    'avatar_url'    => $settings['avatar_url'] ?? null,
                ],
                'behavior'        => [
                    'show_on_mobile'  => $settings['show_on_mobile'] ?? true,
                    'auto_open_delay' => $settings['auto_open_delay'] ?? 0,
                    'show_on_pages'   => $settings['show_on_pages'] ?? 'all',
                ],
                // Mobile layout settings (vw/vh units for responsive chat window)
                // Note: side margin is auto-calculated from width on frontend: (100 - width) / 2
                'mobile_layout'   => [
                    'width_vw'          => $settings['mobile_layout_width_vw'] ?? 94,
                    'height_vh'         => $settings['mobile_layout_height_vh'] ?? 75,
                    'margin_top_vh'     => $settings['mobile_layout_margin_top_vh'] ?? 20,
                    'margin_bottom_vh'  => $settings['mobile_layout_margin_bottom_vh'] ?? 5,
                ],
                'widgets_enabled' => [],  // No bot = no widgets (NEVER use global settings)
                'language'        => $config_language,
                'is_rtl'          => BoostBot_Language_Manager::is_rtl_language( $config_language ),
                'locale'          => $config_locale,
                'direction_mode'  => $settings['direction_mode'] ?? 'auto',
                'is_active'       => false,
                'is_test_mode'    => false,
                'license_status'  => $license_status,
                'is_pro'          => $license_status === 'pro',
            ];

            // Week 28: caps (global only)
            $config['visitor_input_char_limit_enabled'] = (bool) BoostBot_Settings::get( 'visitor_input_char_limit_enabled', true );
            $config['visitor_input_char_limit']         = absint( BoostBot_Settings::get( 'visitor_input_char_limit', 500 ) ) ?: 500;
            $config['bot_output_char_limit_global']     = absint( BoostBot_Settings::get( 'bot_output_char_limit_global', 150 ) ) ?: 150;
            $config['bot_output_char_limit']            = $config['bot_output_char_limit_global'];
        }

        BoostBot_Logger::debug('Config returned', ['config' => $config]);

        // Week 26 watermark (free tier only): expose safe credit_type + watermark config to widget.
        // Never expose backend API keys to the browser.
        $config['credit_type'] = null;
        $config['site_status'] = null;
        $config['watermark']   = array(
            'enabled' => false,
            'text'    => 'Powered by BoostMedia',
        );

        $backend_base_url = (string) BoostBot_Settings::get( 'backend_base_url', '' );
        $backend_base_url = trim( $backend_base_url );

        // Reuse a single client instance & site_id for all backend calls in this
        // request to avoid duplicate HTTP round-trips.
        $resolved_site_id = null;

        if ( ! empty( $backend_base_url ) && class_exists( 'BoostBot_Credits_Backend_Client' ) ) {
            $client           = new BoostBot_Credits_Backend_Client();
            $resolved_site_id = $client->ensure_site_id();

            if ( ! is_wp_error( $resolved_site_id ) ) {
                // Cache credits status for 5 minutes (300 s).  The previous 30-second
                // TTL caused a fresh external HTTP call on almost every page view,
                // adding 2-5 seconds of latency to the config endpoint which blocks
                // the chat bubble from appearing.
                $cache_key = 'boostbot_public_credits_status_' . md5( home_url( '/' ) );
                $status    = get_transient( $cache_key );

                if ( ! is_array( $status ) ) {
                    $status = $client->get_status( (string) $resolved_site_id );
                    if ( ! is_wp_error( $status ) && is_array( $status ) ) {
                        set_transient( $cache_key, $status, 300 );
                    }
                }

                if ( is_array( $status ) ) {
                    $config['site_status'] = isset( $status['site_status'] ) ? $status['site_status'] : null;
                    $config['credit_type'] = isset( $status['active_model']['credit_type'] ) ? $status['active_model']['credit_type'] : null;

                    if ( defined( 'BOOSTBOT_SELF_HOSTED' ) && BOOSTBOT_SELF_HOSTED ) {
                        if ( 'free' === $config['credit_type'] ) {
                            $config['watermark']['enabled'] = true;
                        }
                    } else {
                        if ( filter_var( BoostBot_Settings::get( 'show_watermark' ), FILTER_VALIDATE_BOOLEAN ) ) {
                            $config['watermark']['enabled'] = true;
                        }
                    }
                }
            }
        }
        
        // Direct SSE streaming configuration (browser → backend)
        // Don't pass a default so we use the class default.
        $direct_streaming_enabled = (bool) BoostBot_Settings::get( 'direct_streaming_enabled' );
        $config['direct_streaming_enabled'] = $direct_streaming_enabled;
        
        // Provide backend stream URL whenever backend is configured.
        // Orchestrated flow also relies on this base route (derived to /chat/orchestrate client-side).
        if ( ! empty( $backend_base_url ) && ! is_wp_error( $resolved_site_id ) && $resolved_site_id ) {
            $stream_url = rtrim( $backend_base_url, '/' ) . '/api/v1/sites/' . rawurlencode( (string) $resolved_site_id ) . '/chat/stream';
            $config['backend_stream_url'] = $stream_url;
            // Backward compatibility for older frontend bundles.
            $config['vercel_stream_url'] = $stream_url;
        }
        
        // Legacy streaming flag (WordPress proxy)
        $config['streaming_enabled'] = (bool) BoostBot_Settings::get( 'streaming_enabled', false );
        
        // Orchestrated flow (Gemini orchestrator + GPT executor)
        // When enabled, uses two-tier chunk retrieval and progressive responses
        $config['orchestrated_flow_enabled'] = (bool) BoostBot_Settings::get( 'orchestrated_flow_enabled', false );
        
        // Page transition triggers (when user navigates to page with different bot)
        // When enabled, bot sends contextual message when user moves between pages with different bots
        $config['page_transition_triggers_enabled'] = (bool) BoostBot_Settings::get( 'page_transition_triggers_enabled', true );

        // WooCommerce context injection — use direct term-meta for currency
        // instead of pll_set_current_language() which is unreliable in REST contexts.
        if (class_exists('WooCommerce')) {
            $config_bot_lang = $config['language'] ?? 'he';
            $currency = BoostBot_Polylang_Context::get_currency_for_language( $config_bot_lang );
            $woo_service = class_exists( 'BoostBot_WooCommerce_Service' ) ? new BoostBot_WooCommerce_Service() : null;
            $woo_preset = $this->get_bot_woocommerce_preset( $bot );

            $config['woocommerce'] = array(
                'active'          => true,
                'show_cart_button' => (bool) ( $woo_preset->show_cart_button ?? false ),
                'currency_symbol' => $currency['symbol'],
                'currency_code'   => $currency['code'],
                'cart_url'        => $woo_service ? $woo_service->get_store_page_url( 'cart', $config_bot_lang ) : '',
                'checkout_url'    => $woo_service ? $woo_service->get_store_page_url( 'checkout', $config_bot_lang ) : '',
            );
            // Product-specific context when post_id refers to a product
            if ($post_id && function_exists('wc_get_product')) {
                $product = wc_get_product($post_id);
                if ($product && $product instanceof \WC_Product) {
                    $config['woocommerce']['product'] = array(
                        'id'            => $product->get_id(),
                        'name'          => $product->get_name(),
                        'price'         => $product->get_price(),
                        'currency_code' => $currency['code'],
                    );
                }
            }
        } else {
            $config['woocommerce'] = array('active' => false);
        }
        
        return $this->success($config);
    }

    /**
     * Get only public-safe settings.
     *
     * @since 1.0.0
     * @return array Public settings only.
     */
    private function get_public_safe_settings()
    {
        // List of settings that are safe to expose publicly
        $public_keys = [
            'bot_name',
            'welcome_message',
            'primary_color',
            'accent_color',
            'position',
            'avatar_url',
            'show_on_mobile',
            'auto_open_delay',
            'show_on_pages',
            'widgets_enabled',
            'direction_mode',
            'is_active',

            // Week 28: Character caps (safe to expose to the widget)
            'visitor_input_char_limit_enabled',
            'visitor_input_char_limit',
            'bot_output_char_limit_global',

            // Mobile layout settings (vw/vh based)
            // Note: side margin is auto-calculated from width on frontend
            'mobile_layout_width_vw',
            'mobile_layout_height_vh',
            'mobile_layout_margin_top_vh',
            'mobile_layout_margin_bottom_vh',

            // Per-visitor message rate limiting (safe to expose for frontend UX)
            'visitor_message_limit_enabled',
            'visitor_messages_per_hour',
            'visitor_messages_per_day',
            'visitor_cooldown_enabled',
        ];

        $settings = [];

        foreach ($public_keys as $key) {
            $settings[$key] = BoostBot_Settings::get($key);
        }

        return $settings;
    }
    
    /**
     * Get widget types assigned to a bot.
     *
     * @param object|null $bot The bot object.
     * @return array Array of widget type strings (e.g., ['form', 'gallery', 'calendar_meeting']).
     */
    private function get_bot_widget_types( $bot ): array {
        if ( ! $bot || empty( $bot->id ) || ! class_exists( 'BoostBot_Widget_Model' ) ) {
            return array();
        }
        
        $widget_types = array();
        try {
            $widget_model = new BoostBot_Widget_Model();
            $assigned_widgets = $widget_model->get_for_chatbot( absint( $bot->id ) );
            if ( ! empty( $assigned_widgets ) ) {
                foreach ( $assigned_widgets as $widget ) {
                    $widget_type = $widget->type ?? 'unknown';
                    if ( ! in_array( $widget_type, $widget_types, true ) ) {
                        $widget_types[] = $widget_type;
                    }
                }
            }
        } catch ( Exception $e ) {
            BoostBot_Logger::error( 'Config: Failed to get bot widget types: ' . $e->getMessage() );
        }

        return $this->filter_widget_types_for_bot( $bot, $widget_types );
    }

    /**
     * Remove WooCommerce widget types when the current bot has no valid Woo preset.
     *
     * @param object $bot          Bot object.
     * @param array  $widget_types Raw widget types.
     * @return array
     */
    private function filter_widget_types_for_bot( $bot, array $widget_types ): array {
        if ( $this->bot_has_valid_woocommerce_preset( $bot ) ) {
            return $widget_types;
        }

        return array_values(
            array_filter(
                $widget_types,
                static function ( $widget_type ) {
                    return ! class_exists( 'BoostBot_Widget_Model' )
                        || ! BoostBot_Widget_Model::is_woocommerce_widget_type( (string) $widget_type );
                }
            )
        );
    }

    /**
     * Check whether the bot has a valid WooCommerce preset linked.
     *
     * @param object|null $bot Bot object.
     * @return bool
     */
    private function bot_has_valid_woocommerce_preset( $bot ): bool {
        return null !== $this->get_bot_woocommerce_preset( $bot );
    }

    /**
     * Get the WooCommerce preset linked to the current bot, if any.
     *
     * @param object|null $bot Bot object.
     * @return object|null
     */
    private function get_bot_woocommerce_preset( $bot ) {
        if ( ! $bot || empty( $bot->woocommerce_preset_id ) || ! class_exists( 'BoostBot_WooCommerce_Preset_Model' ) ) {
            return null;
        }

        try {
            $preset = ( new BoostBot_WooCommerce_Preset_Model() )->get( (int) $bot->woocommerce_preset_id );

            return ! empty( $preset ) ? $preset : null;
        } catch ( \Throwable $e ) {
            BoostBot_Logger::error( 'Config: Failed to load Woo preset: ' . $e->getMessage() );
        }

        return null;
    }

    /**
     * Create a test headphones product.
     *
     * @param WP_REST_Request $request The request.
     * @return WP_REST_Response The response.
     */
    public function create_test_product( $request ) {
        if ( ! class_exists( 'WooCommerce' ) ) {
            return $this->error( 'WooCommerce not active', 400 );
        }

        // Check if product already exists
        $existing = wc_get_products( array(
            'sku'   => 'BT-HP-249',
            'limit' => 1,
        ) );

        if ( ! empty( $existing ) ) {
            $product = $existing[0];
            // Update price to 249
            $product->set_price( '249' );
            $product->set_regular_price( '249' );
            $product->save();
            
            return $this->success( array(
                'message' => 'Product already exists, price updated',
                'id' => $product->get_id(),
                'name' => $product->get_name(),
                'price' => $product->get_price(),
            ) );
        }

        // Create new product
        $product = new WC_Product_Simple();
        
        $is_he = class_exists( 'BoostBot_Language' ) && 'he' === BoostBot_Language::get_primary();
        $product->set_name( $is_he ? 'אוזניות Bluetooth מקצועיות' : 'Professional Bluetooth Headphones' );
        $product->set_slug( 'bluetooth-headphones-pro' );
        $product->set_regular_price( '249' );
        $product->set_price( '249' );
        $product->set_description( $is_he
            ? 'אוזניות Bluetooth מקצועיות עם ביטול רעשים אקטיבי וסוללה עד 30 שעות. איכות סאונד יוצאת דופן עם בס עמוק וצלילים צלולים.'
            : 'Professional Bluetooth headphones with active noise cancellation and up to 30-hour battery life. Exceptional sound quality with deep bass and crystal-clear treble.' );
        $product->set_short_description( $is_he
            ? 'אוזניות אלחוטיות עם ביטול רעשים אקטיבי וסוללה עד 30 שעות'
            : 'Wireless headphones with active noise cancellation and up to 30-hour battery' );
        $product->set_sku( 'BT-HP-249' );
        $product->set_manage_stock( true );
        $product->set_stock_quantity( 100 );
        $product->set_stock_status( 'instock' );
        $product->set_status( 'publish' );
        $product->set_catalog_visibility( 'visible' );
        
        $product_id = $product->save();
        
        return $this->success( array(
            'message' => 'Product created successfully',
            'id' => $product_id,
            'name' => $product->get_name(),
            'price' => $product->get_price(),
        ) );
    }

    /**
     * List products for debugging.
     *
     * @param WP_REST_Request $request The request.
     * @return WP_REST_Response The response.
     */
    public function list_products( $request ) {
        if ( ! class_exists( 'WooCommerce' ) ) {
            return $this->error( 'WooCommerce not active', 400 );
        }

        $products = wc_get_products( array(
            'limit'   => 20,
            'status'  => 'publish',
            'orderby' => 'date',
            'order'   => 'DESC',
        ) );

        $list = array();
        foreach ( $products as $product ) {
            $list[] = array(
                'id'    => $product->get_id(),
                'name'  => $product->get_name(),
                'price' => $product->get_price(),
                'regular_price' => $product->get_regular_price(),
                'sku'   => $product->get_sku(),
                'image' => wp_get_attachment_url( $product->get_image_id() ),
            );
        }

        return $this->success( array( 'products' => $list ) );
    }

    /**
     * Add image to a WooCommerce product from URL.
     *
     * @param WP_REST_Request $request The request.
     * @return WP_REST_Response Result.
     */
    public function add_product_image( $request ) {
        if ( ! class_exists( 'WooCommerce' ) ) {
            return $this->error( 'WooCommerce not active', 400 );
        }

        $product_id = $request->get_param( 'product_id' ) ?: 14075;
        $image_url  = $request->get_param( 'image_url' ) ?: plugins_url( 'assets/images/placeholder-product.png', BOOSTBOT_PLUGIN_FILE );

        // Get the product
        $product = wc_get_product( $product_id );
        if ( ! $product ) {
            return $this->error( 'Product not found: ' . $product_id, 404 );
        }

        // Check if product already has an image
        if ( $product->get_image_id() ) {
            return $this->success( array(
                'message'    => 'Product already has an image',
                'product_id' => $product_id,
                'image_url'  => wp_get_attachment_url( $product->get_image_id() ),
            ) );
        }

        // Download and attach image
        require_once ABSPATH . 'wp-admin/includes/media.php';
        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';

        // Download image to media library
        $attachment_id = media_sideload_image( $image_url, $product_id, $product->get_name() . ' - ' . __( 'Image', 'boostbot' ), 'id' );

        if ( is_wp_error( $attachment_id ) ) {
            return $this->error( 'Error uploading image: ' . $attachment_id->get_error_message(), 500 );
        }

        // Set as product image
        $product->set_image_id( $attachment_id );
        $product->save();

        return $this->success( array(
            'message'       => 'Image added successfully!',
            'product_id'    => $product_id,
            'attachment_id' => $attachment_id,
            'image_url'     => wp_get_attachment_url( $attachment_id ),
        ) );
    }

    /**
     * Get CSS for Elementor custom fonts.
     *
     * Checks if the font name matches an Elementor custom font and returns the @font-face CSS.
     *
     * @param string $font_family The font family name.
     * @return string|null The CSS for the custom font, or null if not found.
     */
    private function get_elementor_custom_font_css( $font_family ) {
        // Check if Elementor Pro is active
        if ( ! defined( 'ELEMENTOR_PRO_VERSION' ) ) {
            return null;
        }

        // Query for custom fonts
        $custom_fonts = get_posts( array(
            'post_type'      => 'elementor_font',
            'posts_per_page' => -1,
            'post_status'    => 'publish',
        ) );

        if ( empty( $custom_fonts ) ) {
            return null;
        }

        $css = '';

        foreach ( $custom_fonts as $font_post ) {
            $font_name = $font_post->post_title;
            
            // Check if this font matches the requested font family
            if ( strcasecmp( trim( $font_name ), trim( $font_family ) ) !== 0 ) {
                continue;
            }

            // Get font face data from Elementor
            $font_face = get_post_meta( $font_post->ID, 'elementor_font_face', true );
            
            if ( ! empty( $font_face ) ) {
                $css .= $font_face;
            } else {
                // Try to build font-face from font files
                $font_data = get_post_meta( $font_post->ID, 'elementor_font_files', true );
                
                if ( ! empty( $font_data ) && is_array( $font_data ) ) {
                    foreach ( $font_data as $weight => $files ) {
                        if ( ! is_array( $files ) ) {
                            continue;
                        }
                        
                        $sources = array();
                        
                        // Build src for each file type
                        foreach ( $files as $type => $file_data ) {
                            if ( ! empty( $file_data['url'] ) ) {
                                $url = esc_url( $file_data['url'] );
                                $format = '';
                                
                                switch ( $type ) {
                                    case 'woff2':
                                        $format = "format('woff2')";
                                        break;
                                    case 'woff':
                                        $format = "format('woff')";
                                        break;
                                    case 'ttf':
                                        $format = "format('truetype')";
                                        break;
                                    case 'svg':
                                        $format = "format('svg')";
                                        break;
                                    case 'eot':
                                        $format = "format('embedded-opentype')";
                                        break;
                                }
                                
                                if ( $format ) {
                                    $sources[] = "url('{$url}') {$format}";
                                }
                            }
                        }
                        
                        if ( ! empty( $sources ) ) {
                            $font_weight = is_numeric( $weight ) ? $weight : '400';
                            $src = implode( ', ', $sources );
                            
                            $css .= "@font-face {
    font-family: '{$font_name}';
    src: {$src};
    font-weight: {$font_weight};
    font-style: normal;
    font-display: swap;
}
";
                        }
                    }
                }
            }
        }

        return ! empty( $css ) ? $css : null;
    }

    /**
     * Restrict a SQL identifier to [A-Za-z0-9_] characters.
     *
     * DDL cannot use $wpdb->prepare() for identifiers, so any identifier
     * that ends up interpolated into a query first passes through this
     * allow-list.
     *
     * @param string $identifier Raw identifier.
     * @return string Identifier with disallowed characters removed.
     */
    private static function sanitize_identifier( $identifier ) {
        return preg_replace( '/[^A-Za-z0-9_]/', '', (string) $identifier );
    }
}
