<?php
/**
 * WPMR_Browser_Driven_Scanner_Operations_UI
 *
 * Generated during Phase 2 restructuring
 * This trait contains methods extracted from the original monolithic wpmr.php
 *
 * @package WP_Malware_Removal
 */

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

trait WPMR_Browser_Driven_Scanner_Operations_UI {

	/**
	 * Render the operations overlay markup in the admin footer (WPMR screen only).
	 *
	 * Hooked via `add_action( 'admin_footer', array( $this, 'operations_overlay' ) )`.
	 *
	 * The overlay is used by the inline admin JavaScript to present a consistent UX while
	 * long-running operations execute (e.g., registration, definition updates, repairs).
	 *
	 * Behavior:
	 * - Checks the current admin screen and only outputs markup on the plugin page
	 *   (screen id matches `toplevel_page_wpmr`, including multisite variants).
	 *
	 * Side effects:
	 * - Echoes HTML directly.
	 *
	 * Suggested rename: `render_operations_overlay_markup()` or `print_operations_overlay_html()`.
	 *
	 * @return void
	 */
	function operations_overlay() {
		$screen = get_current_screen();
		if ( preg_match( '/toplevel_page_wpmr/', $screen->id ) ) { // use preg_match to allow multisite
			?>
			<div id="wpmr_operation_overlay" style="display:none">
			<div class="wpmr_overlay_content">
				<div id="wpmr_overlay_message">Working&nbsp;…</div>
				<div class="wpmr_progress_bar">
					<div class="wpmr_progress_indicator"></div>
				</div>
			</div>
		</div>
			<?php
		}
	}

	/**
	 * Print the plugin's inline admin JavaScript in the admin footer (WPMR screen only).
	 *
	 * Hooked via `add_action( 'admin_footer', array( $this, 'scripts' ) )`.
	 *
	 * This method emits a large inline `<script>` block that powers most of the dashboard UX:
	 * scan initiation, progress UI updates, file inspection/repair controls, results copy-to-clipboard,
	 * and multiple
	 * authenticated `admin-ajax.php` requests with per-action nonces.
	 *
	 * Behavior:
	 * - Checks the current admin screen and only outputs the script on the plugin page
	 *   (screen id matches `toplevel_page_wpmr`, including multisite variants).
	 *
	 * Dependencies:
	 * - WordPress admin context (`ajaxurl`, `postboxes`, `get_current_screen()`).
	 * - Instance methods used to inject state/nonces (e.g., `is_registered()`, `is_advanced_edition()`).
	 *
	 * Side effects:
	 * - Echoes HTML/JS directly.
	 *
	 * Suggested rename: `print_admin_footer_inline_js()` or `print_wpmr_dashboard_inline_js()`.
	 *
	 * @return void
	 */
	function scripts() {
		$screen = get_current_screen();
		if ( preg_match( '/toplevel_page_wpmr/', $screen->id ) ) { // use preg_match to allow multisite
			?>
			<script type="text/javascript">
			//<![CDATA[

			wpmrOverlayTimeout = 0;
			wpmr_cta_context = null;

			// Functions to show and hide overlay
			function showOverlay(message) {
				$ = jQuery.noConflict();

				if (!message) {
					message = '';
				}
				
				$('#wpmr_overlay_message').text(message);
				$('#wpmr_operation_overlay').fadeIn(200);
				
				// Start progress animation
				if (typeof wpmrOverlayTimeout !== 'undefined' && wpmrOverlayTimeout !== null && wpmrOverlayTimeout > 0) {
					clearTimeout(wpmrOverlayTimeout);
				} else {
					console.log('Error clearing timeout'+wpmrOverlayTimeout);
				}
				
				// If operation takes too long, add a timeout message
				wpmrOverlayTimeout = setTimeout(function() {
					$('#wpmr_overlay_message').html(message + '<br><small>(Taking longer than expected, please be patient\u00A0…)</small>');
				}, 5000); // Show message after 5 seconds (The default WordPress remote request timeout... that's what users expect UX-wise)
			}

			function hideOverlay() {
				$ = jQuery.noConflict();
				$('#wpmr_operation_overlay').fadeOut(200);
				if (wpmrOverlayTimeout) {
					clearTimeout(wpmrOverlayTimeout);
					wpmrOverlayTimeout = 0;
				}
			}

			function millisToMinutesAndSeconds(millis) {
				var minutes = Math.floor(millis / 60000);
				var seconds = ((millis % 60000) / 1000).toFixed(0);
				return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
			}

			function wpmr_update_file_only_operations_visibility() {
				var $ = jQuery.noConflict();
				var inspectedFile = ($('#wpmr_inspect_file').attr('data-file') || '').trim();
				if (inspectedFile) {
					$('.wpmr-file-only-op').show();
				} else {
					$('.wpmr-file-only-op').hide();
				}
			}

			function file_inspect_handler(event) {
				var $ = jQuery;
				if ( event ) {
					event.preventDefault();
				}
				$('#wpmr_inspect_file').attr('data-db-table', '');
				$('#wpmr_inspect_file').attr('data-db-id', '');
				$('#wpmr_inspect_file').attr('data-db-rowkey', '');
				var $target = $(this);
				var dataAttr = $target.attr('data-file');
				var file = dataAttr ? dataAttr.trim() : '';

				if ( ! file && $target.hasClass('inspect_file_debug') ) {
					file = ($target.val() || '').trim();
					if ( file ) {
						$target.attr('data-file', file);
					}
				}
				$('#wpmr_inspect_file').scrollTop(0);
				$('#wpmr_inspect_file').val('');
				wpmr_update_file_only_operations_visibility();
				if (file) {
					wpmr_inspect_file = {
						wpmr_inspect_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_inspect_file' ) ); ?>',
						action: "wpmr_inspect_file",
						file: btoa(file)
					};
					$('#wpmr_inspect_box').removeClass('hidden');
					$.ajax({
						url: ajaxurl,
						method: 'GET',
						data: wpmr_inspect_file,
						complete: function (jqXHR, textStatus) {
							$('#wpmr_inspect_box').removeClass('closed');
							$('#inspect_file_path').html('<strong>File:</strong> <code>' + file + '</code>');

							if (textStatus == 'success') {
								if (jqXHR.hasOwnProperty('responseJSON')) { // proper json will have success and data vars.
									$('#wpmr_inspect_file').val(jqXHR.responseJSON.data);
									$('#wpmr_inspect_file').attr('data-file', file);
								}
								else {
									$('#wpmr_inspect_file').val('Invalid response from server!');
								}
							}
							else { // ajax failed
								$('#wpmr_inspect_file').val('Request failed!');
								console.dir(jqXHR);
							}

							wpmr_update_file_only_operations_visibility();

							$('html,body').animate({
								scrollTop: $('#wpmr_inspect_box').offset().top
							}, 'slow');
						},
					});
				}
			}

			function db_inspect_handler(event) {
				var $ = jQuery;
				if ( event ) {
					event.preventDefault();
				}
				var $target = $(this);
				var table = ($target.attr('data-table') || '').trim();
				var id = parseInt(($target.attr('data-id') || '0').trim(), 10) || 0;

				$('#wpmr_inspect_file').scrollTop(0);
				$('#wpmr_inspect_file').val('');
				$('#wpmr_inspect_file').attr('data-file', '');
				$('#wpmr_inspect_file').attr('data-db-table', '');
				$('#wpmr_inspect_file').attr('data-db-id', '');
				$('#wpmr_inspect_file').attr('data-db-rowkey', '');
				wpmr_update_file_only_operations_visibility();

				if (table && id > 0) {
					wpmr_inspect_db = {
						wpmr_inspect_db_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_inspect_db_record' ) ); ?>',
						action: 'wpmr_inspect_db_record',
						table: table,
						id: id
					};
					$('#wpmr_inspect_box').removeClass('hidden');
					$.ajax({
						url: ajaxurl,
						method: 'GET',
						data: wpmr_inspect_db,
						complete: function (jqXHR, textStatus) {
							$('#wpmr_inspect_box').removeClass('closed');
							// Build header safely (no HTML injection).
							var $hdr = $('#inspect_file_path');
							$hdr.empty();
							$hdr
								.append($('<strong/>').text('DB:'))
								.append(' ')
								.append($('<code/>').text(table))
								.append(' ')
								.append($('<strong/>').text('ID:'))
								.append(' ')
								.append($('<code/>').text(String(id)));

							if (textStatus == 'success') {
								if (jqXHR.hasOwnProperty('responseJSON')) {
									if (jqXHR.responseJSON.success) {
										var payload = jqXHR.responseJSON.data;
										var content = '';
										var identifierLabel = '';
										var identifierValue = '';

										if (typeof payload === 'string') {
											content = payload;
										} else if (typeof payload === 'object' && payload !== null) {
											content = (payload.hasOwnProperty('content') ? payload.content : '');
											identifierLabel = (payload.hasOwnProperty('identifier_label') ? payload.identifier_label : '');
											identifierValue = (payload.hasOwnProperty('identifier_value') ? payload.identifier_value : '');
										}

										$('#wpmr_inspect_file').val(content);
										$('#wpmr_inspect_file').attr('data-db-table', table);
										$('#wpmr_inspect_file').attr('data-db-id', id);
										$('#wpmr_inspect_file').attr('data-db-rowkey', identifierValue || '');

										if (identifierLabel && identifierValue) {
											$hdr
												.append(' ')
												.append($('<strong/>').text(identifierLabel + ':'))
												.append(' ')
												.append($('<code/>').text(identifierValue));
										}
									} else {
										var msg = jqXHR.responseJSON.data;
										if (typeof msg === 'object' && msg !== null && msg.hasOwnProperty('message')) {
											msg = msg.message;
										}
										$('#wpmr_inspect_file').val(msg);
									}
								} else {
									$('#wpmr_inspect_file').val('Invalid response from server!');
								}
							}
							else {
								$('#wpmr_inspect_file').val('Request failed!');
								console.dir(jqXHR);
							}

							$('html,body').animate({
								scrollTop: $('#wpmr_inspect_box').offset().top
							}, 'slow');
							wpmr_update_file_only_operations_visibility();
						},
					});
				}
			}

			function handle_whitelist_labels($) {
				// Only consider rendered whitelist entry rows, not incidental elements.
				var whitelist = $('#whitelist p[data-file-wrap]');
				if (whitelist.length) {
					$('#whitelist-present-placeholder').show();
					$('#whitelist-absent-placeholder').hide();
				}
				else {
					$('#whitelist-present-placeholder').hide();
					$('#whitelist-absent-placeholder').show();
				}
			}

			function handle_db_whitelist_labels($) {
				// Only consider rendered DB whitelist entry rows, not incidental elements.
				var whitelist = $('#db_whitelist p[data-db-wrap]');
				if (whitelist.length) {
					$('#db-whitelist-present-placeholder').show();
					$('#db-whitelist-absent-placeholder').hide();
				}
				else {
					$('#db-whitelist-present-placeholder').hide();
					$('#db-whitelist-absent-placeholder').show();
				}
			}

			function flash_file_op($) {
				$('#file_op_status').fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500);
			}

			function highlight_results($) {
				setTimeout(function () {
					$('html,body').animate({
						scrollTop: $('#wpmr_cta_wrap').offset().top - 200
					}, 'slow');
				}, 1000);
			}

			function wpmr_get_license_cta(action) {
				return 'A valid license is required to access this API service. <a href="<?php echo esc_js( $this->wpmr_url( 'p=116', 'scan-result-cta', '' ) ); ?>" target="_blank" rel="noopener">Click here to get a license.</a>';
			}

			function wpmr_build_decision_panel(opts) {
				const totalSevere = parseInt(opts.severe, 10) || 0;
				const dbInfectionCount = parseInt(opts.dbInfectionCount, 10) || 0;
				
				// Use explicit file counts passed from the scanner
				const severeFileCount = parseInt(opts.severeFileCount, 10) || 0;
				const suspiciousFileCount = parseInt(opts.suspiciousFileCount, 10) || 0;
				
				const hasDbInfections = dbInfectionCount > 0;
				const hasFileInfections = (severeFileCount > 0) || (suspiciousFileCount > 0);
				const licenseActive = (typeof wpmr_is_pro !== 'undefined') && parseInt(wpmr_is_pro, 10) === 1;
				const context = (opts.context || (totalSevere > 0 ? 'severe' : 'suspicious')).toString().toLowerCase();
				const isSuspiciousContext = context === 'suspicious';

				const summaryParts = [];
				if (hasDbInfections) {
					summaryParts.push(dbInfectionCount + ' database infection' + (dbInfectionCount === 1 ? '' : 's'));
				}
				const userSevereCount = parseInt(opts.userSevereCount, 10) || 0;
				const userSuspiciousCount = parseInt(opts.userSuspiciousCount, 10) || 0;
				if (userSevereCount > 0) {
					summaryParts.push(userSevereCount + ' severe user account flag' + (userSevereCount === 1 ? '' : 's'));
				}
				if (userSuspiciousCount > 0) {
					summaryParts.push(userSuspiciousCount + ' suspicious user account flag' + (userSuspiciousCount === 1 ? '' : 's'));
				}
				if (severeFileCount > 0) {
					summaryParts.push(severeFileCount + ' severe file infection' + (severeFileCount === 1 ? '' : 's'));
				}
				if (suspiciousFileCount > 0) {
					summaryParts.push(suspiciousFileCount + ' suspicious file incident' + (suspiciousFileCount === 1 ? '' : 's'));
				}
				
				const issueSummary = summaryParts.length > 0 ? summaryParts.join(' plus ') : 'issues in this scan';
				
				const panelHeadline = '<span class="brandname">Malcure</span> Found ' + issueSummary;
				// Recommendation policy:
				// - DB infections: DFY (analyst-only)
				// - Suspicious-only: DIY (inspection-first), especially when Advanced Edition is active
				// - Severe file infections: DIY (repair tooling applies)
				// - Otherwise: DFY
				const recommendedPath = hasDbInfections
					? 'dfy'
					: (severeFileCount > 0
						? 'diy'
						: (suspiciousFileCount > 0 ? 'diy' : 'dfy'));
				const recommendationName = recommendedPath === 'dfy' ? 'Expert Malware Removal service' : 'Advanced Edition';
				const dfyLink = '<?php echo esc_js( $this->wpmr_url( 'p=107', 'scan-result-cta', 'dfy' ) ); ?>';
				const diyLink = '<?php echo esc_js( $this->wpmr_url( 'p=116', 'scan-result-cta', 'diy' ) ); ?>';
				const dfyPrimaryLabel = 'Fix My Site Now &rarr;';
				const dfySupport = hasDbInfections
					? 'Database infections require analyst-only SQL cleanup, blacklist assistance, and validation across the stack.'
					: 'Includes complete malware removal, security hardening, and blacklist removal assistance.';
				const diySupport = licenseActive
					? (isSuspiciousContext
						? 'Advanced Edition is active here—use the tools below to inspect flagged items. For suspicious-only indicators, confirm before making repairs.'
						: 'Advanced Edition is active here—run repairs inside WordPress, trigger wp malcure automation, or sync signatures headlessly.')
					: (isSuspiciousContext
						? 'Advanced Edition helps you inspect suspicious indicators and validate files. Use it to investigate before changing anything.'
						: 'Advanced Edition unlocks repair controls for these files inside WordPress, single-command WP-CLI automation, and flexible multi-site licensing options.');
				const supportingCopy = hasDbInfections
					? 'Database compromises are complex and are not safe for auto-repair. Engage Malcure analysts to remediate the SQL payloads and verify the site end-to-end.'
					: (isSuspiciousContext
						? (licenseActive
							? 'Advanced Edition is active—inspect the flagged items below. If you want a second set of eyes, Malcure experts can validate findings and handle cleanup end-to-end.'
							: 'Suspicious indicators detected—investigate before making changes. Advanced Edition provides inspection tools, or you can engage Malcure experts for analyst-led validation and cleanup.')
						: (recommendedPath === 'dfy'
							? 'Immediate expert attention is recommended. Review the findings with Malcure\'s service team.'
							: (licenseActive
								? 'Advanced Edition is active on this site. Launch guided repairs now or hand things off to Malcure if you prefer white-glove service.'
								: 'Confirmed infections detected. Upgrade now to unlock guided repair for each flagged file inside WordPress or headless via WP-CLI.')));
				const supportingLabel = (hasDbInfections || recommendedPath === 'dfy')
					? dfyPrimaryLabel
					: (licenseActive ? 'Open Advanced Controls &rarr;' : 'Get Advanced Edition &rarr;');
				const supportingLink = hasDbInfections
					? dfyLink
					: (recommendedPath === 'dfy'
						? dfyLink
						: (licenseActive ? '#file_results' : diyLink));
				const supportingTarget = supportingLink.charAt(0) === '#' ? '_self' : '_blank';
				const supportingRel = supportingTarget === '_blank' ? ' rel="noopener"' : '';
				const hideSupportingCta = (recommendedPath === 'diy') || (licenseActive && supportingLink === '#file_results');

				const columnTemplate = (column) => {
					const badgeText = column.badgeLabel
						? column.badgeLabel
						: (column.recommended ? 'Recommended' : '');
					const badge = badgeText
						? '<span class="wpmr_recommended_badge">' + badgeText + '</span>'
						: '';
					const targetAttr = column.target || '_blank';
					const relAttr = targetAttr === '_blank' ? ' rel="noopener"' : '';
					const ctaMarkup = column.cta
						? `
							<p class="wpmr_no_copy wpmr_cta_wrapper">
								<a class="malcure-button-primary wpmr_no_copy" href="${column.link}" target="${targetAttr}"${relAttr}>${column.cta}</a>
							</p>
						`
						: '';
					return `
						<div class="wpmr_decision_col ${column.slug}">
							${badge}
							<h4>${column.title}</h4>
							<div class="wpmr_price">${column.price}</div>
							<p>${column.summary}</p>
							${ctaMarkup}
							<p class="wpmr_support_copy">${column.support}</p>
						</div>
					`;
				};

				const dfyColumn = columnTemplate({
					slug: 'dfy',
					title: 'Expert Malware Removal',
					price: 'Guaranteed Clean Site',
					summary: 'Our security experts will manually clean your site, remove backdoors, and blacklist warnings. 100% Guaranteed.',
					link: dfyLink,
					cta: dfyPrimaryLabel,
					support: dfySupport,
					recommended: (recommendedPath === 'dfy')
				});

				const diyExtraBadge = (licenseActive && !hasDbInfections && recommendedPath !== 'diy') ? 'Recommended' : '';
				const diyColumn = columnTemplate({
					slug: 'diy',
					title: 'Advanced Edition (DIY)',
					price: licenseActive ? 'Advanced Edition Active' : 'Unlock Repair Tools',
					summary: licenseActive
						? (isSuspiciousContext
							? 'Advanced Edition is active—inspect these suspicious findings before you change anything.'
							: 'Advanced Edition is active—launch dashboard repairs or automate via wp malcure commands.')
						: (isSuspiciousContext
							? 'Inspect suspicious findings with Advanced Edition before making changes.'
							: 'Get instant access to 1-click file repair, WP-CLI automation, and advanced malware definitions.'),
					link: licenseActive ? '#file_results' : diyLink,
					target: licenseActive ? '_self' : '_blank',
					cta: (licenseActive && recommendedPath === 'diy') ? 'Inspect Findings &rarr;' : (licenseActive ? '' : 'Get Advanced Edition &rarr;'),
					support: diySupport,
					recommended: (recommendedPath === 'diy'),
					badgeLabel: diyExtraBadge
				});

				const ctaColumns = hasDbInfections ? [dfyColumn] : [dfyColumn, diyColumn];
				const columnsClass = ctaColumns.length > 1 ? 'two-column' : 'single-column';
				const columnMarkup = ctaColumns.join('');
				const panelIntro = hasDbInfections
					? 'Database infections require Malcure\'s forensics team. File repair tooling is limited to Advanced Edition once the database is clean.'
					: (licenseActive ? 'Advanced Edition tools are live on this site. Use them now or delegate cleanup to Malcure experts.' : 'Your site is at risk. Choose a cleanup option below to secure your site immediately.');
				const manualSteps = [
					'Take full backups of both the site files (SFTP/SSH) and the database via WP-CLI or phpMyAdmin before editing anything.',
					'List every flagged file and database table from the scan report so nothing gets skipped during remediation.'
				];
				if (hasDbInfections) {
					manualSteps.push(
						'Export a dedicated database dump you can roll back to instantly if a change goes sideways.',
						'Open phpMyAdmin (or another SQL client), navigate to each infected table, and review the suspicious rows/fields for encoded payloads.',
						'Unserialize values when required, strip malicious fragments, reserialize, and save the cleaned record back to the table.'
					);
				}
				if (hasFileInfections) {
					manualSteps.push(
						'Pull every infected file over SFTP/SSH (or your host\'s file manager) and keep pristine copies in a local working folder.',
						'Compare each file against the matching WordPress/plugin/theme source for the same version, remove injected code, and upload the repaired file with correct permissions.'
					);
				}
				manualSteps.push(
					'Flush caches/CDNs, then run a fresh Malcure scan to confirm both file and database artifacts are removed.',
					'Shuffle WordPress salts (`wp config shuffle-salts` or via wp-config.php) and reset all user passwords to kick out any lingering backdoors.'
				);
				const manualIntro = hasDbInfections
					? 'Prefer to fix things yourself? Work through the database payloads and the infected files carefully before bringing the site back online.'
					: 'Prefer to remediate things manually? (Risky) Follow these steps after taking full backups of the site and database.';
				const manualNote = licenseActive
					? 'Advanced Edition is already active, so you can also run the guided repair controls below once you finish backing up. Manual work stays optional if you want to inspect everything yourself.'
					: '<strong>Warning:</strong> Manual cleanup requires technical expertise. Incorrectly editing files can break your site. We strongly recommend the ' + recommendationName + '.';
				const manualList = manualSteps.map((step) => '<li>' + step + '</li>').join('');
				const manualMarkup = `
					<div class="wpmr_manual_cta">
						<h4>Manual Cleanup Option&nbsp;&rarr;</h4>
						<p>${manualIntro}</p>
						<ol>${manualList}</ol>
						<p class="wpmr_manual_note">${manualNote}</p>
					</div>
				`;
				const supportingMarkup = hideSupportingCta ? '' : `
					<div class="wpmr_supporting_cta">
						<p>${supportingCopy}</p>
						<p class="wpmr_no_copy wpmr_cta_wrapper">
							<a class="malcure-button-primary wpmr_no_copy" href="${supportingLink}" target="${supportingTarget}"${supportingRel}>${supportingLabel}</a>
						</p>
					</div>
				`;

				return `
					<div id="wpmr_decision_panel" class="wpmr_decision_panel">
						<h3 class="mc_center heading">${panelHeadline}.</h3>
						<p class="mc_center wpmr_panel_intro">${panelIntro}</p>
						${manualMarkup}
						<div class="wpmr_decision_cols ${columnsClass}">${columnMarkup}</div>
						${supportingMarkup}
					</div>
				`;
			}

			function show_cta_severe($) {
				$('#wpmr_cta_wrap').show();
				const hadPanel = $('#wpmr_decision_panel').length > 0;
				$('#percent').addClass('severe');
				$('#service_cta').html(wpmr_build_decision_panel({
					context: 'severe',
					severe: severe,
					suspicious: suspicious,
					dbInfectionCount: db_infection_count,
					severeFileCount: severeFileCount,
					suspiciousFileCount: suspiciousFileCount,
					userSevereCount: userSevereCount,
					userSuspiciousCount: userSuspiciousCount
				}));
				wpmr_cta_context = 'severe';
				if (!highlight_cta && !hadPanel) {
					highlight_results($);
					highlight_cta = 1;
				}
			}

			function show_cta_suspicious($) {
				if (typeof wpmr_cta_context !== 'undefined' && wpmr_cta_context === 'severe') {
					return;
				}
				$('#wpmr_cta_wrap').show();
				console.log('Suspicious count: ' + suspicious);
				const hadPanel = $('#wpmr_decision_panel').length > 0;
				$('#percent').addClass('suspicious');
				$('#service_cta').html(wpmr_build_decision_panel({
					context: 'suspicious',
					severe: severe,
					suspicious: suspicious,
					dbInfectionCount: db_infection_count,
					severeFileCount: severeFileCount,
					suspiciousFileCount: suspiciousFileCount,
					userSevereCount: userSevereCount,
					userSuspiciousCount: userSuspiciousCount
				}));
				wpmr_cta_context = 'suspicious';
				if (!highlight_cta && !hadPanel) {
					highlight_results($);
					highlight_cta = 1;
				}
			}

			function is_full_scan() {
				return full_scan;
			}

			function show_cta_voila($) {
				$('#wpmr_cta_wrap').show();
				wpmr_cta_context = 'clean';
				if ($('#cta_logo_contribute').length == 0) {
					msgvoila = '';
					msg = '';
					if (!is_full_scan()) {
						msgvoila = 'You selected to skip some scans. Unable to detect.';
						clsvoila = 'unclear';
						msg = '<p>Malcure couldn\'t detect malware.</p><p><strong>Please do a complete scan.</strong></p>';
					}
					else {
						msgvoila = 'No Malware Found!';
						clsvoila = 'clear';
						msg = '<p>Congratulations! <strong>Malcure</strong> didn\'t detect any malware.</p><p><strong>Love this plugin?</strong></p><p><a class="cta_btn" target="_blank" href="https://wordpress.org/support/plugin/wp-malware-removal/reviews/">Give Us A Rating&nbsp;&rarr;<span class="emoji">⭐⭐⭐⭐⭐</span></a></p>';
						d = new Date().toUTCString();
						v = '<div id="cta_logo_contribute"><h1 class="premium" style="font-weight:bold;color:hsl(120, 50%, 40%);">Your WordPress Site is Clean</h1><h2 style="font-weight: 500;color: hsl(0deg 0% 100%);font-variant: small-caps;background: hsl(120, 50%, 40%);display: table;width: auto;margin: 10px auto;padding: 4px 8px;">Scanned On: ' + d + '</h2><h1 style="font-weight: 400;font-size: 1.618em;">Share some love!</h1><h2><span class="brandname">Malcure</span> is Premium Security for Free!</h2><div class="love"><div class="column aligncenter"><p><a class="cta_btn" target="_blank" href="https://wordpress.org/support/plugin/wp-malware-removal/reviews/#new-post"><span class="rating">⭐⭐⭐⭐⭐</span> Rate this plugin&nbsp;→</a></p></div></div></div>';
						$('#service_cta').html(v);
						highlight_results($);
					}
				}
			}

			function clear_infection_stats($) {
				if (!is_full_scan()) {
					return;
				}
				wpmr_clear_infection_stats = {
					wpmr_clear_infection_stats_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_clear_infection_stats' ) ); ?>',
					action: "wpmr_clear_infection_stats",
				}
				$.ajax({
					url: ajaxurl,
					method: 'POST',
					data: wpmr_clear_infection_stats,
					complete: function (jqXHR, textStatus) {
						if (textStatus == 'success') {
							if (jqXHR.hasOwnProperty('responseJSON')) {
								if (jqXHR.responseJSON.hasOwnProperty('content') && jqXHR.responseJSON.content.length) {
									console.log('cleared');
								} else {
									if (jqXHR.responseJSON.hasOwnProperty('error')) {
										console.log('jqXHR.responseJSON.error');
									} else {
										console.log('No content in jqXHR.responseJSON');
									}
								}
							} else {
								console.log('Invalid response from server');
							}
						} else {
							console.log('Request failed.');
						}
						$("#wpmr-infected-alert").remove();
					},
				});
			}

			// what exactly does it do? Is it supposed to reset everything to 0 or should it set everything to complete?
			function reset_ui($) {
				$('#wpmr_batchsize').prop('disabled', false);
				

				log_scan_completed();
				wpmr_stop_marquee();
				$('#files_remaining').html('0');
				$('.gauge_c').removeClass('rotating');
				$('#logo').removeClass('running');
				$('#percent').removeClass('running');
				$('.engine_status').removeClass('blink');
				$('.engine_status').html('complete');
				$('#time_remaining').html('finished');
				console.log('total_files:' + total_files);
				console.log('timestamp record:' + record);
				wpmr_scanspeed = (total_files) / ((Math.floor((new Date()).getTime() / 1000) - record));
				$('#scan_speed').html((wpmr_scanspeed).toFixed(0) + '&nbsp;items / sec');
				$('#files_to_scan').html('none');
				//$('#service_cta').html('');
				setTimeout(async function () { $('#file_scroll').html(''); }, 1000);
				percent = 100;
				if (typeof (results) === "undefined") {
					results = {
						timeelapsed: 0
					};
				}
				if (!results.hasOwnProperty('timeelapsed')) {
					results.timeelapsed = 0;
				}
				if (!results.hasOwnProperty('iterationUpdated')) {
					results.iterationUpdated = 0;
				}
				$('#percent').html('<span class="percentage">' + parseFloat(percent).toFixed(0) + '%</span><div id="time_counter">' + msToTime(results.timeelapsed) + '</div>');
				$('.gauge_c').css('transform', 'rotate( ' + (.005 * percent) + 'turn)');

				if ((!suspicious && !severe)) {
					show_cta_voila($);
				}
				else {
					highlight_results($);
				}
				if (!severe) {
					clear_infection_stats($);
				}
				$('#file_results .blink').removeClass('blink');
				if (do_file_scan && !$('#file_records').length) {
					$('#file_results').html('<p>Nothing Detected.</p>');
				}

				wpmr_notify_audio('scan-complete');
				document.title = "✔ Malcure Scan Complete! " + wpmr_original_title;
				setTimeout(function () {
					document.title = wpmr_original_title;
				}, 5000);

				// $("#scan_control").val('Re-Scan');
				$("#scan_control").prop('disabled', false);
				$("#scan_control_deep").prop('disabled', false);
				$("#scan_control_deep").prop('value', 'Re-Initiate DeepScan™→');
				
				$("#wpmr_god").prop('disabled', false);

				$('.scan_control').removeClass('unused');
				severe = 0;
				suspicious = 0;
				severeFileCount = 0;
				suspiciousFileCount = 0;
				db_infection_count = 0;
				userSevereCount = 0;
				userSuspiciousCount = 0;
			}

			function wpmr_js_arr_encode($str) {
				return $str;
			}

			function wpmr_prompt_register($) {
				$('#wpmr_updates_box').addClass('prompt_register');
				fix_blur($);
			}

			function msToTime(duration) {

				seconds = Math.floor((duration / 1000) % 60),
					minutes = Math.floor((duration / (1000 * 60)) % 60),
					hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
				hours = (hours < 10) ? "0" + hours : hours;
				minutes = (minutes < 10) ? "0" + minutes : minutes;
				seconds = (seconds < 10) ? "0" + seconds : seconds;
				if (parseInt(hours)) {
					ret = hours + ":" + minutes + ":" + seconds;
					//console.log(ret);
					return ret;
				} else {
					ret = minutes + ":" + seconds;
					//console.log(ret);
					return ret;
				}

				return hours + ":" + minutes + ":" + seconds + "." + milliseconds;
			}

			function fix_blur($) {
				setTimeout(() => {
					if (jQuery('.prompt_register .inside').length) {
						matrix = jQuery('.prompt_register .inside').css('transform');
						// console.log("before:" + matrix);
						matrix = matrix.replace(/\.\d+/gi, '');
						jQuery('.prompt_register .inside').css('transform', matrix);
						// console.log(matrix);
					}
				}, 2000);
				//$('.prompt_register .inside').css('transform'))
			}

			function wpmr_wheel() {
				var rangeInput = document.getElementById("wpmr_batchsize");
				rangeInput.addEventListener("wheel", function (event) {
					// Determine the scroll direction (positive for scroll down, negative for scroll up)
					var scrollDirection = event.deltaY;

					// Determine the change in value based on the scroll direction
					var valueChange = scrollDirection > 0 ? 1 : -1;

					// Update the value of the range input, ensuring it stays within the min and max bounds
					rangeInput.value = Math.min(rangeInput.max, Math.max(rangeInput.min, parseInt(rangeInput.value) + valueChange));
					// Create and dispatch the change event
					var changeEvent = new Event('change', {
						'bubbles': true,
						'cancelable': true
					});
					rangeInput.dispatchEvent(changeEvent);
					// Prevent the default scroll behavior (optional, depending on your needs)
					event.preventDefault();
				}
				)
			}

			fix_blur($);

			registered = <?php echo $this->is_registered() ? 1 : 0; ?>;
			wpmr_is_pro = <?php echo $this->is_advanced_edition() ? 1 : 0; ?>;
			file_scan_type = '';
		severe = 0;
			suspicious = 0;
			severeFileCount = 0;
			suspiciousFileCount = 0;
			db_infection_count = 0;
			userSevereCount = 0;
			userSuspiciousCount = 0;

			// Initial setup
			const wpmr_original_title = document.title;
			let wpmr_base_message = "Malcure @";
			let wpmr_scroll_message = wpmr_base_message;
			let wpmr_current_percentage = 0;
			let wpmr_interval;
			let wpmr_scroll_index = 0; // Keep track of the scroll position

			// Function to update and scroll the title
			function wpmr_scroll_and_update_title() {
				// Prepare the title with the current percentage at the end
				let titleWithPercentage = `${wpmr_scroll_message} ${wpmr_current_percentage}%… `;
				// Ensure the scroll index is within bounds
				wpmr_scroll_index = wpmr_scroll_index % titleWithPercentage.length;
				// Create the scrolling effect
				document.title = titleWithPercentage.substring(wpmr_scroll_index) + titleWithPercentage.substring(0, wpmr_scroll_index);
				// Move to the next scroll position
				wpmr_scroll_index++;
			}

			// Function to update the percentage
			function wpmr_update_percentage(newPercentage) {
				wpmr_current_percentage = newPercentage;
				// No need to reset wpmr_scroll_message or wpmr_scroll_index here
			}

			// Function to start the marquee effect
			function wpmr_start_marquee() {
				if (wpmr_interval) clearInterval(wpmr_interval); // Clear any existing interval
				wpmr_interval = setInterval(wpmr_scroll_and_update_title, 200); // Adjust the speed as needed
			}

			// Function to stop the marquee and restore the original title
			function wpmr_stop_marquee() {
				clearInterval(wpmr_interval);
				document.title = wpmr_original_title;
			}

			function wpmr_show_message(message) {
				// wpmr_dir('Msg received' + message);
				jQuery('#wpmr_message_content').html(message);
				jQuery('#wpmr_messaging').css('bottom', '-' + (jQuery('#wpmr_messaging').height() + 10) + 'px').animate({
					"bottom": "10px",
					'opacity': '1'
				}, {
					duration: 300,
					complete: function () {
						jQuery('#wpmr_message_content').addClass('showing');
					}
				}).delay(1 * 10000).animate({
					"bottom": '-' + jQuery('#wpmr_messaging').height() + 'px',
					'opacity': '0'
				}, {
					duration: 300,
					complete: function () {
						jQuery('#wpmr_message_content').removeClass('showing');
					}
				});
			}
			
			jQuery(document).ready(function ($) { //wrapper
				handle_whitelist_labels($);
				handle_db_whitelist_labels($);
				wpmr_wheel();
				wpmr_update_file_only_operations_visibility();

				$('#scan_hint_value').html($('#wpmr_batchsize').val());

				$('#wpmr_message_control').click(function () {
					$('#wpmr_messaging').css('bottom', '-' + ($('#wpmr_messaging').height() + 10) + 'px').animate({ // without this line there's no control over closing
						"bottom": '-' + $('#wpmr_messaging').height() + 'px',
						'opacity': '0'
					});
				});

				$("#wpmr_register_cancel").click(function (e) {
					$('.prompt_register .inside').removeAttr('style');
				});

				$("#wpmr_register").click(function (e) {
					e.preventDefault();
					if (!document.querySelector('#wpmp_reg_form').reportValidity()) {
						return false;
					}
					showOverlay('Connecting to server\u00A0…');
					wpmr_web_register = {
						wpmr_web_register_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_web_register' ) ); ?>',
						action: "wpmr_web_register",
						user: {
							fn: $('#wpmr_fn').val(),
							ln: $('#wpmr_ln').val(),
							email: $('#wpmr_eml').val(),
							key: '<?php echo esc_js( md5( site_url() ) ); ?>'
						},
						notify_scan: $('#wpmr_scan_notify_registrant').is(':checked') ? 1 : 0,
						notify_pulse: $('#wpmr_pulse_notify_registrant').is(':checked') ? 1 : 0
					};

					$.ajax({
						url: ajaxurl,
						method: 'POST',
						data: wpmr_web_register,
						complete: function (jqXHR, textStatus) {
							hideOverlay();
						},
						success: function (response) {
							if ((typeof response) != 'object') {
								if (response === null || response === '' || response === undefined) {
									$('#reg_error').html('<p><strong>Server returned empty response</strong></p>');
									return;
								}
								try {
									response = JSON.parse(response);
								} catch (e) {
									$('#reg_error').html('<p><strong>Invalid response from server</strong></p>');
									return;
								}
							}
							if (response && response.hasOwnProperty('success')) {
								$('#is_unregistered').html('<p style="text-align:center;"><strong>Thank you for registering!</strong></p>');
								var url = new URL( window.location.href );
								url.searchParams.set( 'wpmr_registration', 'true' );
								window.location.href = url.toString();
							} else {
								$('#reg_error').html('<p><strong>' + (response && response.error ? response.error : 'Unknown error occurred') + '</strong></p>');
							}
						} // initialize
					}); // ajax post
					return false;
				});

				$('#definition_warning').click(function () {
					$("#wpmr_updates_box").addClass("prompt_register");
					fix_blur($);
				});

				$('#cta-register').click(function () {
					$("#wpmr_updates_box").addClass("prompt_register");
					fix_blur($);
				});

				$('#do_file_scan').change(function () {
					if (this.checked) {
						$('#skipdirs').prop("disabled", false);
					}
					else {
						$('#skipdirs').attr("disabled", "disabled");
					}
				});

				$('#wpmr_god').change(function () {
					if ($(this).prop('checked')) {
						$('#wpmr_batchsize_wrap').removeClass('transparent');
					} else {
						$('#wpmr_batchsize_wrap').addClass('transparent');
					}
				});

				$('#wpmr_batchsize').change(function () {
					val = $('#wpmr_batchsize').val();
					if (val > 1) {
						html = 'Scan ' + val + ' Items per req.';
					} else {
						html = 'Scan ' + val + ' Item per req.';
					}
					//$('#scan_hint').html(html);
					$('#scan_hint_value').html(val);

				});

				// close postboxes that should be closed
				$('.if-js-closed').removeClass('if-js-closed').addClass('closed');
				// postboxes setup
				postboxes.add_postbox_toggles('toplevel_page_wpmr');

				function wpmr_get_stat(stat = '') {

					// let is important else the variable will be declared as global rendering the function useless
					let wpmr_stats = {
						wpmr_stats_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_stats' ) ); ?>',
						action: "wpmr_get_stats",
						stat_type: stat
					};
					$.ajax({
						url: ajaxurl,
						method: 'POST',
						data: wpmr_stats,
						dataType: "json",
						// async: false,
						success: function (response, textStatus, jqXHR) {
							if ((typeof response) === 'object' && response.hasOwnProperty('success') && response.success) { // is the server not sending us JSON?
								if (wpmr_stats.stat_type == 'bootstrap') {
									if (response.data.checksums) {
										$('#checksum_count').html(response.data.checksums);
									} else {
										$('#checksum_count').html('Checksum Count Error');
									}
									if (response.data.count !== undefined && response.data.count !== null) {
										$('.total_files').html(response.data.count);
										$('#files_to_scan').html(response.data.count);
										$('#files_remaining').html(response.data.count);
										$('#status_total_files').html(response.data.count);
									}
									else {
										$('.total_files').html('Error File Count');
										$('#files_to_scan').html('Error File Count');
										$('#files_remaining').html('Error File Count');
										$('#status_total_files').html('Error File Count');
									}
									if (response.data.db_stats) {

										db_total = 0;
										db_total_count = 0;
										db_stats = response.data.db_stats;
										let tables = Object.keys(db_stats);
										tables.forEach(table => {
											db_total += parseInt(db_stats[table]['max'], 10);
											db_total_count += parseInt(db_stats[table]['count'], 10);
										});

										$('#total_records_count').html(db_total_count);
										$('#total_records').html(db_total);
										$('#records_remaining').html(db_total);
									} else {
										console.log('No db stats');
										$('#total_records').html('Error Counting Records');
										$('#records_remaining').html('Error Counting Records');
									}
								}

								if (wpmr_stats.stat_type == 'definition_count' && response.data) {
									$('.sig_count').html(response.data);
								}
								if (wpmr_stats.stat_type == 'definition_version' && response.data) {
									$('.sig_version').html(response.data);
									$('#status_definition_version').html(response.data);
								}
								if (wpmr_stats.stat_type == 'last_updated' && response.data) {
									$('.sig_date').html(response.data);
									$('#last_updated').html(response.data);
								}
								if (wpmr_stats.stat_type == 'hidden_files' && response.data) {
									$('#hidden_files').html(response.data);
								}

							}
						},
						complete: function (jqXHR, textStatus) {
							console.dir(jqXHR);
						}
					}); // ajax post
				}

				// Stats behind the registration modal are invisible — skip all server work.
				// The wpmr_setup_complete handler fires the first real refresh after registration.
				if (registered) {
					wpmr_get_stat('bootstrap');
					wpmr_get_stat('definition_count');
					wpmr_get_stat('definition_version');
					wpmr_get_stat('last_updated');
					wpmr_get_stat('memory_limit');
				}

				$("#wpmr_skin").change(function (e) { //event

					classes = $('body').attr('class', $('body').attr('class').replace(/malcure_skin_.*?\s/, 'malcure_skin_' + $(this).val() + ' '));

					wpmr_ajax_request(
						'update_setting',
						['wpmr_skin', $(this).val()],
						'<p>Skin changed successfully to ' + $(this).val() + '.</p>', // success message
						'<p>Skin change failed.</p>' // failure message
					);
				});

				$("#wpmr_show_hidden_files").click(function () {
					wpmr_get_stat('hidden_files');
				});

				$('#wpmr_results_box').addClass('closed');
				$('#wpmr_debug_box').addClass('closed');
				$('#wpmr_inspect_box').addClass('closed');
				$('#wpmr_inspect_box').addClass('hidden');
				$('#wpmr_diagnostics_box').addClass('closed');

				$("#wpmr_def_auto_update_enabled").change(function (e) { //event
					e.preventDefault();
					wpmr_def_auto_update_enabled = {
						wpmr_def_auto_update_enabled_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_def_auto_update_enabled' ) ); ?>',
						action: "wpmr_def_auto_update_enabled",
						enabled: this.checked
					};
					$.ajax({
						url: ajaxurl,
						method: 'POST',
						data: wpmr_def_auto_update_enabled,
						complete: function (jqXHR, textStatus) {
							console.dir(jqXHR);
						}
					}); // ajax post
				});


				$("#wpmr_ux_notifications_enabled").change(function (e) { //event
					wpmr_ajax_request(
						'update_setting',
						['ux_notifications_enabled', this.checked ? 'on' : 'off'],
						'<p>Notification preferences saved.</p>', // success message
						'<p>Failed to save notification preferences.</p>' // failure message
					);
				});

				$("#malcure_destroy_sessions").click(function () {
					wpmr_ajax_request(
						'destroy_sessions',
						['<?php echo esc_js( get_current_user_id() ); ?>'],
						'<p>All users have been logged out (except you).</p>', // success message
						'<p>Failed to logout other users.</p>' // failure message
					);
				});

				$("#malcure_shuffle_salts").click(function () {
					wpmr_ajax_request('ajax_shuffle_salts').done(function (data) {
						if (data && data.success) {
							window.location.reload();
						}
					});
					
				});

				function wpmr_set_definitions_update_notice(type, message) {
					var noticeClass = type === 'success' ? 'wpmr_notice_success' : 'wpmr_notice_error';
					$('#update_response').stop(true, true).fadeIn(200).empty().append(
						$('<span/>', {
							'class': noticeClass,
							text: message
						})
					);
				}

				function wpmr_get_definitions_update_error_message(jqXHR, textStatus) {
					var responseJSON = jqXHR && jqXHR.responseJSON ? jqXHR.responseJSON : null;
					var responseText = jqXHR && typeof jqXHR.responseText === 'string' ? jqXHR.responseText.trim() : '';
					var serializedResponse = '';

					if (responseText === '-1') {
						return 'Security check failed. Please reload the page and try again.';
					}

					if (responseJSON && typeof responseJSON === 'object' && responseJSON.hasOwnProperty('data')) {
						if (typeof responseJSON.data === 'string' && responseJSON.data.trim().length) {
							return responseJSON.data.trim();
						}

						if (responseJSON.data && typeof responseJSON.data === 'object') {
							if (typeof responseJSON.data.message === 'string' && responseJSON.data.message.trim().length) {
								return responseJSON.data.message.trim();
							}

							try {
								serializedResponse = JSON.stringify(responseJSON.data);
							} catch (error) {
								serializedResponse = '';
							}

							if (serializedResponse && serializedResponse !== '{}' && serializedResponse !== '[]') {
								return serializedResponse;
							}
						}
					}

					if (typeof responseJSON === 'string' && responseJSON.trim().length) {
						return responseJSON.trim();
					}

					if (responseJSON && typeof responseJSON === 'object') {
						if (typeof responseJSON.message === 'string' && responseJSON.message.trim().length) {
							return responseJSON.message.trim();
						}

						try {
							serializedResponse = JSON.stringify(responseJSON);
						} catch (error) {
							serializedResponse = '';
						}

						if (serializedResponse && serializedResponse !== '{}' && serializedResponse !== '[]') {
							return serializedResponse;
						}
					}

					if (responseText.length) {
						if (/^<[^>]+>/.test(responseText)) {
							return 'Server returned an unexpected HTML response while updating definitions.';
						}
						return responseText;
					}

					if (textStatus === 'parsererror') {
						return 'Unexpected response received while updating definitions.';
					}

					if (textStatus === 'success') {
						return 'Definition update failed without an error message.';
					}

					return 'Request failed. Please try again.';
				}

				$("#wpmr_update").click(function (e) { //event
					e.preventDefault();
					wpmr_update = {
						wpmr_update_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_update_sigs' ) ); ?>',
						action: "wpmr_update_sigs",
					};
					$.ajax({
						url: ajaxurl,
						method: 'POST',
						data: wpmr_update,
						complete: function (jqXHR, textStatus) {
							console.dir(jqXHR);
							console.dir(textStatus);
							if (textStatus == 'success') {  // ajax res received
								if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('success') && jqXHR.responseJSON.success) {
									wpmr_set_definitions_update_notice('success', 'Successfully updated definitions.');
									$('.sig_count').html(jqXHR.responseJSON.data.count);
									$('.sig_version').html(jqXHR.responseJSON.data.version); // in the dashboard_wrap
									$('#status_definition_version').html(jqXHR.responseJSON.data.version); // in the system status box
									$('.sig_date').html(jqXHR.responseJSON.data.sig_time); // for the engine stats
									$('#last_updated').html(jqXHR.responseJSON.data.sig_time); // for the System Status
									$('#definition_warning').remove();
									$('#update_notice_p').remove();
									$('#update_response').fadeOut(10000);
									$("#wpmr_new_def_alert").remove();
								}
								else {
									wpmr_set_definitions_update_notice('error', 'Failed. Reason: ' + wpmr_get_definitions_update_error_message(jqXHR, textStatus));
								}
							}
							else {  // no ajax response
								wpmr_set_definitions_update_notice('error', wpmr_get_definitions_update_error_message(jqXHR, textStatus));
							}
						},
						success: function (response) {
						} // initialize
					}); // ajax post
				});

				$("#wpmr_reset").click(function (e) { //event
					e.preventDefault();
					if (!confirm("Whoa! This will delete plugin settings including definitions, checksums & registration data.\nAre you sure you want to proceed?\nClick cancel if not sure.")) {
						return false;
					}
					wpmr_reset = {
						wpmr_reset_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_reset' ) ); ?>',
						action: "wpmr_reset",
						reset_logs: $('#wpmr_reset_logs').is(':checked'),
					};
					$.ajax({
						url: ajaxurl,
						method: 'POST',
						data: wpmr_reset,
						complete: function (jqXHR, textStatus) {
							if (textStatus == 'success') {  // ajax res received
								if (jqXHR.hasOwnProperty('responseJSON') &&
									jqXHR.responseJSON.hasOwnProperty('success')) {
									$('#update_response').fadeIn(200).html('<span class="wpmr_notice_success">Successfully reset plugin settings.</span>');
									$('#update_notice_p').remove();
									$('#update_response').fadeOut(10000);
									setTimeout(function () {
										if (confirm('Plugin has been reset. Reload the page now?')) {
											window.location.reload();
										}
									}, 1000);
								}
								else {
									if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('data') && jqXHR.responseJSON.data.length) {
										$('#update_response').html('Failed.');
									}
								}
							}
							else {  // no ajax response
								$('#update_response').html('Unknown failure.');
							}
						},
						success: function (response) {
						} // initialize
					}); // ajax post
				});

				$('#wpmr_register_cancel').click(function (e) {
					$('#wpmr_updates_box').removeClass('prompt_register');
				});

				$("#scan_control").click(js_scanner); //click

				$("#scan_control_deep").click(js_scanner); //click					

				$('.inspect_file_debug').blur(file_inspect_handler);
				
				/*
				$("#wpmr_copy").click(function () {
					try {
						let range = document.createRange();
						let selection = window.getSelection();
						let node = document.getElementById('result_range');
						range.selectNodeContents(node);
						console.dir('node.innerText');
						console.dir(node.innerText);



						console.dir('node.textContent');
						console.dir(node.textContent);
						selection.removeAllRanges();
						selection.addRange(range);
						document.execCommand("copy");
						selection.removeAllRanges();
						$('#copied_check').fadeTo(1000, 0).fadeTo(1000, 1);
					}
					catch (e) {
					}
				});
				*/

				$('#wpmr_copy').on('click', function () {
					var root = document.getElementById('result_range');

					// ---------- helpers ----------
					function hasUserSelectNone(el) {
						for (var n = el; n && n.nodeType === 1; n = n.parentElement) {
							var cs = getComputedStyle(n);
							if (
								cs.getPropertyValue('user-select') === 'none' ||
								cs.getPropertyValue('display') === 'none' ||
								cs.getPropertyValue('visibility') === 'hidden' ||
								n.hidden || n.getAttribute('aria-hidden') === 'true'
							) {
								return true;
							}
						}
						return false;
					}

					function textFrom(el) {
						if (!el) {
							return '';
						}
						var tw = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
						var out = '';
						var node;
						while ((node = tw.nextNode())) {
							var s = node.nodeValue;
							if (!s || !s.trim()) {
								continue;
							}
							if (hasUserSelectNone(node.parentElement)) {
								continue;
							}
							out += s + ' ';
						}
						return out.replace(/\u00A0/g, ' ').replace(/\s+/g, ' ').trim();
					}

					function safeText(sel, fallback) {
						if (typeof fallback === 'undefined') {
							fallback = 'Nothing Detected.';
						}
						var el = root.querySelector(sel);
						var t = textFrom(el);
						if (t) {
							return t;
						} else {
							return fallback;
						}
					}

					// File/DB rows -> ["severity code\tpath", ...]
					function linesFromRecords(tableSel, preClass) {
						var table = root.querySelector(tableSel);
						if (!table) {
							return [];
						}
						var rows = table.querySelectorAll('tbody tr');
						var KNOWN = {
							'severe':1,
							'high':1,
							'suspicious':1,
							'skipped':1,
							'unreadable':1,
							'vulnerable':1
						};

						function getSeverity(tr) {
							var a = tr.querySelector('.level a.threat, .severity, [data-severity]');
							if (a) {
								for (var i=0; i<a.classList.length; i++) {
									var c = a.classList[i];
									if (KNOWN[c]) {
										return c;
									}
								}
								if (a.getAttribute('data-severity')) {
									return a.getAttribute('data-severity');
								}
								var t = textFrom(a);
								if (t) {
									return t.split(/\s+/)[0];
								}
							}
							return 'Unknown';
						}

						function getCode(tr) {
							var el = tr.querySelector('.wpmr_offset, .offset, .sig, .id, .code');
							var t = textFrom(el);
							if (t) {
								return t;
							} else {
								return 'unknown';
							}
						}

						function getPath(tr) {
							var pre = tr.querySelector('pre.'+preClass) ||
									tr.querySelector('td.infected_file pre, td.path pre, pre') ||
									tr.querySelector('.path, .file, .affected, a[href]');
							if (pre) {
								return textFrom(pre);
							} else {
								return textFrom(tr);
							}
						}

						var out = [];
						for (var j=0; j<rows.length; j++) {
							var tr = rows[j];
							var path = getPath(tr);
							if (!path) {
								continue;
							}
							var sev  = getSeverity(tr);
							var code = getCode(tr);
							out.push(sev + ' ' + code + '\t' + path);
						}
						return out;
					}

					// DB whitelist lines
					function dbWhitelistLines() {
						var wrap = root.querySelector('#db_whitelist');
						if (!wrap) {
							return [];
						}
						// Preferred: explicit whitelist row wrappers.
						var rows = wrap.querySelectorAll('p[data-db-wrap]');
						if (rows.length) {
							var arr = [];
							for (var i=0; i<rows.length; i++) {
								var s = textFrom(rows[i]);
								if (s) {
									arr.push(s);
								}
							}
							return arr;
						}
						// Fallback: best-effort capture, but ignore empty-state placeholders.
						var t = textFrom(wrap);
						if (!t) {
							return [];
						}
						return t
							.split(/\s*\n+\s*/)
							.map(function (s) { return (s || '').trim(); })
							.filter(function (s) {
								if (!s) { return false; }
								if (/^No database records whitelisted\.?$/i.test(s)) { return false; }
								if (/^The following whitelisted database records will not be scanned/i.test(s)) { return false; }
								return true;
							});
					}

					// Whitelist lines
					function whitelistLines() {
						var wrap = root.querySelector('#whitelist');
						if (!wrap) {
							return [];
						}
						var items = wrap.querySelectorAll('[data-file]');
						if (items.length) {
							var arr = [];
							for (var i=0; i<items.length; i++) {
								var val = items[i].getAttribute('data-file');
								if (val) {
									arr.push(val);
								}
							}
							return arr;
						}
						// Fallback: support legacy markup, but ignore empty-state placeholders.
						var rows = wrap.querySelectorAll('p[data-file-wrap]');
						if (rows.length) {
							var out = [];
							for (var j=0; j<rows.length; j++) {
								var s = textFrom(rows[j]);
								if (s) {
									out.push(s);
								}
							}
							return out;
						}
						var t = textFrom(wrap);
						if (!t) {
							return [];
						}
						return t
							.split(/\s*\n+\s*/)
							.map(function (s) { return (s || '').trim(); })
							.filter(function (s) {
								if (!s) { return false; }
								if (/^No files whitelisted\.?$/i.test(s)) { return false; }
								if (/^The following whitelisted files will not be scanned/i.test(s)) { return false; }
								return true;
							});
					}

					// ---------- build output ----------
					var out = '';
					out += '⥳←←← MALCURE SCAN LOG →→→⥴\n';

					out += '\n— Redirect Hijack —\n';
					out += safeText('#redirect_hijack', 'Nothing Detected.') + '\n\n';

					out += '—  Title Hack Results —\n';
					out += safeText('#title_hack', 'Nothing Detected.') + '\n\n';

					// Vulnerabilities
					out += '— Vulnerabilities —\n';
					var vulnLines = linesFromRecords('#vulnerability_records', 'recorded_vuln');
					if (!vulnLines.length) {
						vulnLines = linesFromRecords('#vulnerabilities', 'recorded_vuln');
					}
					if (vulnLines.length) {
						out += vulnLines.join('\n') + '\n\n';
					} else {
						out += 'Nothing Detected.\n\n';
					}

					out += '— User Account Audit —\n';
					var userRecords = root.querySelector('#user_records');
					if (userRecords) {
						var userRows = userRecords.querySelectorAll('tbody tr.detected');
						var KNOWN_SEV = {severity:1,suspicious:1,severe:1,skipped:1,high:1};
						var userLinesOut = [];
						for (var ui = 0; ui < userRows.length; ui++) {
							var utr = userRows[ui];
							// Severity from the .threat badge class list.
							var uBadge = utr.querySelector('.level a.threat, .level .severity');
							var uSev = '';
							if (uBadge) {
								for (var ci = 0; ci < uBadge.classList.length; ci++) {
									if (KNOWN_SEV[uBadge.classList[ci]]) {
										uSev = uBadge.classList[ci];
										break;
									}
								}
							}
							// Cause from the tr title attribute (already carries: 'User ID N — severity (factor reasons)').
							// Extract the parenthesised reasons; if none, use the substring after the severity word.
							var uTitle = utr.getAttribute('title') || '';
							var uCause = '';
							var parenMatch = uTitle.match(/\(([^)]*)\)/);
							if (parenMatch) {
								uCause = parenMatch[1];
							} else {
								// No reasons; fall back to the text after ' — ' in the title.
								var dashIdx = uTitle.indexOf(' — ');
								if (dashIdx !== -1) {
									uCause = uTitle.substring(dashIdx + 3);
								}
							}
							// User ID from the <pre class="recorded_user"> text.
							var uId = '';
							var uPre = utr.querySelector('pre.recorded_user');
							if (uPre) {
								uId = textFrom(uPre);
							} else {
								uId = textFrom(utr);
							}
							userLinesOut.push(uSev + '\t' + uId + '\t' + uCause);
						}
						if (userLinesOut.length) {
							out += userLinesOut.join('\n') + '\n\n';
						} else {
							out += 'Nothing Detected.\n\n';
						}
					} else {
						out += 'Nothing Detected.\n\n';
					}

					out += '— Database Scan Results —\n';
					var db = linesFromRecords('#db_records', 'recorded_db');
					if (db.length) {
						out += db.join('\n') + '\n\n';
					} else {
						out += 'Nothing Detected.\n\n';
					}

					out += '— Whitelisted Database Records —\n';
					var dbwl = (typeof dbWhitelistLines === 'function') ? dbWhitelistLines() : [];
					if (dbwl.length) {
						out += '\t' + dbwl.join('\n\t') + '\n\n';
					} else {
						out += 'No database records whitelisted.\n\n';
					}

					out += '— File Scan Results —\n';
					out += '\t— WHITELISTED FILES —\n';
					var wl = whitelistLines();
					if (wl.length) {
						out += '\t' + wl.join('\n\t') + '\n\n';
					} else {
						out += 'No files whitelisted.\n\n';
					}

					out += '— INFECTED FILES —\n';
					var files = linesFromRecords('#file_records', 'recorded_file');
					if (files.length) {
						out += files.join('\n') + '\n';
					}

					var dbInfectionCount = db.length;
					var fileInfectionCount = files.length;
					var infectionCount = dbInfectionCount + fileInfectionCount;

					function isSevereOrCriticalLine(s) {
						return /^(severe|critical)\b/i.test(s);
					}

					var severeInfectionCount = 0;
					for (var i=0; i<files.length; i++) {
						if (isSevereOrCriticalLine(files[i])) {
							severeInfectionCount++;
						}
					}
					for (var j=0; j<db.length; j++) {
						if (isSevereOrCriticalLine(db[j])) {
							severeInfectionCount++;
						}
					}

					var severeVulnerabilityCount = 0;
					for (var k=0; k<vulnLines.length; k++) {
						if (isSevereOrCriticalLine(vulnLines[k])) {
							severeVulnerabilityCount++;
						}
					}

					if (infectionCount > 0) {
						out += '\n\nMalcure Detected ' + infectionCount + ' Infection(s) (' + dbInfectionCount + ' database, ' + fileInfectionCount + ' file)\n';
					}
					// if ((severeInfectionCount + severeVulnerabilityCount) > 0) {
					// 	out += 'Severe/Critical Findings: ' + (severeInfectionCount + severeVulnerabilityCount) + '\n';
					// }

					// Normalize
					// out = out.replace(/\u00A0/g, ' '); // replace non-breaking spaces
					// out = out.replace(/\s+\n/g, '\n'); // remove trailing spaces before newlines
					// out = out.replace(/\n{3,}/g, '\n\n'); // reduce multiple newlines to two
					// out = out.replace(/\s+$/, ''); // remove trailing spaces at the end

					// ---------- copy ----------
					try {
						navigator.clipboard.writeText(out);
					}
					catch (e) {
						var ta = document.createElement('textarea');
						ta.value = out;
						ta.style.position = 'fixed';
						ta.style.left = '-9999px';
						document.body.appendChild(ta);
						ta.select();
						document.execCommand('copy');
						document.body.removeChild(ta);
					}

					$('#copied_check').fadeTo(1000, 0).fadeTo(1000, 1);
				});




				$('#file_results').on('click', '.wpmr_inspect_file', file_inspect_handler);
				$('#db_results').on('click', '.wpmr_inspect_db', db_inspect_handler);

				$('#wpmr_cleanup').click(function (event) {
					file = $('#wpmr_inspect_file').attr('data-file');
					if (!file) {
						$('#file_op_status').html('No file selected.'); // update the status message
						wpmr_update_file_only_operations_visibility();
						return;
					}
					if (!confirm("\nAre you sure you have backed-up this file and want to attempt to repair it?\n\n" + file + "\n")) {
						return;
					}
					$('#file_op_status').html('');
					event.preventDefault();
					var element = this;

					wpmr_clean_file = {
						wpmr_clean_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_clean_file' ) ); ?>',
						action: "wpmr_clean_file",
						file: btoa(file),
					};
					$.ajax({
						url: ajaxurl,
						method: 'GET',
						data: wpmr_clean_file,
						complete: function (jqXHR, textStatus) {
							document.getElementById('file_op_status').scrollIntoView({ behavior: "smooth", block: "center", inline: "center" });
							setTimeout(function () { flash_file_op($) }, 500);
							if (textStatus == 'success') {
								if (jqXHR.hasOwnProperty('responseJSON')) {
									if (jqXHR.responseJSON.success) {
										$('#wpmr_inspect_file').val(jqXHR.responseJSON.data); // update the file inspector contents
										$('#file_op_status').html('File repair succeeded on ' + file); // update the status message
										$('.wpmr_inspect_file[data-file="' + file + '"]').closest('tr.detected').fadeOut(1000, function () { $(this).remove(); }); // remove the file from the infection results.
										$('#wpmr_inspect_file').attr('data-file', '');
										wpmr_update_file_only_operations_visibility();
									} else {
										var msg = jqXHR.responseJSON.data;
										if (typeof msg === 'object' && msg !== null && msg.hasOwnProperty('message')) {
											msg = msg.message;
										}
										if (!wpmr_is_pro || msg === 'Invalid license.') {
											msg = wpmr_get_license_cta('repair');
										}
										$('#file_op_status').html(msg);
									}
								} else {
									$('#file_op_status').html('Invalid response from server!');
								}
							} else {
								$('#file_op_status').html('Request failed!');
							}
						},
					});

				});

				$('#wpmr_delete').click(function (event) {
					file = $('#wpmr_inspect_file').attr('data-file');
					if (!file) {
						$('#file_op_status').html('No file selected.'); // update the status message
						wpmr_update_file_only_operations_visibility();
						return;
					}

					if (!confirm("\nAre you sure you have backed-up this file and want to delete it?\n\n" + file + "\n")) {
						return;
					}
					$('#file_op_status').html('');
					event.preventDefault();
					var element = this;

					wpmr_delete_file = {
						wpmr_delete_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_delete_file' ) ); ?>',
						action: "wpmr_delete_file",
						file: btoa(file),
					};
					$.ajax({
						url: ajaxurl,
						method: 'GET',
						data: wpmr_delete_file,
						complete: function (jqXHR, textStatus) {
							document.getElementById('file_op_status').scrollIntoView();
							setTimeout(function () { flash_file_op($) }, 500);
							if (textStatus == 'success') {
								if (jqXHR.hasOwnProperty('responseJSON')) {
									if (jqXHR.responseJSON.success) {
										$('#wpmr_inspect_file').val(''); // clear the file inspector contents
										$('#file_op_status').html(jqXHR.responseJSON.data); // update the UI about response status
										$('.wpmr_inspect_file[data-file="' + file + '"]').closest('tr.detected').fadeOut(1000, function () { $(this).remove(); }); // remove the file from infected results
										$('#wpmr_inspect_file').attr('data-file', '');
										wpmr_update_file_only_operations_visibility();
									} else {
										var msg = jqXHR.responseJSON.data;
										if (typeof msg === 'object' && msg !== null && msg.hasOwnProperty('message')) {
											msg = msg.message;
										}
										$('#file_op_status').html(msg);
									}
								} else {
									$('#file_op_status').html('Invalid response from server!');
								}
							} else {
								$('#file_op_status').html('Request failed!');
							}
						},
					});

				});

				$('#wpmr_whitelist_item').click(function (event) {
					var file_path = ($('#wpmr_inspect_file').attr('data-file') || '').trim();
					var real_path = file_path;
					var table = ($('#wpmr_inspect_file').attr('data-db-table') || '').trim();
					var id = parseInt(($('#wpmr_inspect_file').attr('data-db-id') || '0').trim(), 10) || 0;

					if (!file_path && (!table || id <= 0)) {
						$('#file_op_status').html('No item selected.');
						return;
					}

					$('#file_op_status').html('');
					event.preventDefault();

					// Prefer file mode when a file is selected.
					if (file_path) {
						if (!confirm("\nAre you sure you want to whitelist this file?\n\n" + file_path + "\n")) {
							return;
						}
						wpmr_whitelist_file = {
							wpmr_whitelist_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_whitelist_file' ) ); ?>',
							action: "wpmr_whitelist_file",
							file: btoa(file_path),
						};
						$.ajax({
							url: ajaxurl,
							method: 'GET',
							data: wpmr_whitelist_file,
							complete: function (jqXHR, textStatus) {
								document.getElementById('file_op_status').scrollIntoView();
								setTimeout(function () { flash_file_op($) }, 500);
								if (textStatus == 'success') {
									if (jqXHR.hasOwnProperty('responseJSON')) {
										if (jqXHR.responseJSON.success) {
											$('#wpmr_inspect_file').val('');
											$('#file_op_status').html(jqXHR.responseJSON.data);
											$('.wpmr_inspect_file[data-file="' + file_path + '"]').closest('tr.detected').fadeOut(1000, function () { $(this).remove(); });
											$('#wpmr_inspect_file').attr('data-file', '');
											$('#whitelist').append('<p data-file-wrap="' + real_path + '"><span data-file="' + real_path + '" class="dashicons dashicons-dismiss remove-from-whitelist"></span>' + real_path + '</p>');
											handle_whitelist_labels($);
										} else {
											var msg = jqXHR.responseJSON.data;
											if (typeof msg === 'object' && msg !== null && msg.hasOwnProperty('message')) {
												msg = msg.message;
											}
											if (!wpmr_is_pro || msg === 'Invalid license.') {
												msg = wpmr_get_license_cta('whitelist');
											}
											$('#file_op_status').html(msg);
										}
									} else {
										$('#file_op_status').html('Invalid response from server!');
									}
								} else {
									$('#file_op_status').html('Request failed!');
								}
							},
						});
						return;
					}

					// DB mode
					if (!confirm("\nAre you sure you want to whitelist this DB record (it will be ignored in future scans even if its content changes)?\n\n" + table + " ID " + id + "\n")) {
						return;
					}
					wpmr_whitelist_db = {
						wpmr_db_whitelist_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_whitelist_db_record' ) ); ?>',
						action: 'wpmr_whitelist_db_record',
						table: table,
						id: id
					};
					$.ajax({
						url: ajaxurl,
						method: 'GET',
						data: wpmr_whitelist_db,
						complete: function (jqXHR, textStatus) {
							document.getElementById('file_op_status').scrollIntoView();
							setTimeout(function () { flash_file_op($) }, 500);
							if (textStatus == 'success') {
								if (jqXHR.hasOwnProperty('responseJSON')) {
									if (jqXHR.responseJSON.success) {
										$('#file_op_status').html(jqXHR.responseJSON.data);
										$('#wpmr_inspect_file').val('');
										$('#wpmr_inspect_file').attr('data-db-table', '');
										$('#wpmr_inspect_file').attr('data-db-id', '');
										$('#db_whitelist').append('<p data-db-wrap="' + table + ':' + id + '"><span data-table="' + table + '" data-id="' + id + '" class="dashicons dashicons-dismiss remove-db-from-whitelist"></span>' + table + ' ID ' + id + '</p>');
										handle_db_whitelist_labels($);
										$('.wpmr_inspect_db[data-table="' + table + '"][data-id="' + id + '"]').closest('tr.detected').fadeOut(1000, function () { $(this).remove(); });
									} else {
										var msg = jqXHR.responseJSON.data;
										if (typeof msg === 'object' && msg !== null && msg.hasOwnProperty('message')) {
											msg = msg.message;
										}
										if (!wpmr_is_pro || msg === 'Invalid license.') {
											msg = wpmr_get_license_cta('db-whitelist');
										}
										$('#file_op_status').html(msg);
									}
								} else {
									$('#file_op_status').html('Invalid response from server!');
								}
							} else {
								$('#file_op_status').html('Request failed!');
							}
						}
					});
				});

				$('#whitelist').on('click', '.remove-from-whitelist', function (event) {
					$element = this;
					file = $(this).attr('data-file');
					$(this).addClass('mc-waiting');
					wpmr_unwhitelist_file = {
						wpmr_unwhitelist_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_unwhitelist_file' ) ); ?>',
						action: "wpmr_unwhitelist_file",
						file: btoa(file),
					};

					$.ajax({
						url: ajaxurl,
						method: 'GET',
						data: wpmr_unwhitelist_file,
						complete: function (jqXHR, textStatus) {

							if (textStatus == 'success') {
								if (jqXHR.hasOwnProperty('responseJSON')) {
									if (jqXHR.responseJSON.success) {
										$("[data-file-wrap='" + file + "']").remove();
										$(this).removeClass('mc-waiting');

										handle_whitelist_labels($);
									} else {
										alert(jqXHR.responseJSON.data); // alert the error message
									}
								} else {
									alert('Invalid response from server!');
								}
							} else {
								alert('Request failed!');
							}
						},
					});
				});

				$('#db_whitelist').on('click', '.remove-db-from-whitelist', function (event) {
					$element = this;
					var table = ($(this).attr('data-table') || '').trim();
					var id = parseInt(($(this).attr('data-id') || '0').trim(), 10) || 0;
					if (!table || id <= 0) {
						return;
					}
					$(this).addClass('mc-waiting');
					wpmr_unwhitelist_db = {
						wpmr_unwhitelist_db_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_unwhitelist_db_record' ) ); ?>',
						action: "wpmr_unwhitelist_db_record",
						table: table,
						id: id,
					};
					$.ajax({
						url: ajaxurl,
						method: 'GET',
						data: wpmr_unwhitelist_db,
						complete: function (jqXHR, textStatus) {
							if (textStatus == 'success') {
								if (jqXHR.hasOwnProperty('responseJSON')) {
									if (jqXHR.responseJSON.success) {
										$("[data-db-wrap='" + table + ':' + id + "']").remove();
										$($element).removeClass('mc-waiting');
										handle_db_whitelist_labels($);
									} else {
										alert(jqXHR.responseJSON.data);
									}
								} else {
									alert('Invalid response from server!');
								}
							} else {
								alert('Request failed!');
							}
						}
					});
				});

				function js_scanner(e) { //event
					e.preventDefault();
					if ($(this).attr('id') == 'scan_control') {
						file_scan_type = 'fast';
						$('#scan_control_deep').addClass('unused');
					}
					if ($(this).attr('id') == 'scan_control_deep') {
						file_scan_type = 'deep';
						$('#scan_control').addClass('unused');
					}

					if (typeof (scanned) !== 'undefined') { // if this is not the first scan then warn before rescanning
						if (!confirm('This will clear the current results. Are you sure you want to continue?')) {
							return;
						}
					}
					scanned = true;
					wpmr_doctitle = document.title;
					wpmr_server_max_execution_time = <?php echo (int) @ini_get( 'max_execution_time' ); ?>; // casting to int returns 0 in case of false which equates to no timeout.
					wpmr_server_max_execution_time = ( wpmr_server_max_execution_time -5 ) * 1000; // convert to milliseconds and subtract 5 seconds to be safe
					if( wpmr_server_max_execution_time > 5 ) {
						wpmr_server_max_execution_time = ( wpmr_server_max_execution_time - 5 ) * 1000;
					} else {
						wpmr_server_max_execution_time = 0;
					}
					do_vuln_scan = $('#do_vuln_scan').is(':checked');
					do_db_scan = $('#do_db_scan').is(':checked');
					do_file_scan = $('#do_file_scan').is(':checked');
					do_redirect_scan = $('#do_redirect_scan').is(':checked');
					do_user_scan = $('#do_user_scan').is(':checked');
					skipdirs = ($('#skipdirs').length && $('#skipdirs').val().trim().length) ? $('#skipdirs').val() : '';
					show_suspicious = $('#show_suspicious').is(':checked');
					full_scan = (do_db_scan && do_file_scan && (!$('#skipdirs').val().trim().length));
					if (!do_vuln_scan && !do_db_scan && !do_file_scan && !do_redirect_scan && !do_user_scan ) {
						joking = confirm("You've hit the easter egg!\nAll scans are unchecked.\nPretty sure you're joking right?");
						return false;
					}
					$('#wpmr_results_box').addClass('closed');
					$('#wpmr_debug_box').addClass('closed');
					$('#wpmr_inspect_box').addClass('closed');
					$('#wpmr_diagnostics_box').addClass('closed');
					$('#service_cta').html('');
					severe = 0;
					suspicious = 0;
					severeFileCount = 0;
					suspiciousFileCount = 0;
					userSevereCount = 0;
					userSuspiciousCount = 0;
					if (!registered) {
						msgnodef = window.confirm("A definition update is required to detect the latest malware.\n        OK: update definitions (recommended).\n        Cancel: Integrity-Check & basic scan (not recommended).");
						if (msgnodef) {
							return wpmr_prompt_register($);
						}
					}
					window.onbeforeunload = function () {
						return true;
					};
					record = Math.floor((new Date()).getTime() / 1000);
					$('.gauge_c').css('transform', 'rotate( 0turn)');
					$('.gauge_c').addClass('rotating');
					$('#db_results').html('');
					$('#vulnerabilities').html('');
					$('#title_hack').html('');
					$('#redirect_hijack').html('');
					$('#file_results').html('');
					$('#percent').html('');
					$('#lcd').html('');
					$('.engine_status').toggleClass('blink');
					$('#logo').toggleClass('running');
					$('#percent').toggleClass('running');
					$('#percent').removeClass('suspicious');
					$('#percent').removeClass('severe');
					if(do_vuln_scan){
						$('.engine_status').html('scanning…');
					}
					else {
						$('.engine_status').html('initialising…');
					}
					if (do_db_scan) {
						$('.engine_status').html('scanning database…');
					} else {
						
					}
					total_files = 0;
					highlight_cta = 0;
					wpmr_cta_context = null;
					$("#scan_control").attr('disabled', 'disabled');
					$("#scan_control_deep").attr('value', 'DeepScan™ Running…');
					$("#scan_control_deep").attr('disabled', 'disabled');
					$("#wpmr_god").attr('disabled', 'disabled');
					regex = ($('#wpmr_extra_reg').length && $('#wpmr_extra_reg').val().trim().length) ? btoa($('#wpmr_extra_reg').val()) : '';
					wpmr_extra_db_query = ($('#wpmr_extra_db_query').length && $('#wpmr_extra_db_query').val().trim().length) ? btoa($('#wpmr_extra_db_query').val()) : '';
					wpmr_extra_db_regex = ($('#wpmr_extra_db_regex').length && $('#wpmr_extra_db_regex').val().trim().length) ? btoa($('#wpmr_extra_db_regex').val()) : '';
					wpmr_scan_only_dirs = ($('#wpmr_scan_only_dirs').length && $('#wpmr_scan_only_dirs').val().trim().length) ? $('#wpmr_scan_only_dirs').val() : '';
					wpmr_init_scan = {
						wpmr_init_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_init_scan' ) ); ?>',
						action: "wpmr_init_scan",
						timestamp: record,
						regex: regex,
						wpmr_extra_db_query: wpmr_extra_db_query,
						wpmr_extra_db_regex: wpmr_extra_db_regex,
						skipdirs: skipdirs,
						suspicious: show_suspicious,
						do_vuln_scan: do_vuln_scan,
						do_db_scan: do_db_scan,
						do_file_scan: do_file_scan,
						do_redirect_scan: do_redirect_scan,
						do_user_scan: do_user_scan,
						wpmr_scan_only_dirs: wpmr_scan_only_dirs
					};

					$('#wpmr_batchsize').prop('disabled', true);

					$.ajax({
						url: ajaxurl,
						method: 'POST',
						data: wpmr_init_scan,

						success: function (response) {

							// wpmr_dir(response);

							$('#wpmr_results_box').removeClass('closed');
							db_result = response.hasOwnProperty('db_scan') ? Object.values(response.db_scan) : false;
							
							if (response.checksums) {
								$('#checksum_count').html(response.checksums);
							}

							if (response.last_updated !== "Never") {
								$('.sig_date').html(response.last_updated);
								$('#definition_warning').remove();
								$('#update_notice_p').remove(); // remove the update notice to avoid distraction during scan
							}

							if (response.definition_version) {
								$('.sig_version').html(response.definition_version);
								$('#status_definition_version').html(response.definition_version);
							}

							if (response.definition_count) {
								$('.sig_count').html(response.definition_count);
							}

							title_hack = response.title_hack;
							vulnerabilities = response.vulnerabilities_scan
							redirect_hijack = response.redirect_hijack;
							found_anything = 0;
							found_files = 0;

							if(vulnerabilities) {
								console.dir(vulnerabilities);
								
								vulnerabilities_html = '';
								var vuln_count = 0;
								
								// Create table structure for vulnerabilities
								vulnerabilities_html += '<table id="vulnerability_records"><tbody>';
								
								// Process core vulnerabilities
								if (vulnerabilities.core) {
									Object.keys(vulnerabilities.core).forEach(function(coreItem) {
										var vuln = vulnerabilities.core[coreItem];
										vuln_count++;
										vulnerabilities_html += '<tr class="detected">';
										vulnerabilities_html += '<td class="level"><a target="_blank" class="threat '+vuln.severity+'" href="' + window.wpmrUtm.buildIocUrl(vuln.signature, 'vulnerability') + '">' + vuln.severity + ' <span class="wpmr_offset">' + vuln.signature + '</span></a></td>';
										vulnerabilities_html += '<td class="vuln_record"><pre class="recorded_vuln">' + vuln.message.trim() + '</pre></td>';
										vulnerabilities_html += '</tr>';
									});
								}
								
								// Process plugin vulnerabilities
								if (vulnerabilities.plugins) {
									Object.keys(vulnerabilities.plugins).forEach(function(pluginPath) {
										var vuln = vulnerabilities.plugins[pluginPath];
										vuln_count++;
										vulnerabilities_html += '<tr class="detected">';
										vulnerabilities_html += '<td class="level"><a target="_blank" class="threat '+vuln.severity+'" href="' + window.wpmrUtm.buildIocUrl(vuln.signature, 'vulnerability') + '">' + vuln.severity + ' <span class="wpmr_offset">' + vuln.signature + '</span></a></td>';
										vulnerabilities_html += '<td class="vuln_record"><pre class="recorded_vuln">' + vuln.message.trim() + '</pre></td>';
										vulnerabilities_html += '</tr>';
									});
								}
								
								// Process theme vulnerabilities
								if (vulnerabilities.themes) {
									Object.keys(vulnerabilities.themes).forEach(function(themePath) {
										var vuln = vulnerabilities.themes[themePath];
										vuln_count++;
										vulnerabilities_html += '<tr class="detected">';
										vulnerabilities_html += '<td class="level"><a target="_blank" class="threat '+vuln.severity+'" href="' + window.wpmrUtm.buildIocUrl(vuln.signature, 'vulnerability') + '">' + vuln.severity + ' <span class="wpmr_offset">' + vuln.signature + '</span></a></td>';
										vulnerabilities_html += '<td class="vuln_record"><pre class="recorded_vuln">' + vuln.message.trim() + '</pre></td>';
										vulnerabilities_html += '</tr>';
									});
								}
								
								// Close table structure
								vulnerabilities_html += '</tbody></table>';
								
								// Set found_anything flag if vulnerabilities were found
								if (vuln_count > 0) {
									$('#vulnerabilities').html(vulnerabilities_html);
								}
							} else {
								$('#vulnerabilities').html('<p>No vulnerabilities detected.</p>');
							}

							if (title_hack) {
								$('#title_hack').html('<span class="threat severe">Site Title or Tagline hack detected! Your Search Engine Ranks will be adversely affected and even lead to blacklisting.</span>');
								found_anything = 1;
								severe++;
								show_cta_severe($);
							}
							else {
								$('#title_hack').html('<p>Nothing Detected.</p>');
							}

							if (redirect_hijack) {
								$('#redirect_hijack').html('<span class="threat severe">Site redirect hijack detected! Your Search Engine Ranks will be adversely affected and even lead to blacklisting.</span>');
								found_anything = 1;
								severe++;
								show_cta_severe($);
							}
							else {
								$('#redirect_hijack').html('<p>Nothing Detected.</p>');
							}

							if ($('#wpmr_scan_single_file').length && $('#wpmr_scan_single_file').val().trim().length) {
								files = [$('#wpmr_scan_single_file').val()];
							}
							else {
								files = response.files;

								// Populate hidden files as a scan-init byproduct — no extra AJAX needed.
								// Matches the PHP server-side rendering in wpmr_get_stats() case 'hidden_files':
								// dedup by associative key, [*DIR] labels for hidden parent directories.
								if (response.files && response.files.length) {
									var hidden = response.files.filter(function (v) {
										var base = v.split('/').pop();
										var parent = v.substring(0, v.lastIndexOf('/'));
										var parentBase = parent ? parent.split('/').pop() : '';
										return (
											!String(base.split('.')[0]).length ||
											!String(parentBase.split('.')[0]).length
										);
									});
									if (hidden.length) {
										var newlist = {};
										hidden.forEach(function (v) {
											var parent = v.substring(0, v.lastIndexOf('/'));
											if (parent) {
												var parentBase = parent.split('/').pop();
												if (!String(parentBase.split('.')[0]).length) {
													newlist[parent] = '<strong>[*DIR] ' + parent + '</strong>';
												}
											}
											newlist[v] = '[FILE] ' + v;
										});
										var html = Object.values(newlist).join('<br />');
										$('#hidden_files').html(html);
									}
								}
							}

							files = Object.values(files);
							$('.engine_status').html('initialized…');
							results = [];
							orig_timestamp = Math.floor((new Date()).getTime() / 1000);

							results.starttime = orig_timestamp;
							results.totalfiles = total_files = files.length;
							$('.files_excluded').html(parseInt($('.total_files').html()) - files.length);
							$('#files_to_scan').html(results.totalfiles);
							$('#checksum_count').html(response.checksums);
							results.memory = 0;
							results.iterationUpdated = 0;
							results.infectedfiles = {};
							failed = [];
							iteration = 0;
							batchsize = $('#wpmr_batchsize').val() ;
							// example of db_stats: {"posts": {"count": "322","min": "1","max": "108091"},"postmeta": {"count": "5976","min": "1","max": "6179"},"options": {"count": "805","min": "1","max": "234171"},"comments": {"count": "30","min": "1","max": "30"}}
							
						// === User Scan ===
						// User scan runs after vuln/title/redirect, before DB/file scan gate.
						// When done (or skipped), calls proceed_to_db_or_file_scan() which contains
						// the original DB-scan-or-skip code, moved verbatim.
						var wpmr_scan_users_nonce = '<?php echo esc_js( wp_create_nonce( 'wpmr_scan_users' ) ); ?>';

						if (do_user_scan && response.user_stats && parseInt(response.user_stats.count, 10) > 0) {
							var user_pointer   = Math.max(0, parseInt(response.user_stats.min_id, 10) - 1);
							var user_batchsize = 100;
							var user_iteration = 0;
							var user_failed    = [];
							var all_users      = [];
							var user_scan_done = false;

							$('.engine_status').html('scanning users…');

							function scan_users_batch() {
								if (user_pointer >= parseInt(response.user_stats.max_id, 10)) {
									if (user_failed.length && user_iteration === 0) {
										user_iteration  = 1;
										user_pointer    = Math.min.apply(null, user_failed) - 1;
										user_failed     = [];
										user_batchsize  = 1;
										wpmr_log('Retrying user scan for failed batches…');
										scan_users_batch();
									} else {
										finish_user_scan();
									}
									return;
								}

								jQuery.ajax({
									url: ajaxurl,
									type: 'POST',
									data: {
										wpmr_scan_users_nonce: wpmr_scan_users_nonce,
										action: 'wpmr_scan_users',
										batchsize: user_batchsize,
										pointer: user_pointer,
										timestamp: record,
									},
									success: function(resp) {
										if (resp && resp.results) {
											all_users = all_users.concat(resp.results);
										}
										if (resp && resp.new_pointer) {
											user_pointer = parseInt(resp.new_pointer, 10);
										} else {
											user_pointer += user_batchsize;
										}
										scan_users_batch();
									},
									error: function(jqXHR, textStatus) {
										if (user_iteration === 0) {
											var failed_start = user_pointer + 1;
											var failed_end   = Math.min(user_pointer + user_batchsize, parseInt(response.user_stats.max_id, 10));
											for (var fid = failed_start; fid <= failed_end; fid++) {
												user_failed.push(fid);
											}
										}
										user_pointer += user_batchsize;
										scan_users_batch();
									}
								});
							}

							function finish_user_scan() {
								if (user_scan_done) return;
								user_scan_done = true;
								// Persistence is now server-side per batch inside wpmr_scan_users_callback.
								// JS only renders the already-accumulated all_users and advances the scan.
								// Render BEFORE advancing — the operator must see user findings before the
								// DB/file scan overwrites #user_results or moves the scan forward.
								if (!all_users || !all_users.length) {
									$('#user_results').html('<p style="grid-column: 1 / span 2;"><strong><em>No suspicious users found.</em></strong></p>');
									proceed_to_db_or_file_scan();
									return;
								}

								// Count severities for the CTA summary panel.
								all_users.forEach(function(u) {
									if (u.severity === 'severe') {
										severe++;
										userSevereCount++;
									} else if (u.severity === 'suspicious') {
										suspicious++;
										userSuspiciousCount++;
									}
								});

								var edit_base = '<?php echo esc_js( is_multisite() ? network_admin_url( 'user-edit.php?user_id=' ) : admin_url( 'user-edit.php?user_id=' ) ); ?>';
								var html = '<table id="user_records"><thead><tr><th>Severity</th><th>User</th></tr></thead><tbody>';
								all_users.forEach(function(u) {
									// Severity badge maps to existing classes: 'severe' and 'suspicious' get
									// the .threat badge; 'skipped' is informational and uses a plain label.
									// Badge + User ID link only. Username is intentionally NOT displayed —
									// the operator reviews the account on the user-edit screen, not here.
									// See non-versioned/plans/user-scan.md "## UI output format".
									var sev_class = (u.severity === 'severe' || u.severity === 'suspicious' || u.severity === 'skipped') ? u.severity : '';
									// Row tooltip + #wpmr_copy exporter consume the canonical server-built
									// reason string (format_user_reasons() via scan_user() return value).
									// JS no longer re-implements factor ordering, thresholds, or severity
									// gating — same single source of truth as CLI and stateful UI tooltip.
									// No screen-reader span here: the stateless page's #wpmr_copy handler
									// parses reason from title="" via parenMatch, and a hidden span would
									// double up the reason inside uId via textFrom(). Stateless copy UX
									// differs from the stateful page's native-select button.
									var copy_line = 'User ID ' + u.ID + ' — ' + u.severity + (u.reason ? ' (' + u.reason + ')' : '');
									var display_line = 'User ID ' + u.ID;
									html += '<tr class="detected" title="' + copy_line.replace(/"/g, '&quot;') + '">';
									if (sev_class) {
										html += '<td class="level"><a class="threat ' + sev_class + '" href="' + edit_base + u.ID + '" target="_blank">' + u.severity + '</a></td>';
									} else {
										html += '<td class="level"><a class="threat skipped" href="' + edit_base + u.ID + '" target="_blank">' + u.severity + '</a></td>';
									}
									// Content cell matches vuln/db/file convention: <td><pre class="recorded_user">…</pre></td>.
									// Visible content is the user ID only; the factor detail lives in the
									// row tooltip and is extracted from there by the copy exporter.
									html += '<td class="user_record"><pre class="recorded_user">' + display_line + '</pre></td>';
									html += '</tr>';
								});
								html += '</tbody></table>';
								$('#user_results').html(html);

								// Advance AFTER rendering. CTA is triggered below for severe/suspicious
								// counts; this must run after count accumulation, before DB/file scan.
								if (userSevereCount > 0) {
									show_cta_severe($);          // RED panel, severe messaging
								} else if (userSuspiciousCount > 0) {
									show_cta_suspicious($);      // AMBER panel, suspicious messaging
								}
								proceed_to_db_or_file_scan();
								// else: nothing — clean scan, no panel
							}

							scan_users_batch();

						} else {
							// User scan skipped — proceed directly to DB/file gate.
							if (!do_user_scan) {
								$('#user_results').html('<p style="grid-column: 1 / span 2;"><strong><em>Skipped user scan on request.</em></strong></p>');
							} else {
								$('#user_results').html('<p style="grid-column: 1 / span 2;"><strong><em>No users found.</em></strong></p>');
							}
							proceed_to_db_or_file_scan();
						}

						function proceed_to_db_or_file_scan() {
							if (do_db_scan && response.hasOwnProperty('db_stats')) {

								$('.engine_status').html('scanning database…');
								var db_stats = response.db_stats;
								var tables = Object.keys(db_stats);
								var active_table = 0;                                
								var pointer = parseInt(db_stats[tables[active_table]].min) - 1; // we use > in sql so start one before that
								db_batchsize = 11 * batchsize; // speed it up
								db_batchsize = Math.max(11, Math.min(db_batchsize, 1100));
								var db_max_range = 0;
								db_progress_counters = {};
								tables.forEach(table => {
									db_max_range += parseInt(db_stats[table]['max'], 10);
									db_progress_counters[table] = 0;
								});
								
								function scandb_batch() {
									// Update UI with current table and pointer
									$('#file_scroll').html('<p class="file_name">Scanning Table ' + tables[active_table].toUpperCase() + ' @ marker ' + pointer + '</p>');
									if(active_table < tables.length){
										if(pointer < parseInt(db_stats[tables[active_table]].max)){
											jQuery.ajax({
												url: ajaxurl,
												type: 'POST',
												// timeout: wpmr_server_max_execution_time, since we do only one iteration, we'll just wait instead of timing out
												data: {
													wpmr_scan_db_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_scan_db' ) ); ?>',
													action: 'wpmr_scan_db',
													table: tables[active_table],
													batchsize: db_batchsize,
													pointer: pointer,
													timestamp: record,
													wpmr_extra_db_query: wpmr_extra_db_query,
													wpmr_extra_db_regex: wpmr_extra_db_regex,
												},
												success: function(response) {},
												error: function(xhr, status, error) {},
												complete: function(jqXHR, textStatus) {
													if (jqXHR.hasOwnProperty('responseJSON')) {
														xhr_response = jqXHR.responseJSON;
														// wpmr_dir('xhr_response');
														// wpmr_dir(xhr_response);
														if (xhr_response.hasOwnProperty('report') && xhr_response.report.hasOwnProperty('results')) {
															let results = xhr_response.report.results;
															db_batch_html = '';
															if ((Array.isArray(results) && results.length > 0) || (typeof results === 'object' && Object.keys(results).length > 0)) {
																
																// Create table structure if it doesn't exist
																if ($('#db_results table').length === 0) {
																	$('#db_results').html('<table id="db_records"><tbody></tbody></table>');
																}
																
																results.forEach(result => {
																	severe++;
																	db_infection_count++;
																	// console.dir(result.severity + Date.now());
																	// Create table row with severity button in first column and message in second column
																	db_batch_html += '<tr class="detected">';
																	var rowTable = (result.table || '').toString();
																	var rowId = parseInt((result.id || 0), 10) || 0;
																	db_batch_html += '<td class="inspect"><span class="wpmr_inspect_db button malcure-button-primary" data-table="' + rowTable + '" data-id="' + rowId + '">Inspect</span></td>';
																	db_batch_html += '<td class="level"><a target="_blank" class="threat ' + result.severity + '" href="' + window.wpmrUtm.buildIocUrl(result.signature, 'database') + '">' + result.severity + ' <span class="wpmr_offset">' + result.signature + '</span></a></td>';
																	db_batch_html += '<td class="infected_record"><pre class="recorded_db">' + result.message.trim() + '</pre></td>';
																	db_batch_html += '</tr>';
																	// console.log(`Severity: ${result.severity}, Message: ${result.message}, Signature: ${result.signature}`);
																});
																if(severe) {
																	show_cta_severe($);
																}
																$('#db_records tbody').append(db_batch_html);
															}
															// wpmr_log('Before db_progress_counters');
															// wpmr_log({...db_progress_counters});
															if(xhr_response.report.hasOwnProperty('new_pointer')){
																pointer = parseInt(xhr_response.report.new_pointer);
																// wpmr_log(`Server Pointer: ${pointer}` );
															}
															else {
																pointer += db_batchsize;
																// wpmr_log(`Manual Pointer Progress: ${pointer}` );
															}
															// if we have a pointer greater than max, set pointer to max so that we can increment active_table
															if (pointer >= parseInt(db_stats[tables[active_table]].max)) { 
																	pointer = parseInt(db_stats[tables[active_table]].max);
															}
															db_progress_counters[tables[active_table]] = pointer;
														} // results
														// calculate_db_progress();
														// wpmr_log('After Increment db_progress_counters');
														// wpmr_log({...db_progress_counters});
														db_progress = Object.values(db_progress_counters).reduce(function(total, value) {
															return total + value;
														}, 0);
														// wpmr_log(`db_max_range - db_progress ${db_max_range - db_progress} db_max_range${db_max_range} db_progress${db_progress}`);
														$('#records_remaining').html(db_max_range - db_progress);
														let percent = ( db_progress / db_max_range ) * 100;
														
														// wpmr_log(`db_progress ${percent} : ${db_progress} / ${db_max_range}`);
														db_time_elapsed = Math.floor((new Date()).getTime() / 1000) - results.starttime;
														db_scan_speed = db_progress / db_time_elapsed;
														// wpmr_log(`db_scan_speed ${db_scan_speed}: db_progress:${db_progress}, db_time_elapsed:${db_time_elapsed} active_table ${tables[active_table]}`);
														// wpmr_log(`time_remaining ${(db_max_range - db_progress) / db_scan_speed}: db_max_range:${db_max_range}, db_progress:${db_progress}, db_scan_speed:${db_scan_speed}, db_batchsize:${db_batchsize}`);
														stats = {
															percent: percent,
															scan_speed: db_scan_speed,
															time_elapsed: db_time_elapsed,
															time_remaining: Math.abs((db_max_range - db_progress) / db_scan_speed)
														};
														if (xhr_response.hasOwnProperty('memory')) {
															stats.memory = xhr_response.memory;
														}
														if (xhr_response.hasOwnProperty('memory_limit')) {
															stats.memory_limit = xhr_response.memory_limit;
														}
														if (xhr_response.hasOwnProperty('cpu')) {
															stats.cpu = xhr_response.cpu;
														}

														wpmr_update_ui(stats);
														
													} // if (jqXHR.hasOwnProperty('responseJSON')) {
													else {
														pointer += db_batchsize;
													} // else (jqXHR.hasOwnProperty('responseJSON')) {
													scandb_batch(); 
												}
											});
										}
										else { // pointer is greater than or equal to max, increment active_table, reset pointer and call scandb again
											active_table++;                                            
											if(active_table < tables.length){                                                
												pointer = Math.max(0, parseInt( db_stats[tables[active_table]].min ) - 1 );                                                
												// wpmr_log(`After changing table out of ${db_max_range} pointer is ${pointer}`);
												scandb_batch();
											}
											else {
												if($.trim($('#db_results').html()) === ''){
													$('#db_results').html('<p style="grid-column: 1 / span 2;">Nothing Detected.</p>');
												}
												init_file_scan();
											}
										}
									}
									else { // active table is greater than or equal to tables length
										init_file_scan();
									}
									// ===
								}
								scandb_batch(); // initial call
							} else {
								wpmr_log("Response does not have db_stats property. Proceeding to file scan.");
								$('#db_results').html('<p style="grid-column: 1 / span 2;"><strong><em>Skipped database scan on request.</em></strong></p>');
								init_file_scan(1);
							}
						} // EO proceed_to_db_or_file_scan

							function calculate_db_progress() {
								// Initialize completedRange to accumulate the ID ranges processed from completed tables.
								let completedRange = 0;

								// Loop through all previously scanned tables to sum their complete ranges.
								// This will only include the range from 'min' to 'max' of each table that has been fully scanned.
								for (let i = 0; i < active_table; i++) {
									completedRange += (parseInt(db_stats[tables[i]].max, 10) - parseInt(db_stats[tables[i]].min, 10));
								}

								// Calculate the progress in the current table by determining how far the pointer has moved past the 'min'.
								// This provides a measure of how much of the current table has been processed.
								let currentTableProgress = pointer - parseInt(db_stats[tables[active_table]].min, 10);

								// Total progress combines the completed ranges of fully scanned tables with the progress in the current table.
								let totalProgress = completedRange + currentTableProgress;
								//console.log(`Pseudo progress: ${totalProgress} /  ${db_max_range}`);

								// Calculate the overall progress as a percentage of the total range that was initially determined.
								let progressPercentage = (totalProgress / db_max_range) * 100;

								// Update the UI to reflect the current progress percentage, formatted to two decimal places for precision.
								// $('#progress').html(progressPercentage.toFixed(2) + '%');

								// Log progress details for debugging and verification purposes.
								wpmr_log(`Pseudo progress: ${progressPercentage}% (Total: ${totalProgress}, Range: ${db_max_range})`);
							}

							function init_file_scan($now) {
								if (files.length) { // if the file scan will be initialised, let's reset the progress
									wpmr_update_ui({
										percent: 0,
										time_elapsed: Math.floor((new Date()).getTime() / 1000) - orig_timestamp
									});
									setTimeout(scan_files, 1000);
								}
								else {
									console.log('else');
									window.onbeforeunload = null;
									if (wpmr_init_scan.do_file_scan) { // no files but file scan was chosen
										console.log('else do_file_scan');
										alert("There are no files to scan.\nBroken WordPress Install?");
									}
									else {
										console.log('else !do_file_scan');
										$('#file_results').html('<p><strong><em>Skipped file-scan on request.</em></strong></p>');
										reset_ui($);
									}
								}
							}

							function scan_files() {
								$('.engine_status').html('scanning files…');
								batchsize = (iteration == 0) ? $('#wpmr_batchsize').val() : batchsize;

								if (!files.length) {
									return;
								}
								wpmr_scan_files = {
									action: "wpmr_scan_files",
									wpmr_scan_files_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_scan_files' ) ); ?>',
									regex: regex,
									timestamp: record,
										<?php
										// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This read-only admin render flag only enables extra browser debug output.
										if ( isset( $_REQUEST['debug'] ) ) {
											echo 'debug: true,' . PHP_EOL;
										}
										?>
									iteration: iteration,
									suspicious: document.getElementById('show_suspicious').checked,
									files: files.splice(0, batchsize).map(function (filename) { return btoa(encodeURIComponent(filename)); }),
								};
								
								lastbatch = wpmr_scan_files.files.map(function (filename) { return decodeURIComponent(atob(filename)); });
								siteroot = '<?php echo esc_js( $this->get_home_dir() ); ?>';
								lastbatch.forEach((element, index) => {
									setTimeout(function () { $('#file_scroll').html('<p class="file_name">' + element.replace(siteroot, '') + '</p>'); }, (index * 1000 / batchsize));
								});
								$.ajax({
									url: ajaxurl,
									method: 'POST',
									timeout: wpmr_server_max_execution_time,
									data: wpmr_scan_files,
									success: function (scan_status) { },
									complete(jqXHR, textStatus) {
										// wpmr_dir(lastbatch);
										if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('debug') & jqXHR.responseJSON.debug.length) {
											console.dir(jqXHR.responseJSON.debug);
										}
										var scan_status = {};
										if (textStatus == "success" && jqXHR.hasOwnProperty('responseJSON')) {
											scan_status = jqXHR.responseJSON.report;
										} else { // ajax failure
											console.group("%cAjax Batch Scan Failure → " + Math.floor((new Date()).getTime() / 1000), 'text-transform:uppercase;font-weight:bold;color:#df2040;');
											console.table(lastbatch);
											console.groupEnd();
											Object.entries(lastbatch).forEach(([key,value]) => {
												if (iteration == 0) {
													failed.push(value);
												}
												else {
													scan_status[value] = JSON.parse('{"severity" : "skipped","signature":"failure","message":"Invalid scan response"}');
												}
											});
										}
										orig_timestamp = Math.floor((new Date()).getTime() / 1000);
										html = '';
										if (Object.keys(scan_status).length) {
											Object.entries(scan_status).forEach(([key,
												value
											]) => {
												results.infectedfiles[key] = value;
												if (value.severity == 'high' || value.severity == 'severe') {
													severe++;
													severeFileCount++;
												}
												else {
													if (value.severity != 'skipped') { // flag suspicious only if the file has been scanned
														suspicious++;
														suspiciousFileCount++;
													}
												}
											});
											found_files = Object.keys(results.infectedfiles).length;
											found_anything = 1;
										}
										if (found_files) {
											html = '<table id="file_records"><tbody>';
										}
										Object.entries(results.infectedfiles).forEach(([key, value]) => {
											if (wpmr_is_pro) {
												clean_tip = 'Delete this file from server?';
											}
											else {
												clean_tip = 'This feature is only available in paid version.';
											}                                    
											<?php
											if ( 1 || $this->is_advanced_edition() ) {
												echo 'has_seasoned_eyes = key;';
											} else {
												echo 'has_seasoned_eyes = key.split(/[\\/]/).pop();';
											}
											?>
											html += '<tr class="detected"><td class="inspect"><span class="wpmr_inspect_file button malcure-button-primary" data-file="' + key + '">Click To Fix</span></td><td class="level"><a target="_blank" class="threat ' + value.severity + '" href="' + window.wpmrUtm.buildIocUrl(value.signature, 'file') + '">' + value.message + ' <span class="wpmr_offset">' + value.signature + '</span></a></td><td class="infected_file"><pre class="recorded_file">' + has_seasoned_eyes + '</pre></td></tr>';
										});
										if (suspicious && !severe) {
											show_cta_suspicious($);
										}
										else {
											if (severe) {
												show_cta_severe($);
											}
										}
										if (found_files) {
											html += '</tbody></table>';
										} else {
											html = '<p id="file_scan_blink" class="blink">Nothing yet&hellip;</p>';
										}
										$('#file_results').html(html);
										results.filesremaining = files.length;

										if (iteration === 0) {
										}
										else {
											if (!results.iterationUpdated) { // update this only once
												results.iterationUpdated = 1;
												results.totalfiles = files.length;
												results.starttime = Math.floor((new Date()).getTime() / 1000);
											}
										}
										if (textStatus == "success" && jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('memory') && jqXHR.responseJSON.memory > results.memory) {
											results.memory = jqXHR.responseJSON.memory;
										}
										results.memory_limit = '';
										if (textStatus == "success" && jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('memory_limit')) {
											results.memory_limit = ' / ' + jqXHR.responseJSON.memory_limit;
										}
										results.scanspeed = (results.totalfiles - results.filesremaining) / ((Math.floor((new Date()).getTime() / 1000) - results.starttime));
										$('#percent').fadeTo(2000, 1);
										results.cpu = [];
										try {
											if (textStatus == "success" && jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('cpu') && Array.isArray(jqXHR.responseJSON.cpu)) {
												results.cpu = jqXHR.responseJSON.cpu;
											}
											else {
											}
											results.cpu = results.cpu.join(' / ');
										}
										catch (e) {
											console.dir(e);
										}
										$('#lcd').html('<table>' +
											'<tr id="lcd_tr_speed"><th><span class="data_head">Rate</span><span class="colon">&nbsp;:</span></th><td id="lcd_speed">' + (results.scanspeed).toFixed(0) + '&nbsp;items / sec</td></tr>' +
											'<tr id="lcd_tr_ram"><th><span class="data_head">RAM</span><span class="colon">&nbsp;&nbsp;:</span></th><td title="Max Mem Utilized" id="lcd_ram">' + parseFloat(results.memory).toFixed(0) + 'M' + results.memory_limit + '</td></tr>' +
											'<tr id="lcd_tr_cpu"><th><span class="data_head">CPU</span><span class="colon">&nbsp;&nbsp;:</span></th><td title="Avg. CPU Load 1 / 5 / 15" id="lcd_cpu">' + results.cpu + '</td></tr></table>').fadeTo(2000, .5);
										$('#files_remaining').html(results.filesremaining);
										if (iteration === 0) {
											results.timeelapsed = (Math.floor((new Date()).getTime() / 1000) - results.starttime);
										} else {
											results.timeelapsed = (Math.floor((new Date()).getTime() / 1000) - record);
										}
										$('#time_elapsed').html(msToTime(results.timeelapsed * 1000));
										$('#scan_speed').html((results.scanspeed).toFixed(0) + ' items / sec.');
										results.timeremaining = results.filesremaining / results.scanspeed;
										$('#time_remaining').html(msToTime(results.timeremaining * 1000));
										percent = ((results.totalfiles - results.filesremaining) / results.totalfiles) * 100;
										wpmr_update_percentage(parseFloat(percent).toFixed(0));
										wpmr_start_marquee();
										$('#percent').html('<span class="percentage">' + parseFloat(percent).toFixed(0) + '%</span><br /><div id="time_counter">' + msToTime(results.timeelapsed * 1000) + '</div>');
										$('.gauge_c').css('transform', 'rotate( ' + (.005 * percent) + 'turn)');

										if (files.length) {
											scan_files();
										}
										else { // this is the last batch
											iteration++;
											if (failed.length && iteration == 1) {
												files = failed;
												failed = [];
												batchsize = 1;
												console.error("%cFiles Failed Due to Ajax Error → %i", 'text-transform:uppercase;font-weight:bold;color:#df2040;', files.length);
												console.group('Phase 2');
												console.table(files);
												console.groupEnd();
												console.warn('Initiating partial rescan due to previous failures…');
												$('.engine_status').html('re-scanning…');
												scan_files();
											}
											else {
												reset_ui($);
											}
										}
									},
									error: function (jqXHR, textStatus, errorThrown) { }
								});
							} // EO fn scan_files

						}, // wpmr_init_scan success

						error: function (oJqXHR, sStatus, sErrorThrown) {
							console.dir(sStatus + ' ' + oJqXHR.status);
							window.onbeforeunload = null;
							reset_ui($);
							$('#service_cta').html('<div class="wpmr_init_error" style="background:hsla(350,65%,50%,1);padding:1em;color:white;"><h1 style="text-transform:capitalize;color:white;">' + sStatus + ' ' + oJqXHR.status + '</h1><p>Please make sure that WordPress is working before trying to scan. <a href="<?php echo esc_url( MALCURE_API . '/blog/security/wordpress-debugging-tools/' ); ?>" target="_blank" style="color:hsla(60,100%,50%);font-weight:bold;">Click here for troubleshooting information</a>.</p></div>');
							$('#wpmr_results_box').removeClass('closed');
						}, // wpmr_init_scan error

						complete: function (jqXHR, textStatus) {
							console.dir('AJAX wpmr_init_scan complete → ' + textStatus);
						},
					}); // ajax wpmr_init_scan
				} // EO fn js_scanner

				// Refresh stat counters after first-time setup completes
				$( document ).on( 'wpmr_setup_complete', function() {
					console.log( 'wpmr_setup_complete event received. Refreshing stats...' );
					wpmr_get_stat( 'bootstrap' );
					wpmr_get_stat( 'definition_count' );
					wpmr_get_stat( 'definition_version' );
					wpmr_get_stat( 'last_updated' );
				} );
			}); //ready

			const wpmr_update_ui = (parms) => {

				$ = jQuery.noConflict();
				// wpmr_log('parms');
				// wpmr_dir(parms);
				// Engine statistics

				// checksums
				if ('checksum_count' in parms) {
					$('#checksum_count').html(parms.checksum_count);
				}

				// signature count
				if ('sig_count' in parms) {
					$('.sig_count').html(parms.sig_count);
				}

				// signature version
				if ('sig_version' in parms) {
					$('.sig_version').html(parms.sig_version);
				}

				// signature update time / 4 mins ago...
				if ('sig_date' in parms) {
					$('.sig_date').html(parms.sig_date);
				}

				// total_files
				if ('total_files' in parms) {
					$('.total_files').html(parms.total_files);
				}

				// files_remaining
				if ('files_remaining' in parms) {
					$('#files_remaining').html(parms.files_remaining);
				}

				// time_elapsed
				if ('time_elapsed' in parms) {
					$('#time_elapsed').html(msToTime(parms.time_elapsed * 1000));
				}
				if ('time_remaining' in parms) {
					$('#time_remaining').html(msToTime(parms.time_remaining * 1000));
				}

				// scan_speed (items per second)
				if ('scan_speed' in parms) {
					$('#scan_speed').html((parms.scan_speed).toFixed(0) + ' items / sec');
				}

				// Status (initialising, scanning etc...)
				if ('status' in parms) {
					$('.engine_status').html(parms.status);
				}

				// SECOND COLUMN

				// Percentage speedometer NEEDLE
				if ('percent' in parms) {
					$('.gauge_c').css('transform', 'rotate(' + (.005 * parms.percent) + 'turn)');
				}

				// Central Percentage text counter
				if ('percent' in parms && 'time_elapsed' in parms) {
					$('#percent').html('<span class="percentage">' + parseFloat(parms.percent).toFixed(0) + '%</span><div id="time_counter">' + msToTime(parms.time_elapsed * 1000) + '</div>').fadeTo(2000, 1);
				}

				// THIRD COLUMN
				if ('wpmr_skin' in parms) {
					$('#wpmr_skin').html(parms.wpmr_skin);
				}

				if (
					'scan_speed' in parms &&
					'memory' in parms &&
					'memory_limit' in parms &&
					'cpu' in parms
				) {
					$('#lcd').html('<table><tr><th><span class="data_head">Rate</span><span class="colon">&nbsp;:</span></th><td>' + parms.scan_speed.toFixed(0) + '&nbsp;items / sec</td></tr><tr><th><span class="data_head">RAM</span><span class="colon">&nbsp;&nbsp;:</span></th><td title="Max Mem Utilized">' + parseFloat(parms.memory).toFixed(0) + 'M / ' + parms.memory_limit + '</td></tr><tr><th><span class="data_head">CPU</span><span class="colon">&nbsp;&nbsp;:</span></th><td title="Avg. CPU Load 1 / 5 / 15"">' + parms.cpu.join(' / ') + '</td></tr></table>').fadeTo(2000, .5);
				}

				// #logo running or not
			};

			function log_scan_completed(){
				console.log('Scan complete');
				var duration_sec = Math.floor((new Date()).getTime() / 1000) - record;
				wpmr_ajax_request(
					'log_malware_scan_complete',
					[record, duration_sec]
				);
			}

			wpmr_debug = <?php echo( defined( 'WP_DEBUG' ) && WP_DEBUG ) ? 1 : 0; ?>;
			
			function wpmr_log($data) {
				if (!wpmr_debug) {
					return;
				}
				console.log($data);
			}

			function wpmr_dir($data) {
				if (!wpmr_debug) {
					return;
				}
				console.dir($data);
			}
			//]]>
			</script> 
			<?php
		}
	}
}
