<?php
/**
 * Maintenance GENIUS - Remote Control
 * Contrôle à distance ON/OFF via mobile sans login WordPress
 * Sécurité: Token 64 chars + PIN + IP Whitelist + Rate Limiting
 * Version 2.9.126
 * 
 * Usage: votresite.com/?mg_remote=TOKEN_SECRET
 */

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

class MG_Remote {

    /** Option keys */
    const OPT_CONFIG = 'mg_remote_config';
    const OPT_LOGS   = 'mg_remote_logs';
    
    /** Dernière erreur de validation (pour diagnostic) */
    private static $last_error = '';

    /**
     * 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('MG Remote: ' . $message);
        }
    }

    /**
     * Sanitize a boolean POST value
     */
    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Remote API uses token/PIN authentication
    private static function post_bool($key) {
        return sanitize_text_field(wp_unslash($_POST[$key] ?? '')) === 'true'; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
    }

    /**
     * Initialise les hooks
     */
    public static function init() {
        add_action('template_redirect', [__CLASS__, 'handle_remote_request'], 0);

        add_action('wp_ajax_nopriv_mg_remote_toggle', [__CLASS__, 'ajax_toggle']);
        add_action('wp_ajax_mg_remote_toggle', [__CLASS__, 'ajax_toggle']);

        add_action('wp_ajax_nopriv_mg_remote_verify_pin', [__CLASS__, 'ajax_verify_pin']);
        add_action('wp_ajax_mg_remote_verify_pin', [__CLASS__, 'ajax_verify_pin']);

        add_action('wp_ajax_nopriv_mg_remote_status', [__CLASS__, 'ajax_status']);
        add_action('wp_ajax_mg_remote_status', [__CLASS__, 'ajax_status']);

        add_action('wp_ajax_mg_remote_save_config', [__CLASS__, 'ajax_save_config']);
        add_action('wp_ajax_mg_remote_generate_token', [__CLASS__, 'ajax_generate_token']);
        add_action('wp_ajax_mg_remote_send_email', [__CLASS__, 'ajax_send_email']);
        add_action('wp_ajax_mg_remote_get_logs', [__CLASS__, 'ajax_get_logs']);
        add_action('wp_ajax_mg_remote_clear_logs', [__CLASS__, 'ajax_clear_logs']);
        add_action('wp_ajax_mg_remote_reset_ratelimit', [__CLASS__, 'ajax_reset_ratelimit']);
        
        add_action('wp_ajax_nopriv_mg_remote_manifest', [__CLASS__, 'ajax_manifest']);
        add_action('wp_ajax_mg_remote_manifest', [__CLASS__, 'ajax_manifest']);
    }


    /**
     * Retourne la config par défaut
     */
    public static function get_defaults() {
        return [
            'enabled'          => false,
            'token_hash'       => '',
            'token_raw'        => '',
            'token_hint'       => '',
            'token_created'    => '',
            'pin_hash'         => '',
            'pin_enabled'      => true,
            'ip_whitelist'     => [],
            'ip_whitelist_enabled' => false,
            'rate_limit_max'   => 5,
            'rate_limit_window'=> 15,
            'token_expires'    => 0,
            'require_https'    => false,
        ];
    }

    /**
     * Charge la config remote
     */
    public static function load_config() {
        $config = get_option(self::OPT_CONFIG, []);
        $config = wp_parse_args($config, self::get_defaults());
        
        if (isset($config['require_https'])) {
            $config['require_https'] = false;
        }
        
        return $config;
    }

    /**
     * Sauvegarde la config remote
     */
    public static function save_config($config) {
        update_option(self::OPT_CONFIG, $config, false);
    }


    /**
     * Génère un token cryptographiquement sécurisé de 64 caractères
     * @return string Le token en clair (à afficher UNE SEULE FOIS)
     */
    public static function generate_token() {
        return bin2hex(random_bytes(32));
    }

    /**
     * Hash un token pour stockage sécurisé
     */
    public static function hash_token($token) {
        return hash('sha256', $token . wp_salt('auth'));
    }

    /**
     * Hash un PIN pour stockage sécurisé
     */
    public static function hash_pin($pin) {
        return wp_hash_password($pin);
    }

    /**
     * Vérifie un PIN contre son hash
     */
    public static function verify_pin($pin, $hash) {
        return wp_check_password($pin, $hash);
    }


    /**
     * Vérifie le rate limiting par IP
     * @return array ['allowed' => bool, 'remaining' => int, 'retry_after' => int]
     */
    
    // SÉCURITÉ — Rate limiting, tentatives échouées
    private static function check_rate_limit() {
        $config = self::load_config();
        $ip = self::get_client_ip();
        $ip_hash = md5($ip . wp_salt());

        $transient_key = 'mg_remote_attempts_' . $ip_hash;
        $lockout_key   = 'mg_remote_lockout_' . $ip_hash;

        $lockout_until = get_transient($lockout_key);
        if ($lockout_until && $lockout_until > time()) {
            return [
                'allowed'     => false,
                'remaining'   => 0,
                'retry_after' => $lockout_until - time(),
            ];
        }

        $attempts = (int) get_transient($transient_key);
        $max = intval($config['rate_limit_max'] ?: 5);

        return [
            'allowed'     => $attempts < $max,
            'remaining'   => max(0, $max - $attempts),
            'retry_after' => 0,
        ];
    }

    /**
     * Enregistre une tentative échouée
     */
    private static function record_failed_attempt() {
        $config = self::load_config();
        $ip = self::get_client_ip();
        $ip_hash = md5($ip . wp_salt());

        $transient_key = 'mg_remote_attempts_' . $ip_hash;
        $lockout_key   = 'mg_remote_lockout_' . $ip_hash;

        $window = intval($config['rate_limit_window'] ?: 15) * MINUTE_IN_SECONDS;
        $max = intval($config['rate_limit_max'] ?: 5);

        $attempts = (int) get_transient($transient_key);
        $attempts++;
        set_transient($transient_key, $attempts, $window);

        if ($attempts >= $max) {
            set_transient($lockout_key, time() + $window, $window);
            delete_transient($transient_key);
        }
    }

    /**
     * Reset le compteur après succès
     */
    private static function reset_attempts() {
        $ip = self::get_client_ip();
        $ip_hash = md5($ip . wp_salt());
        delete_transient('mg_remote_attempts_' . $ip_hash);
        delete_transient('mg_remote_lockout_' . $ip_hash);
    }


    /**
     * Valide un token remote
     * @return bool|array False si invalide, config si valide
     */
    public static function validate_token($token) {
        if (empty($token) || strlen($token) < 32) {
            self::$last_error = 'Token trop court (' . strlen($token) . ' chars)';
            self::debug_log(self::$last_error);
            return false;
        }

        if (!get_option('mg_license_valid', false)) {
            self::$last_error = 'Licence PRO non active';
            self::debug_log(self::$last_error);
            return false;
        }

        $config = self::load_config();

        if (empty($config['enabled'])) {
            self::$last_error = 'Remote control désactivé';
            self::debug_log(self::$last_error);
            return false;
        }

        if (empty($config['token_hash'])) {
            self::$last_error = 'Aucun token_hash en base';
            self::debug_log(self::$last_error);
            return false;
        }

        if (!empty($config['require_https']) && !is_ssl()) {
            $site_is_ssl = (strpos(home_url(), 'https://') === 0);
            if ($site_is_ssl) {
                self::$last_error = 'HTTPS requis mais connexion HTTP';
                self::debug_log(self::$last_error);
                return false;
            }
        }

        if (!empty($config['token_expires']) && $config['token_expires'] < time()) {
            self::$last_error = 'Token expiré le ' . gmdate('Y-m-d H:i:s', $config['token_expires']);
            self::debug_log(self::$last_error);
            return false;
        }

        $provided_hash = self::hash_token($token);
        if (!hash_equals($config['token_hash'], $provided_hash)) {
            self::$last_error = 'Hash mismatch (reçu: ' . substr($token, 0, 6) . '...' . substr($token, -4) . ', ' . strlen($token) . ' chars)';
            self::debug_log('Token hash mismatch');
            self::debug_log('Provided hash: ' . substr($provided_hash, 0, 16) . '...');
            self::debug_log('Stored hash:   ' . substr($config['token_hash'], 0, 16) . '...');
            return false;
        }

        if (!empty($config['ip_whitelist_enabled']) && !empty($config['ip_whitelist'])) {
            $client_ip = self::get_client_ip();
            if (!self::is_ip_allowed($client_ip, $config['ip_whitelist'])) {
                self::$last_error = 'IP non autorisée: ' . $client_ip;
                self::log_action('ip_blocked', self::$last_error);
                self::debug_log(self::$last_error);
                return false;
            }
        }

        self::debug_log('Token validé avec succès');
        self::$last_error = '';
        return $config;
    }

    /**
     * Vérifie si une IP est dans la whitelist
     */
    private static function is_ip_allowed($ip, $whitelist) {
        if (empty($whitelist)) return true;

        foreach ($whitelist as $pattern) {
            $pattern = trim($pattern);
            if (empty($pattern)) continue;

            if ($pattern === $ip) return true;

            if (strpos($pattern, '/') !== false) {
                list($subnet, $bits) = explode('/', $pattern);
                $ip_long = ip2long($ip);
                $subnet_long = ip2long($subnet);
                if ($ip_long !== false && $subnet_long !== false) {
                    $mask = -1 << (32 - intval($bits));
                    if (($ip_long & $mask) === ($subnet_long & $mask)) {
                        return true;
                    }
                }
            }

            if (strpos($pattern, '*') !== false) {
                $regex = '/^' . str_replace(['.', '*'], ['\\.', '\\d+'], $pattern) . '$/';
                if (preg_match($regex, $ip)) return true;
            }
        }

        return false;
    }


    /**
     * Log une action remote
     */
    public static function log_action($action, $details = '') {
        $logs = get_option(self::OPT_LOGS, []);

        $logs[] = [
            'time'    => current_time('mysql'),
            'action'  => $action,
            'ip'      => self::get_client_ip(),
            'details' => $details,
            'ua'      => substr(sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ) ), 0, 150),
        ];

        if (count($logs) > 200) {
            $logs = array_slice($logs, -200);
        }

        update_option(self::OPT_LOGS, $logs, false);
    }


    /**
     * Intercepte les requêtes ?mg_remote=TOKEN
     */
    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Remote request uses token authentication
    
    // REMOTE PAGE — Intercepte ?mg_remote=TOKEN, valide, affiche
    // Auth: token SHA-256 + PIN bcrypt (pas de nonce WordPress)
    public static function handle_remote_request() {
        if (!isset($_GET['mg_remote'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
            return;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Remote request uses token authentication

        $token = sanitize_text_field( wp_unslash( $_GET['mg_remote'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
        self::debug_log('Requête reçue, token length=' . strlen($token) . ', hint=...' . substr($token, -4));

        $rate = self::check_rate_limit();
        if (!$rate['allowed']) {
            self::render_error_page(
                __('Trop de tentatives', 'maintenance-genius'),
                /* translators: %d: count/number */
                sprintf(__('Please try again in %d minute(s).', 'maintenance-genius'), ceil($rate['retry_after'] / 60))
            );
            exit;
        }

        $config = self::validate_token($token);
        if ($config === false) {
            self::record_failed_attempt();
            self::log_action('invalid_token', 'Token invalide' . (self::$last_error ? ' — ' . self::$last_error : ''));
            
            $debug_info = '';
            if (defined('WP_DEBUG') && WP_DEBUG && self::$last_error) {
                $debug_info = self::$last_error;
            }
            
            $user_msg = __('Invalid or expired token.', 'maintenance-genius');
            if (strpos(self::$last_error, 'Hash mismatch') !== false) {
                $user_msg = __('This link no longer matches the active token. Please regenerate a new token from the admin.', 'maintenance-genius');
            }
            
            self::render_error_page(
                __('Access denied', 'maintenance-genius'),
                $user_msg,
                $debug_info
            );
            exit;
        }

        self::reset_attempts();
        self::render_remote_page($token, $config);
        exit;
    }

    /**
     * AJAX: Vérifier le PIN et retourner le statut
     */
    
    // AJAX PUBLIC — Auth par token+PIN (pas de nonce WP)
    // phpcs:ignore NonceVerification — Remote API auth
    public static function ajax_verify_pin() {
        header('Content-Type: application/json; charset=utf-8');
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Remote PIN auth uses token+PIN, not nonces

        $token = sanitize_text_field( wp_unslash( $_POST['token'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
        $pin   = sanitize_text_field( wp_unslash( $_POST['pin'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash

        $rate = self::check_rate_limit();
        if (!$rate['allowed']) {
            wp_send_json_error([
                /* translators: %d: count/number */
                'message' => sprintf(__('Too many attempts. Try again in %d min.', 'maintenance-genius'), ceil($rate['retry_after'] / 60)),
                'locked'  => true,
            ]);
        }

        $config = self::validate_token($token);
        if (!$config) {
            self::record_failed_attempt();
            wp_send_json_error(['message' => __('Token invalide', 'maintenance-genius')]);
        }

        if (!empty($config['pin_enabled']) && !empty($config['pin_hash'])) {
            if (empty($pin) || !self::verify_pin($pin, $config['pin_hash'])) {
                self::record_failed_attempt();
                self::log_action('invalid_pin', 'PIN incorrect');
                wp_send_json_error(['message' => __('Code PIN incorrect', 'maintenance-genius')]);
            }
        }

        self::reset_attempts();
        self::log_action('pin_verified', 'Authentification réussie');

        $session_token = bin2hex(random_bytes(16));
        $session_hash = hash('sha256', $session_token . wp_salt('auth'));
        // même si la session est rafraîchie en continu (fin du sliding window infini).
        set_transient('mg_remote_session_' . $session_hash, ['created' => time()], 15 * MINUTE_IN_SECONDS);

        $settings = MG_Settings::load();
        $mg_stats = MG_Settings::get_stats();
        wp_send_json_success([
            'session_token'      => $session_token,
            'maintenance_enabled'=> !empty($settings['maintenance_enabled']),
            'stats_visits'       => $mg_stats['visits'],
            'stats_unique'       => $mg_stats['unique'],
        ]);
    }

    /**
     * AJAX: Toggle maintenance ON/OFF (public, sécurisé par session token)
     */
    public static function ajax_toggle() {
        header('Content-Type: application/json; charset=utf-8');
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Remote toggle uses session token auth

        $token         = sanitize_text_field( wp_unslash( $_POST['token'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
        $session_token = sanitize_text_field( wp_unslash( $_POST['session_token'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
        $new_state     = self::post_bool('enabled');

        $config = self::validate_token($token);
        if (!$config) {
            wp_send_json_error(['message' => __('Token invalide', 'maintenance-genius')]);
        }

        $session_hash = hash('sha256', $session_token . wp_salt('auth'));
        $valid_session = get_transient('mg_remote_session_' . $session_hash);
        if (!$valid_session) {
            wp_send_json_error([
                'message'        => __('Session expired. Please re-enter your PIN.', 'maintenance-genius'),
                'session_expired'=> true,
            ]);
        }

        // Compat : anciennes sessions (valeur true) → created = maintenant (une seule fois).
        $session_created = ( is_array($valid_session) && isset($valid_session['created']) ) ? (int) $valid_session['created'] : time();
        if ( time() - $session_created > 2 * HOUR_IN_SECONDS ) {
            delete_transient('mg_remote_session_' . $session_hash);
            wp_send_json_error([
                'message'        => __('Session expired. Please re-enter your PIN.', 'maintenance-genius'),
                'session_expired'=> true,
            ]);
        }

        set_transient('mg_remote_session_' . $session_hash, ['created' => $session_created], 15 * MINUTE_IN_SECONDS);

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

        if ($was_enabled && !$new_state) {
            if (class_exists('MG_Ajax')) {
                MG_Ajax::send_notifications();
            }
        }

        $action_label = $new_state ? 'enabled' : 'disabled';
        self::log_action('toggle_' . $action_label, 'Maintenance ' . ($new_state ? 'activée' : 'désactivée') . ' via remote');

        wp_send_json_success([
            'enabled' => $new_state,
            'message' => $new_state
                ? __('✅ Maintenance ENABLED', 'maintenance-genius')
                : __('🟢 Maintenance DISABLED', 'maintenance-genius'),
        ]);
    }

    /**
     * AJAX: Statut actuel (requiert session valide)
     */
    public static function ajax_status() {
        header('Content-Type: application/json; charset=utf-8');
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Remote status uses session token auth

        $token         = sanitize_text_field( wp_unslash( $_POST['token'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
        $session_token = sanitize_text_field( wp_unslash( $_POST['session_token'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash

        $config = self::validate_token($token);
        if (!$config) {
            wp_send_json_error(['message' => __('Token invalide', 'maintenance-genius')]);
        }

        $session_hash = hash('sha256', $session_token . wp_salt('auth'));
        $mg_status_session = get_transient('mg_remote_session_' . $session_hash);
        if (!$mg_status_session) {
            wp_send_json_error(['message' => __('Session expired', 'maintenance-genius'), 'session_expired' => true]);
        }
        $mg_status_created = ( is_array($mg_status_session) && isset($mg_status_session['created']) ) ? (int) $mg_status_session['created'] : time();
        if ( time() - $mg_status_created > 2 * HOUR_IN_SECONDS ) {
            delete_transient('mg_remote_session_' . $session_hash);
            wp_send_json_error(['message' => __('Session expired', 'maintenance-genius'), 'session_expired' => true]);
        }

        $settings = MG_Settings::load();
        $mg_stats = MG_Settings::get_stats();
        wp_send_json_success([
            'maintenance_enabled'=> !empty($settings['maintenance_enabled']),
            'stats_visits'       => $mg_stats['visits'],
            'stats_unique'       => $mg_stats['unique'],
        ]);
    }


    /**
     * AJAX: Sauvegarder la config remote
     */
    
    // AJAX ADMIN — Protégé par nonce WordPress
    public static function ajax_save_config() {
        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')]);
        }

        $config = self::load_config();

        if (isset($_POST['remote_enabled'])) {
            $config['enabled'] = self::post_bool('remote_enabled');
        }

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

        if (!empty($_POST['remote_pin'])) {
            $pin = sanitize_text_field( wp_unslash( $_POST['remote_pin'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
            if (strlen($pin) >= 4 && strlen($pin) <= 8) {
                $config['pin_hash'] = self::hash_pin($pin);
            }
        }

        if (isset($_POST['ip_whitelist_enabled'])) {
            $config['ip_whitelist_enabled'] = self::post_bool('ip_whitelist_enabled');
        }
        if (isset($_POST['ip_whitelist'])) {
            $ips = sanitize_textarea_field( wp_unslash( $_POST['ip_whitelist'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
            $config['ip_whitelist'] = array_filter(array_map('trim', explode("\n", $ips)));
        }

        if (isset($_POST['rate_limit_max'])) {
            $config['rate_limit_max'] = max(3, min(20, absint( wp_unslash( $_POST['rate_limit_max'] ) )));
        }
        if (isset($_POST['rate_limit_window'])) {
            $config['rate_limit_window'] = max(5, min(60, absint( wp_unslash( $_POST['rate_limit_window'] ) )));
        }

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

        if (isset($_POST['token_expires'])) {
            $expires = sanitize_text_field( wp_unslash( $_POST['token_expires'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
            if ($expires === '0' || $expires === 'never') {
                $config['token_expires'] = 0;
            } else {
                $config['token_expires'] = absint($expires);
            }
        }

        self::save_config($config);

        wp_send_json_success([
            'message'    => __('Remote configuration saved!', 'maintenance-genius'),
            'has_pin'    => !empty($config['pin_hash']),
            'ip_enabled' => !empty($config['ip_whitelist_enabled']),
        ]);
    }

    /**
     * AJAX: Générer un nouveau token
     */
    public static function ajax_generate_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')]);
        }

        $config = self::load_config();

        $config['enabled'] = true;

        $token = self::generate_token();
        $config['token_hash'] = self::hash_token($token);
        $config['token_raw']  = $token;
        $config['token_hint'] = '...' . substr($token, -4);
        $config['token_created'] = current_time('mysql');

        $expires_in = sanitize_text_field( wp_unslash( $_POST['expires_in'] ?? 'never' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
        switch ($expires_in) {
            case '30d':  $config['token_expires'] = time() + 30 * DAY_IN_SECONDS; break;
            case '90d':  $config['token_expires'] = time() + 90 * DAY_IN_SECONDS; break;
            case '180d': $config['token_expires'] = time() + 180 * DAY_IN_SECONDS; break;
            case '365d': $config['token_expires'] = time() + 365 * DAY_IN_SECONDS; break;
            default:     $config['token_expires'] = 0; break;
        }

        self::save_config($config);

        global $wpdb;
        $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_mg_remote_session_%')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

        self::log_action('token_generated', 'Nouveau token généré');

        $url = home_url('/?mg_remote=' . $token);

        wp_send_json_success([
            'message'    => __('New token generated! Copy it now, it will not be shown again.', 'maintenance-genius'),
            'token'      => $token,
            'url'        => $url,
            'hint'       => $config['token_hint'],
            'expires'    => $config['token_expires'] ? date_i18n('j M Y', $config['token_expires']) : __('Jamais', 'maintenance-genius'),
        ]);
    }

    /**
     * AJAX: Envoyer le lien remote par email avec instructions iOS/Android
     */
    public static function ajax_send_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')]);
        }

        $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $token = sanitize_text_field( wp_unslash( $_POST['token'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $url   = sanitize_text_field( wp_unslash( $_POST['url'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $include_pin = self::post_bool('include_pin');

        // Si pas de token/url fournis, utiliser le token stocké
        if (empty($url)) {
            $config = self::load_config();
            if (!empty($config['token_raw'])) {
                $token = $config['token_raw'];
                $url = home_url('/?mg_remote=' . $token);
            }
        }

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

        if (empty($url)) {
            wp_send_json_error(['message' => __('Aucun token généré. Veuillez d\'abord générer un token.', 'maintenance-genius')]);
        }

        $site_name = get_bloginfo('name');
        /* translators: %s: name or value */
        $subject = sprintf(__('🔧 [%s] Your Remote Control Access', 'maintenance-genius'), $site_name);

        $message  = __('Hello,', 'maintenance-genius') . "\n\n";
        /* translators: %s: name or value */
        $message .= sprintf(__('Here is your remote control link for %s:', 'maintenance-genius'), $site_name) . "\n\n";
        $message .= __('🔗 YOUR REMOTE LINK:', 'maintenance-genius') . "\n";
        $message .= $url . "\n\n";

        if ($include_pin) {
            $message .= __('🔐 YOUR PIN CODE:', 'maintenance-genius') . "\n";
            $message .= __('(The PIN code you configured in the plugin settings)', 'maintenance-genius') . "\n\n";
        }

        $message .= "━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
        $message .= __('📱 INSTALL ON YOUR PHONE', 'maintenance-genius') . "\n\n";

        $message .= __('iPhone (iOS):', 'maintenance-genius') . "\n";
        $message .= __('1. Open the link above in Safari', 'maintenance-genius') . "\n";
        $message .= __('2. Tap the share icon ⬆️ (bottom of screen)', 'maintenance-genius') . "\n";
        $message .= __('3. Scroll down and tap "Add to Home Screen"', 'maintenance-genius') . "\n";
        $message .= __('4. Name it "Maintenance" and tap "Add"', 'maintenance-genius') . "\n\n";

        $message .= __('Android:', 'maintenance-genius') . "\n";
        $message .= __('1. Open the link above in Chrome', 'maintenance-genius') . "\n";
        $message .= __('2. Tap the menu ⋮ (3 dots at top right)', 'maintenance-genius') . "\n";
        $message .= __('3. Tap "Add to Home screen"', 'maintenance-genius') . "\n";
        $message .= __('4. Confirm with "Add"', 'maintenance-genius') . "\n\n";

        $message .= "━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";

        $message .= __('🔒 SECURITY', 'maintenance-genius') . "\n";
        $message .= __('• Never share this link', 'maintenance-genius') . "\n";
        $message .= __('• If compromised, regenerate the token from the WordPress dashboard', 'maintenance-genius') . "\n";
        $message .= __('• PIN code is required at each login', 'maintenance-genius') . "\n";
        $message .= __('• 15-minute session, automatic expiration', 'maintenance-genius') . "\n\n";

        $message .= "---\n";
        $message .= __('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')),
        ];

        $sent = wp_mail($email, $subject, $message, $headers);

        if ($sent) {
            self::log_action('email_sent', 'Remote link sent to ' . $email);
            // Marquer l'étape 3 comme complétée
            $config = self::load_config();
            $config['email_sent'] = true;
            self::save_config($config);
            wp_send_json_success(['message' => __('Email sent!', 'maintenance-genius')]);
        } else {
            wp_send_json_error(['message' => __('Error sending email', 'maintenance-genius')]);
        }
    }

    /**
     * AJAX: Récupérer les logs remote
     */
    public static function ajax_get_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(self::OPT_LOGS, []);
        wp_send_json_success(['logs' => array_reverse(array_slice($logs, -50))]);
    }

    /**
     * AJAX: Vider les logs
     */
    public static function ajax_clear_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(self::OPT_LOGS);
        wp_send_json_success(['message' => __('Logs cleared', 'maintenance-genius')]);
    }

    /**
     * AJAX: Réinitialiser le rate limiting (purger tous les transients de blocage)
     */
    public static function ajax_reset_ratelimit() {
        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')]);
        }

        global $wpdb;
        $deleted = $wpdb->query($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s 
             OR option_name LIKE %s
             OR option_name LIKE %s
             OR option_name LIKE %s",
            '_transient_mg_remote_attempts_%',
            '_transient_timeout_mg_remote_attempts_%',
            '_transient_mg_remote_lockout_%',
            '_transient_timeout_mg_remote_lockout_%'
        ));

        self::log_action('ratelimit_reset', "Rate limiting reset ($deleted entries purged)");
        /* translators: %d: count/number */
        wp_send_json_success(['message' => sprintf(__('Rate limiting reset (%d entries purged)', 'maintenance-genius'), $deleted)]);
    }

    /**
     * AJAX: Web App Manifest pour PWA (Add to Home Screen)
     */
    public static function ajax_manifest() {
        header('Content-Type: application/manifest+json');
        header('Cache-Control: no-cache');
        $site_name = get_bloginfo('name');
        
        $config = self::load_config();
        
        echo json_encode([
            'name'             => $site_name . ' — Remote',
            'short_name'       => 'Maintenance',
            'description'      => __('Remote maintenance control', 'maintenance-genius'),
            'start_url'        => './',
            'scope'            => '/',
            'display'          => 'standalone',
            'orientation'      => 'portrait',
            'background_color' => '#0a0a12',
            'theme_color'      => '#0f0f1a',
            'icons'            => [
                [
                    'src'     => 'data:image/svg+xml,' . rawurlencode('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100" rx="20" fill="#14142a"/><text x="50" y="65" text-anchor="middle" font-size="50">🔧</text></svg>'),
                    'sizes'   => '192x192',
                    'type'    => 'image/svg+xml',
                    'purpose' => 'any',
                ],
                [
                    'src'     => 'data:image/svg+xml,' . rawurlencode('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100" rx="20" fill="#14142a"/><text x="50" y="65" text-anchor="middle" font-size="50">🔧</text></svg>'),
                    'sizes'   => '512x512',
                    'type'    => 'image/svg+xml',
                    'purpose' => 'maskable',
                ],
            ],
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }


    /**
     * IP du client
     */
    private static function get_client_ip() {
        foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'] 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';
    }


    private static function render_error_page($title, $message, $debug = '') {
        status_header(403);
        header('Content-Type: text/html; charset=utf-8');
        header('X-Robots-Tag: noindex, nofollow');
        header('Referrer-Policy: no-referrer');
        ?>
<!DOCTYPE html>
<html lang="<?php echo esc_attr(substr(get_locale(), 0, 2)); ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="robots" content="noindex,nofollow">
<title>Maintenance GENIUS - <?php echo esc_html($title); ?></title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f0f1a;color:#fff;display:flex;align-items:center;justify-content:center;min-height:100vh;min-height:100dvh}
.err{text-align:center;padding:40px 24px}
.err h1{font-size:2rem;color:#ff6b6b;margin-bottom:16px}
.err p{font-size:1rem;color:#8888aa;line-height:1.5}
.err .debug{margin-top:24px;padding:16px;background:#1a1a2e;border:1px solid #333;border-radius:8px;font-size:0.8rem;color:#ff9f43;font-family:monospace;text-align:left;word-break:break-all}
</style>
</head>
<body>
<div class="err">
<h1>🔒 <?php echo esc_html($title); ?></h1>
<p><?php echo esc_html($message); ?></p>
<?php if (!empty($debug)): ?>
<div class="debug">⚙️ Debug: <?php echo esc_html($debug); ?></div>
<?php endif; ?>
</div>
</body>
</html>
        <?php
    }


    
    // RENDU — Page HTML/CSS/JS standalone (hors WordPress)
    // Interface: écran PIN → toggle ON/OFF → statut
    // PWA: manifest.json si activé
    private static function render_remote_page($token, $config) {
        status_header(200);
        header('Content-Type: text/html; charset=utf-8');
        header('X-Robots-Tag: noindex, nofollow');
        header('Referrer-Policy: no-referrer');
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
        header('X-Frame-Options: DENY');

        $site_name = get_bloginfo('name');
        $ajax_url  = admin_url('admin-ajax.php');
        $has_pin   = !empty($config['pin_enabled']) && !empty($config['pin_hash']);
        $settings  = MG_Settings::load();
        $is_active = !empty($settings['maintenance_enabled']);
        ?>
<!DOCTYPE html>
<html lang="<?php echo esc_attr(substr(get_locale(), 0, 2)); ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="robots" content="noindex,nofollow">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="<?php echo esc_attr($site_name); ?>">
<meta name="theme-color" content="#0f0f1a">
<?php
$remote_url = home_url('/?mg_remote=' . urlencode($token));
$manifest = json_encode([
    'name'             => $site_name . ' — Remote',
    'short_name'       => 'Maintenance',
    'start_url'        => $remote_url,
    'scope'            => home_url('/'),
    'display'          => 'standalone',
    'orientation'      => 'portrait',
    'background_color' => '#0a0a12',
    'theme_color'      => '#0f0f1a',
    'icons'            => [
        ['src' => 'data:image/svg+xml,' . rawurlencode('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100" rx="20" fill="#14142a"/><text x="50" y="65" text-anchor="middle" font-size="50">🔧</text></svg>'), 'sizes' => '192x192', 'type' => 'image/svg+xml', 'purpose' => 'any'],
        ['src' => 'data:image/svg+xml,' . rawurlencode('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100" rx="20" fill="#14142a"/><text x="50" y="65" text-anchor="middle" font-size="50">🔧</text></svg>'), 'sizes' => '512x512', 'type' => 'image/svg+xml', 'purpose' => 'maskable'],
    ],
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
<link rel="manifest" href="data:application/manifest+json;charset=utf-8,<?php echo esc_attr( rawurlencode( $manifest ) ); ?>">
<title>🔧 Remote - <?php echo esc_html($site_name); ?></title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<?php // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- Standalone remote control page, not within WP context ?>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
/* ===== RESET & BASE ===== */
*{margin:0;padding:0;box-sizing:border-box}
:root{
    --bg:#0a0a12;--surface:#14142a;--surface-2:#1e1e3a;--border:#2a2a4a;
    --green:#00d68f;--green-glow:rgba(0,214,143,0.3);--green-dim:rgba(0,214,143,0.12);
    --red:#ff6b6b;--red-glow:rgba(255,107,107,0.3);--red-dim:rgba(255,107,107,0.12);
    --text:#e4e4f0;--text-dim:#6e6e8a;--text-muted:#45455e;
    --accent:#7c6aff;--accent-glow:rgba(124,106,255,0.25);
}
html{height:100%;-webkit-text-size-adjust:100%}
body{
    font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);
    min-height:100vh;min-height:100dvh;display:flex;flex-direction:column;
    align-items:center;justify-content:center;padding:20px;
    -webkit-font-smoothing:antialiased;
}

/* ===== PIN SCREEN ===== */
.pin-screen,.remote-screen{
    width:100%;max-width:380px;animation:fadeUp .5s ease;
}
.pin-screen{text-align:center}

.pin-logo{
    width:64px;height:64px;background:var(--surface-2);border-radius:20px;
    display:flex;align-items:center;justify-content:center;margin:0 auto 24px;
    font-size:28px;border:1px solid var(--border);
}
.pin-title{font-size:1.15rem;font-weight:600;margin-bottom:6px}
.pin-subtitle{font-size:.85rem;color:var(--text-dim);margin-bottom:32px}

.pin-dots{display:flex;gap:12px;justify-content:center;margin-bottom:32px;min-height:20px}
.pin-dot{
    width:16px;height:16px;border-radius:50%;border:2px solid var(--border);
    transition:all .2s ease;
}
.pin-dot.filled{background:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
.pin-dot.error{background:var(--red);border-color:var(--red);animation:shake .4s ease}

.pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;max-width:280px;margin:0 auto}
.pin-key{
    aspect-ratio:1.4;border-radius:16px;border:1px solid var(--border);
    background:var(--surface);color:var(--text);font-size:1.5rem;font-weight:600;
    cursor:pointer;display:flex;align-items:center;justify-content:center;
    transition:all .15s ease;-webkit-tap-highlight-color:transparent;
    user-select:none;
}
.pin-key:active{background:var(--surface-2);transform:scale(.95)}
.pin-key.action{font-size:1.1rem;color:var(--text-dim)}
.pin-key.action.confirm{color:var(--green)}

.pin-error-msg{
    color:var(--red);font-size:.8rem;margin-top:16px;min-height:20px;
    transition:opacity .3s;
}

/* ===== REMOTE SCREEN ===== */
.remote-screen{display:none}
.remote-screen.active{display:block}

.remote-header{
    display:flex;align-items:center;justify-content:space-between;
    margin-bottom:32px;
}
.site-info h2{font-size:1rem;font-weight:600}
.site-info p{font-size:.75rem;color:var(--text-dim);font-family:'JetBrains Mono',monospace}
.status-badge{
    font-size:.7rem;font-weight:600;padding:5px 12px;border-radius:20px;
    text-transform:uppercase;letter-spacing:0.5px;
}
.status-badge.on{background:var(--green-dim);color:var(--green)}
.status-badge.off{background:var(--red-dim);color:var(--red)}

/* ===== BIG TOGGLE ===== */
.toggle-container{
    display:flex;flex-direction:column;align-items:center;
    padding:40px 0;margin-bottom:32px;
}
.big-toggle{
    width:120px;height:64px;border-radius:64px;cursor:pointer;position:relative;
    background:var(--surface-2);border:2px solid var(--border);
    transition:all .4s cubic-bezier(.4,0,.2,1);
    -webkit-tap-highlight-color:transparent;
}
.big-toggle.on{
    background:var(--green-dim);border-color:var(--green);
    box-shadow:0 0 30px var(--green-glow),0 0 60px rgba(0,214,143,0.1);
}
.big-toggle.off{
    background:var(--red-dim);border-color:var(--red);
    box-shadow:0 0 30px var(--red-glow),0 0 60px rgba(255,107,107,0.1);
}
.big-toggle .thumb{
    width:52px;height:52px;border-radius:50%;
    position:absolute;top:4px;left:4px;
    background:#fff;
    transition:all .4s cubic-bezier(.4,0,.2,1);
    box-shadow:0 4px 12px rgba(0,0,0,0.3);
    display:flex;align-items:center;justify-content:center;
    font-size:22px;
}
.big-toggle.on .thumb{
    left:calc(100% - 56px);
    background:var(--green);color:#fff;
    box-shadow:0 4px 20px var(--green-glow);
}
.big-toggle.off .thumb{
    background:var(--red);color:#fff;
    box-shadow:0 4px 20px var(--red-glow);
}
.big-toggle.loading{opacity:.6;pointer-events:none}
.big-toggle.loading .thumb{animation:pulse 1s ease infinite}

.toggle-label{
    margin-top:20px;font-size:1.1rem;font-weight:700;letter-spacing:1px;
    transition:color .4s ease;
}
.toggle-label.on{color:var(--green)}
.toggle-label.off{color:var(--red)}

.toggle-hint{font-size:.75rem;color:var(--text-muted);margin-top:8px}

/* ===== STATS CARDS ===== */
.stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:24px}
.stat-card{
    background:var(--surface);border:1px solid var(--border);border-radius:16px;
    padding:16px;text-align:center;
}
.stat-value{font-size:1.6rem;font-weight:700;font-family:'JetBrains Mono',monospace;color:var(--text)}
.stat-label{font-size:.7rem;color:var(--text-dim);margin-top:4px;text-transform:uppercase;letter-spacing:.5px}

/* ===== FOOTER ===== */
.remote-footer{
    text-align:center;padding-top:16px;border-top:1px solid var(--border);
}
.remote-footer a{
    color:var(--text-dim);text-decoration:none;font-size:.75rem;
    transition:color .2s;
}
.remote-footer a:hover{color:var(--accent)}
.session-info{font-size:.65rem;color:var(--text-muted);margin-top:8px;font-family:'JetBrains Mono',monospace}

/* ===== TOAST ===== */
.toast{
    position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(100px);
    background:var(--surface-2);border:1px solid var(--border);color:var(--text);
    padding:12px 24px;border-radius:12px;font-size:.85rem;font-weight:500;
    z-index:9999;transition:transform .4s cubic-bezier(.4,0,.2,1);
    box-shadow:0 8px 32px rgba(0,0,0,0.4);white-space:nowrap;
}
.toast.visible{transform:translateX(-50%) translateY(0)}

/* ===== ANIMATIONS ===== */
@keyframes fadeUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
@keyframes shake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-6px)}40%,80%{transform:translateX(6px)}}
@keyframes pulse{0%,100%{opacity:.6}50%{opacity:1}}

/* ===== SAFE AREAS (notch) ===== */
@supports(padding:max(0px)){
    body{padding:max(20px,env(safe-area-inset-top)) 20px max(20px,env(safe-area-inset-bottom))}
}
</style>
</head>
<body>

<!-- ===== PIN SCREEN ===== -->
<?php if ($has_pin): ?>
<div class="pin-screen" id="pinScreen">
    <div class="pin-logo">🔧</div>
    <div class="pin-title"><?php echo esc_html($site_name); ?></div>
    <div class="pin-subtitle"><?php esc_html_e('Enter your PIN code', 'maintenance-genius'); ?></div>
    
    <div class="pin-dots" id="pinDots"></div>
    
    <div class="pin-pad" id="pinPad">
        <button class="pin-key" data-key="1">1</button>
        <button class="pin-key" data-key="2">2</button>
        <button class="pin-key" data-key="3">3</button>
        <button class="pin-key" data-key="4">4</button>
        <button class="pin-key" data-key="5">5</button>
        <button class="pin-key" data-key="6">6</button>
        <button class="pin-key" data-key="7">7</button>
        <button class="pin-key" data-key="8">8</button>
        <button class="pin-key" data-key="9">9</button>
        <button class="pin-key action" data-key="clear">✕</button>
        <button class="pin-key" data-key="0">0</button>
        <button class="pin-key action confirm" data-key="submit">✓</button>
    </div>
    
    <div class="pin-error-msg" id="pinError"></div>
</div>
<?php endif; ?>

<!-- ===== REMOTE CONTROL SCREEN ===== -->
<div class="remote-screen <?php echo $has_pin ? '' : 'active'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static string ?>" id="remoteScreen">
    <div class="remote-header">
        <div class="site-info">
            <h2>🔧 <?php echo esc_html($site_name); ?></h2>
            <p>Remote Control v<?php echo esc_html( MG_VERSION ); ?></p>
        </div>
        <span class="status-badge <?php echo $is_active ? 'on' : 'off'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static string ?>" id="statusBadge">
            <?php echo $is_active ? 'ON' : 'OFF'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static string ?>
        </span>
    </div>

    <div class="toggle-container">
        <div class="big-toggle <?php echo $is_active ? 'on' : 'off'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static string ?>" id="bigToggle">
            <div class="thumb" id="toggleThumb"><?php echo $is_active ? '⚡' : '○'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static string ?></div>
        </div>
        <div class="toggle-label <?php echo $is_active ? 'on' : 'off'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static string ?>" id="toggleLabel">
            <?php echo esc_html( $is_active ? __('MAINTENANCE ON', 'maintenance-genius') : __('SITE ONLINE', 'maintenance-genius') ); ?>
        </div>
        <div class="toggle-hint"><?php esc_html_e('Tap to toggle', 'maintenance-genius'); ?></div>
    </div>

    <div class="stats-grid">
        <div class="stat-card">
            <div class="stat-value" id="statVisits"><?php echo intval(MG_Settings::get_stats()['visits']); ?></div>
            <div class="stat-label"><?php esc_html_e('Visits', 'maintenance-genius'); ?></div>
        </div>
        <div class="stat-card">
            <div class="stat-value" id="statUnique"><?php echo intval(MG_Settings::get_stats()['unique']); ?></div>
            <div class="stat-label"><?php esc_html_e('Unique', 'maintenance-genius'); ?></div>
        </div>
    </div>

    <!-- Bouton installation PWA -->
    <div id="installBanner" style="display:none;text-align:center;margin-top:16px;">
        <button id="installBtn" style="background:var(--accent);color:#fff;border:none;padding:12px 24px;border-radius:12px;font-size:.9rem;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;gap:8px;">
            <?php esc_html_e('📲 Install to Home Screen', 'maintenance-genius'); ?>
        </button>
    </div>

    <div class="remote-footer">
        <a href="<?php echo esc_url(admin_url('admin.php?page=maintenance-genius')); ?>" target="_blank">
            <?php esc_html_e('Open Dashboard →', 'maintenance-genius'); ?>
        </a>
        <div class="session-info" id="sessionInfo"><?php esc_html_e('Active session • 15 min', 'maintenance-genius'); ?></div>
    </div>
</div>

<!-- TOAST -->
<div class="toast" id="toast"></div>

<script>
(function(){
    'use strict';

    var AJAX_URL = '<?php echo esc_js($ajax_url); ?>';
    var TOKEN = '<?php echo esc_js($token); ?>';
    var HAS_PIN = <?php echo $has_pin ? 'true' : 'false'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static JS boolean ?>;
    var sessionToken = '';
    var isEnabled = <?php echo $is_active ? 'true' : 'false'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static JS boolean ?>;
    var pinCode = '';
    var PIN_LENGTH = <?php echo !empty($config['pin_hash']) ? 'null' : '4'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static JS value ?>;

    var i18n = {
        authenticated: '<?php echo esc_js(__('🔓 Authenticated', 'maintenance-genius')); ?>',
        pin_incorrect: '<?php echo esc_js(__('Incorrect PIN', 'maintenance-genius')); ?>',
        session_expired_reload: '<?php echo esc_js(__('🔒 Session expired — reload the page', 'maintenance-genius')); ?>',
        session_expired: '<?php echo esc_js(__('🔒 Session expired', 'maintenance-genius')); ?>',
        maintenance_on: '<?php echo esc_js(__('MAINTENANCE ON', 'maintenance-genius')); ?>',
        site_online: '<?php echo esc_js(__('SITE ONLINE', 'maintenance-genius')); ?>',
        installed: '<?php echo esc_js(__('✅ Installed!', 'maintenance-genius')); ?>',
        install_ios: '<?php echo esc_js(__('📲 Tap ⬆️ then "Add to Home Screen"', 'maintenance-genius')); ?>',
        install_other: '<?php echo esc_js(__('📲 Use your browser menu → Add to Home Screen', 'maintenance-genius')); ?>',
        connection_error: '<?php echo esc_js(__('Connection error', 'maintenance-genius')); ?>'
    };

    function showToast(msg, duration){
        var t = document.getElementById('toast');
        t.textContent = msg;
        t.classList.add('visible');
        setTimeout(function(){ t.classList.remove('visible'); }, duration || 3000);
    }

    if (HAS_PIN) {
        var dotsEl = document.getElementById('pinDots');
        var errorEl = document.getElementById('pinError');

        function renderDots(){
            dotsEl.innerHTML = '';
            for (var i = 0; i < Math.max(pinCode.length, 4); i++) {
                var dot = document.createElement('span');
                dot.className = 'pin-dot' + (i < pinCode.length ? ' filled' : '');
                dotsEl.appendChild(dot);
            }
        }
        renderDots();

        function showPinError(msg){
            errorEl.textContent = msg;
            var dots = dotsEl.querySelectorAll('.pin-dot');
            for (var i = 0; i < dots.length; i++) {
                dots[i].classList.add('error');
            }
            setTimeout(function(){
                pinCode = '';
                renderDots();
                errorEl.textContent = '';
            }, 1200);
        }

        document.getElementById('pinPad').addEventListener('click', function(e){
            var btn = e.target.closest('.pin-key');
            if (!btn) return;

            var key = btn.getAttribute('data-key');

            if (key === 'clear') {
                pinCode = '';
                renderDots();
                errorEl.textContent = '';
            } else if (key === 'submit') {
                if (pinCode.length < 4) {
                    showPinError('Code trop court (min. 4 chiffres)');
                    return;
                }
                verifyPin();
            } else {
                if (pinCode.length < 8) {
                    pinCode += key;
                    renderDots();
                    if (navigator.vibrate) navigator.vibrate(30);
                }
            }
        });

        function verifyPin(){
            var keys = document.querySelectorAll('.pin-key');
            for (var i = 0; i < keys.length; i++) keys[i].disabled = true;

            var fd = new FormData();
            fd.append('action', 'mg_remote_verify_pin');
            fd.append('token', TOKEN);
            fd.append('pin', pinCode);

            fetch(AJAX_URL, {method:'POST', body:fd})
            .then(function(r){ return r.json(); })
            .then(function(resp){
                for (var i = 0; i < keys.length; i++) keys[i].disabled = false;

                if (resp.success) {
                    sessionToken = resp.data.session_token;
                    isEnabled = resp.data.maintenance_enabled;
                    updateUI();
                    updateStats(resp.data);

                    document.getElementById('pinScreen').style.display = 'none';
                    var rs = document.getElementById('remoteScreen');
                    rs.classList.add('active');
                    rs.style.animation = 'fadeUp .5s ease';

                    showToast(i18n.authenticated);
                } else {
                    showPinError(resp.data.message || i18n.pin_incorrect);
                    if (resp.data.locked) {
                        showPinError(resp.data.message);
                    }
                }
            })
            .catch(function(){
                for (var i = 0; i < keys.length; i++) keys[i].disabled = false;
                showPinError('Erreur de connexion');
            });
        }
    } else {
        sessionToken = '<?php
            if (!$has_pin) {
                $session_token = bin2hex(random_bytes(16));
                $session_hash = hash('sha256', $session_token . wp_salt('auth'));
                set_transient('mg_remote_session_' . $session_hash, ['created' => time()], 15 * MINUTE_IN_SECONDS);
                echo esc_js($session_token);
            }
        ?>';
    }

    var toggle = document.getElementById('bigToggle');
    toggle.addEventListener('click', function(){
        if (toggle.classList.contains('loading')) return;
        doToggle();
    });

    function doToggle(){
        toggle.classList.add('loading');
        var newState = !isEnabled;

        var fd = new FormData();
        fd.append('action', 'mg_remote_toggle');
        fd.append('token', TOKEN);
        fd.append('session_token', sessionToken);
        fd.append('enabled', newState ? 'true' : 'false');

        fetch(AJAX_URL, {method:'POST', body:fd})
        .then(function(r){ return r.json(); })
        .then(function(resp){
            toggle.classList.remove('loading');
            if (resp.success) {
                isEnabled = resp.data.enabled;
                updateUI();
                showToast(resp.data.message);
                if (navigator.vibrate) navigator.vibrate([50, 30, 50]);
            } else {
                if (resp.data.session_expired) {
                    showToast(i18n.session_expired_reload);
                    setTimeout(function(){ location.reload(); }, 2000);
                } else {
                    showToast('❌ ' + (resp.data.message || i18n.connection_error));
                }
            }
        })
        .catch(function(){
            toggle.classList.remove('loading');
            showToast('❌ Erreur de connexion');
        });
    }

    function updateUI(){
        toggle.className = 'big-toggle ' + (isEnabled ? 'on' : 'off');
        document.getElementById('toggleThumb').textContent = isEnabled ? '⚡' : '○';

        var label = document.getElementById('toggleLabel');
        label.textContent = isEnabled ? i18n.maintenance_on : i18n.site_online;
        label.className = 'toggle-label ' + (isEnabled ? 'on' : 'off');

        var badge = document.getElementById('statusBadge');
        badge.textContent = isEnabled ? 'ON' : 'OFF';
        badge.className = 'status-badge ' + (isEnabled ? 'on' : 'off');

        var meta = document.querySelector('meta[name="theme-color"]');
        if (meta) meta.content = isEnabled ? '#0a1a0a' : '#0f0f1a';
    }

    function updateStats(data){
        if (data.stats_visits !== undefined) {
            document.getElementById('statVisits').textContent = data.stats_visits;
        }
        if (data.stats_unique !== undefined) {
            document.getElementById('statUnique').textContent = data.stats_unique;
        }
    }

    setInterval(function(){
        if (!sessionToken) return;
        var fd = new FormData();
        fd.append('action', 'mg_remote_status');
        fd.append('token', TOKEN);
        fd.append('session_token', sessionToken);

        fetch(AJAX_URL, {method:'POST', body:fd})
        .then(function(r){ return r.json(); })
        .then(function(resp){
            if (resp.success) {
                isEnabled = resp.data.maintenance_enabled;
                updateUI();
                updateStats(resp.data);
            } else if (resp.data && resp.data.session_expired) {
                showToast(i18n.session_expired);
                setTimeout(function(){ location.reload(); }, 2000);
            }
        })
        .catch(function(){});
    }, 30000);

    var deferredPrompt = null;
    var installBanner = document.getElementById('installBanner');
    var installBtn = document.getElementById('installBtn');

    window.addEventListener('beforeinstallprompt', function(e) {
        e.preventDefault();
        deferredPrompt = e;
        if (installBanner) installBanner.style.display = 'block';
    });

    if (installBtn) {
        installBtn.addEventListener('click', function() {
            if (deferredPrompt) {
                deferredPrompt.prompt();
                deferredPrompt.userChoice.then(function(result) {
                    if (result.outcome === 'accepted') {
                        showToast(i18n.installed);
                    }
                    deferredPrompt = null;
                    installBanner.style.display = 'none';
                });
            } else if (navigator.standalone !== undefined && !navigator.standalone) {
                showToast(i18n.install_ios);
            } else {
                showToast(i18n.install_other);
            }
        });
    }

    if (window.navigator.standalone === false && installBanner) {
        installBanner.style.display = 'block';
    }

    if (window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true) {
        if (installBanner) installBanner.style.display = 'none';
    }

})();
</script>

</body>
</html>
        <?php
    }
}
