/**
 * Backup & Restore admin scripts (step-based backup + progress UI).
 *
 * @package BackupRestore
 */
(function ($) {
	'use strict';

	var activityTimer = null;
	var activeBackupId = '';
	/** UpdraftPlus-style: secret for restore ticks if admin session dies after DB import */
	var restoreContinuationActive = false;
	/** @type {'backup'|'restore'} */
	var progressJobKind = 'backup';
	var lastTickTs = 0;
	/** Lines collected during the current backup job for the inline live log under the progress bar. */
	var liveBackupLogText = '';
	/** Lines collected during the current restore job (full-page restore progress). */
	var liveRestoreLogText = '';
	/** Set when the styled restore confirm is open; cleared after proceed or cancel. */
	var restoreConfirmPending = null;
	var partPanelBackup = '';
	var partPanelPart = '';
	var browseZipEntries = [];
	var browseZipTreeRoot = null;
	/** When true, folder listing comes from server (full-zip scan per folder); tree is not used. */
	var browseZipLazy = false;
	var browseCurrentPath = '';
	var browseZipContext = {
		backupId: '',
		part: '',
		archiveLabel: '',
		entryNonce: '',
		adminPostUrl: '',
		mode: '',
		sqlDownloadUrl: ''
	};

	/** @type {JQuery.jqXHR|null} */
	var bundlePrepareXhr = null;
	/** When true, the browser download started; ignore any late tick responses. */
	var bundleHandoffDone = false;
	var bundleAutoCloseTimer = null;

	function formatBundleElapsed(totalSec) {
		var m = Math.floor(totalSec / 60);
		var s = totalSec % 60;
		return m + ':' + (s < 10 ? '0' : '') + s;
	}

	function closeBundlePrepareModal() {
		if (bundleAutoCloseTimer !== null) {
			window.clearTimeout(bundleAutoCloseTimer);
			bundleAutoCloseTimer = null;
		}
		if (!bundleHandoffDone && bundlePrepareXhr && bundlePrepareXhr.abort) {
			bundlePrepareXhr.abort();
			bundlePrepareXhr = null;
		}
		bundleHandoffDone = false;
		$('#wpps-br-bundle-modal').attr('hidden', true);
		$('#wpps-br-bundle-timeline').empty();
		$('#wpps-br-bundle-cancel').text(str('bundleCancel', 'Cancel'));
	}

	var bundleTimelineLabels = {
		scan: 'Scanning backup',
		pack: 'Packing ZIP bundle',
		stage: 'Preparing download',
		download: 'Downloading in browser'
	};

	function seedBundlePrepareTimeline() {
		renderBundleTimeline({
			steps: [
				{ key: 'scan', status: 'active', started_at: Date.now() / 1000 },
				{ key: 'pack', status: 'pending' },
				{ key: 'stage', status: 'pending' }
			]
		});
	}

	/**
	 * Final timeline once the server handoff is complete (all prep steps done + browser download started).
	 *
	 * @param {object|null} serverProgress Optional progress payload from bundle_prepare.
	 */
	function renderBundleDownloadReadyTimeline(serverProgress) {
		var src = serverProgress && serverProgress.steps ? serverProgress.steps : [];
		var keys = ['scan', 'pack', 'stage'];
		var steps = [];

		for (var i = 0; i < keys.length; i++) {
			var found = null;
			for (var j = 0; j < src.length; j++) {
				if (src[j].key === keys[i]) {
					found = src[j];
					break;
				}
			}
			steps.push(
				Object.assign({}, found || {}, {
					key: keys[i],
					status: 'done'
				})
			);
		}
		steps.push({ key: 'download', status: 'done' });
		renderBundleTimeline({ steps: steps });
	}

	function renderBundleTimeline(data) {
		var $tl = $('#wpps-br-bundle-timeline');
		if (!$tl.length || !data || !data.steps || !data.steps.length) {
			return;
		}
		var now = Date.now() / 1000;
		var html = '';

		for (var i = 0; i < data.steps.length; i++) {
			var step = data.steps[i];
			var cls = 'wpps-br-btl-step';
			var icon = '';
			var timeHtml = '';
			var label = bundleTimelineLabels[step.key] || step.key;

			if (step.status === 'done') {
				cls += ' wpps-br-btl-step--done';
				icon = '<span class="dashicons dashicons-yes-alt" style="color:#46b450"></span>';
				if (step.elapsed != null) {
					timeHtml = '<span class="wpps-br-btl-time">' + formatBundleElapsed(Math.round(step.elapsed)) + '</span>';
				}
				if (step.key === 'scan' && step.total_size) {
					label += ' \u2014 ' + (step.parts ? step.parts.length : 0) + ' parts, ' + formatBytes(step.total_size);
				}
			} else if (step.status === 'active') {
				cls += ' wpps-br-btl-step--active';
				icon = '<span class="dashicons dashicons-update" style="animation:rotation 1.4s linear infinite"></span>';
				if (step.started_at) {
					var elapsed = Math.max(0, Math.round(now - step.started_at));
					timeHtml = '<span class="wpps-br-btl-time">' + formatBundleElapsed(elapsed) + '</span>';
				}
				if (step.key === 'pack' && step.total_size) {
					label += ' (' + formatBytes(step.total_size) + ')';
				}
				if (step.label_extra) {
					label += ' — ' + String(step.label_extra);
				}
			} else {
				icon = '<span class="dashicons dashicons-marker" style="color:#ccc"></span>';
			}

			html += '<div class="' + cls + '">';
			html += '<span class="wpps-br-btl-icon">' + icon + '</span>';
			html += '<span class="wpps-br-btl-label">' + label + '</span>';
			html += timeHtml;
			html += '</div>';

			if (step.key === 'scan' && step.status === 'done' && step.parts && step.parts.length) {
				html += '<div class="wpps-br-btl-parts">';
				for (var j = 0; j < step.parts.length; j++) {
					var part = step.parts[j];
					html += '<span class="wpps-br-btl-chip">';
					html += '<span>' + part.name + '</span>';
					html += '<span class="wpps-br-btl-chip-size">' + formatBytes(part.size) + '</span>';
					html += '</span>';
				}
				html += '</div>';
			}
		}

		$tl.html(html);
	}

	/**
	 * Full bundle download: same native browser path as individual parts (admin-post iframe).
	 * No File System Access / fetch chunking — those caused save-dialog hangs and fake “chunk 1 of 1” UI.
	 */
	function startBundleFileDownload(url, fileSize, $modal, serverProgress) {
		bundleHandoffDone = true;
		renderBundleDownloadReadyTimeline(serverProgress || null);
		var sizeLabel = fileSize > 0 ? formatBytes(fileSize) : '';
		$('#wpps-br-bundle-modal-title').text(str('bundleDownloadTitle', 'Download started'));
		$('#wpps-br-bundle-modal-lead').text(
			sizeLabel
				? str(
						'bundleDownloadLeadSized',
						'Your browser is downloading %1$s now. Check the downloads bar for progress — you can close this dialog.'
				  ).replace('%1$s', sizeLabel)
				: str(
						'bundleDownloadLead',
						'Your browser is downloading the file now. Check the downloads bar for progress — you can close this dialog.'
				  )
		);
		$('#wpps-br-bundle-cancel').text(str('bundleClose', 'Close'));
		triggerBrFileDownload(url);
		if (bundleAutoCloseTimer !== null) {
			window.clearTimeout(bundleAutoCloseTimer);
		}
		bundleAutoCloseTimer = window.setTimeout(function () {
			bundleAutoCloseTimer = null;
			if ($modal && $modal.length && !$modal.attr('hidden')) {
				closeBundlePrepareModal();
			}
		}, 2500);
	}

	function str(key, fallback) {
		if (wppsBackupRestore && wppsBackupRestore.strings && wppsBackupRestore.strings[key]) {
			return wppsBackupRestore.strings[key];
		}
		return fallback;
	}

	/** Fetches lock snapshot via admin-ajax (see wpps_br_lock_debug). */
	function logLockDebug(context, backupId) {
		if (typeof wppsBackupRestore === 'undefined' || !window.console) {
			return;
		}
		var payload = {
			action: 'wpps_br_lock_debug',
			nonce: wppsBackupRestore.nonce
		};
		if (backupId) {
			payload.backup_id = backupId;
		}
		$.post(wppsBackupRestore.ajaxUrl, payload)
			.done(function (res) {
				if (res && res.success && res.data) {
					console.warn('[WPPS Backup/Restore lock debug]', context || '', res.data);
				} else {
					console.warn('[WPPS Backup/Restore lock debug] unexpected response', context, res);
				}
			})
			.fail(function (xhr) {
				console.warn('[WPPS Backup/Restore lock debug] HTTP error', context, xhr && xhr.status);
			});
	}

	/** Append deploy-proof fields from restore AJAX (br_engine) or explain missing. */
	function appendBrRestoreAjaxDebug(msg, data) {
		var m = msg == null ? '' : String(msg);
		if (!data || typeof data !== 'object') {
			return (
				m +
				'\n\n[WPPS debug: no JSON data — open DevTools → Network → admin-ajax.php (action wpps_br_restore_tick) → Response.]'
			);
		}
		if (data.br_engine) {
			return (
				m +
				'\n\n[WPPS debug: br_engine=' +
				data.br_engine +
				' br_service_engine=' +
				(data.br_service_engine || '?') +
				' BackupService.php mtime=' +
				data.br_service_mtime +
				']'
			);
		}
		var low = m.toLowerCase();
		if (low.indexOf('backup is running') !== -1 && low.indexOf('finish it before restoring') !== -1) {
			return (
				m +
				'\n\n[WPPS debug: This exact sentence was removed in plugin v1.4.2+. Your response has no br_engine — WordPress is still loading old BackupService.php from disk (wrong plugin path, zip not replaced, or PHP opcode cache).]'
			);
		}
		return m + '\n\n[WPPS debug: response missing br_engine — not the 1.4.4+ restore handler.]';
	}

	function closePartPanel() {
		$('#wpps-br-part-panel').attr('hidden', true);
		$('.wpps-br-part-trigger--active').removeClass('wpps-br-part-trigger--active');
		partPanelBackup = '';
		partPanelPart = '';
	}

	function escapeHtml(text) {
		var div = document.createElement('div');
		div.textContent = text == null ? '' : String(text);
		return div.innerHTML;
	}

	function escapeAttr(text) {
		return String(text == null ? '' : text)
			.replace(/&/g, '&amp;')
			.replace(/"/g, '&quot;')
			.replace(/</g, '&lt;')
			.replace(/\r?\n/g, ' ');
	}

	/**
	 * Compact verify row: icon (pass/fail) + heading only; detail in native tooltip.
	 */
	function renderVerifyChecklistRow(pass, heading, detail) {
		var tip = detail ? String(detail) : '';
		var aria = tip ? String(heading) + '. ' + tip : String(heading);
		var iconWrapClass = pass
			? 'wpps-br-verify-checklist__icon wpps-br-verify-checklist__icon--pass'
			: 'wpps-br-verify-checklist__icon wpps-br-verify-checklist__icon--fail';
		var dash = pass ? 'dashicons-yes' : 'dashicons-no-alt';
		var titleAttr = tip ? ' title="' + escapeAttr(tip) + '"' : '';
		return (
			'<li class="wpps-br-verify-checklist__item"' +
			titleAttr +
			' aria-label="' +
			escapeAttr(aria) +
			'">' +
			'<span class="' +
			iconWrapClass +
			'" aria-hidden="true"><span class="dashicons ' +
			dash +
			'"></span></span>' +
			'<span class="wpps-br-verify-checklist__label">' +
			escapeHtml(heading) +
			'</span>' +
			'</li>'
		);
	}

	/**
	 * Compact pill for backup-part verification (icon + label; detail in tooltip).
	 */
	function renderVerifyPartsStripItem(pass, heading, detail) {
		var tip = detail ? String(detail) : '';
		var aria = tip ? String(heading) + '. ' + tip : String(heading);
		var iconClass = pass
			? 'wpps-br-verify-parts-strip__icon wpps-br-verify-parts-strip__icon--pass'
			: 'wpps-br-verify-parts-strip__icon wpps-br-verify-parts-strip__icon--fail';
		var dash = pass ? 'dashicons-yes' : 'dashicons-no-alt';
		var titleAttr = tip ? ' title="' + escapeAttr(tip) + '"' : '';
		return (
			'<li class="wpps-br-verify-parts-strip__item"' +
			titleAttr +
			' aria-label="' +
			escapeAttr(aria) +
			'">' +
			'<span class="' +
			iconClass +
			'" aria-hidden="true"><span class="dashicons ' +
			dash +
			'"></span></span>' +
			'<span class="wpps-br-verify-parts-strip__label">' +
			escapeHtml(heading) +
			'</span>' +
			'</li>'
		);
	}

	function fmFolderIcon() {
		return (
			'<span class="wpps-br-fm-icon wpps-br-fm-icon--folder" aria-hidden="true">' +
				'<svg width="16" height="16" viewBox="0 0 24 24" focusable="false">' +
				'<path fill="currentColor" d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>' +
				'</svg></span>'
		);
	}

	function fmFileIcon() {
		return (
			'<span class="wpps-br-fm-icon wpps-br-fm-icon--file" aria-hidden="true">' +
				'<svg width="16" height="16" viewBox="0 0 24 24" focusable="false">' +
				'<path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>' +
				'</svg></span>'
		);
	}

	function fixAmpInUrl(u) {
		if (!u || u === '#') {
			return u;
		}
		return String(u).replace(/&amp;/gi, '&');
	}

	/**
	 * Build multipart body explicitly so the file is always attached. Some browsers
	 * omit file inputs from `new FormData(form)` when fields live inside a closed
	 * <details>, which makes admin-ajax see an empty $_FILES.
	 */
	function buildBrUploadFormData(form) {
		var fd = new FormData();
		if (!form || !form.elements) {
			return fd;
		}
		var i, el, f, j, tag, typ;
		for (i = 0; i < form.elements.length; i++) {
			el = form.elements[i];
			if (!el.name || el.disabled) {
				continue;
			}
			tag = el.tagName.toLowerCase();
			if (tag === 'input') {
				typ = (el.type || '').toLowerCase();
				if (typ === 'file') {
					f = el.files;
					if (f && f.length > 0) {
						var fname = el.name || 'backup_file[]';
						for (var fi = 0; fi < f.length; fi++) {
							fd.append(fname, f[fi]);
						}
					}
				} else if (typ === 'checkbox' || typ === 'radio') {
					if (el.checked) {
						fd.append(el.name, el.value);
					}
				} else if (typ !== 'button' && typ !== 'submit' && typ !== 'reset') {
					fd.append(el.name, el.value);
				}
			} else if (tag === 'select') {
				if (el.multiple) {
					for (j = 0; j < el.options.length; j++) {
						if (el.options[j].selected) {
							fd.append(el.name, el.options[j].value);
						}
					}
				} else {
					fd.append(el.name, el.value);
				}
			} else if (tag === 'textarea') {
				fd.append(el.name, el.value);
			}
		}
		return fd;
	}

	function triggerBrFileDownload(url) {
		if (!url || url === '#') {
			return;
		}
		var iframe = document.createElement('iframe');
		iframe.setAttribute('hidden', 'hidden');
		iframe.setAttribute('aria-hidden', 'true');
		iframe.style.cssText = 'position:absolute;width:0;height:0;border:0;clip:rect(0,0,0,0)';
		iframe.src = url;
		document.body.appendChild(iframe);
		window.setTimeout(function () {
			try {
				iframe.remove();
			} catch (e) {}
		}, 120000);
	}

	function escapeAttr(s) {
		return String(s == null ? '' : s)
			.replace(/&/g, '&amp;')
			.replace(/"/g, '&quot;')
			.replace(/'/g, '&#39;')
			.replace(/</g, '&lt;');
	}

	function parseZipEntryRaw(raw) {
		if (raw && typeof raw === 'object' && raw.name != null) {
			return {
				path: String(raw.name).replace(/\\/g, '/'),
				size: typeof raw.size === 'number' && raw.size >= 0 ? raw.size : null
			};
		}
		return { path: String(raw || '').replace(/\\/g, '/'), size: null };
	}

	function buildTreeFromPaths(paths) {
		var root = { children: {}, isDir: true, name: '' };
		(paths || []).forEach(function (raw) {
			var parsed = parseZipEntryRaw(raw);
			var path = parsed.path;
			var fileSize = parsed.size;
			var dirOnly = /\/\s*$/.test(path);
			var trimmed = path.replace(/\/+$/, '');
			var parts = trimmed.split('/').filter(function (seg) {
				return seg.length > 0;
			});
			if (!parts.length) {
				return;
			}
			var node = root;
			for (var i = 0; i < parts.length; i++) {
				var seg = parts[i];
				var isLast = i === parts.length - 1;
				if (!node.children[seg]) {
					var isDir = !isLast || dirOnly;
					node.children[seg] = {
						name: seg,
						children: {},
						isDir: isDir,
						fileSize: isDir ? null : fileSize
					};
				} else {
					if (!isLast || dirOnly) {
						node.children[seg].isDir = true;
					} else if (fileSize != null) {
						node.children[seg].fileSize = fileSize;
					}
				}
				node = node.children[seg];
			}
		});
		return root;
	}

	function formatBytes(n) {
		if (n == null || n < 0 || !isFinite(n)) {
			return '';
		}
		var u = ['B', 'KB', 'MB', 'GB', 'TB'];
		var i = 0;
		var v = n;
		while (v >= 1024 && i < u.length - 1) {
			v /= 1024;
			i++;
		}
		var decimals = i === 0 ? 0 : v < 10 ? 2 : v < 100 ? 1 : 0;
		return v.toFixed(decimals) + ' ' + u[i];
	}

	function parentBrowsePath(relPath) {
		var s = (relPath || '').replace(/\\/g, '/').replace(/\/+$/, '');
		if (!s) {
			return '';
		}
		var i = s.lastIndexOf('/');
		return i <= 0 ? '' : s.substring(0, i);
	}

	function browseZipGetNodeAtPath(root, relPath) {
		if (!root) {
			return null;
		}
		var s = (relPath || '').replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
		if (!s) {
			return root;
		}
		var parts = s.split('/').filter(function (p) {
			return p.length > 0;
		});
		var node = root;
		for (var i = 0; i < parts.length; i++) {
			var seg = parts[i];
			if (!node.children || !node.children[seg]) {
				return null;
			}
			node = node.children[seg];
			var isLast = i === parts.length - 1;
			if (!isLast && !node.isDir) {
				return null;
			}
		}
		return node;
	}

	function browseZipListChildren(root, relPath) {
		var node = browseZipGetNodeAtPath(root, relPath);
		if (!node || !node.isDir || !node.children) {
			return [];
		}
		var keys = Object.keys(node.children);
		keys.sort(function (a, b) {
			var ca = node.children[a],
				cb = node.children[b];
			if (ca.isDir !== cb.isDir) {
				return ca.isDir ? -1 : 1;
			}
			return a.localeCompare(b, undefined, { sensitivity: 'base' });
		});
		var base = (relPath || '').replace(/\\/g, '/').replace(/\/+$/, '');
		var out = [];
		keys.forEach(function (k) {
			var ch = node.children[k];
			var full = base === '' ? k : base + '/' + k;
			out.push({ name: k, isDir: ch.isDir, relPath: full, size: ch.fileSize });
		});
		return out;
	}

	function renderFmBreadcrumbs(relPath) {
		var $nav = $('#wpps-br-fm-breadcrumbs');
		var rootLabel = str('fmRoot', 'Archive root');
		var s = (relPath || '').replace(/\\/g, '/').replace(/\/+$/, '');
		var parts = s ? s.split('/').filter(Boolean) : [];
		var html = '';
		var prefixAcc = '';
		html +=
			'<button type="button" class="wpps-br-fm-crumb' +
			(!parts.length ? ' wpps-br-fm-crumb--current' : '') +
			'" data-fm-path="">' +
			escapeHtml(rootLabel) +
			'</button>';
		parts.forEach(function (seg, idx) {
			prefixAcc = prefixAcc === '' ? seg : prefixAcc + '/' + seg;
			var isLast = idx === parts.length - 1;
			html +=
				'<span class="wpps-br-fm-crumb-sep" aria-hidden="true">/</span>' +
				'<button type="button" class="wpps-br-fm-crumb' +
				(isLast ? ' wpps-br-fm-crumb--current' : '') +
				'" data-fm-path="' +
				escapeAttr(prefixAcc) +
				(isLast ? '" disabled' : '"') +
				'>' +
				escapeHtml(seg) +
				'</button>';
		});
		$nav.html(html);
	}

	function clearFmSelection() {
		$('#wpps-br-fm-tbody .wpps-br-fm-row--selected').removeClass('wpps-br-fm-row--selected');
		$('#wpps-br-fm-download').prop('disabled', true).removeAttr('data-entry-path');
		$('#wpps-br-fm-status').text('');
	}

	function paintBrowseRowsFromApi(rows) {
		var $tb = $('#wpps-br-fm-tbody');
		$tb.empty();
		clearFmSelection();
		if (!rows.length) {
			$tb.append(
				'<tr><td colspan="3" class="wpps-br-fm-empty">' +
					escapeHtml(str('fmEmptyFolder', 'This folder is empty.')) +
					'</td></tr>'
			);
			return;
		}
		rows.forEach(function (r) {
			var typeLabel = r.isDir
				? str('fmTypeFolder', 'Folder')
				: str('fmTypeFile', 'File');
			var sizeCell = r.isDir ? '—' : r.size != null && r.size >= 0 ? formatBytes(r.size) : '—';
			var sizeAttr =
				!r.isDir && r.size != null && r.size >= 0 ? ' data-file-size="' + String(r.size) + '"' : '';
			var rowClass = 'wpps-br-fm-row' + (r.isDir ? ' wpps-br-fm-row--dir' : ' wpps-br-fm-row--file');
			var icon = r.isDir ? fmFolderIcon() : fmFileIcon();
			var labelExtra = r.isDir ? ' — ' + str('fmOpenFolder', 'Open folder') : '';
			var entryPath = (r.relPath != null && String(r.relPath) !== '') ? r.relPath : r.name;
			$tb.append(
				'<tr class="' +
					rowClass +
					'" tabindex="0" role="button" data-fm-dir="' +
					(r.isDir ? '1' : '0') +
					'" data-entry-path="' +
					escapeAttr(entryPath) +
					'"' +
					sizeAttr +
					' aria-label="' +
					escapeAttr(r.name + labelExtra) +
					'">' +
					'<td class="wpps-br-fm-cell-name">' +
					'<div class="wpps-br-fm-name-wrap">' +
					icon +
					'<span class="wpps-br-fm-name-text">' +
					escapeHtml(r.name) +
					'</span></div></td>' +
					'<td class="wpps-br-fm-cell-size">' +
					escapeHtml(sizeCell) +
					'</td>' +
					'<td class="wpps-br-fm-cell-type">' +
					escapeHtml(typeLabel) +
					'</td></tr>'
			);
		});
	}

	function refreshBrowseFileManagerView() {
		if (browseZipLazy) {
			return;
		}
		if (!browseZipTreeRoot) {
			return;
		}
		var pathNorm = (browseCurrentPath || '').replace(/\\/g, '/').replace(/\/+$/, '');
		browseCurrentPath = pathNorm;
		renderFmBreadcrumbs(browseCurrentPath);
		$('#wpps-br-fm-up').prop('disabled', browseCurrentPath === '');
		var rows = browseZipListChildren(browseZipTreeRoot, browseCurrentPath);
		paintBrowseRowsFromApi(rows);
	}

	function loadBrowseZipFolder(relPath) {
		if (typeof wppsBackupRestore === 'undefined') {
			return;
		}
		if (!partPanelBackup || !partPanelPart) {
			return;
		}
		var pathNorm = (relPath || '').replace(/\\/g, '/').replace(/\/+$/, '');
		browseCurrentPath = pathNorm;
		renderFmBreadcrumbs(browseCurrentPath);
		$('#wpps-br-fm-up').prop('disabled', browseCurrentPath === '');
		clearFmSelection();
		$('#wpps-br-fm-tbody').html(
			'<tr><td colspan="3" class="wpps-br-fm-empty">' +
				escapeHtml(str('browseLoading', 'Loading…')) +
				'</td></tr>'
		);
		$.post(
			wppsBackupRestore.ajaxUrl,
			{
				action: 'wpps_br_browse_part',
				nonce: wppsBackupRestore.nonce,
				backup_id: partPanelBackup,
				part: partPanelPart,
				browse_subpath: browseCurrentPath
			},
			function (res) {
				if (!res || !res.success || !res.data) {
					$('#wpps-br-fm-tbody').html(
						'<tr><td colspan="3" class="wpps-br-fm-empty">' +
							escapeHtml(
								(res && res.data && res.data.message) ||
									str('browseZipFail', 'Could not read archive.')
							) +
							'</td></tr>'
					);
					return;
				}
				var d = res.data;
				if (d.kind !== 'zip') {
					return;
				}
				if (d.browse_title) {
					$('#wpps-br-browse-title').text(d.browse_title);
				}
				browseZipLazy = true;
				browseZipTreeRoot = null;
				browseZipContext.backupId = partPanelBackup;
				browseZipContext.part = partPanelPart;
				browseZipContext.archiveLabel = d.archive_label || browseZipContext.archiveLabel;
				browseZipContext.entryNonce = d.zip_entry_nonce || '';
				browseZipContext.adminPostUrl =
					(wppsBackupRestore && wppsBackupRestore.adminPostUrl) || '';
				setBrowseZipMode('zip');
				paintBrowseRowsFromApi(d.children || []);
			}
		).fail(function () {
			$('#wpps-br-fm-tbody').html(
				'<tr><td colspan="3" class="wpps-br-fm-empty">' +
					escapeHtml(str('browseZipFail', 'Could not read archive.')) +
					'</td></tr>'
			);
		});
	}

	function setBrowseZipMode(kind) {
		var isSql = kind === 'sql';
		var isZip = kind === 'zip';
		browseZipContext.mode = kind || '';
		if (isSql) {
			$('#wpps-br-fm-toolbar').attr('hidden', true);
			$('#wpps-br-fm-main').attr('hidden', true);
			$('#wpps-br-fm-sql').removeAttr('hidden');
		} else if (isZip) {
			$('#wpps-br-fm-sql').attr('hidden', true);
			$('#wpps-br-fm-toolbar').removeAttr('hidden');
			$('#wpps-br-fm-main').removeAttr('hidden');
		} else {
			$('#wpps-br-fm-toolbar').attr('hidden', true);
			$('#wpps-br-fm-main').attr('hidden', true);
			$('#wpps-br-fm-sql').attr('hidden', true);
		}
		if (!isZip) {
			browseZipLazy = false;
			browseZipTreeRoot = null;
			browseCurrentPath = '';
			$('#wpps-br-fm-tbody').empty();
		}
	}

	function buildBrowseZipEntryUrl(entryPath) {
		if (
			!entryPath ||
			!browseZipContext.entryNonce ||
			!browseZipContext.backupId ||
			!browseZipContext.part
		) {
			return '#';
		}
		var base =
			(wppsBackupRestore && wppsBackupRestore.adminPostUrl) ||
			browseZipContext.adminPostUrl ||
			'';
		if (!base) {
			return '#';
		}
		var u =
			base +
			(base.indexOf('?') > -1 ? '&' : '?') +
			'action=wpps_br_browse_zip_entry' +
			'&backup=' +
			encodeURIComponent(browseZipContext.backupId) +
			'&part=' +
			encodeURIComponent(browseZipContext.part) +
			'&entry=' +
			encodeURIComponent(entryPath) +
			'&_wpnonce=' +
			encodeURIComponent(browseZipContext.entryNonce);
		return fixAmpInUrl(u);
	}

	function closeBrowseModal() {
		$('#wpps-br-browse-modal').attr('hidden', true);
		browseZipEntries = [];
		browseZipLazy = false;
		browseZipTreeRoot = null;
		browseCurrentPath = '';
		browseZipContext = {
			backupId: '',
			part: '',
			archiveLabel: '',
			entryNonce: '',
			adminPostUrl: '',
			mode: '',
			sqlDownloadUrl: ''
		};
		$('#wpps-br-fm-tbody').empty();
		$('#wpps-br-fm-breadcrumbs').empty();
		$('#wpps-br-fm-sql-meta').text('');
		$('#wpps-br-fm-sql-download').removeAttr('data-href');
		$('#wpps-br-browse-notice').attr('hidden', true).text('');
		$('#wpps-br-fm-toolbar').attr('hidden', true);
		$('#wpps-br-fm-main').attr('hidden', true);
		$('#wpps-br-fm-sql').attr('hidden', true);
		clearFmSelection();
	}

	function openModal() {
		$('#wpps-br-modal').removeAttr('hidden');
	}

	function closeModal() {
		$('#wpps-br-modal').attr('hidden', true);
	}

	function parseAjaxError(xhr) {
		if (xhr.responseJSON && xhr.responseJSON.data && typeof xhr.responseJSON.data.message === 'string') {
			return xhr.responseJSON.data.message;
		}
		if (xhr.responseText) {
			var recovered = parseWpAdminAjaxJson(xhr.responseText);
			if (recovered && recovered.data && typeof recovered.data.message === 'string') {
				return recovered.data.message;
			}
			try {
				var j = JSON.parse(xhr.responseText);
				if (j.data && typeof j.data.message === 'string') {
					return j.data.message;
				}
			} catch (e) {
				if (xhr.responseText.length < 400 && xhr.responseText.indexOf('{') === -1) {
					return xhr.responseText.trim();
				}
			}
		}
		if (xhr.status === 0 || xhr.statusText === 'timeout') {
			return (
				(wppsBackupRestore && wppsBackupRestore.strings && wppsBackupRestore.strings.timeoutHint) ||
				'Request timed out or was interrupted. Try database-only first or increase PHP max_execution_time.'
			);
		}
		if (
			xhr.status === 502 ||
			xhr.status === 503 ||
			xhr.status === 504 ||
			xhr.status === 520 ||
			xhr.status === 522 ||
			xhr.status === 524
		) {
			return (
				str(
					'gatewayTimeoutHint',
					'Server timeout or gateway error (%s). The host stopped a long request. Try again, use “Download all parts”, or ask your host to raise max_execution_time / FastCGI read timeout.'
				).replace('%s', String(xhr.status))
			);
		}
		if (xhr.status === 500 && xhr.responseText && xhr.responseText.indexOf('{') === -1) {
			return (
				'Server error (500) with a non-JSON response. Check restore.log in the backup folder, PHP error log, or enable WP_DEBUG_LOG for details.'
			);
		}
		return (wppsBackupRestore && wppsBackupRestore.strings && wppsBackupRestore.strings.errorGeneric) || 'Error';
	}

	function showProgressPanel(show) {
		var $p = $('#wpps-br-progress-panel');
		if (show) {
			$p.removeAttr('hidden');
		} else {
			$p.attr('hidden', true);
		}
	}

	function updateActivityLabel() {
		if (!lastTickTs) return;
		var sec = Math.max(0, Math.floor((Date.now() - lastTickTs) / 1000));
		var label = (wppsBackupRestore && wppsBackupRestore.strings && wppsBackupRestore.strings.lastActivity) || 'Last activity';
		var text = label + ': ' + sec + 's ago';
		if ($('#wpps-br-restore-flow').data('restore-in-modal')) {
			$('#wpps-br-restore-modal-activity').text(text);
		} else {
			$('#wpps-br-last-activity').text(text);
		}
	}

	function startActivityClock() {
		if (activityTimer) {
			clearInterval(activityTimer);
		}
		lastTickTs = Date.now();
		updateActivityLabel();
		activityTimer = setInterval(updateActivityLabel, 1000);
	}

	function stopActivityClock() {
		if (activityTimer) {
			clearInterval(activityTimer);
			activityTimer = null;
		}
		lastTickTs = 0;
		$('#wpps-br-last-activity').text('');
		$('#wpps-br-restore-modal-activity').text('');
	}

	function syncBackupLiveLogPanel() {
		var $pre = $('#wpps-br-backup-live-log-pre');
		if (!$pre.length) {
			return;
		}
		var t =
			liveBackupLogText.trim() ||
			str('backupLogEmpty', 'No log lines yet for this job. Status messages will appear here as the backup runs.');
		$pre.text(t);
		var el = $pre[0];
		if (el) {
			el.scrollTop = el.scrollHeight;
		}
	}

	function syncRestoreLiveLogPanel() {
		var $pre = $('#wpps-br-restore-live-log-pre');
		if (!$pre.length) {
			return;
		}
		var t =
			liveRestoreLogText.trim() ||
			str(
				'restoreLogEmpty',
				'No log lines yet for this restore. Status messages will appear here as the restore runs.'
			);
		$pre.text(t);
		var el = $pre[0];
		if (el) {
			el.scrollTop = el.scrollHeight;
		}
	}

	function applyStepResponse(d) {
		lastTickTs = Date.now();
		if ($('#wpps-br-restore-flow').data('restore-in-modal')) {
			applyRestoreModalProgress(d);
			return;
		}
		var pct = typeof d.progress === 'number' ? d.progress : 0;
		$('#wpps-br-progress-fill').css('width', pct + '%');
		$('#wpps-br-progress-track').attr('aria-valuenow', pct);
		$('#wpps-br-progress-pct').text(pct + '%');
		var msg = d.message || d.log_line || '';
		if (d.log_excerpt && !msg) {
			msg = d.log_excerpt;
		}
		$('#wpps-br-progress-status').text(msg);
		if (progressJobKind === 'backup') {
			var line = d.message || d.log_line || '';
			if (line) {
				liveBackupLogText += line + '\n';
			}
			// Do not append log_excerpt each tick — it is the same log tail every request and floods the live panel.
			if (liveBackupLogText.length > 200000) {
				liveBackupLogText = liveBackupLogText.slice(-200000);
			}
			if (!$('#wpps-br-backup-live-log-section').prop('hidden')) {
				syncBackupLiveLogPanel();
			}
		}
	}

	function backupStep(backupId, backupType, label) {
		if (typeof wppsBackupRestore === 'undefined') return;
		var data = {
			action: 'wpps_br_backup_step',
			nonce: wppsBackupRestore.nonce,
			backup_id: backupId || '',
			backup_type: backupType,
			label: backupId ? '' : label
		};
		$.ajax({
			url: wppsBackupRestore.ajaxUrl,
			type: 'POST',
			dataType: 'json',
			timeout: 0,
			data: data
		})
			.done(function (res) {
				if (!res) {
					stopActivityClock();
					showProgressPanel(false);
					$('#wpps-br-start').prop('disabled', false);
					progressJobKind = 'backup';
					alert(parseAjaxError({ responseText: '', status: 0 }));
					return;
				}
				if (!res.success) {
					stopActivityClock();
					showProgressPanel(false);
					$('#wpps-br-start').prop('disabled', false);
					progressJobKind = 'backup';
					var msg =
						(res.data && res.data.message) ||
						(wppsBackupRestore && wppsBackupRestore.strings && wppsBackupRestore.strings.errorGeneric);
					if (res.data && res.data.lock_conflict) {
						logLockDebug('backup_step lock_conflict', backupId || activeBackupId || '');
					}
					alert(msg);
					return;
				}
				var d = res.data || {};
				applyStepResponse(d);
				if (d.done) {
					stopActivityClock();
					activeBackupId = '';
					showProgressPanel(false);
					$('#wpps-br-start').prop('disabled', false);
					window.location.reload();
					return;
				}
				activeBackupId = d.backup_id || backupId;
				setTimeout(function () {
					backupStep(activeBackupId, backupType, '');
				}, 250);
			})
			.fail(function (xhr) {
				stopActivityClock();
				showProgressPanel(false);
				$('#wpps-br-start').prop('disabled', false);
				activeBackupId = '';
				progressJobKind = 'backup';
				var err = parseAjaxError(xhr);
				alert(err);
			});
	}

	function setRestoreWizardStep(step) {
		var prepare = document.getElementById('wpps-br-restore-step-prepare');
		var verify = document.getElementById('wpps-br-restore-step-verify');
		var run = document.getElementById('wpps-br-restore-step-run');
		if (prepare) {
			prepare.hidden = step !== 1;
		}
		if (verify) {
			verify.hidden = step !== 2;
		}
		if (run) {
			run.hidden = step !== 3;
		}
		var s;
		for (s = 1; s <= 3; s++) {
			$('[data-step-dot="' + s + '"]').toggleClass('wpps-br-restore-rail__step--active', s === step);
		}
	}

	function resetRestorePrepareFlow() {
		$('#wpps-br-restore-prepare-loading').attr('hidden', true);
		setRestorePrecheckSuccessVisible(false);
		$('#wpps-br-restore-continue').removeAttr('hidden').prop('disabled', false);
		$('#wpps-br-restore-start').attr('hidden', true).prop('disabled', true);
		$('#wpps-br-restore-change-selection').attr('hidden', true);
		$('#wpps-br-restore-checkboxes input').prop('disabled', false);
		setRestoreWizardStep(1);
	}

	function restoreModalReturnToPrepareAfterError() {
		resetRestoreModalProgressUi();
		$('#wpps-br-restore-flow').removeData('restore-in-modal');
		var dPre = $('#wpps-br-restore-flow').data('restore-precheck');
		var sel = $('#wpps-br-restore-flow').data('restore-selected') || [];
		$('#wpps-br-restore-prepare-loading').attr('hidden', true);
		if (!dPre || !sel.length) {
			resetRestorePrepareFlow();
			return;
		}
		setRestoreWizardStep(2);
		$('#wpps-br-restore-continue').attr('hidden', true);
		$('#wpps-br-restore-change-selection').removeAttr('hidden');
		$('#wpps-br-restore-checkboxes input').prop('disabled', true);
		renderRestorePrepareList(dPre, sel);
		var $block = $('#wpps-br-restore-prepare-block');
		if (!dPre.ok) {
			setRestorePrecheckSuccessVisible(false);
			var blockMsgs = dPre.blocking || [];
			$block.html(escapeHtml(blockMsgs.join('\n')).replace(/\n/g, '<br />'));
			$block.removeAttr('hidden');
			$('#wpps-br-restore-start').attr('hidden', true).prop('disabled', true);
		} else {
			$block.attr('hidden', true).empty();
			setRestorePrecheckSuccessVisible(true);
			var warns = dPre.warnings || [];
			var $warn = $('#wpps-br-restore-prepare-warn');
			if (warns.length) {
				$warn.text(warns.join('\n\n'));
				$warn.removeAttr('hidden');
			} else {
				$warn.attr('hidden', true).empty();
			}
			$('#wpps-br-restore-start').removeAttr('hidden').prop('disabled', false);
		}
	}

	function renderServerChecks(precheckData) {
		var checks = (precheckData && precheckData.server_checks) || [];
		var $section = $('#wpps-br-restore-server-section');
		var $box = $('#wpps-br-restore-server-checks');
		if (!$section.length || !$box.length) {
			return;
		}
		if (!checks.length) {
			$section.attr('hidden', true);
			$box.empty();
			if ($section.is('details')) {
				$section.prop('open', false);
			}
			return;
		}
		var hasFail = checks.some(function (c) {
			return !c.ok;
		});
		var html = '<ul class="wpps-br-verify-checklist">';
		checks.forEach(function (c) {
			var pass = !!c.ok;
			html += renderVerifyChecklistRow(pass, c.label || '', c.detail || '');
		});
		html += '</ul>';
		$box.html(html);
		if ($section.is('details')) {
			$section.prop('open', !!hasFail);
		}
		$section.removeAttr('hidden');
	}

	function renderRestorePrepareList(precheckData, selectedKeys) {
		var parts = (wppsBackupRestore && wppsBackupRestore.restoreParts) || {};
		var order = ['database', 'plugins', 'themes', 'uploads', 'others'];
		var items = (precheckData && precheckData.items) || {};
		var html = '<ul class="wpps-br-verify-parts-strip" role="list">';
		order.forEach(function (key) {
			if (selectedKeys.indexOf(key) === -1) {
				return;
			}
			var it = items[key];
			if (!it) {
				return;
			}
			var pass = !!it.ok;
			var heading = it.label || parts[key] || key;
			html += renderVerifyPartsStripItem(pass, heading, it.detail || '');
		});
		html += '</ul>';
		$('#wpps-br-restore-prepare-list').html(html);
		$('.wpps-br-restore-verify-subhead--parts').removeAttr('hidden');
		renderServerChecks(precheckData);
	}

	function setRestorePrecheckSuccessVisible(show) {
		var $el = $('#wpps-br-restore-prepare-success');
		if (!$el.length) {
			return;
		}
		if (show) {
			var msg = str(
				'restorePrecheckOk',
				'The selected backup files are on this server and validated. Press Restore to begin.'
			);
			$el.html('<p>' + escapeHtml(msg) + '</p>').removeAttr('hidden');
		} else {
			$el.attr('hidden', true).empty();
		}
	}

	function setRestoreProgressBar(pct) {
		pct = Math.max(0, Math.min(100, parseInt(pct, 10) || 0));
		$('#wpps-br-restore-modal-fill').css('width', pct + '%');
		$('#wpps-br-restore-modal-track').attr('aria-valuenow', pct);
		$('#wpps-br-restore-modal-pct').text(pct + '%');
	}

	function resetRestoreModalProgressUi() {
		$('#wpps-br-restore-modal-phases').empty();
		setRestoreProgressBar(0);
		$('#wpps-br-restore-modal-lead').text('');
		$('#wpps-br-restore-modal-status').text('');
		$('#wpps-br-restore-complete-panel').attr('hidden', true);
		$('#wpps-br-restore-complete-hint').text('');
		$('#wpps-br-restore-elementor-notice').attr('hidden', true);
		$('#wpps-br-restore-visit-site').attr('href', '/');
		$('#wpps-br-restore-run-during').removeAttr('hidden');
		$('#wpps-br-restore-dl-log').attr('href', '#').off('click.wppsRestoreLog');
		$('#wpps-br-restore-modal-stop').prop('disabled', false);
		$('#wpps-br-restore-cancel-during').removeAttr('hidden');
		liveRestoreLogText = '';
		$('#wpps-br-restore-live-log-pre').text('');
		$('#wpps-br-restore-live-log-section').prop('hidden', true);
		$('#wpps-br-show-restore-live-log')
			.text(str('showLog', 'Show log'))
			.attr('aria-expanded', 'false');
	}

	function fileStepIndices(plan) {
		var ix = [];
		if (!plan || !plan.length) {
			return ix;
		}
		var re = /^restore_(plugins|themes|uploads|others)_(extract|install)$/;
		for (var i = 0; i < plan.length; i++) {
			var s = plan[i];
			if (s === 'restore_database' || s === 'restore_finalize') {
				continue;
			}
			if (re.test(s)) {
				ix.push(i);
			}
		}
		return ix;
	}

	function phaseRowState(id, ctx) {
		var fd = ctx.fd;
		var active = ctx.active;
		var jobDone = ctx.jobDone;
		var boot = ctx.boot;
		var dbIx = ctx.dbIx;
		var finIx = ctx.finIx;
		var firstFile = ctx.firstFile;
		var lastFile = ctx.lastFile;
		var fileRe = ctx.fileRe;

		if (id === 'verify') {
			if (boot) {
				return 'active';
			}
			return 'done';
		}
		if (id === 'db') {
			if (jobDone) {
				return 'done';
			}
			if (fd > dbIx) {
				return 'done';
			}
			if (active === 'restore_database') {
				return 'active';
			}
			return 'pending';
		}
		if (id === 'files') {
			if (jobDone) {
				return 'done';
			}
			if (lastFile < 0) {
				return 'pending';
			}
			if (fd > lastFile) {
				return 'done';
			}
			if (fileRe.test(active)) {
				return 'active';
			}
			return 'pending';
		}
		if (id === 'clean') {
			if (jobDone) {
				return 'done';
			}
			if (finIx < 0) {
				return 'pending';
			}
			if (fd > finIx) {
				return 'done';
			}
			if (active === 'restore_finalize') {
				return 'active';
			}
			return 'pending';
		}
		if (id === 'fin') {
			if (jobDone) {
				return 'done';
			}
			return 'pending';
		}
		return 'pending';
	}

	function renderRestoreModalPhases(d) {
		var plan = d.restore_plan || [];
		var fd = typeof d.restore_fully_done_steps === 'number' ? d.restore_fully_done_steps : 0;
		var active = d.restore_active_step || '';
		var jobDone = !!d.done;
		var boot = d.restore_bootstrap === true;
		var dbIx = plan.indexOf('restore_database');
		var finIx = plan.indexOf('restore_finalize');
		var fIx = fileStepIndices(plan);
		var firstFile = fIx.length ? Math.min.apply(null, fIx) : -1;
		var lastFile = fIx.length ? Math.max.apply(null, fIx) : -1;
		var fileRe = /^restore_(plugins|themes|uploads|others)_(extract|install)$/;

		var ctx = {
			fd: fd,
			active: active,
			jobDone: jobDone,
			boot: boot,
			dbIx: dbIx,
			finIx: finIx,
			firstFile: firstFile,
			lastFile: lastFile,
			fileRe: fileRe
		};

		var phases = [];
		phases.push({ id: 'verify', label: str('restorePhaseVerify', 'Verifying') });
		if (dbIx >= 0) {
			phases.push({ id: 'db', label: str('restorePhaseDatabase', 'Database') });
		}
		if (firstFile >= 0) {
			phases.push({ id: 'files', label: str('restorePhaseFiles', 'Files') });
		}
		if (finIx >= 0) {
			phases.push({ id: 'clean', label: str('restorePhaseCleaning', 'Cleaning') });
		}
		phases.push({ id: 'fin', label: str('restorePhaseFinished', 'Finished') });

		var html = '';
		phases.forEach(function (p) {
			var st = phaseRowState(p.id, ctx);
			var icon = 'dashicons-clock';
			if (st === 'done') {
				icon = 'dashicons-yes';
			} else if (st === 'active') {
				icon = 'dashicons-update';
			}
			html +=
				'<li class="wpps-br-phase wpps-br-phase--' +
				st +
				'"><span class="wpps-br-phase__icon"><span class="dashicons ' +
				icon +
				'" aria-hidden="true"></span></span><span class="wpps-br-phase__label">' +
				escapeHtml(p.label) +
				'</span></li>';
		});
		$('#wpps-br-restore-modal-phases').html(html);
	}

	function applyRestoreModalProgress(d) {
		var $m = $('#wpps-br-restore-flow');
		if (d.restore_plan && d.restore_plan.length) {
			$m.data('restore-plan-cache', d.restore_plan);
		}
		var view = $.extend({}, d);
		var cached = $m.data('restore-plan-cache');
		if ((!view.restore_plan || !view.restore_plan.length) && cached && cached.length) {
			view.restore_plan = cached;
		}
		var pct = typeof view.progress === 'number' ? view.progress : 0;
		setRestoreProgressBar(pct);
		var line =
			(view.message && String(view.message)) ||
			(view.log_line && String(view.log_line)) ||
			(view.log_excerpt && String(view.log_excerpt)) ||
			'';
		$('#wpps-br-restore-modal-status').text(line);
		var liveLine = (view.message && String(view.message)) || (view.log_line && String(view.log_line)) || '';
		if (liveLine) {
			liveRestoreLogText += liveLine + '\n';
			if (liveRestoreLogText.length > 200000) {
				liveRestoreLogText = liveRestoreLogText.slice(-200000);
			}
			if (!$('#wpps-br-restore-live-log-section').prop('hidden')) {
				syncRestoreLiveLogPanel();
			}
		}
		renderRestoreModalPhases(view);
	}

	/**
	 * admin-ajax must return pure JSON; PHP notices or BOM before "{" break jQuery's dataType:"json".
	 */
	function parseWpAdminAjaxJson(raw) {
		if (raw == null) {
			return null;
		}
		if (typeof raw === 'object' && raw !== null && 'success' in raw) {
			return raw;
		}
		var s = String(raw).trim();
		if (!s) {
			return null;
		}
		try {
			return JSON.parse(s);
		} catch (e1) {
			/* continue */
		}
		var idx = s.indexOf('{"success"');
		if (idx === -1) {
			idx = s.search(/\{\s*"success"\s*:/);
		}
		if (idx > 0) {
			try {
				return JSON.parse(s.slice(idx));
			} catch (e2) {
				/* continue */
			}
		}
		var j = s.indexOf('{');
		if (j > 0) {
			try {
				return JSON.parse(s.slice(j));
			} catch (e3) {
				/* continue */
			}
		}
		return null;
	}

	function closeRestoreModal() {
		var $m = $('#wpps-br-restore-flow');
		if (!$m.length) {
			return;
		}
		if ($m.data('restore-in-modal')) {
			return;
		}
		if (wppsBackupRestore && wppsBackupRestore.backupRestoreMainUrl) {
			window.location.href = wppsBackupRestore.backupRestoreMainUrl;
		}
	}

	function processRestoreTickSuccess(res, backupId, inModal) {
		var d = res.data || {};
		restoreContinuationActive = true;
		applyStepResponse(d);
		if (d.done) {
			restoreContinuationActive = false;
			stopActivityClock();
			activeBackupId = '';
			progressJobKind = 'backup';
			if (inModal) {
				$('#wpps-br-restore-flow').removeData('restore-in-modal');
				setRestoreProgressBar(100);
				$('#wpps-br-restore-modal-stop').prop('disabled', true);
				$('#wpps-br-restore-cancel-during').attr('hidden', true);
				$('#wpps-br-restore-modal-status').text('');
				$('#wpps-br-restore-run-during').attr('hidden', true);
				var hintLines = [];
				var serverDone = (d.message && String(d.message).trim()) || '';
				var defaultComplete = str('restoreComplete', 'Restore completed successfully.');
				if (serverDone && serverDone !== defaultComplete) {
					hintLines.push(serverDone);
				}
				hintLines.push(
					str(
						'restoreReloginHint',
						'If the database was replaced, log in again. Download the restore log if you need a record.'
					)
				);
				$('#wpps-br-restore-complete-hint').text(hintLines.join(' '));
				var logDl = fixAmpInUrl((d.restore_log_download_url || '').trim());
				var $dl = $('#wpps-br-restore-dl-log');
				$dl.off('click.wppsRestoreLog');
				if (logDl && logDl !== '#') {
					$dl.attr('href', logDl);
					$dl.on('click.wppsRestoreLog', function (ev) {
						ev.preventDefault();
						triggerBrFileDownload(logDl);
					});
				} else {
					$dl.attr('href', '#');
				}
				var $copyBtn = $('#wpps-br-restore-copy-log');
				$copyBtn.off('click.wppsCopyLog');
				if (d.log_excerpt) {
					$copyBtn.removeAttr('hidden');
					$copyBtn.on('click.wppsCopyLog', function () {
						if (navigator.clipboard && navigator.clipboard.writeText) {
							navigator.clipboard.writeText(d.log_excerpt).then(function () {
								$copyBtn.text(str('copied', 'Copied!'));
								setTimeout(function () {
									$copyBtn.text(str('copyLog', 'Copy log'));
								}, 2000);
							});
						} else {
							var ta = document.createElement('textarea');
							ta.value = d.log_excerpt;
							ta.style.position = 'fixed';
							ta.style.opacity = '0';
							document.body.appendChild(ta);
							ta.select();
							document.execCommand('copy');
							document.body.removeChild(ta);
							$copyBtn.text(str('copied', 'Copied!'));
							setTimeout(function () {
								$copyBtn.text(str('copyLog', 'Copy log'));
							}, 2000);
						}
					});
				} else {
					$copyBtn.attr('hidden', true);
				}

				if (d.site_url) {
					$('#wpps-br-restore-visit-site').attr('href', d.site_url);
				}

				var $elNotice = $('#wpps-br-restore-elementor-notice');
				if (d.url_migration && d.elementor_active) {
					$elNotice.removeAttr('hidden');
				} else {
					$elNotice.attr('hidden', true);
				}

				$('#wpps-br-restore-complete-panel').removeAttr('hidden');
				return;
			}
			showProgressPanel(false);
			window.location.reload();
			return;
		}
		activeBackupId = d.backup_id || backupId;
		setTimeout(function () {
			restoreStep(activeBackupId, null);
		}, 250);
	}

	function restoreStep(backupId, componentsForFirstPost, restoreRetryAttempt) {
		if (typeof wppsBackupRestore === 'undefined') return;
		var retryNum = typeof restoreRetryAttempt === 'number' ? restoreRetryAttempt : 0;
		var inModal = !!$('#wpps-br-restore-flow').data('restore-in-modal');
		if (componentsForFirstPost && componentsForFirstPost.length) {
			restoreContinuationActive = false;
		}
		var data = {
			action: 'wpps_br_restore_tick',
			nonce: wppsBackupRestore.nonce,
			backup_id: backupId || ''
		};
		if (componentsForFirstPost && componentsForFirstPost.length) {
			data.components = JSON.stringify(componentsForFirstPost);
		}
		$.ajax({
			url: wppsBackupRestore.ajaxUrl,
			type: 'POST',
			dataType: 'text',
			timeout: 0,
			xhrFields: { withCredentials: true },
			data: data
		})
			.done(function (raw) {
				var res = parseWpAdminAjaxJson(raw);
				if (!res) {
					var maxParseRetry = restoreContinuationActive ? 5 : 3;
					if (retryNum < maxParseRetry) {
						setTimeout(function () {
							restoreStep(backupId, null, retryNum + 1);
						}, 900 + retryNum * 600);
						return;
					}
					stopActivityClock();
					if (inModal) {
						activeBackupId = '';
						restoreModalReturnToPrepareAfterError();
					}
					showProgressPanel(false);
					progressJobKind = 'backup';
					restoreContinuationActive = false;
					var frag =
						typeof raw === 'string' && raw.length > 800 ? raw.slice(0, 800) + '…' : String(raw || '');
					alert(
						str(
							'restoreMalformedJson',
							'The server returned invalid JSON (often PHP warnings or zlib compression breaking long restore responses). Check restore.log in the backup folder and PHP error_log.'
						) +
							(frag ? '\n\n' + frag : '')
					);
					return;
				}
				if (!res.success) {
					stopActivityClock();
					var sessionLost = !!(res.data && res.data.restore_session_lost);
					var msg =
						(res.data && res.data.message) ||
						(wppsBackupRestore && wppsBackupRestore.strings && wppsBackupRestore.strings.errorGeneric);
					if (res.data && res.data.lock_conflict) {
						logLockDebug('restore_step lock_conflict', backupId || activeBackupId || '');
					}
					activeBackupId = '';
					progressJobKind = 'backup';
					restoreContinuationActive = false;
					showProgressPanel(false);
					alert(appendBrRestoreAjaxDebug(msg, res.data));
					if (sessionLost) {
						window.location.reload();
						return;
					}
					if (inModal) {
						restoreModalReturnToPrepareAfterError();
					}
					return;
				}
				processRestoreTickSuccess(res, backupId, inModal);
			})
			.fail(function (xhr) {
				var raw = xhr.responseText || '';
				var recovered = parseWpAdminAjaxJson(raw);
				if (recovered && recovered.success) {
					processRestoreTickSuccess(recovered, backupId, inModal);
					return;
				}
				var st = typeof xhr.status === 'number' ? xhr.status : 0;
				var transportRetryable =
					st === 0 ||
					st === 502 ||
					st === 503 ||
					st === 504 ||
					st === 520 ||
					st === 522 ||
					st === 524;
				var sessionRetryable =
					restoreContinuationActive && (st === 401 || st === 403 || st === 200);
				if (retryNum < 3 && (transportRetryable || sessionRetryable)) {
					setTimeout(function () {
						restoreStep(backupId, null, retryNum + 1);
					}, 1200 + retryNum * 800);
					return;
				}
				stopActivityClock();
				var msg = parseAjaxError(xhr);
				if (xhr.status === 200 && raw) {
					try {
						JSON.parse(raw);
					} catch (parseErr) {
						msg =
							str(
								'restoreMalformedJson',
								'The server returned invalid JSON (PHP warnings or compression can break long restore responses).'
							) +
							(msg ? '\n\n' + msg : '');
					}
				}
				var zlibWarn = /zlib output compression/i.test(raw);
				if (zlibWarn) {
					msg +=
						'\n\n' +
						str(
							'restoreZlibHint',
							'Disable PHP zlib.output_compression for this site if restore keeps failing mid-run.'
						);
				}
				var transportLikely =
					st === 0 ||
					st === 502 ||
					st === 503 ||
					st === 504 ||
					st === 520 ||
					st === 522 ||
					st === 524;
				if (inModal && !zlibWarn && !transportLikely) {
					msg +=
						'\n\n' +
						str(
							'restorePartialHint',
							'If the database finished first, re-run the same full restore with Database and all file parts so activations match your plugin files.'
						);
				}
				if (inModal) {
					activeBackupId = '';
					progressJobKind = 'backup';
					restoreContinuationActive = false;
					showProgressPanel(false);
					alert(msg);
					restoreModalReturnToPrepareAfterError();
					return;
				}
				showProgressPanel(false);
				activeBackupId = '';
				progressJobKind = 'backup';
				restoreContinuationActive = false;
				alert(msg);
			});
	}

	$(function () {
		$('#wpps-br-clear-stuck-lock').on('click', function () {
			if (typeof wppsBackupRestore === 'undefined') return;
			if (!confirm(str('confirmClearStuckLock', ''))) return;
			var $btn = $(this);
			$btn.prop('disabled', true);
			$.post(
				wppsBackupRestore.ajaxUrl,
				{
					action: 'wpps_br_clear_stuck_lock',
					nonce: wppsBackupRestore.nonce
				}
			).always(function () {
				window.location.reload();
			});
		});

		function syncBulkSelectToggle() {
			var $btn = $('#wpps-br-toggle-select-all');
			if (!$btn.length) return;
			var $rows = $('.wpps-br-row-check');
			var total = $rows.length;
			var checked = $('.wpps-br-row-check:checked').length;
			var all = total > 0 && checked === total;
			$btn.text(
				all
					? str('bulkDeselectAll', 'Deselect')
					: str('bulkSelectAll', 'Select all')
			);
			$btn.attr('data-all-selected', all ? '1' : '0');
		}

		syncBulkSelectToggle();

		$('#wpps-br-toggle-select-all').on('click', function (e) {
			e.preventDefault();
			var allOn = $(this).attr('data-all-selected') === '1';
			if (allOn) {
				$('.wpps-br-row-check').prop('checked', false);
				$('#wpps-br-select-all').prop('checked', false);
			} else {
				$('.wpps-br-row-check').prop('checked', true);
				$('#wpps-br-select-all').prop('checked', true);
			}
			syncBulkSelectToggle();
		});

		$(document).on('change', '.wpps-br-row-check', function () {
			syncBulkSelectToggle();
			var $rows = $('.wpps-br-row-check');
			var total = $rows.length;
			var checked = $('.wpps-br-row-check:checked').length;
			$('#wpps-br-select-all').prop('checked', total > 0 && checked === total);
		});

		$('.wpps-br-backup-now').on('click', function () {
			openModal();
		});

		$('[data-close]').on('click', function () {
			closeModal();
		});

		$('#wpps-br-start').on('click', function () {
			var type = $('input[name="wpps_br_type"]:checked').val() || 'full';
			var label = ($('#wpps-br-label').val() || '').toString().trim();
			$('#wpps-br-start').prop('disabled', true);
			closeModal();
			showProgressPanel(true);
			var sp = (wppsBackupRestore && wppsBackupRestore.startedPrefix) || 'Started:';
			$('#wpps-br-progress-started').text(sp + ' ' + new Date().toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }));
			liveBackupLogText = '';
			$('#wpps-br-backup-live-log-pre').text('');
			$('#wpps-br-backup-live-log-section').prop('hidden', true);
			$('#wpps-br-show-backup-live-log')
				.text(str('showLog', 'Show log'))
				.attr('aria-expanded', 'false');
			$('#wpps-br-progress-fill').css('width', '2%');
			$('#wpps-br-progress-pct').text('2%');
			$('#wpps-br-progress-track').attr('aria-valuenow', 2);
			$('#wpps-br-progress-status').text(
				(wppsBackupRestore && wppsBackupRestore.strings && wppsBackupRestore.strings.working) || '…'
			);
			startActivityClock();
			activeBackupId = '';
			progressJobKind = 'backup';
			backupStep('', type, label);
		});

		$('#wpps-br-stop-backup').on('click', function () {
			if (!activeBackupId || typeof wppsBackupRestore === 'undefined') {
				return;
			}
			var msg =
				progressJobKind === 'restore'
					? str('confirmStopRestore', 'Stop this restore?')
					: str('confirmStop', '');
			if (!confirm(msg)) {
				return;
			}
			var act = progressJobKind === 'restore' ? 'wpps_br_restore_abort' : 'wpps_br_backup_abort';
			var stopPayload = {
				action: act,
				nonce: wppsBackupRestore.nonce,
				backup_id: activeBackupId
			};
			$.ajax({
				url: wppsBackupRestore.ajaxUrl,
				type: 'POST',
				data: stopPayload,
				xhrFields: { withCredentials: true }
			}).always(function () {
				stopActivityClock();
				activeBackupId = '';
				progressJobKind = 'backup';
				showProgressPanel(false);
				$('#wpps-br-start').prop('disabled', false);
				window.location.reload();
			});
		});

		$(document).on('click', '.wpps-br-restore-dismiss', function (e) {
			e.preventDefault();
			if ($('#wpps-br-restore-flow').data('restore-in-modal') && activeBackupId) {
				if (!confirm(str('confirmStopRestore', 'Stop this restore?'))) {
					return;
				}
				var dismissAbort = {
					action: 'wpps_br_restore_abort',
					nonce: wppsBackupRestore.nonce,
					backup_id: activeBackupId
				};
				$.ajax({
					url: wppsBackupRestore.ajaxUrl,
					type: 'POST',
					data: dismissAbort,
					xhrFields: { withCredentials: true }
				}).always(function () {
					stopActivityClock();
					activeBackupId = '';
					progressJobKind = 'backup';
					restoreContinuationActive = false;
					$('#wpps-br-restore-flow').removeData('restore-in-modal');
					window.location.reload();
				});
				return;
			}
			closeRestoreModal();
		});

		function collectRestoreSelection() {
			var selected = [];
			$('#wpps-br-restore-checkboxes input:checked').each(function () {
				selected.push($(this).val());
			});
			return selected;
		}

		$('#wpps-br-restore-reload-admin').on('click', function () {
			window.location.reload();
		});

		$('#wpps-br-restore-modal-stop').on('click', function () {
			if ($(this).prop('disabled')) {
				return;
			}
			if (!activeBackupId || typeof wppsBackupRestore === 'undefined') {
				return;
			}
			if (!confirm(str('confirmStopRestore', 'Stop this restore?'))) {
				return;
			}
			var modalStop = {
				action: 'wpps_br_restore_abort',
				nonce: wppsBackupRestore.nonce,
				backup_id: activeBackupId
			};
			$.ajax({
				url: wppsBackupRestore.ajaxUrl,
				type: 'POST',
				data: modalStop,
				xhrFields: { withCredentials: true }
			}).always(function () {
				stopActivityClock();
				activeBackupId = '';
				progressJobKind = 'backup';
				restoreContinuationActive = false;
				$('#wpps-br-restore-flow').removeData('restore-in-modal');
				window.location.reload();
			});
		});

		$('#wpps-br-restore-change-selection').on('click', function () {
			resetRestorePrepareFlow();
			$('#wpps-br-restore-server-section').attr('hidden', true);
			$('#wpps-br-restore-server-section').prop('open', false);
			$('#wpps-br-restore-server-checks').empty();
			$('.wpps-br-restore-verify-subhead--parts').attr('hidden', true);
			$('#wpps-br-restore-prepare-list').empty();
			$('#wpps-br-restore-prepare-block').attr('hidden', true).empty();
			$('#wpps-br-restore-prepare-warn').attr('hidden', true).empty();
			setRestorePrecheckSuccessVisible(false);
			$('#wpps-br-restore-flow').removeData('restore-precheck');
			$('#wpps-br-restore-flow').removeData('restore-selected');
		});

		$('#wpps-br-restore-continue').on('click', function () {
			if (typeof wppsBackupRestore === 'undefined') return;
			var $btn = $(this);
			if ($btn.prop('disabled')) return;
			var selected = collectRestoreSelection();
			if (!selected.length) {
				alert(str('errorGeneric', 'Select at least one item.'));
				return;
			}
			var bid = ($('#wpps-br-restore-flow').data('restore-backup-id') || '').toString();
			if (!bid) return;
			$btn.prop('disabled', true);
			$('#wpps-br-restore-flow').data('restore-selected', selected.slice());
			setRestoreWizardStep(2);
			$('#wpps-br-restore-server-section').attr('hidden', true);
			$('#wpps-br-restore-server-section').prop('open', false);
			$('#wpps-br-restore-server-checks').empty();
			$('.wpps-br-restore-verify-subhead--parts').attr('hidden', true);
			$('#wpps-br-restore-prepare-list').empty();
			$('#wpps-br-restore-prepare-block').attr('hidden', true).empty();
			$('#wpps-br-restore-prepare-warn').attr('hidden', true).empty();
			setRestorePrecheckSuccessVisible(false);
			$('#wpps-br-restore-start').attr('hidden', true).prop('disabled', true);
			$('#wpps-br-restore-change-selection').attr('hidden', true);
			$('#wpps-br-restore-prepare-loading').removeAttr('hidden');
			$.ajax({
				url: wppsBackupRestore.ajaxUrl,
				type: 'POST',
				dataType: 'json',
				timeout: 120000,
				data: {
					action: 'wpps_br_restore_precheck',
					nonce: wppsBackupRestore.nonce,
					backup_id: bid,
					components: JSON.stringify(selected)
				}
			})
				.done(function (res) {
					$('#wpps-br-restore-prepare-loading').attr('hidden', true);
					$btn.prop('disabled', false);
					if (!res || !res.success) {
						setRestorePrecheckSuccessVisible(false);
						resetRestorePrepareFlow();
						alert(
							(res && res.data && res.data.message) ||
								str('errorGeneric', 'Verification failed.')
						);
						return;
					}
					var d = res.data || {};
					$('#wpps-br-restore-flow').data('restore-precheck', d);
					renderRestorePrepareList(d, selected);
					var $block = $('#wpps-br-restore-prepare-block');
					$('#wpps-br-restore-continue').attr('hidden', true);
					$('#wpps-br-restore-change-selection').removeAttr('hidden');
					$('#wpps-br-restore-checkboxes input').prop('disabled', true);
					if (!d.ok) {
						setRestorePrecheckSuccessVisible(false);
						var blockMsgs = d.blocking || [];
						$block.html(escapeHtml(blockMsgs.join('\n')).replace(/\n/g, '<br />'));
						$block.removeAttr('hidden');
						$('#wpps-br-restore-start').attr('hidden', true).prop('disabled', true);
					} else {
						setRestorePrecheckSuccessVisible(true);
						$block.attr('hidden', true).empty();
						$('#wpps-br-restore-start').removeAttr('hidden').prop('disabled', false);
					}
					var $warn = $('#wpps-br-restore-prepare-warn');
					var warns = d.warnings || [];
					if (warns.length && d.ok) {
						$warn.text(warns.join('\n\n'));
						$warn.removeAttr('hidden');
					} else {
						$warn.attr('hidden', true).empty();
					}
				})
				.fail(function (xhr) {
					$('#wpps-br-restore-prepare-loading').attr('hidden', true);
					$btn.prop('disabled', false);
					setRestorePrecheckSuccessVisible(false);
					resetRestorePrepareFlow();
					alert(parseAjaxError(xhr));
				});
		});

		function beginRestoreJob(bid, selected) {
			var leadTpl = str(
				'restoreModalLead',
				'The restore operation has begun (%s). Do not close this page until it reports completion.'
			);
			resetRestoreModalProgressUi();
			$('#wpps-br-restore-modal-lead').text(leadTpl.replace('%s', bid));
			setRestoreProgressBar(2);
			$('#wpps-br-restore-flow').data('restore-in-modal', true);
			setRestoreWizardStep(3);
			progressJobKind = 'restore';
			startActivityClock();
			activeBackupId = bid;
			restoreStep(bid, selected);
		}

		$('#wpps-br-restore-start').on('click', function () {
			var $start = $(this);
			if ($start.prop('disabled')) return;
			if (typeof wppsBackupRestore === 'undefined') return;
			var bid = ($('#wpps-br-restore-flow').data('restore-backup-id') || '').toString();
			var selected = $('#wpps-br-restore-flow').data('restore-selected') || [];
			var d = $('#wpps-br-restore-flow').data('restore-precheck') || {};
			if (!bid || !selected.length || !d.ok) {
				return;
			}
			var $overlay = $('#wpps-br-restore-confirm');
			if (!$overlay.length) {
				var warnsFallback = d.warnings || [];
				if (warnsFallback.length) {
					var proceedQ =
						(wppsBackupRestore.strings && wppsBackupRestore.strings.precheckProceed) ||
						'Continue anyway?';
					if (!confirm(warnsFallback.join('\n\n') + '\n\n' + proceedQ)) {
						return;
					}
				}
				if (!confirm(str('confirmRestore', 'Proceed with restore?'))) {
					return;
				}
				$start.prop('disabled', true);
				beginRestoreJob(bid, selected);
				return;
			}
			var warns = d.warnings || [];
			var body = '';
			if (warns.length) {
				var proceedQ2 =
					(wppsBackupRestore.strings && wppsBackupRestore.strings.precheckProceed) ||
					'Continue anyway?';
				body += warns.join('\n\n') + '\n\n' + proceedQ2 + '\n\n';
			}
			body += str(
				'confirmRestore',
				'Restore these items into the live site now? This cannot be undone.'
			);
			$('#wpps-br-restore-confirm-body').text(body);
			$overlay.removeAttr('hidden');
			restoreConfirmPending = { bid: bid, selected: selected.slice() };
		});

		$('#wpps-br-restore-confirm-cancel').on('click', function () {
			$('#wpps-br-restore-confirm').attr('hidden', true);
			restoreConfirmPending = null;
			$('#wpps-br-restore-start').prop('disabled', false);
		});

		$(document).on('click', '#wpps-br-restore-confirm-backdrop', function () {
			$('#wpps-br-restore-confirm-cancel').trigger('click');
		});

		$('#wpps-br-restore-confirm-proceed').on('click', function () {
			if (!restoreConfirmPending) {
				return;
			}
			var bid = restoreConfirmPending.bid;
			var selected = restoreConfirmPending.selected;
			$('#wpps-br-restore-confirm').attr('hidden', true);
			restoreConfirmPending = null;
			$('#wpps-br-restore-start').prop('disabled', true);
			beginRestoreJob(bid, selected);
		});

		$(document).on('click', '.wpps-br-delete', function () {
			var $btn = $(this);
			var id = $btn.data('id');
			if (!id || !confirm(str('confirmDelete', 'Delete this backup?'))) return;
			$btn.prop('disabled', true);
			$.post(wppsBackupRestore.ajaxUrl, {
				action: 'wpps_br_delete_backup',
				nonce: wppsBackupRestore.nonce,
				backup_id: id
			})
				.done(function (res) {
					if (res && res.success) {
						window.location.reload();
					} else {
						alert(str('errorGeneric', 'Something went wrong.'));
						$btn.prop('disabled', false);
					}
				})
				.fail(function () {
					alert(str('errorGeneric', 'Something went wrong.'));
					$btn.prop('disabled', false);
				});
		});

		$('.wpps-br-bulk-delete').on('click', function () {
			var $btn = $(this);
			var ids = $('.wpps-br-row-check:checked')
				.map(function () {
					return $(this).val();
				})
				.get();
			if (!ids.length || !confirm(str('bulkDelete', 'Delete selected backups?'))) return;
			$btn.prop('disabled', true);
			var failures = [];
			function deleteNext(index) {
				if (index >= ids.length) {
					if (failures.length) {
						alert(str('errorGeneric', 'Something went wrong.') + '\n\nFailed: ' + failures.join(', '));
					}
					window.location.reload();
					return;
				}
				$.post(wppsBackupRestore.ajaxUrl, {
					action: 'wpps_br_delete_backup',
					nonce: wppsBackupRestore.nonce,
					backup_id: ids[index]
				})
					.fail(function () {
						failures.push(ids[index]);
					})
					.always(function () {
						deleteNext(index + 1);
					});
			}
			deleteNext(0);
		});

		$('#wpps-br-select-all').on('change', function () {
			$('.wpps-br-row-check').prop('checked', $(this).prop('checked'));
			syncBulkSelectToggle();
		});

		$('.wpps-br-refresh-disk').on('click', function () {
			window.location.reload();
		});

		$('.wpps-br-rescan-local').on('click', function () {
			var $btn = $(this);
			$btn.prop('disabled', true);
			$.post(
				wppsBackupRestore.ajaxUrl,
				{
					action: 'wpps_br_rescan',
					nonce: wppsBackupRestore.nonce
				},
				function (res) {
					$btn.prop('disabled', false);
					if (res && res.success) {
						window.location.reload();
					} else {
						alert(wppsBackupRestore.strings.errorGeneric);
					}
				}
			).fail(function () {
				$btn.prop('disabled', false);
				alert(wppsBackupRestore.strings.errorGeneric);
			});
		});

		$('#wpps-br-toggle-upload').on('click', function (e) {
			e.preventDefault();
			var $panel = $('#wpps-br-upload-panel');
			if ($panel.attr('hidden')) {
				$panel.removeAttr('hidden');
				$(this).attr('aria-expanded', 'true');
			} else {
				$panel.attr('hidden', true);
				$(this).attr('aria-expanded', 'false');
			}
		});

		$('#wpps-br-plupload-drop-hint').text(
			str('uploadDropHint', 'Drag backup files here, or click to browse.')
		);

		function initBrPlupload() {
			if (typeof wppsBackupRestore === 'undefined' || !wppsBackupRestore.plupload) {
				return;
			}
			if (typeof plupload === 'undefined') {
				return;
			}
			var pc = wppsBackupRestore.plupload;
			var browseId = pc.browse_button || 'wpps-br-plupload-browse';
			var containerId = pc.container || 'wpps-br-plupload-ui';
			var dropId = pc.drop_element || 'wpps-br-plupload-drop';
			if (
				!document.getElementById(browseId) ||
				!document.getElementById(containerId) ||
				!document.getElementById(dropId)
			) {
				return;
			}
			var base = $.extend(true, {}, wppsBackupRestore.plupload);
			var uploader = new plupload.Uploader(base);
			var hadSuccess = false;
			var pendingFinalize = 0;
			var finalizeFailed = false;
			var pluploadStickyBackupId = '';
			var $busy = $('#wpps-br-upload-busy');
			var $busyTitle = $('#wpps-br-upload-busy-title');
			var $busyDetail = $('#wpps-br-upload-busy-detail');
			var $plWrap = $('#wpps-br-plupload-ui');
			var ajaxUrl =
				(typeof wppsBackupRestore.ajaxUrl === 'string' && wppsBackupRestore.ajaxUrl) ||
				(typeof window.ajaxurl === 'string' && window.ajaxurl) ||
				'';

			function setUploadBusyBanner(show, title, detail) {
				if (!show) {
					$busy.attr('hidden', true);
					$busyTitle.text('');
					$busyDetail.text('');
					$plWrap.removeClass('wpps-br-plupload-ui--busy');
					return;
				}
				$busyTitle.text(title || '');
				$busyDetail.text(detail || '');
				$busy.removeAttr('hidden');
				$plWrap.addClass('wpps-br-plupload-ui--busy');
			}

			function finishUploadQueueIfIdle() {
				if (pendingFinalize > 0) {
					return;
				}
				if (finalizeFailed) {
					setUploadBusyBanner(false);
					return;
				}
				if (hadSuccess) {
					window.location.reload();
					return;
				}
				setUploadBusyBanner(false);
			}

			function runUploadFinalize(token, fileName) {
				pendingFinalize += 1;
				var fin = str(
					'uploadServerProcessing',
					'Upload complete — finishing on server (unpack / import). Large bundles can take several minutes.'
				);
				setUploadBusyBanner(
					true,
					str('uploadFinishing', 'Finishing on server…'),
					(fileName ? fileName + ' — ' : '') + fin
				);
				var nonce =
					(base.multipart_params && base.multipart_params._ajax_nonce) ||
					(wppsBackupRestore.nonce || '');
				$.ajax({
					url: ajaxUrl,
					type: 'POST',
					dataType: 'json',
					timeout: 0,
					data: {
						action: 'wpps_br_finalize_upload',
						_ajax_nonce: nonce,
						nonce: nonce,
						finalize_token: token
					}
				})
					.done(function (fres) {
						if (fres && fres.success && fres.data) {
							hadSuccess = true;
							if (fres.data.backup_id) {
								pluploadStickyBackupId = String(fres.data.backup_id);
							}
							return;
						}
						finalizeFailed = true;
						var msg =
							(fres && fres.data && fres.data.message) ||
							(fres && fres.message) ||
							str('uploadPluploadErr', 'Upload failed.');
						alert((fileName ? fileName + ': ' : '') + msg);
					})
					.fail(function (xhr) {
						finalizeFailed = true;
						var st = xhr && xhr.status ? ' (HTTP ' + xhr.status + ')' : '';
						alert(
							(fileName ? fileName + ': ' : '') +
								str('uploadPluploadErr', 'Upload failed.') +
								st
						);
					})
					.always(function () {
						pendingFinalize -= 1;
						finishUploadQueueIfIdle();
					});
			}

			function pluploadQueuePosition(up, file) {
				var total = up.files ? up.files.length : 0;
				for (var i = 0; i < total; i++) {
					if (up.files[i].id === file.id) {
						return { index: i + 1, total: total };
					}
				}
				return { index: 1, total: Math.max(1, total) };
			}

			uploader.bind('BeforeUpload', function (up, file) {
				var rawSet = ($('#wpps-br-backup-set').val() || '').trim() || '__new__';
				var backupSet = rawSet;
				if (backupSet === '__new__' && pluploadStickyBackupId) {
					backupSet = pluploadStickyBackupId;
				}
				var clientName = file && file.name ? String(file.name) : '';
				up.setOption(
					'multipart_params',
					$.extend({}, base.multipart_params, {
						backup_set: backupSet,
						component:
							($('#wpps-br-upload-component').val() || '').trim() || 'auto',
						name: clientName
					})
				);
			});

			uploader.bind('FilesAdded', function (up, files) {
				pluploadStickyBackupId = '';
				var names = [];
				plupload.each(files, function (file) {
					names.push(file.name);
				});
				hadSuccess = false;
				pendingFinalize = 0;
				finalizeFailed = false;
				var uploadTitle = str('uploadUploading', 'Uploading backup…');
				var n = names.length;
				var q = n ? names.join(', ') : '';
				var detail = q;
				if (n > 1) {
					detail = str('uploadNFilesQueued', '%1$d files queued: %2$s')
						.replace('%1$d', String(n))
						.replace('%2$s', q);
				}
				setUploadBusyBanner(true, uploadTitle, detail);
				up.refresh();
				up.start();
			});

			uploader.bind('UploadProgress', function (up, file) {
				var pos = pluploadQueuePosition(up, file);
				var uploadTitle =
					pos.total > 1
						? str('uploadMultiTitle', 'Uploading backup (file %1$s of %2$s)…')
								.replace('%1$s', String(pos.index))
								.replace('%2$s', String(pos.total))
						: str('uploadUploading', 'Uploading backup…');
				var pct = typeof file.percent === 'number' ? file.percent : 0;
				var serverProc = str(
					'uploadServerProcessing',
					'Upload complete — finishing on server (unpack / import). Large bundles can take several minutes.'
				);
				var fn = file && file.name ? String(file.name) : '';
				var line =
					pct >= 100
						? serverProc
						: (fn
								? fn +
									' — ' +
									str('uploadPercent', '%s% sent').replace('%s', String(pct))
								: str('uploadPercent', '%s% sent').replace('%s', String(pct)));
				setUploadBusyBanner(true, uploadTitle, line);
			});

			uploader.bind('FileUploaded', function (up, file, info) {
				var raw = String((info && info.response) || '').trim();
				var msg = str('uploadPluploadErr', 'Upload failed.');
				var fn = file && file.name ? String(file.name) : '';
				try {
					var res = JSON.parse(raw);
					if (res.success && res.data) {
						if (res.data.needs_finalize && res.data.finalize_token) {
							runUploadFinalize(String(res.data.finalize_token), fn);
							return;
						}
						hadSuccess = true;
						if (res.data.backup_id) {
							pluploadStickyBackupId = String(res.data.backup_id);
						}
						var fin = str(
							'uploadServerProcessing',
							'Upload complete — finishing on server (unpack / import). Large bundles can take several minutes.'
						);
						setUploadBusyBanner(true, str('uploadFinishing', 'Finishing on server…'), fin);
						return;
					}
					msg =
						(res.data && res.data.message) ||
						res.message ||
						msg;
				} catch (e) {
					msg = raw.length && raw.length < 500 ? raw : msg;
				}
				finalizeFailed = true;
				setUploadBusyBanner(false);
				alert((fn ? fn + ': ' : '') + msg);
			});

			uploader.bind('Error', function (up, err) {
				finalizeFailed = true;
				setUploadBusyBanner(false);
				var prefix = err.file && err.file.name ? err.file.name + ': ' : '';
				var baseMsg = err.message || str('uploadPluploadErr', 'Upload failed.');
				var extra = '';
				if (typeof err.status === 'number' && err.status > 0) {
					extra = ' (HTTP ' + err.status + ')';
				}
				if (err.response) {
					var rt = String(err.response).trim();
					if (rt.length && rt.length < 500) {
						extra += (extra ? ' ' : '') + rt;
					}
				}
				alert(prefix + baseMsg + extra);
			});

			uploader.bind('UploadComplete', function () {
				finishUploadQueueIfIdle();
				if (pendingFinalize > 0 || hadSuccess || finalizeFailed) {
					return;
				}
				setUploadBusyBanner(false);
			});

			uploader.init();

			$('#wpps-br-backup-set').on('change', function () {
				pluploadStickyBackupId = '';
			});
		}

		initBrPlupload();

		// Fallback form: admin- AJAX same as before (admin-post also works without JS).
		$('.wpps-br-upload-form-fallback').on('submit', function (e) {
			if (typeof wppsBackupRestore === 'undefined' || !wppsBackupRestore.ajaxUrl) {
				return;
			}
			var form = this;
			if (form.checkValidity && !form.checkValidity()) {
				return;
			}
			e.preventDefault();
			var $form = $(form);
			var $btn = $form.find('button[type="submit"]');
			var $prog = $('#wpps-br-upload-progress');
			var $bar = $('#wpps-br-upload-progress-bar');
			var $track = $('#wpps-br-upload-progress-track');
			var $progLabel = $('#wpps-br-upload-progress-label');

			function hideUploadProgress() {
				$prog.attr('hidden', true);
				$prog.removeClass('wpps-br-upload-progress--indeterminate');
				$bar.css('width', '0%');
				$track.attr('aria-valuenow', '0');
			}

			function setUploadProgress(pct, indet) {
				if (indet) {
					$prog.addClass('wpps-br-upload-progress--indeterminate');
					$bar.css('width', '100%');
					$track.attr('aria-valuenow', '0');
					$progLabel.text(str('uploadSending', 'Sending file to server…'));
					return;
				}
				$prog.removeClass('wpps-br-upload-progress--indeterminate');
				var p = Math.max(0, Math.min(100, pct));
				$bar.css('width', p + '%');
				$track.attr('aria-valuenow', String(p));
				if (p >= 100) {
					$progLabel.text(str('uploadSending', 'Sending file to server…'));
				} else {
					var tpl = str('uploadPercent', '%s% sent');
					$progLabel.text(tpl.replace('%s', String(p)));
				}
			}

			$btn.prop('disabled', true);
			$prog.removeAttr('hidden');
			setUploadProgress(0, false);
			$progLabel.text(str('uploadSending', 'Sending file to server…'));
			$bar.css('width', '0%');

			var fd = buildBrUploadFormData(form);
			$.ajax({
				url: wppsBackupRestore.ajaxUrl,
				type: 'POST',
				data: fd,
				processData: false,
				contentType: false,
				xhr: function () {
					var xhr = new window.XMLHttpRequest();
					xhr.upload.addEventListener('progress', function (ev) {
						if (ev.lengthComputable && ev.total > 0) {
							setUploadProgress(Math.round((ev.loaded / ev.total) * 100), false);
						} else {
							setUploadProgress(0, true);
						}
					});
					return xhr;
				}
			})
				.done(function (res) {
					if (res && res.success) {
						window.location.reload();
						return;
					}
					var msg =
						(res && res.data && res.data.message) ||
						str('errorGeneric', 'Error');
					alert(msg);
				})
				.fail(function (xhr) {
					var msg = str('errorGeneric', 'Error');
					if (
						xhr.responseJSON &&
						xhr.responseJSON.data &&
						xhr.responseJSON.data.message
					) {
						msg = xhr.responseJSON.data.message;
					}
					alert(msg);
				})
				.always(function () {
					hideUploadProgress();
					$btn.prop('disabled', false);
				});
		});

		$(document).on('click', '.wpps-br-browse-dismiss', function () {
			closeBrowseModal();
		});

		function openFmFolder(path) {
			var pathNorm = (path || '').replace(/\\/g, '/').replace(/\/+$/, '');
			if (browseZipContext.mode === 'zip' && browseZipLazy) {
				loadBrowseZipFolder(pathNorm);
				return;
			}
			browseCurrentPath = pathNorm;
			refreshBrowseFileManagerView();
		}

		function selectFmFileRow($row) {
			var entryPath = ($row.attr('data-entry-path') || '').trim();
			if (!entryPath) {
				return;
			}
			$('#wpps-br-fm-tbody .wpps-br-fm-row--selected').removeClass('wpps-br-fm-row--selected');
			$row.addClass('wpps-br-fm-row--selected');
			var sizeStr = $row.attr('data-file-size');
			var sizeNum = sizeStr != null && sizeStr !== '' ? parseInt(sizeStr, 10) : NaN;
			var suffix =
				!isNaN(sizeNum) && sizeNum >= 0 ? ' — ' + formatBytes(sizeNum) : '';
			var selLabel = str('fmSelected', 'Selected');
			var line = selLabel + ': ' + entryPath + suffix;
			$('#wpps-br-fm-status').text(line);
			var url = buildBrowseZipEntryUrl(entryPath);
			$('#wpps-br-fm-download').prop('disabled', url === '#').attr('data-entry-path', entryPath);
		}

		function triggerFmZipDownloadForPath(entryPath) {
			var href = buildBrowseZipEntryUrl(entryPath);
			if (!href || href === '#' || href.indexOf('action=wpps_br_browse_zip_entry') === -1) {
				return;
			}
			triggerBrFileDownload(href);
		}

		$('#wpps-br-fm-up').on('click', function () {
			if (browseZipContext.mode !== 'zip') {
				return;
			}
			if (browseZipLazy) {
				loadBrowseZipFolder(parentBrowsePath(browseCurrentPath));
				return;
			}
			if (!browseZipTreeRoot) {
				return;
			}
			browseCurrentPath = parentBrowsePath(browseCurrentPath);
			refreshBrowseFileManagerView();
		});

		$(document).on('click', '#wpps-br-fm-breadcrumbs .wpps-br-fm-crumb:not(.wpps-br-fm-crumb--current)', function (e) {
			e.preventDefault();
			if (browseZipContext.mode !== 'zip') {
				return;
			}
			if (!browseZipLazy && !browseZipTreeRoot) {
				return;
			}
			var p = $(this).attr('data-fm-path');
			if (p == null) {
				p = '';
			}
			openFmFolder(p);
		});

		$(document).on('click', '#wpps-br-fm-tbody .wpps-br-fm-row--dir', function (e) {
			e.preventDefault();
			if (browseZipContext.mode !== 'zip') {
				return;
			}
			var folder = ($(this).attr('data-entry-path') || '').trim();
			openFmFolder(folder);
		});

		$(document).on('click', '#wpps-br-fm-tbody .wpps-br-fm-row--file', function (e) {
			e.preventDefault();
			selectFmFileRow($(this));
		});

		$(document).on('dblclick', '#wpps-br-fm-tbody .wpps-br-fm-row--file', function (e) {
			e.preventDefault();
			var entryPath = ($(this).attr('data-entry-path') || '').trim();
			if (!entryPath) {
				return;
			}
			triggerFmZipDownloadForPath(entryPath);
		});

		$(document).on('keydown', '#wpps-br-fm-tbody .wpps-br-fm-row', function (e) {
			if (e.key !== 'Enter' && e.key !== ' ') {
				return;
			}
			e.preventDefault();
			var $r = $(this);
			if ($r.hasClass('wpps-br-fm-row--dir')) {
				openFmFolder($r.attr('data-entry-path') || '');
			} else {
				selectFmFileRow($r);
				triggerFmZipDownloadForPath($r.attr('data-entry-path') || '');
			}
		});

		$('#wpps-br-fm-download').on('click', function (e) {
			if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) {
				return;
			}
			var entryPath = ($(this).attr('data-entry-path') || '').trim();
			if (!entryPath || $(this).prop('disabled')) {
				return;
			}
			triggerFmZipDownloadForPath(entryPath);
			e.preventDefault();
		});

		$('#wpps-br-fm-sql-download').on('click', function (e) {
			if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) {
				return;
			}
			var href = fixAmpInUrl(($(this).attr('data-href') || '').trim());
			if (!href || href === '#' || href.indexOf('action=wpps_br_download') === -1) {
				return;
			}
			e.preventDefault();
			triggerBrFileDownload(href);
		});

		$(document).on('click', '.wpps-br-part-trigger', function (e) {
			e.preventDefault();
			if (typeof wppsBackupRestore === 'undefined') return;
			var $btn = $(this);
			var bid = ($btn.data('backup-id') || '').toString();
			var part = ($btn.data('part') || '').toString();
			if (!bid || !part) return;
			$('.wpps-br-part-trigger--active').removeClass('wpps-br-part-trigger--active');
			$btn.addClass('wpps-br-part-trigger--active');
			partPanelBackup = bid;
			partPanelPart = part;
			var $wrap = $('#wpps-br-part-panel');
			$wrap.removeAttr('hidden');
			$('#wpps-br-part-panel-status').text('');
			$('#wpps-br-part-panel-loading').removeAttr('hidden');
			$('#wpps-br-part-panel-actions').attr('hidden', true);
			$('#wpps-br-part-download').attr('href', '#');
			$.post(
				wppsBackupRestore.ajaxUrl,
				{
					action: 'wpps_br_part_panel',
					nonce: wppsBackupRestore.nonce,
					backup_id: bid,
					part: part
				},
				function (res) {
					$('#wpps-br-part-panel-loading').attr('hidden', true);
					if (!res || !res.success || !res.data) {
						$('#wpps-br-part-panel-status').text(
							(res && res.data && res.data.message) || str('errorGeneric', 'Error')
						);
						return;
					}
					var d = res.data;
					$('#wpps-br-part-panel-status').text(d.status_line || '');
					$('#wpps-br-part-download').attr('href', fixAmpInUrl(d.download_url || '#'));
					$('#wpps-br-part-panel-actions').removeAttr('hidden');
				}
			).fail(function () {
				$('#wpps-br-part-panel-loading').attr('hidden', true);
				$('#wpps-br-part-panel-status').text(str('errorGeneric', 'Error'));
			});
		});

		$('#wpps-br-part-download').on('click', function (e) {
			if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) {
				return;
			}
			var href = fixAmpInUrl(($(this).attr('href') || '').trim());
			if (!href || href === '#') {
				return;
			}
			if (href.indexOf('action=wpps_br_download') === -1) {
				return;
			}
			e.preventDefault();
			triggerBrFileDownload(href);
		});

		$(document).on('click', '[data-wpps-br-bundle-dismiss]', function (e) {
			e.preventDefault();
			closeBundlePrepareModal();
		});

		$(document).on('click', '.wpps-br-download-full-zip', function (e) {
			e.preventDefault();
			if (typeof wppsBackupRestore === 'undefined') {
				return;
			}
			var bid = ($(this).data('backup-id') || '').toString();
			if (!bid) {
				return;
			}
			closeBundlePrepareModal();
			bundleHandoffDone = false;
			var $modal = $('#wpps-br-bundle-modal');
			$('#wpps-br-bundle-modal-title').text(str('bundleModalTitle', 'Preparing full backup ZIP'));
			$('#wpps-br-bundle-modal-lead').text(
				str(
					'bundleModalLead',
					'The zip file is being created on the server. Please keep this page open. When it is ready, your browser will start the download.'
				)
			);
			$('#wpps-br-bundle-cancel').text(str('bundleCancel', 'Cancel'));
			seedBundlePrepareTimeline();
			$modal.removeAttr('hidden');

			var tickRetries = 0;

			function runBundlePrepareTick() {
				if (bundleHandoffDone) {
					return;
				}
				bundlePrepareXhr = $.ajax({
					url: wppsBackupRestore.ajaxUrl,
					type: 'POST',
					dataType: 'json',
					timeout: 0,
					data: {
						action: 'wpps_br_bundle_prepare',
						nonce: wppsBackupRestore.nonce,
						backup_id: bid
					}
				})
					.done(function (res) {
						if (bundleHandoffDone) {
							return;
						}
						if (!res || !res.success || !res.data) {
							closeBundlePrepareModal();
							alert(
								(res && res.data && res.data.message) ||
									str('errorGeneric', 'Something went wrong.')
							);
							return;
						}
						tickRetries = 0;
						if (res.data.progress) {
							renderBundleTimeline(res.data.progress);
						}
						if (res.data.done && res.data.download_url) {
							var url = fixAmpInUrl(res.data.download_url);
							var fsz = parseInt(res.data.file_size || '0', 10) || 0;
							startBundleFileDownload(url, fsz, $modal, res.data.progress || null);
							return;
						}
						// Next part — short gap keeps the UI responsive without stacking requests.
						window.setTimeout(runBundlePrepareTick, 40);
					})
					.fail(function (xhr) {
						if (bundleHandoffDone) {
							return;
						}
						if (xhr && (xhr.status === 0 || xhr.statusText === 'abort')) {
							closeBundlePrepareModal();
							return;
						}
						// Packing resumes from the parts already in the zip, so a transient
						// gateway hiccup (502/503/504/timeout) only costs a retry, not the run.
						var st = xhr ? xhr.status : 0;
						if ((st === 502 || st === 503 || st === 504 || xhr.statusText === 'timeout') && tickRetries < 3) {
							tickRetries++;
							window.setTimeout(runBundlePrepareTick, 1500 * tickRetries);
							return;
						}
						closeBundlePrepareModal();
						alert(parseAjaxError(xhr));
					})
					.always(function () {
						bundlePrepareXhr = null;
					});
			}

			runBundlePrepareTick();
		});

		$(document).on('click', '.wpps-br-download-all-parts', function (e) {
			e.preventDefault();
			var $tr = $(this).closest('tr');
			var raw = ($tr.attr('data-wpps-download-parts') || '').trim();
			if (!raw) {
				return;
			}
			var urls;
			try {
				urls = JSON.parse(raw);
			} catch (err) {
				return;
			}
			var order = ['database', 'plugins', 'themes', 'uploads', 'others'];
			var list = [];
			for (var i = 0; i < order.length; i++) {
				var k = order[i];
				if (urls[k]) {
					list.push(fixAmpInUrl(urls[k]));
				}
			}
			if (!list.length) {
				return;
			}
			var delayMs = 2500;
			list.forEach(function (url, idx) {
				window.setTimeout(function () {
					triggerBrFileDownload(url);
				}, idx * delayMs);
			});
		});

		$('#wpps-br-part-panel-close').on('click', function () {
			closePartPanel();
		});

		$('#wpps-br-part-delete-srv').on('click', function () {
			if (typeof wppsBackupRestore === 'undefined') return;
			if (!partPanelBackup || !partPanelPart) return;
			if (!confirm(str('confirmDeletePart', ''))) return;
			$.post(
				wppsBackupRestore.ajaxUrl,
				{
					action: 'wpps_br_delete_part',
					nonce: wppsBackupRestore.nonce,
					backup_id: partPanelBackup,
					part: partPanelPart
				},
				function (res) {
					if (res && res.success) {
						closePartPanel();
						window.location.reload();
					} else {
						alert((res && res.data && res.data.message) || str('errorGeneric', 'Error'));
					}
				}
			).fail(function () {
				alert(str('errorGeneric', 'Error'));
			});
		});

		$('#wpps-br-part-browse').on('click', function () {
			if (typeof wppsBackupRestore === 'undefined') return;
			if (!partPanelBackup || !partPanelPart) return;
			browseZipEntries = [];
			browseZipTreeRoot = null;
			browseZipLazy = false;
			browseCurrentPath = '';
			browseZipContext = {
				backupId: '',
				part: '',
				archiveLabel: '',
				entryNonce: '',
				adminPostUrl: (wppsBackupRestore && wppsBackupRestore.adminPostUrl) || '',
				mode: '',
				sqlDownloadUrl: ''
			};
			setBrowseZipMode('');
			$('#wpps-br-browse-modal').removeAttr('hidden');
			$('#wpps-br-browse-title').text(str('browseLoading', 'Loading…'));
			$('#wpps-br-browse-notice').attr('hidden', true).text('');
			$('#wpps-br-fm-sql-meta').text('');
			$('#wpps-br-fm-sql-download').removeAttr('data-href');
			$('#wpps-br-fm-breadcrumbs').empty();
			clearFmSelection();
			$('#wpps-br-fm-main').removeAttr('hidden');
			$('#wpps-br-fm-toolbar').attr('hidden', true);
			$('#wpps-br-fm-tbody').html(
				'<tr><td colspan="3" class="wpps-br-fm-empty">' +
					escapeHtml(str('browseLoading', 'Loading…')) +
					'</td></tr>'
			);

			$.post(
				wppsBackupRestore.ajaxUrl,
				{
					action: 'wpps_br_browse_part',
					nonce: wppsBackupRestore.nonce,
					backup_id: partPanelBackup,
					part: partPanelPart,
					browse_subpath: ''
				},
				function (res) {
					if (!res || !res.success || !res.data) {
						$('#wpps-br-browse-title').text(str('browseContents', 'Browse contents'));
						setBrowseZipMode('');
						$('#wpps-br-fm-main').removeAttr('hidden');
						$('#wpps-br-fm-toolbar').attr('hidden', true);
						$('#wpps-br-fm-tbody').html(
							'<tr><td colspan="3" class="wpps-br-fm-empty">' +
								escapeHtml(
									(res && res.data && res.data.message) ||
										str('browseZipFail', 'Could not read archive.')
								) +
								'</td></tr>'
						);
						return;
					}
					var d = res.data;
					$('#wpps-br-browse-title').text(d.browse_title || str('browseContents', 'Browse contents'));

					if (d.kind === 'sql') {
						setBrowseZipMode('sql');
						var meta = d.file ? String(d.file) : '';
						if (d.size_formatted) {
							meta += (meta ? ' — ' : '') + d.size_formatted;
						}
						$('#wpps-br-fm-sql-meta').text(meta);
						var du = fixAmpInUrl(d.download_url || '#');
						$('#wpps-br-fm-sql-download').attr('data-href', du);
						return;
					}

					browseZipContext = {
						backupId: partPanelBackup,
						part: partPanelPart,
						archiveLabel: d.archive_label || '',
						entryNonce: d.zip_entry_nonce || '',
						adminPostUrl: (wppsBackupRestore && wppsBackupRestore.adminPostUrl) || '',
						mode: '',
						sqlDownloadUrl: ''
					};
					setBrowseZipMode('zip');
					browseZipLazy = true;
					browseZipTreeRoot = null;
					browseCurrentPath = d.browse_subpath != null ? d.browse_subpath : '';
					renderFmBreadcrumbs(browseCurrentPath);
					$('#wpps-br-fm-up').prop('disabled', browseCurrentPath === '');
					$('#wpps-br-browse-notice').attr('hidden', true).text('');
					paintBrowseRowsFromApi(d.children || []);
				}
			).fail(function () {
				$('#wpps-br-browse-title').text(str('browseContents', 'Browse contents'));
				setBrowseZipMode('');
				$('#wpps-br-fm-main').removeAttr('hidden');
				$('#wpps-br-fm-toolbar').attr('hidden', true);
				$('#wpps-br-fm-tbody').html(
					'<tr><td colspan="3" class="wpps-br-fm-empty">' +
						escapeHtml(str('browseZipFail', 'Could not read archive.')) +
						'</td></tr>'
				);
			});
		});
	});

	/* ── Logs accordion toggle ── */
	$(document).on('click', '.wpps-br-accordion-header', function () {
		var $acc = $(this).closest('.wpps-br-accordion');
		var isOpen = $acc.hasClass('is-open');
		$acc.toggleClass('is-open', !isOpen);
		$(this).attr('aria-expanded', !isOpen);
	});

	/* ── Log files filter, clear & delete ── */
	$(document).on('click', '.wpps-br-activity-filter', function () {
		var filter = $(this).data('filter');
		$('.wpps-br-activity-filter').removeClass('is-active');
		$(this).addClass('is-active');
		var $rows = $('#wpps-br-activity-log-table tbody tr');
		if (filter === 'all') {
			$rows.removeClass('wpps-br-al-hidden');
		} else {
			$rows.each(function () {
				var type = $(this).data('log-type');
				$(this).toggleClass('wpps-br-al-hidden', type !== filter);
			});
		}
	});

	$('#wpps-br-activity-clear').on('click', function () {
		if (!confirm(str('clearLogsConfirm', 'Delete all log files? This cannot be undone.'))) {
			return;
		}
		var $btn = $(this).prop('disabled', true);
		$.post(
			ajaxurl,
			{
				action: 'wpps_br_clear_activity_log',
				nonce: wppsBackupRestore.nonce
			},
			function (res) {
				$btn.prop('disabled', false);
				if (res && res.success) {
					$('#wpps-br-activity-log-table tbody').html('');
					$('#wpps-br-activity-log-section .wpps-br-activity-log-body').html(
						'<p class="wpps-br-muted">' + escapeHtml(str('noLogs', 'No log files found.')) + '</p>'
					);
					$('.wpps-br-count', '#wpps-br-activity-log-section').remove();
				}
			}
		);
	});

	$(document).on('click', '.wpps-br-delete-log-file', function () {
		var $row   = $(this).closest('tr');
		var bid    = $row.data('backup-id');
		var logFile = $row.data('log-file');
		if (!confirm(str('deleteLogConfirm', 'Delete this log file?'))) {
			return;
		}
		var $btn = $(this).prop('disabled', true);
		$.post(
			ajaxurl,
			{
				action: 'wpps_br_delete_log_file',
				nonce: wppsBackupRestore.nonce,
				backup_id: bid,
				log_file: logFile
			},
			function (res) {
				if (res && res.success) {
					$row.fadeOut(300, function () {
						$row.remove();
						var remaining = $('#wpps-br-activity-log-table tbody tr').length;
						if (remaining === 0) {
							$('#wpps-br-activity-log-section .wpps-br-activity-log-body').html(
								'<p class="wpps-br-muted">' + escapeHtml(str('noLogs', 'No log files found.')) + '</p>'
							);
							$('.wpps-br-count', '#wpps-br-activity-log-section').remove();
						} else {
							$('.wpps-br-count', '#wpps-br-activity-log-section').text(remaining);
						}
					});
				} else {
					$btn.prop('disabled', false);
				}
			}
		);
	});

	function openBrLogFileModal(title, text) {
		var t = title || str('logModalDefaultTitle', 'Log');
		$('#wpps-br-log-text-modal-title').text(t);
		$('#wpps-br-log-text-modal-pre').text(text == null ? '' : String(text));
		$('#wpps-br-log-text-modal').removeAttr('hidden');
	}

	$(document).on('click', '.wpps-br-log-text-modal-dismiss', function () {
		$('#wpps-br-log-text-modal').attr('hidden', true);
	});

	$(document).on('click', '#wpps-br-log-text-modal .wpps-br-modal__backdrop', function () {
		$('#wpps-br-log-text-modal').attr('hidden', true);
	});

	$('#wpps-br-show-backup-live-log').on('click', function () {
		var $sec = $('#wpps-br-backup-live-log-section');
		var $btn = $(this);
		if ($sec.prop('hidden')) {
			$sec.prop('hidden', false);
			syncBackupLiveLogPanel();
			$btn.text(str('hideLog', 'Hide log')).attr('aria-expanded', 'true');
		} else {
			$sec.prop('hidden', true);
			$btn.text(str('showLog', 'Show log')).attr('aria-expanded', 'false');
		}
	});

	$('#wpps-br-show-restore-live-log').on('click', function () {
		var $sec = $('#wpps-br-restore-live-log-section');
		var $btn = $(this);
		if (!$sec.length || !$btn.length) {
			return;
		}
		if ($sec.prop('hidden')) {
			$sec.prop('hidden', false);
			syncRestoreLiveLogPanel();
			$btn.text(str('hideLog', 'Hide log')).attr('aria-expanded', 'true');
		} else {
			$sec.prop('hidden', true);
			$btn.text(str('showLog', 'Show log')).attr('aria-expanded', 'false');
		}
	});

	/* ── Schedule tab conditional fields ─────────────────────────────── */
	(function initScheduleTab() {
		var $freq = $('#wpps-br-schedule-frequency');
		var $rowDow = $('#wpps-br-row-day-of-week');
		var $rowDom = $('#wpps-br-row-day-of-month');
		var $enabled = $('#wpps-br-schedule-enabled');
		var $fields = $('#wpps-br-schedule-fields');
		var $emailEnabled = $('#wpps-br-email-enabled');
		var $emailFields = $('#wpps-br-email-fields');

		if (!$freq.length) return;

		function toggleDayFields() {
			var v = $freq.val();
			$rowDow.toggle(v === 'weekly');
			$rowDom.toggle(v === 'monthly');
		}

		function toggleBody() {
			var on = $enabled.is(':checked');
			$fields.slideToggle(on ? 200 : 150);
		}

		function toggleEmailBody() {
			var on = $emailEnabled.is(':checked');
			$emailFields.slideToggle(on ? 200 : 150);
		}

		$freq.on('change', toggleDayFields);
		$enabled.on('change', toggleBody);
		$emailEnabled.on('change', toggleEmailBody);

		toggleDayFields();
		if (!$enabled.is(':checked')) $fields.hide();
		if (!$emailEnabled.is(':checked')) $emailFields.hide();
	})();

	/* ── Settings tab: manual backup / restore email toggles ─────────── */
	(function initSettingsManualEmailToggles() {
		var $bk = $('#wpps-br-manual-backup-email-enabled');
		var $bkFields = $('#wpps-br-manual-backup-email-fields');
		var $rs = $('#wpps-br-manual-restore-email-enabled');
		var $rsFields = $('#wpps-br-manual-restore-email-fields');
		if (!$bk.length && !$rs.length) {
			return;
		}
		function toggle($on, $body) {
			var show = $on.is(':checked');
			$body.slideToggle(show ? 200 : 150);
		}
		if ($bk.length) {
			$bk.on('change', function () {
				toggle($bk, $bkFields);
			});
			if (!$bk.is(':checked')) {
				$bkFields.hide();
			}
		}
		if ($rs.length) {
			$rs.on('change', function () {
				toggle($rs, $rsFields);
			});
			if (!$rs.is(':checked')) {
				$rsFields.hide();
			}
		}
	})();

})(jQuery);
