<?php
/**
 * WPMR_Client_JS
 *
 * 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_Client_JS {

	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
		}
	}

	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 file_inspect_handler(event) {
				$ = jQuery.noConflict();
				if ( event ) {
					event.preventDefault();
				}
				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('');
				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);
							}

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

			function handle_whitelist_labels($) {
				whitelist = $('#whitelist [data-file]');
				if (whitelist.length) {
					$('#whitelist-present-placeholder').show();
					$('#whitelist-absent-placeholder').hide();
				}
				else {
					$('#whitelist-present-placeholder').hide();
					$('#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="https://malcure.com/?p=116&utm_source=wpmr_invalid_license&utm_medium=web&utm_campaign=wpmr&utm_content=' + action + '" 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 summaryParts = [];
				if (hasDbInfections) {
					summaryParts.push(dbInfectionCount + ' database infection' + (dbInfectionCount === 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;
				const recommendedPath = hasDbInfections ? 'dfy' : (severeFileCount > 0 ? 'diy' : 'dfy');
				const recommendationName = recommendedPath === 'dfy' ? 'Expert Malware Removal service' : 'Advanced Edition';
				const dfyLink = 'https://www.malcure.com/?p=107&utm_source=scanresults&utm_medium=web&utm_campaign=wpmr&utm_content=dfy_' + context;
				const diyLink = 'https://www.malcure.com/?p=116&utm_source=scanresults&utm_medium=web&utm_campaign=wpmr&utm_content=diy_' + context;
				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
					? 'Advanced Edition is active here—run repairs inside WordPress, trigger wp malcure automation, or sync signatures headlessly.'
					: '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.'
					: (recommendedPath === 'dfy'
						? 'Suspicious indicators require a human analyst before any changes are made. 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 = 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 ? 'Advanced Edition is active—launch dashboard repairs or automate via wp malcure commands.' : '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 ? '' : '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
				}));
				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
				}));
				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('scan-complete');

				// $("#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;
			}

			function wpmr_notify(audioFilename) {

				if (jQuery('#wpmr_ux_notifications_enabled').is(':checked')) {

					document.title = "✔ Malcure Scan Complete! " + wpmr_doctitle;

					setTimeout(() => {
						document.title = wpmr_doctitle;
					}, 5000);

					if (!window.Audio) {
						console.error('Audio API not supported');
						return;
					}

					if (!audioFilename) {
						console.error("No audio file specified.");
						return;
					}

					var audioPath = '<?php echo esc_js( WPMR_PLUGIN_DIR_URL ); ?>assets/sounds/' + audioFilename + '.wav';
					var audio = new Audio(audioPath);

					//audio.onerror = function() {
					//	console.error("Error: Audio file not found at " + audioPath);
					//};
					//audio.play().then(() => {
					//	console.log("Audio playing successfully");
					//}).catch(error => {
					//	console.error("Error in playing audio: ", error);
					//});
					// Check if the audio file was loaded successfully
					audio.addEventListener('canplaythrough', () => {
						audio.play();
					}, false);

					// Handle errors
					audio.addEventListener('error', (error) => {
						console.error('Error playing audio:', error);
					}, false);

				}
				else {
					console.log('Notification preferences disabled.');
				}
			}

			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;

			// 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;
			}

			/**
			 * Sends an AJAX request to the WordPress backend and handles the response.
			 *
			 * This function allows you to send data to a server-side handler and display 
			 * appropriate feedback messages based on the response.
			 *
			 * @param {string} f_call - The name of the server-side action or method to call.
			 *                          Example: 'update_setting'.
			 * @param {Array} a_args - An array of arguments to pass to the server-side method; pass empty array other-wise.
			 * @param {string} [successMessage] - (Optional) A message to display on a successful 
			 *                                     AJAX response. Wrap it in <p> tags. Defaults to console.log('Request complete.');
			 *                                     if not provided.
			 *                                     Example: '<p>Skin changed successfully to ' + 
			 *                                     $(this).val() + '.</p>'.
			 * @param {string} [failureMessage] - (Optional) A message to display on a failed 
			 *                                     AJAX response. Wrap it in <p> tags. Defaults to the server response 
			 *                                     if not provided.
			 *                                     Example: '<p>Skin change failed.</p>'.
			 *
			 * @example
			 * // Example usage
			 * 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
			 * );
			 */

			function wpmr_ajax_request(f_call, a_args) {
				// wpmr_log(arguments.callee.name);
				wpmr_messaging_arguments = arguments;
				// wpmr_dir('wpmr_messaging_arguments');
				// wpmr_dir(wpmr_messaging_arguments);
				wpmr_ajax_data = {
					wpmr_ajax_data_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_ajax_data' ) ); ?>',
					action: "wpmr_ajax_request",
					cachebust: Date.now(),
					request: [f_call, a_args],
				};

				// wpmr_dir(wpmr_ajax_data);
				$=jQuery;
				return $.ajax({ // https://api.jquery.com/jquery.ajax/
					url: ajaxurl,
					method: 'POST',
					data: wpmr_ajax_data,
					complete: function (o_jqXHR, s_textStatus) {
						// Triggers regardless of ajax success failure
						if (s_textStatus == 'success') {  // ajax res received
							if (o_jqXHR.hasOwnProperty('responseJSON')) { // is valid json
								// wpmr_dir('o_jqXHR.responseJSON');
								// wpmr_dir(o_jqXHR.responseJSON);
								if (o_jqXHR.responseJSON.hasOwnProperty('success')) { // wp_send_json_success || wp_send_json_error
									if (o_jqXHR.responseJSON.success) { // wp_send_json_success
										if (wpmr_messaging_arguments[2]) {
											wpmr_show_message(wpmr_messaging_arguments[2]);
										}
										else {
											// wpmr_show_message('Request complete.');
											wpmr_log('Request complete.');
										}
									}
									else { // wp_send_json_error
										if (wpmr_messaging_arguments[3]) {
											wpmr_show_message(wpmr_messaging_arguments[3] + ' ' + o_jqXHR.responseJSON.data);
										}
										else {
											if(/^<p>[\s\S]*<\/p>$/.test(o_jqXHR.responseJSON.data)) {
												wpmr_show_message( o_jqXHR.responseJSON.data );
											}
											else {
												wpmr_show_message('<p>' + o_jqXHR.responseJSON.data + '</p>');
											}
										}
										wpmr_dir(o_jqXHR.responseJSON);
									}
								}
								else { //wp_send_json
									if(/^<p>[\s\S]*<\/p>$/.test(o_jqXHR.responseJSON)) {
										wpmr_show_message( o_jqXHR.responseJSON );
									}
									else {
										wpmr_show_message('<p>' + o_jqXHR.responseJSON + '</p>');
									}
								}
							}
						}
						else {
							wpmr_log('Ajax textstatus unsuccessful.');
						}

					},
					success: function (o_data, s_textStatus, o_jqXHR) {
						// Received ajax response. Defer to complete callback
						wpmr_dir('Success: Request was successful.');
						wpmr_log(o_jqXHR.responseJSON);
					},
					error: function (o_jqXHR, s_textStatus, s_errorThrown) {
						// Ajax request failed. Defer to complete callback
						wpmr_dir('Error: Request failed.');
						wpmr_log(o_jqXHR.responseJSON);
					}

				});
			}

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

				$('#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",
						cachebust: Math.floor((new Date()).getTime() / 1000),
						user: {
							fn: $('#wpmr_fn').val(),
							ln: $('#wpmr_ln').val(),
							email: $('#wpmr_eml').val(),
							key: '<?php echo esc_js( md5( site_url() ) ); ?>'
						}
					};

					$.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><strong>Thank you for registering!</strong></p>');
								window.location.reload();
							} 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,
						cachebust: Math.floor((new Date()).getTime() / 1000)
					};
					$.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) {
										$('.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
				}

				wpmr_get_stat('bootstrap');
				wpmr_get_stat('definition_count');
				wpmr_get_stat('definition_version');
				wpmr_get_stat('last_updated');
				wpmr_get_stat('hidden_files');
				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_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",
						cachebust: Math.floor((new Date()).getTime() / 1000),
						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();
						}
					});
					
				});

				$("#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",
						cachebust: Math.floor((new Date()).getTime() / 1000),
					};
					$.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) {
									$('#update_response').html('<span class="wpmr_notice_success">Successfully updated definitions.</span>');
									$('.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 {
									if (jqXHR.hasOwnProperty('responseJSON') && jqXHR.responseJSON.hasOwnProperty('data') && jqXHR.responseJSON.data.length) {
										$('#update_response').html('<span class="wpmr_notice_error">Failed. Reason: ' + jqXHR.responseJSON.data + '</span>');
									}
								}
							}
							else {  // no ajax response
									// $('#update_response').html('<span class="wpmr_notice_error">Unknown failure.</span>');
								$('#update_response').html('<span class="wpmr_notice_error">Request failed. Please try again.</span>');
							}
						},
						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'),
						cachebust: Date.now(),
					};
					$.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').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 = {
							'critical':1,'severe':1,'high':1,'medium':1,'low':1,
							'warning':1,'notice':1,'invalid':1,'suspicious':1,
							'error':1,'vulnerable':1,'skipped':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;
					}

					// 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;
						}
						var t = textFrom(wrap);
						if (t) {
							return t.split(/\s*\n+\s*/).filter(Boolean);
						} else {
							return [];
						}
					}

					// ---------- 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 += '— 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 += '— 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 severeCount = 0;
					for (var i=0; i<files.length; i++) {
						if (/^severe\b/i.test(files[i])) {
							severeCount++;
						}
					}
					for (var k=0; k<vulnLines.length; k++) {
						if (/^(severe|critical)\b/i.test(vulnLines[k])) {
							severeCount++;
						}
					}
					if (severeCount > 0) {
						out += '\n\nMalcure Detected ' + severeCount + ' Severe Infection(s)\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);

				$('#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
						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),
						cachebust: Math.floor((new Date()).getTime() / 1000),
					};
					$.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', '');
									} 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
						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),
						cachebust: Math.floor((new Date()).getTime() / 1000),
					};
					$.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', '');
									} 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_file_whitelist').click(function (event) {
					file_path = $('#wpmr_inspect_file').attr('data-file');
					real_path = file_path;
					if (!file_path) {
						$('#file_op_status').html('No file selected.'); // update the status message
						return;
					}

					if (!confirm("\nAre you sure you want to whitelist this file?\n\n" + file_path + "\n")) {
						return;
					}
					$('#file_op_status').html('');
					event.preventDefault();
					var element = this;

					wpmr_whitelist_file = {
						wpmr_whitelist_nonce: '<?php echo esc_js( wp_create_nonce( 'wpmr_whitelist_file' ) ); ?>',
						action: "wpmr_whitelist_file",
						file: btoa(file_path),
						cachebust: Math.floor((new Date()).getTime() / 1000),
					};
					$.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(''); // clear the file inspector contents
										$('#file_op_status').html(jqXHR.responseJSON.data); // update the UI about response status
										$('.wpmr_inspect_file[data-file="' + file_path + '"]').closest('tr.detected').fadeOut(1000, function () { $(this).remove(); }); // remove the file from infected results
										$('#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!');
							}
						},
					});

				});

				$('#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),
						cachebust: Math.floor((new Date()).getTime() / 1000),
					};

					$.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!');
							}
						},
					});
				});

				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;
					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');
					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) {
						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;
					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,
						cachebust: Date.now(),
						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,
						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="<?php echo esc_js( trailingslashit( MALCURE_API ) . '?p=2074&ssig=' ); ?>' + vuln.signature + '&utm_source=scanrun&utm_medium=web&utm_campaign=wpmrsevere">' + 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="<?php echo esc_js( trailingslashit( MALCURE_API ) . '?p=2074&ssig=' ); ?>' + vuln.signature + '&utm_source=scanrun&utm_medium=web&utm_campaign=wpmrsevere">' + 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="<?php echo esc_js( trailingslashit( MALCURE_API ) . '?p=2074&ssig=' ); ?>' + vuln.signature + '&utm_source=scanrun&utm_medium=web&utm_campaign=wpmrsevere">' + 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;
							}

							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"}}
							
							if (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',
												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">';
																	db_batch_html += '<td class="level"><a target="_blank" class="threat ' + result.severity + '" href="<?php echo esc_js( trailingslashit( MALCURE_API ) . '?p=2074&ssig=' ); ?>' + result.signature + '&utm_source=scanrun&utm_medium=web&utm_campaign=wpmr' + result.severity + '">' + 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 = parseInt( db_stats[tables[active_table]].min );                                                
												// 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);
							}

							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
										if ( isset( $_REQUEST['debug'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Debug parameter validated by nonce in parent AJAX handler
											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',
									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="<?php echo esc_js( trailingslashit( MALCURE_API ) . '?p=2074&ssig=' ); ?>' + value.signature + '&utm_source=scanrun&utm_medium=web&utm_campaign=wpmr' + value.severity + '">' + 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="https://malcure.com/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
			}); //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');
				wpmr_ajax_request(
					'log_malware_scan_complete'
				);
			}

			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
		}
	}
}
