<?php

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

class BS2FA_Plugin {
    public function init() {
        BS2FA_Privacy::register_hooks();
        add_action('admin_menu', [$this, 'admin_menu']);
        add_action('admin_init', [$this, 'register_settings']);
        add_action('admin_init', [$this, 'handle_admin_actions']);
        add_action('show_user_profile', [$this, 'render_profile_section']);
        add_action('edit_user_profile', [$this, 'render_profile_section']);
        add_action('personal_options_update', [$this, 'save_profile_actions']);
        add_action('edit_user_profile_update', [$this, 'save_profile_actions']);
        add_action('login_init', [$this, 'handle_login_views']);
        add_filter('authenticate', [$this, 'pre_auth_rate_limit'], 5, 3);
        add_filter('authenticate', [$this, 'intercept_login'], 100, 3);
        add_action('wp_login_failed', [$this, 'note_primary_login_failed'], 10, 1);
        add_action('admin_init', [$this, 'enforce_enrollment']);
        add_action('template_redirect', [$this, 'enforce_enrollment_front']);
        add_action('wp_login', [$this, 'mark_enrollment_start'], 10, 2);
        add_action('wp_login', [$this, 'clear_primary_login_rate_limit'], 20, 2);
        add_action('wp_login', [$this, 'maybe_process_login_context'], 30, 2);
        add_action('wp_ajax_bs2fa_register_options', [$this, 'ajax_register_options']);
        add_action('wp_ajax_bs2fa_register_verify', [$this, 'ajax_register_verify']);
        add_action('wp_ajax_bs2fa_remove_passkey', [$this, 'ajax_remove_passkey']);
        add_action('wp_ajax_bs2fa_regen_backup_codes', [$this, 'ajax_regen_backup_codes']);
        add_action('wp_ajax_nopriv_bs2fa_assertion_options', [$this, 'ajax_assertion_options']);
        add_action('wp_ajax_nopriv_bs2fa_assertion_verify', [$this, 'ajax_assertion_verify']);
        add_action('login_enqueue_scripts', [$this, 'login_assets']);
        add_action('admin_enqueue_scripts', [$this, 'admin_assets']);
        add_action('init', [$this, 'register_woocommerce_endpoint']);
        add_action('wp_dashboard_setup', [$this, 'register_dashboard_widget']);
        add_action('rest_api_init', [$this, 'register_rest_routes']);
        add_filter('rest_authentication_errors', [$this, 'filter_rest_authentication_errors'], 99);
        add_filter('woocommerce_account_menu_items', [$this, 'filter_woocommerce_account_menu_items']);
        add_action('woocommerce_account_bs2fa_security_endpoint', [$this, 'render_woocommerce_security_endpoint']);
        add_action('woocommerce_before_account_navigation', [$this, 'maybe_render_woocommerce_account_notice']);
        add_action('woocommerce_before_cart', [$this, 'maybe_render_woocommerce_cart_notice'], 5);
        add_action('woocommerce_before_checkout_form', [$this, 'maybe_render_woocommerce_checkout_notice'], 5);
        add_action('woocommerce_check_cart_items', [$this, 'maybe_enforce_woocommerce_checkout_security']);
        add_action('template_redirect', [$this, 'handle_woocommerce_account_requests'], 5);
        add_filter('wp_is_application_passwords_available_for_user', [$this, 'filter_application_passwords_for_user'], 10, 2);
    }

    public static function activate() {
        if (!get_option('bs2fa_options')) {
            BS2FA_Helpers::update_options(BS2FA_Helpers::defaults());
        } else {
            BS2FA_Helpers::update_options(wp_parse_args(get_option('bs2fa_options', []), BS2FA_Helpers::defaults()));
        }

        add_rewrite_endpoint('bs2fa_security', EP_ROOT | EP_PAGES);
        flush_rewrite_rules();
    }

    public static function deactivate() {
        BS2FA_Helpers::clear_cookie('bs2fa_pending');
        BS2FA_Trusted_Devices::clear_cookie();
        flush_rewrite_rules();
    }

    public function admin_menu() {
        add_options_page(
                __('Brightery Secure 2FA', 'brightery-secure-2fa'),
                __('Brightery Secure 2FA', 'brightery-secure-2fa'),
                'manage_options',
                'bs2fa-settings',
                [$this, 'render_settings_page']
        );

        add_menu_page(
                __('2FA Setup', 'brightery-secure-2fa'),
                __('2FA Setup', 'brightery-secure-2fa'),
                'read',
                'bs2fa-setup',
                [$this, 'render_setup_page'],
                'dashicons-shield',
                81
        );
    }

    public function register_settings() {
        register_setting('bs2fa_settings_group', 'bs2fa_options', [$this, 'sanitize_settings']);
    }

    public function sanitize_settings($input) {
        $defaults = BS2FA_Helpers::defaults();
        $output = wp_parse_args((array) $input, $defaults);
        $output['enabled'] = empty($output['enabled']) ? 0 : 1;
        $output['force_enrollment'] = empty($output['force_enrollment']) ? 0 : 1;
        $output['hide_for_xmlrpc'] = empty($output['hide_for_xmlrpc']) ? 0 : 1;
        $output['alert_new_enrollment'] = empty($output['alert_new_enrollment']) ? 0 : 1;
        $output['alert_admin_on_enrollment'] = empty($output['alert_admin_on_enrollment']) ? 0 : 1;
        $output['rate_limit_enabled'] = empty($output['rate_limit_enabled']) ? 0 : 1;
        $output['trusted_devices_enabled'] = empty($output['trusted_devices_enabled']) ? 0 : 1;
        $output['alert_admin_on_lockout'] = empty($output['alert_admin_on_lockout']) ? 0 : 1;
        $output['block_application_passwords'] = empty($output['block_application_passwords']) ? 0 : 1;
        $output['revoke_other_sessions_on_change'] = empty($output['revoke_other_sessions_on_change']) ? 0 : 1;
        $output['rest_api_policy_enabled'] = empty($output['rest_api_policy_enabled']) ? 0 : 1;
        $output['rest_api_block_basic_auth'] = empty($output['rest_api_block_basic_auth']) ? 0 : 1;
        $output['rest_api_status_endpoint'] = empty($output['rest_api_status_endpoint']) ? 0 : 1;
        $output['woocommerce_account_integration'] = empty($output['woocommerce_account_integration']) ? 0 : 1;
        $output['woocommerce_force_account_security'] = empty($output['woocommerce_force_account_security']) ? 0 : 1;
        $output['dashboard_widget_enabled'] = empty($output['dashboard_widget_enabled']) ? 0 : 1;
        $output['alert_user_on_new_location'] = empty($output['alert_user_on_new_location']) ? 0 : 1;
        $output['alert_admin_on_new_location'] = empty($output['alert_admin_on_new_location']) ? 0 : 1;
        $output['woocommerce_checkout_notice'] = empty($output['woocommerce_checkout_notice']) ? 0 : 1;
        $output['woocommerce_block_checkout_if_required'] = empty($output['woocommerce_block_checkout_if_required']) ? 0 : 1;
        $output['allowed_methods'] = array_values(array_intersect((array) $output['allowed_methods'], ['totp', 'webauthn']));
        if (empty($output['allowed_methods'])) {
            $output['allowed_methods'] = ['totp'];
        }
        $output['required_roles'] = BS2FA_Helpers::sanitize_role_list($output['required_roles'] ?? []);
        $output['rest_api_protected_roles'] = BS2FA_Helpers::sanitize_role_list($output['rest_api_protected_roles'] ?? []);
        $output['rest_api_allowlisted_users'] = BS2FA_Helpers::sanitize_identifier_list($output['rest_api_allowlisted_users'] ?? '');
        $output['enrollment_grace_hours'] = max(0, (int) $output['enrollment_grace_hours']);
        $output['passkey_user_verification'] = in_array(($output['passkey_user_verification'] ?? 'required'), ['required', 'preferred'], true) ? $output['passkey_user_verification'] : 'required';
        $output['rest_api_role_mode'] = in_array(($output['rest_api_role_mode'] ?? 'required_or_enrolled'), ['required_or_enrolled', 'custom_roles', 'custom_roles_or_enrolled'], true) ? $output['rest_api_role_mode'] : 'required_or_enrolled';
        $output['primary_max_attempts'] = max(2, (int) ($output['primary_max_attempts'] ?? 5));
        $output['primary_lockout_minutes'] = max(1, (int) ($output['primary_lockout_minutes'] ?? 15));
        $output['challenge_max_attempts'] = max(2, (int) ($output['challenge_max_attempts'] ?? 5));
        $output['challenge_lockout_minutes'] = max(1, (int) ($output['challenge_lockout_minutes'] ?? 10));
        $output['log_retention_items'] = max(50, min(2000, (int) ($output['log_retention_items'] ?? 300)));
        $output['trusted_device_days'] = max(1, min(180, (int) ($output['trusted_device_days'] ?? 30)));
        return $output;
    }

    public function handle_admin_actions() {
        if (!is_admin() || !current_user_can('manage_options')) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        if (empty($_GET['page']) || sanitize_key(wp_unslash($_GET['page'])) !== 'bs2fa-settings') {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        if (!empty($_GET['bs2fa_action']) && sanitize_key(wp_unslash($_GET['bs2fa_action'])) === 'clear_logs') {
            check_admin_referer('bs2fa_clear_logs');
            BS2FA_Logger::clear();
            wp_safe_redirect(admin_url('options-general.php?page=bs2fa-settings&bs2fa_notice=logs_cleared'));
            exit;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        if (!empty($_GET['bs2fa_action']) && sanitize_key(wp_unslash($_GET['bs2fa_action'])) === 'export_logs') {
            check_admin_referer('bs2fa_export_logs');
            $filters = BS2FA_Helpers::log_filters_from_request();
            $filters['limit'] = max(1, BS2FA_Logger::count_logs($filters));
            $filters['offset'] = 0;
            $logs = BS2FA_Logger::get_logs($filters);
            nocache_headers();
            header('Content-Type: text/csv; charset=utf-8');
            header('Content-Disposition: attachment; filename=bs2fa-security-logs-' . gmdate('Y-m-d-His') . '.csv');
            header('X-Content-Type-Options: nosniff');

            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
            $out = fopen('php://output', 'w');
            fputcsv($out, ['time', 'level', 'event', 'user', 'username', 'ip', 'details']);
            foreach ($logs as $log) {
                fputcsv($out, [
                        BS2FA_Helpers::format_time((int) ($log['time'] ?? 0)),
                        (string) ($log['level'] ?? ''),
                        (string) ($log['event'] ?? ''),
                        !empty($log['user_id']) ? BS2FA_Helpers::user_label((int) $log['user_id']) : '',
                        (string) ($log['username'] ?? ''),
                        (string) ($log['ip'] ?? ''),
                        (string) ($log['details'] ?? ''),
                ]);
            }
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
            fclose($out);
            exit;
        }
    }

    public function login_assets() {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        if (empty($_GET['bs2fa'])) {
            return;
        }

        wp_enqueue_style('bs2fa-style', BS2FA_Helpers::plugin_url('assets/css/bs2fa.css'), [], BS2FA_VERSION);
        wp_enqueue_script('bs2fa-webauthn', BS2FA_Helpers::plugin_url('assets/js/bs2fa-webauthn.js'), [], BS2FA_VERSION, true);
        wp_localize_script('bs2fa-webauthn', 'BS2FA', [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'challengeAction' => 'bs2fa_assertion_options',
                'verifyAction' => 'bs2fa_assertion_verify',
                'strings' => [
                        'passkeyNotSupported' => __('Your browser/device does not support passkeys.', 'brightery-secure-2fa'),
                        'passkeyCancelled' => __('Passkey login was cancelled.', 'brightery-secure-2fa'),
                        'genericError' => __('Passkey request failed.', 'brightery-secure-2fa'),
                        'preparingPasskey' => __('Preparing passkey request…', 'brightery-secure-2fa'),
                        'waitingForPasskey' => __('Waiting for your passkey…', 'brightery-secure-2fa'),
                ],
        ]);
    }

    public function admin_assets($hook = '') {
        if (strpos((string) $hook, 'bs2fa') === false && !in_array($hook, ['profile.php', 'user-edit.php'], true)) {
            return;
        }

        wp_enqueue_style('bs2fa-style', BS2FA_Helpers::plugin_url('assets/css/bs2fa.css'), [], BS2FA_VERSION);
        wp_enqueue_script('bs2fa-webauthn', BS2FA_Helpers::plugin_url('assets/js/bs2fa-webauthn.js'), [], BS2FA_VERSION, true);
        wp_enqueue_script('bs2fa-qr', BS2FA_Helpers::plugin_url('assets/js/bs2fa-qr.js'), [], BS2FA_VERSION, true);
        wp_localize_script('bs2fa-webauthn', 'BS2FA', [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'registerAction' => 'bs2fa_register_options',
                'registerVerifyAction' => 'bs2fa_register_verify',
                'removePasskeyAction' => 'bs2fa_remove_passkey',
                'regenBackupCodesAction' => 'bs2fa_regen_backup_codes',
                'nonce' => wp_create_nonce('bs2fa_manage'),
                'strings' => [
                        'passkeyNotSupported' => __('Your browser/device does not support passkeys.', 'brightery-secure-2fa'),
                        'genericError' => __('Passkey request failed.', 'brightery-secure-2fa'),
                        'passkeyCreated' => __('Passkey added successfully.', 'brightery-secure-2fa'),
                        'passkeyRemoved' => __('Passkey removed.', 'brightery-secure-2fa'),
                        'backupCodesGenerated' => __('Backup codes generated. Save them now.', 'brightery-secure-2fa'),
                        'preparingPasskey' => __('Preparing passkey request…', 'brightery-secure-2fa'),
                        'waitingForPasskey' => __('Waiting for your security device…', 'brightery-secure-2fa'),
                        'copied' => __('Copied.', 'brightery-secure-2fa'),
                        'passkeyNeedsSecureContext' => __('Passkeys require HTTPS or another secure browser context on this page.', 'brightery-secure-2fa'),
                        'passkeyContextUnavailable' => __('This browser context cannot access the passkey API on this page.', 'brightery-secure-2fa'),
                ],
        ]);
    }

    protected function render_notice() {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        if (empty($_GET['bs2fa_notice'])) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $notice = sanitize_key(wp_unslash($_GET['bs2fa_notice']));
        if ($notice === 'logs_cleared') {
            echo '<div class="notice notice-success"><p>' . esc_html__('Security logs cleared.', 'brightery-secure-2fa') . '</p></div>';
        }

        if ($notice === 'setup_saved') {
            echo '<div class="notice notice-success"><p>' . esc_html__('Security settings updated.', 'brightery-secure-2fa') . '</p></div>';
        }
    }

    public function render_settings_page() {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Access denied.', 'brightery-secure-2fa'));
        }

        $options = BS2FA_Helpers::get_option();
        $roles = function_exists('get_editable_roles') ? get_editable_roles() : [];
        if (empty($roles) && function_exists('wp_roles')) {
            $roles_obj = wp_roles();
            $roles = $roles_obj ? (array) $roles_obj->roles : [];
        }
        $counts = BS2FA_Helpers::count_required_users();
        $log_filters = BS2FA_Helpers::log_filters_from_request();
        $logs = BS2FA_Logger::get_logs($log_filters);
        $log_total = BS2FA_Logger::count_logs($log_filters);
        $event_options = BS2FA_Logger::distinct_events();
        $https_ready = is_ssl() || stripos(home_url('/'), 'https://') === 0;
        $lockouts_24h = BS2FA_Helpers::count_recent_events(['primary_login_blocked', 'totp_login_failed', 'passkey_login_failed'], DAY_IN_SECONDS);
        $rest_allowlist_count = count(BS2FA_Helpers::resolve_user_ids_from_identifiers($options['rest_api_allowlisted_users'] ?? ''));
        ?>
        <div class="wrap bs2fa-admin-wrap">
            <h1><?php esc_html_e('Brightery Secure 2FA v10', 'brightery-secure-2fa'); ?></h1>
            <p><?php esc_html_e('Production-focused 2FA with forced enrollment, passkeys, local TOTP QR setup, throttling, trusted devices, REST/API role controls, service-account allowlists, login-context alerts, WooCommerce security messaging, dashboard visibility, and session-hardening controls.', 'brightery-secure-2fa'); ?></p>
            <?php $this->render_notice(); ?>

            <div class="bs2fa-grid bs2fa-metrics">
                <div class="bs2fa-metric-card"><span><?php esc_html_e('Required users', 'brightery-secure-2fa'); ?></span><strong><?php echo esc_html((string) $counts['required']); ?></strong></div>
                <div class="bs2fa-metric-card"><span><?php esc_html_e('Already enrolled', 'brightery-secure-2fa'); ?></span><strong><?php echo esc_html((string) $counts['enrolled']); ?></strong></div>
                <div class="bs2fa-metric-card"><span><?php esc_html_e('Still pending', 'brightery-secure-2fa'); ?></span><strong><?php echo esc_html((string) $counts['pending']); ?></strong></div>
                <div class="bs2fa-metric-card"><span><?php esc_html_e('Passkey readiness', 'brightery-secure-2fa'); ?></span><strong><?php echo $https_ready ? esc_html__('HTTPS ready', 'brightery-secure-2fa') : esc_html__('HTTPS needed', 'brightery-secure-2fa'); ?></strong></div>
                <div class="bs2fa-metric-card"><span><?php esc_html_e('Recent lockout signals (24h)', 'brightery-secure-2fa'); ?></span><strong><?php echo esc_html((string) $lockouts_24h); ?></strong></div>
            </div>

            <form method="post" action="options.php" class="bs2fa-card bs2fa-settings-form">
                <?php settings_fields('bs2fa_settings_group'); ?>

                <div class="bs2fa-grid bs2fa-settings-grid">
                    <div class="bs2fa-card-section">
                        <h2><?php esc_html_e('Core policy', 'brightery-secure-2fa'); ?></h2>
                        <table class="form-table" role="presentation">
                            <tr>
                                <th scope="row"><?php esc_html_e('Enable plugin', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[enabled]" value="1" <?php checked(!empty($options['enabled'])); ?>> <?php esc_html_e('Turn on 2FA enforcement', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Allowed methods', 'brightery-secure-2fa'); ?></th>
                                <td>
                                    <label><input type="checkbox" name="bs2fa_options[allowed_methods][]" value="totp" <?php checked(in_array('totp', (array) $options['allowed_methods'], true)); ?>> <?php esc_html_e('Authenticator App (TOTP)', 'brightery-secure-2fa'); ?></label><br>
                                    <label><input type="checkbox" name="bs2fa_options[allowed_methods][]" value="webauthn" <?php checked(in_array('webauthn', (array) $options['allowed_methods'], true)); ?>> <?php esc_html_e('Passkey / Fingerprint / Face ID / Windows Hello', 'brightery-secure-2fa'); ?></label>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Required roles', 'brightery-secure-2fa'); ?></th>
                                <td>
                                    <?php if (!empty($roles)) : ?>
                                        <?php foreach ($roles as $key => $role) : ?>
                                            <label style="display:block; margin:0 0 4px 0;">
                                                <input type="checkbox" name="bs2fa_options[required_roles][]" value="<?php echo esc_attr($key); ?>" <?php checked(in_array($key, (array) $options['required_roles'], true)); ?>>
                                                <?php echo esc_html(is_array($role) ? ($role['name'] ?? $key) : $key); ?>
                                            </label>
                                        <?php endforeach; ?>
                                        <p class="description"><?php esc_html_e('These user groups must enroll in at least one allowed method.', 'brightery-secure-2fa'); ?></p>
                                    <?php else : ?>
                                        <p class="description"><?php esc_html_e('No editable roles were found on this site. Forced role-based enrollment will be skipped until roles are available.', 'brightery-secure-2fa'); ?></p>
                                    <?php endif; ?>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Force enrollment', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[force_enrollment]" value="1" <?php checked(!empty($options['force_enrollment'])); ?>> <?php esc_html_e('Block required roles until they configure 2FA', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Enrollment grace period (hours)', 'brightery-secure-2fa'); ?></th>
                                <td>
                                    <input type="number" min="0" name="bs2fa_options[enrollment_grace_hours]" value="<?php echo esc_attr((int) $options['enrollment_grace_hours']); ?>">
                                    <p class="description"><?php esc_html_e('0 means immediate enforcement.', 'brightery-secure-2fa'); ?></p>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Passkey user verification', 'brightery-secure-2fa'); ?></th>
                                <td>
                                    <select name="bs2fa_options[passkey_user_verification]">
                                        <option value="required" <?php selected(($options['passkey_user_verification'] ?? 'required'), 'required'); ?>><?php esc_html_e('Required (recommended)', 'brightery-secure-2fa'); ?></option>
                                        <option value="preferred" <?php selected(($options['passkey_user_verification'] ?? 'required'), 'preferred'); ?>><?php esc_html_e('Preferred', 'brightery-secure-2fa'); ?></option>
                                    </select>
                                    <p class="description"><?php esc_html_e('Required forces biometric/PIN verification on compatible devices.', 'brightery-secure-2fa'); ?></p>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Legacy/XML-RPC note', 'brightery-secure-2fa'); ?></th>
                                <td>
                                    <label><input type="checkbox" name="bs2fa_options[hide_for_xmlrpc]" value="1" <?php checked(!empty($options['hide_for_xmlrpc'])); ?>> <?php esc_html_e('Treat this plugin as interactive-login protection only (recommended)', 'brightery-secure-2fa'); ?></label>
                                </td>
                            </tr>
                        </table>
                    </div>

                    <div class="bs2fa-card-section">
                        <h2><?php esc_html_e('Hardening', 'brightery-secure-2fa'); ?></h2>
                        <table class="form-table" role="presentation">
                            <tr>
                                <th scope="row"><?php esc_html_e('Rate limiting', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[rate_limit_enabled]" value="1" <?php checked(!empty($options['rate_limit_enabled'])); ?>> <?php esc_html_e('Throttle repeated login and 2FA failures', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Primary login max attempts', 'brightery-secure-2fa'); ?></th>
                                <td><input type="number" min="2" name="bs2fa_options[primary_max_attempts]" value="<?php echo esc_attr((int) $options['primary_max_attempts']); ?>"></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Primary lockout minutes', 'brightery-secure-2fa'); ?></th>
                                <td><input type="number" min="1" name="bs2fa_options[primary_lockout_minutes]" value="<?php echo esc_attr((int) $options['primary_lockout_minutes']); ?>"></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('2FA challenge max attempts', 'brightery-secure-2fa'); ?></th>
                                <td><input type="number" min="2" name="bs2fa_options[challenge_max_attempts]" value="<?php echo esc_attr((int) $options['challenge_max_attempts']); ?>"></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('2FA challenge lockout minutes', 'brightery-secure-2fa'); ?></th>
                                <td><input type="number" min="1" name="bs2fa_options[challenge_lockout_minutes]" value="<?php echo esc_attr((int) $options['challenge_lockout_minutes']); ?>"></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Trusted devices', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[trusted_devices_enabled]" value="1" <?php checked(!empty($options['trusted_devices_enabled'])); ?>> <?php esc_html_e('Let users skip 2FA on a trusted browser after successful verification', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Trusted device days', 'brightery-secure-2fa'); ?></th>
                                <td><input type="number" min="1" max="180" name="bs2fa_options[trusted_device_days]" value="<?php echo esc_attr((int) $options['trusted_device_days']); ?>"></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Application passwords', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[block_application_passwords]" value="1" <?php checked(!empty($options['block_application_passwords'])); ?>> <?php esc_html_e('Disable WordPress application passwords for protected / 2FA-enabled accounts', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('REST/API policy', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[rest_api_policy_enabled]" value="1" <?php checked(!empty($options['rest_api_policy_enabled'])); ?>> <?php esc_html_e('Apply extra REST API restrictions to protected / 2FA-enabled accounts', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('REST/API role mode', 'brightery-secure-2fa'); ?></th>
                                <td>
                                    <select name="bs2fa_options[rest_api_role_mode]">
                                        <option value="required_or_enrolled" <?php selected(($options['rest_api_role_mode'] ?? 'required_or_enrolled'), 'required_or_enrolled'); ?>><?php esc_html_e('Required roles or any 2FA-enrolled user', 'brightery-secure-2fa'); ?></option>
                                        <option value="custom_roles" <?php selected(($options['rest_api_role_mode'] ?? 'required_or_enrolled'), 'custom_roles'); ?>><?php esc_html_e('Custom REST roles only', 'brightery-secure-2fa'); ?></option>
                                        <option value="custom_roles_or_enrolled" <?php selected(($options['rest_api_role_mode'] ?? 'required_or_enrolled'), 'custom_roles_or_enrolled'); ?>><?php esc_html_e('Custom REST roles plus any 2FA-enrolled user', 'brightery-secure-2fa'); ?></option>
                                    </select>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('REST/API protected roles', 'brightery-secure-2fa'); ?></th>
                                <td>
                                    <?php foreach ($roles as $key => $role) : ?>
                                        <label style="display:block; margin:0 0 4px 0;">
                                            <input type="checkbox" name="bs2fa_options[rest_api_protected_roles][]" value="<?php echo esc_attr($key); ?>" <?php checked(in_array($key, (array) ($options['rest_api_protected_roles'] ?? []), true)); ?>>
                                            <?php echo esc_html($role['name']); ?>
                                        </label>
                                    <?php endforeach; ?>
                                    <p class="description"><?php esc_html_e('Used when REST/API role mode is set to a custom-role option. Leave empty to fall back to Required roles.', 'brightery-secure-2fa'); ?></p>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Block Basic/Auth headers on REST', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[rest_api_block_basic_auth]" value="1" <?php checked(!empty($options['rest_api_block_basic_auth'])); ?>> <?php esc_html_e('Deny Authorization-header REST authentication for protected / enrolled users and keep them on browser session + nonce flows', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('REST/API service account allowlist', 'brightery-secure-2fa'); ?></th>
                                <td>
                                    <textarea name="bs2fa_options[rest_api_allowlisted_users]" rows="5" class="large-text code"><?php echo esc_textarea((string) ($options['rest_api_allowlisted_users'] ?? '')); ?></textarea>
                                    <p class="description"><?php esc_html_e('Enter one user ID, login, or email per line. These accounts bypass the plugin\'s REST Authorization-header block and application-password restriction so you can keep dedicated service accounts working.', 'brightery-secure-2fa'); ?></p>
                                    <p class="description"><?php
                                        /* translators: %d: Number of users */
                                        printf(esc_html__('%d allowlisted user(s) currently resolve from this list.', 'brightery-secure-2fa'), (int) $rest_allowlist_count);
                                        ?></p>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('2FA status REST endpoint', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[rest_api_status_endpoint]" value="1" <?php checked(!empty($options['rest_api_status_endpoint'])); ?>> <?php esc_html_e('Expose /wp-json/bs2fa/v1/me for authenticated users', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('WooCommerce account integration', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[woocommerce_account_integration]" value="1" <?php checked(!empty($options['woocommerce_account_integration'])); ?>> <?php esc_html_e('Add a Security screen inside My Account when WooCommerce is active', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('WooCommerce forced-security gate', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[woocommerce_force_account_security]" value="1" <?php checked(!empty($options['woocommerce_force_account_security'])); ?>> <?php esc_html_e('Redirect required WooCommerce users to My Account → Security until 2FA is configured', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('WooCommerce cart/checkout notices', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[woocommerce_checkout_notice]" value="1" <?php checked(!empty($options['woocommerce_checkout_notice'])); ?>> <?php esc_html_e('Show security reminder notices on My Account, cart, and checkout when a required user is still pending setup', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('WooCommerce checkout block', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[woocommerce_block_checkout_if_required]" value="1" <?php checked(!empty($options['woocommerce_block_checkout_if_required'])); ?>> <?php esc_html_e('Prevent pending required users from placing an order until they finish security setup', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Revoke other sessions on security change', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[revoke_other_sessions_on_change]" value="1" <?php checked(!empty($options['revoke_other_sessions_on_change'])); ?>> <?php esc_html_e('When a user changes passkeys, authenticator settings, or backup codes, revoke their other active sessions', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                        </table>

                        <h2><?php esc_html_e('Alerts & logs', 'brightery-secure-2fa'); ?></h2>
                        <table class="form-table" role="presentation">
                            <tr>
                                <th scope="row"><?php esc_html_e('Email user on security changes', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[alert_new_enrollment]" value="1" <?php checked(!empty($options['alert_new_enrollment'])); ?>> <?php esc_html_e('Send user alerts when a passkey or authenticator method is enabled/changed', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Email admin on enrollment', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[alert_admin_on_enrollment]" value="1" <?php checked(!empty($options['alert_admin_on_enrollment'])); ?>> <?php esc_html_e('Send a short admin alert when users add a new method', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Email admin on lockout', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[alert_admin_on_lockout]" value="1" <?php checked(!empty($options['alert_admin_on_lockout'])); ?>> <?php esc_html_e('Notify the admin when a login or 2FA lockout window starts', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Email user on new login context', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[alert_user_on_new_location]" value="1" <?php checked(!empty($options['alert_user_on_new_location'])); ?>> <?php esc_html_e('Send the user a notice when their account signs in from a new local network context or country header', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Email admin on new login context', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[alert_admin_on_new_location]" value="1" <?php checked(!empty($options['alert_admin_on_new_location'])); ?>> <?php esc_html_e('Send the admin a short alert when a protected account signs in from a new local context', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Log retention items', 'brightery-secure-2fa'); ?></th>
                                <td><input type="number" min="50" max="2000" name="bs2fa_options[log_retention_items]" value="<?php echo esc_attr((int) $options['log_retention_items']); ?>"></td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e('Dashboard widget', 'brightery-secure-2fa'); ?></th>
                                <td><label><input type="checkbox" name="bs2fa_options[dashboard_widget_enabled]" value="1" <?php checked(!empty($options['dashboard_widget_enabled'])); ?>> <?php esc_html_e('Show a security summary widget on the WordPress dashboard for administrators', 'brightery-secure-2fa'); ?></label></td>
                            </tr>
                        </table>
                    </div>
                </div>

                <?php submit_button(__('Save Settings', 'brightery-secure-2fa')); ?>
            </form>

            <div class="bs2fa-card bs2fa-log-card">
                <div class="bs2fa-log-head">
                    <div>
                        <h2><?php esc_html_e('Security activity', 'brightery-secure-2fa'); ?></h2>
                        <p><?php esc_html_e('Filter, search, and export the lightweight audit trail stored inside WordPress options.', 'brightery-secure-2fa'); ?></p>
                    </div>
                    <div class="bs2fa-log-actions">
                        <a class="button button-secondary" href="<?php echo esc_url(wp_nonce_url(BS2FA_Helpers::current_admin_url(['bs2fa_action' => 'export_logs']), 'bs2fa_export_logs')); ?>"><?php esc_html_e('Export filtered CSV', 'brightery-secure-2fa'); ?></a>
                        <a class="button" href="<?php echo esc_url(wp_nonce_url(admin_url('options-general.php?page=bs2fa-settings&bs2fa_action=clear_logs'), 'bs2fa_clear_logs')); ?>"><?php esc_html_e('Clear logs', 'brightery-secure-2fa'); ?></a>
                    </div>
                </div>
                <form method="get" class="bs2fa-filter-form">
                    <input type="hidden" name="page" value="bs2fa-settings">
                    <div class="bs2fa-filter-field">
                        <label for="bs2fa_level"><?php esc_html_e('Level', 'brightery-secure-2fa'); ?></label>
                        <select name="bs2fa_level" id="bs2fa_level">
                            <option value=""><?php esc_html_e('All', 'brightery-secure-2fa'); ?></option>
                            <option value="info" <?php selected(($log_filters['level'] ?? ''), 'info'); ?>>info</option>
                            <option value="warning" <?php selected(($log_filters['level'] ?? ''), 'warning'); ?>>warning</option>
                            <option value="error" <?php selected(($log_filters['level'] ?? ''), 'error'); ?>>error</option>
                        </select>
                    </div>
                    <div class="bs2fa-filter-field">
                        <label for="bs2fa_event"><?php esc_html_e('Event', 'brightery-secure-2fa'); ?></label>
                        <select name="bs2fa_event" id="bs2fa_event">
                            <option value=""><?php esc_html_e('All events', 'brightery-secure-2fa'); ?></option>
                            <?php foreach ($event_options as $event_name) : ?>
                                <option value="<?php echo esc_attr($event_name); ?>" <?php selected(($log_filters['event'] ?? ''), $event_name); ?>><?php echo esc_html($event_name); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="bs2fa-filter-field">
                        <label for="bs2fa_user_id"><?php esc_html_e('User ID', 'brightery-secure-2fa'); ?></label>
                        <input type="number" min="0" name="bs2fa_user_id" id="bs2fa_user_id" value="<?php echo esc_attr((string) ($log_filters['user_id'] ?? '')); ?>">
                    </div>
                    <div class="bs2fa-filter-field">
                        <label for="bs2fa_ip"><?php esc_html_e('IP', 'brightery-secure-2fa'); ?></label>
                        <input type="text" name="bs2fa_ip" id="bs2fa_ip" value="<?php echo esc_attr((string) ($log_filters['ip'] ?? '')); ?>">
                    </div>
                    <div class="bs2fa-filter-field">
                        <label for="bs2fa_search"><?php esc_html_e('Search', 'brightery-secure-2fa'); ?></label>
                        <input type="text" name="bs2fa_search" id="bs2fa_search" value="<?php echo esc_attr((string) ($log_filters['search'] ?? '')); ?>">
                    </div>
                    <div class="bs2fa-filter-field">
                        <label for="bs2fa_per_page"><?php esc_html_e('Rows', 'brightery-secure-2fa'); ?></label>
                        <select name="bs2fa_per_page" id="bs2fa_per_page">
                            <?php foreach ([20, 50, 100] as $pp) : ?>
                                <option value="<?php echo esc_attr((string) $pp); ?>" <?php selected((int) ($log_filters['per_page'] ?? 20), $pp); ?>><?php echo esc_html((string) $pp); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="bs2fa-filter-actions">
                        <button type="submit" class="button button-primary"><?php esc_html_e('Apply filters', 'brightery-secure-2fa'); ?></button>
                        <a class="button" href="<?php echo esc_url(admin_url('options-general.php?page=bs2fa-settings')); ?>"><?php esc_html_e('Reset', 'brightery-secure-2fa'); ?></a>
                    </div>
                </form>
                <div class="bs2fa-log-table-wrap">
                    <table class="widefat striped">
                        <thead><tr><th><?php esc_html_e('Time', 'brightery-secure-2fa'); ?></th><th><?php esc_html_e('Event', 'brightery-secure-2fa'); ?></th><th><?php esc_html_e('User', 'brightery-secure-2fa'); ?></th><th><?php esc_html_e('IP', 'brightery-secure-2fa'); ?></th><th><?php esc_html_e('Details', 'brightery-secure-2fa'); ?></th></tr></thead>
                        <tbody>
                        <?php if (empty($logs)) : ?>
                            <tr><td colspan="5"><?php esc_html_e('No log entries match the current filters.', 'brightery-secure-2fa'); ?></td></tr>
                        <?php else : foreach ($logs as $log) : ?>
                            <tr>
                                <td><?php echo esc_html(BS2FA_Helpers::format_time((int) ($log['time'] ?? 0))); ?></td>
                                <td><span class="bs2fa-pill bs2fa-pill-<?php echo esc_attr($log['level'] ?? 'info'); ?>"><?php echo esc_html((string) ($log['event'] ?? '')); ?></span></td>
                                <td><?php echo !empty($log['user_id']) ? esc_html(BS2FA_Helpers::user_label((int) $log['user_id'])) : esc_html((string) ($log['username'] ?? '—')); ?></td>
                                <td><?php echo esc_html((string) ($log['ip'] ?? '—')); ?></td>
                                <td><?php echo esc_html((string) ($log['details'] ?? '')); ?></td>
                            </tr>
                        <?php endforeach; endif; ?>
                        </tbody>
                    </table>
                </div>
                <div class="bs2fa-log-foot">
                    <div class="bs2fa-muted"><?php
                        /* translators: 1: Current count, 2: Total count */
                        printf(esc_html__('Showing %1$d of %2$d matching log entries', 'brightery-secure-2fa'), (int) count($logs), (int) $log_total); ?></div>
                    <div class="bs2fa-pagination">
                        <?php if (($log_filters['page'] ?? 1) > 1) : ?>
                            <a class="button" href="<?php echo esc_url(BS2FA_Helpers::current_admin_url(['bs2fa_log_page' => max(1, (int) $log_filters['page'] - 1)])); ?>"><?php esc_html_e('Previous', 'brightery-secure-2fa'); ?></a>
                        <?php endif; ?>
                        <?php if ((($log_filters['offset'] ?? 0) + count($logs)) < $log_total) : ?>
                            <a class="button" href="<?php echo esc_url(BS2FA_Helpers::current_admin_url(['bs2fa_log_page' => (int) $log_filters['page'] + 1])); ?>"><?php esc_html_e('Next', 'brightery-secure-2fa'); ?></a>
                        <?php endif; ?>
                    </div>
                </div>
            </div>
        </div>
        <?php
    }

    public function render_profile_section($user) {
        if (!current_user_can('edit_user', $user->ID)) {
            return;
        }

        $allowed = BS2FA_Helpers::allowed_methods();
        $totp_enabled = BS2FA_Helpers::user_has_totp($user->ID);
        $passkeys = BS2FA_WebAuthn::get_credentials($user->ID);
        $pending_secret = get_user_meta($user->ID, 'bs2fa_pending_totp_secret', true);
        $generated_backup = BS2FA_Helpers::consume_backup_codes_flash();
        $recent_logs = BS2FA_Helpers::user_recent_logs($user->ID, 6);
        $trusted_devices = BS2FA_Trusted_Devices::get_devices($user->ID);
        ?>
        <h2><?php esc_html_e('Brightery Secure 2FA', 'brightery-secure-2fa'); ?></h2>
        <div class="bs2fa-card">
            <div class="bs2fa-grid bs2fa-profile-stats">
                <div class="bs2fa-mini-stat"><span><?php esc_html_e('Required by policy', 'brightery-secure-2fa'); ?></span><strong><?php echo BS2FA_Helpers::user_has_required_role($user) ? esc_html__('Yes', 'brightery-secure-2fa') : esc_html__('No', 'brightery-secure-2fa'); ?></strong></div>
                <div class="bs2fa-mini-stat"><span><?php esc_html_e('Authenticator app', 'brightery-secure-2fa'); ?></span><strong><?php echo $totp_enabled ? esc_html__('Enabled', 'brightery-secure-2fa') : esc_html__('Off', 'brightery-secure-2fa'); ?></strong></div>
                <div class="bs2fa-mini-stat"><span><?php esc_html_e('Passkeys', 'brightery-secure-2fa'); ?></span><strong><?php echo esc_html((string) count($passkeys)); ?></strong></div>
                <div class="bs2fa-mini-stat"><span><?php esc_html_e('Backup codes left', 'brightery-secure-2fa'); ?></span><strong><?php echo esc_html((string) BS2FA_Helpers::backup_codes_left($user->ID)); ?></strong></div>
                <div class="bs2fa-mini-stat"><span><?php esc_html_e('Trusted devices', 'brightery-secure-2fa'); ?></span><strong><?php echo esc_html((string) count($trusted_devices)); ?></strong></div>
            </div>

            <?php if (in_array('totp', $allowed, true)) : ?>
                <div class="bs2fa-section">
                    <h3><?php esc_html_e('Authenticator App (TOTP)', 'brightery-secure-2fa'); ?></h3>
                    <p><?php
                        /* translators: %1$s: App URL */
                        $app_text = __('Recommended app: <a href="%1$s" target="_blank" rel="noopener noreferrer">Brightery OTP Vault & Autofill</a> or Use Google Authenticator, Microsoft Authenticator', 'brightery-secure-2fa');

                        printf(
                                wp_kses(
                                        $app_text,
                                        [
                                                'a' => [
                                                        'href'   => [],
                                                        'target' => [],
                                                        'rel'    => [],
                                                ],
                                        ]
                                ),
                                esc_url('https://auth.brightery.com/')
                        );
                        ?></p>
                    <?php if (!$totp_enabled && empty($pending_secret)) : ?>
                        <?php submit_button(__('Prepare Authenticator Secret', 'brightery-secure-2fa'), 'secondary', 'bs2fa_prepare_totp', false); ?>
                    <?php endif; ?>

                    <?php if (!empty($pending_secret) && !$totp_enabled) : ?>
                        <?php $uri = BS2FA_TOTP::provisioning_uri($user, $pending_secret); ?>
                        <div class="bs2fa-highlight">
                            <div class="bs2fa-totp-grid">
                                <div class="bs2fa-totp-qr-wrap">
                                    <div class="bs2fa-totp-qr" data-qr="<?php echo esc_attr($uri); ?>" data-filename="bs2fa-totp-<?php echo esc_attr(sanitize_file_name($user->user_login)); ?>.svg" aria-label="<?php esc_attr_e('Authenticator app QR code', 'brightery-secure-2fa'); ?>"></div>
                                    <p class="description"><?php esc_html_e('This QR code is rendered locally on your site. Nothing is sent to a third-party QR service.', 'brightery-secure-2fa'); ?></p>
                                    <p class="bs2fa-inline-actions bs2fa-qr-actions">
                                        <button type="button" class="button bs2fa-qr-download"><?php esc_html_e('Download QR SVG', 'brightery-secure-2fa'); ?></button>
                                    </p>
                                </div>
                                <div class="bs2fa-totp-setup-fields">
                                    <p><strong><?php esc_html_e('Manual secret', 'brightery-secure-2fa'); ?>:</strong> <code><?php echo esc_html($pending_secret); ?></code></p>
                                    <p><strong><?php esc_html_e('Setup URI', 'brightery-secure-2fa'); ?>:</strong></p>
                                    <textarea readonly class="large-text code bs2fa-copy-target" rows="3"><?php echo esc_textarea($uri); ?></textarea>
                                    <p class="bs2fa-inline-actions">
                                        <button type="button" class="button bs2fa-copy-btn" data-copy-target="previousElementSibling"><?php esc_html_e('Copy setup URI', 'brightery-secure-2fa'); ?></button>
                                        <a class="button button-secondary" href="<?php echo esc_url($uri); ?>"><?php esc_html_e('Open in authenticator app', 'brightery-secure-2fa'); ?></a>
                                    </p>
                                    <p class="description"><?php esc_html_e('Scan the QR code or use the secret/setup link above, then verify a 6-digit code below.', 'brightery-secure-2fa'); ?></p>
                                    <input type="text" name="bs2fa_totp_code" maxlength="6" pattern="[0-9]*" placeholder="123456" class="regular-text">
                                    <?php submit_button(__('Enable Authenticator App', 'brightery-secure-2fa'), 'primary', 'bs2fa_enable_totp', false); ?>
                                </div>
                            </div>
                        </div>
                    <?php endif; ?>

                    <?php if ($totp_enabled) : ?>
                        <?php submit_button(__('Disable Authenticator App', 'brightery-secure-2fa'), 'delete', 'bs2fa_disable_totp', false, ['onclick' => "return confirm('Disable authenticator app for this user?');"]); ?>
                    <?php endif; ?>
                </div>
            <?php endif; ?>

            <?php if (in_array('webauthn', $allowed, true)) : ?>
                <div class="bs2fa-section">
                    <h3><?php esc_html_e('Passkeys / Fingerprint / Face ID', 'brightery-secure-2fa'); ?></h3>
                    <p><?php esc_html_e('Passkeys use your device biometrics or device PIN through WebAuthn. HTTPS is required in production.', 'brightery-secure-2fa'); ?></p>
                    <p class="description"><?php echo (is_ssl() || stripos(admin_url(), 'https://') === 0) ? esc_html__('Secure context detected for this admin page.', 'brightery-secure-2fa') : esc_html__('This page is not running in a secure HTTPS context, so passkeys may be unavailable here.', 'brightery-secure-2fa'); ?></p>
                    <button type="button" class="button button-secondary bs2fa-add-passkey" data-user="<?php echo esc_attr($user->ID); ?>"><?php esc_html_e('Add Passkey', 'brightery-secure-2fa'); ?></button>
                    <div class="bs2fa-passkey-status"></div>
                    <?php if (!empty($passkeys)) : ?>
                        <ul class="bs2fa-passkey-list">
                            <?php foreach ($passkeys as $credential) : ?>
                                <li>
                                    <div style="width:100%;">
                                        <strong><?php echo esc_html($credential['label'] ?: __('Passkey', 'brightery-secure-2fa')); ?></strong>
                                        <div class="description"><?php
                                            /* translators: 1: Added time, 2: Last used time */
                                            printf(esc_html__('Added: %1$s | Last used: %2$s', 'brightery-secure-2fa'), esc_html(BS2FA_Helpers::format_time((int) ($credential['added_at'] ?? 0))), esc_html(BS2FA_Helpers::format_time((int) ($credential['last_used_at'] ?? 0)))); ?></div>
                                        <div class="bs2fa-label-grid-row" style="margin-top:8px;">
                                            <input type="text" class="regular-text" name="bs2fa_passkey_labels[<?php echo esc_attr($credential['id']); ?>]" value="<?php echo esc_attr((string) ($credential['label'] ?? '')); ?>" placeholder="<?php esc_attr_e('Passkey label', 'brightery-secure-2fa'); ?>">
                                            <button type="button" class="button-link-delete bs2fa-remove-passkey" data-credential="<?php echo esc_attr($credential['id']); ?>"><?php esc_html_e('Remove', 'brightery-secure-2fa'); ?></button>
                                        </div>
                                    </div>
                                </li>
                            <?php endforeach; ?>
                        </ul>
                        <?php submit_button(__('Save Passkey Labels', 'brightery-secure-2fa'), 'secondary', 'bs2fa_save_passkey_labels', false); ?>
                    <?php else : ?>
                        <p><?php esc_html_e('No passkeys added yet.', 'brightery-secure-2fa'); ?></p>
                    <?php endif; ?>
                </div>
            <?php endif; ?>

            <div class="bs2fa-section">
                <h3><?php esc_html_e('Backup Codes', 'brightery-secure-2fa'); ?></h3>
                <p><?php
                    /* translators: %d: Backup codes count */
                    printf(esc_html__('Unused codes remaining: %d', 'brightery-secure-2fa'), (int) BS2FA_Helpers::backup_codes_left($user->ID)); ?></p>
                <button type="button" class="button button-secondary bs2fa-regenerate-codes" data-user="<?php echo esc_attr($user->ID); ?>"><?php esc_html_e('Generate New Backup Codes', 'brightery-secure-2fa'); ?></button>
                <div class="bs2fa-backup-output">
                    <?php if (is_array($generated_backup) && !empty($generated_backup)) : ?>
                        <p><strong><?php esc_html_e('Save these codes now. They are shown once.', 'brightery-secure-2fa'); ?></strong></p>
                        <pre><?php echo esc_html(implode("\n", $generated_backup)); ?></pre>
                    <?php endif; ?>
                </div>
            </div>

            <div class="bs2fa-section">
                <h3><?php esc_html_e('Trusted devices', 'brightery-secure-2fa'); ?></h3>
                <p><?php
                    /* translators: %d: Device days */
                    printf(esc_html__('Users can trust a browser for up to %d day(s) after successful 2FA if trusted devices are enabled.', 'brightery-secure-2fa'), (int) BS2FA_Helpers::get_option('trusted_device_days', 30)); ?></p>
                <?php if (empty($trusted_devices)) : ?>
                    <p><?php esc_html_e('No trusted devices currently stored for this user.', 'brightery-secure-2fa'); ?></p>
                <?php else : ?>
                    <ul class="bs2fa-passkey-list">
                        <?php foreach ($trusted_devices as $device) : ?>
                            <li>
                                <div style="width:100%;">
                                    <strong><?php echo esc_html((string) ($device['label'] ?? __('Trusted device', 'brightery-secure-2fa'))); ?></strong>
                                    <div class="description"><?php
                                        /* translators: 1: Added time, 2: Last used time, 3: Expiration time */
                                        printf(esc_html__('Added: %1$s | Last used: %2$s | Expires: %3$s', 'brightery-secure-2fa'), esc_html(BS2FA_Helpers::format_time((int) ($device['added_at'] ?? 0))), esc_html(BS2FA_Helpers::format_time((int) ($device['last_used_at'] ?? 0))), esc_html(BS2FA_Helpers::format_time((int) ($device['expires_at'] ?? 0)))); ?></div>
                                    <div class="bs2fa-label-grid-row" style="margin-top:8px;">
                                        <input type="text" class="regular-text" name="bs2fa_trusted_device_labels[<?php echo esc_attr((string) ($device['selector'] ?? '')); ?>]" value="<?php echo esc_attr((string) ($device['label'] ?? '')); ?>" placeholder="<?php esc_attr_e('Trusted device label', 'brightery-secure-2fa'); ?>">
                                        <button type="submit" class="button-link-delete" name="bs2fa_revoke_trusted_device" value="<?php echo esc_attr((string) ($device['selector'] ?? '')); ?>" onclick="return confirm('Remove this trusted device?');"><?php esc_html_e('Remove', 'brightery-secure-2fa'); ?></button>
                                    </div>
                                </div>
                            </li>
                        <?php endforeach; ?>
                    </ul>
                    <p class="bs2fa-inline-actions">
                        <?php submit_button(__('Save Device Labels', 'brightery-secure-2fa'), 'secondary', 'bs2fa_save_trusted_device_labels', false); ?>
                        <?php submit_button(__('Revoke All Trusted Devices', 'brightery-secure-2fa'), 'delete', 'bs2fa_revoke_all_trusted_devices', false, ['onclick' => "return confirm('Revoke all trusted devices for this user?');"]); ?>
                    </p>
                <?php endif; ?>
            </div>

            <div class="bs2fa-section">
                <h3><?php esc_html_e('Recent security activity for this user', 'brightery-secure-2fa'); ?></h3>
                <?php if (empty($recent_logs)) : ?>
                    <p><?php esc_html_e('No recent activity yet.', 'brightery-secure-2fa'); ?></p>
                <?php else : ?>
                    <ul class="bs2fa-activity-list">
                        <?php foreach ($recent_logs as $log) : ?>
                            <li><strong><?php echo esc_html(BS2FA_Helpers::format_time((int) ($log['time'] ?? 0))); ?></strong> — <?php echo esc_html((string) ($log['event'] ?? '')); ?><?php if (!empty($log['details'])) : ?>: <?php echo esc_html((string) $log['details']); ?><?php endif; ?></li>
                        <?php endforeach; ?>
                    </ul>
                <?php endif; ?>
            </div>
        </div>
        <?php
    }

    protected function security_form_request() {
        $keys = [
                'bs2fa_save_passkey_labels',
                'bs2fa_passkey_labels',
                'bs2fa_save_trusted_device_labels',
                'bs2fa_trusted_device_labels',
                'bs2fa_revoke_trusted_device',
                'bs2fa_revoke_all_trusted_devices',
                'bs2fa_prepare_totp',
                'bs2fa_enable_totp',
                'bs2fa_totp_code',
                'bs2fa_disable_totp',
        ];

        $request = [];
        foreach ($keys as $key) {
            // phpcs:ignore WordPress.Security.NonceVerification.Missing
            if (!isset($_POST[$key])) {
                continue;
            }
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
            $request[$key] = map_deep(wp_unslash($_POST[$key]), static function ($value) {
                return is_string($value) ? sanitize_text_field(trim($value)) : $value;
            });
        }

        return $request;
    }

    public function save_profile_actions($user_id) {
        if (!current_user_can('edit_user', $user_id)) {
            return;
        }

        if (!isset($_POST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'update-user_' . $user_id)) {
            return;
        }

        $this->process_security_form_submission((int) $user_id, $this->security_form_request());
    }


    protected function process_security_form_submission($user_id, $request) {
        $user = get_user_by('id', (int) $user_id);
        if (!$user) {
            return;
        }

        if (isset($request['bs2fa_save_passkey_labels']) && !empty($request['bs2fa_passkey_labels']) && is_array($request['bs2fa_passkey_labels'])) {
            $changed = BS2FA_WebAuthn::update_labels($user_id, $request['bs2fa_passkey_labels']);
            if ($changed > 0) {
                BS2FA_Logger::add('passkey_labels_updated', ['user_id' => $user_id, 'details' => sprintf('Updated %d passkey label(s).', $changed)]);
            }
        }

        if (isset($request['bs2fa_save_trusted_device_labels']) && !empty($request['bs2fa_trusted_device_labels']) && is_array($request['bs2fa_trusted_device_labels'])) {
            $changed = BS2FA_Trusted_Devices::update_labels($user_id, $request['bs2fa_trusted_device_labels']);
            if ($changed > 0) {
                BS2FA_Logger::add('trusted_device_labels_updated', ['user_id' => $user_id, 'details' => sprintf('Updated %d trusted-device label(s).', $changed)]);
            }
        }

        if (isset($request['bs2fa_revoke_trusted_device'])) {
            $selector = sanitize_key((string) $request['bs2fa_revoke_trusted_device']);
            BS2FA_Trusted_Devices::revoke_device($user_id, $selector);
            BS2FA_Logger::add('trusted_device_revoked', ['user_id' => $user_id, 'details' => 'Trusted device revoked from user profile.'], 'warning');
            BS2FA_Helpers::maybe_invalidate_other_sessions($user_id, __('Trusted device revoked from profile.', 'brightery-secure-2fa'));
        }

        if (isset($request['bs2fa_revoke_all_trusted_devices'])) {
            BS2FA_Trusted_Devices::revoke_all($user_id);
            BS2FA_Logger::add('trusted_devices_revoked_all', ['user_id' => $user_id, 'details' => 'All trusted devices revoked from user profile.'], 'warning');
            BS2FA_Helpers::maybe_invalidate_other_sessions($user_id, __('All trusted devices revoked from profile.', 'brightery-secure-2fa'));
        }

        if (isset($request['bs2fa_prepare_totp'])) {
            update_user_meta($user_id, 'bs2fa_pending_totp_secret', BS2FA_TOTP::generate_secret());
            BS2FA_Logger::add('totp_secret_prepared', ['user_id' => $user_id, 'details' => 'Pending authenticator setup started.']);
        }

        if (isset($request['bs2fa_enable_totp'])) {
            $secret = get_user_meta($user_id, 'bs2fa_pending_totp_secret', true);
            $code   = sanitize_text_field((string) ($request['bs2fa_totp_code'] ?? ''));
            if ($secret && BS2FA_TOTP::verify($secret, $code)) {
                BS2FA_Crypto::encrypt_user_meta($user_id, 'bs2fa_totp_secret', $secret);
                delete_user_meta($user_id, 'bs2fa_pending_totp_secret');
                $codes = BS2FA_TOTP::generate_backup_codes();
                update_user_meta($user_id, 'bs2fa_backup_codes', BS2FA_TOTP::hash_backup_codes($codes));
                BS2FA_Helpers::set_backup_codes_flash($codes);
                BS2FA_Logger::add('totp_enabled', ['user_id' => $user_id, 'details' => 'Authenticator app enabled.']);

                /* translators: 1: User login name, 2: Site URL */
                $msg = sprintf(__('An authenticator app was enabled for %1$s on %2$s.', 'brightery-secure-2fa'), $user->user_login, home_url('/'));
                BS2FA_Helpers::notify_user($user_id, __('New authenticator app enabled', 'brightery-secure-2fa'), $msg);

                /* translators: %s: User login */
                $admin_msg = sprintf(__('User %s enabled an authenticator app.', 'brightery-secure-2fa'), $user->user_login);
                BS2FA_Helpers::notify_admin(__('User enabled authenticator app', 'brightery-secure-2fa'), $admin_msg);

                BS2FA_Helpers::maybe_invalidate_other_sessions($user_id, __('Authenticator app enabled.', 'brightery-secure-2fa'));
            } else {
                BS2FA_Logger::add('totp_enable_failed', ['user_id' => $user_id, 'details' => 'Authenticator verification failed during setup.'], 'warning');
            }
        }

        if (isset($request['bs2fa_disable_totp'])) {
            delete_user_meta($user_id, 'bs2fa_totp_secret');
            delete_user_meta($user_id, 'bs2fa_pending_totp_secret');
            BS2FA_Logger::add('totp_disabled', ['user_id' => $user_id, 'details' => 'Authenticator app disabled.'], 'warning');

            /* translators: 1: User login name, 2: Site URL */
            $msg = sprintf(__('The authenticator app for %1$s was disabled on %2$s.', 'brightery-secure-2fa'), $user->user_login, home_url('/'));
            BS2FA_Helpers::notify_user($user_id, __('Authenticator app disabled', 'brightery-secure-2fa'), $msg);

            BS2FA_Helpers::maybe_invalidate_other_sessions($user_id, __('Authenticator app disabled.', 'brightery-secure-2fa'));
        }
    }

    public function register_woocommerce_endpoint() {
        if (!BS2FA_Helpers::get_option('woocommerce_account_integration', 1)) {
            return;
        }

        add_rewrite_endpoint('bs2fa_security', EP_ROOT | EP_PAGES);
    }

    public function handle_woocommerce_account_requests() {
        if (!BS2FA_Helpers::is_woocommerce_active() || !BS2FA_Helpers::get_option('woocommerce_account_integration', 1) || !is_user_logged_in()) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        if (function_exists('is_account_page') && is_account_page() && BS2FA_Helpers::request_method() === 'POST' && !empty($_POST['bs2fa_frontend_save'])) {
            $user_id = get_current_user_id();
            if (!isset($_POST['bs2fa_frontend_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['bs2fa_frontend_nonce'])), 'bs2fa_frontend_save_' . $user_id)) {
                return;
            }

            $this->process_security_form_submission($user_id, $this->security_form_request());
            if (function_exists('wc_add_notice')) {
                wc_add_notice(__('Security settings updated.', 'brightery-secure-2fa'), 'success');
            }
            wp_safe_redirect(BS2FA_Helpers::wc_security_url());
            exit;
        }
    }

    public function filter_woocommerce_account_menu_items($items) {
        if (!BS2FA_Helpers::is_woocommerce_active() || !BS2FA_Helpers::get_option('woocommerce_account_integration', 1)) {
            return $items;
        }

        $updated = [];
        $inserted = false;
        foreach ((array) $items as $key => $label) {
            if ($key === 'customer-logout') {
                $updated['bs2fa_security'] = __('Security', 'brightery-secure-2fa');
                $inserted = true;
            }
            $updated[$key] = $label;
        }

        if (!$inserted) {
            $updated['bs2fa_security'] = __('Security', 'brightery-secure-2fa');
        }

        return $updated;
    }

    public function render_woocommerce_security_endpoint() {
        if (!is_user_logged_in()) {
            echo '<p>' . esc_html__('Please sign in to manage account security.', 'brightery-secure-2fa') . '</p>';
            return;
        }

        $user = wp_get_current_user();
        echo '<div class="bs2fa-admin-wrap">';
        echo '<h2>' . esc_html__('Account Security', 'brightery-secure-2fa') . '</h2>';
        echo '<p>' . esc_html__('Manage your authenticator app, passkeys, trusted devices, and backup codes from your WooCommerce account area.', 'brightery-secure-2fa') . '</p>';
        if (BS2FA_Helpers::should_force_enrollment($user->ID) && function_exists('wc_print_notice')) {
            $message = __('Your account must finish two-factor setup before you continue.', 'brightery-secure-2fa');
            if (BS2FA_Helpers::get_option('woocommerce_block_checkout_if_required', 0)) {
                $message .= ' ' . __('Checkout can stay blocked until setup is complete.', 'brightery-secure-2fa');
            }
            wc_print_notice($message, 'notice');
        }
        echo '<form method="post">';
        wp_nonce_field('bs2fa_frontend_save_' . $user->ID, 'bs2fa_frontend_nonce');
        echo '<input type="hidden" name="bs2fa_frontend_save" value="1">';
        $this->render_profile_section($user);
        submit_button(__('Save Changes', 'brightery-secure-2fa'));
        echo '</form>';
        echo '</div>';
    }


    protected function wc_pending_security_message($context = 'account') {
        $link = '<a href="' . esc_url(BS2FA_Helpers::wc_security_url()) . '">' . esc_html__('My Account → Security', 'brightery-secure-2fa') . '</a>';
        if ($context === 'checkout' && BS2FA_Helpers::get_option('woocommerce_block_checkout_if_required', 0)) {
            /* translators: %s: Security page link */
            return sprintf(__('Security setup is required before checkout can continue. Finish setup in %s.', 'brightery-secure-2fa'), $link);
        }
        if ($context === 'cart') {
            /* translators: %s: Security page link */
            return sprintf(__('Your user group still needs two-factor setup. You can finish it from %s before checkout.', 'brightery-secure-2fa'), $link);
        }
        /* translators: %s: Security page link */
        return sprintf(__('Your account security setup is still pending. Finish it from %s.', 'brightery-secure-2fa'), $link);
    }

    protected function maybe_wc_notice($context = 'account', $type = 'notice') {
        if (!BS2FA_Helpers::is_woocommerce_active() || !BS2FA_Helpers::get_option('woocommerce_account_integration', 1) || !BS2FA_Helpers::get_option('woocommerce_checkout_notice', 1) || !is_user_logged_in()) {
            return;
        }

        $user_id = get_current_user_id();
        if (!BS2FA_Helpers::should_force_enrollment($user_id) || !function_exists('wc_print_notice')) {
            return;
        }

        wc_print_notice(wp_kses_post($this->wc_pending_security_message($context)), $type);
    }

    public function maybe_render_woocommerce_account_notice() {
        $this->maybe_wc_notice('account', 'notice');
    }

    public function maybe_render_woocommerce_cart_notice() {
        if (function_exists('is_cart') && is_cart()) {
            $this->maybe_wc_notice('cart', 'notice');
        }
    }

    public function maybe_render_woocommerce_checkout_notice() {
        if (function_exists('is_checkout') && is_checkout() && !is_wc_endpoint_url('order-received')) {
            $type = BS2FA_Helpers::get_option('woocommerce_block_checkout_if_required', 0) ? 'error' : 'notice';
            $this->maybe_wc_notice('checkout', $type);
        }
    }

    public function maybe_enforce_woocommerce_checkout_security() {
        if (!BS2FA_Helpers::is_woocommerce_active() || !BS2FA_Helpers::get_option('woocommerce_account_integration', 1) || !BS2FA_Helpers::get_option('woocommerce_block_checkout_if_required', 0) || !is_user_logged_in()) {
            return;
        }

        if (!function_exists('is_checkout') || !is_checkout() || is_wc_endpoint_url('order-received')) {
            return;
        }

        $user_id = get_current_user_id();
        if (!BS2FA_Helpers::should_force_enrollment($user_id) || !function_exists('wc_add_notice')) {
            return;
        }

        wc_add_notice(wp_kses_post($this->wc_pending_security_message('checkout')), 'error');
        BS2FA_Logger::add('woocommerce_checkout_blocked_pending_security', ['user_id' => $user_id, 'details' => 'Checkout blocked until required 2FA setup is completed.'], 'warning');
    }

    public function register_dashboard_widget() {
        if (!current_user_can('manage_options') || !BS2FA_Helpers::get_option('dashboard_widget_enabled', 1)) {
            return;
        }

        wp_add_dashboard_widget('bs2fa_dashboard_widget', __('Brightery Secure 2FA', 'brightery-secure-2fa'), [$this, 'render_dashboard_widget']);
    }

    public function render_dashboard_widget() {
        $counts = BS2FA_Helpers::count_required_users();
        $recent = BS2FA_Logger::get_logs(['limit' => 6]);
        $lockouts = BS2FA_Helpers::count_recent_events(['primary_login_blocked', 'totp_login_failed', 'passkey_login_failed'], DAY_IN_SECONDS);
        $rest_blocks = BS2FA_Helpers::count_recent_events(['rest_api_auth_blocked', 'rest_api_pending_blocked'], DAY_IN_SECONDS);
        $new_contexts = BS2FA_Helpers::count_recent_events(['new_login_context_detected'], DAY_IN_SECONDS);
        $allowlisted = count(BS2FA_Helpers::resolve_user_ids_from_identifiers(BS2FA_Helpers::get_option('rest_api_allowlisted_users', '')));
        echo '<p><strong>' . esc_html__('Required users:', 'brightery-secure-2fa') . '</strong> ' . esc_html((string) $counts['required']) . ' &nbsp; <strong>' . esc_html__('Enrolled:', 'brightery-secure-2fa') . '</strong> ' . esc_html((string) $counts['enrolled']) . ' &nbsp; <strong>' . esc_html__('Pending:', 'brightery-secure-2fa') . '</strong> ' . esc_html((string) $counts['pending']) . '</p>';
        echo '<p><strong>' . esc_html__('Lockout signals (24h):', 'brightery-secure-2fa') . '</strong> ' . esc_html((string) $lockouts) . ' &nbsp; <strong>' . esc_html__('REST blocks (24h):', 'brightery-secure-2fa') . '</strong> ' . esc_html((string) $rest_blocks) . ' &nbsp; <strong>' . esc_html__('New login contexts (24h):', 'brightery-secure-2fa') . '</strong> ' . esc_html((string) $new_contexts) . '</p>';
        echo '<p><strong>' . esc_html__('Allowlisted service accounts:', 'brightery-secure-2fa') . '</strong> ' . esc_html((string) $allowlisted) . '</p>';
        if (empty($recent)) {
            echo '<p>' . esc_html__('No recent security activity yet.', 'brightery-secure-2fa') . '</p>';
        } else {
            echo '<ul class="bs2fa-activity-list">';
            foreach ($recent as $log) {
                echo '<li><strong>' . esc_html(BS2FA_Helpers::format_time((int) ($log['time'] ?? 0))) . '</strong> — ' . esc_html((string) ($log['event'] ?? ''));
                if (!empty($log['details'])) {
                    echo ': ' . esc_html((string) $log['details']);
                }
                echo '</li>';
            }
            echo '</ul>';
        }
        echo '<p><a class="button button-secondary" href="' . esc_url(admin_url('options-general.php?page=bs2fa-settings')) . '">' . esc_html__('Open plugin settings', 'brightery-secure-2fa') . '</a></p>';
    }

    public function register_rest_routes() {
        if (!BS2FA_Helpers::get_option('rest_api_status_endpoint', 1)) {
            return;
        }

        register_rest_route('bs2fa/v1', '/me', [
                'methods' => 'GET',
                'permission_callback' => function () {
                    return is_user_logged_in() && current_user_can('read');
                },
                'callback' => [$this, 'rest_status_me'],
        ]);
    }

    public function rest_status_me($request) {
        $user_id = get_current_user_id();
        $user = get_user_by('id', $user_id);
        if (!$user) {
            return new WP_Error('bs2fa_user_missing', __('User not found.', 'brightery-secure-2fa'), ['status' => 404]);
        }

        return rest_ensure_response([
                'user' => [
                        'id' => $user_id,
                        'login' => $user->user_login,
                ],
                'required' => BS2FA_Helpers::user_has_required_role($user),
                'enrolled' => BS2FA_Helpers::user_is_enrolled($user_id),
                'methods' => [
                        'totp' => BS2FA_Helpers::user_has_totp($user_id),
                        'passkeys' => BS2FA_Helpers::user_has_passkeys($user_id),
                ],
                'backup_codes_left' => BS2FA_Helpers::backup_codes_left($user_id),
                'trusted_devices' => count(BS2FA_Trusted_Devices::get_devices($user_id)),
                'force_enrollment_now' => BS2FA_Helpers::should_force_enrollment($user_id),
                'allowed_methods' => BS2FA_Helpers::allowed_methods(),
                'setup_url' => BS2FA_Helpers::get_setup_url(),
                'woocommerce_security_url' => BS2FA_Helpers::wc_security_url(),
                'api_policy' => [
                        'mode' => BS2FA_Helpers::get_option('rest_api_role_mode', 'required_or_enrolled'),
                        'protected_roles' => BS2FA_Helpers::rest_policy_roles(),
                        'allowlisted' => BS2FA_Helpers::user_is_rest_allowlisted($user),
                        'authorization_header_blocked' => (bool) BS2FA_Helpers::get_option('rest_api_block_basic_auth', 1),
                ],
        ]);
    }

    public function filter_rest_authentication_errors($result) {
        if (!BS2FA_Helpers::get_option('enabled', 1) || !BS2FA_Helpers::get_option('rest_api_policy_enabled', 1)) {
            return $result;
        }

        if (!empty($result) || !defined('REST_REQUEST') || !REST_REQUEST) {
            return $result;
        }

        $user_id = get_current_user_id();
        if (!$user_id) {
            return $result;
        }

        $user = get_user_by('id', $user_id);
        if ($user && BS2FA_Helpers::user_is_rest_allowlisted($user) && BS2FA_Helpers::request_has_authorization_header()) {
            BS2FA_Logger::add('rest_api_allowlisted_bypass', ['user_id' => $user_id, 'details' => 'REST Authorization-header request allowed for configured service account.']);
            return $result;
        }

        if (!BS2FA_Helpers::user_needs_api_policy($user_id)) {
            return $result;
        }

        if (BS2FA_Helpers::should_force_enrollment($user_id) && BS2FA_Helpers::request_has_authorization_header()) {
            BS2FA_Logger::add('rest_api_pending_blocked', ['user_id' => $user_id, 'details' => 'REST authentication blocked until the required account finishes 2FA enrollment.'], 'warning');
            return new WP_Error('bs2fa_rest_pending', __('This account must finish two-factor setup before it can use REST API authentication headers.', 'brightery-secure-2fa'), ['status' => 403]);
        }

        if (BS2FA_Helpers::get_option('rest_api_block_basic_auth', 1) && BS2FA_Helpers::request_has_authorization_header() && !BS2FA_Helpers::request_has_rest_nonce()) {
            BS2FA_Logger::add('rest_api_auth_blocked', ['user_id' => $user_id, 'details' => 'Authorization-header REST authentication blocked for a protected/enrolled account.'], 'warning');
            return new WP_Error('bs2fa_rest_auth_blocked', __('This protected account cannot use Authorization-header REST authentication. Use a normal browser session with nonce-backed REST requests or a dedicated service account.', 'brightery-secure-2fa'), ['status' => 403]);
        }

        return $result;
    }

    public function mark_enrollment_start($user_login, $user) {
        if ($user instanceof WP_User && BS2FA_Helpers::user_has_required_role($user) && !get_user_meta($user->ID, 'bs2fa_enrollment_started_at', true)) {
            update_user_meta($user->ID, 'bs2fa_enrollment_started_at', time());
        }
    }

    public function clear_primary_login_rate_limit($user_login, $user) {
        if ($user instanceof WP_User) {
            BS2FA_Rate_Limit::clear('primary', BS2FA_Rate_Limit::primary_identity($user->user_login));
        }
    }

    public function note_primary_login_failed($username) {
        if (!BS2FA_Rate_Limit::enabled()) {
            return;
        }

        $identity = BS2FA_Rate_Limit::primary_identity($username);
        $lock_until = BS2FA_Rate_Limit::note_failure('primary', $identity);

        if ($lock_until) {
            /* translators: %s: Formatted time */
            $details = sprintf(__('Primary login locked until %s.', 'brightery-secure-2fa'), BS2FA_Helpers::format_time($lock_until));
        } else {
            $details = __('Primary login failed.', 'brightery-secure-2fa');
        }

        BS2FA_Logger::add('primary_login_failed', ['username' => (string) $username, 'details' => $details], $lock_until ? 'warning' : 'info');

        if ($lock_until) {
            /* translators: 1: Username, 2: Formatted time */
            $this->maybe_send_lockout_alert('primary:' . strtolower((string) $username), sprintf(__('Primary login lockout for %1$s until %2$s.', 'brightery-secure-2fa'), (string) $username, BS2FA_Helpers::format_time($lock_until)));
        }
    }

    public function enforce_enrollment() {
        if (!is_user_logged_in()) {
            return;
        }

        if (!BS2FA_Helpers::should_force_enrollment(get_current_user_id())) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : '';
        $allowed_pages = ['bs2fa-setup'];
        if (in_array($page, $allowed_pages, true) || wp_doing_ajax()) {
            return;
        }

        $screen = function_exists('get_current_screen') ? get_current_screen() : null;
        if ($screen && in_array($screen->base, ['profile', 'user-edit'], true)) {
            return;
        }

        BS2FA_Logger::add('enrollment_forced', ['user_id' => get_current_user_id(), 'details' => 'User redirected to complete required 2FA setup.'], 'warning');
        wp_safe_redirect(BS2FA_Helpers::get_setup_url());
        exit;
    }

    public function enforce_enrollment_front() {
        if (!is_user_logged_in()) {
            return;
        }

        if (!BS2FA_Helpers::should_force_enrollment(get_current_user_id())) {
            return;
        }

        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
            return;
        }

        if (BS2FA_Helpers::is_woocommerce_active() && BS2FA_Helpers::get_option('woocommerce_account_integration', 1) && BS2FA_Helpers::get_option('woocommerce_force_account_security', 1) && function_exists('is_account_page') && is_account_page()) {
            if (BS2FA_Helpers::is_wc_security_endpoint()) {
                return;
            }

            BS2FA_Logger::add('woocommerce_enrollment_forced', ['user_id' => get_current_user_id(), 'details' => 'WooCommerce account access redirected to the security endpoint pending required 2FA setup.'], 'warning');
            wp_safe_redirect(BS2FA_Helpers::wc_security_url());
            exit;
        }

        BS2FA_Logger::add('enrollment_forced_front', ['user_id' => get_current_user_id(), 'details' => 'Front-end access blocked pending required 2FA setup.'], 'warning');
        wp_safe_redirect(BS2FA_Helpers::get_setup_url());
        exit;
    }

    public function render_setup_page() {
        if (!is_user_logged_in()) {
            auth_redirect();
        }

        $user = wp_get_current_user();

        if (BS2FA_Helpers::request_method() === 'POST' && !empty($_POST['bs2fa_setup_save'])) {
            check_admin_referer('bs2fa_setup_save_' . $user->ID, 'bs2fa_setup_nonce');
            $this->process_security_form_submission($user->ID, $this->security_form_request());
            wp_safe_redirect(add_query_arg('bs2fa_notice', 'setup_saved', BS2FA_Helpers::get_setup_url()));
            exit;
        }

        echo '<div class="wrap bs2fa-admin-wrap">';
        echo '<h1>' . esc_html__('Finish your 2FA setup', 'brightery-secure-2fa') . '</h1>';
        $this->render_notice();
        if (BS2FA_Helpers::should_force_enrollment($user->ID)) {
            echo '<div class="notice notice-warning"><p>' . esc_html__('Your account role requires security setup before you can continue.', 'brightery-secure-2fa') . '</p></div>';
        }
        echo '<form method="post" action="' . esc_url(BS2FA_Helpers::get_setup_url()) . '">';
        wp_nonce_field('bs2fa_setup_save_' . $user->ID, 'bs2fa_setup_nonce');
        echo '<input type="hidden" name="bs2fa_setup_save" value="1">';
        $this->render_profile_section($user);
        submit_button(__('Save Changes', 'brightery-secure-2fa'));
        echo '</form></div>';
    }

    public function pre_auth_rate_limit($user, $username, $password) {
        if ($user instanceof WP_Error) {
            return $user;
        }

        if (!BS2FA_Helpers::get_option('enabled', 1) || !BS2FA_Rate_Limit::enabled() || !BS2FA_Helpers::is_login_request()) {
            return $user;
        }

        $identity = BS2FA_Rate_Limit::primary_identity($username);
        if (!$identity) {
            return $user;
        }

        if (BS2FA_Rate_Limit::is_blocked('primary', $identity)) {
            $remaining = BS2FA_Rate_Limit::remaining_seconds('primary', $identity);
            /* translators: %d: Number of minutes */
            $msg = sprintf(__('Too many login failures. Try again in about %d minute(s).', 'brightery-secure-2fa'), max(1, (int) ceil($remaining / MINUTE_IN_SECONDS)));
            BS2FA_Logger::add('primary_login_blocked', ['username' => (string) $username, 'details' => $msg], 'warning');
            return new WP_Error('bs2fa_rate_limited', $msg);
        }

        return $user;
    }

    public function intercept_login($user, $username, $password) {
        if (!$user instanceof WP_User) {
            return $user;
        }

        if (!BS2FA_Helpers::get_option('enabled', 1)) {
            return $user;
        }

        if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST && BS2FA_Helpers::get_option('hide_for_xmlrpc', 1)) {
            return $user;
        }

        if (!BS2FA_Helpers::is_login_request()) {
            return $user;
        }

        if (!$this->user_requires_challenge($user->ID)) {
            return $user;
        }

        if (BS2FA_Trusted_Devices::is_trusted_for_user($user->ID)) {
            BS2FA_Logger::add('trusted_device_login_bypass', ['user_id' => $user->ID, 'details' => 'Second factor skipped because this browser is trusted.']);
            return $user;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $remember = !empty($_POST['rememberme']);
        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $redirect_to = BS2FA_Helpers::sanitize_redirect_target(isset($_POST['redirect_to']) ? sanitize_text_field(wp_unslash($_POST['redirect_to'])) : '', admin_url());
        BS2FA_Helpers::begin_pending_login($user->ID, $remember, $redirect_to);
        BS2FA_Logger::add('second_factor_required', ['user_id' => $user->ID, 'details' => 'Primary credentials accepted. Waiting for second factor.']);
        wp_safe_redirect(wp_login_url() . '?bs2fa=challenge');
        exit;
    }

    protected function user_requires_challenge($user_id) {
        if (BS2FA_Helpers::user_has_totp($user_id) || BS2FA_Helpers::user_has_passkeys($user_id)) {
            return true;
        }
        return false;
    }

    protected function challenge_is_blocked($user_id) {
        $identity = BS2FA_Rate_Limit::challenge_identity($user_id);
        if (!BS2FA_Rate_Limit::enabled() || !BS2FA_Rate_Limit::is_blocked('challenge', $identity)) {
            return false;
        }

        $remaining = BS2FA_Rate_Limit::remaining_seconds('challenge', $identity);
        /* translators: %d: Number of minutes */
        return sprintf(__('Too many second-factor failures. Try again in about %d minute(s).', 'brightery-secure-2fa'), max(1, (int) ceil($remaining / MINUTE_IN_SECONDS)));
    }

    public function handle_login_views() {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        if (empty($_GET['bs2fa'])) {
            return;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $action = sanitize_key(wp_unslash($_GET['bs2fa']));
        if ($action !== 'challenge') {
            return;
        }

        $pending = BS2FA_Helpers::get_login_pending();
        if (!$pending) {
            wp_safe_redirect(wp_login_url());
            exit;
        }

        $user = get_user_by('id', (int) $pending['user_id']);
        if (!$user) {
            BS2FA_Helpers::destroy_login_pending($pending['token']);
            wp_safe_redirect(wp_login_url());
            exit;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        if (BS2FA_Helpers::request_method() === 'POST' && isset($_POST['bs2fa_verify_totp'])) {
            $this->handle_totp_login_post($user, $pending);
        }

        $login_error = null;
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        if (!empty($_GET['bs2fa_error'])) {
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            $login_error = new WP_Error('bs2fa_invalid', sanitize_text_field(wp_unslash($_GET['bs2fa_error'])));
        }

        $block_message = $this->challenge_is_blocked($user->ID);
        if ($block_message) {
            $login_error = new WP_Error('bs2fa_blocked', $block_message);
        }

        login_header(__('Second Factor Required', 'brightery-secure-2fa'), '', $login_error ?: new WP_Error('bs2fa_required', __('Complete your second factor to sign in.', 'brightery-secure-2fa')));
        ?>
        <div class="bs2fa-login-card">
            <p><strong><?php esc_html_e('Account:', 'brightery-secure-2fa'); ?></strong> <?php echo esc_html($user->user_login); ?></p>
            <p><?php esc_html_e('Choose one of your configured security methods to continue.', 'brightery-secure-2fa'); ?></p>

            <?php if (BS2FA_Helpers::user_has_totp($user->ID)) : ?>
                <form method="post" class="bs2fa-totp-form">
                    <?php wp_nonce_field('bs2fa_challenge'); ?>
                    <label for="bs2fa_totp_login"><?php esc_html_e('Authenticator Code', 'brightery-secure-2fa'); ?></label>
                    <input type="text" name="bs2fa_totp_login" id="bs2fa_totp_login" inputmode="numeric" pattern="[0-9]*" maxlength="10" autocomplete="one-time-code" <?php disabled((bool) $block_message); ?>>
                    <p class="description"><?php esc_html_e('You can also enter a backup code.', 'brightery-secure-2fa'); ?></p>
                    <?php if (BS2FA_Trusted_Devices::enabled()) : ?>
                        <p class="bs2fa-trust-device"><label><input type="checkbox" name="bs2fa_trust_device" value="1" <?php disabled((bool) $block_message); ?>> <?php
                                /* translators: %d: Device days */
                                printf(esc_html__('Trust this device for %d day(s)', 'brightery-secure-2fa'), (int) BS2FA_Helpers::get_option('trusted_device_days', 30)); ?></label></p>
                        <p class="bs2fa-trust-name"><label for="bs2fa_trust_device_label"><?php esc_html_e('Optional device name', 'brightery-secure-2fa'); ?></label><input type="text" name="bs2fa_trust_device_label" id="bs2fa_trust_device_label" maxlength="80" placeholder="<?php esc_attr_e('Office MacBook, Personal iPhone…', 'brightery-secure-2fa'); ?>" <?php disabled((bool) $block_message); ?>></p>
                    <?php endif; ?>
                    <p class="submit"><button type="submit" class="button button-primary" name="bs2fa_verify_totp" value="1" <?php disabled((bool) $block_message); ?>><?php esc_html_e('Verify and Login', 'brightery-secure-2fa'); ?></button></p>
                </form>
            <?php endif; ?>

            <?php if (BS2FA_Helpers::user_has_passkeys($user->ID)) : ?>
                <div class="bs2fa-passkey-login">
                    <button type="button" class="button button-secondary bs2fa-login-passkey" <?php disabled((bool) $block_message); ?>><?php esc_html_e('Use Passkey / Fingerprint', 'brightery-secure-2fa'); ?></button>
                    <div class="bs2fa-passkey-status"></div>
                </div>
            <?php endif; ?>

            <?php if (BS2FA_Trusted_Devices::enabled() && !BS2FA_Helpers::user_has_totp($user->ID)) : ?>
                <p class="bs2fa-trust-device"><label><input type="checkbox" name="bs2fa_trust_device" value="1" <?php disabled((bool) $block_message); ?>> <?php
                        /* translators: %d: Device days */
                        printf(esc_html__('Trust this device for %d day(s)', 'brightery-secure-2fa'), (int) BS2FA_Helpers::get_option('trusted_device_days', 30)); ?></label></p>
                <p class="bs2fa-trust-name"><label for="bs2fa_trust_device_label_alt"><?php esc_html_e('Optional device name', 'brightery-secure-2fa'); ?></label><input type="text" name="bs2fa_trust_device_label" id="bs2fa_trust_device_label_alt" maxlength="80" placeholder="<?php esc_attr_e('Office MacBook, Personal iPhone…', 'brightery-secure-2fa'); ?>" <?php disabled((bool) $block_message); ?>></p>
            <?php endif; ?>

            <p><a href="<?php echo esc_url(wp_login_url()); ?>"><?php esc_html_e('Back to login', 'brightery-secure-2fa'); ?></a></p>
        </div>
        <?php
        login_footer();
        exit;
    }

    protected function handle_totp_login_post($user, $pending) {
        check_admin_referer('bs2fa_challenge');
        $code = sanitize_text_field(wp_unslash($_POST['bs2fa_totp_login'] ?? ''));
        $secret = BS2FA_Crypto::decrypt_user_meta($user->ID, 'bs2fa_totp_secret');
        $identity = BS2FA_Rate_Limit::challenge_identity($user->ID);

        if ($message = $this->challenge_is_blocked($user->ID)) {
            wp_safe_redirect(wp_login_url() . '?bs2fa=challenge&bs2fa_error=' . rawurlencode($message));
            exit;
        }

        $valid = false;
        if ($secret && BS2FA_TOTP::verify($secret, $code)) {
            $valid = true;
            BS2FA_Logger::add('totp_login_success', ['user_id' => $user->ID, 'details' => 'Authenticator code accepted.']);
        }

        if (!$valid && BS2FA_TOTP::verify_backup_code($user->ID, $code)) {
            $valid = true;
            BS2FA_Logger::add('backup_code_login_success', ['user_id' => $user->ID, 'details' => 'Backup code accepted.'], 'warning');
        }

        if (!$valid) {
            $lock_until = BS2FA_Rate_Limit::note_failure('challenge', $identity);

            if ($lock_until) {
                /* translators: %s: Formatted time */
                $details = sprintf(__('Second-factor lockout until %s.', 'brightery-secure-2fa'), BS2FA_Helpers::format_time($lock_until));
            } else {
                $details = __('Invalid authenticator or backup code.', 'brightery-secure-2fa');
            }

            BS2FA_Logger::add('totp_login_failed', ['user_id' => $user->ID, 'details' => $details], 'warning');

            if ($lock_until) {
                /* translators: 1: User label, 2: Formatted time */
                $this->maybe_send_lockout_alert('challenge:user:' . $user->ID, sprintf(__('Second-factor lockout for %1$s until %2$s.', 'brightery-secure-2fa'), BS2FA_Helpers::user_label($user->ID), BS2FA_Helpers::format_time($lock_until)));
            }
            wp_safe_redirect(wp_login_url() . '?bs2fa=challenge&bs2fa_error=' . rawurlencode(__('Invalid authenticator or backup code.', 'brightery-secure-2fa')));
            exit;
        }

        BS2FA_Rate_Limit::clear('challenge', $identity);
        $this->complete_pending_login($user->ID, $pending, false, BS2FA_Trusted_Devices::trust_requested_from_request(), BS2FA_Trusted_Devices::trust_label_from_request());
    }

    protected function complete_pending_login($user_id, $pending, $as_json = false, $trust_device = false, $trust_device_label = '') {
        $user = get_user_by('id', (int) $user_id);
        if (!$user) {
            if ($as_json) {
                BS2FA_Helpers::json_response(['success' => false, 'message' => __('User account not found.', 'brightery-secure-2fa')], 400);
            }
            wp_safe_redirect(wp_login_url());
            exit;
        }

        wp_set_current_user($user->ID);
        wp_set_auth_cookie($user->ID, !empty($pending['remember']), is_ssl());
        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
        do_action('wp_login', $user->user_login, $user);
        BS2FA_Helpers::destroy_login_pending($pending['token']);

        if ($trust_device) {
            $device = BS2FA_Trusted_Devices::register_device($user->ID, $trust_device_label);
            if (!empty($device)) {
                BS2FA_Logger::add('trusted_device_added', ['user_id' => $user->ID, 'details' => 'A browser was marked as trusted after successful 2FA.']);
            }
        }

        $redirect_to = !empty($pending['redirect_to']) ? $pending['redirect_to'] : admin_url();
        BS2FA_Logger::add('login_completed', ['user_id' => $user->ID, 'details' => 'Second factor completed successfully.']);
        if ($as_json) {
            BS2FA_Helpers::json_response(['success' => true, 'redirect' => $redirect_to]);
        }

        wp_safe_redirect($redirect_to);
        exit;
    }

    public function filter_application_passwords_for_user($available, $user) {
        if (!$available || !BS2FA_Helpers::get_option('block_application_passwords', 1) || !($user instanceof WP_User)) {
            return $available;
        }

        if (BS2FA_Helpers::user_is_rest_allowlisted($user)) {
            return $available;
        }

        if (BS2FA_Helpers::user_has_required_role($user) || BS2FA_Helpers::user_is_enrolled($user->ID)) {
            return false;
        }

        return $available;
    }

    protected function maybe_send_lockout_alert($key, $message) {
        if (!BS2FA_Helpers::get_option('alert_admin_on_lockout', 1)) {
            return;
        }

        $cache_key = 'bs2fa_lockout_notice_' . md5((string) $key);
        if (get_transient($cache_key)) {
            return;
        }

        set_transient($cache_key, 1, 30 * MINUTE_IN_SECONDS);
        /* translators: %s: Client IP Address */
        BS2FA_Helpers::send_admin_mail(__('Brightery Secure 2FA lockout alert', 'brightery-secure-2fa'), $message . "\n\n" . sprintf(__('IP: %s', 'brightery-secure-2fa'), BS2FA_Helpers::client_ip()));
    }


    public function maybe_process_login_context($user_login, $user) {
        if (!($user instanceof WP_User) || !BS2FA_Helpers::get_option('enabled', 1)) {
            return;
        }

        if (!BS2FA_Helpers::get_option('alert_user_on_new_location', 1) && !BS2FA_Helpers::get_option('alert_admin_on_new_location', 0)) {
            return;
        }

        $context = BS2FA_Helpers::current_login_context();
        if (empty($context['hash'])) {
            return;
        }

        $known_contexts = BS2FA_Helpers::get_login_contexts($user->ID);
        if (empty($known_contexts)) {
            BS2FA_Helpers::remember_login_context($user->ID, $context);
            BS2FA_Logger::add('login_context_baselined', ['user_id' => $user->ID, 'details' => 'Initial login context recorded for future sign-in comparisons.']);
            return;
        }

        if (BS2FA_Helpers::has_login_context($user->ID, $context['hash'])) {
            BS2FA_Helpers::remember_login_context($user->ID, $context);
            return;
        }

        BS2FA_Helpers::remember_login_context($user->ID, $context);
        $summary = BS2FA_Helpers::login_context_summary($context);
        $network_label = $summary ? $summary : __('an unknown network', 'brightery-secure-2fa');

        $details = sprintf(
        /* translators: 1: User login, 2: Context summary */
                __('New login context detected for %1$s from %2$s.', 'brightery-secure-2fa'),
                $user->user_login,
                $network_label
        );

        BS2FA_Logger::add('new_login_context_detected', ['user_id' => $user->ID, 'details' => $details], 'warning');

        $message = sprintf(
        /* translators: 1: User login, 2: Site URL, 3: Network, 4: IP, 5: Country, 6: Time, 7: Browser */
                __('A sign-in completed for %1$s on %2$s from a new local login context.

Network: %3$s
IP: %4$s
Country header: %5$s
Time: %6$s
Browser: %7$s

This alert is generated only from request data already available to WordPress. No third-party geolocation service is used.', 'brightery-secure-2fa'),
                $user->user_login,
                home_url('/'),
                $summary ? $summary : __('Unknown network', 'brightery-secure-2fa'),
                (string) ($context['masked_ip'] ?? ''),
                !empty($context['country']) ? (string) $context['country'] : __('Unavailable', 'brightery-secure-2fa'),
                BS2FA_Helpers::format_time((int) ($context['time'] ?? time())),
                !empty($context['user_agent']) ? (string) $context['user_agent'] : __('Unavailable', 'brightery-secure-2fa')
        );

        if (BS2FA_Helpers::get_option('alert_user_on_new_location', 1)) {
            BS2FA_Helpers::send_user_mail($user->ID, __('Brightery Secure 2FA new login context', 'brightery-secure-2fa'), $message);
        }

        if (BS2FA_Helpers::get_option('alert_admin_on_new_location', 0)) {
            /* translators: 1: User login, 2: User ID, 3: Message */
            BS2FA_Helpers::send_admin_mail(__('Brightery Secure 2FA new login context', 'brightery-secure-2fa'), sprintf(__('User: %1$s (#%2$d)

%3$s', 'brightery-secure-2fa'), $user->user_login, (int) $user->ID, $message));
        }
    }

    public function ajax_register_options() {
        if (!is_user_logged_in()) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => __('Not logged in.', 'brightery-secure-2fa')], 403);
        }

        check_ajax_referer('bs2fa_manage', 'nonce');

        try {
            $options = BS2FA_WebAuthn::registration_options(get_current_user_id());
            BS2FA_Helpers::json_response(['success' => true, 'options' => $options]);
        } catch (Throwable $e) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => $e->getMessage()], 400);
        }
    }

    public function ajax_register_verify() {
        if (!is_user_logged_in()) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => __('Not logged in.', 'brightery-secure-2fa')], 403);
        }

        check_ajax_referer('bs2fa_manage', 'nonce');
        $payload = BS2FA_Helpers::json_input();

        try {
            BS2FA_WebAuthn::verify_registration(get_current_user_id(), $payload);
            $user_id = get_current_user_id();
            BS2FA_Logger::add('passkey_added', ['user_id' => $user_id, 'details' => 'New passkey registered.']);
            $user = get_user_by('id', $user_id);
            if ($user) {
                /* translators: 1: User login name, 2: Site URL */
                $msg = sprintf(__('A new passkey was added for %1$s on %2$s.', 'brightery-secure-2fa'), $user->user_login, home_url('/'));
                BS2FA_Helpers::notify_user($user_id, __('New passkey added', 'brightery-secure-2fa'), $msg);

                /* translators: %s: User login */
                BS2FA_Helpers::notify_admin(__('User added a passkey', 'brightery-secure-2fa'), sprintf(__('User %s added a new passkey.', 'brightery-secure-2fa'), $user->user_login));
            }
            BS2FA_Helpers::maybe_invalidate_other_sessions($user_id, __('Passkey added.', 'brightery-secure-2fa'));
            BS2FA_Helpers::json_response(['success' => true]);
        } catch (Throwable $e) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => $e->getMessage()], 400);
        }
    }

    public function ajax_remove_passkey() {
        if (!is_user_logged_in()) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => __('Not logged in.', 'brightery-secure-2fa')], 403);
        }

        check_ajax_referer('bs2fa_manage', 'nonce');
        $credential_id = sanitize_text_field(wp_unslash($_POST['credential_id'] ?? ''));
        if (!$credential_id) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => __('Credential ID is required.', 'brightery-secure-2fa')], 400);
        }

        $user_id = get_current_user_id();
        BS2FA_WebAuthn::delete_credential($user_id, $credential_id);
        BS2FA_Logger::add('passkey_removed', ['user_id' => $user_id, 'details' => 'Passkey removed.'], 'warning');
        $user = get_user_by('id', $user_id);
        if ($user) {
            /* translators: 1: User login name, 2: Site URL */
            $msg = sprintf(__('A passkey was removed for %1$s on %2$s.', 'brightery-secure-2fa'), $user->user_login, home_url('/'));
            BS2FA_Helpers::notify_user($user_id, __('Passkey removed', 'brightery-secure-2fa'), $msg);
        }
        BS2FA_Helpers::maybe_invalidate_other_sessions($user_id, __('Passkey removed.', 'brightery-secure-2fa'));
        BS2FA_Helpers::json_response(['success' => true]);
    }

    public function ajax_regen_backup_codes() {
        if (!is_user_logged_in()) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => __('Not logged in.', 'brightery-secure-2fa')], 403);
        }

        check_ajax_referer('bs2fa_manage', 'nonce');

        $codes = BS2FA_TOTP::generate_backup_codes();
        update_user_meta(get_current_user_id(), 'bs2fa_backup_codes', BS2FA_TOTP::hash_backup_codes($codes));
        BS2FA_Logger::add('backup_codes_regenerated', ['user_id' => get_current_user_id(), 'details' => 'Backup codes regenerated.'], 'warning');
        BS2FA_Helpers::maybe_invalidate_other_sessions(get_current_user_id(), __('Backup codes regenerated.', 'brightery-secure-2fa'));
        BS2FA_Helpers::json_response(['success' => true, 'codes' => $codes]);
    }

    public function ajax_assertion_options() {
        $pending = BS2FA_Helpers::get_login_pending();
        if (!$pending) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => __('Login session expired.', 'brightery-secure-2fa')], 403);
        }

        if ($message = $this->challenge_is_blocked((int) $pending['user_id'])) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => $message], 429);
        }

        try {
            $options = BS2FA_WebAuthn::assertion_options((int) $pending['user_id']);
            BS2FA_Helpers::json_response(['success' => true, 'options' => $options]);
        } catch (Throwable $e) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => $e->getMessage()], 400);
        }
    }

    public function ajax_assertion_verify() {
        $pending = BS2FA_Helpers::get_login_pending();
        if (!$pending) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => __('Login session expired.', 'brightery-secure-2fa')], 403);
        }

        $user_id = (int) $pending['user_id'];
        if ($message = $this->challenge_is_blocked($user_id)) {
            BS2FA_Helpers::json_response(['success' => false, 'message' => $message], 429);
        }

        $payload = BS2FA_Helpers::json_input();

        try {
            BS2FA_WebAuthn::verify_assertion($user_id, $payload, $pending['token']);
            BS2FA_Rate_Limit::clear('challenge', BS2FA_Rate_Limit::challenge_identity($user_id));
            BS2FA_Logger::add('passkey_login_success', ['user_id' => $user_id, 'details' => 'Passkey assertion verified.']);
            $trust_device = !empty($payload['trustDevice']) && BS2FA_Trusted_Devices::enabled();
            $trust_device_label = !empty($payload['trustDeviceLabel']) ? sanitize_text_field((string) $payload['trustDeviceLabel']) : '';
            $this->complete_pending_login($user_id, $pending, true, $trust_device, $trust_device_label);
        } catch (Throwable $e) {
            $lock_until = BS2FA_Rate_Limit::note_failure('challenge', BS2FA_Rate_Limit::challenge_identity($user_id));

            if ($lock_until) {
                $details = sprintf(
                /* translators: 1: Formatted time, 2: Error message */
                        __('Passkey lockout until %1$s. %2$s', 'brightery-secure-2fa'),
                        BS2FA_Helpers::format_time($lock_until),
                        $e->getMessage()
                );
            } else {
                $details = $e->getMessage();
            }

            BS2FA_Logger::add('passkey_login_failed', ['user_id' => $user_id, 'details' => $details], 'warning');

            if ($lock_until) {
                /* translators: 1: User label, 2: Formatted time */
                $alert_msg = __('Second-factor lockout for %1$s until %2$s.', 'brightery-secure-2fa');
                $this->maybe_send_lockout_alert('challenge:user:' . $user_id, sprintf($alert_msg, BS2FA_Helpers::user_label($user_id), BS2FA_Helpers::format_time($lock_until)));
            }
            BS2FA_Helpers::json_response(['success' => false, 'message' => $e->getMessage()], 400);
        }
    }
}