<?php
/**
 * Maintenance GENIUS - AJAX Handlers
 * Gestion de toutes les requêtes AJAX
 * Version 2.9.126
 */

if (!defined('ABSPATH')) {
    exit;
}

class MG_Ajax {

    /**
     * Enregistre les hooks AJAX
     */
    public static function init() {
        add_action('wp_ajax_mg_toggle', [__CLASS__, 'toggle']);
        add_action('wp_ajax_mg_save', [__CLASS__, 'save']);
        add_action('wp_ajax_mg_save_ai_key', [__CLASS__, 'save_ai_key']);
        add_action('wp_ajax_mg_clear_visitor_cache', [__CLASS__, 'clear_visitor_cache']);
        add_action('wp_ajax_mg_reset_design', [__CLASS__, 'reset_design']);
        add_action('wp_ajax_mg_apply_preset', [__CLASS__, 'apply_preset']);
        add_action('wp_ajax_mg_save_access', [__CLASS__, 'save_access']);
        add_action('wp_ajax_mg_get_blacklist_logs', [__CLASS__, 'get_blacklist_logs']);
        add_action('wp_ajax_mg_clear_blacklist_logs', [__CLASS__, 'clear_blacklist_logs']);

        add_action('wp_ajax_mg_create_token', [__CLASS__, 'create_token']);
        add_action('wp_ajax_mg_delete_token', [__CLASS__, 'delete_token']);
        add_action('wp_ajax_mg_get_tokens', [__CLASS__, 'get_tokens']);
        add_action('wp_ajax_mg_get_access_stats', [__CLASS__, 'get_access_stats']);
        add_action('wp_ajax_mg_get_access_logs', [__CLASS__, 'get_access_logs']);
        add_action('wp_ajax_mg_clear_access_logs', [__CLASS__, 'clear_access_logs']);
        add_action('wp_ajax_mg_resend_token_email', [__CLASS__, 'resend_token_email']);
        add_action('wp_ajax_mg_save_token_settings', [__CLASS__, 'save_token_settings']);

        add_action('wp_ajax_mg_activate_license', [__CLASS__, 'activate_license']);
        add_action('wp_ajax_mg_deactivate_license', [__CLASS__, 'deactivate_license']);

        add_action('wp_ajax_mg_chat_ask', [__CLASS__, 'chat_ask']);

        add_action('wp_ajax_mg_export_notification_emails', [__CLASS__, 'export_notification_emails']);
        add_action('wp_ajax_mg_clear_notification_emails', [__CLASS__, 'clear_notification_emails']);
        add_action('wp_ajax_nopriv_mg_subscribe_notification', [__CLASS__, 'subscribe_notification']);
        add_action('wp_ajax_mg_subscribe_notification', [__CLASS__, 'subscribe_notification']);

        // Update checker only for PRO (WP.org handles updates for free version)
        if ( get_option('mg_license_valid') ) {
            add_action('wp_ajax_mg_check_update', [__CLASS__, 'check_update']);
        }

        add_action('wp_ajax_mg_save_custom_preset', [__CLASS__, 'save_custom_preset']);
        add_action('wp_ajax_mg_apply_custom_preset', [__CLASS__, 'apply_custom_preset']);
        add_action('wp_ajax_mg_delete_custom_preset', [__CLASS__, 'delete_custom_preset']);
        add_action('wp_ajax_mg_export_custom_preset', [__CLASS__, 'export_custom_preset']);
        add_action('wp_ajax_mg_import_custom_preset', [__CLASS__, 'import_custom_preset']);
        add_action('wp_ajax_mg_export_all_presets', [__CLASS__, 'export_all_presets']);

        add_action('wp_ajax_mg_export_all_settings', [__CLASS__, 'export_all_settings']);
        add_action('wp_ajax_mg_import_all_settings', [__CLASS__, 'import_all_settings']);

        add_action('wp_ajax_mg_upload_lottie', [__CLASS__, 'upload_lottie']);

        add_action('wp_ajax_mg_reset_stats', [__CLASS__, 'reset_stats']);
        add_action('wp_ajax_mg_track_visit', [__CLASS__, 'track_visit']);
        add_action('wp_ajax_nopriv_mg_track_visit', [__CLASS__, 'track_visit']);

        add_action('wp_ajax_mg_monitoring_save', [__CLASS__, 'monitoring_save']);
        add_action('wp_ajax_mg_monitoring_test_check', [__CLASS__, 'monitoring_test_check']);
        add_action('wp_ajax_mg_monitoring_test_email', [__CLASS__, 'monitoring_test_email']);
        add_action('wp_ajax_mg_monitoring_clear_events', [__CLASS__, 'monitoring_clear_events']);
        add_action('wp_ajax_mg_monitoring_get_status', [__CLASS__, 'monitoring_get_status']);

        add_action('wp_ajax_nopriv_mg_visitor_login', [__CLASS__, 'visitor_login']);
        add_action('wp_ajax_mg_visitor_login', [__CLASS__, 'visitor_login']);
        add_action('wp_ajax_nopriv_mg_refresh_nonce', [__CLASS__, 'refresh_nonce']);
        add_action('wp_ajax_mg_refresh_nonce', [__CLASS__, 'refresh_nonce']);

        add_action('wp_ajax_mg_wizard_save', [__CLASS__, 'wizard_save']);

        add_action('wp_ajax_mg_apply_theme',  [__CLASS__, 'apply_theme_handler']);
        add_action('wp_ajax_mg_revert_theme', [__CLASS__, 'revert_theme_handler']);
    }

    /**
     * Sanitize a boolean POST value
     * @param string $key POST parameter name
     * @return bool
     */
    private static function post_bool($key) {
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in calling AJAX handlers
        $v = strtolower( sanitize_text_field( wp_unslash( $_POST[$key] ?? '' ) ) );
        return in_array( $v, array( 'true', '1', 'on', 'yes' ), true );
    }

    /**
     * Debug logging wrapper — only logs when WP_DEBUG is enabled
     */
    private static function debug_log($message) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug helper
            error_log('Maintenance GENIUS: ' . $message);
        }
    }

    /**
     * Vérifier le token Turnstile auprès de Cloudflare (v2.6.14)
     * @param string $token Le token Turnstile reçu du frontend
     * @param string $secret_key La clé secrète Turnstile
     * @return bool True si valide, false sinon
     */
    private static function verify_turnstile($token, $secret_key) {
        if (empty($token) || empty($secret_key)) {
            return false;
        }

        $response = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
            'timeout' => 10,
            'body' => [
                'secret' => $secret_key,
                'response' => $token,
                'remoteip' => sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) )
            ]
        ]);

        if (is_wp_error($response)) {
            self::debug_log('Turnstile verification failed - ' . $response->get_error_message());
            return true;
        }

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

        return !empty($result['success']);
    }

    /**
     * Vérifier si Turnstile est requis pour un formulaire spécifique
     * @param string $form_type 'notification' ou 'visitor'
     * @return array ['required' => bool, 'secret_key' => string]
     */
    private static function is_turnstile_required($form_type) {
        $settings = MG_Settings::load();
        
        if (empty($settings['turnstile_enabled']) || empty($settings['turnstile_secret_key'])) {
            return ['required' => false, 'secret_key' => ''];
        }

        $is_pro = get_option('mg_license_valid', false);
        if (!$is_pro) {
            return ['required' => false, 'secret_key' => ''];
        }

        $required = false;
        if ($form_type === 'notification' && !empty($settings['turnstile_on_notification'])) {
            $required = true;
        } elseif ($form_type === 'visitor' && !empty($settings['turnstile_on_visitor'])) {
            $required = true;
        }

        return [
            'required' => $required,
            'secret_key' => $settings['turnstile_secret_key'] ?? ''
        ];
    }

    /**
     * AJAX: Toggle maintenance ON/OFF
     */
    
    // TOGGLE — Active/désactive le mode maintenance
    // AJAX: wp_ajax_mg_toggle
    public static function toggle() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();
        $was_enabled = !empty($settings['maintenance_enabled']);
        $settings['maintenance_enabled'] = self::post_bool('enabled');
        MG_Settings::save($settings);

        MG_Main::flush_all_caches();

        if ($was_enabled && !$settings['maintenance_enabled']) {
            self::send_notifications();
        }

        wp_send_json_success([
            'enabled' => $settings['maintenance_enabled'],
            'message' => $settings['maintenance_enabled'] ? __('Maintenance mode enabled', 'maintenance-genius') : __('Maintenance mode disabled', 'maintenance-genius')
        ]);
    }

    /**
     * AJAX: Sauvegarde des paramètres
     */
    
    // SAVE AI KEY — v2.9.106 BYOK
    // AJAX: wp_ajax_mg_save_ai_key
    // Endpoint DÉDIÉ (hors blob mg_settings — règle audit S2).
    // Write-only : la clé n'est jamais renvoyée, seulement son statut.
    // Chiffrée AES-256 (wp_salt) en option autoload=false.
    public static function save_ai_key() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        // La constante wp-config a toujours priorité — refuser la confusion.
        if ( defined('MG_ANTHROPIC_KEY') && MG_ANTHROPIC_KEY ) {
            wp_send_json_error([
                'message' => __('A key is already defined in wp-config.php (MG_ANTHROPIC_KEY) and takes priority. Remove it first to manage the key here.', 'maintenance-genius')
            ]);
        }

        $clear = ! empty( $_POST['clear_key'] );
        if ( $clear ) {
            delete_option('mg_anthropic_key');
            wp_send_json_success([
                'message' => __('API key removed.', 'maintenance-genius'),
                'status'  => 'none',
                'last4'   => '',
            ]);
        }

        $raw_key = isset($_POST['api_key']) ? trim( sanitize_text_field( wp_unslash( $_POST['api_key'] ) ) ) : '';

        if ( empty( $raw_key ) ) {
            wp_send_json_error(['message' => __('Empty key. Paste your Anthropic API key (sk-ant-...).', 'maintenance-genius')]);
        }

        // Format Anthropic : sk-ant-... (borne large, longueur plausible)
        if ( ! preg_match('/^sk-ant-[A-Za-z0-9_\-]{20,200}$/', $raw_key) ) {
            wp_send_json_error(['message' => __('Invalid key format. An Anthropic key starts with sk-ant-.', 'maintenance-genius')]);
        }

        $encrypted = MG_Chat::encrypt_key( $raw_key );
        if ( empty( $encrypted ) ) {
            wp_send_json_error(['message' => __('Encryption failed (OpenSSL unavailable on this server). Use the MG_ANTHROPIC_KEY constant in wp-config.php instead.', 'maintenance-genius')]);
        }

        // Option dédiée, autoload=false — jamais chargée hors besoin.
        if ( false === get_option('mg_anthropic_key', false) ) {
            add_option('mg_anthropic_key', $encrypted, '', 'no');
        } else {
            update_option('mg_anthropic_key', $encrypted, 'no');
        }

        wp_send_json_success([
            'message' => __('API key saved. The AI chat now uses your key.', 'maintenance-genius'),
            'status'  => 'option',
            'last4'   => substr( $raw_key, -4 ),
        ]);
    }

    // SAVE — Sauvegarde TOUS les settings (60+ champs)
    // AJAX: wp_ajax_mg_save
    // Chaque $_POST est sanitizé individuellement selon son type
    public static function save() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();

        if ( isset( $_POST['page_mode'] ) ) {
            $raw_mode = sanitize_key( wp_unslash( $_POST['page_mode'] ) );
            $settings['page_mode'] = in_array( $raw_mode, ['maintenance', 'coming_soon'], true ) ? $raw_mode : 'maintenance';
        }

        if (isset($_POST['title'])) {
            $settings['title'] = sanitize_text_field( wp_unslash( $_POST['title'] ) );
        }
        if (isset($_POST['subtitle'])) {
            $settings['subtitle'] = sanitize_text_field( wp_unslash( $_POST['subtitle'] ) );
        }
        if (isset($_POST['message'])) {
            $settings['message'] = wp_kses_post( wp_unslash( $_POST['message'] ) );
        }
        if (isset($_POST['logo_url'])) {
            $settings['logo_url'] = esc_url_raw( wp_unslash( $_POST['logo_url'] ) );
        }

        if (isset($_POST['show_admin_button'])) {
            $settings['show_admin_button'] = self::post_bool('show_admin_button');
        }
        if (isset($_POST['visitor_access_enabled'])) {
            $settings['visitor_access_enabled'] = self::post_bool('visitor_access_enabled');
        }
        if (isset($_POST['visitor_password'])) {
            $new_password = sanitize_text_field( wp_unslash( $_POST['visitor_password'] ) );
            if (!empty($new_password) && !wp_check_password($new_password, $settings['visitor_password'])) {
                $settings['visitor_password'] = wp_hash_password($new_password);
            } elseif (empty($new_password) && self::post_bool('clear_password')) {
                $settings['visitor_password'] = '';
            }
        }
        // échouer TOUT l'enregistrement, ce qui bloquait aussi les autres réglages).
        // Le bouton dédié « Enregistrer l'accès » valide déjà côté client avec un message clair.
        if (!empty($settings['visitor_access_enabled']) && empty($settings['visitor_password'])) {
            $settings['visitor_access_enabled'] = false;
        }
        if (isset($_POST['visitor_access_duration'])) {
            $dur_raw = sanitize_text_field( wp_unslash( $_POST['visitor_access_duration'] ) );
            if ($dur_raw === 'custom') {
                $days = isset($_POST['visitor_access_custom_days']) ? absint( wp_unslash( $_POST['visitor_access_custom_days'] ) ) : 0;
                $days = max(1, min(365, $days));
                $settings['visitor_access_duration'] = $days * DAY_IN_SECONDS;
            } else {
                // Clamp 1 heure – 1 an.
                $settings['visitor_access_duration'] = max(3600, min(31536000, absint($dur_raw)));
            }
        }

        if (isset($_POST['rgpd_mode'])) {
            $settings['rgpd_mode'] = self::post_bool('rgpd_mode');
        }

        if (isset($_POST['seo_503_enabled'])) {
            $settings['seo_503_enabled'] = self::post_bool('seo_503_enabled');
        }
        if (isset($_POST['retry_after_hours'])) {
            $settings['retry_after_hours'] = absint( wp_unslash( $_POST['retry_after_hours'] ) );
        }
        if (isset($_POST['noindex_enabled'])) {
            $settings['noindex_enabled'] = self::post_bool('noindex_enabled');
        }

        if (isset($_POST['hide_credits'])) {
            $settings['hide_credits'] = self::post_bool('hide_credits');
        }

        if (isset($_POST['notification_enabled'])) {
            $settings['notification_enabled'] = self::post_bool('notification_enabled');
        }
        if (isset($_POST['notification_button_text'])) {
            $settings['notification_button_text'] = sanitize_text_field( wp_unslash( $_POST['notification_button_text'] ) );
        }
        if (isset($_POST['notification_success_msg'])) {
            $settings['notification_success_msg'] = sanitize_text_field( wp_unslash( $_POST['notification_success_msg'] ) );
        }

        if (isset($_POST['turnstile_enabled'])) {
            $settings['turnstile_enabled'] = self::post_bool('turnstile_enabled');
        }
        if (isset($_POST['turnstile_site_key'])) {
            $settings['turnstile_site_key'] = sanitize_text_field( wp_unslash( $_POST['turnstile_site_key'] ) );
        }
        if (isset($_POST['turnstile_secret_key'])) {
            $settings['turnstile_secret_key'] = sanitize_text_field( wp_unslash( $_POST['turnstile_secret_key'] ) );
        }
        if (isset($_POST['turnstile_on_notification'])) {
            $settings['turnstile_on_notification'] = self::post_bool('turnstile_on_notification');
        }
        if (isset($_POST['turnstile_on_visitor'])) {
            $settings['turnstile_on_visitor'] = self::post_bool('turnstile_on_visitor');
        }

        if (isset($_POST['countdown_enabled'])) {
            $settings['countdown_enabled'] = self::post_bool('countdown_enabled');
        }
        if (isset($_POST['countdown_date'])) {
            $settings['countdown_date'] = sanitize_text_field( wp_unslash( $_POST['countdown_date'] ) );
        }
        if (isset($_POST['countdown_time'])) {
            $settings['countdown_time'] = sanitize_text_field( wp_unslash( $_POST['countdown_time'] ) );
        }
        if (isset($_POST['countdown_title'])) {
            $settings['countdown_title'] = sanitize_text_field( wp_unslash( $_POST['countdown_title'] ) );
        }
        if (isset($_POST['countdown_end_text'])) {
            $settings['countdown_end_text'] = sanitize_text_field( wp_unslash( $_POST['countdown_end_text'] ) );
        }
        if (isset($_POST['countdown_style'])) {
            $settings['countdown_style'] = sanitize_text_field( wp_unslash( $_POST['countdown_style'] ) );
        }
        if (isset($_POST['countdown_show_days'])) {
            $settings['countdown_show_days'] = self::post_bool('countdown_show_days');
        }
        if (isset($_POST['countdown_show_hours'])) {
            $settings['countdown_show_hours'] = self::post_bool('countdown_show_hours');
        }
        if (isset($_POST['countdown_show_minutes'])) {
            $settings['countdown_show_minutes'] = self::post_bool('countdown_show_minutes');
        }
        if (isset($_POST['countdown_show_seconds'])) {
            $settings['countdown_show_seconds'] = self::post_bool('countdown_show_seconds');
        }
        if (isset($_POST['countdown_auto_disable'])) {
            $settings['countdown_auto_disable'] = self::post_bool('countdown_auto_disable');
        }

        if (isset($_POST['lottie_enabled'])) {
            $settings['lottie_enabled'] = self::post_bool('lottie_enabled');
        }
        if (isset($_POST['lottie_url'])) {
            $settings['lottie_url'] = esc_url_raw( wp_unslash( $_POST['lottie_url'] ) );
        }
        if (isset($_POST['lottie_position'])) {
            $settings['lottie_position'] = sanitize_text_field( wp_unslash( $_POST['lottie_position'] ) );
        }
        if (isset($_POST['lottie_size'])) {
            $settings['lottie_size'] = absint( wp_unslash( $_POST['lottie_size'] ) );
        }
        if (isset($_POST['lottie_speed'])) {
            $settings['lottie_speed'] = floatval( wp_unslash( $_POST['lottie_speed'] ) );
        }
        if (isset($_POST['lottie_loop'])) {
            $settings['lottie_loop'] = self::post_bool('lottie_loop');
        }
        if (isset($_POST['lottie_autoplay'])) {
            $settings['lottie_autoplay'] = self::post_bool('lottie_autoplay');
        }

        if (isset($_POST['video_bg_enabled'])) {
            $settings['video_bg_enabled'] = self::post_bool('video_bg_enabled');
        }
        if (isset($_POST['video_bg_url'])) {
            $settings['video_bg_url'] = esc_url_raw( wp_unslash( $_POST['video_bg_url'] ) );
        }
        if (isset($_POST['video_bg_opacity'])) {
            $settings['video_bg_opacity'] = min(100, max(10, absint( wp_unslash( $_POST['video_bg_opacity'] ) )));
        }
        if (isset($_POST['video_bg_muted'])) {
            $settings['video_bg_muted'] = self::post_bool('video_bg_muted');
        }

        if (isset($_POST['parallax_enabled'])) {
            $settings['parallax_enabled'] = self::post_bool('parallax_enabled');
        }
        if (isset($_POST['parallax_intensity'])) {
            $settings['parallax_intensity'] = min(100, max(10, absint( wp_unslash( $_POST['parallax_intensity'] ) )));
        }

        if (isset($_POST['stats_enabled'])) {
            $new_stats_enabled = self::post_bool('stats_enabled');
            if ($new_stats_enabled && empty($settings['stats_enabled'])) {
                $settings['stats_start_date'] = current_time('mysql');
            }
            $settings['stats_enabled'] = $new_stats_enabled;
        }

        if (isset($_POST['schedule_enabled'])) {
            $settings['schedule_enabled'] = self::post_bool('schedule_enabled');
        }
        if (isset($_POST['schedule_start_date'])) {
            $settings['schedule_start_date'] = sanitize_text_field( wp_unslash( $_POST['schedule_start_date'] ) );
        }
        if (isset($_POST['schedule_start_time'])) {
            $settings['schedule_start_time'] = sanitize_text_field( wp_unslash( $_POST['schedule_start_time'] ) );
        }
        if (isset($_POST['schedule_end_date'])) {
            $settings['schedule_end_date'] = sanitize_text_field( wp_unslash( $_POST['schedule_end_date'] ) );
        }
        if (isset($_POST['schedule_end_time'])) {
            $settings['schedule_end_time'] = sanitize_text_field( wp_unslash( $_POST['schedule_end_time'] ) );
        }
        if (isset($_POST['schedule_timezone'])) {
            $settings['schedule_timezone'] = sanitize_text_field( wp_unslash( $_POST['schedule_timezone'] ) );
        }

        $color_fields = [
            'bg_color_1', 'bg_color_2', 'title_color', 
            'message_color', 'button_color', 'container_bg_color'
        ];
        foreach ($color_fields as $field) {
            if (isset($_POST[$field])) {
                $settings[$field] = sanitize_hex_color( wp_unslash( $_POST[$field] ) );
            }
        }

        $opacity_fields = [
            'bg_color_1_opacity', 'bg_color_2_opacity', 'title_opacity',
            'message_opacity', 'button_opacity', 'bg_overlay_opacity',
            'container_bg_opacity', 'container_shadow_intensity'
        ];
        foreach ($opacity_fields as $field) {
            if (isset($_POST[$field])) {
                $settings[$field] = min(100, max(0, absint( wp_unslash( $_POST[$field] ) )));
            }
        }

        $bool_fields = [
            'bg_image_enabled', 'container_shadow_enabled', 'animation_enabled', 'rgpd_mode',
            'seo_503_enabled', 'noindex_enabled', 'sidebar_enabled', 'custom_bg_enabled', 'hide_credits',
            'purge_data_on_uninstall'
        ];
        foreach ($bool_fields as $field) {
            if (isset($_POST[$field])) {
                $settings[$field] = self::post_bool($field);
            }
        }

        if (isset($_POST['bg_image_url'])) {
            $settings['bg_image_url'] = esc_url_raw( wp_unslash( $_POST['bg_image_url'] ) );
        }

        if (isset($_POST['custom_bg_url'])) {
            $settings['custom_bg_url'] = esc_url_raw( wp_unslash( $_POST['custom_bg_url'] ) );
        }

        $dimension_fields = [
            'container_max_width', 'container_padding', 
            'container_border_radius', 'animation_duration',
            'retry_after_hours', 'title_size', 'message_size', 'logo_size'
        ];
        foreach ($dimension_fields as $field) {
            if (isset($_POST[$field])) {
                $settings[$field] = absint( wp_unslash( $_POST[$field] ) );
            }
        }

        if (isset($_POST['sidebar_position'])) {
            $settings['sidebar_position'] = in_array( sanitize_text_field( wp_unslash( $_POST['sidebar_position'] ) ), ['left', 'right']) 
                ? sanitize_text_field( wp_unslash( $_POST['sidebar_position'] ) ) 
                : 'right';
        }
        if (isset($_POST['sidebar_tab_color'])) {
            $settings['sidebar_tab_color'] = sanitize_hex_color( wp_unslash( $_POST['sidebar_tab_color'] ) );
        }
        
        $social_fields = ['social_facebook', 'social_twitter', 'social_instagram', 'social_linkedin', 'social_youtube', 'social_tiktok'];
        foreach ($social_fields as $field) {
            if (isset($_POST[$field])) {
                $settings[$field] = esc_url_raw( wp_unslash( $_POST[$field] ) );
            }
        }
        
        if (isset($_POST['contact_email'])) {
            $settings['contact_email'] = sanitize_email( wp_unslash( $_POST['contact_email'] ) );
        }
        if (isset($_POST['contact_phone'])) {
            $settings['contact_phone'] = sanitize_text_field( wp_unslash( $_POST['contact_phone'] ) );
        }
        if (isset($_POST['contact_whatsapp'])) {
            $settings['contact_whatsapp'] = sanitize_text_field( wp_unslash( $_POST['contact_whatsapp'] ) );
        }

        $allowed_fonts = array_keys(MG_Settings::get_fonts());
        if (isset($_POST['title_font']) && in_array( sanitize_text_field( wp_unslash( $_POST['title_font'] ) ), $allowed_fonts)) {
            $settings['title_font'] = sanitize_text_field( wp_unslash( $_POST['title_font'] ) );
        }
        if (isset($_POST['message_font']) && in_array( sanitize_text_field( wp_unslash( $_POST['message_font'] ) ), $allowed_fonts)) {
            $settings['message_font'] = sanitize_text_field( wp_unslash( $_POST['message_font'] ) );
        }

        $allowed_animations = array_keys(MG_Settings::get_animations());
        $animation_fields = ['logo_animation', 'text_animation', 'button_animation'];
        foreach ($animation_fields as $field) {
            if (isset($_POST[$field]) && in_array( sanitize_text_field( wp_unslash( $_POST[$field] ) ), $allowed_animations)) {
                $settings[$field] = sanitize_text_field( wp_unslash( $_POST[$field] ) );
            }
        }

        MG_Settings::save($settings);

        wp_send_json_success([
            'message' => __('Settings saved successfully!', 'maintenance-genius')
        ]);
    }

    /**
     * AJAX: Révoquer tous les accès visiteurs
     */
    
    // ACCÈS VISITEUR — Gestion cache, tokens, stats
    public static function clear_visitor_cache() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();
        $settings['visitor_token'] = wp_generate_password(32, false);
        MG_Settings::save($settings);

        wp_send_json_success([
            'message' => __('All visitor access has been revoked!', 'maintenance-genius')
        ]);
    }

    /**
     * AJAX: Réinitialiser le design
     */
    public static function reset_design() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();
        $settings = MG_Settings::reset_design($settings);
        MG_Settings::save($settings);

        wp_send_json_success([
            'message' => __('Design reset successfully!', 'maintenance-genius')
        ]);
    }

    /**
     * AJAX: Appliquer un preset de design
     */
    
    // PRESETS — Application, sauvegarde, import/export
    public static function apply_preset() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $preset_key = sanitize_text_field( wp_unslash( $_POST['preset'] ?? '' ) );
        $presets = MG_Settings::get_presets();

        if (!isset($presets[$preset_key])) {
            wp_send_json_error(['message' => __('Preset not found', 'maintenance-genius')]);
        }

        $preset = $presets[$preset_key];
        $settings = MG_Settings::load();

        $settings['bg_color_1'] = $preset['bg_color_1'];
        $settings['bg_color_2'] = $preset['bg_color_2'];
        $settings['title_color'] = $preset['title_color'];
        $settings['message_color'] = $preset['message_color'];
        $settings['button_color'] = $preset['button_color'];
        $settings['container_bg_color'] = $preset['container_bg_color'];
        $settings['container_bg_opacity'] = $preset['container_bg_opacity'] ?? 85;
        
        if (!empty($preset['bg_image_enabled'])) {
            $settings['bg_image_enabled'] = true;
            $settings['bg_image_url'] = $preset['bg_image_url'] ?? '';
            $settings['video_bg_enabled'] = false;
        } else {
            $settings['bg_image_enabled'] = false;
            $settings['bg_image_url'] = '';
        }

        if (!empty($preset['video_bg_enabled'])) {
            $settings['video_bg_enabled'] = true;
            $settings['video_bg_url'] = $preset['video_bg_url'] ?? '';
            $settings['video_bg_opacity'] = $preset['video_bg_opacity'] ?? 30;
            $settings['bg_image_enabled'] = false;
            $settings['lottie_enabled'] = false;
        } else if (empty($preset['bg_image_enabled'])) {
            $settings['video_bg_enabled'] = false;
        }

        $settings['lottie_enabled'] = false;
        $settings['lottie_url'] = '';

        MG_Settings::save($settings);

        wp_send_json_success([
            /* translators: %s: name or value */
            'message' => sprintf(__('Preset "%s" applied!', 'maintenance-genius'), $preset['name']),
            'preset' => $preset
        ]);
    }

    /**
     * AJAX: Sauvegarder les paramètres d'accès (whitelist/blacklist)
     */
    
    // ACCÈS — Rôles, IPs, géo-blocage, tokens
    public static function save_access() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();

        if (isset($_POST['whitelist_enabled'])) {
            $settings['whitelist_enabled'] = self::post_bool('whitelist_enabled');
        }
        if (isset($_POST['whitelist_urls'])) {
            $urls = sanitize_textarea_field( wp_unslash( $_POST['whitelist_urls'] ) );
            $settings['whitelist_urls'] = array_filter(array_map('trim', explode("\n", $urls)));
        }
        if (isset($_POST['whitelist_ips'])) {
            $ips = sanitize_textarea_field( wp_unslash( $_POST['whitelist_ips'] ) );
            $settings['whitelist_ips'] = array_filter(array_map('trim', explode("\n", $ips)));
        }
        if (isset($_POST['whitelist_roles'])) {
            $roles = is_array($_POST['whitelist_roles']) ? array_map('sanitize_text_field', wp_unslash($_POST['whitelist_roles'])) : [];
            $settings['whitelist_roles'] = array_map('sanitize_text_field', $roles);
        }
        if (isset($_POST['whitelist_user_agents'])) {
            $agents = sanitize_textarea_field( wp_unslash( $_POST['whitelist_user_agents'] ) );
            $settings['whitelist_user_agents'] = array_filter(array_map('trim', explode("\n", $agents)));
        }

        if (isset($_POST['blacklist_enabled'])) {
            $settings['blacklist_enabled'] = self::post_bool('blacklist_enabled');
        }
        if (isset($_POST['blacklist_ips'])) {
            $ips = sanitize_textarea_field( wp_unslash( $_POST['blacklist_ips'] ) );
            $settings['blacklist_ips'] = array_filter(array_map('trim', explode("\n", $ips)));
        }
        if (isset($_POST['blacklist_user_agents'])) {
            $agents = sanitize_textarea_field( wp_unslash( $_POST['blacklist_user_agents'] ) );
            $settings['blacklist_user_agents'] = array_filter(array_map('trim', explode("\n", $agents)));
        }
        if (isset($_POST['blacklist_referrers'])) {
            $refs = sanitize_textarea_field( wp_unslash( $_POST['blacklist_referrers'] ) );
            $settings['blacklist_referrers'] = array_filter(array_map('trim', explode("\n", $refs)));
        }
        if (isset($_POST['blacklist_countries'])) {
            $countries = is_array($_POST['blacklist_countries']) ? array_map('sanitize_text_field', wp_unslash($_POST['blacklist_countries'])) : [];
            $settings['blacklist_countries'] = array_map('sanitize_text_field', $countries);
        }
        if (isset($_POST['blacklist_message'])) {
            $settings['blacklist_message'] = sanitize_text_field( wp_unslash( $_POST['blacklist_message'] ) );
        }
        if (isset($_POST['blacklist_logging'])) {
            $settings['blacklist_logging'] = self::post_bool('blacklist_logging');
        }

        MG_Settings::save($settings);

        wp_send_json_success([
            'message' => __('Access settings saved!', 'maintenance-genius')
        ]);
    }

    /**
     * AJAX: Obtenir les logs de la blacklist
     */
    public static function get_blacklist_logs() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $logs = get_option('mg_blacklist_logs', []);
        
        $logs = array_slice($logs, -100);

        wp_send_json_success([
            'logs' => array_reverse($logs),
            'count' => count($logs)
        ]);
    }

    /**
     * AJAX: Vider les logs de la blacklist
     */
    public static function clear_blacklist_logs() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        delete_option('mg_blacklist_logs');

        wp_send_json_success([
            'message' => __('Logs cleared!', 'maintenance-genius')
        ]);
    }

    /**
     * AJAX: Refresh nonce visiteur (public)
     * Retourne un nonce frais pour contrer le cache mobile (bfcache, proxy)
     * @since 2.9.16
     */
    /**
     * Refresh visitor nonce (nopriv)
     * Intentionally accessible without nonce verification — this endpoint
     * serves to renew expired nonces for the frontend login/subscribe forms.
     */
    public static function refresh_nonce() {
        header('Content-Type: application/json; charset=utf-8');
        wp_send_json_success([
            'nonce' => wp_create_nonce('mg_visitor_nonce')
        ]);
    }

    /**
     * AJAX: Connexion visiteur (public)
     * Sécurisé avec nonce, hashage, cookie sécurisé et rate limiting
     */
    
    // VISITEUR — Login par mot de passe + Turnstile
    public static function visitor_login() {
        header('Content-Type: application/json; charset=utf-8');

        $ip = self::get_client_ip();
        $ip_hash = md5($ip . 'mg_salt');
        $transient_key = 'mg_login_attempts_' . $ip_hash;
        $lockout_key = 'mg_lockout_' . $ip_hash;

        $lockout_until = get_transient($lockout_key);
        if ($lockout_until) {
            $remaining = $lockout_until - time();
            $minutes = ceil($remaining / 60);
            wp_send_json_error([
                /* translators: %d: count/number */
                'message' => sprintf(__('Too many attempts. Try again in %d minute(s).', 'maintenance-genius'), $minutes),
                'locked' => true
            ]);
        }

        if (!wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ?? '' ) ), 'mg_visitor_nonce')) {
            wp_send_json_error(['message' => __('Session expired, please refresh the page', 'maintenance-genius')]);
        }

        $turnstile = self::is_turnstile_required('visitor');
        if ($turnstile['required']) {
            $token = isset($_POST['turnstile_token']) ? sanitize_text_field( wp_unslash( $_POST['turnstile_token'] ) ) : '';
            if (empty($token)) {
                wp_send_json_error(['message' => __('Please complete the security verification', 'maintenance-genius')]);
            }
            if (!self::verify_turnstile($token, $turnstile['secret_key'])) {
                wp_send_json_error(['message' => __('Security verification failed. Please try again.', 'maintenance-genius')]);
            }
        }

        $password = sanitize_text_field( wp_unslash( $_POST['password'] ?? '' ) );
        $settings = MG_Settings::load();

        if (empty($password)) {
            wp_send_json_error(['message' => __('Please enter the password', 'maintenance-genius')]);
        }

        if (wp_check_password($password, $settings['visitor_password'])) {
            delete_transient($transient_key);
            delete_transient($lockout_key);

            $duration = absint($settings['visitor_access_duration'] ?? 86400);
            if ($duration < 3600) { $duration = 86400; }
            $expiry = time() + $duration;

            $token = $settings['visitor_token'] ?? '';
            if (empty($token)) {
                $token = wp_generate_password(32, false);
                $settings['visitor_token'] = $token;
                MG_Settings::save($settings);
            }

            $cookie_hash = hash('sha256', $expiry . $token . SECURE_AUTH_KEY);
            $cookie_value = $expiry . '.' . $cookie_hash;

            setcookie('mg_visitor_access', $cookie_value, [
                'expires'  => $expiry,
                'path'     => '/',
                'secure'   => is_ssl(),
                'httponly' => true,
                'samesite' => 'Lax'
            ]);

            wp_send_json_success([
                'message'    => __('Access granted', 'maintenance-genius'),
                'expires_in' => $duration
            ]);
        } else {
            $attempts = (int) get_transient($transient_key);
            $attempts++;
            set_transient($transient_key, $attempts, 15 * MINUTE_IN_SECONDS);

            if ($attempts >= 5) {
                $lockout_until = time() + (15 * MINUTE_IN_SECONDS);
                set_transient($lockout_key, $lockout_until, 15 * MINUTE_IN_SECONDS);
                delete_transient($transient_key);
                
                wp_send_json_error([
                    'message' => __('Too many attempts. Try again in 15 minutes.', 'maintenance-genius'),
                    'locked' => true
                ]);
            }

            $remaining = 5 - $attempts;
            $message = 'Mot de passe incorrect';
            if ($remaining <= 2) {
                $message .= sprintf(' (%d tentative(s) restante(s))', $remaining);
            }
            
            wp_send_json_error(['message' => $message]);
        }
    }

    /**
     * Obtenir l'IP du client de manière sécurisée
     */
    private static function get_client_ip() {
        $ip_keys = ['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'];
        
        foreach ($ip_keys as $key) {
            if (!empty(sanitize_text_field( wp_unslash( $_SERVER[$key] ?? '' ) ))) {
                $ip = sanitize_text_field( wp_unslash( $_SERVER[$key] ?? '' ) );
                if (strpos($ip, ',') !== false) {
                    $ip = trim(explode(',', $ip)[0]);
                }
                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    return $ip;
                }
            }
        }
        
        return '0.0.0.0';
    }


    /**
     * AJAX: Créer un token d'accès
     */
    public static function create_token() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $name = sanitize_text_field( wp_unslash( $_POST['name'] ?? '' ) );
        $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) );
        $expiration = sanitize_text_field( wp_unslash( $_POST['expiration'] ?? '7d' ) );
        $custom_date = sanitize_text_field( wp_unslash( $_POST['custom_date'] ?? '' ) );
        $single_use = self::post_bool('single_use');
        $ip_restrict = sanitize_text_field( wp_unslash( $_POST['ip_restrict'] ?? '' ) );
        $send_email = self::post_bool('send_email');
        $include_password = sanitize_text_field( wp_unslash( $_POST['include_password'] ?? '' ) );

        if (empty($name)) {
            wp_send_json_error(['message' => __('Name is required', 'maintenance-genius')]);
        }

        $expires = self::calculate_expiration($expiration, $custom_date);

        $token = MG_Settings::create_access_token([
            'name'        => $name,
            'email'       => $email,
            'expires'     => $expires,
            'single_use'  => $single_use,
            'ip_restrict' => $ip_restrict,
        ]);

        if ($token === false) {
            $settings = MG_Settings::load();
            $limit = intval($settings['custom_access_limit'] ?? 25);
            if ($limit > 0 && MG_Settings::count_active_tokens() >= $limit) {
                wp_send_json_error([
                    /* translators: %d: count/number */
                    'message' => sprintf(__('Active token limit of %d reached. Delete expired tokens or increase the limit.', 'maintenance-genius'), $limit)
                ]);
            }
            wp_send_json_error([
                'message' => __('Token could not be saved to the database. Run the Diagnostic tab to investigate.', 'maintenance-genius')
            ]);
        }

        $access_url = home_url('?mg_access=' . $token);

        $email_sent = false;
        if ($send_email && !empty($email)) {
            $email_sent = self::send_token_email($email, $name, $access_url, $expires, $include_password);
        }

        wp_send_json_success([
            'message'    => __('Access created successfully!', 'maintenance-genius'),
            'token'      => $token,
            'url'        => $access_url,
            'email_sent' => $email_sent,
            'stats'      => MG_Settings::get_access_stats(),
        ]);
    }

    /**
     * Calculer la date d'expiration
     */
    private static function calculate_expiration($expiration, $custom_date = '') {
        $now = current_time('timestamp');

        switch ($expiration) {
            case '1h':
                return wp_date('Y-m-d H:i:s', $now + HOUR_IN_SECONDS);
            case '6h':
                return wp_date('Y-m-d H:i:s', $now + 6 * HOUR_IN_SECONDS);
            case '24h':
                return wp_date('Y-m-d H:i:s', $now + DAY_IN_SECONDS);
            case '48h':
                return wp_date('Y-m-d H:i:s', $now + 2 * DAY_IN_SECONDS);
            case '7d':
                return wp_date('Y-m-d H:i:s', $now + 7 * DAY_IN_SECONDS);
            case '14d':
                return wp_date('Y-m-d H:i:s', $now + 14 * DAY_IN_SECONDS);
            case '30d':
                return wp_date('Y-m-d H:i:s', $now + 30 * DAY_IN_SECONDS);
            case '90d':
                return wp_date('Y-m-d H:i:s', $now + 90 * DAY_IN_SECONDS);
            case 'custom':
                if (!empty($custom_date)) {
                    return wp_date('Y-m-d 23:59:59', strtotime($custom_date));
                }
                return wp_date('Y-m-d H:i:s', $now + 7 * DAY_IN_SECONDS);
            default:
                return wp_date('Y-m-d H:i:s', $now + 7 * DAY_IN_SECONDS);
        }
    }

    /**
     * Envoyer l'email avec le lien d'accès
     */
    private static function send_token_email($to, $name, $url, $expires, $password = '') {
        $site_name = get_bloginfo('name');
        $site_url = home_url();
        $expires_formatted = date_i18n('j F Y à H:i', strtotime($expires));
        
        $settings = MG_Settings::load();
        $has_visitor_password = !empty($settings['visitor_access_enabled']) && !empty($settings['visitor_password']);

        $subject = sprintf('[%s] ' . __('Your visitor access', 'maintenance-genius'), $site_name);

        $message = sprintf(
            /* translators: %s: name or value */
            __("Hello %s,", 'maintenance-genius') . "\n\n" .
            /* translators: %s: name or value */
            __("You have received visitor access to the site %s.", 'maintenance-genius') . "\n\n" .
            /* translators: %s: name or value */
            "🔗 " . __("YOUR DIRECT ACCESS LINK:", 'maintenance-genius') . "\n%s\n\n" .
            /* translators: %s: name or value */
            "⏰ " . __("This link expires on:", 'maintenance-genius') . " %s\n\n",
            $name,
            $site_name,
            $url,
            $expires_formatted
        );
        
        if ($has_visitor_password && !empty($password)) {
            $message .= sprintf(
                "🔐 " . __("ALTERNATIVE - Password access:", 'maintenance-genius') . "\n" .
                /* translators: %s: name or value */
                __("URL:", 'maintenance-genius') . " %s\n" .
                /* translators: %s: name or value */
                __("Password:", 'maintenance-genius') . " %s\n" .
                __('Click on "Visitor Access" and enter this password.', 'maintenance-genius') . "\n\n",
                $site_url,
                $password
            );
        } elseif ($has_visitor_password) {
            $message .= sprintf(
                "🔐 " . __("ALTERNATIVE - Password access:", 'maintenance-genius') . "\n" .
                /* translators: %s: name or value */
                __("You can also access the site via:", 'maintenance-genius') . " %s\n" .
                __('Use the "Visitor Access" button with the password that will be provided to you.', 'maintenance-genius') . "\n\n",
                $site_url
            );
        }
        
        $message .= "---\n" .
            __("This email was sent automatically by Maintenance GENIUS.", 'maintenance-genius');

        $headers = [
            'Content-Type: text/plain; charset=UTF-8',
            sprintf('From: %s <%s>', $site_name, get_option('admin_email')),
        ];

        return wp_mail($to, $subject, $message, $headers);
    }

    /**
     * AJAX: Supprimer un token
     */
    public static function delete_token() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $token = sanitize_text_field( wp_unslash( $_POST['token'] ?? '' ) );

        if (empty($token)) {
            wp_send_json_error(['message' => __('Invalid token', 'maintenance-genius')]);
        }

        if (MG_Settings::delete_access_token($token)) {
            wp_send_json_success([
                'message' => __('Access deleted!', 'maintenance-genius'),
                'stats'   => MG_Settings::get_access_stats(),
            ]);
        } else {
            wp_send_json_error(['message' => __('Token not found', 'maintenance-genius')]);
        }
    }

    /**
     * AJAX: Récupérer tous les tokens
     */
    public static function get_tokens() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $tokens = MG_Settings::get_access_tokens();
        $now = current_time('timestamp');
        $formatted = [];

        foreach ($tokens as $token => $data) {
            $is_expired = false;
            $expires_text = 'Jamais';

            if (!empty($data['expires'])) {
                $expires_time = strtotime($data['expires']);
                $is_expired = $expires_time <= $now;
                $expires_text = date_i18n('j M Y', $expires_time);
            }

            $last_visit_text = '-';
            if (!empty($data['last_visit'])) {
                $last_visit_time = strtotime($data['last_visit']);
                $diff = $now - $last_visit_time;

                if ($diff < HOUR_IN_SECONDS) {
                    $last_visit_text = sprintf('Il y a %d min', floor($diff / 60));
                } elseif ($diff < DAY_IN_SECONDS) {
                    $last_visit_text = sprintf('Il y a %dh', floor($diff / HOUR_IN_SECONDS));
                } else {
                    $last_visit_text = date_i18n('j M', $last_visit_time);
                }
            }

            $formatted[] = [
                'token'       => $token,
                'name'        => $data['name'],
                'email'       => $data['email'] ?? '',
                'url'         => home_url('?mg_access=' . $token),
                'expires'     => $expires_text,
                'expires_raw' => $data['expires'] ?? '',
                'is_expired'  => $is_expired,
                'single_use'  => $data['single_use'] ?? false,
                'used'        => $data['used'] ?? false,
                'visits'      => $data['visits'] ?? 0,
                'last_visit'  => $last_visit_text,
                'ip_restrict' => $data['ip_restrict'] ?? '',
                'created'     => $data['created'] ?? '',
            ];
        }

        usort($formatted, function($a, $b) {
            if ($a['is_expired'] !== $b['is_expired']) {
                return $a['is_expired'] ? 1 : -1;
            }
            return strtotime($b['created']) - strtotime($a['created']);
        });

        wp_send_json_success([
            'tokens' => $formatted,
            'stats'  => MG_Settings::get_access_stats(),
        ]);
    }

    /**
     * AJAX: Récupérer les stats
     */
    public static function get_access_stats() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        wp_send_json_success([
            'stats' => MG_Settings::get_access_stats(),
        ]);
    }

    /**
     * AJAX: Récupérer les logs d'accès
     */
    public static function get_access_logs() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $logs = MG_Settings::get_access_logs(100);

        $formatted = [];
        foreach ($logs as $log) {
            $ua = $log['user_agent'] ?? '';
            $device = 'Inconnu';

            if (strpos($ua, 'iPhone') !== false || strpos($ua, 'iPad') !== false) {
                $device = 'Safari/iOS';
            } elseif (strpos($ua, 'Android') !== false) {
                $device = 'Android';
            } elseif (strpos($ua, 'Chrome') !== false) {
                $device = 'Chrome';
            } elseif (strpos($ua, 'Firefox') !== false) {
                $device = 'Firefox';
            } elseif (strpos($ua, 'Safari') !== false) {
                $device = 'Safari';
            } elseif (strpos($ua, 'Edge') !== false) {
                $device = 'Edge';
            }

            $formatted[] = [
                'time'   => date_i18n('j M H:i', strtotime($log['time'])),
                'name'   => $log['name'],
                'ip'     => $log['ip'],
                'device' => $device,
            ];
        }

        wp_send_json_success([
            'logs'  => $formatted,
            'count' => count($formatted),
        ]);
    }

    /**
     * AJAX: Vider les logs d'accès
     */
    public static function clear_access_logs() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        MG_Settings::clear_access_logs();

        wp_send_json_success([
            'message' => __('Logs cleared!', 'maintenance-genius'),
        ]);
    }

    /**
     * AJAX: Renvoyer l'email d'un token
     */
    public static function resend_token_email() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $token = sanitize_text_field( wp_unslash( $_POST['token'] ?? '' ) );
        $tokens = MG_Settings::get_access_tokens();

        if (!isset($tokens[$token])) {
            wp_send_json_error(['message' => __('Token not found', 'maintenance-genius')]);
        }

        $data = $tokens[$token];

        if (empty($data['email'])) {
            wp_send_json_error(['message' => __('No email associated with this access', 'maintenance-genius')]);
        }

        $url = home_url('?mg_access=' . $token);
        $sent = self::send_token_email($data['email'], $data['name'], $url, $data['expires']);

        if ($sent) {
            wp_send_json_success(['message' => __('Email sent!', 'maintenance-genius')]);
        } else {
            wp_send_json_error(['message' => __('Error sending email', 'maintenance-genius')]);
        }
    }

    /**
     * AJAX: Sauvegarder les paramètres des tokens
     */
    public static function save_token_settings() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();

        if (isset($_POST['custom_access_limit'])) {
            $settings['custom_access_limit'] = absint( wp_unslash( $_POST['custom_access_limit'] ) );
        }
        if (isset($_POST['custom_access_max_days'])) {
            $settings['custom_access_max_days'] = absint( wp_unslash( $_POST['custom_access_max_days'] ) );
        }
        if (isset($_POST['custom_access_default_days'])) {
            $settings['custom_access_default_days'] = absint( wp_unslash( $_POST['custom_access_default_days'] ) );
        }

        MG_Settings::save($settings);

        wp_send_json_success([
            'message' => __('Settings saved!', 'maintenance-genius'),
        ]);
    }

    /**
     * AJAX: Activer une licence PRO
     */
    
    // LICENCE — Activation/Désactivation via WHMCS ou master key
    public static function activate_license() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $license_key = isset($_POST['license_key']) ? sanitize_text_field( wp_unslash( $_POST['license_key'] ) ) : '';

        if (empty($license_key)) {
            wp_send_json_error(['message' => __('Please enter a license key', 'maintenance-genius')]);
        }

        $result = MG_License::activate($license_key);

        if ($result['success']) {
            wp_send_json_success([
                'message' => $result['message'] . ' ' . __('Reloading...', 'maintenance-genius'),
                'reload' => true
            ]);
        } else {
            wp_send_json_error(['message' => $result['message']]);
        }
    }

    /**
     * AJAX: Désactiver la licence PRO
     */
    public static function deactivate_license() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $result = MG_License::deactivate();

        wp_send_json_success([
            'message' => $result['message'],
            'reload' => true
        ]);
    }

    /**
     * AJAX: Chat IA - Poser une question
     */
    
    // CHAT IA — Interface avec l'API Anthropic (Claude)
    public static function chat_ask() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $question = isset($_POST['question']) ? sanitize_text_field( wp_unslash( $_POST['question'] ) ) : '';

        if (empty($question)) {
            wp_send_json_error(['message' => __('Please enter a question.', 'maintenance-genius')]);
        }

        $result = MG_Chat::process_question($question);

        if ($result['success']) {
            wp_send_json_success([
                'message' => $result['message'],
                'remaining' => isset($result['remaining']) ? $result['remaining'] : -1
            ]);
        } else {
            wp_send_json_error([
                'message' => $result['message'],
                'limit_reached' => isset($result['limit_reached']) ? $result['limit_reached'] : false
            ]);
        }
    }

    /**
     * AJAX: Exporter les emails de notification en CSV
     */
    public static function export_notification_emails() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $emails = MG_Settings::get_notification_emails();

        if (empty($emails)) {
            wp_send_json_error(['message' => __('No emails to export', 'maintenance-genius')]);
        }

        $csv = "Email,Date d'inscription\n";
        foreach ($emails as $email_data) {
            $csv .= '"' . $email_data['email'] . '","' . $email_data['date'] . '"' . "\n";
        }

        wp_send_json_success(['csv' => $csv]);
    }

    /**
     * AJAX: Vider les emails de notification
     */
    public static function clear_notification_emails() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        if ( ! MG_Settings::set_notification_emails( [] ) ) {
            wp_send_json_error(['message' => __('Deletion failed. Please try again.', 'maintenance-genius')]);
        }

        wp_send_json_success(['message' => __('Emails deleted', 'maintenance-genius')]);
    }

    /**
     * AJAX: Inscription à la notification (publique)
     */
    
    // NOTIFICATIONS EMAIL — Inscription visiteurs
    public static function subscribe_notification() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_visitor_nonce', 'nonce');

        $visitor_ip = self::get_validated_visitor_ip();
        if ( self::is_rate_limited( 'subscribe', $visitor_ip, 3, HOUR_IN_SECONDS ) ) {
            wp_send_json_error( [ 'message' => __( 'Too many requests. Please try again later.', 'maintenance-genius' ) ] );
        }

        $turnstile = self::is_turnstile_required('notification');
        if ($turnstile['required']) {
            $token = isset($_POST['turnstile_token']) ? sanitize_text_field( wp_unslash( $_POST['turnstile_token'] ) ) : '';
            if (empty($token)) {
                wp_send_json_error(['message' => __('Please complete the security verification', 'maintenance-genius')]);
            }
            if (!self::verify_turnstile($token, $turnstile['secret_key'])) {
                wp_send_json_error(['message' => __('Security verification failed. Please try again.', 'maintenance-genius')]);
            }
        }

        $email = isset($_POST['email']) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : '';

        if (empty($email) || !is_email($email)) {
            wp_send_json_error(['message' => __('Invalid email address', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();

        // Bug corrigé : « Merci ! » était envoyé même quand update_option échouait
        // silencieusement (object cache Redis). S3 : IP hashée, jamais stockée en clair.
        $mg_add_result = MG_Settings::add_notification_email( $email, $visitor_ip );

        if ( 'error' === $mg_add_result ) {
            wp_send_json_error(['message' => __('Registration failed. Please try again.', 'maintenance-genius')]);
        }

        // 'added' et 'duplicate' → même réponse (pas de fuite d'info sur les inscrits).
        wp_send_json_success(['message' => $settings['notification_success_msg'] ?? __('Thank you! You will be notified.', 'maintenance-genius')]);
    }

    /**
     * Envoyer les notifications par email lors de la désactivation de la maintenance
     */
    public static function send_notifications() {
        $settings = MG_Settings::load();
        $emails = MG_Settings::get_notification_emails();

        if (empty($emails)) {
            self::debug_log('Aucun email à notifier (désactivation manuelle)');
            return;
        }

        $email_count = count($emails);
        self::debug_log('Désactivation manuelle - ' . $email_count . ' email(s) à notifier');

        $site_name = get_bloginfo('name');
        $site_url = home_url();
        /* translators: %s: name or value */
        $subject = sprintf('🎉 ' . __('%s is now online!', 'maintenance-genius'), $site_name);
        
        $message = sprintf(
            __("Hello,", 'maintenance-genius') . "\n\n" .
            /* translators: %s: name or value */
            __("Good news! The site %s is now online.", 'maintenance-genius') . "\n\n" .
            /* translators: %s: name or value */
            __("You can visit it now:", 'maintenance-genius') . "\n%s\n\n" .
            __("Thank you for your patience!", 'maintenance-genius') . "\n\n" .
            "---\n" .
            __("This email was sent automatically because you requested to be notified.", 'maintenance-genius'),
            $site_name,
            $site_url
        );

        $headers = [
            'Content-Type: text/plain; charset=UTF-8',
            'From: ' . $site_name . ' <' . get_option('admin_email') . '>'
        ];

        $sent_count = 0;
        $failed_count = 0;
        
        foreach ($emails as $email_data) {
            $email = $email_data['email'] ?? '';
            if (!empty($email) && is_email($email)) {
                self::debug_log('Envoi email à ' . $email);
                if (wp_mail($email, $subject, $message, $headers)) {
                    $sent_count++;
                    self::debug_log('✓ Email envoyé à ' . $email);
                } else {
                    $failed_count++;
                    self::debug_log('✗ Échec envoi à ' . $email);
                }
            }
        }

        MG_Settings::set_notification_emails( [] );
        
        self::debug_log('Résultat - ' . $sent_count . ' envoyé(s), ' . $failed_count . ' échoué(s)');
    }

    /**
     * AJAX: Vérifier les mises à jour
     */
    public static function check_update() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $current_version = MG_VERSION;
        
        $update_url = 'https://maintenancegenius.ca/api/version.json';
        
        $response = wp_remote_get($update_url, [
            'timeout' => 10,
            'sslverify' => true
        ]);

        if (is_wp_error($response)) {
            wp_send_json_error([
                'message' => __('Unable to contact the update server. Please try again later.', 'maintenance-genius')
            ]);
            return;
        }

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

        if ($data && isset($data['version'])) {
            $latest_version = $data['version'];
            $update_available = version_compare($latest_version, $current_version, '>');

            wp_send_json_success([
                'update_available' => $update_available,
                'current_version' => $current_version,
                'latest_version' => $latest_version,
                'download_url' => $data['download_url'] ?? 'https://maintenancegenius.ca/telechargements/#download',
                'changelog' => $data['changelog'] ?? ''
            ]);
        } else {
            wp_send_json_error([
                'message' => __('Unable to verify the latest version. Please try again later.', 'maintenance-genius')
            ]);
        }
    }


    /**
     * Champs à sauvegarder dans un preset
     */
    private static function get_preset_fields() {
        return [
            'bg_color_1', 'bg_color_2', 'bg_opacity_1', 'bg_opacity_2',
            'title_color', 'title_opacity', 'message_color', 'message_opacity',
            'button_color', 'button_opacity', 'button_text_color', 'button_text_opacity',
            'container_bg', 'container_bg_opacity', 'container_shadow',
            'container_max_width', 'container_padding', 'container_border_radius',
            'title_font', 'message_font', 'title_size', 'message_size',
            'title_size_mobile', 'message_size_mobile',
            'animation',
            'custom_bg_enabled', 'custom_bg_url', 'bg_overlay',
            'lottie_enabled', 'lottie_url', 'lottie_position', 'lottie_size',
            'lottie_loop', 'lottie_autoplay', 'lottie_speed',
            'preset'
        ];
    }

    /**
     * AJAX: Sauvegarder un preset personnalisé
     */
    
    // PRESETS CUSTOM — CRUD + Import/Export
    public static function save_custom_preset() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        if (!get_option('mg_license_valid', false)) {
            wp_send_json_error(['message' => __('PRO license required', 'maintenance-genius')]);
        }

        $preset_name = sanitize_text_field( wp_unslash( $_POST['preset_name'] ?? '' ) );
        if (empty($preset_name)) {
            wp_send_json_error(['message' => __('Please enter a name for the preset', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();
        
        $preset_data = [];
        foreach (self::get_preset_fields() as $field) {
            if (isset($settings[$field])) {
                $preset_data[$field] = $settings[$field];
            }
        }

        $preset_id = 'preset_' . time() . '_' . wp_rand(1000, 9999);

        $custom_presets = $settings['custom_presets'] ?? [];
        $custom_presets[$preset_id] = [
            'name' => $preset_name,
            'created' => current_time('mysql'),
            'data' => $preset_data
        ];

        $settings['custom_presets'] = $custom_presets;
        MG_Settings::save($settings);

        wp_send_json_success([
            /* translators: %s: name or value */
            'message' => sprintf(__('Preset "%s" saved!', 'maintenance-genius'), $preset_name),
            'preset_id' => $preset_id,
            'preset' => $custom_presets[$preset_id]
        ]);
    }

    /**
     * AJAX: Appliquer un preset personnalisé
     */
    public static function apply_custom_preset() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $preset_id = sanitize_text_field( wp_unslash( $_POST['preset_id'] ?? '' ) );
        if (empty($preset_id)) {
            wp_send_json_error(['message' => __('Preset ID missing', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();
        $custom_presets = $settings['custom_presets'] ?? [];

        if (!isset($custom_presets[$preset_id])) {
            wp_send_json_error(['message' => __('Preset not found', 'maintenance-genius')]);
        }

        $preset_data = $custom_presets[$preset_id]['data'];
        foreach ($preset_data as $key => $value) {
            $settings[$key] = $value;
        }

        MG_Settings::save($settings);

        wp_send_json_success([
            /* translators: %s: name or value */
            'message' => sprintf(__('Preset "%s" applied!', 'maintenance-genius'), $custom_presets[$preset_id]['name']),
            'settings' => $preset_data
        ]);
    }

    /**
     * AJAX: Supprimer un preset personnalisé
     */
    public static function delete_custom_preset() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $preset_id = sanitize_text_field( wp_unslash( $_POST['preset_id'] ?? '' ) );
        if (empty($preset_id)) {
            wp_send_json_error(['message' => __('Preset ID missing', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();
        $custom_presets = $settings['custom_presets'] ?? [];

        if (!isset($custom_presets[$preset_id])) {
            wp_send_json_error(['message' => __('Preset not found', 'maintenance-genius')]);
        }

        $preset_name = $custom_presets[$preset_id]['name'];
        unset($custom_presets[$preset_id]);

        $settings['custom_presets'] = $custom_presets;
        MG_Settings::save($settings);

        wp_send_json_success([
            /* translators: %s: name or value */
            'message' => sprintf(__('Preset "%s" deleted', 'maintenance-genius'), $preset_name)
        ]);
    }

    /**
     * AJAX: Exporter un preset personnalisé
     */
    public static function export_custom_preset() {
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Permission denied', 'maintenance-genius'));
        }

        $preset_id = sanitize_text_field( wp_unslash( $_GET['preset_id'] ?? '' ) );
        if (empty($preset_id)) {
            wp_die(esc_html__('Preset ID missing', 'maintenance-genius'));
        }

        $settings = MG_Settings::load();
        $custom_presets = $settings['custom_presets'] ?? [];

        if (!isset($custom_presets[$preset_id])) {
            wp_die(esc_html__('Preset not found', 'maintenance-genius'));
        }

        $preset = $custom_presets[$preset_id];
        $export_data = [
            'plugin' => 'maintenance-genius',
            'version' => MG_VERSION,
            'export_date' => current_time('mysql'),
            'preset' => $preset
        ];

        $filename = sanitize_file_name('mg-preset-' . $preset['name'] . '-' . gmdate('Y-m-d') . '.json');

        header('Content-Type: application/json; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Pragma: no-cache');
        header('Expires: 0');

        echo json_encode($export_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        exit;
    }

    /**
     * AJAX: Exporter tous les presets
     */
    public static function export_all_presets() {
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Permission denied', 'maintenance-genius'));
        }

        $settings = MG_Settings::load();
        $custom_presets = $settings['custom_presets'] ?? [];

        if (empty($custom_presets)) {
            wp_die(esc_html__('No presets to export', 'maintenance-genius'));
        }

        $export_data = [
            'plugin' => 'maintenance-genius',
            'version' => MG_VERSION,
            'export_date' => current_time('mysql'),
            'presets_count' => count($custom_presets),
            'presets' => $custom_presets
        ];

        $filename = 'mg-all-presets-' . gmdate('Y-m-d') . '.json';

        header('Content-Type: application/json; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Pragma: no-cache');
        header('Expires: 0');

        echo json_encode($export_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        exit;
    }

    /**
     * AJAX: Importer un preset
     */
    public static function import_custom_preset() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        if (!get_option('mg_license_valid', false)) {
            wp_send_json_error(['message' => __('PRO license required', 'maintenance-genius')]);
        }

        if (!isset($_FILES['preset_file']) || $_FILES['preset_file']['error'] !== UPLOAD_ERR_OK) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
            wp_send_json_error(['message' => __('Error uploading the file', 'maintenance-genius')]);
        }

        $file_content = file_get_contents($_FILES['preset_file']['tmp_name']); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
        $import_data = json_decode($file_content, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            wp_send_json_error(['message' => __('Invalid JSON file', 'maintenance-genius')]);
        }

        if (empty($import_data['plugin']) || $import_data['plugin'] !== 'maintenance-genius') {
            wp_send_json_error(['message' => __('This file is not a valid Maintenance GENIUS export', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();
        $custom_presets = $settings['custom_presets'] ?? [];
        $imported_count = 0;

        if (isset($import_data['preset'])) {
            $preset = $import_data['preset'];
            $preset_id = 'preset_' . time() . '_' . wp_rand(1000, 9999);
            $preset['name'] = $preset['name'] . ' (' . __('imported', 'maintenance-genius') . ')';
            $preset['created'] = current_time('mysql');
            $custom_presets[$preset_id] = $preset;
            $imported_count = 1;
        }
        elseif (isset($import_data['presets']) && is_array($import_data['presets'])) {
            foreach ($import_data['presets'] as $preset) {
                $preset_id = 'preset_' . time() . '_' . wp_rand(1000, 9999);
                $preset['name'] = $preset['name'] . ' (' . __('imported', 'maintenance-genius') . ')';
                $preset['created'] = current_time('mysql');
                $custom_presets[$preset_id] = $preset;
                $imported_count++;
                usleep(1000);
            }
        } else {
            wp_send_json_error(['message' => __('Unrecognized file format', 'maintenance-genius')]);
        }

        $settings['custom_presets'] = $custom_presets;
        MG_Settings::save($settings);

        wp_send_json_success([
            /* translators: %d: count/number */
            'message' => sprintf(__('%d preset(s) imported successfully!', 'maintenance-genius'), $imported_count),
            'count' => $imported_count
        ]);
    }


    /**
     * AJAX: Upload fichier Lottie
     */
    
    // UPLOAD LOTTIE — Fichier .json/.lottie
    public static function upload_lottie() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        if (!get_option('mg_license_valid', false)) {
            wp_send_json_error(['message' => __('PRO license required', 'maintenance-genius')]);
        }

        if (!isset($_FILES['lottie_file']) || $_FILES['lottie_file']['error'] !== UPLOAD_ERR_OK) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
            $error_code = $_FILES['lottie_file']['error'] ?? 'unknown'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
            /* translators: %s: name or value */
            wp_send_json_error(['message' => sprintf(__('Upload error: code %s', 'maintenance-genius'), $error_code)]);
        }

        $file = $_FILES['lottie_file']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
        $filename = sanitize_file_name($file['name']);
        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

        if (!in_array($ext, ['json', 'lottie'])) {
            wp_send_json_error(['message' => __('Unsupported format. Use .json or .lottie', 'maintenance-genius')]);
        }

        if ($ext === 'json') {
            $content = file_get_contents($file['tmp_name']);
            $json = json_decode($content, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                wp_send_json_error(['message' => __('Invalid JSON file', 'maintenance-genius')]);
            }
            if (!isset($json['v']) || (!isset($json['layers']) && !isset($json['assets']))) {
                wp_send_json_error(['message' => __('This JSON file is not a valid Maintenance GENIUS export', 'maintenance-genius')]);
            }
        }

        $upload_dir = wp_upload_dir();
        $lottie_dir = $upload_dir['basedir'] . '/maintenance-genius/lottie';
        
        if (!file_exists($lottie_dir)) {
            wp_mkdir_p($lottie_dir);
        }

        $new_filename = 'lottie-' . time() . '-' . wp_rand(1000, 9999) . '.' . $ext;
        $dest_path = $lottie_dir . '/' . $new_filename;

        // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found -- Custom Lottie upload handler within nonce-verified, capability-checked AJAX
        if (!move_uploaded_file($file['tmp_name'], $dest_path)) {
            wp_send_json_error(['message' => __('Error saving the file', 'maintenance-genius')]);
        }

        $file_url = $upload_dir['baseurl'] . '/maintenance-genius/lottie/' . $new_filename;

        $settings = MG_Settings::load();
        $old_url = $settings['lottie_url'] ?? '';
        if (!empty($old_url) && strpos($old_url, '/maintenance-genius/lottie/') !== false) {
            $old_path = str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $old_url);
            if (file_exists($old_path)) {
                wp_delete_file( $old_path );
            }
        }

        $settings['lottie_url'] = $file_url;
        MG_Settings::save($settings);

        wp_send_json_success([
            'message' => __('Animation uploaded successfully!', 'maintenance-genius'),
            'url' => $file_url,
            'filename' => $new_filename
        ]);
    }

    /**
     * AJAX: Réinitialiser les statistiques (v2.6.27)
     */
    
    // STATISTIQUES — Visites, tracking
    public static function reset_stats() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();

        MG_Settings::reset_stats();
        $settings['stats_start_date'] = current_time('mysql');
        MG_Settings::save($settings);

        wp_send_json_success([
            'message' => __('Statistics reset successfully!', 'maintenance-genius')
        ]);
    }

    /**
     * AJAX: Tracker une visite (v2.6.27) - appelé par le frontend
     */
    public static function track_visit() {
        check_ajax_referer('mg_visitor_nonce', 'nonce');
        $settings = MG_Settings::load();

        if (empty($settings['stats_enabled'])) {
            wp_send_json_error(['message' => __('Stats disabled', 'maintenance-genius')]);
            return;
        }

        // Remplace l'ancien code qui acceptait HTTP_CLIENT_IP sans validation (spoofable).
        $visitor_ip = self::get_validated_visitor_ip();
        if ( empty( $visitor_ip ) ) {
            wp_send_json_error( [ 'message' => __( 'Invalid request', 'maintenance-genius' ) ] );
            return;
        }

        if ( self::is_rate_limited( 'track', $visitor_ip, 30, MINUTE_IN_SECONDS ) ) {
            // Silencieux (pas d'erreur visible côté JS pour ne pas alerter le bot)
            wp_send_json_success( [ 'tracked' => false ] );
            return;
        }

        // à chaque visite (élimine write amplification + lost updates sous charge).
        $ip_hash = md5($visitor_ip . wp_salt());
        $stats   = MG_Settings::record_visit($ip_hash);

        wp_send_json_success([
            'visits' => $stats['visits'],
            'unique' => $stats['unique'],
        ]);
    }

    /**
     * AJAX: Exporter TOUTES les configurations (v2.6.29)
     */
    
    // IMPORT/EXPORT CONFIG COMPLÈTE
    public static function export_all_settings() {
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();
        
        $exclude_keys = [
            'stats_visitors_ips',
            'notification_emails',
        ];
        
        foreach ($exclude_keys as $key) {
            unset($settings[$key]);
        }

        // Remote config (sans token/PIN hash pour sécurité)
        $remote_config = get_option('mg_remote_config', []);
        $remote_export = $remote_config;
        unset($remote_export['token_hash'], $remote_export['token_raw'], $remote_export['pin_hash']);

        $export_data = [
            'plugin' => 'maintenance-genius',
            'version' => MG_VERSION,
            'export_date' => current_time('mysql'),
            'site_url' => home_url(),
            'settings' => $settings,
            'remote_config' => $remote_export,
            'access_tokens' => get_option('mg_access_tokens', []),
            'cache_saved_states' => get_option('mg_cache_saved_states', []),
            'wizard_completed' => get_option('mg_wizard_completed', false),
        ];

        wp_send_json_success([
            'data' => $export_data,
            'filename' => 'maintenance-genius-config-' . gmdate('Y-m-d') . '.json'
        ]);
    }

    /**
     * AJAX: Importer TOUTES les configurations (v2.6.29)
     */
    public static function import_all_settings() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        if (empty($_FILES['config_file'])) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
            wp_send_json_error(['message' => __('No file uploaded', 'maintenance-genius')]);
        }

        $file = $_FILES['config_file']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
        
        $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if ($ext !== 'json') {
            wp_send_json_error(['message' => __('Invalid file format. Please upload a JSON file.', 'maintenance-genius')]);
        }

        $content = file_get_contents($file['tmp_name']);
        $import_data = json_decode($content, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            wp_send_json_error(['message' => __('Invalid JSON file', 'maintenance-genius')]);
        }

        if (empty($import_data['plugin']) || $import_data['plugin'] !== 'maintenance-genius') {
            wp_send_json_error(['message' => __('This file is not a valid Maintenance GENIUS configuration export.', 'maintenance-genius')]);
        }

        if (empty($import_data['settings']) || !is_array($import_data['settings'])) {
            wp_send_json_error(['message' => __('No settings found in the export file.', 'maintenance-genius')]);
        }

        $current_settings = MG_Settings::load();
        
        $preserve_keys = [
            'stats_visits',
            'stats_unique_visitors', 
            'stats_visitors_ips',
            'stats_start_date',
            'notification_emails',
        ];

        $new_settings = $import_data['settings'];
        
        foreach ($preserve_keys as $key) {
            if (isset($current_settings[$key])) {
                $new_settings[$key] = $current_settings[$key];
            }
        }

        MG_Settings::save($new_settings);

        // Restaurer remote config (merge avec config existante pour préserver token/PIN)
        if (!empty($import_data['remote_config']) && is_array($import_data['remote_config'])) {
            $current_remote = get_option('mg_remote_config', []);
            // Garder les secrets actuels, importer le reste
            $imported_remote = array_merge($current_remote, $import_data['remote_config']);
            // Restaurer les secrets originaux
            foreach (['token_hash', 'token_raw', 'pin_hash'] as $secret) {
                if (isset($current_remote[$secret])) {
                    $imported_remote[$secret] = $current_remote[$secret];
                }
            }
            update_option('mg_remote_config', $imported_remote, false);
        }

        // Restaurer access tokens
        if (isset($import_data['access_tokens']) && is_array($import_data['access_tokens'])) {
            update_option('mg_access_tokens', $import_data['access_tokens']);
        }

        // Restaurer cache saved states
        if (isset($import_data['cache_saved_states']) && is_array($import_data['cache_saved_states'])) {
            update_option('mg_cache_saved_states', $import_data['cache_saved_states']);
        }

        // Restaurer wizard flag
        if (isset($import_data['wizard_completed'])) {
            update_option('mg_wizard_completed', $import_data['wizard_completed']);
        }

        $source_site = $import_data['site_url'] ?? __('Unknown', 'maintenance-genius');
        $export_date = $import_data['export_date'] ?? __('Unknown', 'maintenance-genius');
        $export_version = $import_data['version'] ?? __('Unknown', 'maintenance-genius');

        wp_send_json_success([
            'message' => sprintf(
                /* translators: %s: name or value */
                __('Configuration imported successfully! Source: %1$s (v%2$s, exported on %3$s)', 'maintenance-genius'),
                $source_site,
                $export_version,
                $export_date
            )
        ]);
    }


    /**
     * Sauvegarde les paramètres monitoring
     */
    
    // MONITORING UPTIME — Config, test, événements
    public static function monitoring_save() {
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();

        $settings['monitoring_enabled']       = self::post_bool('monitoring_enabled');
        $settings['monitoring_independent']   = self::post_bool('monitoring_independent');
        $settings['monitoring_interval']      = intval($_POST['monitoring_interval'] ?? 5);
        $settings['monitoring_emails']        = sanitize_textarea_field( wp_unslash( $_POST['monitoring_emails'] ?? '' ) );
        $settings['monitoring_email_cooldown']= intval($_POST['monitoring_email_cooldown'] ?? 5);
        $settings['monitoring_subject_down']  = sanitize_text_field( wp_unslash( $_POST['monitoring_subject_down'] ?? '' ) );
        $settings['monitoring_body_down']     = sanitize_textarea_field( wp_unslash( $_POST['monitoring_body_down'] ?? '' ) );
        $settings['monitoring_subject_up']    = sanitize_text_field( wp_unslash( $_POST['monitoring_subject_up'] ?? '' ) );
        $settings['monitoring_body_up']       = sanitize_textarea_field( wp_unslash( $_POST['monitoring_body_up'] ?? '' ) );

        $allowed_intervals = [2, 5, 10, 15, 30];
        if (!in_array($settings['monitoring_interval'], $allowed_intervals)) {
            $settings['monitoring_interval'] = 5;
        }

        MG_Settings::save($settings);

        MG_Monitoring::manage_cron();

        wp_send_json_success([
            'message' => __('Monitoring settings saved.', 'maintenance-genius')
        ]);
    }

    /**
     * Test de vérification immédiat
     */
    public static function monitoring_test_check() {
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $result = MG_Monitoring::test_check();
        wp_send_json_success($result);
    }

    /**
     * Envoi d'un email de test
     */
    public static function monitoring_test_email() {
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $settings = MG_Settings::load();
        if (!empty($_POST['monitoring_emails'])) {
            $settings['monitoring_emails'] = sanitize_textarea_field( wp_unslash( $_POST['monitoring_emails'] ) );
        }

        $result = MG_Monitoring::send_test_email($settings);

        if ($result['success']) {
            wp_send_json_success($result);
        } else {
            wp_send_json_error($result);
        }
    }

    /**
     * Vider l'historique des événements
     */
    public static function monitoring_clear_events() {
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        MG_Monitoring::clear_events();
        wp_send_json_success([
            'message' => __('Event history cleared.', 'maintenance-genius')
        ]);
    }

    /**
     * Récupère le statut et les événements monitoring
     */
    public static function monitoring_get_status() {
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        wp_send_json_success([
            'status' => MG_Monitoring::get_status(),
            'events' => MG_Monitoring::get_events(50),
            'next_check' => wp_next_scheduled(MG_Monitoring::CRON_HOOK),
        ]);
    }

    /**
     * Sauvegarde les settings du wizard onboarding
     * @since 2.9.18
     */
    
    // WIZARD — Configuration initiale
    public static function wizard_save() {
        check_ajax_referer('mg_nonce', 'nonce');

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

        if (!empty($_POST['skip'])) {
            update_option('mg_wizard_completed', true);
            wp_send_json_success();
            return;
        }

        $settings = MG_Settings::load();

        if (isset($_POST['title'])) {
            $settings['title'] = sanitize_text_field(wp_unslash($_POST['title']));
        }
        if (isset($_POST['message'])) {
            $settings['message'] = sanitize_textarea_field(wp_unslash($_POST['message']));
        }

        if (!empty($_POST['preset'])) {
            $presets = MG_Settings::get_presets();
            $preset_key = sanitize_text_field( wp_unslash( $_POST['preset'] ) );
            if (isset($presets[$preset_key])) {
                $preset = $presets[$preset_key];
                foreach (['bg_color_1', 'bg_color_2', 'title_color', 'message_color', 'button_color', 'container_bg_color', 'container_bg_opacity', 'bg_image_url', 'bg_image_enabled'] as $field) {
                    if (isset($preset[$field])) {
                        $settings[$field] = $preset[$field];
                    }
                }
            }
        }

        $settings['visitor_access_enabled'] = !empty($_POST['visitor_enabled']);
        if (!empty($_POST['visitor_password'])) {
            $settings['visitor_password'] = wp_hash_password(sanitize_text_field(wp_unslash($_POST['visitor_password'])));
        }

        if (!empty($_POST['activate'])) {
            $settings['maintenance_enabled'] = true;
        }

        update_option('mg_settings', $settings);
        update_option('mg_wizard_completed', true);

        if (!empty($settings['maintenance_enabled'])) {
            MG_Main::flush_all_caches();
        }

        wp_send_json_success(['activated' => !empty($_POST['activate'])]);
    }

    // THÈMES COMPLETS v2.9.118 — Application + Annulation

    /**
     * AJAX: Appliquer un thème complet
     */
    public static function apply_theme_handler() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        $theme_id = sanitize_key(wp_unslash($_POST['theme_id'] ?? ''));
        $themes   = MG_Settings::get_themes();

        if (!isset($themes[$theme_id])) {
            wp_send_json_error(['message' => __('Theme not found', 'maintenance-genius')]);
        }

        if (!MG_Settings::apply_theme($theme_id)) {
            wp_send_json_error(['message' => __('Theme could not be applied. Run the Diagnostic tab to investigate.', 'maintenance-genius')]);
        }

        // Toast spécifique Lancement si le countdown vient d\'être activé (aucune date pré-existante).
        $settings_after = MG_Settings::load();
        $lancement_msg  = '';
        if ('lancement' === $theme_id && !empty($settings_after['countdown_enabled']) && empty($settings_after['countdown_date'])) {
            $lancement_msg = __('Theme applied — configure your launch date in the Content tab.', 'maintenance-genius');
        }

        /* translators: %s: theme label */
        $default_msg = sprintf(__('Theme "%s" applied! Your texts and advanced settings are preserved.', 'maintenance-genius'), esc_html($themes[$theme_id]['label']));

        wp_send_json_success([
            'message'      => $lancement_msg ?: $default_msg,
            'theme_id'     => $theme_id,
            'has_snapshot' => true,
        ]);
    }

    /**
     * AJAX: Annuler le dernier thème appliqué (restaure le snapshot)
     */
    public static function revert_theme_handler() {
        header('Content-Type: application/json; charset=utf-8');
        check_ajax_referer('mg_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Permission denied', 'maintenance-genius')]);
        }

        if (!MG_Settings::revert_theme()) {
            wp_send_json_error(['message' => __('No theme snapshot found. Nothing to revert.', 'maintenance-genius')]);
        }

        wp_send_json_success([
            'message'      => __('Theme reverted — your previous design has been restored.', 'maintenance-genius'),
            'has_snapshot' => false,
        ]);
    }

    /**
     * v2.9.52 SECURITY (S3) — Récupère l'IP visiteur validée.
     *
     * Lit HTTP_X_FORWARDED_FOR (premier IP de la chaîne) ou REMOTE_ADDR,
     * puis valide avec FILTER_VALIDATE_IP pour bloquer les valeurs spoofées.
     * NE FAIT PAS confiance à HTTP_CLIENT_IP (header trivialement spoofable).
     *
     * @return string IP validée (IPv4 ou IPv6), ou chaîne vide si invalide.
     */
    private static function get_validated_visitor_ip() {
        $candidates = [];

        // X-Forwarded-For : premier IP de la chaîne (= IP réelle si proxy/Cloudflare)
        if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
            $xff = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) );
            $parts = array_map( 'trim', explode( ',', $xff ) );
            if ( ! empty( $parts[0] ) ) {
                $candidates[] = $parts[0];
            }
        }

        // Fallback : REMOTE_ADDR (toujours présent, fiable derrière proxy direct)
        if ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
            $candidates[] = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
        }

        // Validation stricte : retourne le premier candidat IP valide
        foreach ( $candidates as $ip ) {
            if ( filter_var( $ip, FILTER_VALIDATE_IP ) !== false ) {
                return $ip;
            }
        }

        return '';
    }

    /**
     * v2.9.52 SECURITY (S4) — Vérifie si une IP a dépassé un quota d'actions.
     *
     * Utilise les transients WP comme compteur sliding window. Sans dépendance externe.
     *
     * @param string $action  Identifiant unique de l'action (ex: 'subscribe', 'track_visit').
     * @param string $ip      IP visiteur (déjà validée par get_validated_visitor_ip).
     * @param int    $max     Nombre max d'actions autorisées dans la période.
     * @param int    $period  Fenêtre de temps en secondes (ex: 3600 pour 1h).
     * @return bool  true si rate-limited (BLOQUER), false si OK (laisser passer).
     */
    private static function is_rate_limited( $action, $ip, $max, $period ) {
        if ( empty( $ip ) ) {
            // IP invalide = bloquer par sécurité
            return true;
        }

        $key = 'mg_rl_' . $action . '_' . md5( $ip );
        $count = (int) get_transient( $key );

        if ( $count >= $max ) {
            return true;
        }

        set_transient( $key, $count + 1, $period );
        return false;
    }
}
