<?php

namespace AIPT\Api\Chatbot;

use AIPT\Api\Constants\ChatbotConstants;

/**
 * Chatbot settings management class.
 *
 * Handles CRUD operations for chatbot settings stored in wp_options.
 *
 * @package AIPT\Api\Chatbot
 * @since 2.5.0
 */
class ChatbotSettings implements ChatbotSettingsInterface {

    /**
     * Option key for chatbot settings.
     */
    private const OPTION_KEY = 'aipt_chatbot_settings';

    /**
     * Valid sections.
     */
    private const VALID_SECTIONS = ['general', 'placement', 'appearance', 'behavior', 'rate_limit', 'provider_cooldown', 'localization'];

    /**
     * Valid rate limit key modes.
     */
    private const VALID_KEY_MODES = ['session', 'session_ip_hashed'];

    /**
     * Valid language codes (ISO 639-1).
     * Synced with CHATBOT_LANGUAGE_OPTIONS in options.ts
     */
    private const VALID_LANGUAGES = [
        'af', 'ar', 'bg', 'bn', 'ca', 'cs', 'da', 'de', 'el', 'en', 'en-GB',
        'es', 'et', 'fa', 'fi', 'fr', 'he', 'hi', 'hr', 'hu', 'id',
        'it', 'ja', 'ko', 'lt', 'lv', 'ms', 'nl', 'no', 'pl', 'pt',
        'ro', 'ru', 'sr', 'sk', 'sl', 'sv', 'th', 'tr', 'uk', 'ur',
        'vi', 'zh', 'zh-TW',
    ];

    /**
     * Valid tone types.
     */
    private const VALID_TONES = ['professional', 'friendly', 'casual', 'formal'];

    /**
     * Valid position types.
     */
    private const VALID_POSITIONS = ['bottom-right', 'bottom-left', 'custom'];

    /**
     * Valid visibility modes.
     */
    private const VALID_VISIBILITY_MODES = ['all', 'specific', 'exclude'];

    /**
     * Valid device types.
     */
    private const VALID_DEVICES = ['all', 'desktop', 'mobile'];

    /**
     * Valid product recommendation modes.
     */
    private const VALID_RECOMMENDATION_MODES = ['auto', 'always', 'never'];

    /**
     * Valid page types for exclusion.
     */
    private const VALID_PAGE_TYPES = ['checkout', 'cart', 'my-account'];

    /**
     * Valid theme presets.
     */
    private const VALID_THEME_PRESETS = ['light', 'dark', 'brand-match', 'custom'];

    /**
     * Valid widget launcher icons.
     */
    private const VALID_WIDGET_ICONS = [
        'chat', 'headset', 'robot', 'help', 'message', 'sparkles',
        'heart', 'star', 'lightning', 'smile', 'support', 'question'
    ];

    /**
     * Valid launcher animation types.
     */
    private const VALID_LAUNCHER_ANIMATIONS = ['none', 'pulse', 'wiggle'];

    /**
     * Valid response lengths.
     */
    private const VALID_RESPONSE_LENGTHS = ['concise', 'balanced', 'detailed'];

    /**
     * Valid AI providers.
     */
    private const VALID_PROVIDERS = ['openai', 'gemini', 'openrouter', 'claude'];

    /**
     * Default models per provider.
     */
    private const DEFAULT_MODELS = [
        'openai' => 'gpt-4.1-mini',
        'gemini' => 'gemini-2.5-flash',
        'openrouter' => 'openrouter/free',
        'claude' => 'claude-sonnet-4-6',
    ];

    /**
     * All valid label keys for chatbot localization.
     * These keys are used across all chatbot components.
     *
     * IMPORTANT: This is the authoritative list for validation.
     * When adding new labels:
     * 1. Add the key here
     * 2. Add English default in chatbot-labels.php
     * 3. Add translations to language files in src/Assets/data/languages/
     *
     * Keys are validated during settings save (see validate() method).
     * Invalid keys in custom_labels are rejected to prevent injection.
     */
    private const ALL_LABEL_KEYS = [

        'placeholder',
        'input_placeholder',
        'send',
        'send_message',
        'clear',
        'emoji',
        'thinking',
        'error',
        'queued',

        'online',
        'offline',
        'close',
        'open_chat',
        'close_chat',

        'home_tab',
        'messages_tab',

        'hero_greeting',
        'hero_subtitle',
        'send_message_cta',
        'send_message_subtitle',
        'whatsapp_cta',
        'whatsapp_subtitle',

        'attention_bubble',

        'prechat_title',
        'prechat_subtitle',
        'name_label',
        'name_placeholder',
        'name_required',
        'email_label',
        'email_placeholder',
        'email_required',
        'invalid_email',
        'message_label',
        'prechat_message_placeholder',
        'message_required',
        'consent_text_before',
        'consent_text',
        'consent_and',
        'consent_required',
        'start_chat',

        'view_product',
        'add_to_cart',
        'addedToCart',
        'out_of_stock',
        'backorder',
        'variable_options',
        'cart_error',
        'cart_error_generic',
        'collapse_products',
        'expand_products',

        'support_team',
        'message_queued',
        'start_conversation',

        'today',
        'yesterday',

        'privacy_label',
        'terms_label',
        'powered_by',
    ];

    /**
     * Cached settings.
     *
     * @var array|null
     */
    private $cached_settings = null;

    /**
     * Get default settings.
     *
     * @return array Default values.
     */
    public static function getDefaults(): array {
        return [
            'general' => [
                'enabled' => false,
                'bot_name' => __('Shop Assistant', 'ai-product-tools'),
                'greeting_message' => __('Hi! How can I help you find what you\'re looking for today?', 'ai-product-tools'),
                'language' => 'en',
                'tone' => 'friendly',
                'fallback_contact_url' => '',
                'rate_limit_message' => __('We\'re experiencing high demand. Please try again in a moment.', 'ai-product-tools'),

                'show_errors_to_user'      => true,
                'use_custom_error_message' => false,
                'store_context' => '',
                'pre_chat_form_enabled' => true,
                'terms_page_id' => null,
                'privacy_page_id' => null,
                'whatsapp_number' => '',
                'knowledge_pages' => [],
                'starter_messages_enabled' => true,
                'starter_messages' => [
                    __('What products do you recommend?', 'ai-product-tools'),
                    __('Do you have any deals or discounts?', 'ai-product-tools'),
                    __('Tell me about your shipping policy', 'ai-product-tools'),
                ],
            ],
            'placement' => [
                'position' => 'bottom-right',
                'offset_x' => 20,
                'offset_y' => 20,
                'visibility_mode' => 'all',
                'specific_pages' => [],
                'excluded_pages' => [],
                'device' => 'all',
                'excluded_page_types' => ['checkout'],
            ],
            'appearance' => [
                'theme_preset' => 'light',
                'launcher_icon' => 'chat',
                'custom_launcher_icon_url' => '',
                'profile_picture_url' => '',
                'colors' => [
                    'primary' => '#3b82f6',
                    'secondary' => '#64748b',
                    'background' => '#ffffff',
                    'text' => '#1f2937',
                    'user_bubble' => '#3b82f6',
                    'bot_bubble' => '#f0f0f0',
                    'header_bg' => '#ffffff',
                    'footer_bg' => '#ffffff',
                    'button_hover' => '#1e88e5',
                    'online_indicator' => '#22c55e',
                    'launcher_bg' => '#3b82f6',
                    'launcher_icon_color' => '#ffffff',
                    'attention_bubble_bg' => '#ffffff',
                    'attention_bubble_text' => '#1f2937',
                    'attention_badge_bg' => '#ef4444',
                    'attention_badge_text' => '#ffffff',
                ],
                'font_family' => 'inherit',
                'border_radius' => 12,
                'custom_css' => '',
                'launcher_animation' => 'none',
                'hero_image_url' => '',
                'attention_bubble_enabled' => false,
                'attention_bubble_text' => __('Chat with us!', 'ai-product-tools'),
                'attention_badge_enabled' => false,
                'attention_badge_text' => '1',
            ],
            'behavior' => [
                'index_profile' => '1', 
                'max_products' => 3,
                'show_view_product' => true,
                'show_add_to_cart' => true,
                'show_stock_status' => true,
                'response_length' => 'balanced',
                'provider' => 'openai',
                'model' => 'gpt-4.1-mini',
                'product_recommendation_mode' => 'auto',
            ],
            'rate_limit' => [
                'enabled' => true,
                'max_requests' => ChatbotConstants::DEFAULT_RATE_LIMIT_MAX_REQUESTS,
                'window_seconds' => ChatbotConstants::DEFAULT_RATE_LIMIT_WINDOW_SECONDS,
                'key_mode' => 'session',
            ],
            'provider_cooldown' => [
                'enabled' => true,
                'seconds' => ChatbotConstants::DEFAULT_PROVIDER_COOLDOWN_SECONDS,
                'enqueue_on_failure' => false,
            ],
            'localization' => [
                'auto_detect_enabled' => true,
                'fallback_language' => 'en',
                'custom_labels' => [],
            ],
        ];
    }

    /**
     * Check if chatbot is enabled.
     *
     * @return bool
     */
    public function isEnabled(): bool {
        return (bool) $this->get('general.enabled', false);
    }

    /**
     * Get default model for a provider.
     *
     * @param string $provider The provider name.
     * @return string Default model identifier.
     */
    private function getDefaultModelForProvider(string $provider): string {
        return self::DEFAULT_MODELS[$provider] ?? self::DEFAULT_MODELS['openai'];
    }

    /**
     * Get all settings.
     *
     * @return array Complete settings array.
     */
    public function getAll(): array {
        if ($this->cached_settings !== null) {
            return $this->cached_settings;
        }

        $stored = get_option(self::OPTION_KEY, []);
        $defaults = self::getDefaults();

        $this->cached_settings = $this->mergeSettings($defaults, $stored);
        return $this->cached_settings;
    }

    /**
     * Get a specific setting section.
     *
     * @param string $section Section name (general, placement, appearance, behavior).
     * @return array Section settings.
     */
    public function getSection(string $section): array {
        if (!in_array($section, self::VALID_SECTIONS, true)) {
            return [];
        }

        $settings = $this->getAll();
        return $settings[$section] ?? [];
    }

    /**
     * Get a specific setting value using dot notation.
     *
     * @param string $key     Dot-notation key (e.g., 'general.bot_name').
     * @param mixed  $default Default value if not set.
     * @return mixed Setting value.
     */
    public function get(string $key, $default = null) {
        $settings = $this->getAll();
        $keys = explode('.', $key);
        $value = $settings;

        foreach ($keys as $k) {
            if (!isset($value[$k])) {
                return $default;
            }
            $value = $value[$k];
        }

        return $value;
    }

    /**
     * Update settings.
     *
     * @param array $settings Partial or full settings array.
     * @return bool|\WP_Error True on success or a drift error when persistence differs.
     * @throws \InvalidArgumentException If validation fails.
     */
    public function update(array $settings) {
        $current = $this->getAll();
        $requested_paths = $this->collectRequestedPaths($settings);

        $explicit_empty_custom_labels = isset($settings['localization']['custom_labels'])
            && is_array($settings['localization']['custom_labels'])
            && empty($settings['localization']['custom_labels']);

        $merged = $this->mergeSettings($current, $settings);

        if ($explicit_empty_custom_labels) {
            $merged['localization']['custom_labels'] = [];
        }

        $errors = $this->validate($merged);
        if (!empty($errors)) {
            throw new \InvalidArgumentException(wp_json_encode($errors));
        }

        $sanitized = $this->sanitize($merged);
        update_option(self::OPTION_KEY, $sanitized);

        $persisted_raw = get_option(self::OPTION_KEY, []);
        $persisted = is_array($persisted_raw)
            ? $this->mergeSettings(self::getDefaults(), $persisted_raw)
            : self::getDefaults();

        $drift = $this->detectPersistenceDrift($requested_paths, $sanitized, $persisted, $current);
        $this->cached_settings = $persisted;

        if (!empty($drift)) {
            return new \WP_Error(
                'aipt_settings_drift',
                __('Some chatbot settings could not be persisted as requested.', 'ai-product-tools'),
                [
                    'status' => 422,
                    'drift' => $drift,
                ]
            );
        }

        return true;
    }

    /**
     * Get public settings subset (for storefront widget).
     *
     * @return array Public-safe settings.
     */
    public function getPublicSettings(): array {
        $settings = $this->getAll();

        $terms_url = null;
        $privacy_url = null;
        if (!empty($settings['general']['terms_page_id'])) {
            $permalink = get_permalink($settings['general']['terms_page_id']);
            $terms_url = ($permalink !== false) ? esc_url($permalink) : null;
        }
        if (!empty($settings['general']['privacy_page_id'])) {
            $permalink = get_permalink($settings['general']['privacy_page_id']);
            $privacy_url = ($permalink !== false) ? esc_url($permalink) : null;
        }

        return [
            'enabled' => $settings['general']['enabled'],
            'bot_name' => $settings['general']['bot_name'],
            'greeting_message' => $settings['general']['greeting_message'],
            'language' => $settings['general']['language'],
            'pre_chat_form_enabled' => $settings['general']['pre_chat_form_enabled'] ?? true,
            'starter_messages_enabled' => $settings['general']['starter_messages_enabled'] ?? true,
            'starter_messages' => $settings['general']['starter_messages'] ?? [],
            'terms_url' => $terms_url,
            'privacy_url' => $privacy_url,
            'placement' => [
                'position' => $settings['placement']['position'],
                'offset_x' => $settings['placement']['offset_x'],
                'offset_y' => $settings['placement']['offset_y'],
            ],
            'appearance' => [
                'launcher_icon' => $settings['appearance']['launcher_icon'] ?? 'chat',
                'custom_launcher_icon_url' => $settings['appearance']['custom_launcher_icon_url'] ?? '',
                'profile_picture_url' => $settings['appearance']['profile_picture_url'] ?? '',
                'colors' => $settings['appearance']['colors'],
                'font_family' => $settings['appearance']['font_family'],
                'border_radius' => $settings['appearance']['border_radius'],
                'custom_css' => $settings['appearance']['custom_css'],
                'launcher_animation' => $settings['appearance']['launcher_animation'] ?? 'none',
                'hero_image_url' => $settings['appearance']['hero_image_url'] ?? '',
                'attention_bubble_enabled' => $settings['appearance']['attention_bubble_enabled'] ?? false,
                'attention_bubble_text' => $settings['appearance']['attention_bubble_text'] ?? __('Chat with us!', 'ai-product-tools'),
                'attention_badge_enabled' => $settings['appearance']['attention_badge_enabled'] ?? false,
                'attention_badge_text' => $settings['appearance']['attention_badge_text'] ?? '1',
            ],
            'behavior' => [
                'max_products' => $settings['behavior']['max_products'],
                'show_view_product' => $settings['behavior']['show_view_product'],
                'show_add_to_cart' => $settings['behavior']['show_add_to_cart'],
                'show_stock_status' => $settings['behavior']['show_stock_status'],
            ],
            'labels' => $this->getLabels(),
        ];
    }

    /**
     * Get UI labels in configured language with custom overrides applied.
     *
     * @return array Translated labels.
     */
    public function getLabels(): array {
        $language = $this->get('general.language', 'en');
        $custom_labels = $this->get('localization.custom_labels', []);
        return self::getLabelsForLanguage($language, $custom_labels);
    }

    /**
     * Get all label keys.
     *
     * @return array List of all valid label keys.
     */
    public static function getAllLabelKeys(): array {
        return self::ALL_LABEL_KEYS;
    }

    /**
     * Check if auto-detect language is enabled.
     *
     * @return bool
     */
    public function isAutoDetectEnabled(): bool {
        return (bool) $this->get('localization.auto_detect_enabled', true);
    }

    /**
     * Get fallback language for auto-detection.
     *
     * @return string Fallback language code.
     */
    public function getFallbackLanguage(): string {
        return $this->get('localization.fallback_language', 'en');
    }

    /**
     * Get supported languages list.
     *
     * @return array Array of supported language codes.
     */
    public static function getSupportedLanguages(): array {
        return self::VALID_LANGUAGES;
    }

    /**
     * Detect browser language from Accept-Language header.
     *
     * Parses the HTTP Accept-Language header and returns the best matching
     * supported language code. This enables server-side language detection
     * for optimized payload delivery.
     *
     * @param string $fallback Fallback language if no match found.
     * @return string Detected language code.
     */
    public static function detectBrowserLanguage( string $fallback = 'en' ): string {

        $accept_language = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] )
            ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) )
            : '';

        if ( empty( $accept_language ) ) {
            return $fallback;
        }

        $languages = [];
        $parts = explode( ',', $accept_language );

        foreach ( $parts as $part ) {
            $part = trim( $part );
            if ( empty( $part ) ) {
                continue;
            }

            if ( strpos( $part, ';q=' ) !== false ) {
                list( $lang, $q ) = explode( ';q=', $part );
                $quality = (float) $q;
            } else {
                $lang = $part;
                $quality = 1.0;
            }

            $lang = strtolower( trim( $lang ) );
            $languages[ $lang ] = $quality;
        }

        arsort( $languages );

        $supported = self::VALID_LANGUAGES;

        foreach ( $languages as $lang => $quality ) {

            foreach ( $supported as $supported_lang ) {
                if ( strtolower( $supported_lang ) === $lang ) {
                    return $supported_lang;
                }
            }

            $base_lang = explode( '-', $lang )[0];
            foreach ( $supported as $supported_lang ) {
                if ( strtolower( $supported_lang ) === $base_lang ) {
                    return $supported_lang;
                }
            }
        }

        return $fallback;
    }

    /**
     * Get labels for a specific language with optional custom overrides.
     *
     * @param string $language Language code.
     * @param array  $custom_labels Optional custom label overrides.
     * @return array Translated labels.
     */
    public static function getLabelsForLanguage(string $language, array $custom_labels = []): array {

        $defaults = self::getDefaultLabelsForLanguage($language);

        if (!empty($custom_labels)) {
            $defaults = array_merge($defaults, array_filter($custom_labels, function($value) {
                return !empty($value) && is_string($value);
            }));
        }

        return $defaults;
    }

    /**
     * Static cache for labels per language (in-memory for single request).
     *
     * @var array<string, array>
     */
    private static array $labels_cache = [];

    /**
     * Get default labels for a specific language (without custom overrides).
     *
     * Uses PHP native arrays for optimal performance via opcache.
     * Falls back to JSON for backwards compatibility if PHP files don't exist.
     *
     * @param string $language Language code.
     * @return array Default translated labels.
     */
    public static function getDefaultLabelsForLanguage(string $language): array {

        if (isset(self::$labels_cache[$language])) {
            return self::$labels_cache[$language];
        }

        $labels = self::loadLabelsFromPhp($language);

        self::$labels_cache[$language] = $labels;

        return $labels;
    }

    /**
     * Load labels from PHP files with WordPress transient caching.
     *
     * PHP files are opcached by the server, providing ~100x faster access
     * compared to JSON parsing on every request.
     *
     * @param string $language Language code.
     * @return array Labels for the specified language.
     */
    private static function loadLabelsFromPhp(string $language): array {

        $safe_language = preg_replace('/[^a-zA-Z0-9\-]/', '', $language);

        $cache_key = 'aipt_chatbot_labels_' . $safe_language;
        $cached = get_transient($cache_key);
        if ($cached !== false && is_array($cached)) {
            return $cached;
        }

        $defaults = self::getDefaultLabelsFromPhp();

        if ($safe_language === 'en') {
            set_transient($cache_key, $defaults, DAY_IN_SECONDS);
            return $defaults;
        }

        $lang_file = AIPT_PLUGIN_DIR . 'src/Assets/data/languages/' . $safe_language . '.php';

        if (file_exists($lang_file)) {
            $translations = include $lang_file;
            if (is_array($translations)) {
                $merged = array_merge($defaults, $translations);
                set_transient($cache_key, $merged, DAY_IN_SECONDS);
                return $merged;
            }
        }

        set_transient($cache_key, $defaults, DAY_IN_SECONDS);
        return $defaults;
    }

    /**
     * Get default (English) labels from PHP file.
     *
     * @return array Default English labels.
     */
    private static function getDefaultLabelsFromPhp(): array {
        static $defaults = null;

        if ($defaults !== null) {
            return $defaults;
        }

        $php_file = AIPT_PLUGIN_DIR . 'src/Assets/data/chatbot-labels.php';

        if (file_exists($php_file)) {
            require_once $php_file;
            if (function_exists('aipt_get_chatbot_labels')) {
                $all_labels = aipt_get_chatbot_labels();
                $defaults = $all_labels['defaults'] ?? [];
                if (!empty($defaults)) {
                    return $defaults;
                }
            }
        }

        $defaults = self::getFallbackLabels()['defaults'];
        return $defaults;
    }

    /**
     * Get fallback labels when all other sources are unavailable.
     *
     * Provides comprehensive English defaults for chatbot functionality.
     * This is the last resort fallback - normally PHP files are used.
     *
     * @return array Fallback labels structure with 'defaults' and 'translations' keys.
     */
    private static function getFallbackLabels(): array {
        return [
            'defaults' => [
                'placeholder' => __('Type your message...', 'ai-product-tools'),
                'input_placeholder' => __('Type your message...', 'ai-product-tools'),
                'send' => __('Send', 'ai-product-tools'),
                'send_message' => __('Send', 'ai-product-tools'),
                'clear' => __('Clear Chat', 'ai-product-tools'),
                'emoji' => __('Emoji', 'ai-product-tools'),
                'thinking' => __('Thinking...', 'ai-product-tools'),
                'error' => __('Something went wrong. Please try again.', 'ai-product-tools'),
                'queued' => __('Message queued', 'ai-product-tools'),
                'online' => __('Online', 'ai-product-tools'),
                'offline' => __('Offline', 'ai-product-tools'),
                'close' => __('Close', 'ai-product-tools'),
                'open_chat' => __('Open chat', 'ai-product-tools'),
                'close_chat' => __('Close chat', 'ai-product-tools'),
                'prechat_title' => __('Start a conversation', 'ai-product-tools'),
                'prechat_subtitle' => __('Fill in your details to get started', 'ai-product-tools'),
                'name_label' => __('Name', 'ai-product-tools'),
                'name_placeholder' => __('Your name', 'ai-product-tools'),
                'name_required' => __('Name is required', 'ai-product-tools'),
                'email_label' => __('Email', 'ai-product-tools'),
                'email_placeholder' => __('your@email.com', 'ai-product-tools'),
                'email_required' => __('Email is required', 'ai-product-tools'),
                'invalid_email' => __('Invalid email address', 'ai-product-tools'),
                'message_label' => __('Message', 'ai-product-tools'),
                'prechat_message_placeholder' => __('How can we help you?', 'ai-product-tools'),
                'message_required' => __('Message is required', 'ai-product-tools'),
                'consent_text_before' => __('I agree to the ', 'ai-product-tools'),
                'consent_text' => __('I agree to the privacy policy', 'ai-product-tools'),
                'consent_and' => __(' and ', 'ai-product-tools'),
                'consent_required' => __('Please agree to continue', 'ai-product-tools'),
                'start_chat' => __('Start Chat', 'ai-product-tools'),
                'view_product' => __('View Product', 'ai-product-tools'),
                'add_to_cart' => __('Add to Cart', 'ai-product-tools'),
                'addedToCart' => __('Added to cart!', 'ai-product-tools'),
                'out_of_stock' => __('Out of Stock', 'ai-product-tools'),
                'backorder' => __('Backorder', 'ai-product-tools'),
                'variable_options' => __('Multiple options available', 'ai-product-tools'),
                'cart_error' => __('Could not add to cart', 'ai-product-tools'),
                'cart_error_generic' => __('Error adding to cart', 'ai-product-tools'),
                'support_team' => __('Support Team', 'ai-product-tools'),
                'message_queued' => __('Message queued', 'ai-product-tools'),
                'start_conversation' => __('Start a conversation!', 'ai-product-tools'),
                'today' => __('Today', 'ai-product-tools'),
                'yesterday' => __('Yesterday', 'ai-product-tools'),
                'privacy_label' => __('Privacy', 'ai-product-tools'),
                'terms_label' => __('Terms', 'ai-product-tools'),
                'powered_by' => __('Powered by AI Product Tools', 'ai-product-tools'),
                'home_tab' => __('Home', 'ai-product-tools'),
                'messages_tab' => __('Messages', 'ai-product-tools'),
                'hero_greeting' => __('Hi there', 'ai-product-tools'),
                'hero_subtitle' => __('How can we help?', 'ai-product-tools'),
                'send_message_cta' => __('Send us a message', 'ai-product-tools'),
                'send_message_subtitle' => __('We typically reply in a few minutes', 'ai-product-tools'),
                'whatsapp_cta' => __('Chat on WhatsApp', 'ai-product-tools'),
                'whatsapp_subtitle' => __('Get instant support via WhatsApp', 'ai-product-tools'),
                'attention_bubble' => __('Chat with us!', 'ai-product-tools'),
                'collapse_products' => __('Show less', 'ai-product-tools'),
                'expand_products' => __('Show more', 'ai-product-tools'),
            ],
            'translations' => [],
        ];
    }

    /**
     * Clear the labels cache (useful for testing or after label updates).
     *
     * Clears both in-memory cache and WordPress transients.
     *
     * @return void
     */
    public static function clearLabelsCache(): void {

        self::$labels_cache = [];

        global $wpdb;

        $transient_pattern = $wpdb->esc_like( '_transient_aipt_chatbot_labels_' ) . '%';
        $timeout_pattern   = $wpdb->esc_like( '_transient_timeout_aipt_chatbot_labels_' ) . '%';

        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
                $transient_pattern
            )
        );
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
                $timeout_pattern
            )
        );

    }

    /**
     * Sanitize a hex color value with fallback for environments without Customizer.
     *
     * @param string $color The color value to sanitize.
     * @return string Sanitized hex color or empty string.
     */
    private function sanitizeHexColorSafe( string $color ): string {
        $color = trim( $color );

        if ( empty( $color ) ) {
            return '';
        }

        if ( function_exists( 'sanitize_hex_color' ) ) {
            $sanitized = \sanitize_hex_color( $color );
            return $sanitized ? $sanitized : '';
        }

        if ( function_exists( 'sanitize_hex_color_no_hash' ) ) {

            $color_no_hash = ltrim( $color, '#' );
            $sanitized = \sanitize_hex_color_no_hash( $color_no_hash );
            if ( ! empty( $sanitized ) ) {
                return '#' . $sanitized;
            }
        }

        if ( preg_match( '/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/', $color ) ) {
            return strtolower( $color );
        }

        return '';
    }

    /**
     * Detect brand colors from theme.
     *
     * @return array Detected colors or empty array.
     */
    public function detectBrandColors(): array {
        $colors = [];

        $primary = \get_theme_mod('primary_color');
        if ($primary) {
            $colors['primary'] = $primary;
        }

        $secondary = \get_theme_mod('secondary_color');
        if ($secondary) {
            $colors['secondary'] = $secondary;
        }

        $background = \get_theme_mod('background_color');
        if ($background) {
            $colors['background'] = '#' . ltrim($background, '#');
        }

        $global_styles = function_exists('wp_get_global_styles') ? (array) \wp_get_global_styles(['color']) : [];
        if (!empty($global_styles)) {

            if (!empty($global_styles['background'])) {
                $colors['background'] = $global_styles['background'];
            }

            if (!empty($global_styles['text'])) {
                $colors['text'] = $global_styles['text'];
            }
        }

        return $colors;
    }

    /**
     * Merge settings arrays recursively.
     *
     * @param array $defaults Default values.
     * @param array $stored   Stored values.
     * @return array Merged settings.
     */
    private function mergeSettings(array $defaults, array $stored): array {
        $merged = $defaults;

        foreach ($stored as $key => $value) {
            if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
                $merged[$key] = $this->mergeSettings($merged[$key], $value);
            } else {
                $merged[$key] = $value;
            }
        }

        return $merged;
    }

    private function collectRequestedPaths(array $settings, string $prefix = ''): array {
        $paths = [];

        foreach ($settings as $key => $value) {
            $path = '' === $prefix ? (string) $key : $prefix . '.' . (string) $key;

            if (is_array($value) && $this->shouldCollectChildPaths($value)) {
                $paths = array_merge($paths, $this->collectRequestedPaths($value, $path));
                continue;
            }

            $paths[] = $path;
        }

        return array_values(array_unique($paths));
    }

    private function shouldCollectChildPaths(array $value): bool {
        if (empty($value)) {
            return false;
        }

        return array_keys($value) !== range(0, count($value) - 1);
    }

    private function detectPersistenceDrift(array $paths, array $requested, array $persisted, array $previous): array {
        $drift = [];

        foreach ($paths as $path) {
            $requested_exists = $this->hasPath($requested, $path);
            $persisted_exists = $this->hasPath($persisted, $path);

            if (!$requested_exists && !$persisted_exists) {
                continue;
            }

            $requested_value = $requested_exists ? $this->getPathValue($requested, $path) : null;
            $persisted_value = $persisted_exists ? $this->getPathValue($persisted, $path) : null;

            if ($requested_value === $persisted_value) {
                continue;
            }

            $previous_exists = $this->hasPath($previous, $path);
            $previous_value = $previous_exists ? $this->getPathValue($previous, $path) : null;

            $drift[] = [
                'field' => $path,
                'requested' => $requested_value,
                'persisted' => $persisted_value,
                'reason' => $previous_exists && $previous_value === $persisted_value
                    ? 'persisted_value_mismatch'
                    : 'filter_modified_or_db_refused',
            ];
        }

        return $drift;
    }

    private function hasPath(array $settings, string $path): bool {
        $value = $settings;

        foreach (explode('.', $path) as $segment) {
            if (!is_array($value) || !array_key_exists($segment, $value)) {
                return false;
            }

            $value = $value[$segment];
        }

        return true;
    }

    private function getPathValue(array $settings, string $path) {
        $value = $settings;

        foreach (explode('.', $path) as $segment) {
            $value = $value[$segment];
        }

        return $value;
    }

    /**
     * Validate settings.
     *
     * @param array $settings Settings to validate.
     * @return array Validation errors.
     */
    private function validate(array $settings): array {
        $errors = [];

        if (empty($settings['general']['bot_name'])) {
            $errors['general.bot_name'] = [__('Bot name is required', 'ai-product-tools')];
        } elseif (mb_strlen($settings['general']['bot_name'], 'UTF-8') > ChatbotConstants::maxBotNameLength()) {
            $errors['general.bot_name'] = [__('Bot name must be under 50 characters', 'ai-product-tools')];
        }

        if (mb_strlen($settings['general']['greeting_message'] ?? '', 'UTF-8') > ChatbotConstants::maxGreetingMessageLength()) {
            $errors['general.greeting_message'] = [__('Greeting message must be under 500 characters', 'ai-product-tools')];
        }

        if (!in_array($settings['general']['language'] ?? 'en', self::VALID_LANGUAGES, true)) {
            $errors['general.language'] = [__('Invalid language code', 'ai-product-tools')];
        }

        if (!in_array($settings['general']['tone'] ?? 'friendly', self::VALID_TONES, true)) {
            $errors['general.tone'] = [__('Invalid tone type', 'ai-product-tools')];
        }

        if (mb_strlen($settings['general']['store_context'] ?? '', 'UTF-8') > ChatbotConstants::maxStoreContextLength()) {
            $errors['general.store_context'] = [__('Store context must be under 5000 characters', 'ai-product-tools')];
        }

        $starter_messages = $settings['general']['starter_messages'] ?? [];
        if (!is_array($starter_messages)) {
            $errors['general.starter_messages'] = [__('Starter messages must be an array', 'ai-product-tools')];
        } elseif (count($starter_messages) > 5) {
            $errors['general.starter_messages'] = [__('Maximum 5 starter messages allowed', 'ai-product-tools')];
        } else {
            foreach ($starter_messages as $msg) {
                if (!is_string($msg) || mb_strlen($msg, 'UTF-8') > 100) {
                    $errors['general.starter_messages'] = [__('Each starter message must be a string under 100 characters', 'ai-product-tools')];
                    break;
                }
            }
        }

        $offset_x = $settings['placement']['offset_x'] ?? 20;
        if ($offset_x < 0 || $offset_x > 500) {
            $errors['placement.offset_x'] = [__('Offset must be between 0 and 500 pixels', 'ai-product-tools')];
        }

        $offset_y = $settings['placement']['offset_y'] ?? 20;
        if ($offset_y < 0 || $offset_y > 500) {
            $errors['placement.offset_y'] = [__('Offset must be between 0 and 500 pixels', 'ai-product-tools')];
        }

        if (!in_array($settings['placement']['position'] ?? 'bottom-right', self::VALID_POSITIONS, true)) {
            $errors['placement.position'] = [__('Invalid position', 'ai-product-tools')];
        }

        if (!in_array($settings['placement']['visibility_mode'] ?? 'all', self::VALID_VISIBILITY_MODES, true)) {
            $errors['placement.visibility_mode'] = [__('Invalid visibility mode', 'ai-product-tools')];
        }

        if (!in_array($settings['placement']['device'] ?? 'all', self::VALID_DEVICES, true)) {
            $errors['placement.device'] = [__('Invalid device type', 'ai-product-tools')];
        }

        $all_color_keys = [
            'primary', 'secondary', 'background', 'text',
            'user_bubble', 'bot_bubble', 'header_bg', 'footer_bg', 'button_hover', 'online_indicator',
            'hero_bg',
            'launcher_bg', 'launcher_icon_color',
            'attention_bubble_bg', 'attention_bubble_text', 'attention_badge_bg', 'attention_badge_text'
        ];
        foreach ($all_color_keys as $colorKey) {
            $color = $settings['appearance']['colors'][$colorKey] ?? '';
            if (!empty($color) && !preg_match('/^#[0-9A-Fa-f]{6}$/', $color)) {
                $errors["appearance.colors.{$colorKey}"] = [__('Must be valid hex color', 'ai-product-tools')];
            }
        }

        $border_radius = $settings['appearance']['border_radius'] ?? 12;
        if ($border_radius < 0 || $border_radius > 50) {
            $errors['appearance.border_radius'] = [__('Border radius must be between 0 and 50 pixels', 'ai-product-tools')];
        }

        if (!in_array($settings['appearance']['theme_preset'] ?? 'light', self::VALID_THEME_PRESETS, true)) {
            $errors['appearance.theme_preset'] = [__('Invalid theme preset', 'ai-product-tools')];
        }

        $launcher_icon = $settings['appearance']['launcher_icon'] ?? 'chat';
        if (!empty($launcher_icon) && !in_array($launcher_icon, self::VALID_WIDGET_ICONS, true)) {
            $errors['appearance.launcher_icon'] = [__('Invalid launcher icon', 'ai-product-tools')];
        }

        $launcher_animation = $settings['appearance']['launcher_animation'] ?? 'none';
        if (!in_array($launcher_animation, self::VALID_LAUNCHER_ANIMATIONS, true)) {
            $errors['appearance.launcher_animation'] = [__('Invalid launcher animation', 'ai-product-tools')];
        }

        $attention_bubble_text = $settings['appearance']['attention_bubble_text'] ?? '';
        if (mb_strlen($attention_bubble_text, 'UTF-8') > 50) {
            $errors['appearance.attention_bubble_text'] = [__('Attention bubble text must be under 50 characters', 'ai-product-tools')];
        }

        $attention_badge_text = $settings['appearance']['attention_badge_text'] ?? '';
        if (mb_strlen($attention_badge_text, 'UTF-8') > 5) {
            $errors['appearance.attention_badge_text'] = [__('Attention badge text must be under 5 characters', 'ai-product-tools')];
        }

        $terms_page_id = $settings['general']['terms_page_id'] ?? null;
        if ($terms_page_id !== null && $terms_page_id > 0) {
            $terms_page = \get_post($terms_page_id);
            if (!$terms_page || $terms_page->post_type !== 'page' || $terms_page->post_status !== 'publish') {
                $errors['general.terms_page_id'] = [__('Selected terms page does not exist or is not published', 'ai-product-tools')];
            }
        }

        $privacy_page_id = $settings['general']['privacy_page_id'] ?? null;
        if ($privacy_page_id !== null && $privacy_page_id > 0) {
            $privacy_page = \get_post($privacy_page_id);
            if (!$privacy_page || $privacy_page->post_type !== 'page' || $privacy_page->post_status !== 'publish') {
                $errors['general.privacy_page_id'] = [__('Selected privacy page does not exist or is not published', 'ai-product-tools')];
            }
        }

        $knowledge_pages = $this->sanitizeKnowledgePages((array) ($settings['general']['knowledge_pages'] ?? []));
        if (!empty($knowledge_pages)) {
            foreach ($knowledge_pages as $page_id) {
                $page = \get_post($page_id);
                if (!$page || $page->post_type !== 'page' || $page->post_status !== 'publish') {
                    $errors['general.knowledge_pages'] = [__('One or more selected pages do not exist or are not published', 'ai-product-tools')];
                    break;
                }
            }
        }

        $max_products = $settings['behavior']['max_products'] ?? 3;
        if ($max_products < 1 || $max_products > 10) {
            $errors['behavior.max_products'] = [__('Must be between 1 and 10 products', 'ai-product-tools')];
        }

        if (!in_array($settings['behavior']['response_length'] ?? 'balanced', self::VALID_RESPONSE_LENGTHS, true)) {
            $errors['behavior.response_length'] = [__('Invalid response length', 'ai-product-tools')];
        }

        $defaults = self::getDefaults();
        $provider = $settings['behavior']['provider'] ?? $defaults['behavior']['provider'];
        if (!in_array($provider, self::VALID_PROVIDERS, true)) {
            $errors['behavior.provider'] = [__('Invalid AI provider', 'ai-product-tools')];
        } else {

            $api_key_getters = [
                'openai' => [\AIPT\Utils\ApiKeyManager::class, 'get_openai_key'],
                'gemini' => [\AIPT\Utils\ApiKeyManager::class, 'get_gemini_key'],
                'openrouter' => [\AIPT\Utils\ApiKeyManager::class, 'get_openrouter_key'],
                'claude' => [\AIPT\Utils\ApiKeyManager::class, 'get_claude_key'],
            ];

            $api_key = isset($api_key_getters[$provider]) ? call_user_func($api_key_getters[$provider]) : null;

            if (empty($api_key)) {
                $provider_names = [
                    'openai' => 'OpenAI',
                    'gemini' => 'Google Gemini',
                    'openrouter' => 'OpenRouter',
                    'claude' => 'Claude',
                ];
                $errors['behavior.provider'] = [
                    sprintf(
                        /* translators: %s: AI provider name (e.g. OpenAI, Gemini) */
                        __('No API key configured for %s. Please configure an API key in plugin settings.', 'ai-product-tools'),
                        $provider_names[$provider] ?? $provider
                    )
                ];
            }
        }

        $model = $settings['behavior']['model'] ?? '';
        if (empty($model) || mb_strlen($model, 'UTF-8') > 100) {
            $errors['behavior.model'] = [__('Model must be between 1 and 100 characters', 'ai-product-tools')];
        }

        $recommendation_mode = $settings['behavior']['product_recommendation_mode'] ?? 'auto';
        if (!in_array($recommendation_mode, self::VALID_RECOMMENDATION_MODES, true)) {
            $errors['behavior.product_recommendation_mode'] = [__('Invalid product recommendation mode', 'ai-product-tools')];
        }

        if (isset($settings['rate_limit'])) {
            $max_requests = $settings['rate_limit']['max_requests'] ?? ChatbotConstants::DEFAULT_RATE_LIMIT_MAX_REQUESTS;
            if ($max_requests < 1 || $max_requests > ChatbotConstants::MAX_RATE_LIMIT_MAX_REQUESTS) {
                $errors['rate_limit.max_requests'] = [__('Must be between 1 and 200', 'ai-product-tools')];
            }

            $window_seconds = $settings['rate_limit']['window_seconds'] ?? ChatbotConstants::DEFAULT_RATE_LIMIT_WINDOW_SECONDS;
            if ($window_seconds < ChatbotConstants::MIN_RATE_LIMIT_WINDOW_SECONDS || $window_seconds > ChatbotConstants::MAX_RATE_LIMIT_WINDOW_SECONDS) {
                $errors['rate_limit.window_seconds'] = [__('Must be between 10 and 3600 seconds', 'ai-product-tools')];
            }

            $key_mode = $settings['rate_limit']['key_mode'] ?? 'session';
            if (!in_array($key_mode, self::VALID_KEY_MODES, true)) {
                $errors['rate_limit.key_mode'] = [__('Invalid key mode', 'ai-product-tools')];
            }
        }

        if (isset($settings['provider_cooldown'])) {
            $cooldown_seconds = $settings['provider_cooldown']['seconds'] ?? ChatbotConstants::DEFAULT_PROVIDER_COOLDOWN_SECONDS;
            if ($cooldown_seconds < ChatbotConstants::MIN_PROVIDER_COOLDOWN_SECONDS || $cooldown_seconds > ChatbotConstants::MAX_PROVIDER_COOLDOWN_SECONDS) {
                $errors['provider_cooldown.seconds'] = [__('Must be between 5 and 600 seconds', 'ai-product-tools')];
            }
        }

        if (isset($settings['localization'])) {

            $fallback_language = $settings['localization']['fallback_language'] ?? 'en';
            if (!in_array($fallback_language, self::VALID_LANGUAGES, true)) {
                $errors['localization.fallback_language'] = [__('Invalid fallback language code', 'ai-product-tools')];
            }

            $custom_labels = $settings['localization']['custom_labels'] ?? [];
            if (!is_array($custom_labels)) {
                $errors['localization.custom_labels'] = [__('Custom labels must be an array', 'ai-product-tools')];
            } else {
                $label_errors = [];
                foreach ($custom_labels as $key => $value) {

                    if (!in_array($key, self::ALL_LABEL_KEYS, true)) {
                        $label_errors[] = sprintf(
                            /* translators: %s: label key name */
                            __('Invalid label key: %s', 'ai-product-tools'),
                            esc_html($key)
                        );
                        continue; 
                    }

                    if (!empty($value) && !is_string($value)) {
                        $label_errors[] = sprintf(
                            /* translators: %s: label key name */
                            __('Label "%s" must be a string', 'ai-product-tools'),
                            esc_html($key)
                        );
                        continue;
                    }
                    if (is_string($value) && mb_strlen($value, 'UTF-8') > ChatbotConstants::MAX_LABEL_LENGTH) {
                        $label_errors[] = sprintf(
                            /* translators: %s: label key name */
                            __('Label "%s" exceeds 500 characters', 'ai-product-tools'),
                            esc_html($key)
                        );
                    }
                }
                if (!empty($label_errors)) {
                    $errors['localization.custom_labels'] = $label_errors;
                }
            }
        }

        return $errors;
    }

    /**
     * Sanitize settings.
     *
     * @param array $settings Settings to sanitize.
     * @return array Sanitized settings.
     */
    private function sanitize(array $settings): array {

        $settings['general']['enabled'] = (bool) ($settings['general']['enabled'] ?? false);
        $settings['general']['bot_name'] = sanitize_text_field($settings['general']['bot_name'] ?? '');
        $settings['general']['greeting_message'] = sanitize_textarea_field($settings['general']['greeting_message'] ?? '');
        $settings['general']['language'] = sanitize_text_field($settings['general']['language'] ?? 'en');
        $settings['general']['tone'] = sanitize_text_field($settings['general']['tone'] ?? 'friendly');
        $settings['general']['fallback_contact_url'] = esc_url_raw((string) ($settings['general']['fallback_contact_url'] ?? ''));
        $settings['general']['rate_limit_message'] = sanitize_textarea_field($settings['general']['rate_limit_message'] ?? '');

		$settings['general']['show_errors_to_user']      = rest_sanitize_boolean($settings['general']['show_errors_to_user'] ?? true);
		$settings['general']['use_custom_error_message'] = rest_sanitize_boolean($settings['general']['use_custom_error_message'] ?? false);
        $settings['general']['store_context'] = sanitize_textarea_field($settings['general']['store_context'] ?? '');
        $settings['general']['pre_chat_form_enabled'] = (bool) ($settings['general']['pre_chat_form_enabled'] ?? true);
        $settings['general']['terms_page_id'] = isset($settings['general']['terms_page_id']) && $settings['general']['terms_page_id'] > 0
            ? absint($settings['general']['terms_page_id'])
            : null;
        $settings['general']['privacy_page_id'] = isset($settings['general']['privacy_page_id']) && $settings['general']['privacy_page_id'] > 0
            ? absint($settings['general']['privacy_page_id'])
            : null;

        $whatsapp_number = $settings['general']['whatsapp_number'] ?? '';
        $settings['general']['whatsapp_number'] = preg_replace('/[^0-9+]/', '', sanitize_text_field($whatsapp_number));

        $settings['general']['knowledge_pages'] = $this->sanitizeKnowledgePages(
            (array) ($settings['general']['knowledge_pages'] ?? [])
        );

        $settings['general']['starter_messages_enabled'] = (bool) ($settings['general']['starter_messages_enabled'] ?? true);

        $starter_messages = (array) ($settings['general']['starter_messages'] ?? []);
        $sanitized_starter_messages = [];
        foreach (array_slice($starter_messages, 0, 5) as $msg) {
            if (is_string($msg) && !empty(trim($msg))) {
                $sanitized_starter_messages[] = mb_substr(sanitize_text_field($msg), 0, 100);
            }
        }
        $settings['general']['starter_messages'] = apply_filters('aipt_chatbot_validate_starter_messages', $sanitized_starter_messages);

        $settings['placement']['position'] = sanitize_text_field($settings['placement']['position'] ?? 'bottom-right');
        $settings['placement']['offset_x'] = absint($settings['placement']['offset_x'] ?? 20);
        $settings['placement']['offset_y'] = absint($settings['placement']['offset_y'] ?? 20);
        $settings['placement']['visibility_mode'] = sanitize_text_field($settings['placement']['visibility_mode'] ?? 'all');
        $settings['placement']['specific_pages'] = array_map('absint', (array) ($settings['placement']['specific_pages'] ?? []));
        $settings['placement']['excluded_pages'] = array_map('absint', (array) ($settings['placement']['excluded_pages'] ?? []));
        $settings['placement']['device'] = sanitize_text_field($settings['placement']['device'] ?? 'all');
        $settings['placement']['excluded_page_types'] = array_map('sanitize_text_field', (array) ($settings['placement']['excluded_page_types'] ?? []));

        $settings['appearance']['theme_preset'] = sanitize_text_field($settings['appearance']['theme_preset'] ?? 'light');

        $launcher_icon = sanitize_text_field($settings['appearance']['launcher_icon'] ?? 'chat');
        $settings['appearance']['launcher_icon'] = in_array($launcher_icon, self::VALID_WIDGET_ICONS, true) ? $launcher_icon : 'chat';

        $custom_launcher_icon_url = esc_url_raw((string) ($settings['appearance']['custom_launcher_icon_url'] ?? ''));
        $settings['appearance']['custom_launcher_icon_url'] = apply_filters('aipt_chatbot_validate_custom_launcher_icon', $custom_launcher_icon_url);

        $settings['appearance']['profile_picture_url'] = esc_url_raw((string) ($settings['appearance']['profile_picture_url'] ?? ''));

        $all_color_keys = [
            'primary', 'secondary', 'background', 'text',
            'user_bubble', 'bot_bubble', 'header_bg', 'footer_bg', 'button_hover', 'online_indicator',
            'hero_bg',
            'launcher_bg', 'launcher_icon_color',
            'attention_bubble_bg', 'attention_bubble_text', 'attention_badge_bg', 'attention_badge_text'
        ];
        foreach ($all_color_keys as $colorKey) {
            $color_value = $settings['appearance']['colors'][$colorKey] ?? '';
            $settings['appearance']['colors'][$colorKey] = $this->sanitizeHexColorSafe( is_string( $color_value ) ? $color_value : '' );
        }

        $settings['appearance']['font_family'] = sanitize_text_field($settings['appearance']['font_family'] ?? 'inherit');
        $settings['appearance']['border_radius'] = absint($settings['appearance']['border_radius'] ?? 12);

        $custom_css = $this->sanitizeCustomCss($settings['appearance']['custom_css'] ?? '');
        $settings['appearance']['custom_css'] = apply_filters('aipt_chatbot_validate_custom_css', $custom_css);

        $launcher_animation = sanitize_text_field($settings['appearance']['launcher_animation'] ?? 'pulse');
        $launcher_animation = apply_filters('aipt_chatbot_validate_launcher_animation', $launcher_animation);
        $settings['appearance']['launcher_animation'] = in_array($launcher_animation, self::VALID_LAUNCHER_ANIMATIONS, true)
            ? $launcher_animation
            : 'pulse';

        $hero_image_url = esc_url_raw((string) ($settings['appearance']['hero_image_url'] ?? ''));
        $settings['appearance']['hero_image_url'] = apply_filters('aipt_chatbot_validate_hero_image', $hero_image_url);

        $settings['appearance']['attention_bubble_enabled'] = (bool) ($settings['appearance']['attention_bubble_enabled'] ?? false);
        $attention_bubble_text = sanitize_text_field($settings['appearance']['attention_bubble_text'] ?? '');
        $settings['appearance']['attention_bubble_text'] = mb_substr($attention_bubble_text, 0, 50);

        $settings['appearance']['attention_badge_enabled'] = (bool) ($settings['appearance']['attention_badge_enabled'] ?? false);
        $attention_badge_text = sanitize_text_field($settings['appearance']['attention_badge_text'] ?? '1');
        $settings['appearance']['attention_badge_text'] = mb_substr($attention_badge_text, 0, 5);

        $index_profile = $settings['behavior']['index_profile'] ?? '0';
        $index_profile = sanitize_text_field((string) $index_profile);

        $index_profile_int = is_numeric($index_profile) ? (int) $index_profile : 0;
        $settings['behavior']['index_profile'] = (string) max(0, $index_profile_int);

        $max_products = absint($settings['behavior']['max_products'] ?? 3);
        $settings['behavior']['max_products'] = apply_filters('aipt_chatbot_validate_max_products', $max_products);

        $settings['behavior']['show_view_product'] = (bool) ($settings['behavior']['show_view_product'] ?? true);

        $show_add_to_cart = (bool) ($settings['behavior']['show_add_to_cart'] ?? true);
        $settings['behavior']['show_add_to_cart'] = apply_filters('aipt_chatbot_validate_show_add_to_cart', $show_add_to_cart);

        $settings['behavior']['show_stock_status'] = (bool) ($settings['behavior']['show_stock_status'] ?? true);

        $response_length = sanitize_text_field($settings['behavior']['response_length'] ?? 'balanced');
        $settings['behavior']['response_length'] = apply_filters('aipt_chatbot_validate_response_length', $response_length);

        $recommendation_mode = (string) ($settings['behavior']['product_recommendation_mode'] ?? 'auto');
        $recommendation_mode = apply_filters('aipt_chatbot_validate_recommendation_mode', $recommendation_mode);
        $settings['behavior']['product_recommendation_mode'] = in_array($recommendation_mode, self::VALID_RECOMMENDATION_MODES, true)
            ? $recommendation_mode
            : 'auto';

        $default_provider = self::getDefaults()['behavior']['provider'];
        $input_provider = sanitize_text_field($settings['behavior']['provider'] ?? $default_provider);
        $valid_provider = in_array($input_provider, self::VALID_PROVIDERS, true) ? $input_provider : $default_provider;
        $settings['behavior']['provider'] = $valid_provider;

        $input_model = sanitize_text_field($settings['behavior']['model'] ?? '');
        if ($input_provider !== $valid_provider || empty($input_model)) {
            $settings['behavior']['model'] = $this->getDefaultModelForProvider($valid_provider);
        } else {
            $settings['behavior']['model'] = $input_model;
        }

        $settings['rate_limit']['enabled'] = (bool) ($settings['rate_limit']['enabled'] ?? true);
        $settings['rate_limit']['max_requests'] = max(1, min(200, absint($settings['rate_limit']['max_requests'] ?? 10)));
        $settings['rate_limit']['window_seconds'] = max(10, min(3600, absint($settings['rate_limit']['window_seconds'] ?? 60)));
        $key_mode = sanitize_text_field($settings['rate_limit']['key_mode'] ?? 'session');
        $settings['rate_limit']['key_mode'] = in_array($key_mode, self::VALID_KEY_MODES, true) ? $key_mode : 'session';

        $settings['provider_cooldown']['enabled'] = (bool) ($settings['provider_cooldown']['enabled'] ?? true);
        $settings['provider_cooldown']['seconds'] = max(5, min(600, absint($settings['provider_cooldown']['seconds'] ?? 60)));
        $settings['provider_cooldown']['enqueue_on_failure'] = (bool) ($settings['provider_cooldown']['enqueue_on_failure'] ?? false);

        $settings['localization']['auto_detect_enabled'] = (bool) ($settings['localization']['auto_detect_enabled'] ?? true);

        $fallback_language = sanitize_text_field($settings['localization']['fallback_language'] ?? 'en');
        $settings['localization']['fallback_language'] = in_array($fallback_language, self::VALID_LANGUAGES, true)
            ? $fallback_language
            : 'en';

        $custom_labels = (array) ($settings['localization']['custom_labels'] ?? []);
        $sanitized_labels = [];
        foreach ($custom_labels as $key => $value) {

            if (in_array($key, self::ALL_LABEL_KEYS, true) && is_string($value) && !empty(trim($value))) {
                $sanitized_labels[$key] = mb_substr(sanitize_textarea_field($value), 0, 500);
            }
        }
        $settings['localization']['custom_labels'] = apply_filters('aipt_chatbot_validate_custom_labels', $sanitized_labels);

        return $settings;
    }

    /**
     * Sanitize knowledge pages to existing, published page IDs.
     *
     * @param array $knowledge_pages Raw knowledge page IDs.
     * @return array Sanitized knowledge page IDs.
     */
    private function sanitizeKnowledgePages(array $knowledge_pages): array {
        $knowledge_pages = array_values(array_filter(array_map('absint', $knowledge_pages)));

        return array_values(array_filter($knowledge_pages, static function ($page_id) {
            $page = get_post($page_id);
            return $page
                && isset($page->post_type, $page->post_status)
                && $page->post_type === 'page'
                && $page->post_status === 'publish';
        }));
    }

    /**
     * Clear cached settings.
     *
     * @return void
     */
    public function clearCache(): void {
        $this->cached_settings = null;
    }

    /**
     * Sanitize custom CSS to prevent XSS attacks.
     *
     * Removes dangerous CSS patterns while preserving valid styles.
     *
     * @param string $css Raw CSS input.
     * @return string Sanitized CSS.
     */
    private function sanitizeCustomCss(string $css): string {

        $css = wp_strip_all_tags($css);

        $dangerous_patterns = [

            '/expression\s*\(/i',
            '/javascript\s*:/i',
            '/vbscript\s*:/i',
            '/data\s*:/i',

            '/behavior\s*:/i',
            '/-moz-binding\s*:/i',

            '/url\s*\(\s*["\']?\s*(?!https?:\/\/|\/|\.\/|\.\.\/|#)/i',

            '/@import/i',
            '/@charset/i',

            '/&#/i',

            '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/',
        ];

        foreach ($dangerous_patterns as $pattern) {
            $css = preg_replace($pattern, '', $css);
        }

        $css = substr($css, 0, 10000);

        return trim($css);
    }
}
