<?php
/**
 * Magic Login — Switchboard-parity passwordless login (DB-backed links, registration, placeholders).
 *
 * @package MagicLogin
 */

namespace MagicLogin;

use WP_Error;
use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\ModuleManager;
use WP_PowerSuite\Core\OptionStoreModuleSettingsRepository;
use WP_PowerSuite\Email\EmailComponents;
use WP_PowerSuite\Email\EmailDesign;
use WP_PowerSuite\Email\EmailRenderer;
use WP_User;

require_once __DIR__ . '/includes/class-link-repository.php';

/**
 * Core module (options align with WP Switchboard Pro magic-login).
 */
class Module extends AbstractModule {

	private const MODULE_ID        = 'magic-login';
	private const MODULE_VERSION   = '2.1.1';
	private const DB_READY_OPTION  = 'wpps_ml_db_ready';
	private const CODE_SESSION_PREFIX = 'wpps_ml_cs_';
	private const FLASH_PREFIX        = 'wpps_ml_flash_';
	public const SHORTCODE         = 'wp_powersuite_magic_login';

	private const VERIFY_ACTION    = 'wpps_ml_verify';
	private const REGISTER_ACTION   = 'wpps_ml_register';
	private const CODE_ACTION      = 'wpps_ml_code';
	private const SCREEN_ACTION    = 'wpps_ml';

	private const AJAX_REQUEST     = 'wpps_request_magic_link';
	private const AJAX_VERIFY_CODE = 'wpps_verify_magic_code';

	private const NONCE_ACTION     = 'wpps_magic_link';
	private const NONCE_CODE       = 'wpps_magic_login_code';
	private const CAPTCHA_VERIFY_ACTION = 'wpps_magic_login';
	private const HONEYPOT_FIELD   = 'wpps_ml_company';

	/** Content-ID for inline QR image (Gmail and others block data: URIs in email). */
	private const MAIL_QR_CID = 'wpps_ml_qr';

	private const REG_PREFIX       = 'wpps_ml_reg_';
	private const CRON_HOOK        = 'wpps_magic_links_cleanup';

	/** @var array<string, mixed>|null */
	private static ?array $settings_cache = null;

	/** @var string|null PNG bytes for the next wp_mail PHPMailer inline QR attachment. */
	private static ?string $inline_mail_qr_png = null;

	private ?LinkRepository $repository = null;

	private function isLicenseActive(): bool {
		return function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid();
	}

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Magic Login', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Passwordless login with magic links on wp-login, a shortcode builder, QR codes and one-time codes, optional registration, and email placeholders.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'frontend', 'ajax', 'cron' ];
	}

	private function repo(): LinkRepository {
		if ( $this->repository === null ) {
			$this->repository = new LinkRepository();
		}
		return $this->repository;
	}

	/**
	 * @return array<string, mixed>
	 */
	public function getSettings(): array {
		if ( self::$settings_cache !== null ) {
			return self::$settings_cache;
		}

		$raw     = $this->moduleSettings()->all( [] );
		$raw_arr = is_array( $raw ) ? $raw : [];
		$merged  = wp_parse_args( $this->migrateLegacyMagicLoginSettings( $raw_arr ), self::getDefaultSettings() );

		if ( ! is_array( $merged['allowed_roles'] ) ) {
			$merged['allowed_roles'] = [];
		}
		$merged['error_messages'] = self::normalizeErrorMessages( $merged['error_messages'] ?? [] );

		self::$settings_cache = $merged;

		return self::$settings_cache;
	}

	/**
	 * Map v1 / admin legacy keys into Switchboard-style keys.
	 *
	 * @param array<string, mixed> $raw
	 * @return array<string, mixed>
	 */
	private function migrateLegacyMagicLoginSettings( array $raw ): array {
		$out = $raw;

		if ( isset( $raw['token_expiry_minutes'] ) && ! isset( $raw['expiry_minutes'] ) ) {
			$out['expiry_minutes'] = max( 5, min( 60, absint( $raw['token_expiry_minutes'] ) ) );
		}

		if ( isset( $raw['rate_limit_ip'] ) && ! isset( $raw['rate_ip_max'] ) ) {
			$out['rate_ip_max'] = max( 0, absint( $raw['rate_limit_ip'] ) );
		}
		if ( isset( $raw['rate_limit_email'] ) && ! isset( $raw['rate_email_max'] ) ) {
			$out['rate_email_max'] = max( 0, absint( $raw['rate_limit_email'] ) );
		}
		if ( isset( $raw['rate_window_minutes'] ) ) {
			$w = max( 1, absint( $raw['rate_window_minutes'] ) );
			if ( ! isset( $raw['rate_ip_window_minutes'] ) ) {
				$out['rate_ip_window_minutes'] = $w;
			}
			if ( ! isset( $raw['rate_email_window_minutes'] ) ) {
				$out['rate_email_window_minutes'] = $w;
			}
		}

		if ( isset( $raw['enable_login_code'] ) && ! isset( $raw['enable_code_login'] ) ) {
			$out['enable_code_login'] = ! empty( $raw['enable_login_code'] );
		}
		if ( isset( $raw['email_include_qr'] ) && ! isset( $raw['include_qr_code'] ) ) {
			$out['include_qr_code'] = ! empty( $raw['email_include_qr'] );
		}
		if ( isset( $raw['enable_login_stats'] ) && ! isset( $raw['show_users_column_stats'] ) ) {
			$out['show_users_column_stats'] = ! empty( $raw['enable_login_stats'] );
		}

		if ( isset( $raw['form_title'] ) && $raw['form_title'] !== '' ) {
			$out['sc_title'] = (string) $raw['form_title'];
		}
		if ( isset( $raw['form_description'] ) && $raw['form_description'] !== '' ) {
			$out['sc_description'] = (string) $raw['form_description'];
		}
		if ( isset( $raw['form_label'] ) && $raw['form_label'] !== '' ) {
			$out['sc_email_label'] = (string) $raw['form_label'];
		}
		if ( isset( $raw['button_text'] ) && $raw['button_text'] !== '' ) {
			$out['sc_button_text'] = (string) $raw['button_text'];
		}

		if ( ! isset( $raw['show_on_login'] ) && isset( $raw['login_form_button'] ) ) {
			$out['show_on_login'] = ! empty( $raw['login_form_button'] );
		}

		if ( isset( $raw['error_generic'] ) && trim( (string) $raw['error_generic'] ) !== '' ) {
			$msgs = isset( $out['error_messages'] ) && is_array( $out['error_messages'] ) ? $out['error_messages'] : [];
			if ( ! isset( $msgs['unknown'] ) || trim( (string) $msgs['unknown'] ) === '' ) {
				$msgs['unknown'] = sanitize_text_field( (string) $raw['error_generic'] );
				$out['error_messages'] = $msgs;
			}
		}

		return $out;
	}

	/**
	 * Default user-visible error strings keyed by internal code.
	 *
	 * @return array<string, string>
	 */
	public static function getDefaultErrorMessages(): array {
		return [
			'nonce'            => __( 'Security check failed. Please try again.', 'wp-powersuite' ),
			'invalid_email'    => __( 'Please enter a valid email address.', 'wp-powersuite' ),
			'captcha'          => __( 'CAPTCHA verification failed.', 'wp-powersuite' ),
			'rate_ip'          => __( 'Too many requests. Please try again in a few minutes.', 'wp-powersuite' ),
			'rate_email'       => __( 'A link was recently sent. Please check your inbox.', 'wp-powersuite' ),
			'send_failed'      => __( 'Unable to send. Please try again.', 'wp-powersuite' ),
			'unknown'          => __( 'Something went wrong. Please try again.', 'wp-powersuite' ),
			'code_invalid'     => __( 'Invalid code.', 'wp-powersuite' ),
			'code_rate'        => __( 'Too many code attempts.', 'wp-powersuite' ),
			'code_expired'     => __( 'Invalid or expired code.', 'wp-powersuite' ),
			'code_invalid_user' => __( 'Invalid user.', 'wp-powersuite' ),
			'code_role_denied' => __( 'This account is not allowed to use magic login.', 'wp-powersuite' ),
		];
	}

	/**
	 * Map internal/runtime error codes to canonical admin-editable keys.
	 *
	 * @return array<string, string>
	 */
	public static function getErrorMessageAliases(): array {
		return [
			'nonce_ajax'     => 'nonce',
			'code_nonce'     => 'nonce',
			'code_rate_ip'   => 'code_rate',
			'code_rate_user' => 'code_rate',
			'honeypot'       => 'unknown',
		];
	}

	/**
	 * Resolve a saved custom message, including legacy keys merged into canonical fields.
	 *
	 * @param array<string, string> $saved
	 * @param array<string, string> $legacy_keys
	 */
	public static function getSavedErrorMessage( array $saved, string $key, array $legacy_keys = [] ): string {
		if ( isset( $saved[ $key ] ) && trim( (string) $saved[ $key ] ) !== '' ) {
			return (string) $saved[ $key ];
		}
		foreach ( $legacy_keys as $legacy_key ) {
			if ( isset( $saved[ $legacy_key ] ) && trim( (string) $saved[ $legacy_key ] ) !== '' ) {
				return (string) $saved[ $legacy_key ];
			}
		}
		return '';
	}

	/**
	 * @param mixed $raw
	 * @return array<string, string>
	 */
	public static function normalizeErrorMessages( $raw ): array {
		$defaults = self::getDefaultErrorMessages();
		if ( ! is_array( $raw ) ) {
			return [];
		}

		$legacy_nonce = [ 'nonce_ajax', 'code_nonce' ];
		foreach ( $legacy_nonce as $legacy_key ) {
			if ( ( ! isset( $raw['nonce'] ) || trim( (string) $raw['nonce'] ) === '' ) && isset( $raw[ $legacy_key ] ) && trim( (string) $raw[ $legacy_key ] ) !== '' ) {
				$raw['nonce'] = $raw[ $legacy_key ];
			}
		}

		$legacy_rate = [ 'code_rate_ip', 'code_rate_user' ];
		foreach ( $legacy_rate as $legacy_key ) {
			if ( ( ! isset( $raw['code_rate'] ) || trim( (string) $raw['code_rate'] ) === '' ) && isset( $raw[ $legacy_key ] ) && trim( (string) $raw[ $legacy_key ] ) !== '' ) {
				$raw['code_rate'] = $raw[ $legacy_key ];
			}
		}

		$clean = [];
		foreach ( $defaults as $key => $default ) {
			if ( ! array_key_exists( $key, $raw ) ) {
				continue;
			}
			$val = trim( sanitize_text_field( (string) $raw[ $key ] ) );
			if ( $val !== '' ) {
				$clean[ $key ] = $val;
			}
		}
		return $clean;
	}

	private function resolveErrorMessageKey( string $code ): string {
		$code    = sanitize_key( $code );
		$aliases = self::getErrorMessageAliases();
		return $aliases[ $code ] ?? $code;
	}

	private function errorMessage( string $code, ?string $default = null ): string {
		$code     = $this->resolveErrorMessageKey( $code );
		$defaults = self::getDefaultErrorMessages();
		$custom   = $this->getSettings()['error_messages'] ?? [];
		if ( is_array( $custom ) && isset( $custom[ $code ] ) && trim( (string) $custom[ $code ] ) !== '' ) {
			return (string) $custom[ $code ];
		}
		if ( $default !== null && $default !== '' ) {
			return $default;
		}
		if ( isset( $defaults[ $code ] ) ) {
			return $defaults[ $code ];
		}
		if ( is_array( $custom ) && isset( $custom['unknown'] ) && trim( (string) $custom['unknown'] ) !== '' ) {
			return (string) $custom['unknown'];
		}
		return $defaults['unknown'];
	}

	/**
	 * @return array<string, mixed>
	 */
	public static function getDefaultSettings(): array {
		return [
			'show_on_login'              => true,
			'expiry_minutes'             => 15,
			'allowed_roles'              => [],
			'include_qr_code'            => true,
			'enable_code_login'          => true,
			'enable_auto_login'          => false,
			'enable_registration'       => false,
			'registration_default_role' => 'subscriber',
			'sc_mode'                   => 'auto',
			'sc_title'                  => __( 'Sign in with email', 'wp-powersuite' ),
			'sc_description'            => __( 'Enter your email and we will send you a link to sign in instantly.', 'wp-powersuite' ),
			'sc_button_text'            => __( 'Send Login Link', 'wp-powersuite' ),
			'sc_placeholder'            => __( 'you@example.com', 'wp-powersuite' ),
			'sc_show_password_link'     => true,
			'style_bg_color'            => '#ffffff',
			'style_border_color'        => '#e2e8f0',
			'style_border_radius'       => 12,
			'style_title_color'         => '#1e293b',
			'style_desc_color'          => '#64748b',
			'style_input_border_color'  => '#d1d5db',
			'style_input_border_radius' => 8,
			'style_button_bg'           => '#3b82f6',
			'style_button_text_color'   => '#ffffff',
			'style_button_radius'       => 8,
			'shortcode_css'             => '',
			'rate_ip_max'               => 5,
			'rate_ip_window_minutes'    => 5,
			'rate_email_max'            => 3,
			'rate_email_window_minutes' => 2,
			'captcha_provider'          => 'none',
			'enable_honeypot'           => true,
			'bypass_2fa'                => false,
			'enable_ajax'               => true,
			'woo_login_form'            => false,
			'show_users_column_stats'   => true,
			'code_rate_max'             => 10,
			'code_rate_max_user'        => 5,
			'login_code_digits'         => 8,
			'error_messages'            => [],
			'sc_email_label'            => __( 'Email address', 'wp-powersuite' ),
		];
	}

	private ?string $last_code_login_url = null;

	protected function registerHooks(): void {
		if ( ! $this->isLicenseActive() ) {
			wp_clear_scheduled_hook( self::CRON_HOOK );
			return;
		}

		add_action( self::CRON_HOOK, [ $this, 'runCleanup' ] );

		if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
			return;
		}

		add_filter( 'wp_mail', [ $this, 'filterWpMailPlaceholders' ], 10, 1 );
		add_action( 'phpmailer_init', [ self::class, 'maybeEmbedMagicLoginQrImage' ], 10, 1 );

		add_action( 'init', [ $this, 'registerShortcodes' ], 20 );
		add_action( 'init', [ $this, 'maybeHandleFrontendPost' ], 12 );

		// Must register before wp-login.php validates custom actions (before login_init).
		add_action( 'login_form_' . self::CODE_ACTION, [ $this, 'handleCodeFormPost' ], 5 );
		add_action( 'login_form_' . self::CODE_ACTION, [ $this, 'renderCodeEntryScreen' ] );
		add_action( 'login_form_' . self::SCREEN_ACTION, [ $this, 'renderFullMagicScreen' ] );
		add_action( 'login_enqueue_scripts', [ $this, 'maybeEnqueueLoginPageAssets' ] );
		add_filter( 'login_body_class', [ $this, 'filterLoginBodyClass' ], 10, 1 );
		add_action( 'login_form', [ $this, 'renderLoginFormMagicOption' ], 999 );
		add_action( 'login_footer', [ $this, 'finalizeLoginFormMagicOption' ], 5 );

		add_action( 'login_init', [ $this, 'bootLoginContext' ], 1 );

		add_action( 'wp_ajax_' . self::AJAX_REQUEST, [ $this, 'ajaxRequestMagicLink' ] );
		add_action( 'wp_ajax_nopriv_' . self::AJAX_REQUEST, [ $this, 'ajaxRequestMagicLink' ] );
		add_action( 'wp_ajax_' . self::AJAX_VERIFY_CODE, [ $this, 'ajaxVerifyMagicCode' ] );
		add_action( 'wp_ajax_nopriv_' . self::AJAX_VERIFY_CODE, [ $this, 'ajaxVerifyMagicCode' ] );

		$s = $this->getSettings();
		if ( ! empty( $s['woo_login_form'] ) && class_exists( 'WooCommerce' ) ) {
			add_action( 'woocommerce_login_form_end', [ $this, 'maybeRenderWooCommerceForm' ], 20 );
		}

		if ( is_admin() ) {
			add_action( 'admin_menu', [ $this, 'addAdminMenu' ], 20 );
			add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAdminAssets' ] );
			add_action( 'admin_init', [ $this, 'maybeInstallTable' ] );
			add_action( 'load-powersuite_page_powersuite-magic-login', [ $this, 'registerActivityScreenOptions' ] );
			add_action( 'wp_powersuite_module_settings_saved', [ $this, 'bustSettingsCacheAfterSave' ], 10, 2 );
			if ( ! empty( $s['show_users_column_stats'] ) ) {
				add_action( 'load-users.php', [ $this, 'registerUsersListHooks' ] );
			}
		}
	}

	/**
	 * Dedicated settings submenu (Code Snippets / Temporary Login pattern).
	 */
	public function addAdminMenu(): void {
		add_submenu_page(
			'powersuite',
			__( 'Magic Login', 'wp-powersuite' ),
			__( 'Magic Login', 'wp-powersuite' ),
			$this->adminCapability(),
			'powersuite-magic-login',
			[ $this, 'renderAdminPage' ]
		);
	}

	/**
	 * Per-page screen option for the activity list table.
	 */
	public function registerActivityScreenOptions(): void {
		add_screen_option(
			'per_page',
			[
				'label'   => __( 'Activity rows per page', 'wp-powersuite' ),
				'default' => 20,
				'option'  => 'wpps_ml_activity_per_page',
			]
		);
		add_filter(
			'set_screen_option_wpps_ml_activity_per_page',
			static function ( $screen_option, $option, $value ) {
				unset( $screen_option, $option );
				return max( 1, min( 100, (int) $value ) );
			},
			10,
			3
		);
	}

	public function enqueueAdminAssets( string $hook_suffix ): void {
		$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['page'] ) ) : '';
		if ( $page !== 'powersuite-magic-login' ) {
			return;
		}
		$base        = plugin_dir_url( __FILE__ );
		$plugin_base = defined( 'WP_POWERSUITE_PLUGIN_URL' ) ? WP_POWERSUITE_PLUGIN_URL : '';
		if ( $plugin_base !== '' ) {
			wp_enqueue_style(
				'wpps-cs-admin',
				$plugin_base . 'modules/code-snippet/assets/cs-admin.css',
				[],
				self::MODULE_VERSION
			);
		}
		$style_deps = $plugin_base !== '' ? [ 'wpps-cs-admin' ] : [];
		wp_enqueue_style( 'wpps-ml-admin', $base . 'assets/ml-admin.css', $style_deps, self::MODULE_VERSION );

		$plugin_dir = defined( 'WP_POWERSUITE_PLUGIN_DIR' ) ? WP_POWERSUITE_PLUGIN_DIR : '';
		$color_js   = $plugin_dir . 'src/Admin/Assets/color-panel.js';
		if ( $plugin_dir !== '' && is_readable( $color_js ) ) {
			wp_enqueue_script(
				'wp-powersuite-color-panel',
				$plugin_base . 'src/Admin/Assets/color-panel.js',
				[],
				defined( 'WP_POWERSUITE_VERSION' ) ? WP_POWERSUITE_VERSION : self::MODULE_VERSION,
				true
			);
		}

		wp_enqueue_script( 'wpps-ml-admin', $base . 'assets/ml-admin.js', [], self::MODULE_VERSION, true );
		if ( ! function_exists( 'wpps_enqueue_accordion_tooltip_script' ) ) {
			$module_settings_ui = defined( 'WP_POWERSUITE_SRC_DIR' )
				? WP_POWERSUITE_SRC_DIR . 'Admin' . DIRECTORY_SEPARATOR . 'ModuleSettingsUi.php'
				: '';
			if ( $module_settings_ui !== '' && is_readable( $module_settings_ui ) ) {
				require_once $module_settings_ui;
			}
		}
		if ( function_exists( 'wpps_enqueue_accordion_tooltip_script' ) ) {
			wpps_enqueue_accordion_tooltip_script();
		}
		wp_localize_script(
			'wpps-ml-admin',
			'wppsMagicLoginAdmin',
			[
				'ajaxUrl'      => admin_url( 'admin-ajax.php' ),
				'nonce'        => wp_create_nonce( 'wp_powersuite_ajax' ),
				'moduleId'     => self::MODULE_ID,
				'saveAction'   => 'wp_powersuite_save_module_settings',
				'savedMessage' => __( 'Settings saved.', 'wp-powersuite' ),
				'saveFailed'   => __( 'Could not save settings.', 'wp-powersuite' ),
				'saving'       => __( 'Saving…', 'wp-powersuite' ),
			]
		);
	}

	/**
	 * Render Magic Login settings admin page.
	 */
	public function renderAdminPage(): void {
		if ( ! $this->canManagePlugin() ) {
			wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wp-powersuite' ) );
		}

		$module_settings_ui = defined( 'WP_POWERSUITE_SRC_DIR' )
			? WP_POWERSUITE_SRC_DIR . 'Admin' . DIRECTORY_SEPARATOR . 'ModuleSettingsUi.php'
			: '';
		if ( $module_settings_ui !== '' && is_readable( $module_settings_ui ) ) {
			require_once $module_settings_ui;
		}

		$this->maybeInstallTable();

		$module_id         = self::MODULE_ID;
		$module_enabled    = OptionStoreModuleSettingsRepository::isModuleEnabled( self::MODULE_ID );
		$powersuite_url    = admin_url( 'admin.php?page=powersuite' );

		include __DIR__ . '/admin.php';
	}

	/**
	 * wp-login.php bootstrap (runs once per login request at login_init priority 1).
	 */
	public function bootLoginContext(): void {
		static $booted = false;
		if ( $booted ) {
			return;
		}
		$booted = true;

		$this->maybeInstallTable();

		// Run in hook order — cannot register nested login_init callbacks here (too late for this request).
		$this->handleLoginPageFlows();
		$this->maybeHandleFullScreenPost();
	}

	/**
	 * Default wp-login.php sign-in screen only (not register, reset, or magic flows).
	 */
	private function isDefaultLoginScreen(): bool {
		global $pagenow;
		if ( ! isset( $pagenow ) || 'wp-login.php' !== $pagenow ) {
			return (bool) apply_filters( 'wp_powersuite_magic_login_is_default_login_screen', false );
		}

		$action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( (string) $_REQUEST['action'] ) ) : 'login';

		return ( '' === $action || 'login' === $action )
			|| (bool) apply_filters( 'wp_powersuite_magic_login_is_default_login_screen', false );
	}

	/**
	 * @param string[] $classes
	 * @return string[]
	 */
	public function filterLoginBodyClass( array $classes ): array {
		if ( ! empty( $this->getSettings()['show_on_login'] ) && $this->isDefaultLoginScreen() ) {
			$classes[] = 'wpps-magic-login-active';
		}

		return $classes;
	}

	/**
	 * Enqueue wp-login assets for the magic-link UI on the default login screen.
	 */
	public function maybeEnqueueLoginPageAssets(): void {
		if ( ! $this->isDefaultLoginScreen() || empty( $this->getSettings()['show_on_login'] ) ) {
			return;
		}
		$this->enqueueLoginPageAssets();
	}

	public function registerUsersListHooks(): void {
		add_filter( 'manage_users_columns', [ $this, 'filterUsersColumns' ] );
		add_filter( 'manage_users_custom_column', [ $this, 'renderUsersColumn' ], 10, 3 );
	}

	/**
	 * Clear static settings cache when options are saved from the PowerSuite modal.
	 *
	 * @param array<string, mixed> $settings Saved settings (unused).
	 */
	public function bustSettingsCacheAfterSave( string $module_id, array $settings ): void {
		unset( $settings );
		if ( $module_id === self::MODULE_ID ) {
			self::$settings_cache = null;
		}
	}

	public function maybeInstallTable(): void {
		if ( get_option( self::DB_READY_OPTION ) ) {
			return;
		}
		$this->repo()->install();
		update_option( self::DB_READY_OPTION, '1', false );
	}

	public function runCleanup(): void {
		$this->repo()->cleanupExpired();
	}

	public function registerShortcodes(): void {
		add_shortcode( self::SHORTCODE, [ $this, 'renderShortcode' ] );
	}

	/**
	 * wp-login.php: verify token, complete registration, etc.
	 */
	public function handleLoginPageFlows(): void {
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return;
		}
		global $pagenow;
		if ( 'wp-login.php' !== $pagenow ) {
			return;
		}
		$action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : 'login';
		if ( self::VERIFY_ACTION === $action ) {
			$this->consumeVerifyLink();
		} elseif ( self::REGISTER_ACTION === $action ) {
			$this->consumeRegistrationLink();
		}
	}

	private function consumeVerifyLink(): void {
		$this->sendSensitiveLoginHeaders();
		$token = isset( $_GET['token'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['token'] ) ) : '';
		$uid   = isset( $_GET['uid'] ) ? absint( $_GET['uid'] ) : 0;
		$redir = '';
		if ( isset( $_GET['redirect_to'] ) && is_string( $_GET['redirect_to'] ) ) {
			$redir = wp_validate_redirect( esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ), '' );
		}
		if ( strlen( $token ) !== 64 || ! ctype_xdigit( $token ) || $uid < 1 ) {
			wp_die( esc_html__( 'Invalid login link.', 'wp-powersuite' ), esc_html__( 'Error', 'wp-powersuite' ), [ 'response' => 400 ] );
		}
		$user = get_user_by( 'ID', $uid );
		if ( ! $user instanceof WP_User ) {
			wp_die( esc_html__( 'Invalid login link.', 'wp-powersuite' ), esc_html__( 'Error', 'wp-powersuite' ), [ 'response' => 400 ] );
		}
		$row = $this->repo()->findByTokenAndUser( hash( 'sha256', $token ), $uid );
		if ( ! $row ) {
			wp_die( esc_html__( 'This link has expired or was already used.', 'wp-powersuite' ), esc_html__( 'Login link', 'wp-powersuite' ), [ 'response' => 410 ] );
		}
		if ( ! $this->userMayUseMagicLogin( $user ) ) {
			wp_die( esc_html__( 'You are not allowed to use magic login.', 'wp-powersuite' ), esc_html__( 'Error', 'wp-powersuite' ), [ 'response' => 403 ] );
		}
		if ( is_multisite() && function_exists( 'is_user_member_of_blog' ) && ! is_user_member_of_blog( $uid, get_current_blog_id() ) ) {
			wp_die( esc_html__( 'This account is not a member of this site.', 'wp-powersuite' ), esc_html__( 'Error', 'wp-powersuite' ), [ 'response' => 403 ] );
		}
		if ( ! $this->repo()->markUsed( (int) $row->id ) ) {
			wp_die( esc_html__( 'This link has expired or was already used.', 'wp-powersuite' ), esc_html__( 'Login link', 'wp-powersuite' ), [ 'response' => 410 ] );
		}
		$this->completeLoginSession( $user, $redir );
	}

	private function consumeRegistrationLink(): void {
		$this->sendSensitiveLoginHeaders();
		$token = isset( $_GET['token'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['token'] ) ) : '';
		$redir = '';
		if ( isset( $_GET['redirect_to'] ) && is_string( $_GET['redirect_to'] ) ) {
			$redir = wp_validate_redirect( esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ), '' );
		}
		if ( strlen( $token ) !== 64 || ! ctype_xdigit( $token ) ) {
			wp_die( esc_html__( 'Invalid registration link.', 'wp-powersuite' ), esc_html__( 'Registration', 'wp-powersuite' ), [ 'response' => 400 ] );
		}
		$th  = hash( 'sha256', $token );
		$key = self::REG_PREFIX . $th;
		$data = get_transient( $key );
		if ( ! is_array( $data ) || empty( $data['email'] ) || ! is_email( $data['email'] ) ) {
			wp_die( esc_html__( 'This registration link has expired or was already used.', 'wp-powersuite' ), esc_html__( 'Registration', 'wp-powersuite' ), [ 'response' => 410 ] );
		}
		$email = strtolower( (string) $data['email'] );
		delete_transient( $key );
		if ( get_user_by( 'email', $email ) ) {
			wp_die( esc_html__( 'An account with this email already exists.', 'wp-powersuite' ), esc_html__( 'Registration', 'wp-powersuite' ), [ 'response' => 409 ] );
		}
		$s    = $this->getSettings();
		$role = isset( $s['registration_default_role'] ) ? sanitize_key( (string) $s['registration_default_role'] ) : 'subscriber';
		if ( ! wp_roles()->is_role( $role ) ) {
			$role = 'subscriber';
		}
		$local = sanitize_user( current( explode( '@', $email, 2 ) ), true );
		$base  = $local;
		$i     = 1;
		while ( username_exists( $local ) ) {
			$local = $base . $i;
			++$i;
		}
		$pass = wp_generate_password( 24 );
		$uid  = wp_create_user( $local, $pass, $email );
		if ( is_wp_error( $uid ) ) {
			wp_die( esc_html( $uid->get_error_message() ), esc_html__( 'Registration failed', 'wp-powersuite' ), [ 'response' => 500 ] );
		}
		$user = new WP_User( $uid );
		$user->set_role( $role );
		do_action( 'wp_powersuite_magic_login_user_registered', $uid, $email );
		$this->completeLoginSession( $user, $redir !== '' ? $redir : ( is_string( $data['redirect_to'] ?? '' ) ? wp_validate_redirect( esc_url_raw( $data['redirect_to'] ), home_url( '/' ) ) : home_url( '/' ) ) );
	}

	public function renderFullMagicScreen(): void {
		if ( is_user_logged_in() ) {
			wp_safe_redirect( admin_url() );
			exit;
		}
		nocache_headers();
		$this->enqueueShortcodeAssets();
		login_header( __( 'Log in', 'wp-powersuite' ), '' );
		echo $this->renderShortcode( [ 'context' => 'wp_login_full' ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		login_footer();
		exit;
	}

	public function maybeHandleFullScreenPost(): void {
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return;
		}
		if ( strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) ) ) !== 'POST' ) {
			return;
		}
		global $pagenow;
		if ( 'wp-login.php' !== $pagenow ) {
			return;
		}
		$action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : '';
		if ( self::SCREEN_ACTION !== $action ) {
			return;
		}
		if ( ! isset( $_POST['wpps_ml_request'], $_POST['_wpnonce'] ) || sanitize_key( wp_unslash( $_POST['wpps_ml_request'] ) ) !== '1' ) {
			return;
		}
		$base = add_query_arg( 'action', self::SCREEN_ACTION, wp_login_url() );
		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), self::NONCE_ACTION ) ) {
			wp_safe_redirect( add_query_arg( 'wpps_ml_err', 'nonce', $base ) );
			exit;
		}
		$email = isset( $_POST['wpps_ml_email'] ) ? sanitize_email( wp_unslash( (string) $_POST['wpps_ml_email'] ) ) : '';
		$redirect_raw = $_POST['wpps_ml_redirect_to'] ?? ( $_POST['redirect_to'] ?? '' );
		$redir = is_string( $redirect_raw ) ? wp_validate_redirect( esc_url_raw( wp_unslash( $redirect_raw ) ), '' ) : '';
		$mode  = isset( $_POST['wpps_ml_mode'] )
			? sanitize_key( wp_unslash( (string) $_POST['wpps_ml_mode'] ) )
			: ( isset( $_POST['mode'] ) ? sanitize_key( wp_unslash( (string) $_POST['mode'] ) ) : 'login' );
		$res   = $this->requestMagicLinkCore( $email, $redir, $mode );
		if ( ! empty( $res['error'] ) ) {
			wp_safe_redirect( add_query_arg( 'wpps_ml_err', sanitize_key( (string) $res['error'] ), $base ) );
			exit;
		}
		$d = $res['data'] ?? [];
		$code_url = isset( $d['code_url'] ) ? (string) $d['code_url'] : '';
		$ok_query = array_merge(
			[ 'action' => self::SCREEN_ACTION ],
			$this->successRedirectQueryArgs( $code_url !== '' ? $code_url : null )
		);
		wp_safe_redirect( add_query_arg( $ok_query, wp_login_url() ) );
		exit;
	}

	public function maybeHandleFrontendPost(): void {
		if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
			return;
		}
		global $pagenow;
		if ( 'wp-login.php' === $pagenow ) {
			return;
		}
		if ( strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) ) ) !== 'POST' ) {
			return;
		}
		if ( ! isset( $_POST['wpps_ml_request'] ) || sanitize_key( wp_unslash( (string) $_POST['wpps_ml_request'] ) ) !== '1' ) {
			return;
		}
		$base = wp_get_referer();
		if ( ! is_string( $base ) || $base === '' ) {
			$base = home_url( '/' );
		}
		$base = remove_query_arg( [ 'wpps_ml_err', 'wpps_ml_ok', 'wpps_ml_cs', 'wpps_ml_flash' ], $base );
		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), self::NONCE_ACTION ) ) {
			wp_safe_redirect( add_query_arg( 'wpps_ml_err', 'nonce', $base ) );
			exit;
		}
		$email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( (string) $_POST['email'] ) ) : '';
		$redir = isset( $_POST['redirect_to'] ) ? wp_validate_redirect( esc_url_raw( wp_unslash( (string) $_POST['redirect_to'] ) ), '' ) : '';
		$mode  = isset( $_POST['mode'] ) ? sanitize_key( wp_unslash( (string) $_POST['mode'] ) ) : 'login';
		$res   = $this->requestMagicLinkCore( $email, $redir, $mode );
		if ( ! empty( $res['error'] ) ) {
			wp_safe_redirect( add_query_arg( 'wpps_ml_err', sanitize_key( (string) $res['error'] ), $base ) );
			exit;
		}
		$d = $res['data'] ?? [];
		$code_url = isset( $d['code_url'] ) ? (string) $d['code_url'] : '';
		$ok_args  = $this->successRedirectQueryArgs( $code_url !== '' ? $code_url : null );
		wp_safe_redirect( add_query_arg( $ok_args, $base ) );
		exit;
	}

	private function clientIp(): string {
		if ( class_exists( 'IPWhitelistBlacklist\\Module' ) ) {
			$ip = \IPWhitelistBlacklist\Module::getResolvedClientIp();
			if ( $ip !== '0.0.0.0' ) {
				return $ip;
			}
		}

		if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
			$ip = sanitize_text_field( wp_unslash( (string) $_SERVER['HTTP_CF_CONNECTING_IP'] ) );
			if ( filter_var( $ip, FILTER_VALIDATE_IP ) ) {
				return $ip;
			}
		}

		$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( (string) $_SERVER['REMOTE_ADDR'] ) ) : '';
		return ( $ip !== '' && filter_var( $ip, FILTER_VALIDATE_IP ) ) ? $ip : '0.0.0.0';
	}

	/**
	 * @return array{uid:int, redirect:string}|null
	 */
	private function resolveCodeSession( string $cs ): ?array {
		if ( strlen( $cs ) !== 32 || ! ctype_xdigit( $cs ) ) {
			return null;
		}
		$data = get_transient( self::CODE_SESSION_PREFIX . hash( 'sha256', $cs ) );
		if ( ! is_array( $data ) || empty( $data['uid'] ) ) {
			return null;
		}
		$uid = absint( $data['uid'] );
		if ( $uid < 1 ) {
			return null;
		}
		$redirect = isset( $data['redirect'] ) && is_string( $data['redirect'] )
			? wp_validate_redirect( esc_url_raw( $data['redirect'] ), '' )
			: '';

		return [ 'uid' => $uid, 'redirect' => $redirect ];
	}

	private function createCodeSession( int $user_id, string $redirect_to ): string {
		$cs  = bin2hex( random_bytes( 16 ) );
		$key = self::CODE_SESSION_PREFIX . hash( 'sha256', $cs );
		set_transient(
			$key,
			[
				'uid'      => $user_id,
				'redirect' => $redirect_to,
			],
			$this->expiryMinutes() * MINUTE_IN_SECONDS
		);

		return $cs;
	}

	private function maybeBeginTwoFactor( WP_User $user, string $redirect ): ?string {
		if (
			! empty( $this->getSettings()['bypass_2fa'] )
			|| apply_filters( 'wp_powersuite_magic_login_bypass_2fa', false, $user )
		) {
			return null;
		}
		if ( ! \WP_PowerSuite\Core\OptionStoreModuleSettingsRepository::isModuleEnabled( 'two-factor-authentication' ) ) {
			return null;
		}
		$mgr = ModuleManager::getInstance();
		if ( ! $mgr->isModuleLoaded( 'two-factor-authentication' ) ) {
			try {
				$mgr->loadModule( 'two-factor-authentication' );
			} catch ( \Throwable $e ) {
				unset( $e );
				return null;
			}
		}
		$mod = $mgr->getModule( 'two-factor-authentication' );
		if ( ! $mod instanceof \TwoFactorAuthentication\Module ) {
			return null;
		}

		return $mod->beginPendingLoginIfRequired( $user, $redirect );
	}

	private function checkRateCodeForUser( int $user_id ): bool {
		$max = max( 1, absint( $this->getSettings()['code_rate_max_user'] ?? 5 ) );
		$key = 'wpps_magic_code_user_' . $user_id;
		$n   = (int) get_transient( $key );
		if ( $n >= $max ) {
			return false;
		}
		set_transient( $key, $n + 1, 15 * MINUTE_IN_SECONDS );

		return true;
	}

	private function userAgent(): string {
		return isset( $_SERVER['HTTP_USER_AGENT'] ) ? substr( sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ), 0, 255 ) : 'Unknown';
	}

	private function expiryMinutes(): int {
		$s = $this->getSettings();
		return max( 5, min( 60, absint( $s['expiry_minutes'] ?? 15 ) ) );
	}

	private function userMayUseMagicLogin( WP_User $user ): bool {
		if ( (int) $user->ID < 1 || ( function_exists( 'is_user_spammy' ) && is_user_spammy( $user ) ) ) {
			return false;
		}
		if ( isset( $user->user_status ) && (string) $user->user_status !== '0' ) {
			return false;
		}
		if ( empty( $user->user_email ) || ! is_email( $user->user_email ) ) {
			return false;
		}
		if ( class_exists( 'WooCommerce' ) && function_exists( 'wc_get_customer' ) ) {
			$customer = wc_get_customer( (int) $user->ID );
			if ( $customer && method_exists( $customer, 'get_is_paying_customer' ) && method_exists( $customer, 'get_id' ) ) {
				$blocked = get_user_meta( (int) $user->ID, 'wc_blocked', true );
				if ( $blocked === 'yes' || $blocked === '1' || $blocked === 1 ) {
					return false;
				}
			}
		}
		$s             = $this->getSettings();
		$allowed_roles = isset( $s['allowed_roles'] ) && is_array( $s['allowed_roles'] ) ? array_values( array_filter( array_map( 'sanitize_key', $s['allowed_roles'] ) ) ) : [];
		$allowed_roles = apply_filters( 'wp_powersuite_magic_login_allowed_roles', $allowed_roles, $user );
		if ( $allowed_roles !== [] ) {
			$ok = false;
			foreach ( $user->roles as $role ) {
				if ( in_array( (string) $role, $allowed_roles, true ) ) {
					$ok = true;
					break;
				}
			}
			if ( ! $ok ) {
				return false;
			}
		}
		return (bool) apply_filters( 'wp_powersuite_magic_login_allow_user', true, $user );
	}

	private function completeLoginSession( WP_User $user, string $redirect ): void {
		$two_fa_url = $this->maybeBeginTwoFactor( $user, $redirect );
		if ( is_string( $two_fa_url ) && $two_fa_url !== '' ) {
			wp_safe_redirect( $two_fa_url );
			exit;
		}

		wp_clear_auth_cookie();
		$remember = (bool) apply_filters( 'wp_powersuite_magic_login_remember_user', false, $user );
		wp_set_current_user( (int) $user->ID );
		wp_set_auth_cookie( (int) $user->ID, $remember, is_ssl() );
		do_action( 'wp_powersuite_magic_login_authenticated', $user );
		do_action( 'wp_login', $user->user_login, $user );
		$destination = $redirect !== '' ? $redirect : $this->resolveRedirectDestination( $user );
		$destination = apply_filters( 'login_redirect', $destination, $redirect, $user );
		$destination = apply_filters( 'wp_powersuite_magic_login_redirect', $destination, $user );
		wp_safe_redirect( wp_validate_redirect( $destination, home_url( '/' ) ) );
		exit;
	}

	private function resolveRedirectDestination( WP_User $user ): string {
		if ( class_exists( 'WooCommerce' ) && function_exists( 'wc_get_page_permalink' ) ) {
			$m = wc_get_page_permalink( 'myaccount' );
			if ( is_string( $m ) && $m !== '' ) {
				return $m;
			}
		}
		return user_can( $user, 'read' ) ? admin_url() : home_url( '/' );
	}

	private function checkRateIp(): bool {
		return ! $this->isRateIpLimited();
	}

	private function isRateIpLimited(): bool {
		$s   = $this->getSettings();
		$max = absint( $s['rate_ip_max'] ?? 5 );
		if ( $max === 0 ) {
			return false;
		}
		$max = max( 1, $max );
		$key = 'wpps_magic_rate_' . md5( $this->clientIp() );
		$n   = (int) get_transient( $key );

		return $n >= apply_filters( 'wp_powersuite_magic_login_max_ip_requests', $max, $this->clientIp() );
	}

	private function incrementRateIp(): void {
		$s   = $this->getSettings();
		$max = absint( $s['rate_ip_max'] ?? 5 );
		if ( $max === 0 ) {
			return;
		}
		$win_min = max( 1, absint( $s['rate_ip_window_minutes'] ?? 5 ) );
		$key     = 'wpps_magic_rate_' . md5( $this->clientIp() );
		$n       = (int) get_transient( $key );
		set_transient( $key, $n + 1, $win_min * MINUTE_IN_SECONDS );
	}

	private function checkRateEmail( string $email ): bool {
		return ! $this->isRateEmailLimited( $email );
	}

	private function isRateEmailLimited( string $email ): bool {
		$s   = $this->getSettings();
		$max = absint( $s['rate_email_max'] ?? 3 );
		if ( $max === 0 ) {
			return false;
		}
		$max = max( 1, $max );
		$key = 'wpps_magic_email_' . md5( strtolower( $email ) );
		$n   = (int) get_transient( $key );

		return $n >= apply_filters( 'wp_powersuite_magic_login_max_email_requests', $max, $email );
	}

	private function incrementRateEmail( string $email ): void {
		$s   = $this->getSettings();
		$max = absint( $s['rate_email_max'] ?? 3 );
		if ( $max === 0 ) {
			return;
		}
		$win_min = max( 1, absint( $s['rate_email_window_minutes'] ?? 2 ) );
		$key     = 'wpps_magic_email_' . md5( strtolower( $email ) );
		$n       = (int) get_transient( $key );
		set_transient( $key, $n + 1, $win_min * MINUTE_IN_SECONDS );
	}

	/**
	 * Enumeration-safe success payload for magic-link requests.
	 *
	 * @return array{data: array<string, mixed>}
	 */
	private function genericSuccessResponse( ?string $code_url = null ): array {
		$data = [
			'message'  => __( 'If an account exists with this email, a magic link has been sent.', 'wp-powersuite' ),
			'type'     => 'generic',
			'title'    => __( 'Check your email', 'wp-powersuite' ),
			'subtitle' => __( 'If an account exists, we sent you a link.', 'wp-powersuite' ),
		];
		if ( is_string( $code_url ) && $code_url !== '' ) {
			$data['code_url'] = $code_url;
			$data['subtitle'] = __( 'If an account exists, we sent you a link. You can also enter the one-time code from your email.', 'wp-powersuite' );
		}

		return [ 'data' => $data ];
	}

	/**
	 * @return array{wpps_ml_ok: string, wpps_ml_flash?: string}
	 */
	private function successRedirectQueryArgs( ?string $code_url = null ): array {
		$args = [ 'wpps_ml_ok' => 'generic' ];
		if ( is_string( $code_url ) && $code_url !== '' ) {
			$args['wpps_ml_flash'] = $this->createSuccessFlash( $code_url );
		}

		return $args;
	}

	private function createSuccessFlash( string $code_url ): string {
		$flash = bin2hex( random_bytes( 16 ) );
		set_transient(
			self::FLASH_PREFIX . hash( 'sha256', $flash ),
			[ 'code_url' => $code_url ],
			10 * MINUTE_IN_SECONDS
		);

		return $flash;
	}

	private function consumeSuccessFlash( string $flash ): ?string {
		if ( strlen( $flash ) !== 32 || ! ctype_xdigit( $flash ) ) {
			return null;
		}
		$key  = self::FLASH_PREFIX . hash( 'sha256', $flash );
		$data = get_transient( $key );
		delete_transient( $key );
		if ( ! is_array( $data ) || empty( $data['code_url'] ) || ! is_string( $data['code_url'] ) ) {
			return null;
		}

		return $data['code_url'];
	}

	private function sendSensitiveLoginHeaders(): void {
		nocache_headers();
		if ( ! headers_sent() ) {
			header( 'Referrer-Policy: no-referrer', true );
		}
	}

	/**
	 * Shared magic-link request logic (AJAX and full-screen POST).
	 *
	 * @return array{error?:string, data?:array<string, mixed>}
	 */
	private function requestMagicLinkCore( string $email, string $redirect_to, string $mode ): array {
		if ( ! is_email( $email ) ) {
			return [ 'error' => 'invalid_email' ];
		}
		if ( ! $this->passesHoneypot() ) {
			return [ 'error' => 'honeypot' ];
		}
		if ( ! $this->passesCaptchaAjax( $email ) ) {
			return [ 'error' => 'captcha' ];
		}
		if ( ! $this->checkRateIp() ) {
			return [ 'error' => 'rate_ip' ];
		}

		$s                   = $this->getSettings();
		$enable_registration = ! empty( $s['enable_registration'] ) && get_option( 'users_can_register' );
		$user                = get_user_by( 'email', strtolower( $email ) );

		if ( 'auto' === $mode ) {
			if ( $user ) {
				$mode = 'login';
			} elseif ( $enable_registration ) {
				$mode = 'register';
			} else {
				return [
					'data' => [
						'message'  => __( 'If an account exists with this email, a magic link has been sent.', 'wp-powersuite' ),
						'type'     => 'generic',
						'title'    => __( 'Check your email', 'wp-powersuite' ),
						'subtitle' => __( 'If an account exists, we sent you a link.', 'wp-powersuite' ),
					],
				];
			}
		}

		if ( 'register' === $mode ) {
			if ( ! $enable_registration ) {
				return [
					'data' => [
						'message'  => __( 'Check your email for next steps.', 'wp-powersuite' ),
						'type'     => 'generic',
						'title'    => __( 'Check your email', 'wp-powersuite' ),
						'subtitle' => __( 'If registration is available, we sent you a link.', 'wp-powersuite' ),
					],
				];
			}
			if ( $user ) {
			return [
					'data' => [
						'message'  => __( 'Check your email for next steps.', 'wp-powersuite' ),
						'type'     => 'generic',
						'title'    => __( 'Check your email', 'wp-powersuite' ),
						'subtitle' => __( 'Check your inbox for next steps.', 'wp-powersuite' ),
					],
				];
			}
			if ( ! $this->checkRateEmail( $email ) ) {
				return $this->genericSuccessResponse();
			}
			$r = $this->sendRegistrationEmail( strtolower( $email ), $redirect_to );
			if ( is_wp_error( $r ) ) {
				return [ 'error' => 'send_failed' ];
			}
			$this->incrementRateIp();
			$this->incrementRateEmail( $email );
			return $this->genericSuccessResponse();
		}

		if ( ! $user ) {
			return $this->genericSuccessResponse();
		}
		if ( ! $this->userMayUseMagicLogin( $user ) ) {
			return $this->genericSuccessResponse();
		}
		if ( ! $this->checkRateEmail( $email ) ) {
			return $this->genericSuccessResponse();
		}

		$r = $this->createAndDeliverMagicLogin( $user, $redirect_to );
		if ( is_wp_error( $r ) ) {
			return [ 'error' => 'send_failed' ];
		}
		$this->incrementRateIp();
		$this->incrementRateEmail( $user->user_email );

		return $this->genericSuccessResponse( $this->last_code_login_url ?? '' );
	}

	/**
	 * Map machine-readable error codes to user-visible messages (redirect flows).
	 */
	private function magicLinkErrorMessage( string $code ): string {
		$code = sanitize_key( $code );
		if ( $code === 'rate_ip' ) {
			$default = apply_filters(
				'wp_powersuite_magic_login_rate_limit_message',
				self::getDefaultErrorMessages()['rate_ip'],
				$this->clientIp()
			);
			return $this->errorMessage( 'rate_ip', $default );
		}
		if ( isset( self::getDefaultErrorMessages()[ $code ] ) || isset( self::getErrorMessageAliases()[ $code ] ) ) {
			return $this->errorMessage( $code );
		}
		return $this->errorMessage( 'unknown' );
	}

	public function ajaxRequestMagicLink(): void {
		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), self::NONCE_ACTION ) ) {
			wp_send_json_error( [ 'message' => $this->errorMessage( 'nonce_ajax' ) ] );
		}
		$email   = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( (string) $_POST['email'] ) ) : '';
		$redir   = isset( $_POST['redirect_to'] ) ? wp_validate_redirect( esc_url_raw( wp_unslash( (string) $_POST['redirect_to'] ) ), '' ) : '';
		$mode    = isset( $_POST['mode'] ) ? sanitize_key( wp_unslash( (string) $_POST['mode'] ) ) : 'login';
		$res     = $this->requestMagicLinkCore( $email, $redir, $mode );
		if ( isset( $res['error'] ) ) {
			$code = (string) $res['error'];
			wp_send_json_error( [ 'message' => $this->magicLinkErrorMessage( $code ) ] );
		}
		wp_send_json_success( $res['data'] ?? [] );
	}

	/**
	 * @return true|WP_Error
	 */
	private function createAndDeliverMagicLogin( WP_User $user, string $redirect_to ) {
		$this->last_code_login_url = null;
		$this->repo()->invalidateUnusedForUser( (int) $user->ID );
		$token      = bin2hex( random_bytes( 32 ) );
		$token_hash = hash( 'sha256', $token );
		$code       = $this->generateLoginCode();
		$code_hash  = hash( 'sha256', $code );
		$expiry     = $this->expiryMinutes();
		$exp_ts     = time() + $expiry * MINUTE_IN_SECONDS;

		if ( empty( $this->getSettings()['enable_code_login'] ) ) {
			$code_hash = '';
			$code      = '';
		}

		$inserted = $this->repo()->insertRow(
			(int) $user->ID,
			$token_hash,
			$code_hash !== '' ? $code_hash : null,
			$exp_ts,
			$this->clientIp(),
			$this->userAgent()
		);
		if ( false === $inserted ) {
			return new WP_Error( 'db' );
		}

		$base_url  = apply_filters( 'wp_powersuite_magic_login_base_url', wp_login_url(), $user );
		$login_url = add_query_arg(
			[
				'action' => self::VERIFY_ACTION,
				'token'  => $token,
				'uid'    => $user->ID,
			],
			$base_url
		);
		if ( $redirect_to !== '' ) {
			$login_url = add_query_arg( 'redirect_to', $redirect_to, $login_url );
		}
		$login_url = apply_filters( 'wp_powersuite_magic_login_url', $login_url, $user, $redirect_to );

		$code_login_url = '';
		if ( $code !== '' ) {
			$cs = $this->createCodeSession( (int) $user->ID, $redirect_to );
			$code_login_url = add_query_arg(
				[
					'action' => self::CODE_ACTION,
					'cs'     => $cs,
				],
				$base_url
			);
			$this->last_code_login_url = $code_login_url;
		}

		$sent = $this->sendLoginEmail( $user, $login_url, $code, $expiry, $redirect_to, $code_login_url );
		return $sent ? true : new WP_Error( 'mail' );
	}

	private function generateLoginCode(): string {
		$len = $this->loginCodeLength();
		$max = (int) pow( 10, $len ) - 1;
		if ( $max < 1 ) {
			$max = 9;
		}
		return str_pad( (string) random_int( 0, $max ), $len, '0', STR_PAD_LEFT );
	}

	private function loginCodeLength(): int {
		return max( 6, min( 10, absint( $this->getSettings()['login_code_digits'] ?? 8 ) ) );
	}

	private function sendLoginEmail( WP_User $user, string $login_url, string $plain_code, int $expiry_minutes, string $redirect_to = '', string $code_login_url = '' ): bool {
		$s           = $this->getSettings();
		self::$inline_mail_qr_png = null;
		$qr_html     = '';
		if ( ! empty( $s['include_qr_code'] ) ) {
			$png = $this->renderMagicLoginQrPng( $login_url );
			if ( $png !== null && $png !== '' ) {
				self::$inline_mail_qr_png = $png;
				$qr_html = $this->buildQrHtmlMailCid();
			}
		}
		$enable_code = ! empty( $s['enable_code_login'] );
		if ( $enable_code && $plain_code !== '' && $code_login_url === '' ) {
			$code_base = apply_filters( 'wp_powersuite_magic_login_base_url', wp_login_url(), $user );
			$cs        = $this->createCodeSession( (int) $user->ID, $redirect_to );
			$code_login_url = add_query_arg(
				[
					'action' => self::CODE_ACTION,
					'cs'     => $cs,
				],
				$code_base
			);
		}
		$code_block = '';
		if ( $enable_code && $plain_code !== '' && $code_login_url !== '' ) {
			$code_block = '<p style="margin-top:20px;padding-top:20px;border-top:1px solid #e5e7eb;color:#6b7280;font-size:14px;">'
				. esc_html__( 'Or enter this code on the code page:', 'wp-powersuite' )
				. ' <strong style="font-family:monospace;font-size:18px;letter-spacing:2px;">' . esc_html( $plain_code ) . '</strong></p>'
				. '<p style="font-size:13px;"><a href="' . esc_url( $code_login_url ) . '">'
				. esc_html__( 'Open the code entry page', 'wp-powersuite' ) . '</a></p>';
		}
		$body = $this->buildMagicLinkEmailHtml( $user, $login_url, $expiry_minutes, $qr_html, $code_block, $enable_code ? $plain_code : '' );
		$sub  = sprintf(
			'[%1$s] %2$s',
			wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ),
			__( 'Your Login Link', 'wp-powersuite' )
		);
		$sub = apply_filters( 'wp_powersuite_magic_login_email_subject', $sub, $user );
		$headers = EmailRenderer::headers();
		$sent    = wp_mail( $user->user_email, $sub, $body, $headers );
		self::$inline_mail_qr_png = null;
		return $sent;
	}

	private function buildMagicLinkEmailHtml( WP_User $user, string $login_url, int $expiry_minutes, string $qr_html, string $code_html, string $plain_code = '' ): string {
		$site     = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
		$site_url = home_url( '/' );
		$accent   = EmailRenderer::accentColor();

		$valid_for = sprintf(
			/* translators: %d number of minutes */
			_n( '%d minute', '%d minutes', $expiry_minutes, 'wp-powersuite' ),
			$expiry_minutes
		);

		$rows = [
			[
				'label'      => __( 'Site', 'wp-powersuite' ),
				'value_html' => '<a href="' . esc_url( $site_url ) . '" style="color:' . esc_attr( $accent ) . ';text-decoration:none;">' . esc_html( $site_url ) . '</a>',
			],
			[
				'label' => __( 'Account', 'wp-powersuite' ),
				'value' => $user->user_email,
			],
			[
				'label' => __( 'Link valid for', 'wp-powersuite' ),
				'value' => $valid_for,
			],
		];
		if ( $plain_code !== '' ) {
			$rows[] = [
				'label' => __( 'One-time code', 'wp-powersuite' ),
				'value_html' => '<span style="font-family:' . EmailDesign::MONO_STACK . ';font-weight:600;letter-spacing:2px;">' . esc_html( $plain_code ) . '</span>',
			];
		}

		$content  = EmailComponents::paragraph(
			sprintf(
				/* translators: %s: display name */
				esc_html__( 'Hi %s,', 'wp-powersuite' ),
				esc_html( $user->display_name )
			),
			[ 'margin' => '0 0 12px' ]
		);
		$content .= EmailComponents::keyValueTable( $rows );
		$content .= EmailComponents::notice(
			esc_html(
				sprintf(
					/* translators: %s: site name */
					__( 'Use the button below to sign in to %s. This link works once.', 'wp-powersuite' ),
					$site
				)
			) . ' ' . esc_html__( 'If you did not request this email, you can safely ignore it.', 'wp-powersuite' ),
			'info'
		);
		$content .= EmailComponents::button( $login_url, __( 'Sign in securely', 'wp-powersuite' ), [ 'color' => $accent ] );
		$content .= EmailComponents::linkFallback( $login_url );
		$content .= $qr_html . $code_html;

		return EmailRenderer::wrap(
			[
				'title'        => __( 'Secure sign-in', 'wp-powersuite' ),
				'preheader'    => __( 'Your one-time login link is ready.', 'wp-powersuite' ),
				'logo_url'     => $this->getMagicLoginEmailLogoUrl(),
				'site_name'    => $site,
				'content'      => $content,
				'footer_brand' => __( 'Sent via WP PowerSuite Magic Login', 'wp-powersuite' ),
				'config'       => [ 'accent_color' => $accent ],
			]
		);
	}

	private function getMagicLoginEmailLogoUrl(): string {
		$default = '';
		if ( function_exists( 'get_site_icon_url' ) ) {
			$icon = get_site_icon_url( 256 );
			if ( is_string( $icon ) && $icon !== '' ) {
				$default = $icon;
			}
		}

		return (string) apply_filters( 'wp_powersuite_magic_login_email_logo_url', $default );
	}

	/**
	 * Primary CTA (matches Backup & Restore accent).
	 */
	private function magicLoginEmailCtaButton( string $login_url, string $label ): string {
		return EmailComponents::button( $login_url, wp_strip_all_tags( $label ), [ 'color' => EmailRenderer::accentColor() ] );
	}

	/**
	 * Attach QR PNG as inline image (CID) so clients like Gmail show the code (data: URIs are often blocked).
	 *
	 * @param \PHPMailer\PHPMailer\PHPMailer|\PHPMailer $phpmailer PHPMailer instance.
	 */
	public static function maybeEmbedMagicLoginQrImage( $phpmailer ): void {
		if ( self::$inline_mail_qr_png === null || ! is_object( $phpmailer ) ) {
			return;
		}
		$png = self::$inline_mail_qr_png;
		self::$inline_mail_qr_png = null;
		if ( ! is_string( $png ) || $png === '' ) {
			return;
		}
		$tmp = wp_tempnam( 'wpps-ml-qr.png' );
		if ( ! $tmp || false === file_put_contents( $tmp, $png ) ) {
			return;
		}
		if ( method_exists( $phpmailer, 'addEmbeddedImage' ) ) {
			$phpmailer->addEmbeddedImage( $tmp, self::MAIL_QR_CID, 'qrcode.png' );
		} elseif ( method_exists( $phpmailer, 'AddEmbeddedImage' ) ) {
			$phpmailer->AddEmbeddedImage( $tmp, self::MAIL_QR_CID, 'qrcode.png' );
		}
		register_shutdown_function(
			static function () use ( $tmp ): void {
				if ( is_string( $tmp ) && $tmp !== '' && file_exists( $tmp ) ) {
					@unlink( $tmp );
				}
			}
		);
	}

	/**
	 * PNG binary for a QR encoding $url, or null if unavailable.
	 */
	private function renderMagicLoginQrPng( string $url ): ?string {
		if ( ! function_exists( 'imagepng' ) ) {
			return null;
		}
		$file = __DIR__ . '/includes/class-qrcode.php';
		if ( ! is_readable( $file ) ) {
			return null;
		}
		require_once $file;
		if ( ! class_exists( 'WPPS_Magic_Login_QRCode' ) ) {
			return null;
		}
		try {
			$qr = new \WPPS_Magic_Login_QRCode( $url, [ 's' => 'qrm', 'w' => 200, 'h' => 200 ] );
			$im = $qr->render_image();
			if ( ! $im ) {
				return null;
			}
			ob_start();
			imagepng( $im );
			$data = (string) ob_get_clean();
			imagedestroy( $im );
			return $data !== '' ? $data : null;
		} catch ( \Throwable $e ) {
			return null;
		}
	}

	/**
	 * HTML wrapper with CID image reference for MIME multipart mail.
	 */
	private function buildQrHtmlMailCid(): string {
		return sprintf(
			'<div style="text-align:center;margin:24px 0;padding:20px;background:#f9fafb;border-radius:12px;">
					<p style="margin:0 0 12px;color:#6b7280;font-size:14px;">%s</p>
					<img src="cid:%s" alt="" width="180" height="180" style="border-radius:8px;"></div>',
			esc_html__( 'Scan with your phone:', 'wp-powersuite' ),
			esc_attr( self::MAIL_QR_CID )
		);
	}

	private function replaceMagicLoginQrForMail( string $login_url ): string {
		if ( empty( $this->getSettings()['include_qr_code'] ) ) {
			return '';
		}
		$png = $this->renderMagicLoginQrPng( $login_url );
		if ( $png === null || $png === '' ) {
			return '';
		}
		self::$inline_mail_qr_png = $png;
		return $this->buildQrHtmlMailCid();
	}

	private function buildRegistrationInviteEmailHtml( string $registration_url, string $site_name, int $expiry_minutes ): string {
		$site_url = home_url( '/' );
		$accent   = EmailRenderer::accentColor();
		$exp      = sprintf(
			/* translators: %d minutes */
			__( 'This link expires in %d minutes.', 'wp-powersuite' ),
			$expiry_minutes
		);

		$content  = EmailComponents::paragraph(
			sprintf(
				/* translators: %s site name */
				esc_html__( 'Finish setting up your account on %s. This invitation expires soon for your security.', 'wp-powersuite' ),
				esc_html( $site_name )
			)
		);
		$content .= EmailComponents::notice( esc_html( $exp ), 'info' );
		$content .= EmailComponents::button( $registration_url, __( 'Create my account', 'wp-powersuite' ), [ 'color' => $accent ] );
		$content .= EmailComponents::linkFallback( $registration_url );
		$content .= EmailComponents::keyValueTable(
			[
				[
					'label'      => __( 'Site', 'wp-powersuite' ),
					'value_html' => '<a href="' . esc_url( $site_url ) . '" style="color:' . esc_attr( $accent ) . ';text-decoration:none;">' . esc_html( $site_url ) . '</a>',
				],
			]
		);

		return EmailRenderer::wrap(
			[
				'title'        => __( 'Complete your registration', 'wp-powersuite' ),
				'logo_url'     => $this->getMagicLoginEmailLogoUrl(),
				'site_name'    => $site_name,
				'content'      => $content,
				'footer_brand' => __( 'Sent via WP PowerSuite Magic Login', 'wp-powersuite' ),
				'config'       => [ 'accent_color' => $accent ],
			]
		);
	}

	private function sendRegistrationEmail( string $email, string $redirect_to ) {
		$token = bin2hex( random_bytes( 32 ) );
		$th    = hash( 'sha256', $token );
		$min   = $this->expiryMinutes();
		$key   = self::REG_PREFIX . $th;
		if ( ! set_transient( $key, [ 'email' => $email, 'redirect_to' => $redirect_to, 'created_at' => time() ], $min * MINUTE_IN_SECONDS ) ) {
			return new WP_Error( 't' );
		}
		$base = apply_filters( 'wp_powersuite_magic_login_base_url', wp_login_url(), null );
		$url  = add_query_arg(
			[
				'action' => self::REGISTER_ACTION,
				'token'  => $token,
			],
			$base
		);
		if ( $redirect_to !== '' ) {
			$url = add_query_arg( 'redirect_to', $redirect_to, $url );
		}
		$url  = apply_filters( 'wp_powersuite_magic_login_registration_url', $url, $email );
		$site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
		$body = $this->buildRegistrationInviteEmailHtml( $url, $site, $min );
		$sub  = sprintf( '[%s] %s', $site, __( 'Complete registration', 'wp-powersuite' ) );
		if ( ! wp_mail( $email, $sub, $body, EmailRenderer::headers() ) ) {
			delete_transient( $key );
			return new WP_Error( 'mail' );
		}
		return true;
	}

	public function filterWpMailPlaceholders( array $args ): array {
		$s = $this->getSettings();
		if ( empty( $s['enable_auto_login'] ) ) {
			return $args;
		}
		$msg = $args['message'] ?? '';
		if ( ! is_string( $msg ) || ( strpos( $msg, '{{MAGIC_LINK}}' ) === false && strpos( $msg, '{{MAGIC_LINK_BUTTON}}' ) === false && strpos( $msg, '{{MAGIC_LOGIN_QR}}' ) === false ) ) {
			return $args;
		}
		$to = $args['to'] ?? '';
		if ( is_array( $to ) ) {
			$to = $to[0] ?? '';
		}
		if ( preg_match( '/<([^>]+)>/', (string) $to, $m ) ) {
			$to = $m[1];
		}
		$user = get_user_by( 'email', sanitize_email( (string) $to ) );
		if ( ! $user instanceof WP_User || ! $this->userMayUseMagicLogin( $user ) ) {
			$args['message'] = str_replace( [ '{{MAGIC_LINK}}', '{{MAGIC_LINK_BUTTON}}', '{{MAGIC_LOGIN_QR}}' ], '', $msg );
			return $args;
		}
		if ( ! $this->checkRateIp() || ! $this->checkRateEmail( $user->user_email ) ) {
			$args['message'] = str_replace( [ '{{MAGIC_LINK}}', '{{MAGIC_LINK_BUTTON}}', '{{MAGIC_LOGIN_QR}}' ], '', $msg );
			return $args;
		}
		if ( ! apply_filters( 'wp_powersuite_magic_login_replace_placeholder', true, $args, $user ) ) {
			$args['message'] = str_replace( [ '{{MAGIC_LINK}}', '{{MAGIC_LINK_BUTTON}}', '{{MAGIC_LOGIN_QR}}' ], '', $msg );
			return $args;
		}
		$this->repo()->invalidateUnusedForUser( (int) $user->ID );
		$token     = bin2hex( random_bytes( 32 ) );
		$th        = hash( 'sha256', $token );
		$code_enabled = ! empty( $this->getSettings()['enable_code_login'] );
		$code         = $code_enabled ? $this->generateLoginCode() : '';
		$code_hash    = ( $code !== '' ) ? hash( 'sha256', $code ) : null;
		$expiry       = $this->expiryMinutes();
		$exp_ts       = time() + $expiry * MINUTE_IN_SECONDS;
		$insert       = $this->repo()->insertRow( (int) $user->ID, $th, $code_hash, $exp_ts, $this->clientIp(), 'Auto-login email' );
		if ( false === $insert ) {
			$args['message'] = str_replace( [ '{{MAGIC_LINK}}', '{{MAGIC_LINK_BUTTON}}', '{{MAGIC_LOGIN_QR}}' ], '', $msg );
			return $args;
		}
		$this->incrementRateIp();
		$this->incrementRateEmail( $user->user_email );
		$base      = apply_filters( 'wp_powersuite_magic_login_base_url', wp_login_url(), $user );
		$login_url = add_query_arg( [ 'action' => self::VERIFY_ACTION, 'token' => $token, 'uid' => $user->ID ], $base );
		$login_url = apply_filters( 'wp_powersuite_magic_login_url', $login_url, $user, '' );
		$msg       = str_replace( '{{MAGIC_LINK}}', $login_url, $msg );
		$btn       = $this->magicLoginEmailCtaButton( $login_url, esc_html__( 'Sign In', 'wp-powersuite' ) );
		$msg       = str_replace( '{{MAGIC_LINK_BUTTON}}', $btn, $msg );
		$msg       = str_replace( '{{MAGIC_LOGIN_QR}}', $this->replaceMagicLoginQrForMail( $login_url ), $msg );
		do_action( 'wp_powersuite_magic_login_auto_embedded', $user, $login_url, $args );
		$args['message'] = $msg;
		return $args;
	}

	public function ajaxVerifyMagicCode(): void {
		$this->verifyMagicCodeFromRequest( true );
	}

	/**
	 * Handle code form POST on wp-login.php (preferred over admin-ajax for non-XHR).
	 */
	public function handleCodeFormPost(): void {
		if ( strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) ) ) !== 'POST' ) {
			return;
		}
		if ( ! isset( $_POST['login_code'] ) ) {
			return;
		}
		$this->verifyMagicCodeFromRequest( false );
	}

	private function verifyMagicCodeFromRequest( bool $allow_json ): void {
		$cs    = isset( $_POST['wpps_ml_cs'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['wpps_ml_cs'] ) ) : '';
		$uid   = 0;
		$redir = isset( $_POST['redirect_to'] ) ? wp_validate_redirect( esc_url_raw( wp_unslash( (string) $_POST['redirect_to'] ) ), '' ) : '';
		$sess  = $cs !== '' ? $this->resolveCodeSession( $cs ) : null;
		if ( $sess ) {
			$uid   = $sess['uid'];
			$redir = $redir !== '' ? $redir : $sess['redirect'];
		} else {
			$uid = isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : 0;
		}

		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['switchboard_code_nonce'] ?? $_POST['wpps_code_nonce'] ?? '' ) ), self::NONCE_CODE ) ) {
			$this->sendCodeError( $this->errorMessage( 'code_nonce' ), $uid, $redir, $cs );
		}
		if ( ! $this->passesCaptchaAjax( '' ) ) {
			$this->sendCodeError( $this->errorMessage( 'captcha' ), $uid, $redir, $cs );
		}
		$code = isset( $_POST['login_code'] ) ? preg_replace( '/\D+/', '', (string) wp_unslash( $_POST['login_code'] ) ) : '';
		$len  = $this->loginCodeLength();
		if ( $uid < 1 || $cs === '' || strlen( $code ) !== $len ) {
			$this->sendCodeError( $this->errorMessage( 'code_invalid' ), $uid, $redir, $cs );
		}
		$ip_key = 'wpps_magic_code_' . md5( $this->clientIp() );
		$att    = (int) get_transient( $ip_key );
		if ( $att >= max( 1, absint( $this->getSettings()['code_rate_max'] ?? 10 ) ) ) {
			$this->sendCodeError( $this->errorMessage( 'code_rate' ), $uid, $redir, $cs );
		}
		if ( ! $this->checkRateCodeForUser( $uid ) ) {
			$this->sendCodeError( $this->errorMessage( 'code_rate' ), $uid, $redir, $cs );
		}
		set_transient( $ip_key, $att + 1, 15 * MINUTE_IN_SECONDS );
		$row = $this->repo()->findByCodeAndUser( hash( 'sha256', $code ), $uid );
		if ( ! $row ) {
			$this->sendCodeError( $this->errorMessage( 'code_expired' ), $uid, $redir, $cs );
		}
		$user = get_userdata( $uid );
		if ( ! $user instanceof WP_User || ! $this->userMayUseMagicLogin( $user ) ) {
			$this->sendCodeError( $this->errorMessage( 'code_expired' ), $uid, $redir, $cs );
		}
		if ( ! $this->repo()->markUsed( (int) $row->id ) ) {
			$this->sendCodeError( $this->errorMessage( 'code_expired' ), $uid, $redir, $cs );
		}
		delete_transient( $ip_key );
		delete_transient( 'wpps_magic_code_user_' . $uid );
		if ( $cs !== '' ) {
			delete_transient( self::CODE_SESSION_PREFIX . hash( 'sha256', $cs ) );
		}

		$two_fa_url = $this->maybeBeginTwoFactor( $user, $redir );
		if ( is_string( $two_fa_url ) && $two_fa_url !== '' ) {
			if ( $allow_json && $this->isXHR() ) {
				wp_send_json_success( [ 'redirect' => $two_fa_url ] );
			}
			wp_safe_redirect( $two_fa_url );
			exit;
		}

		if ( $allow_json && $this->isXHR() ) {
			wp_clear_auth_cookie();
			$remember = (bool) apply_filters( 'wp_powersuite_magic_login_remember_user', false, $user );
			wp_set_current_user( (int) $user->ID );
			wp_set_auth_cookie( (int) $user->ID, $remember, is_ssl() );
			do_action( 'wp_powersuite_magic_login_authenticated', $user );
			do_action( 'wp_login', $user->user_login, $user );
			$destination = $redir !== '' ? $redir : $this->resolveRedirectDestination( $user );
			$destination = apply_filters( 'login_redirect', $destination, $redir, $user );
			$destination = apply_filters( 'wp_powersuite_magic_login_redirect', $destination, $user );
			wp_send_json_success( [ 'redirect' => wp_validate_redirect( $destination, home_url( '/' ) ) ] );
		}
		$this->completeLoginSession( $user, $redir );
	}

	private function isXHR(): bool {
		return ! empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( (string) $_SERVER['HTTP_X_REQUESTED_WITH'] ) === 'xmlhttprequest';
	}

	private function passesHoneypot(): bool {
		if ( empty( $this->getSettings()['enable_honeypot'] ) ) {
			return true;
		}
		$f = self::HONEYPOT_FIELD;
		$v = isset( $_POST[ $f ] ) ? (string) wp_unslash( $_POST[ $f ] ) : '';
		return trim( $v ) === '';
	}

	private function passesCaptchaAjax( string $email ): bool {
		unset( $email );
		$p = sanitize_key( (string) ( $this->getSettings()['captcha_provider'] ?? 'none' ) );
		if ( 'none' === $p || '' === $p ) {
			return true;
		}
		$pre = apply_filters( 'wp_powersuite_magic_login_validate_captcha', null, $p );
		if ( is_wp_error( $pre ) ) {
			return false;
		}
		if ( true === $pre ) {
			return true;
		}
		if ( false === $pre ) {
			return false;
		}
		if ( 'simple' === $p ) {
			$key = isset( $_POST['wpps_simple_captcha_key'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['wpps_simple_captcha_key'] ) ) : '';
			$ans = isset( $_POST['wpps_simple_captcha_answer'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['wpps_simple_captcha_answer'] ) ) : '';
			return apply_filters( 'wpps_verify_simple_captcha', false, $key, $ans );
		}
		if ( 'recaptcha' === $p ) {
			$token = isset( $_POST['g-recaptcha-response'] ) ? (string) wp_unslash( $_POST['g-recaptcha-response'] ) : '';
			if ( $token === '' && isset( $_POST['g_recaptcha_response'] ) ) {
				$token = (string) wp_unslash( $_POST['g_recaptcha_response'] );
			}
			return apply_filters( 'wpps_verify_google_recaptcha', false, $token, self::CAPTCHA_VERIFY_ACTION );
		}
		if ( 'turnstile' === $p ) {
			$token = isset( $_POST['cf-turnstile-response'] ) ? (string) wp_unslash( $_POST['cf-turnstile-response'] ) : '';
			if ( $token === '' && isset( $_POST['cf_turnstile_response'] ) ) {
				$token = (string) wp_unslash( $_POST['cf_turnstile_response'] );
			}
			return apply_filters( 'wpps_verify_turnstile', false, $token, self::CAPTCHA_VERIFY_ACTION );
		}
		return true;
	}

	private function renderCaptchaFieldMarkup( string $context_key ): void {
		unset( $context_key );
		$p = sanitize_key( (string) ( $this->getSettings()['captcha_provider'] ?? 'none' ) );
		if ( 'none' === $p || '' === $p ) {
			return;
		}
		$mgr = ModuleManager::getInstance();
		$action = self::CAPTCHA_VERIFY_ACTION;
		if ( 'simple' === $p && \WP_PowerSuite\Core\OptionStoreModuleSettingsRepository::isModuleEnabled( 'simple-captcha' ) ) {
			$m = $mgr->getModule( 'simple-captcha' );
			if ( $m && method_exists( $m, 'renderWidget' ) ) {
				$m->renderWidget( $action, false, 'ml' );
			}
			return;
		}
		if ( 'recaptcha' === $p && \WP_PowerSuite\Core\OptionStoreModuleSettingsRepository::isModuleEnabled( 'google-recaptcha' ) ) {
			$m = $mgr->getModule( 'google-recaptcha' );
			if ( $m && method_exists( $m, 'renderWidget' ) ) {
				$m->renderWidget( $action );
			}
			return;
		}
		if ( 'turnstile' === $p && \WP_PowerSuite\Core\OptionStoreModuleSettingsRepository::isModuleEnabled( 'cloudflare-turnstile' ) ) {
			$m = $mgr->getModule( 'cloudflare-turnstile' );
			if ( $m && method_exists( $m, 'renderWidget' ) ) {
				$m->renderWidget( $action );
			}
		}
	}

	/**
	 * @return array{provider: string, recaptchaSiteKey?: string, recaptchaVersion?: string}
	 */
	private function getMagicLoginCaptchaClientConfig(): array {
		$provider = sanitize_key( (string) ( $this->getSettings()['captcha_provider'] ?? 'none' ) );
		$config   = [ 'provider' => $provider ];
		if ( 'recaptcha' !== $provider || ! \WP_PowerSuite\Core\OptionStoreModuleSettingsRepository::isModuleEnabled( 'google-recaptcha' ) ) {
			return $config;
		}
		$raw = \WP_PowerSuite\Core\OptionStoreModuleSettingsRepository::forModule( 'google-recaptcha' )->all( [] );
		if ( ! is_array( $raw ) ) {
			return $config;
		}
		$config['recaptchaSiteKey']  = trim( (string) ( $raw['site_key'] ?? '' ) );
		$config['recaptchaVersion']  = sanitize_key( (string) ( $raw['version'] ?? 'v2' ) );

		return $config;
	}

	private function adjustBrightnessHex( string $hex, int $percent ): string {
		$hex = ltrim( trim( $hex ), '#' );
		if ( strlen( $hex ) !== 6 || ! ctype_xdigit( $hex ) ) {
			return '#' . $hex;
		}
		$r     = hexdec( substr( $hex, 0, 2 ) );
		$g     = hexdec( substr( $hex, 2, 2 ) );
		$b     = hexdec( substr( $hex, 4, 2 ) );
		$r     = max( 0, min( 255, (int) round( $r + ( $r * $percent / 100 ) ) ) );
		$g     = max( 0, min( 255, (int) round( $g + ( $g * $percent / 100 ) ) ) );
		$b     = max( 0, min( 255, (int) round( $b + ( $b * $percent / 100 ) ) ) );
		return sprintf( '#%02x%02x%02x', $r, $g, $b );
	}

	private function sanitizeShortcodeCustomCss( string $css ): string {
		if ( $css === '' ) {
			return '';
		}
		$safe = preg_replace( '/<\s*\/?\s*(style|script|html|body|head|meta|link|iframe|object|embed)[^>]*>/i', '', $css );
		$safe = is_string( $safe ) ? $safe : '';
		$safe = preg_replace( '/javascript\s*:/i', '', $safe );
		$safe = preg_replace( '/expression\s*\(/i', '', $safe );
		$safe = preg_replace( '/url\s*\(\s*["\']?\s*data:/i', 'url(blocked:', $safe );
		return is_string( $safe ) ? $safe : '';
	}

	public function enqueueLoginPageAssets(): void {
		$base = plugin_dir_url( __FILE__ );
		wp_enqueue_style( 'wpps-magic-login', $base . 'assets/magic-login.css', [], self::MODULE_VERSION );
		wp_enqueue_script( 'wpps-magic-login-wp', $base . 'assets/wp-login-magic.js', [], self::MODULE_VERSION, true );
		$s = $this->getSettings();
		$captcha_provider = sanitize_key( (string) ( $s['captcha_provider'] ?? 'none' ) );
		$captcha_client = $this->getMagicLoginCaptchaClientConfig();
		wp_localize_script(
			'wpps-magic-login-wp',
			'wppsMagicLoginWp',
			[
				'ajaxUrl'          => admin_url( 'admin-ajax.php' ),
				'action'           => self::AJAX_REQUEST,
				'nonce'            => wp_create_nonce( self::NONCE_ACTION ),
				'mode'             => 'auto',
				'honeypotName'     => ! empty( $s['enable_honeypot'] ) ? self::HONEYPOT_FIELD : '',
				'captchaRequired'    => $captcha_provider !== '' && $captcha_provider !== 'none',
				'captchaProvider'    => $captcha_client['provider'] ?? 'none',
				'recaptchaSiteKey'   => $captcha_client['recaptchaSiteKey'] ?? '',
				'recaptchaVersion'   => $captcha_client['recaptchaVersion'] ?? 'v2',
				'captchaError'       => $this->magicLinkErrorMessage( 'captcha' ),
				'successTitle'       => __( 'Check your email', 'wp-powersuite' ),
				'errorTitle'         => __( 'Something went wrong', 'wp-powersuite' ),
				'requestFailed'      => __( 'Request failed.', 'wp-powersuite' ),
				'networkError'       => __( 'Network error. Please try again.', 'wp-powersuite' ),
				'enterCodeLabel'     => __( 'Enter one-time code', 'wp-powersuite' ),
				'tryAgain'           => __( 'Try again', 'wp-powersuite' ),
				'backToSignIn'       => __( 'Back to sign in', 'wp-powersuite' ),
			]
		);
	}

	public function enqueueShortcodeAssets(): void {
		if ( wp_style_is( 'wpps-magic-login', 'enqueued' ) && ( empty( $this->getSettings()['enable_ajax'] ) || wp_script_is( 'wpps-magic-login', 'enqueued' ) ) ) {
			return;
		}
		$base = plugin_dir_url( __FILE__ );
		wp_enqueue_style( 'wpps-magic-login', $base . 'assets/magic-login.css', [], self::MODULE_VERSION );
		if ( empty( $this->getSettings()['enable_ajax'] ) ) {
			return;
		}
		wp_enqueue_script( 'wpps-magic-login', $base . 'assets/magic-login.js', [], self::MODULE_VERSION, true );
		wp_localize_script(
			'wpps-magic-login',
			'wppsMagicLogin',
			[
				'ajaxUrl'       => admin_url( 'admin-ajax.php' ),
				'action'        => self::AJAX_REQUEST,
				'nonce'         => wp_create_nonce( self::NONCE_ACTION ),
				'captchaError'  => $this->magicLinkErrorMessage( 'captcha' ),
			]
		);
	}

	public function renderShortcode( array $atts ): string {
		if ( is_user_logged_in() ) {
			return '<p class="wpps-magic-login wpps-magic-login--logged-in">' . esc_html__( 'You are already logged in.', 'wp-powersuite' ) . '</p>';
		}
		$s = $this->getSettings();
		$this->enqueueShortcodeAssets();

		$atts = shortcode_atts(
			[
				'title'              => (string) $s['sc_title'],
				'description'        => (string) $s['sc_description'],
				'button'             => (string) $s['sc_button_text'],
				'redirect'           => '',
				'class'              => '',
				'show_password_link' => ! empty( $s['sc_show_password_link'] ),
				'mode'               => (string) $s['sc_mode'],
				'context'            => '',
			],
			$atts,
			self::SHORTCODE
		);

		$context     = sanitize_key( (string) $atts['context'] );
		$is_wp_login = ( 'wp_login_full' === $context );
		$form_rid    = 'wpps-ml-' . wp_rand( 1000, 999999 );
		$captcha_key = 'magic_sc_' . $form_rid;

		$mode = sanitize_key( (string) $atts['mode'] );
		if ( ! in_array( $mode, [ 'auto', 'login', 'register' ], true ) ) {
			$mode = 'auto';
		}

		$redirect_to = $atts['redirect'] !== ''
			? wp_validate_redirect( esc_url_raw( (string) $atts['redirect'] ), '' )
			: '';

		$header_html = '';
		if ( isset( $_GET['wpps_ml_err'] ) ) {
			$c   = sanitize_key( (string) wp_unslash( $_GET['wpps_ml_err'] ) );
			$msg = $this->magicLinkErrorMessage( $c );
			$header_html .= '<p class="wpps-magic-login__msg wpps-magic-login__msg--err" role="alert">' . esc_html( $msg ) . '</p>';
		} elseif ( isset( $_GET['wpps_ml_ok'] ) ) {
			$pair = [
				__( 'Check your email', 'wp-powersuite' ),
				__( 'If an account exists, we sent you a link.', 'wp-powersuite' ),
			];
			$header_html .= '<p class="wpps-magic-login__msg wpps-magic-login__msg--ok" role="status"><strong>' . esc_html( $pair[0] ) . '</strong><br>' . esc_html( $pair[1] ) . '</p>';
			$code_url = '';
			if ( isset( $_GET['wpps_ml_flash'] ) ) {
				$flash = sanitize_text_field( wp_unslash( (string) $_GET['wpps_ml_flash'] ) );
				$code_url = $this->consumeSuccessFlash( $flash ) ?? '';
			} elseif ( isset( $_GET['wpps_ml_cs'] ) ) {
				$cs = sanitize_text_field( wp_unslash( (string) $_GET['wpps_ml_cs'] ) );
				if ( $cs !== '' ) {
					$code_url = add_query_arg(
						[
							'action' => self::CODE_ACTION,
							'cs'     => $cs,
						],
						wp_login_url()
					);
				}
			}
			if ( $code_url !== '' ) {
				$header_html .= '<p class="wpps-magic-login__msg wpps-magic-login__msg--ok"><a class="button button-secondary" href="' . esc_url( $code_url ) . '">'
					. esc_html__( 'Enter one-time code', 'wp-powersuite' ) . '</a></p>';
			}
		}

		$bg     = sanitize_hex_color( (string) $s['style_bg_color'] ) ?: '#ffffff';
		$border = sanitize_hex_color( (string) $s['style_border_color'] ) ?: '#e2e8f0';
		$rad    = max( 0, absint( $s['style_border_radius'] ?? 12 ) );
		$tcol   = sanitize_hex_color( (string) $s['style_title_color'] ) ?: '#1e293b';
		$dcol   = sanitize_hex_color( (string) $s['style_desc_color'] ) ?: '#64748b';
		$ib     = sanitize_hex_color( (string) $s['style_input_border_color'] ) ?: '#d1d5db';
		$ibr    = max( 0, absint( $s['style_input_border_radius'] ?? 8 ) );
		$bbg    = sanitize_hex_color( (string) $s['style_button_bg'] ) ?: '#3b82f6';
		$btx    = sanitize_hex_color( (string) $s['style_button_text_color'] ) ?: '#ffffff';
		$bbr    = max( 0, absint( $s['style_button_radius'] ?? 8 ) );
		$bhv    = $this->adjustBrightnessHex( $bbg, -12 );

		ob_start();
		?>
		<div class="wpps-magic-login <?php echo esc_attr( (string) $atts['class'] ); ?>" id="<?php echo esc_attr( $form_rid ); ?>-wrap" style="--wpps-ml-accent:<?php echo esc_attr( $bbg ); ?>;--wpps-ml-accent-hover:<?php echo esc_attr( $bhv ); ?>;--wpps-ml-border:<?php echo esc_attr( $border ); ?>;max-width:24rem;">
			<style>
				#<?php echo esc_attr( $form_rid ); ?>-wrap .wpps-magic-login__card{background:<?php echo esc_html( $bg ); ?>;border:1px solid <?php echo esc_html( $border ); ?>;border-radius:<?php echo (int) $rad; ?>px;}
				#<?php echo esc_attr( $form_rid ); ?>-wrap .wpps-magic-login__title{color:<?php echo esc_html( $tcol ); ?>;}
				#<?php echo esc_attr( $form_rid ); ?>-wrap .wpps-magic-login__description{color:<?php echo esc_html( $dcol ); ?>;}
			</style>
			<?php
			$extra = $this->sanitizeShortcodeCustomCss( (string) $s['shortcode_css'] );
			if ( $extra !== '' ) {
				echo '<style id="wpps-ml-custom-' . esc_attr( $form_rid ) . '">' . $extra . '</style>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			}
			?>
			<div class="wpps-magic-login__card">
				<div class="wpps-magic-login__header">
					<?php echo $header_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
				</div>
				<?php if ( (string) $atts['title'] !== '' ) : ?>
					<h3 class="wpps-magic-login__title"><?php echo esc_html( (string) $atts['title'] ); ?></h3>
				<?php endif; ?>
				<?php if ( (string) $atts['description'] !== '' ) : ?>
					<p class="wpps-magic-login__description"><?php echo esc_html( (string) $atts['description'] ); ?></p>
				<?php endif; ?>
				<?php
				$form_action = $is_wp_login
					? site_url( 'wp-login.php', 'login' )
					: ( ! empty( $s['enable_ajax'] ) ? admin_url( 'admin-ajax.php' ) : remove_query_arg( [ 'wpps_ml_err', 'wpps_ml_ok' ], (string) wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) ) );
				$form_tag_open = '<form class="wpps-magic-login__form" method="post" action="' . esc_url( $form_action ) . '" data-wpps-magic-login-form="1">';
				echo $form_tag_open; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				if ( $is_wp_login ) :
					?>
					<input type="hidden" name="action" value="<?php echo esc_attr( self::SCREEN_ACTION ); ?>" />
					<input type="hidden" name="wpps_ml_request" value="1" />
					<?php wp_nonce_field( self::NONCE_ACTION ); ?>
				<?php else : ?>
					<input type="hidden" name="wpps_ml_request" value="1" />
					<?php wp_nonce_field( self::NONCE_ACTION, 'nonce' ); ?>
				<?php endif; ?>
				<input type="hidden" name="redirect_to" value="<?php echo esc_attr( $redirect_to ); ?>" />
				<input type="hidden" name="mode" value="<?php echo esc_attr( $mode ); ?>" />
				<label class="wpps-magic-login__label" for="<?php echo esc_attr( $form_rid ); ?>-email"><?php echo esc_html( (string) $s['sc_email_label'] ); ?></label>
				<input id="<?php echo esc_attr( $form_rid ); ?>-email" class="wpps-magic-login__input" type="email" name="<?php echo $is_wp_login ? 'wpps_ml_email' : 'email'; ?>" required autocomplete="email" placeholder="<?php echo esc_attr( (string) $s['sc_placeholder'] ); ?>" style="border-radius:<?php echo (int) $ibr; ?>px;border-color:<?php echo esc_attr( $ib ); ?>;" />
				<?php if ( ! empty( $s['enable_honeypot'] ) ) : ?>
					<label class="wpps-magic-login__hp"><?php esc_html_e( 'Company', 'wp-powersuite' ); ?><input type="text" name="<?php echo esc_attr( self::HONEYPOT_FIELD ); ?>" value="" tabindex="-1" autocomplete="off" /></label>
				<?php endif; ?>
				<?php
				$this->renderCaptchaFieldMarkup( $captcha_key );
				do_action( 'wp_powersuite_magic_login_form_fields', $form_rid, $s );
				?>
				<button type="submit" class="wpps-magic-login__submit" style="background:<?php echo esc_attr( $bbg ); ?>;color:<?php echo esc_attr( $btx ); ?>;border-radius:<?php echo (int) $bbr; ?>px;">
					<span class="wpps-magic-login__spinner" aria-hidden="true"></span>
					<?php echo esc_html( (string) $atts['button'] ); ?>
				</button>
				</form>
				<?php if ( ! empty( $atts['show_password_link'] ) ) : ?>
					<p class="wpps-magic-login__footer-nav"><a href="<?php echo esc_url( wp_login_url() ); ?>"><?php esc_html_e( 'Use password instead →', 'wp-powersuite' ); ?></a></p>
				<?php endif; ?>
				<?php if ( ! empty( $s['enable_code_login'] ) ) : ?>
					<p class="wpps-magic-login__footer-nav"><a href="<?php echo esc_url( add_query_arg( 'action', self::CODE_ACTION, wp_login_url() ) ); ?>"><?php esc_html_e( 'Enter one-time code →', 'wp-powersuite' ); ?></a></p>
				<?php endif; ?>
			</div>
		</div>
		<?php
		return (string) ob_get_clean();
	}

	public function renderLoginFormMagicOption(): void {
		if ( empty( $this->getSettings()['show_on_login'] ) || ! $this->isDefaultLoginScreen() ) {
			return;
		}
		$s = $this->getSettings();
		?>
		<div id="wpps-magic-login-extras" class="wpps-magic-login-extras">
			<div id="wpps-magic-link-intro" class="wpps-magic-link-intro">
				<div class="wpps-magic-or-wrap" role="presentation">
					<span class="wpps-magic-or-line" aria-hidden="true"></span>
					<span class="wpps-magic-or-label"><?php echo esc_html( _x( 'OR', 'Separator between standard login and magic login on wp-login.php', 'wp-powersuite' ) ); ?></span>
					<span class="wpps-magic-or-line" aria-hidden="true"></span>
				</div>
				<input type="button" id="wpps-magic-link-btn" class="button button-primary button-large wpps-magic-link-cta" value="<?php echo esc_attr( __( 'Email me a login link', 'wp-powersuite' ) ); ?>" />
			</div>
			<div id="wpps-magic-email-step" class="wpps-magic-email-step" hidden>
				<p class="wpps-magic-email-lead"><?php esc_html_e( 'Enter your email address and we will send you a sign-in link.', 'wp-powersuite' ); ?></p>
				<p>
					<label for="wpps-magic-email-input"><?php esc_html_e( 'Email address', 'wp-powersuite' ); ?></label>
					<input type="email" id="wpps-magic-email-input" class="input" name="wpps_ml_magic_email" autocomplete="email" value="" />
				</p>
				<?php if ( ! empty( $s['enable_honeypot'] ) ) : ?>
					<label class="wpps-magic-login__hp" aria-hidden="true"><?php esc_html_e( 'Company', 'wp-powersuite' ); ?>
						<input type="text" id="wpps-magic-hp" name="<?php echo esc_attr( self::HONEYPOT_FIELD ); ?>" value="" tabindex="-1" autocomplete="off" />
					</label>
				<?php endif; ?>
				<div id="wpps-magic-captcha-wrap" aria-live="polite"></div>
				<p class="wpps-magic-email-actions">
					<input type="button" id="wpps-magic-send-btn" class="button button-primary button-large" value="<?php echo esc_attr( __( 'Send link', 'wp-powersuite' ) ); ?>" />
					<input type="button" id="wpps-magic-back-btn" class="button button-large" value="<?php echo esc_attr( __( 'Back', 'wp-powersuite' ) ); ?>" />
				</p>
				<?php if ( ! empty( $s['enable_code_login'] ) ) : ?>
					<div class="wpps-magic-or-wrap wpps-magic-or-wrap--compact" role="presentation">
						<span class="wpps-magic-or-line" aria-hidden="true"></span>
						<span class="wpps-magic-or-label"><?php echo esc_html( _x( 'OR', 'Separator between magic link and one-time code on wp-login.php', 'wp-powersuite' ) ); ?></span>
						<span class="wpps-magic-or-line" aria-hidden="true"></span>
					</div>
					<a href="<?php echo esc_url( add_query_arg( 'action', self::CODE_ACTION, wp_login_url() ) ); ?>" class="button button-primary button-large wpps-magic-link-cta wpps-magic-code-entry-btn"><?php esc_html_e( 'Enter one-time code', 'wp-powersuite' ); ?></a>
				<?php endif; ?>
			</div>
			<div id="wpps-magic-result" class="wpps-magic-result" hidden role="status"></div>
		</div>
		<?php
	}

	/**
	 * Move magic login block after Log In and reveal it once the form is complete.
	 */
	public function finalizeLoginFormMagicOption(): void {
		if ( empty( $this->getSettings()['show_on_login'] ) || ! $this->isDefaultLoginScreen() ) {
			return;
		}
		?>
		<script>
		(function () {
			var form = document.getElementById('loginform');
			var extras = document.getElementById('wpps-magic-login-extras');
			if (!form || !extras) {
				return;
			}
			var submit = form.querySelector('#wp-submit');
			var submitP = submit ? submit.closest('p.submit') : null;
			if (submitP && submitP.nextSibling !== extras) {
				form.insertBefore(extras, submitP.nextSibling);
			}
			extras.classList.add('wpps-magic-login-extras--placed');
		})();
		</script>
		<?php
	}

	public function renderCodeEntryScreen(): void {
		if ( empty( $this->getSettings()['enable_code_login'] ) ) {
			wp_die( esc_html__( 'One-time code login is disabled.', 'wp-powersuite' ), '', [ 'response' => 403 ] );
		}
		$cs    = isset( $_GET['cs'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['cs'] ) ) : '';
		$uid   = 0;
		$redir = isset( $_GET['redirect_to'] ) ? wp_validate_redirect( esc_url_raw( wp_unslash( (string) $_GET['redirect_to'] ) ), '' ) : '';
		$sess  = $cs !== '' ? $this->resolveCodeSession( $cs ) : null;
		if ( $sess ) {
			$uid   = $sess['uid'];
			$redir = $redir !== '' ? $redir : $sess['redirect'];
		}
		$err = isset( $_GET['error'] );
		$this->sendSensitiveLoginHeaders();
		login_header( __( 'Enter login code', 'wp-powersuite' ), '' );
		if ( $err ) {
			echo '<p class="message" style="border-left-color:#d63638;">' . esc_html( $this->errorMessage( 'code_expired' ) ) . '</p>';
		}
		if ( $cs === '' || ! $sess || $uid < 1 ) {
			echo '<p class="message">' . esc_html__( 'Open the code entry link from your login email to enter your one-time code.', 'wp-powersuite' ) . '</p>';
			echo '<p style="max-width:360px;margin:1rem auto;text-align:center;"><a href="' . esc_url( wp_login_url() ) . '">&larr; ' . esc_html__( 'Back to login', 'wp-powersuite' ) . '</a></p>';
			login_footer();
			exit;
		}
		$action_url = add_query_arg( 'action', self::CODE_ACTION, wp_login_url() );
		$len        = $this->loginCodeLength();
		?>
		<form method="post" action="<?php echo esc_url( $action_url ); ?>" class="wpps-magic-code-form" style="max-width:360px;margin:0 auto;">
			<?php wp_nonce_field( self::NONCE_CODE, 'wpps_code_nonce' ); ?>
			<input type="hidden" name="wpps_ml_cs" value="<?php echo esc_attr( $cs ); ?>" />
			<input type="hidden" name="redirect_to" value="<?php echo esc_attr( $redir ); ?>" />
			<p class="wpps-magic-code-field">
				<label for="wpps_ml_code_input"><?php esc_html_e( 'One-time code', 'wp-powersuite' ); ?></label>
				<input name="login_code" id="wpps_ml_code_input" type="text" inputmode="numeric" pattern="[0-9]*" maxlength="<?php echo esc_attr( (string) $len ); ?>" autocomplete="one-time-code" class="input" required />
			</p>
			<?php $this->renderCaptchaFieldMarkup( 'magic_code_' . $cs ); ?>
			<p class="submit"><button type="submit" class="button button-primary"><?php esc_html_e( 'Sign in', 'wp-powersuite' ); ?></button></p>
		</form>
		<p class="wpps-magic-code-back" style="max-width:360px;margin:1rem auto 0;text-align:center;">
			<a href="<?php echo esc_url( wp_login_url() ); ?>">&larr; <?php esc_html_e( 'Back to login', 'wp-powersuite' ); ?></a>
		</p>
		<?php
		login_footer();
		exit;
	}

	public function maybeRenderWooCommerceForm(): void {
		$s = $this->getSettings();
		if ( empty( $s['woo_login_form'] ) || ! class_exists( 'WooCommerce' ) ) {
			return;
		}
		echo '<div class="wpps-magic-login-woo-wrap">';
		echo $this->renderShortcode( [] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		echo '</div>';
	}

	public function filterUsersColumns( array $cols ): array {
		$cols['wpps_magic_login'] = __( 'Magic Login', 'wp-powersuite' );
		return $cols;
	}

	public function renderUsersColumn( string $value, string $column, int $user_id ): string {
		if ( 'wpps_magic_login' !== $column ) {
			return $value;
		}
		$n = $this->repo()->getUserLoginCount( $user_id );
		if ( $n < 1 ) {
			return '<span style="color:#9ca3af;">—</span>';
		}
		$last = $this->repo()->getUserLastUsed( $user_id );
		$ago  = $last ? human_time_diff( strtotime( (string) $last . ' UTC' ) ) : '—';
		return sprintf(
			'<span style="color:#16a34a;">%s</span><br><span style="color:#64748b;font-size:12px;">%s %s</span>',
			esc_html( sprintf( /* translators: %d login count */ _n( '%d login', '%d logins', $n, 'wp-powersuite' ), $n ) ),
			esc_html( $ago ),
			esc_html__( 'ago', 'wp-powersuite' )
		);
	}

	private function sendCodeError( string $message, int $uid, string $redir, string $cs = '' ): void {
		if ( $this->isXHR() ) {
			wp_send_json_error( [ 'message' => $message ] );
		}
		$args = [
			'action' => self::CODE_ACTION,
			'error'  => '1',
		];
		if ( $cs !== '' ) {
			$args['cs'] = $cs;
		} elseif ( $uid > 0 ) {
			$args['uid'] = $uid;
		}
		if ( isset( $_POST['wpps_ml_email'] ) ) {
			unset( $_POST['wpps_ml_email'] );
		}
		if ( $redir !== '' ) {
			$args['redirect_to'] = $redir;
		}
		wp_safe_redirect( add_query_arg( $args, wp_login_url() ) );
		exit;
	}
}
