/**
 * TrustLens Admin JavaScript
 *
 * @package TrustLens
 * @since   1.0.0
 */

(function($) {
	'use strict';

	/**
	 * TrustLens Admin object.
	 */
	var TrustLensAdmin = {

		/**
		 * Initialize.
		 */
		init: function() {
			this.bindEvents();
			this.restoreFlash();
			this.initTokenInputs();
		},

		/**
		 * Show a transient toast notification (bottom-right corner).
		 *
		 * @param {string} message Text to show.
		 * @param {string} type    'success' (default) or 'error'.
		 */
		showToast: function(message, type) {
			if (!message) {
				return;
			}
			var $wrap = $('#wstl-toast-wrap');
			if (!$wrap.length) {
				$wrap = $('<div id="wstl-toast-wrap" class="wstl-toast-wrap"></div>').appendTo(document.body);
			}
			var $toast = $('<div class="wstl-toast"></div>')
				.addClass('wstl-toast-' + (type === 'error' ? 'error' : 'success'))
				.text(message)
				.appendTo($wrap);
			// Force reflow so the entrance transition runs from the start state.
			void $toast[0].offsetHeight;
			$toast.addClass('is-visible');
			setTimeout(function() {
				$toast.removeClass('is-visible');
				setTimeout(function() { $toast.remove(); }, 300);
			}, 3200);
		},

		/**
		 * Reload while preserving scroll position and queuing a toast to show
		 * once the fresh page has loaded. Used for actions that change
		 * server-rendered data (score, segment, signals), where a partial DOM
		 * update would risk showing stale values.
		 *
		 * @param {string} message Toast to show after reload.
		 * @param {string} type    'success' (default) or 'error'.
		 */
		reloadWithFlash: function(message, type) {
			try {
				sessionStorage.setItem('wstl_scroll', String(window.pageYOffset || 0));
				sessionStorage.setItem('wstl_flash', JSON.stringify({ message: message || '', type: type || 'success' }));
			} catch (err) {}
			location.reload();
		},

		/**
		 * Restore scroll position and show any queued toast after a
		 * reloadWithFlash(). Safe no-op when nothing is queued.
		 */
		restoreFlash: function() {
			try {
				var scroll = sessionStorage.getItem('wstl_scroll');
				if (scroll !== null) {
					sessionStorage.removeItem('wstl_scroll');
					window.scrollTo(0, parseInt(scroll, 10) || 0);
				}
				var flash = sessionStorage.getItem('wstl_flash');
				if (flash) {
					sessionStorage.removeItem('wstl_flash');
					var data = JSON.parse(flash);
					this.showToast(data.message, data.type);
				}
			} catch (err) {}
		},

		/**
		 * Toggle the detail-page block button + "Blocked" badge in place.
		 * Block/unblock only flips is_blocked, so no reload is needed.
		 *
		 * @param {jQuery}  $btn    The clicked action button.
		 * @param {boolean} blocked New blocked state.
		 */
		markBlocked: function($btn, blocked) {
			var $meta = $('.wstl-pagebar-meta');
			if (blocked) {
				$btn.removeClass('wstl-action-block').addClass('wstl-action-unblock').text('Unblock').prop('disabled', false);
				if ($meta.length && !$meta.find('[data-chip="blocked"]').length) {
					$('<span class="wstl-page-meta-pill wstl-page-meta-pill-danger" data-chip="blocked"></span>').text('Blocked').appendTo($meta);
				}
			} else {
				$btn.removeClass('wstl-action-unblock').addClass('wstl-action-block').text('Block customer').prop('disabled', false);
				$meta.find('[data-chip="blocked"]').remove();
			}
		},

		/**
		 * Open a styled dialog matching the admin modal design. Global, so it
		 * works on any page. Returns { close, $dialog }.
		 *
		 * @param {Object} opts
		 *   title        Header text.
		 *   bodyHtml     Inner HTML for the body.
		 *   confirmLabel Primary button text (default 'Confirm').
		 *   cancelLabel  Cancel text (default 'Cancel'); pass null to hide.
		 *   danger       Red accent + danger confirm button.
		 *   onReady      fn($dialog) after insert \u2014 wire fields, focus.
		 *   onConfirm    fn($dialog) on confirm; return true to keep it open.
		 */
		openDialog: function(opts) {
			opts = opts || {};
			var hasCancel = opts.cancelLabel !== null;
			var footer = '';
			if (hasCancel) {
				footer += '<button type="button" class="button wstl-dialog-cancel"></button>';
			}
			footer += '<button type="button" class="button wstl-dialog-confirm' + (opts.danger ? ' is-danger' : '') + '"></button>';

			var $overlay = $(
				'<div class="wstl-dialog-overlay">' +
					'<div class="wstl-dialog' + (opts.danger ? ' wstl-dialog-danger' : '') + '" role="dialog" aria-modal="true">' +
						'<div class="wstl-dialog-header"><h2></h2>' +
							'<button type="button" class="wstl-dialog-close" aria-label="Close">\u00d7</button></div>' +
						'<div class="wstl-dialog-body"></div>' +
						'<div class="wstl-dialog-footer">' + footer + '</div>' +
					'</div>' +
				'</div>'
			);
			$overlay.find('.wstl-dialog-header h2').text(opts.title || '');
			$overlay.find('.wstl-dialog-body').html(opts.bodyHtml || '');
			$overlay.find('.wstl-dialog-confirm').text(opts.confirmLabel || 'Confirm');
			if (hasCancel) { $overlay.find('.wstl-dialog-cancel').text(opts.cancelLabel || 'Cancel'); }
			$overlay.appendTo(document.body);

			var $dialog = $overlay.find('.wstl-dialog');

			function close() {
				$overlay.remove();
				$(document).off('keydown.wstlDialog');
				if (opts.onClose) { opts.onClose(); }
			}
			$overlay.on('click', function(e) { if (e.target === this) { close(); } });
			$overlay.on('click', '.wstl-dialog-cancel, .wstl-dialog-close', close);
			$(document).on('keydown.wstlDialog', function(e) { if (e.key === 'Escape') { close(); } });
			$overlay.on('click', '.wstl-dialog-confirm', function() {
				if (opts.onConfirm && opts.onConfirm($dialog) === true) { return; }
				close();
			});

			if (opts.onReady) { opts.onReady($dialog); }
			return { close: close, $dialog: $dialog };
		},

		/**
		 * Styled confirmation dialog (replaces native confirm() for destructive
		 * actions). Danger-red by default.
		 *
		 * @param {Object} opts  { title, message, confirmLabel, danger, onConfirm }
		 */
		confirmDialog: function(opts) {
			opts = opts || {};
			var $body = $('<div>').append($('<p>').text(opts.message || ''));
			TrustLensAdmin.openDialog({
				title: opts.title || 'Are you sure?',
				bodyHtml: $body.html(),
				confirmLabel: opts.confirmLabel || 'Confirm',
				danger: opts.danger !== false,
				onConfirm: function() { if (opts.onConfirm) { opts.onConfirm(); } }
			});
		},

		/**
		 * Show the block-reason picker. Calls onConfirm(reason) when confirmed.
		 *
		 * @param {Object}   opts      Optional { count } for bulk wording.
		 * @param {Function} onConfirm Receives the composed reason string.
		 */
		promptBlockReason: function(opts, onConfirm) {
			opts = opts || {};
			var label = opts.count && opts.count > 1 ? ('Block ' + opts.count + ' customers') : 'Block customer';
			var presets = [
				'High return / refund abuse',
				'Coupon abuse',
				'Linked to fraud ring',
				'Card testing / chargebacks',
				'Manual review \u2014 suspicious activity',
				'Other'
			];
			var optionsHtml = '';
			for (var i = 0; i < presets.length; i++) {
				optionsHtml += '<option value="' + presets[i] + '">' + presets[i] + '</option>';
			}
			var body =
				'<p class="wstl-dialog-note">Saved to the customer notes and event log so you have a record of why.</p>' +
				'<div class="wstl-dialog-field"><label>Reason' +
					'<select class="wstl-reason-select">' + optionsHtml + '</select></label></div>' +
				'<div class="wstl-dialog-field"><label>Details <span class="wstl-dialog-field-optional">optional</span>' +
					'<textarea class="wstl-reason-detail" rows="3"></textarea></label></div>';

			TrustLensAdmin.openDialog({
				title: label,
				bodyHtml: body,
				confirmLabel: label,
				danger: true,
				onReady: function($d) { $d.find('.wstl-reason-select').trigger('focus'); },
				onConfirm: function($d) {
					var preset = $d.find('.wstl-reason-select').val();
					var detail = ($d.find('.wstl-reason-detail').val() || '').replace(/^\s+|\s+$/g, '');
					if (preset === 'Other') {
						onConfirm(detail || 'Blocked from admin');
					} else {
						onConfirm(detail ? (preset + ': ' + detail) : preset);
					}
				}
			});
		},

		/**
		 * Quick Start: persist a setting toggle (blocking / notifications).
		 */
		quickstartToggle: function() {
			var $input  = $(this);
			var setting = $input.data('setting');
			var enabled = $input.is(':checked') ? 1 : 0;
			$input.prop('disabled', true);
			$.post(TrustLens.ajaxUrl, {
				action: 'wstl_quickstart_toggle',
				nonce: TrustLens.nonce,
				setting: setting,
				enabled: enabled
			}, function(response) {
				$input.prop('disabled', false);
				if (response && response.success) {
					TrustLensAdmin.showToast(response.data.message || 'Saved.');
				} else {
					$input.prop('checked', !enabled);
					TrustLensAdmin.showToast((response && response.data && response.data.message) || TrustLens.i18n.error, 'error');
				}
			}).fail(function() {
				$input.prop('disabled', false).prop('checked', !enabled);
				TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
			});
		},

		/**
		 * Quick Start: dismiss the setup card so it stays hidden.
		 */
		quickstartDismiss: function(e) {
			e.preventDefault();
			var $btn = $(this);
			$btn.prop('disabled', true);
			$.post(TrustLens.ajaxUrl, {
				action: 'wstl_quickstart_dismiss',
				nonce: TrustLens.nonce
			}, function(response) {
				if (response && response.success) {
					$('#wstl-quickstart').slideUp(200, function() {
						$(this).closest('.wstl-dashboard-section').remove();
					});
					TrustLensAdmin.showToast(response.data.message || 'Hidden.');
				} else {
					$btn.prop('disabled', false);
					TrustLensAdmin.showToast((response && response.data && response.data.message) || TrustLens.i18n.error, 'error');
				}
			}).fail(function() {
				$btn.prop('disabled', false);
				TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
			});
		},

		/**
		 * Extract a useful message from failed AJAX responses.
		 *
		 * @param {jqXHR} xhr jQuery XHR object.
		 * @return {string} Human-readable error.
		 */
		getAjaxErrorMessage: function(xhr) {
			if (xhr && xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
				return xhr.responseJSON.data.message;
			}

			if (xhr && xhr.responseText) {
				try {
					var parsed = JSON.parse(xhr.responseText);
					if (parsed && parsed.data && parsed.data.message) {
						return parsed.data.message;
					}
				} catch (e) {
					// Not JSON; fall through to generic message.
				}
			}

			return TrustLens.i18n.error;
		},

		/**
		 * Bind events.
		 */
		bindEvents: function() {
			var self = this;

			$(document).on('click', '.wstl-action-block', this.blockCustomer);
			$(document).on('click', '.wstl-action-unblock', this.unblockCustomer);
			$(document).on('click', '.wstl-action-allowlist', this.allowlistCustomer);
			$(document).on('click', '.wstl-action-remove-allowlist', this.removeAllowlist);
			$(document).on('click', '.wstl-action-unflag', this.unflagCustomer);

			// Sync controls.
			$(document).on('click', '#wstl-start-sync', this.startSync);
			$(document).on('click', '#wstl-stop-sync', this.stopSync);
			$(document).on('click', '#wstl-resync', this.startSync);
			$(document).on('click', '#wstl-reset-sync', this.resetSync);

			// API key generation.
			$(document).on('click', '#wstl-generate-api-key, #wstl-regenerate-api-key', this.generateApiKey);

			// Bulk actions.
			$(document).on('click', '#wstl-select-all, #wstl-select-all-bottom', this.toggleSelectAll);
			$(document).on('change', '.wstl-customer-checkbox', this.updateSelectedCount);
			$(document).on('click', '#wstl-bulk-apply, #wstl-bulk-apply-bottom', this.applyBulkAction);
			$(document).on('click', '#wstl-export-csv', this.exportCSV);
			this.updateSelectedCount();
			this.initDirtyWatch();

			// Customer detail actions.
			$(document).on('click', '.wstl-action-recalculate', this.recalculateScore);
			$(document).on('click', '.wstl-save-note', this.saveNote);

			// Quick Start (dashboard).
			$(document).on('change', '.wstl-quickstart-setting', this.quickstartToggle);
			$(document).on('click', '#wstl-quickstart-dismiss', this.quickstartDismiss);

			// Automation rules.
			$(document).on('click', '#wstl-add-automation-rule, #wstl-add-automation-rule-empty, #wstl-add-automation-rule-bar', function() { self.openAutomationModal(); });
			$(document).on('click', '.wstl-edit-rule', function(e) {
				e.preventDefault();
				e.stopPropagation();
				var ruleId = $(this).closest('[data-rule-id]').data('rule-id');
				self.openAutomationModal(ruleId);
			});
			$(document).on('click', '#wstl-add-automation-rule-empty-2', function() { self.openAutomationModal(); });
			$(document).on('click', '.wstl-delete-rule', this.deleteAutomationRule);
			// Submit covers both the click-on-Save and Enter-key paths since
			// #wstl-save-rule is type="submit" inside #wstl-rule-form.
			$(document).on('submit', '#wstl-rule-form', this.saveAutomationRule);
			$(document).on('click', '#wstl-add-condition', this.addAutomationCondition);
			$(document).on('click', '.wstl-remove-condition', this.removeAutomationCondition);

			// Webhooks.
			$(document).on('click', '#wstl-add-webhook', function() { self.openWebhookModal(); });
			$(document).on('click', '.wstl-test-webhook', this.testWebhook);
			$(document).on('click', '.wstl-delete-webhook', this.deleteWebhook);
			$(document).on('submit', '#wstl-webhook-form', this.saveWebhook);

			// Scheduled reports.
			$(document).on('click', '#wstl-add-report-schedule', function() { self.openReportModal(); });
			$(document).on('click', '.wstl-send-report-now', this.sendReportNow);
			$(document).on('click', '.wstl-delete-schedule', this.deleteReportSchedule);
			$(document).on('submit', '#wstl-schedule-form', this.saveReportSchedule);
			$(document).on('change', '.wstl-schedule-enabled', this.toggleReportScheduleEnabled);

			// Test notification.
			$(document).on('click', '#wstl-send-test-notification', this.sendTestNotification);

			// Test data (Settings > Data).
			$(document).on('click', '#wstl-seed-test-data', this.seedTestData);
			$(document).on('click', '#wstl-remove-test-data', this.removeTestData);
			$(document).on('click', '#wstl-check-clean-test-data', this.checkCleanTestData);

			// Modal close.
			$(document).on('click', '.wstl-modal, .wstl-modal-close, .wstl-modal-overlay, .wstl-modal-cancel', this.closeModal);
			$(document).on('keydown', function(e) {
				if (e.key === 'Escape') {
					self.closeModal();
				}
			});

			// Resume AJAX batch processing if sync is running.
			if ($('.wstl-sync-progress:visible').length) {
				this.processNextBatch();
			}
		},

		/**
		 * Block customer.
		 *
		 * @param {Event} e Click event.
		 */
		blockCustomer: function(e) {
			e.preventDefault();

			var $button = $(this);
			var hash = $button.data('hash');

			TrustLensAdmin.promptBlockReason({}, function(reason) {
				$button.prop('disabled', true).text('...');

				$.ajax({
					url: TrustLens.ajaxUrl,
					type: 'POST',
					data: {
						action: 'wstl_block_customer',
						nonce: TrustLens.nonce,
						email_hash: hash,
						reason: reason
					},
					success: function(response) {
						if (response.success) {
							TrustLensAdmin.markBlocked($button, true);
							TrustLensAdmin.showToast(response.data.message || 'Customer blocked.');
						} else {
							TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
							$button.prop('disabled', false).text('Block customer');
						}
					},
					error: function() {
						TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
						$button.prop('disabled', false).text('Block customer');
					}
				});
			});
		},

		/**
		 * Unblock customer.
		 *
		 * @param {Event} e Click event.
		 */
		unblockCustomer: function(e) {
			e.preventDefault();

			var $button = $(this);
			var hash = $button.data('hash');

			$button.prop('disabled', true).text('...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_unblock_customer',
					nonce: TrustLens.nonce,
					email_hash: hash
				},
				success: function(response) {
					if (response.success) {
						TrustLensAdmin.markBlocked($button, false);
						TrustLensAdmin.showToast(response.data.message || 'Customer unblocked.');
					} else {
						TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
						$button.prop('disabled', false).text('Unblock');
					}
				},
				error: function() {
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
					$button.prop('disabled', false).text('Unblock');
				}
			});
		},

		/**
		 * Add to allowlist.
		 *
		 * @param {Event} e Click event.
		 */
		allowlistCustomer: function(e) {
			e.preventDefault();
			var $button = $(this);
			var hash = $button.data('hash');

			TrustLensAdmin.confirmDialog({
				title: 'Add to allowlist',
				message: TrustLens.i18n.confirm_allowlist || 'Add this customer to the allowlist?',
				confirmLabel: 'Add to allowlist',
				danger: false,
				onConfirm: function() {
					$button.prop('disabled', true).text('...');
					$.ajax({
						url: TrustLens.ajaxUrl,
						type: 'POST',
						data: {
							action: 'wstl_allowlist_customer',
							nonce: TrustLens.nonce,
							email_hash: hash
						},
						success: function(response) {
							if (response.success) {
								TrustLensAdmin.reloadWithFlash(response.data.message || 'Customer added to allowlist.');
							} else {
								TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
								$button.prop('disabled', false).text('Add to Allowlist');
							}
						},
						error: function() {
							TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
							$button.prop('disabled', false).text('Add to Allowlist');
						}
					});
				}
			});
		},

		/**
		 * Remove from allowlist.
		 *
		 * @param {Event} e Click event.
		 */
		removeAllowlist: function(e) {
			e.preventDefault();

			var $button = $(this);
			var hash = $button.data('hash');

			$button.prop('disabled', true).text('...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_remove_allowlist',
					nonce: TrustLens.nonce,
					email_hash: hash
				},
				success: function(response) {
					if (response.success) {
						TrustLensAdmin.reloadWithFlash(response.data.message || 'Customer removed from allowlist.');
					} else {
						TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
						$button.prop('disabled', false).text('Remove from Allowlist');
					}
				},
				error: function() {
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
					$button.prop('disabled', false).text('Remove from Allowlist');
				}
			});
		},

		unflagCustomer: function(e) {
			e.preventDefault();

			var $button = $(this);
			var hash = $button.data('hash');

			$button.prop('disabled', true).text('...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_unflag_customer',
					nonce: TrustLens.nonce,
					email_hash: hash
				},
				success: function(response) {
					if (response.success) {
						TrustLensAdmin.reloadWithFlash(response.data.message || 'Flag cleared.');
					} else {
						TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
						$button.prop('disabled', false).text('Clear review flag');
					}
				},
				error: function() {
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
					$button.prop('disabled', false).text('Clear review flag');
				}
			});
		},

		/**
		 * Start historical sync.
		 *
		 * @param {Event} e Click event.
		 */
		startSync: function(e) {
			e.preventDefault();

			var self = TrustLensAdmin;
			var $button = $(this);
			if (!$button.data('original-text')) {
				$button.data('original-text', $button.text());
			}
			$button.prop('disabled', true).text(TrustLens.i18n.starting || 'Starting...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_start_sync',
					nonce: TrustLens.nonce
				},
				success: function(response) {
					if (response.success) {
						var totalOrders = response.data.status.total_orders;
						var ordersText = '0 of ' + self.formatNumber(totalOrders) + ' orders processed';
						var fullText = 'Processed ' + ordersText + ' (0%)';

						// Switch UI: hide start area, show progress area (works on Dashboard and Data tab).
						$('#wstl-sync-start-area').hide();
						$('#wstl-sync-progress-area').show();
						$('#wstl-start-sync').hide();
						$('#wstl-stop-sync').show();

						// Initialize progress bar and text.
						$('.wstl-progress-fill').css('width', '0%');
						$('#wstl-sync-progress-text').text('0%');
						var $ordersEl = $('#wstl-sync-orders-text');
						if ($ordersEl.length) {
							$ordersEl.text($ordersEl.is('span') ? ordersText : fullText);
						}

						// Start processing batches.
						self.processNextBatch();
					} else {
						TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
						$button.prop('disabled', false).text($button.data('original-text') || TrustLens.i18n.start_sync || 'Start Historical Sync');
					}
				},
				error: function(xhr) {
					TrustLensAdmin.showToast(self.getAjaxErrorMessage(xhr), 'error');
					$button.prop('disabled', false).text($button.data('original-text') || TrustLens.i18n.start_sync || 'Start Historical Sync');
				}
			});
		},

		/**
		 * Stop historical sync.
		 *
		 * @param {Event} e Click event.
		 */
		stopSync: function(e) {
			e.preventDefault();

			var self = TrustLensAdmin;
			var $button = $(this);
			$button.prop('disabled', true).text(TrustLens.i18n.stopping || 'Stopping...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_stop_sync',
					nonce: TrustLens.nonce
				},
				success: function(response) {
					location.reload();
				},
				error: function(xhr) {
					TrustLensAdmin.showToast(self.getAjaxErrorMessage(xhr), 'error');
					$button.prop('disabled', false).text(TrustLens.i18n.stop_sync || 'Stop Sync');
				}
			});
		},

		/**
		 * Reset sync data.
		 *
		 * @param {Event} e Click event.
		 */
		resetSync: function(e) {
			e.preventDefault();
			var self = TrustLensAdmin;
			var $button = $(this);

			TrustLensAdmin.confirmDialog({
				title: 'Reset sync data',
				message: TrustLens.i18n.confirm_reset || 'Are you sure you want to delete all TrustLens data? This cannot be undone.',
				confirmLabel: 'Reset',
				onConfirm: function() {
					$button.prop('disabled', true).text(TrustLens.i18n.resetting || 'Resetting...');
					$.ajax({
						url: TrustLens.ajaxUrl,
						type: 'POST',
						data: {
							action: 'wstl_reset_sync',
							nonce: TrustLens.nonce
						},
						success: function(response) {
							location.reload();
						},
						error: function(xhr) {
							self.showToast(self.getAjaxErrorMessage(xhr), 'error');
							$button.prop('disabled', false).text(TrustLens.i18n.reset || 'Reset');
						}
					});
				}
			});
		},

		/**
		 * Process the next sync batch via AJAX.
		 * Calls itself recursively until sync completes.
		 */
		processNextBatch: function() {
			var self = this;

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_process_sync_batch',
					nonce: TrustLens.nonce
				},
				success: function(response) {
					if (response.success) {
						var status = response.data.status;
						var progress = response.data.progress;
						var ordersText = self.formatNumber(status.processed_orders) + ' of ' +
							self.formatNumber(status.total_orders) + ' orders processed';
						var fullText = 'Processed ' + ordersText + ' (' + progress + '%)';

						// Update progress bar and text (Data tab has single #wstl-sync-orders-text <p>, dashboard has two spans).
						$('.wstl-progress-fill').css('width', progress + '%');
						$('#wstl-sync-progress-text').text(progress + '%');
						var $ordersEl = $('#wstl-sync-orders-text');
						if ($ordersEl.length) {
							$ordersEl.text($ordersEl.is('span') ? ordersText : fullText);
						}

						if (status.status === 'completed') {
							// Show success message, then reload to full dashboard.
							var customersText = self.formatNumber(status.total_customers) + ' customer' + (status.total_customers !== 1 ? 's' : '');
							var $successMsg = $(
								'<div class="wstl-sync-success">' +
									'<span class="dashicons dashicons-yes-alt"></span>' +
									'<div>' +
										'<strong>' + (TrustLens.i18n.sync_complete || 'Sync complete!') + '</strong>' +
										'<p>' + customersText + ' profiled \u2014 loading your dashboard\u2026</p>' +
									'</div>' +
								'</div>'
							);
							$('#wstl-sync-progress-area').after($successMsg);
							$('#wstl-stop-sync').hide();

							setTimeout(function() {
								location.reload();
							}, 2500);
						} else {
							// Process next batch.
							self.processNextBatch();
						}
					} else {
						self.handleSyncBatchError(response.data.message || TrustLens.i18n.error);
					}
				},
				error: function(xhr) {
					self.handleSyncBatchError(self.getAjaxErrorMessage(xhr));
				}
			});
		},

		/**
		 * Recover sync UI when batch processing fails.
		 *
		 * @param {string} message Error message.
		 */
		handleSyncBatchError: function(message) {
			TrustLensAdmin.showToast(message || TrustLens.i18n.error, 'error');

			$('#wstl-sync-progress-area').hide().addClass('wstl-modal-hidden');
			$('#wstl-sync-start-area').show().removeClass('wstl-modal-hidden');
			$('#wstl-stop-sync').hide().prop('disabled', false).text(TrustLens.i18n.stop_sync || 'Stop Sync');
			$('#wstl-start-sync').show().prop('disabled', false).text(TrustLens.i18n.start_sync || 'Start Sync');
		},


		/**
		 * Format number with commas.
		 *
		 * @param {number} num Number to format.
		 * @return {string} Formatted number.
		 */
		formatNumber: function(num) {
			return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
		},

		/**
		 * Generate API key.
		 *
		 * @param {Event} e Click event.
		 */
		generateApiKey: function(e) {
			e.preventDefault();

			var $button = $(this);
			var isRegenerate = $button.attr('id') === 'wstl-regenerate-api-key';
			var resetLabel = isRegenerate ? 'Regenerate' : 'Generate API Key';

			function run() {
				$button.prop('disabled', true).text('Generating...');
				$.ajax({
					url: TrustLens.ajaxUrl,
					type: 'POST',
					data: {
						action: 'wstl_generate_api_key',
						nonce: TrustLens.nonce
					},
					success: function(response) {
						if (response.success) {
							TrustLensAdmin.showApiKey(response.data.api_key);
						} else {
							$button.prop('disabled', false).text(resetLabel);
							TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
						}
					},
					error: function() {
						$button.prop('disabled', false).text(resetLabel);
						TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
					}
				});
			}

			if (isRegenerate) {
				TrustLensAdmin.confirmDialog({
					title: 'Regenerate API key',
					message: 'Regenerating the API key will invalidate the existing key. Continue?',
					confirmLabel: 'Regenerate',
					onConfirm: run
				});
			} else {
				run();
			}
		},

		/**
		 * Reveal a freshly generated API key in a dialog with a Copy button.
		 * The page reloads on dismissal so the key's stored state is reflected.
		 *
		 * @param {string} key The generated API key.
		 */
		showApiKey: function(key) {
			var $body = $('<div>');
			$body.append($('<p>').text('Copy this key now — for security it will not be shown again.'));
			var $row = $('<div class="wstl-dialog-key">');
			$row.append($('<code>').text(key));
			$row.append($('<button type="button" class="button wstl-dialog-copy">').text('Copy'));
			$body.append($row);

			TrustLensAdmin.openDialog({
				title: 'Your new API key',
				bodyHtml: $body.html(),
				confirmLabel: 'Done',
				cancelLabel: null,
				onClose: function() { location.reload(); },
				onReady: function($d) {
					$d.find('.wstl-dialog-copy').on('click', function() {
						var $btn = $(this);
						var done = function() { $btn.text('Copied'); };
						if (navigator.clipboard && navigator.clipboard.writeText) {
							navigator.clipboard.writeText(key).then(done).catch(function() {
								TrustLensAdmin.selectText($d.find('.wstl-dialog-key code')[0]);
								$btn.text('Select & copy');
							});
						} else {
							TrustLensAdmin.selectText($d.find('.wstl-dialog-key code')[0]);
							$btn.text('Select & copy');
						}
					});
				}
			});
		},

		/** Select an element's text contents (clipboard fallback). */
		selectText: function(el) {
			if (!el || !window.getSelection) { return; }
			var range = document.createRange();
			range.selectNodeContents(el);
			var sel = window.getSelection();
			sel.removeAllRanges();
			sel.addRange(range);
		},

		/**
		 * Seed test data (Settings > Data).
		 *
		 * @param {Event} e Click event.
		 */
		seedTestData: function(e) {
			e.preventDefault();
			var $btn = $(this);
			var $spinner = $('#wstl-test-data-spinner');
			var $result = $('#wstl-test-data-result');

			TrustLensAdmin.confirmDialog({
				title: 'Generate test data',
				message: TrustLens.i18n.confirm_seed || 'Generate 60 test customers? This will add sample data to your database.',
				confirmLabel: 'Generate',
				danger: false,
				onConfirm: function() {
					$btn.prop('disabled', true);
					$spinner.addClass('is-active');
					$result.html('');

					$.post(TrustLens.ajaxUrl, {
						action: 'wstl_seed_test_data',
						nonce: TrustLens.nonce,
						deterministic: $('#wstl-seed-deterministic').is(':checked') ? 1 : 0
					}, function(response) {
						$spinner.removeClass('is-active');
						if (response.success) {
							$result.html('<span class="wstl-test-data-success"><span class="dashicons dashicons-yes"></span> ' + response.data.message + '</span>');
							setTimeout(function() {
								location.reload();
							}, 1500);
						} else {
							$btn.prop('disabled', false);
							$result.html('<span class="wstl-test-data-error"><span class="dashicons dashicons-no"></span> ' + (response.data.message || 'Error') + '</span>');
						}
					}).fail(function() {
						$spinner.removeClass('is-active');
						$btn.prop('disabled', false);
						$result.html('<span class="wstl-test-data-error"><span class="dashicons dashicons-no"></span> ' + (TrustLens.i18n.request_failed || 'Request failed.') + '</span>');
					});
				}
			});
		},

		/**
		 * Remove test data (Settings > Data).
		 *
		 * @param {Event} e Click event.
		 */
		removeTestData: function(e) {
			e.preventDefault();
			var $btn = $(this);
			var $spinner = $('#wstl-test-data-spinner');
			var $result = $('#wstl-test-data-result');

			TrustLensAdmin.confirmDialog({
				title: 'Remove test data',
				message: TrustLens.i18n.confirm_remove || 'Remove all test customers? This action cannot be undone.',
				confirmLabel: 'Remove',
				onConfirm: function() {
					$btn.prop('disabled', true);
					$spinner.addClass('is-active');
					$result.html('');

					$.post(TrustLens.ajaxUrl, {
						action: 'wstl_remove_test_data',
						nonce: TrustLens.nonce
					}, function(response) {
						$spinner.removeClass('is-active');
						if (response.success) {
							$result.html('<span class="wstl-test-data-success"><span class="dashicons dashicons-yes"></span> ' + response.data.message + '</span>');
							setTimeout(function() {
								location.reload();
							}, 1500);
						} else {
							$btn.prop('disabled', false);
							$result.html('<span class="wstl-test-data-error"><span class="dashicons dashicons-no"></span> ' + (response.data.message || 'Error') + '</span>');
						}
					}).fail(function() {
						$spinner.removeClass('is-active');
						$btn.prop('disabled', false);
						$result.html('<span class="wstl-test-data-error"><span class="dashicons dashicons-no"></span> ' + (TrustLens.i18n.request_failed || 'Request failed.') + '</span>');
					});
				}
			});
		},

		/**
		 * Check for and clear any residual test data (Settings > Data).
		 *
		 * Always available — works even when the Remove button is hidden, to
		 * sweep orphaned/leftover rows from older seeds.
		 *
		 * @param {Event} e Click event.
		 */
		checkCleanTestData: function(e) {
			e.preventDefault();
			var $btn = $(this);
			var $spinner = $('#wstl-test-data-spinner');
			var $result = $('#wstl-test-data-result');

			TrustLensAdmin.confirmDialog({
				title: 'Check & clean test data',
				message: TrustLens.i18n.confirm_check_clean || 'Scan all tables for test data and permanently delete anything found?',
				confirmLabel: 'Check & clean',
				onConfirm: function() {
					$btn.prop('disabled', true);
					$spinner.addClass('is-active');
					$result.html('');

					$.post(TrustLens.ajaxUrl, {
						action: 'wstl_check_clean_test_data',
						nonce: TrustLens.nonce
					}, function(response) {
						$spinner.removeClass('is-active');
						if (response.success) {
							$result.html('<span class="wstl-test-data-success"><span class="dashicons dashicons-yes"></span> ' + response.data.message + '</span>');
							setTimeout(function() {
								location.reload();
							}, 1800);
						} else {
							$btn.prop('disabled', false);
							$result.html('<span class="wstl-test-data-error"><span class="dashicons dashicons-no"></span> ' + (response.data.message || 'Error') + '</span>');
						}
					}).fail(function() {
						$spinner.removeClass('is-active');
						$btn.prop('disabled', false);
						$result.html('<span class="wstl-test-data-error"><span class="dashicons dashicons-no"></span> ' + (TrustLens.i18n.request_failed || 'Request failed.') + '</span>');
					});
				}
			});
		},

		/**
		 * Toggle select all checkboxes.
		 *
		 * @param {Event} e Click event.
		 */
		toggleSelectAll: function(e) {
			var isChecked = $(this).prop('checked');
			$('.wstl-customer-checkbox').prop('checked', isChecked);
			$('#wstl-select-all, #wstl-select-all-bottom').prop('checked', isChecked);
			TrustLensAdmin.updateSelectedCount();
		},

		/**
		 * Watch settings-style forms for edits and surface an "Unsaved changes"
		 * indicator in the main header, plus a leave-page guard. Cleared on submit
		 * so saving (which reloads) never triggers the guard.
		 */
		initDirtyWatch: function() {
			var $forms = $('form[action$="options.php"]');
			if (!$forms.length) {
				return;
			}
			var $indicator = $('#wstl-hdr-unsaved');
			var dirty = false;

			function beforeUnload(e) {
				e.preventDefault();
				e.returnValue = '';
				return '';
			}
			function markDirty() {
				if (dirty) {
					return;
				}
				dirty = true;
				$indicator.prop('hidden', false);
				window.addEventListener('beforeunload', beforeUnload);
			}
			function clearDirty() {
				dirty = false;
				$indicator.prop('hidden', true);
				window.removeEventListener('beforeunload', beforeUnload);
			}

			$forms.on('input change', 'input, select, textarea', markDirty);
			$forms.on('submit', clearDirty);
		},

		/**
		 * Reflect how many customers are selected in the bulk toolbar.
		 */
		updateSelectedCount: function() {
			var $el = $('#wstl-bulk-count');
			if (!$el.length) {
				return;
			}
			var n = $('.wstl-customer-checkbox:checked').length;
			if (n > 0) {
				var tpl = (window.TrustLens && TrustLens.i18n && TrustLens.i18n.n_selected) ? TrustLens.i18n.n_selected : '%d selected';
				$el.text(tpl.replace('%d', n)).addClass('is-visible');
			} else {
				$el.text('').removeClass('is-visible');
			}
		},

		/**
		 * Apply bulk action.
		 *
		 * @param {Event} e Click event.
		 */
		applyBulkAction: function(e) {
			e.preventDefault();

			var action = $('#wstl-bulk-action-selector').val() || $('#wstl-bulk-action-selector-bottom').val();
			var customers = [];

			$('.wstl-customer-checkbox:checked').each(function() {
				customers.push($(this).val());
			});

			if (!action) {
				TrustLensAdmin.showToast(TrustLens.i18n.select_action || 'Please select an action.', 'error');
				return;
			}

			if (customers.length === 0) {
				TrustLensAdmin.showToast(TrustLens.i18n.select_customers || 'Please select at least one customer.', 'error');
				return;
			}

			var $button = $(this);
			var $spinner = $button.siblings('.spinner');
			var $result = $('#wstl-bulk-result');

			function run(reason) {
				$button.prop('disabled', true);
				$spinner.addClass('is-active');
				$result.removeClass('success error').text('');

				$.ajax({
					url: TrustLens.ajaxUrl,
					type: 'POST',
					data: {
						action: 'wstl_bulk_action',
						nonce: TrustLens.nonce,
						bulk_action: action,
						customers: customers,
						reason: reason || ''
					},
					success: function(response) {
						$spinner.removeClass('is-active');
						$button.prop('disabled', false);

						if (response.success) {
							$result.addClass('success').text(response.data.message);
							setTimeout(function() {
								TrustLensAdmin.reloadWithFlash(response.data.message);
							}, 1200);
						} else {
							$result.addClass('error').text(response.data.message || TrustLens.i18n.error);
						}
					},
					error: function() {
						$spinner.removeClass('is-active');
						$button.prop('disabled', false);
						$result.addClass('error').text(TrustLens.i18n.error);
					}
				});
			}

			if (action === 'block') {
				TrustLensAdmin.promptBlockReason({ count: customers.length }, function(reason) {
					run(reason);
				});
			} else if (action === 'delete') {
				TrustLensAdmin.confirmDialog({
					title: 'Delete customers',
					message: 'Delete ' + customers.length + ' customer(s)? This permanently removes their TrustLens records and cannot be undone.',
					confirmLabel: 'Delete',
					onConfirm: function() { run(''); }
				});
			} else {
				run('');
			}
		},

		/**
		 * Export customers to CSV.
		 *
		 * @param {Event} e Click event.
		 */
		exportCSV: function(e) {
			e.preventDefault();

			var $button = $(this);
			$button.prop('disabled', true).text('Exporting...');

			// Get current filters from URL.
			var urlParams = new URLSearchParams(window.location.search);

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_export_csv',
					nonce: TrustLens.nonce,
					segment: urlParams.get('segment') || '',
					search: urlParams.get('s') || ''
				},
				success: function(response) {
					$button.prop('disabled', false).text('Export CSV');

					if (response.success) {
						// Create download link.
						var blob = new Blob([response.data.csv], { type: 'text/csv' });
						var url = window.URL.createObjectURL(blob);
						var a = document.createElement('a');
						a.href = url;
						a.download = response.data.filename;
						document.body.appendChild(a);
						a.click();
						window.URL.revokeObjectURL(url);
						document.body.removeChild(a);
					} else {
						TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
					}
				},
				error: function() {
					$button.prop('disabled', false).text('Export CSV');
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
				}
			});
		},

		/**
		 * Recalculate customer score.
		 *
		 * @param {Event} e Click event.
		 */
		recalculateScore: function(e) {
			e.preventDefault();

			var $button = $(this);
			var hash = $button.data('hash');

			$button.prop('disabled', true).text('Calculating...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_recalculate_score',
					nonce: TrustLens.nonce,
					email_hash: hash
				},
				success: function(response) {
					if (response.success) {
						TrustLensAdmin.reloadWithFlash(response.data.message || 'Score recalculated.');
					} else {
						TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
						$button.prop('disabled', false).text('Recalculate Score');
					}
				},
				error: function() {
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
					$button.prop('disabled', false).text('Recalculate Score');
				}
			});
		},

		/**
		 * Save admin note.
		 *
		 * @param {Event} e Click event.
		 */
		saveNote: function(e) {
			e.preventDefault();

			var $button = $(this);
			var hash = $button.data('hash');
			var note = $('#wstl-add-note').val().trim();

			if (!note) {
				TrustLensAdmin.showToast('Please enter a note.', 'error');
				return;
			}

			$button.prop('disabled', true).text('Saving...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_save_note',
					nonce: TrustLens.nonce,
					email_hash: hash,
					note: note
				},
				success: function(response) {
					if (response.success) {
						location.reload();
					} else {
						TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
						$button.prop('disabled', false).text('Add Note');
					}
				},
				error: function() {
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
					$button.prop('disabled', false).text('Add Note');
				}
			});
		},

		/**
		 * Open automation rule modal.
		 *
		 * @param {number} ruleId Optional rule ID for editing.
		 */
		openAutomationModal: function(ruleId) {
			var $modal = $('#wstl-rule-modal');

			// Reset form + collapse any extra condition rows from a prior open.
			$modal.find('form')[0].reset();
			$modal.find('#wstl-rule-id').val('');
			$modal.find('#wstl-rule-conditions .wstl-condition-row').slice(1).remove();
			// Clear any error carried over from a previous open.
			TrustLensAdmin._hideRuleError();

			if (ruleId) {
				var $row = $('[data-rule-id="' + ruleId + '"]').first();
				$modal.find('#wstl-rule-id').val(ruleId);
				// data-rule lives on the left-list <a> directly, or on the
				// right-pane header's Edit button (the header only carries data-rule-id).
				var ruleData = $row.data('rule') || $row.find('.wstl-edit-rule').data('rule');
				if (ruleData && typeof ruleData === 'object') {
					$modal.find('#wstl-rule-name').val(ruleData.name || '');
					$modal.find('#wstl-rule-trigger').val(ruleData.trigger || 'score_updated');
					$modal.find('#wstl-rule-action').val(ruleData.action || 'send_email');
					$modal.find('#wstl-rule-action-value').val(ruleData.action_value || '');
					$modal.find('#wstl-rule-enabled').prop('checked', !!ruleData.enabled);
					var conditions = ruleData.conditions || [];
					var $container = $modal.find('#wstl-rule-conditions');
					var $first = $container.find('.wstl-condition-row').first();
					if (conditions.length > 0) {
						$first.find('.wstl-condition-field').val(conditions[0].field || 'trust_score');
						$first.find('.wstl-condition-operator').val(conditions[0].operator || '<');
						$first.find('.wstl-condition-value').val(conditions[0].value || '');
					}
					for (var i = 1; i < conditions.length; i++) {
						// Clone first row and append (simplified; add-condition button may add more)
						var $clone = $first.clone();
						$clone.find('.wstl-condition-field').val(conditions[i].field || 'trust_score');
						$clone.find('.wstl-condition-operator').val(conditions[i].operator || '<');
						$clone.find('.wstl-condition-value').val(conditions[i].value || '');
						$clone.find('select, input').each(function() {
							var $el = $(this);
							var name = $el.attr('name');
							if (name) $el.attr('name', name.replace(/\[0\]/, '[' + i + ']'));
						});
						$container.append($clone);
					}
				}
				$modal.find('#wstl-rule-modal-title').text(TrustLens.i18n.edit_rule || 'Edit Automation Rule');
			} else {
				$modal.find('#wstl-rule-modal-title').text(TrustLens.i18n.add_rule || 'Add Automation Rule');
			}

			$modal.addClass('is-open');
		},

		/**
		 * Save automation rule.
		 *
		 * @param {Event} e Submit event.
		 */
		saveAutomationRule: function(e) {
			e.preventDefault();

			var $form = $(this);
			var $button = $form.find('#wstl-save-rule');
			var self = TrustLensAdmin;

			self._hideRuleError();

			var conditions = [];
			$form.find('#wstl-rule-conditions .wstl-condition-row').each(function() {
				var field = $(this).find('.wstl-condition-field').val();
				var op = $(this).find('.wstl-condition-operator').val();
				var val = $(this).find('.wstl-condition-value').val();
				if (field) {
					conditions.push({ field: field, operator: op, value: val });
				}
			});

			var ruleId = $form.find('#wstl-rule-id').val();
			$button.prop('disabled', true).text(TrustLens.i18n.saving || 'Saving...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_save_automation_rule',
					nonce: TrustLens.nonce,
					rule_id: ruleId,
					name: $form.find('#wstl-rule-name').val(),
					trigger: $form.find('#wstl-rule-trigger').val(),
					rule_action: $form.find('#wstl-rule-action').val(),
					action_value: $form.find('#wstl-rule-action-value').val(),
					enabled: $form.find('#wstl-rule-enabled').is(':checked') ? 1 : 0,
					conditions: conditions
				},
				success: function(response) {
					if (response.success) {
						// For edits, surgically update the existing list item
						// + inspector header — no reload, no lost scroll.
						// For brand-new rules, navigate to the new rule's URL
						// so the full inspector (flow viz, stats, runs) renders.
						if (response.data.is_edit) {
							self._updateRuleListItem(response.data.rule, response.data.labels);
							self._updateInspectorHeader(response.data.rule);
							self._closeRuleModal();
							$button.prop('disabled', false).text(TrustLens.i18n.save_rule || 'Save Rule');
						} else {
							self._navigateToRule(response.data.rule.id);
						}
					} else {
						// Blocked save — whitelist rejection, unsatisfiable
						// conditions, incomplete action config, etc. Show
						// the reason(s) inline inside the modal and leave
						// it open so the user can fix + retry.
						self._showRuleError(
							response.data.message || TrustLens.i18n.error,
							response.data.warnings || []
						);
						$button.prop('disabled', false).text(TrustLens.i18n.save_rule || 'Save Rule');
					}
				},
				error: function() {
					self._showRuleError(TrustLens.i18n.error, []);
					$button.prop('disabled', false).text(TrustLens.i18n.save_rule || 'Save Rule');
				}
			});
		},

		/**
		 * Delete automation rule.
		 *
		 * @param {Event} e Click event.
		 */
		deleteAutomationRule: function(e) {
			e.preventDefault();
			var self = TrustLensAdmin;
			var $button = $(this);
			var $row = $button.closest('[data-rule-id]');
			var ruleId = String($row.data('rule-id'));

			TrustLensAdmin.confirmDialog({
				title: 'Delete rule',
				message: TrustLens.i18n.confirm_delete_rule || 'Are you sure you want to delete this automation rule?',
				confirmLabel: 'Delete',
				onConfirm: function() {
					$button.prop('disabled', true);
					$.ajax({
						url: TrustLens.ajaxUrl,
						type: 'POST',
						data: {
							action: 'wstl_delete_automation_rule',
							nonce: TrustLens.nonce,
							rule_id: ruleId
						},
						success: function(response) {
							if (response.success) {
								self._handleDeletedRule(ruleId);
							} else {
								self.showToast(response.data.message || TrustLens.i18n.error, 'error');
								$button.prop('disabled', false);
							}
						},
						error: function() {
							self.showToast(TrustLens.i18n.error, 'error');
							$button.prop('disabled', false);
						}
					});
				}
			});
		},

		/* -------------------------------------------------------------- */
		/*  Automation-modal helpers (internal — prefixed with _)         */
		/* -------------------------------------------------------------- */

		/**
		 * Render message + optional bullet list of reasons into the
		 * modal's inline error panel.
		 */
		_showRuleError: function(message, warnings) {
			var $panel = $('#wstl-rule-error');
			if (!$panel.length) { return; }
			var $content = $('<div>');
			$('<strong>').text(message).appendTo($content);
			if (warnings && warnings.length) {
				var $ul = $('<ul>');
				for (var i = 0; i < warnings.length; i++) {
					$('<li>').text(warnings[i]).appendTo($ul);
				}
				$content.append($ul);
			}
			$panel.empty().append($content.children()).prop('hidden', false);
			// Scroll the panel into view inside the modal content area so
			// a long form doesn't hide it below the fold.
			var panelEl = $panel[0];
			if (panelEl && panelEl.scrollIntoView) {
				panelEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
			}
		},

		_hideRuleError: function() {
			$('#wstl-rule-error').prop('hidden', true).empty();
		},

		_closeRuleModal: function() {
			$('#wstl-rule-modal').removeClass('is-open');
			this._hideRuleError();
		},

		/**
		 * Navigate to a rule's inspector URL (full-page render for the
		 * fresh flow viz + stats panel, but at least shows the right rule).
		 */
		_navigateToRule: function(ruleId) {
			var u = new URL(window.location.href);
			u.searchParams.set('page', 'trustlens-automation');
			u.searchParams.set('rule_id', ruleId);
			window.location.href = u.toString();
		},

		/**
		 * Surgical update of a rule's <li> in the left list. Preserves
		 * the existing SVG separator and stat counts (unchanged on edit).
		 */
		_updateRuleListItem: function(rule, labels) {
			var $link = $('.wstl-auto-list [data-rule-id="' + rule.id + '"]').first();
			if (!$link.length) { return; }

			$link.attr('data-rule', JSON.stringify(rule));
			$link.data('rule', rule);
			$link.find('.wstl-auto-item-name').text(rule.name || 'Unnamed rule');

			// Rebuild the sub-line (trigger ‣ action) while keeping the SVG separator node.
			var $sub = $link.find('.wstl-auto-item-sub');
			var $svg = $sub.find('svg').detach();
			$sub.empty()
				.append(document.createTextNode(labels.trigger + ' '))
				.append($svg.length ? $svg : '')
				.append(document.createTextNode(' ' + labels.action));

			// Enabled-state classes.
			$link.toggleClass('is-disabled', !rule.enabled);
			var $dot = $link.find('.wstl-auto-item-dot');
			$dot.removeClass('is-off');
			if (!rule.enabled) { $dot.addClass('is-off'); }
		},

		/**
		 * Update the right-pane inspector header for a just-edited rule.
		 * Only the header gets refreshed — the flow viz, stats panel, and
		 * recent runs keep their pre-save values. Stats don't change on
		 * edit anyway (they're derived from log rows), and the flow viz
		 * staleness is minor (user just typed the values a moment ago).
		 */
		_updateInspectorHeader: function(rule) {
			var $header = $('.wstl-auto-right-head[data-rule-id="' + rule.id + '"]');
			if (!$header.length) { return; }

			$header.find('.wstl-auto-right-title').text(rule.name || 'Unnamed rule');
			$header.find('.wstl-edit-rule').attr('data-rule', JSON.stringify(rule));
			$header.find('.wstl-edit-rule').data('rule', rule);

			var $dot = $header.find('.wstl-auto-right-sub-dot');
			$dot.toggleClass('is-off', !rule.enabled);
		},

		/**
		 * Handle a successful delete response: remove the <li>, update
		 * selection, fall back to the empty state when nothing is left.
		 */
		_handleDeletedRule: function(ruleId) {
			var $link = $('.wstl-auto-list [data-rule-id="' + ruleId + '"]').first();
			var $li = $link.closest('li');
			var wasSelected = String($('.wstl-auto-right-head').data('rule-id') || '') === ruleId;

			$li.remove();

			var $remaining = $('.wstl-auto-list .wstl-auto-item');
			if (!$remaining.length) {
				// No rules left — a fresh load renders the empty-state aside.
				this._navigateToPage();
				return;
			}

			if (wasSelected) {
				// Selected rule is gone — show the first remaining.
				var href = $remaining.first().attr('href');
				if (href) { window.location.href = href; }
			}
			// Otherwise stay put — inspector is still showing a valid rule.
		},

		_navigateToPage: function() {
			var u = new URL(window.location.href);
			u.searchParams.set('page', 'trustlens-automation');
			u.searchParams.delete('rule_id');
			window.location.href = u.toString();
		},

		/**
		 * Add a condition row to the rule form.
		 */
		addAutomationCondition: function() {
			var $container = $('#wstl-rule-conditions');
			var $first = $container.find('.wstl-condition-row').first();
			var index = $container.find('.wstl-condition-row').length;
			var $clone = $first.clone();
			$clone.find('input, select').val('');
			$clone.find('select.wstl-condition-field').val('trust_score');
			$clone.find('select.wstl-condition-operator').val('<');
			$clone.find('[name^="conditions["]').each(function() {
				var $el = $(this);
				$el.attr('name', $el.attr('name').replace(/\[\d+\]/, '[' + index + ']'));
			});
			$container.append($clone);
		},

		/**
		 * Remove a condition row (keep at least one).
		 */
		removeAutomationCondition: function() {
			var $container = $('#wstl-rule-conditions');
			if ($container.find('.wstl-condition-row').length <= 1) return;
			$(this).closest('.wstl-condition-row').remove();
			$container.find('.wstl-condition-row').each(function(i) {
				$(this).find('[name^="conditions["]').each(function() {
					var $el = $(this);
					$el.attr('name', $el.attr('name').replace(/\[\d+\]/, '[' + i + ']'));
				});
			});
		},

		/**
		 * Open webhook modal.
		 */
		openWebhookModal: function() {
			var $modal = $('#wstl-webhook-modal');
			$modal.find('form')[0].reset();
			$modal.addClass('is-open');
		},

		/**
		 * Save webhook.
		 *
		 * @param {Event} e Click event.
		 */
		saveWebhook: function(e) {
			e.preventDefault();

			var $form = $(this).is('form') ? $(this) : $(this).closest('form');
			var $button = $form.find('button[type="submit"]').first();

			$button.prop('disabled', true).text('Saving...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_add_webhook',
					nonce: TrustLens.nonce,
					url: $form.find('#wstl-webhook-url').val(),
					secret: $form.find('#wstl-webhook-secret').val(),
					enabled: $form.find('#wstl-webhook-enabled').is(':checked') ? 1 : 0
				},
				success: function(response) {
					if (response.success) {
						location.reload();
					} else {
						TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
						$button.prop('disabled', false).text('Add Endpoint');
					}
				},
				error: function() {
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
					$button.prop('disabled', false).text('Add Endpoint');
				}
			});
		},

		/**
		 * Test webhook.
		 *
		 * @param {Event} e Click event.
		 */
		testWebhook: function(e) {
			e.preventDefault();

			var $button = $(this);
			// Send only the endpoint ID — the secret stays server-side so it never
			// reaches the DOM where browser extensions or XSS could read it.
			var endpointId = $button.data('endpoint-id') || $button.closest('tr').data('endpoint-id') || '';

			$button.prop('disabled', true).text('Testing...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_test_webhook',
					nonce: TrustLens.nonce,
					endpoint_id: endpointId
				},
				success: function(response) {
					$button.prop('disabled', false).text('Test');

					if (response.success) {
						TrustLensAdmin.showToast(response.data.message || 'Webhook test sent successfully.');
					} else {
						TrustLensAdmin.showToast('Webhook test failed: ' + (response.data.message || TrustLens.i18n.error), 'error');
					}
				},
				error: function() {
					$button.prop('disabled', false).text('Test');
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
				}
			});
		},

		/**
		 * Delete webhook.
		 *
		 * @param {Event} e Click event.
		 */
		deleteWebhook: function(e) {
			e.preventDefault();
			var $button = $(this);
			var endpointId = $button.closest('tr').data('endpoint-id');

			TrustLensAdmin.confirmDialog({
				title: 'Delete webhook',
				message: 'Are you sure you want to delete this webhook endpoint?',
				confirmLabel: 'Delete',
				onConfirm: function() {
					$button.prop('disabled', true);
					$.ajax({
						url: TrustLens.ajaxUrl,
						type: 'POST',
						data: {
							action: 'wstl_delete_webhook',
							nonce: TrustLens.nonce,
							endpoint_id: endpointId
						},
						success: function(response) {
							if (response.success) {
								$button.closest('tr').fadeOut(function() {
									$(this).remove();
								});
							} else {
								TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
								$button.prop('disabled', false);
							}
						},
						error: function() {
							TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
							$button.prop('disabled', false);
						}
					});
				}
			});
		},

		/**
		 * Open report schedule modal.
		 */
		openReportModal: function() {
			var $modal = $('#wstl-schedule-modal');
			$modal.find('form')[0].reset();
			$modal.addClass('is-open');
		},

		/**
		 * Save report schedule.
		 *
		 * @param {Event} e Click event.
		 */
		saveReportSchedule: function(e) {
			e.preventDefault();

			var $form = $(this).is('form') ? $(this) : $(this).closest('form');
			var $button = $form.find('button[type="submit"]').first();

			$button.prop('disabled', true).text('Saving...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_add_report_schedule',
					nonce: TrustLens.nonce,
					type: $form.find('#wstl-schedule-type').val(),
					frequency: $form.find('#wstl-schedule-frequency').val(),
					day: $form.find('#wstl-schedule-day').val(),
					time: $form.find('#wstl-schedule-time').val(),
					enabled: $form.find('#wstl-schedule-enabled').is(':checked') ? 1 : 0
				},
				success: function(response) {
					if (response.success) {
						location.reload();
					} else {
						TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
						$button.prop('disabled', false).text('Add Schedule');
					}
				},
				error: function() {
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
					$button.prop('disabled', false).text('Add Schedule');
				}
			});
		},

		/**
		 * Toggle report schedule enabled state.
		 */
		toggleReportScheduleEnabled: function() {
			var $checkbox = $(this);
			var $row = $checkbox.closest('tr');
			var scheduleId = $row.data('schedule-id');
			if (!scheduleId) {
				return;
			}

			$checkbox.prop('disabled', true);

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_update_report_schedule_enabled',
					nonce: TrustLens.nonce,
					schedule_id: scheduleId,
					enabled: $checkbox.is(':checked') ? 1 : 0
				},
				success: function(response) {
					$checkbox.prop('disabled', false);

					if (!response.success) {
						TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
						$checkbox.prop('checked', !$checkbox.is(':checked'));
						return;
					}

					location.reload();
				},
				error: function() {
					$checkbox.prop('disabled', false);
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
					$checkbox.prop('checked', !$checkbox.is(':checked'));
				}
			});
		},

		/**
		 * Send report now.
		 *
		 * @param {Event} e Click event.
		 */
		sendReportNow: function(e) {
			e.preventDefault();

			var $button = $(this);
			var scheduleId = $button.closest('tr').data('schedule-id');

			$button.prop('disabled', true).text('Sending...');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_send_report_now',
					nonce: TrustLens.nonce,
					schedule_id: scheduleId
				},
				success: function(response) {
					$button.prop('disabled', false).text('Send Now');

					if (response.success) {
						TrustLensAdmin.showToast(response.data.message || 'Report sent successfully!');
					} else {
						TrustLensAdmin.showToast('Failed to send report: ' + (response.data.message || TrustLens.i18n.error), 'error');
					}
				},
				error: function() {
					$button.prop('disabled', false).text('Send Now');
					TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
				}
			});
		},

		/**
		 * Send test notification email.
		 *
		 * @param {Event} e Click event.
		 */
		sendTestNotification: function(e) {
			e.preventDefault();

			var $button = $(this);
			var $spinner = $button.next('.spinner');
			var $result = $('#wstl-test-notification-result');

			$button.prop('disabled', true);
			$spinner.addClass('is-active');
			$result.text('').removeClass('success error');

			$.ajax({
				url: TrustLens.ajaxUrl,
				type: 'POST',
				data: {
					action: 'wstl_send_test_notification',
					nonce: TrustLens.nonce
				},
				timeout: 15000,
				success: function(response) {
					$button.prop('disabled', false);
					$spinner.removeClass('is-active');

					if (response.success) {
						$result.text(response.data.message).addClass('success');
					} else {
						$result.text(response.data.message || TrustLens.i18n.error).addClass('error');
					}
				},
				error: function(xhr, status) {
					$button.prop('disabled', false);
					$spinner.removeClass('is-active');
					if (status === 'timeout') {
						$result.text(TrustLens.i18n.test_notification_timeout || 'Request timed out. Your server may not be sending mail (e.g. SMTP not configured). Try an SMTP plugin or check your server mail settings.').addClass('error');
					} else {
						$result.text(TrustLens.i18n.error).addClass('error');
					}
				}
			});
		},

		/**
		 * Delete report schedule.
		 *
		 * @param {Event} e Click event.
		 */
		deleteReportSchedule: function(e) {
			e.preventDefault();
			var $button = $(this);
			var scheduleId = $button.closest('tr').data('schedule-id');

			TrustLensAdmin.confirmDialog({
				title: 'Delete schedule',
				message: 'Are you sure you want to delete this scheduled report?',
				confirmLabel: 'Delete',
				onConfirm: function() {
					$button.prop('disabled', true);
					$.ajax({
						url: TrustLens.ajaxUrl,
						type: 'POST',
						data: {
							action: 'wstl_delete_report_schedule',
							nonce: TrustLens.nonce,
							schedule_id: scheduleId
						},
						success: function(response) {
							if (response.success) {
								$button.closest('tr').fadeOut(function() {
									$(this).remove();
								});
							} else {
								TrustLensAdmin.showToast(response.data.message || TrustLens.i18n.error, 'error');
								$button.prop('disabled', false);
							}
						},
						error: function() {
							TrustLensAdmin.showToast(TrustLens.i18n.error, 'error');
							$button.prop('disabled', false);
						}
					});
				}
			});
		},

		/**
		 * Close modal.
		 *
		 * @param {Event} e Click event.
		 */
		closeModal: function(e) {
			if (e) {
				// Only close if clicking overlay, close button, or cancel button.
				if (!$(e.target).hasClass('wstl-modal') && !$(e.target).hasClass('wstl-modal-overlay') && !$(e.target).hasClass('wstl-modal-close') && !$(e.target).hasClass('wstl-modal-cancel') && !$(e.target).closest('.wstl-modal-close').length && !$(e.target).closest('.wstl-modal-cancel').length) {
					return;
				}
			}
			$('.wstl-modal').removeClass('is-open');
			// Clear the rule-modal's inline error panel so it doesn't flash
			// stale content the next time the modal reopens.
			TrustLensAdmin._hideRuleError();
		},

		/**
		 * Email token (chip) inputs — progressive enhancement.
		 *
		 * Upgrades any <input data-token-input> (a plain comma-separated text
		 * field) into a pill editor: each address becomes a removable chip,
		 * Enter or comma commits the typed entry, Backspace on an empty entry
		 * removes the last chip. The original input stays in the DOM (switched
		 * to type="hidden") and its value is kept as the comma-joined string,
		 * so form submission and server-side parsing are unchanged. With JS
		 * off, the field degrades to the original comma-separated text input.
		 */
		initTokenInputs: function() {
			var inputs = document.querySelectorAll('input[data-token-input]');
			if (!inputs.length) {
				return;
			}
			Array.prototype.forEach.call(inputs, function(input) {
				TrustLensAdmin._buildTokenInput(input);
			});
		},

		/**
		 * Build the chip editor around a single token input.
		 *
		 * @param {HTMLInputElement} input The source comma-separated field.
		 */
		_buildTokenInput: function(input) {
			var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
			var tokens = input.value.split(',').map(function(s) { return s.trim(); }).filter(Boolean);

			var wrap = document.createElement('div');
			wrap.className = 'wstl-token-field';

			var entry = document.createElement('input');
			entry.type = 'text';
			entry.className = 'wstl-token-entry';
			entry.setAttribute('autocomplete', 'off');
			entry.setAttribute('spellcheck', 'false');

			// Move the label association (and the id) onto the visible entry so
			// the <label for="..."> still focuses something the user can see.
			if (input.id) {
				entry.id = input.id;
				input.removeAttribute('id');
			}

			// Hide the real field but keep it submitting under its name.
			input.type = 'hidden';

			function sync() {
				input.value = tokens.join(',');
			}

			function render() {
				var chips = wrap.querySelectorAll('.wstl-token');
				Array.prototype.forEach.call(chips, function(el) { el.parentNode.removeChild(el); });
				tokens.forEach(function(addr, i) {
					var chip = document.createElement('span');
					chip.className = 'wstl-token' + (EMAIL_RE.test(addr) ? '' : ' is-invalid');

					var label = document.createElement('span');
					label.className = 'wstl-token-label';
					label.textContent = addr;
					chip.appendChild(label);

					var remove = document.createElement('button');
					remove.type = 'button';
					remove.className = 'wstl-token-remove';
					remove.setAttribute('aria-label', 'Remove ' + addr);
					remove.innerHTML = '&times;';
					remove.addEventListener('click', function() {
						tokens.splice(i, 1);
						sync();
						render();
						entry.focus();
					});
					chip.appendChild(remove);

					wrap.insertBefore(chip, entry);
				});
				entry.placeholder = tokens.length ? '' : 'name@example.com';
			}

			function addTokens(raw) {
				raw.split(',').forEach(function(part) {
					var value = part.trim();
					if (value && tokens.indexOf(value) === -1) {
						tokens.push(value);
					}
				});
				sync();
				render();
			}

			entry.addEventListener('keydown', function(e) {
				if (e.key === 'Enter' || e.key === ',') {
					if (entry.value.trim()) {
						e.preventDefault();
						addTokens(entry.value);
						entry.value = '';
					} else if (e.key === ',') {
						e.preventDefault();
					}
				} else if (e.key === 'Backspace' && !entry.value && tokens.length) {
					tokens.pop();
					sync();
					render();
				}
			});

			// Commit a pending entry when focus leaves (e.g. clicking Save).
			entry.addEventListener('blur', function() {
				if (entry.value.trim()) {
					addTokens(entry.value);
					entry.value = '';
				}
			});

			// Pasting a delimited list splits into multiple chips.
			entry.addEventListener('paste', function(e) {
				var text = (e.clipboardData || window.clipboardData).getData('text');
				if (text && /[,\s]/.test(text)) {
					e.preventDefault();
					addTokens(text.replace(/[\s;]+/g, ','));
					entry.value = '';
				}
			});

			// Clicking the empty area of the field focuses the entry.
			wrap.addEventListener('click', function(e) {
				if (e.target === wrap) {
					entry.focus();
				}
			});

			wrap.appendChild(entry);
			input.parentNode.insertBefore(wrap, input.nextSibling);
			render();
		}
	};

	// Initialize on document ready.
	$(document).ready(function() {
		TrustLensAdmin.init();
	});

})(jQuery);
