/* ==========================================================================
   SEOvault AI — Meta Box JavaScript
   Live character counters + live assessment updates
   ========================================================================== */

(function($) {
	'use strict';

	// Wrap meta box title in a pillbox span
	$('#seovault_seo_meta_box .hndle').contents().filter(function() {
		return this.nodeType === 3; // text nodes only
	}).wrap('<span class="seovaultseo-pillbox-title"></span>');

	var limits = (typeof seovaultseo_metabox !== 'undefined') ? seovaultseo_metabox : null;

	if (!limits) {
		return;
	}

	var titleLimits = limits.title_limits || { ideal_min: 50, ideal_max: 60, max: 70, min: 30 };
	var descLimits  = limits.desc_limits  || { ideal_min: 120, ideal_max: 155, max: 200, min: 70 };
	var ogTitleLimits = limits.og_title_limits || { ideal_min: 25, ideal_max: 88, max: 100, min: 15 };
	var ogDescLimits  = limits.og_desc_limits  || { ideal_min: 80, ideal_max: 200, max: 300, min: 45 };

	// Map counter-target data values to their corresponding limit sets.
	var limitMap = {
		title: titleLimits,
		description: descLimits,
		og_title: ogTitleLimits,
		og_description: ogDescLimits
	};

	// Debounce helper
	function debounce(fn, wait) {
		var t;
		return function() {
			var ctx = this, args = arguments;
			clearTimeout(t);
			t = setTimeout(function() { fn.apply(ctx, args); }, wait);
		};
	}

	// Update a character counter element
	function updateCounter($input, $counter, lim) {
		var len = $input.val().length;
		var cls = 'seovaultseo-counter-good';
		var text = len + ' / ' + lim.ideal_min + '-' + lim.ideal_max;

		if (len === 0) {
			cls = 'seovaultseo-counter-bad';
			text = '0 / ' + lim.ideal_min + '-' + lim.ideal_max;
		} else if (len < lim.min) {
			cls = 'seovaultseo-counter-bad';
		} else if (len < lim.ideal_min) {
			cls = 'seovaultseo-counter-okay';
		} else if (len <= lim.ideal_max) {
			cls = 'seovaultseo-counter-good';
		} else if (len <= lim.max) {
			cls = 'seovaultseo-counter-okay';
			text = len + ' / max ' + lim.max;
		} else {
			cls = 'seovaultseo-counter-bad';
			text = len + ' / max ' + lim.max;
		}

		$counter.removeClass(
			'seovaultseo-counter-good seovaultseo-counter-okay seovaultseo-counter-bad'
		).addClass(cls).text(text);
	}

	// Initialize counters on load
	$('.seovaultseo-counted').each(function() {
		var target = $(this).data('counter-target');
		var $counter = $('.seovaultseo-char-counter[data-target="' + $(this).attr('id') + '"]');
		var lim = limitMap[target] || descLimits;

		if ($counter.length) {
			updateCounter($(this), $counter, lim);
		}
	});

	// Live counter on input
	$('.seovaultseo-counted').on('input', function() {
		var target = $(this).data('counter-target');
		var $counter = $('.seovaultseo-char-counter[data-target="' + $(this).attr('id') + '"]');
		var lim = limitMap[target] || descLimits;

		if ($counter.length) {
			updateCounter($(this), $counter, lim);
		}
	});

	// Live assessment update via AJAX
	var updateAssessment = debounce(function() {
		var $panel = $('#seovaultseo-assessment-panel');
		if (!$panel.length) {
			return;
		}

		var $box = $('.seovault-ai-meta-box');
		var postId = $box.data('post-id');

		var data = {
			action: 'seovaultseo_live_assessment',
			nonce: limits.nonce,
			post_id: postId,
			title: $('#seovault_seo_title').val(),
			description: $('#seovault_seo_description').val(),
			keyword: $('#seovault_focus_keyword').val(),
			noindex: $('#seovault_robots_noindex').is(':checked') ? 1 : 0,
			nofollow: $('#seovault_robots_nofollow').is(':checked') ? 1 : 0,
			canonical: $('#seovault_canonical_url').val(),
			redirect_url: $('#seovault_redirect_url').val()
		};

		$.ajax({
			url: limits.ajax_url,
			method: 'POST',
			data: data,
			success: function(response) {
				if (response.success && response.data) {
					renderAssessment(response.data);
				}
			}
		});
	}, 600);

	// Trigger live assessment when SEO fields change
	$('#seovault_seo_title, #seovault_seo_description, #seovault_focus_keyword, #seovault_canonical_url, #seovault_redirect_url').on('input', updateAssessment);
	$('#seovault_robots_noindex, #seovault_robots_nofollow').on('change', updateAssessment);

	// Render updated assessment panel
	function renderAssessment(data) {
		var i18n = limits || {};
		var overallLabels = {
			good: i18n.assessment_good || 'Good',
			okay: i18n.assessment_okay || 'Okay',
			bad: i18n.assessment_bad || 'Needs Work',
			unknown: i18n.assessment_unknown || 'Unknown'
		};

		var $overall = $('.seovaultseo-assessment-overall');
		$overall.removeClass(
			'seovaultseo-assessment-overall-good seovaultseo-assessment-overall-okay ' +
			'seovaultseo-assessment-overall-bad seovaultseo-assessment-overall-unknown'
		).addClass('seovaultseo-assessment-overall-' + data.overall);

		$('#seovaultseo-assessment-overall-text').text(overallLabels[data.overall] || 'Unknown');

		// Update score circle
		if (typeof data.score !== 'undefined') {
			var $circle = $('#seovaultseo-assessment-score-circle');
			var $num = $('#seovaultseo-assessment-score-num');
			$circle.removeClass(
				'seovaultseo-assessment-score-unknown seovaultseo-assessment-score-bad ' +
				'seovaultseo-assessment-score-good seovaultseo-assessment-score-great'
			).addClass('seovaultseo-assessment-score-' + (data.rating || 'unknown'));
			$num.text(data.score);
		}

		var $list = $('#seovaultseo-assessment-checks');
		$list.empty();

		if (data.checks && data.checks.length) {
			for (var i = 0; i < data.checks.length; i++) {
				var c = data.checks[i];
				$list.append(
					'<li class="seovaultseo-assessment-check seovaultseo-assessment-check-' + c.status + '" data-check-id="' + c.id + '">' +
						'<span class="seovaultseo-assessment-check-icon"></span>' +
						'<span class="seovaultseo-assessment-check-label">' + escapeHtml(c.label) + '</span>' +
						'<span class="seovaultseo-assessment-check-msg">' + escapeHtml(c.message) + '</span>' +
					'</li>'
				);
			}
		}
	}

	function escapeHtml(str) {
		return String(str)
			.replace(/&/g, '&amp;')
			.replace(/</g, '&lt;')
			.replace(/>/g, '&gt;')
			.replace(/"/g, '&quot;')
			.replace(/'/g, '&#039;');
	}

	// ==========================================================================
	// Schema Health
	// ==========================================================================

	(function() {
		var requestSequence = 0;
		var activeRequest = null;
		var setupInstanceCache = {};

		function getPanel() {
			return $('#seovaultseo-schema-health');
		}

		function cacheSetupPayload(setup) {
			setupInstanceCache = {};
			if (!setup || typeof setup !== 'object') {
				return;
			}
			var lists = [];
			if (setup.withheld && setup.withheld.length) {
				lists = lists.concat(setup.withheld);
			}
			if (setup.ready_instances && setup.ready_instances.length) {
				lists = lists.concat(setup.ready_instances);
			}
			lists.forEach(function(row) {
				if (!row || !row.instance_id) {
					return;
				}
				setupInstanceCache[String(row.instance_id)] = row;
			});
		}

		function setRequestError(message) {
			var $panel = getPanel();
			var $error = $panel.find('.seovaultseo-schema-health-request-error');

			$error.text(message).prop('hidden', false);
			$panel.removeClass(
				'seovaultseo-schema-health-valid seovaultseo-schema-health-valid_with_recommendations ' +
				'seovaultseo-schema-health-warning seovaultseo-schema-health-invalid ' +
				'seovaultseo-schema-health-not_checked seovaultseo-schema-health-no_schema ' +
				'seovaultseo-schema-health-healthy seovaultseo-schema-health-improvements_available ' +
				'seovaultseo-schema-health-action_required seovaultseo-schema-health-available_after_publishing'
			).addClass('seovaultseo-schema-health-unavailable');
			$panel.attr('data-schema-health-state', 'unavailable');
		}

		function checkSchema(useSavedState, restoreFocus) {
			var $panel = getPanel();
			var $button = $panel.find('.seovaultseo-schema-health-check');
			var postId = $('.seovault-ai-meta-box').data('post-id');
			var sequence = ++requestSequence;
			var requestData = {
				action: 'seovaultseo_validate_post_schema',
				nonce: limits.schema_nonce,
				post_id: postId
			};

			if (!$panel.length || !postId) {
				return;
			}

			if (!useSavedState) {
				requestData.custom_schema_json = $('#seovault_schema_json').val() || '';
			}

			if (activeRequest && activeRequest.readyState !== 4) {
				activeRequest.abort();
			}

			$button.prop('disabled', true).attr('aria-busy', 'true').text(limits.schema_checking || 'Checking…');
			$panel.find('.seovaultseo-schema-health-live-check').prop('disabled', true);
			$panel.find('.seovaultseo-schema-health-request-error').prop('hidden', true).empty();

			activeRequest = $.ajax({
				url: limits.ajax_url,
				method: 'POST',
				dataType: 'json',
				data: requestData
			}).done(function(response) {
				if (sequence !== requestSequence) {
					return;
				}

				if (response && response.success && response.data && response.data.html) {
					if (response.data.setup) {
						cacheSetupPayload(response.data.setup);
					}
					$panel.replaceWith(response.data.html);
					getPanel().removeClass('seovaultseo-schema-health-stale');
					announceSchemaHealth(getPanel().find('.seovaultseo-schema-health-label').first().text());
					if (restoreFocus !== false) {
						getPanel().find('.seovaultseo-schema-health-check').trigger('focus');
					}
					return;
				}

				var message = response && response.data && response.data.message
					? response.data.message
					: (limits.schema_check_failed || 'Schema check failed. Please try again.');
				setRequestError(message);
			}).fail(function(xhr, textStatus) {
				if (sequence !== requestSequence || textStatus === 'abort') {
					return;
				}

				var response = xhr.responseJSON;
				var message = response && response.data && response.data.message
					? response.data.message
					: (limits.schema_check_failed || 'Schema check failed. Please try again.');
				setRequestError(message);
			}).always(function() {
				if (sequence !== requestSequence) {
					return;
				}

				getPanel().find('.seovaultseo-schema-health-check')
					.prop('disabled', false)
					.removeAttr('aria-busy')
					.text(limits.schema_check || 'Check schema');
				getPanel().find('.seovaultseo-schema-health-live-check')
					.prop('disabled', false);
			});
		}

		function checkPublishedPage() {
			var $panel = getPanel();
			var $button = $panel.find('.seovaultseo-schema-health-live-check');
			var postId = $('.seovault-ai-meta-box').data('post-id');
			var sequence = ++requestSequence;

			if (!$panel.length || !postId) {
				return;
			}

			if (activeRequest && activeRequest.readyState !== 4) {
				activeRequest.abort();
			}

			$button.prop('disabled', true).attr('aria-busy', 'true').text(limits.schema_live_checking || 'Checking published page…');
			$panel.find('.seovaultseo-schema-health-check').prop('disabled', true);
			$panel.find('.seovaultseo-schema-health-request-error').prop('hidden', true).empty();

			activeRequest = $.ajax({
				url: limits.ajax_url,
				method: 'POST',
				dataType: 'json',
				data: {
					action: 'seovaultseo_validate_live_schema',
					nonce: limits.schema_nonce,
					post_id: postId
				}
			}).done(function(response) {
				if (sequence !== requestSequence) {
					return;
				}

				if (response && response.success && response.data && response.data.html) {
					$panel.replaceWith(response.data.html);
					announceSchemaHealth(getPanel().find('.seovaultseo-schema-health-label').first().text());
					getPanel().find('.seovaultseo-schema-health-live-check').trigger('focus');
					return;
				}

				var message = response && response.data && response.data.message
					? response.data.message
					: (limits.schema_live_failed || 'Published-page check failed. Please try again.');
				setRequestError(message);
			}).fail(function(xhr, textStatus) {
				if (sequence !== requestSequence || textStatus === 'abort') {
					return;
				}

				var response = xhr.responseJSON;
				var message = response && response.data && response.data.message
					? response.data.message
					: (limits.schema_live_failed || 'Published-page check failed. Please try again.');
				setRequestError(message);
			}).always(function() {
				if (sequence !== requestSequence) {
					return;
				}

				getPanel().find('.seovaultseo-schema-health-live-check')
					.prop('disabled', false)
					.removeAttr('aria-busy')
					.text(limits.schema_live_check || 'Check published page');
				getPanel().find('.seovaultseo-schema-health-check')
					.prop('disabled', false);
			});
		}

		$(document).on('click', '.seovaultseo-schema-health-check', function(event) {
			event.preventDefault();
			checkSchema(false, true);
		});

		$(document).on('click', '.seovaultseo-schema-health-live-check', function(event) {
			event.preventDefault();
			checkPublishedPage();
		});

		$(document).on('input', '#seovault_schema_json', debounce(function() {
			var $panel = getPanel();
			if ($panel.length) {
				$panel.addClass('seovaultseo-schema-health-stale');
				$panel.find('.seovaultseo-schema-health-scope')
					.text(limits.schema_changes_stale || 'Custom schema changes have not been checked.');
			}
			updateCustomJsonDirtyState();
		}, 250));

		// -----------------------------------------------------------------
		// Advanced custom JSON editor
		// -----------------------------------------------------------------

		function getCustomJsonRoot() {
			return $('[data-schema-custom-json]').first();
		}

		function updateCustomJsonModeUi() {
			var $root = getCustomJsonRoot();
			if (!$root.length) {
				return;
			}
			var mode = String($root.find('[data-schema-custom-json-mode]').val() || 'add');
			var isReplace = mode === 'replace';
			$root.find('[data-schema-custom-json-mode-help-add]').prop('hidden', isReplace);
			$root.find('[data-schema-custom-json-mode-help-replace]').prop('hidden', !isReplace);
			$root.find('[data-schema-custom-json-replace-wrap]').prop('hidden', !isReplace);
			if (!isReplace) {
				$root.find('[data-schema-custom-json-replace-confirm]').prop('checked', false);
			}
		}

		function updateCustomJsonDirtyState() {
			var $root = getCustomJsonRoot();
			if (!$root.length) {
				return;
			}
			var value = String($root.find('[data-schema-custom-json-input]').val() || '');
			if ($root.data('syncedValue') === undefined) {
				$root.data('syncedValue', value);
			}
			var dirty = value !== String($root.data('syncedValue') || '');
			$root.find('[data-schema-custom-json-unsaved]').prop('hidden', !dirty);
			$root.attr('data-dirty', dirty ? '1' : '0');
		}

		function setCustomJsonStatus(message, isError) {
			var $status = getCustomJsonRoot().find('[data-schema-custom-json-status]');
			$status.text(message || '');
			$status.toggleClass('is-error', !!isError);
		}

		function applyCustomJsonUx(ux) {
			var $root = getCustomJsonRoot();
			if (!$root.length || !ux) {
				return;
			}
			if (ux.state) {
				$root.attr('data-ux-state', ux.state);
				$root.find('[data-schema-custom-json-banner]')
					.attr('class', 'seovaultseo-schema-custom-json-banner seovaultseo-schema-custom-json-banner--' + ux.state);
			}
			if (ux.label) {
				$root.find('[data-schema-custom-json-banner-label]').text(ux.label);
			}
			if (ux.summary) {
				$root.find('[data-schema-custom-json-banner-summary]').text(ux.summary);
			}
		}

		$(document).on('change', '[data-schema-custom-json-mode]', function() {
			updateCustomJsonModeUi();
		});

		$(document).on('click', '[data-schema-custom-json-format]', function(event) {
			event.preventDefault();
			var $root = getCustomJsonRoot();
			var $btn = $(this);
			var postId = currentPostId();
			if (!$root.length || !postId) {
				return;
			}
			$btn.prop('disabled', true).text(limits.schema_custom_json_formatting || 'Formatting…');
			setCustomJsonStatus('');
			$.post(ajaxUrl, {
				action: 'seovaultseo_schema_custom_json_format',
				nonce: limits.schema_nonce || '',
				post_id: postId,
				custom_schema_json: $root.find('[data-schema-custom-json-input]').val() || ''
			}).done(function(response) {
				if (!response || !response.success || !response.data || !response.data.json) {
					var message = (response && response.data && response.data.message) || limits.schema_custom_json_format_failed || 'Fix JSON syntax before formatting.';
					setCustomJsonStatus(message, true);
					return;
				}
				$root.find('[data-schema-custom-json-input]').val(response.data.json).trigger('input');
				setCustomJsonStatus(limits.schema_custom_json_format_ok || 'JSON formatted.', false);
			}).fail(function() {
				setCustomJsonStatus(limits.schema_custom_json_format_failed || 'Fix JSON syntax before formatting.', true);
			}).always(function() {
				$btn.prop('disabled', false).text(limits.schema_custom_json_format || 'Format');
			});
		});

		$(document).on('click', '[data-schema-custom-json-preview]', function(event) {
			event.preventDefault();
			var $root = getCustomJsonRoot();
			var $btn = $(this);
			var $panel = $root.find('[data-schema-custom-json-preview-panel]');
			var postId = currentPostId();
			if (!$root.length || !postId) {
				return;
			}
			$btn.prop('disabled', true).text(limits.schema_custom_json_previewing || 'Previewing…');
			setCustomJsonStatus('');
			$.post(ajaxUrl, {
				action: 'seovaultseo_schema_custom_json_preview',
				nonce: limits.schema_nonce || '',
				post_id: postId,
				mode: $root.find('[data-schema-custom-json-mode]').val() || 'add',
				custom_schema_json: $root.find('[data-schema-custom-json-input]').val() || ''
			}).done(function(response) {
				if (!response || !response.success || !response.data) {
					var message = (response && response.data && response.data.message) || limits.schema_custom_json_preview_failed || 'Could not preview custom JSON. Please try again.';
					setCustomJsonStatus(message, true);
					$panel.prop('hidden', true);
					return;
				}
				if (response.data.ux) {
					applyCustomJsonUx(response.data.ux);
				}
				$panel.find('[data-schema-custom-json-preview-body]').html(response.data.html || '');
				$panel.prop('hidden', false);
			}).fail(function() {
				setCustomJsonStatus(limits.schema_custom_json_preview_failed || 'Could not preview custom JSON. Please try again.', true);
			}).always(function() {
				$btn.prop('disabled', false).text(limits.schema_custom_json_preview || 'Preview');
			});
		});

		$(document).on('click', '[data-schema-custom-json-restore]', function(event) {
			// Prefer AJAX restore so the editor updates without a full reload.
			event.preventDefault();
			var $root = getCustomJsonRoot();
			var $btn = $(this);
			var postId = currentPostId();
			if (!$root.length || !postId) {
				return;
			}
			if (!window.confirm(limits.schema_custom_json_restore_confirm || 'Restore the last valid custom JSON? Unsaved editor text will be replaced.')) {
				return;
			}
			$btn.prop('disabled', true).text(limits.schema_custom_json_restoring || 'Restoring…');
			$.post(ajaxUrl, {
				action: 'seovaultseo_schema_custom_json_restore',
				nonce: limits.schema_nonce || '',
				post_id: postId
			}).done(function(response) {
				if (!response || !response.success || !response.data) {
					var message = (response && response.data && response.data.message) || limits.schema_custom_json_restore_failed || 'No last-valid custom JSON is available to restore.';
					setCustomJsonStatus(message, true);
					return;
				}
				var json = response.data.json || '';
				$root.find('[data-schema-custom-json-input]').val(json);
				if (response.data.mode) {
					$root.find('[data-schema-custom-json-mode]').val(response.data.mode);
				}
				if (response.data.replace_confirmed) {
					$root.find('[data-schema-custom-json-replace-confirm]').prop('checked', true);
				}
				if (response.data.saved_fingerprint) {
					$root.attr('data-saved-fingerprint', response.data.saved_fingerprint);
				}
				$root.data('syncedValue', json);
				updateCustomJsonModeUi();
				updateCustomJsonDirtyState();
				if (response.data.ux) {
					applyCustomJsonUx(response.data.ux);
				}
				setCustomJsonStatus(limits.schema_custom_json_restore_ok || 'Last valid custom JSON restored. Save the post to keep other edits.', false);
				if (!response.data.can_restore) {
					$btn.prop('hidden', true);
				}
				refreshSchemaHealthAfterGuidedChange();
			}).fail(function() {
				setCustomJsonStatus(limits.schema_custom_json_restore_failed || 'No last-valid custom JSON is available to restore.', true);
			}).always(function() {
				$btn.prop('disabled', false).text(limits.schema_custom_json_restore || 'Restore last valid');
			});
		});

		updateCustomJsonModeUi();
		(function initCustomJsonSyncedValue() {
			var $root = getCustomJsonRoot();
			if ($root.length) {
				$root.data('syncedValue', String($root.find('[data-schema-custom-json-input]').val() || ''));
			}
		})();

		// -----------------------------------------------------------------
		// Content-to-block suggestions
		// -----------------------------------------------------------------

		var activeSuggestion = null;
		var suggestionReturnFocus = null;
		var suggestionKeyHandler = null;

		function getSuggestionDialog() {
			return document.getElementById('seovaultseo-schema-content-suggestion-dialog');
		}

		function announceSchemaHealth(label) {
			var el = document.getElementById('seovaultseo-schema-health-announcer');
			if (!el) {
				return;
			}
			var text = (limits.schema_health_updated || 'Schema Health updated: %s').replace('%s', label || '');
			el.textContent = '';
			window.setTimeout(function() {
				el.textContent = text;
			}, 50);
		}

		function closeSuggestionDialog() {
			var dialog = getSuggestionDialog();
			if (suggestionKeyHandler) {
				document.removeEventListener('keydown', suggestionKeyHandler, true);
				suggestionKeyHandler = null;
			}
			if (dialog) {
				if (typeof dialog.close === 'function' && dialog.open) {
					dialog.close();
				} else {
					dialog.removeAttribute('open');
					dialog.classList.remove('seovaultseo-schema-dialog-fallback-open');
				}
			}
			activeSuggestion = null;
			if (suggestionReturnFocus && typeof suggestionReturnFocus.focus === 'function') {
				suggestionReturnFocus.focus();
			}
			suggestionReturnFocus = null;
		}

		function openSuggestionDialog(payload) {
			activeSuggestion = payload || null;
			var dialog = getSuggestionDialog();
			var $body = $('[data-suggestion-dialog-body]');
			if (!dialog || !$body.length || !activeSuggestion) {
				return;
			}
			suggestionReturnFocus = document.activeElement;
			$body.html(buildSuggestionEditorHtml(activeSuggestion));
			if (typeof dialog.showModal === 'function') {
				dialog.showModal();
			} else {
				dialog.setAttribute('open', 'open');
				dialog.classList.add('seovaultseo-schema-dialog-fallback-open');
			}
			var $dialog = $(dialog);
			var $focusables = getFocusable($dialog);
			if ($focusables.length) {
				$focusables.first().trigger('focus');
			} else {
				$body.trigger('focus');
			}
			suggestionKeyHandler = trapFocus(dialog, closeSuggestionDialog);
			document.addEventListener('keydown', suggestionKeyHandler, true);
		}

		function buildSuggestionEditorHtml(payload) {
			var kind = payload.kind || 'faq';
			var attrs = payload.attributes || {};
			var html = '';
			html += '<p class="description">' + escapeHtml(payload.reason || '') + '</p>';
			if (kind === 'howto') {
				html += '<p><label>' + escapeHtml(limits.schema_suggestion_howto_name || 'How-to name') + '<br/><input type="text" class="widefat" data-suggestion-field="name" value="' + escapeAttr(attrs.name || '') + '" /></label></p>';
				html += '<div data-suggestion-rows="steps">';
				(attrs.steps || []).forEach(function(step, index) {
					html += suggestionStepRow(step, index);
				});
				html += '</div>';
				html += '<p><button type="button" class="button-link" data-suggestion-add-step>' + escapeHtml(limits.schema_suggestion_add_row || 'Add row') + '</button></p>';
			} else {
				html += '<p><label>' + escapeHtml(limits.schema_suggestion_faq_heading || 'FAQ heading') + '<br/><input type="text" class="widefat" data-suggestion-field="heading" value="' + escapeAttr(attrs.heading || '') + '" /></label></p>';
				html += '<div data-suggestion-rows="items">';
				(attrs.items || []).forEach(function(item, index) {
					html += suggestionFaqRow(item, index);
				});
				html += '</div>';
				html += '<p><button type="button" class="button-link" data-suggestion-add-item>' + escapeHtml(limits.schema_suggestion_add_row || 'Add row') + '</button></p>';
			}
			html += '<p class="seovaultseo-schema-content-suggestion-dialog-error" data-suggestion-dialog-error hidden></p>';
			return html;
		}

		function suggestionFaqRow(item, index) {
			item = item || {};
			return (
				'<div class="seovaultseo-schema-content-suggestion-row" data-row-index="' + index + '">' +
				'<p><label>' + escapeHtml(limits.schema_suggestion_faq_question || 'Question') + '<br/><input type="text" class="widefat" data-suggestion-item-question value="' + escapeAttr(item.question || '') + '" /></label></p>' +
				'<p><label>' + escapeHtml(limits.schema_suggestion_faq_answer || 'Answer') + '<br/><textarea class="widefat" rows="3" data-suggestion-item-answer>' + escapeHtml(item.answer || '') + '</textarea></label></p>' +
				'<p><button type="button" class="button-link" data-suggestion-remove-row>' + escapeHtml(limits.schema_suggestion_remove_row || 'Remove') + '</button></p>' +
				'</div>'
			);
		}

		function suggestionStepRow(step, index) {
			step = step || {};
			return (
				'<div class="seovaultseo-schema-content-suggestion-row" data-row-index="' + index + '">' +
				'<p><label>' + escapeHtml(limits.schema_suggestion_howto_step || 'Step') + '<br/><input type="text" class="widefat" data-suggestion-step-name value="' + escapeAttr(step.name || '') + '" /></label></p>' +
				'<p><label>' + escapeHtml(limits.schema_suggestion_howto_step_text || 'Step details') + '<br/><textarea class="widefat" rows="2" data-suggestion-step-text>' + escapeHtml(step.text || '') + '</textarea></label></p>' +
				'<p><button type="button" class="button-link" data-suggestion-remove-row>' + escapeHtml(limits.schema_suggestion_remove_row || 'Remove') + '</button></p>' +
				'</div>'
			);
		}

		function escapeHtml(value) {
			return String(value == null ? '' : value)
				.replace(/&/g, '&amp;')
				.replace(/</g, '&lt;')
				.replace(/>/g, '&gt;')
				.replace(/"/g, '&quot;');
		}

		function escapeAttr(value) {
			return escapeHtml(value).replace(/'/g, '&#39;');
		}

		function readSuggestionEditorAttributes() {
			if (!activeSuggestion) {
				return null;
			}
			var kind = activeSuggestion.kind || 'faq';
			var $body = $('[data-suggestion-dialog-body]');
			if (kind === 'howto') {
				var steps = [];
				$body.find('[data-suggestion-rows="steps"] .seovaultseo-schema-content-suggestion-row').each(function() {
					steps.push({
						name: $(this).find('[data-suggestion-step-name]').val() || '',
						text: $(this).find('[data-suggestion-step-text]').val() || '',
						imageId: 0
					});
				});
				return {
					name: $body.find('[data-suggestion-field="name"]').val() || '',
					description: (activeSuggestion.attributes && activeSuggestion.attributes.description) || '',
					steps: steps
				};
			}
			var items = [];
			$body.find('[data-suggestion-rows="items"] .seovaultseo-schema-content-suggestion-row').each(function() {
				items.push({
					question: $(this).find('[data-suggestion-item-question]').val() || '',
					answer: $(this).find('[data-suggestion-item-answer]').val() || ''
				});
			});
			return {
				heading: $body.find('[data-suggestion-field="heading"]').val() || '',
				intro: (activeSuggestion.attributes && activeSuggestion.attributes.intro) || '',
				items: items
			};
		}

		$(document).on('click', '.seovaultseo-schema-content-suggestion-review', function(event) {
			event.preventDefault();
			var $li = $(this).closest('.seovaultseo-schema-content-suggestion');
			var raw = $li.find('.seovaultseo-schema-content-suggestion-payload').text() || '{}';
			var payload = null;
			try {
				payload = JSON.parse(raw);
			} catch (err) {
				payload = null;
			}
			if (!payload) {
				return;
			}
			openSuggestionDialog(payload);
		});

		$(document).on('click', '.seovaultseo-schema-content-suggestion-dismiss', function(event) {
			event.preventDefault();
			var $li = $(this).closest('.seovaultseo-schema-content-suggestion');
			var kind = $li.attr('data-suggestion-kind') || '';
			var sid = $li.attr('data-suggestion-id') || '';
			var hash = $li.closest('[data-content-hash]').attr('data-content-hash') || currentContentHash() || '';
			$.post(ajaxUrl, {
				action: 'seovaultseo_content_suggestion_dismiss',
				nonce: limits.schema_nonce || '',
				post_id: currentPostId(),
				kind: kind,
				suggestion_id: sid,
				content_hash: hash
			}).done(function(response) {
				if (response && response.success && response.data && response.data.html) {
					getPanel().replaceWith(response.data.html);
				}
			}).fail(function() {
				window.alert(limits.schema_suggestion_dismiss_failed || 'Could not dismiss that suggestion.');
			});
		});

		$(document).on('click', '.seovaultseo-schema-content-suggestion-dialog-close', function(event) {
			event.preventDefault();
			closeSuggestionDialog();
		});

		$(document).on('click', '[data-suggestion-add-item]', function(event) {
			event.preventDefault();
			$('[data-suggestion-rows="items"]').append(suggestionFaqRow({}, 99));
		});

		$(document).on('click', '[data-suggestion-add-step]', function(event) {
			event.preventDefault();
			$('[data-suggestion-rows="steps"]').append(suggestionStepRow({}, 99));
		});

		$(document).on('click', '[data-suggestion-remove-row]', function(event) {
			event.preventDefault();
			$(this).closest('.seovaultseo-schema-content-suggestion-row').remove();
		});

		$(document).on('click', '.seovaultseo-schema-content-suggestion-confirm', function(event) {
			event.preventDefault();
			if (!activeSuggestion) {
				return;
			}
			var $btn = $(this);
			var $error = $('[data-suggestion-dialog-error]');
			var attrs = readSuggestionEditorAttributes();
			$error.prop('hidden', true).empty();
			$btn.prop('disabled', true).text(limits.schema_suggestion_creating || 'Creating…');
			$.post(ajaxUrl, {
				action: 'seovaultseo_content_suggestion_confirm',
				nonce: limits.schema_nonce || '',
				post_id: currentPostId(),
				kind: activeSuggestion.kind || '',
				attributes: JSON.stringify(attrs || {})
			}).done(function(response) {
				if (!response || !response.success || !response.data) {
					var message = (response && response.data && response.data.message) || limits.schema_suggestion_confirm_failed || 'Could not prepare that block.';
					$error.text(message).prop('hidden', false);
					return;
				}
				var inserted = insertOrFocusSchemaBlock(
					response.data.block_name,
					activeSuggestion.type_label || (activeSuggestion.kind === 'howto' ? 'HowTo' : 'FAQPage'),
					response.data.attributes
				);
				closeSuggestionDialog();
				if (inserted) {
					refreshSchemaHealthAfterGuidedChange();
				}
			}).fail(function() {
				$error.text(limits.schema_suggestion_confirm_failed || 'Could not prepare that block.').prop('hidden', false);
			}).always(function() {
				$btn.prop('disabled', false).text(limits.schema_suggestion_create || 'Create block');
			});
		});

		// -----------------------------------------------------------------
		// Review schema wide surface
		// -----------------------------------------------------------------

		var reviewReturnFocus = null;
		var reviewDiagnostics = null;
		var reviewKeyHandler = null;

		function getReviewDialog() {
			return document.getElementById('seovaultseo-schema-review');
		}

		function getFocusable($root) {
			return $root.find('a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])')
				.filter(':visible')
				.filter(function() {
					return $(this).attr('aria-disabled') !== 'true';
				});
		}

		function openReviewDialog() {
			var dialog = getReviewDialog();
			if (!dialog) {
				return;
			}

			reviewReturnFocus = document.activeElement;
			if (typeof dialog.showModal === 'function') {
				dialog.showModal();
			} else {
				dialog.setAttribute('open', 'open');
				dialog.classList.add('seovaultseo-schema-review-fallback-open');
			}

			var $dialog = $(dialog);
			var $focusables = getFocusable($dialog);
			if ($focusables.length) {
				$focusables.first().trigger('focus');
			} else {
				$dialog.find('[data-schema-review-body]').trigger('focus');
			}

			reviewKeyHandler = function(event) {
				if (event.key === 'Escape') {
					event.preventDefault();
					closeReviewDialog();
					return;
				}
				if (event.key !== 'Tab') {
					return;
				}
				var $items = getFocusable($dialog);
				if (!$items.length) {
					event.preventDefault();
					return;
				}
				var first = $items[0];
				var last = $items[$items.length - 1];
				if (event.shiftKey && document.activeElement === first) {
					event.preventDefault();
					last.focus();
				} else if (!event.shiftKey && document.activeElement === last) {
					event.preventDefault();
					first.focus();
				}
			};
			document.addEventListener('keydown', reviewKeyHandler, true);
		}

		function closeReviewDialog() {
			var dialog = getReviewDialog();
			if (!dialog) {
				return;
			}
			if (reviewKeyHandler) {
				document.removeEventListener('keydown', reviewKeyHandler, true);
				reviewKeyHandler = null;
			}
			if (typeof dialog.close === 'function' && dialog.open) {
				dialog.close();
			} else {
				dialog.removeAttribute('open');
				dialog.classList.remove('seovaultseo-schema-review-fallback-open');
			}
			if (reviewReturnFocus && typeof reviewReturnFocus.focus === 'function') {
				reviewReturnFocus.focus();
			}
			reviewReturnFocus = null;
		}

		function setReviewBody(html) {
			var $body = $('#seovaultseo-schema-review-body');
			$body.html(html);
			var $content = $body.find('[data-schema-review-diagnostics]');
			var raw = $content.attr('data-schema-review-diagnostics');
			reviewDiagnostics = null;
			if (raw) {
				try {
					reviewDiagnostics = JSON.parse(raw);
				} catch (err) {
					reviewDiagnostics = null;
				}
			}
		}

		function loadReviewSchema() {
			var $panel = getPanel();
			var postId = $('.seovault-ai-meta-box').data('post-id');
			var dialog = getReviewDialog();

			if (!dialog || !postId) {
				return;
			}

			setReviewBody(
				'<p class="seovaultseo-schema-review-placeholder">' +
				escapeHtml(limits.schema_review_loading || 'Loading schema details…') +
				'</p>'
			);
			openReviewDialog();

			var requestData = {
				action: 'seovaultseo_review_schema',
				nonce: limits.schema_nonce,
				post_id: postId,
				include_live: 0
			};
			requestData.custom_schema_json = $('#seovault_schema_json').val() || '';

			$.ajax({
				url: limits.ajax_url,
				method: 'POST',
				dataType: 'json',
				data: requestData
			}).done(function(response) {
				if (response && response.success && response.data && response.data.html) {
					setReviewBody(response.data.html);
					if (response.data.diagnostics) {
						reviewDiagnostics = response.data.diagnostics;
					}
					var $dialog = $(dialog);
					var $focusables = getFocusable($dialog);
					if ($focusables.length) {
						$focusables.first().trigger('focus');
					}
					return;
				}
				var message = response && response.data && response.data.message
					? response.data.message
					: (limits.schema_review_failed || 'Could not load schema details. Please try again.');
				setReviewBody('<p class="seovaultseo-schema-review-error" role="alert">' + escapeHtml(message) + '</p>');
			}).fail(function(xhr) {
				var response = xhr.responseJSON;
				var message = response && response.data && response.data.message
					? response.data.message
					: (limits.schema_review_failed || 'Could not load schema details. Please try again.');
				setReviewBody('<p class="seovaultseo-schema-review-error" role="alert">' + escapeHtml(message) + '</p>');
			});
		}

		function getDiagnosticsText() {
			if (!reviewDiagnostics) {
				var raw = $('#seovaultseo-schema-review-body [data-schema-review-diagnostics]').attr('data-schema-review-diagnostics');
				if (raw) {
					try {
						reviewDiagnostics = JSON.parse(raw);
					} catch (err) {
						reviewDiagnostics = null;
					}
				}
			}
			return reviewDiagnostics ? JSON.stringify(reviewDiagnostics, null, 2) : '';
		}

		function copyDiagnostics() {
			var text = getDiagnosticsText();
			var $status = $('.seovaultseo-schema-review-copy-status');
			if (!text) {
				$status.text(limits.schema_review_copy_failed || 'Could not copy diagnostics.').prop('hidden', false);
				return;
			}
			function done(ok) {
				$status
					.text(ok
						? (limits.schema_review_copied || 'Diagnostics copied.')
						: (limits.schema_review_copy_failed || 'Could not copy diagnostics.'))
					.prop('hidden', false);
			}
			if (navigator.clipboard && navigator.clipboard.writeText) {
				navigator.clipboard.writeText(text).then(function() { done(true); }).catch(function() { done(false); });
				return;
			}
			var $tmp = $('<textarea>').css({ position: 'fixed', left: '-9999px' }).val(text).appendTo('body');
			$tmp[0].select();
			try {
				done(document.execCommand('copy'));
			} catch (err) {
				done(false);
			}
			$tmp.remove();
		}

		function downloadDiagnostics() {
			var text = getDiagnosticsText();
			if (!text) {
				return;
			}
			var blob = new Blob([text], { type: 'application/json;charset=utf-8' });
			var url = URL.createObjectURL(blob);
			var anchor = document.createElement('a');
			anchor.href = url;
			anchor.download = 'seovault-schema-diagnostics.json';
			document.body.appendChild(anchor);
			anchor.click();
			document.body.removeChild(anchor);
			URL.revokeObjectURL(url);
		}

		$(document).on('click', '.seovaultseo-schema-health-review', function(event) {
			event.preventDefault();
			loadReviewSchema();
		});

		$(document).on('click', '.seovaultseo-schema-review-close', function(event) {
			event.preventDefault();
			closeReviewDialog();
		});

		$(document).on('click', '.seovaultseo-schema-review-copy', function(event) {
			event.preventDefault();
			copyDiagnostics();
		});

		$(document).on('click', '.seovaultseo-schema-review-download', function(event) {
			event.preventDefault();
			downloadDiagnostics();
		});

		$(document).on('click', '#seovaultseo-schema-review', function(event) {
			if (event.target === getReviewDialog()) {
				closeReviewDialog();
			}
		});

		// -----------------------------------------------------------------
		// Add schema picker + guided form engine
		// -----------------------------------------------------------------

		var addPickerReturnFocus = null;
		var addPickerKeyHandler = null;
		var guidedReturnFocus = null;
		var guidedKeyHandler = null;
		var guidedDirty = false;
		var guidedState = {
			type: '',
			instanceId: '',
			values: {},
			sourceMeta: {}
		};

		function getAddPickerDialog() {
			return document.getElementById('seovaultseo-schema-add-picker');
		}

		function getGuidedFormDialog() {
			return document.getElementById('seovaultseo-schema-guided-form');
		}

		function setAddButtonExpanded(expanded) {
			$('.seovaultseo-schema-health-add').attr('aria-expanded', expanded ? 'true' : 'false');
		}

		function trapFocus(dialog, onEscape) {
			return function(event) {
				if (event.key === 'Escape') {
					event.preventDefault();
					onEscape();
					return;
				}
				if (event.key !== 'Tab') {
					return;
				}
				var $items = getFocusable($(dialog));
				if (!$items.length) {
					event.preventDefault();
					return;
				}
				var first = $items[0];
				var last = $items[$items.length - 1];
				if (event.shiftKey && document.activeElement === first) {
					event.preventDefault();
					last.focus();
				} else if (!event.shiftKey && document.activeElement === last) {
					event.preventDefault();
					first.focus();
				}
			};
		}

		function openNativeDialog(dialog) {
			if (!dialog) {
				return;
			}
			if (typeof dialog.showModal === 'function') {
				dialog.showModal();
			} else {
				dialog.setAttribute('open', 'open');
				dialog.classList.add('seovaultseo-schema-dialog-fallback-open');
			}
		}

		function closeNativeDialog(dialog) {
			if (!dialog) {
				return;
			}
			if (typeof dialog.close === 'function' && dialog.open) {
				dialog.close();
			} else {
				dialog.removeAttribute('open');
				dialog.classList.remove('seovaultseo-schema-dialog-fallback-open');
			}
		}

		function openAddPicker() {
			var dialog = getAddPickerDialog();
			if (!dialog) {
				return;
			}
			addPickerReturnFocus = document.activeElement;
			openNativeDialog(dialog);
			setAddButtonExpanded(true);
			var $dialog = $(dialog);
			var $focusables = getFocusable($dialog);
			if ($focusables.length) {
				$focusables.first().trigger('focus');
			} else {
				$dialog.find('#seovaultseo-schema-add-picker-body').trigger('focus');
			}
			addPickerKeyHandler = trapFocus(dialog, closeAddPicker);
			document.addEventListener('keydown', addPickerKeyHandler, true);
		}

		function closeAddPicker() {
			var dialog = getAddPickerDialog();
			if (addPickerKeyHandler) {
				document.removeEventListener('keydown', addPickerKeyHandler, true);
				addPickerKeyHandler = null;
			}
			closeNativeDialog(dialog);
			setAddButtonExpanded(false);
			if (addPickerReturnFocus && typeof addPickerReturnFocus.focus === 'function') {
				addPickerReturnFocus.focus();
			}
			addPickerReturnFocus = null;
		}

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

		function guidedSchemas() {
			return (limits.guided_form_schemas && typeof limits.guided_form_schemas === 'object')
				? limits.guided_form_schemas
				: {};
		}

		function getFieldValue(values, id) {
			if (values && Object.prototype.hasOwnProperty.call(values, id)) {
				return values[id];
			}
			return undefined;
		}

		function renderGuidedControl(field, values, namePrefix) {
			var id = field.id;
			var control = field.control || 'text';
			var name = namePrefix + '[' + id + ']';
			var inputId = namePrefix.replace(/[\[\]]/g, '_') + '_' + id;
			var raw = getFieldValue(values, id);
			if (typeof raw === 'undefined') {
				raw = field.default != null ? field.default : '';
			}
			var required = !!field.required;
			var desc = field.description ? '<p class="description" id="' + escapeHtml(inputId) + '_desc">' + escapeHtml(field.description) + '</p>' : '';
			var describedby = (field.description ? inputId + '_desc ' : '') + inputId + '_error';
			var labelHtml = '';

			if (control === 'hidden') {
				return '<input type="hidden" name="' + escapeHtml(name) + '" id="' + escapeHtml(inputId) + '" value="' + escapeHtml(raw) + '" data-guided-field="' + escapeHtml(id) + '" data-guided-control="hidden" />';
			}

			if (control === 'group' || control === 'list' || control === 'list_text') {
				labelHtml = '<span class="seovaultseo-schema-guided-legend" id="' + escapeHtml(inputId) + '_legend">' + escapeHtml(field.label || id) + (required ? ' <span class="seovaultseo-schema-guided-required" aria-hidden="true">*</span>' : '') + '</span>';
			} else {
				labelHtml = '<label for="' + escapeHtml(inputId) + '">' + escapeHtml(field.label || id) + (required ? ' <span class="seovaultseo-schema-guided-required" aria-hidden="true">*</span>' : '') + '</label>';
			}

			var controlHtml = '';
			var common = ' id="' + escapeHtml(inputId) + '" name="' + escapeHtml(name) + '" data-guided-field="' + escapeHtml(id) + '" data-guided-control="' + escapeHtml(control) + '" aria-describedby="' + escapeHtml(describedby) + '"' + (required ? ' aria-required="true"' : '');

			if (control === 'textarea') {
				controlHtml = '<textarea class="large-text" rows="4"' + common + '>' + escapeHtml(raw) + '</textarea>';
			} else if (control === 'url') {
				controlHtml = '<input type="url" class="regular-text"' + common + ' value="' + escapeHtml(raw) + '" />';
			} else if (control === 'number') {
				controlHtml = '<input type="number" class="small-text"' + common + ' value="' + escapeHtml(raw) + '" />';
			} else if (control === 'datetime') {
				controlHtml = '<input type="datetime-local" class="regular-text"' + common + ' value="' + escapeHtml(raw) + '" />';
			} else if (control === 'enum') {
				controlHtml = '<select' + common + '><option value="">' + escapeHtml(limits.schema_guided_select_placeholder || 'Select…') + '</option>';
				(field.options || []).forEach(function(opt) {
					var selected = String(raw) === String(opt.value) ? ' selected' : '';
					controlHtml += '<option value="' + escapeHtml(opt.value) + '"' + selected + '>' + escapeHtml(opt.label || opt.value) + '</option>';
				});
				controlHtml += '</select>';
			} else if (control === 'media') {
				controlHtml = '<div class="seovaultseo-schema-guided-media"><input type="text" class="regular-text"' + common + ' value="' + escapeHtml(raw) + '" /><button type="button" class="button seovaultseo-schema-guided-media-select" data-guided-media-for="' + escapeHtml(inputId) + '">' + escapeHtml(limits.schema_guided_select_media || 'Select media') + '</button></div>';
			} else if (control === 'group') {
				controlHtml = '<fieldset class="seovaultseo-schema-guided-group" aria-labelledby="' + escapeHtml(inputId) + '_legend" data-guided-field="' + escapeHtml(id) + '" data-guided-control="group">';
				(field.fields || []).forEach(function(child) {
					controlHtml += renderGuidedControl(child, (raw && typeof raw === 'object') ? raw : {}, name);
				});
				controlHtml += '</fieldset>';
			} else if (control === 'list') {
				var items = Array.isArray(raw) && raw.length ? raw : [{}];
				controlHtml = '<div class="seovaultseo-schema-guided-list" data-guided-field="' + escapeHtml(id) + '" data-guided-control="list" aria-labelledby="' + escapeHtml(inputId) + '_legend">';
				items.forEach(function(item, index) {
					controlHtml += '<div class="seovaultseo-schema-guided-list-item" data-guided-list-index="' + index + '">';
					(field.fields || []).forEach(function(child) {
						controlHtml += renderGuidedControl(child, item || {}, name + '[' + index + ']');
					});
					controlHtml += '<button type="button" class="button-link seovaultseo-schema-guided-list-remove">' + escapeHtml(limits.schema_guided_remove_item || 'Remove item') + '</button></div>';
				});
				controlHtml += '<button type="button" class="button seovaultseo-schema-guided-list-add" data-guided-list-template="' + escapeHtml(id) + '">' + escapeHtml(limits.schema_guided_add_item || 'Add item') + '</button></div>';
			} else if (control === 'list_text') {
				var textItems = Array.isArray(raw) && raw.length ? raw : (raw ? [raw] : ['']);
				var itemLabelTpl = limits.schema_guided_list_item_label || 'Item %d';
				controlHtml = '<div class="seovaultseo-schema-guided-list-text" data-guided-field="' + escapeHtml(id) + '" data-guided-control="list_text" aria-labelledby="' + escapeHtml(inputId) + '_legend">';
				textItems.forEach(function(item, index) {
					var rowLabel = String(itemLabelTpl).replace('%d', String(index + 1));
					controlHtml += '<div class="seovaultseo-schema-guided-list-item"><label class="screen-reader-text" for="' + escapeHtml(inputId) + '_item_' + index + '">' + escapeHtml(rowLabel) + '</label><input type="text" class="regular-text" id="' + escapeHtml(inputId) + '_item_' + index + '" name="' + escapeHtml(name) + '[' + index + ']" value="' + escapeHtml(item) + '" data-guided-list-text="1" aria-label="' + escapeHtml(rowLabel) + '" /><button type="button" class="button-link seovaultseo-schema-guided-list-remove">' + escapeHtml(limits.schema_guided_remove_item || 'Remove') + '</button></div>';
				});
				controlHtml += '<button type="button" class="button seovaultseo-schema-guided-list-add">' + escapeHtml(limits.schema_guided_add_item || 'Add item') + '</button></div>';
			} else {
				controlHtml = '<input type="text" class="regular-text"' + common + ' value="' + escapeHtml(raw) + '" />';
			}

			return '<div class="seovaultseo-schema-guided-field seovaultseo-schema-guided-field-' + escapeHtml(control) + '" data-guided-field-wrap="' + escapeHtml(id) + '"' + visibleWhenAttrs(field) + '>' +
				labelHtml + desc + controlHtml +
				'<p class="seovaultseo-schema-guided-field-error" id="' + escapeHtml(inputId) + '_error" data-guided-field-error hidden></p></div>';
		}

		function visibleWhenAttrs(field) {
			var rule = field && field.visible_when ? field.visible_when : null;
			if (!rule || typeof rule !== 'object') {
				return '';
			}
			var attrs = '';
			if (rule.path) {
				attrs += ' data-visible-when-path="' + escapeHtml(rule.path) + '"';
			}
			if (Array.isArray(rule.in)) {
				attrs += ' data-visible-when-in="' + escapeHtml(JSON.stringify(rule.in)) + '"';
			}
			if (Array.isArray(rule.not_in)) {
				attrs += ' data-visible-when-not-in="' + escapeHtml(JSON.stringify(rule.not_in)) + '"';
			}
			return attrs;
		}

		function parseJsonAttr(value) {
			if (!value) {
				return [];
			}
			try {
				var parsed = JSON.parse(value);
				return Array.isArray(parsed) ? parsed : [];
			} catch (e) {
				return [];
			}
		}

		function applyGuidedConditionals($root) {
			if (!$root || !$root.length) {
				return;
			}
			$root.find('[data-visible-when-path]').each(function() {
				var $wrap = $(this);
				var path = $wrap.attr('data-visible-when-path') || '';
				var $scope = $wrap.closest('[data-guided-control="group"], [data-guided-control="list"], form').first();
				if (!$scope.length) {
					$scope = $root;
				}
				var $driver = $scope.find('[data-guided-field="' + path + '"]').first();
				if (!$driver.length) {
					$driver = $root.find('[data-guided-field="' + path + '"]').first();
				}
				var current = $driver.length ? String($driver.val() == null ? '' : $driver.val()) : '';
				var allowIn = parseJsonAttr($wrap.attr('data-visible-when-in'));
				var denyIn = parseJsonAttr($wrap.attr('data-visible-when-not-in'));
				var visible = true;
				if (allowIn.length) {
					visible = allowIn.indexOf(current) !== -1;
				}
				if (denyIn.length && denyIn.indexOf(current) !== -1) {
					visible = false;
				}
				$wrap.toggle(visible).attr('aria-hidden', visible ? 'false' : 'true');
			});
		}

		function renderVisibilitySection(schema, sourceMeta) {
			var mode = (sourceMeta && sourceMeta.visibility_mode) ? sourceMeta.visibility_mode : 'confirm_visible';
			var anchor = (sourceMeta && sourceMeta.visibility_anchor) ? sourceMeta.visibility_anchor : '';
			var requires = !!(schema && (schema.requires_visibility || schema.requires_visibility_confirmation || schema.requires_visible_content));
			var help = requires
				? (limits.schema_guided_visibility_help || 'Specialized schema should match content readers can see on the page.')
				: (limits.schema_guided_visibility_help_optional || 'Confirm how this schema relates to visible page content.');
			return '<fieldset class="seovaultseo-schema-guided-visibility" data-guided-visibility="1">' +
				'<legend>' + escapeHtml(limits.schema_guided_visibility_legend || 'Content visibility') + '</legend>' +
				'<p class="description">' + escapeHtml(help) + '</p>' +
				'<label class="seovaultseo-schema-guided-visibility-option"><input type="radio" name="__visibility_mode" value="seovault_block"' + (mode === 'seovault_block' ? ' checked' : '') + ' data-guided-visibility-mode="1" /> ' + escapeHtml(limits.schema_guided_visibility_seovault_block || 'I will keep matching visible content (SEOvault block/summary when available).') + '</label>' +
				'<label class="seovaultseo-schema-guided-visibility-option"><input type="radio" name="__visibility_mode" value="confirm_visible"' + (mode === 'confirm_visible' ? ' checked' : '') + ' data-guided-visibility-mode="1" /> ' + escapeHtml(limits.schema_guided_visibility_confirm || 'I confirm equivalent content is already visible on this page.') + '</label>' +
				'<label for="seovault_guided_visibility_anchor">' + escapeHtml(limits.schema_guided_visibility_anchor_label || 'Optional page section / anchor reference') + '</label>' +
				'<input type="text" class="regular-text" id="seovault_guided_visibility_anchor" value="' + escapeHtml(anchor) + '" data-guided-visibility-anchor="1" placeholder="' + escapeHtml(limits.schema_guided_visibility_anchor_placeholder || 'e.g. #event-details or “Event section”') + '" />' +
				'</fieldset>';
		}

		function collectGuidedValues($root, fields) {
			var out = {};
			(fields || []).forEach(function(field) {
				var id = field.id;
				var control = field.control || 'text';
				if (control === 'group') {
					out[id] = collectGuidedValues($root, field.fields || []);
					// Prefer nested inputs scoped under group wrap when present.
					var $group = $root.find('[data-guided-field-wrap="' + id + '"] [data-guided-control="group"]').first();
					if ($group.length) {
						out[id] = {};
						(field.fields || []).forEach(function(child) {
							var $input = $group.find('[data-guided-field="' + child.id + '"]').first();
							if ($input.length) {
								out[id][child.id] = $input.val();
							}
						});
					}
					return;
				}
				if (control === 'list') {
					out[id] = [];
					$root.find('[data-guided-field-wrap="' + id + '"] .seovaultseo-schema-guided-list-item').each(function() {
						var $item = $(this);
						var row = {};
						(field.fields || []).forEach(function(child) {
							var $input = $item.find('[data-guided-field="' + child.id + '"]').first();
							if ($input.length) {
								row[child.id] = $input.val();
							}
						});
						out[id].push(row);
					});
					return;
				}
				if (control === 'list_text') {
					out[id] = [];
					$root.find('[data-guided-field-wrap="' + id + '"] [data-guided-list-text]').each(function() {
						out[id].push($(this).val());
					});
					return;
				}
				var $input = $root.find('[data-guided-field="' + id + '"]').first();
				if ($input.length) {
					out[id] = $input.val();
				}
			});
			return out;
		}

		function collectVisibilityMeta($root) {
			return {
				visibility_mode: $root.find('[data-guided-visibility-mode]:checked').val() || 'confirm_visible',
				visibility_anchor: $root.find('[data-guided-visibility-anchor]').val() || ''
			};
		}

		function setGuidedSummary(setup, readiness) {
			var $summary = $('[data-guided-setup-summary]');
			if (!$summary.length) {
				return;
			}
			var ready = readiness && readiness.ready;
			var text = ready
				? (limits.schema_guided_ready || 'Ready to publish when the page is saved.')
				: (limits.schema_guided_unfinished || 'Saved as unfinished setup — not published yet.');
			if (setup && setup.blockers && setup.blockers.length) {
				var blockersTpl = limits.schema_guided_blockers_remaining || '%d item(s) still needed.';
				text += ' ' + String(blockersTpl).replace('%d', String(setup.blockers.length));
			}
			$summary.text(text).prop('hidden', false);
		}

		function clearGuidedErrors() {
			$('[data-guided-form-errors]').prop('hidden', true).empty();
			$('[data-guided-field-error]').prop('hidden', true).empty();
			$('[data-guided-field], [data-guided-list-text]').removeAttr('aria-invalid');
		}

		function applyGuidedFieldErrors(errors) {
			var firstInvalid = null;
			var defaultMsg = limits.schema_guided_field_invalid || 'This field needs attention.';
			(errors || []).forEach(function(entry) {
				var fieldId = '';
				var message = defaultMsg;
				if (typeof entry === 'string') {
					var colon = entry.indexOf(':');
					if (colon !== -1) {
						fieldId = entry.slice(colon + 1);
					}
				} else if (entry && typeof entry === 'object') {
					fieldId = entry.field_key || entry.field || entry.id || '';
					if (entry.message) {
						message = String(entry.message);
					} else if (entry.title) {
						message = String(entry.title);
					}
				}
				if (!fieldId) {
					return;
				}
				var $wrap = $('[data-guided-field-wrap="' + fieldId + '"]');
				if (!$wrap.length) {
					return;
				}
				var $err = $wrap.find('[data-guided-field-error]').first();
				var $input = $wrap.find(':input:visible').filter(':not([type="hidden"])').first();
				if ($err.length) {
					$err.text(message).prop('hidden', false);
				}
				if ($input.length) {
					$input.attr('aria-invalid', 'true');
					if (!firstInvalid) {
						firstInvalid = $input;
					}
				}
			});
			if (firstInvalid && firstInvalid.length) {
				var el = firstInvalid.get(0);
				if (el && typeof el.scrollIntoView === 'function') {
					el.scrollIntoView({ block: 'center' });
				}
				firstInvalid.trigger('focus');
			}
			return !!firstInvalid;
		}

		function markGuidedDirty() {
			guidedDirty = true;
		}

		function buildGuidedFormHtml(schema, values, sourceMeta) {
			var html = '<form class="seovaultseo-schema-guided-form-engine" data-schema-type="' + escapeHtml(schema.type) + '" novalidate>';
			if (schema.help) {
				html += '<p class="seovaultseo-schema-guided-form-help" data-guided-type-help="1">' + escapeHtml(schema.help) + '</p>';
			}
			var sourceLabel = (sourceMeta && sourceMeta.activation_source === 'template')
				? (limits.schema_source_template || 'Template')
				: ((sourceMeta && sourceMeta.activation_source === 'integration')
					? (limits.schema_source_integration || 'Integration')
					: (limits.schema_source_guided || 'Guided form'));
			html += '<p class="seovaultseo-schema-guided-form-source" data-guided-source-label="1"><span class="seovaultseo-schema-source-label">' + escapeHtml(sourceLabel) + '</span></p>';
			html += '<p class="seovaultseo-schema-guided-form-advisory" role="note">' + escapeHtml(limits.schema_guided_advisory || 'Field hints help you fill the form. SEOvault decides readiness on the server when you save.') + '</p>';
			(schema.fields || []).forEach(function(field) {
				html += renderGuidedControl(field, values || {}, 'seovault_guided_' + schema.type);
			});
			html += renderVisibilitySection(schema, sourceMeta || {});
			html += '</form>';
			return html;
		}

		function openGuidedForm(type, options) {
			options = options || {};
			var dialog = getGuidedFormDialog();
			var schemas = guidedSchemas();
			var schema = schemas[type];
			if (!dialog || !schema || !schema.ok) {
				return;
			}

			guidedReturnFocus = document.activeElement;
			guidedDirty = false;
			guidedState = {
				type: type,
				instanceId: options.instanceId || '',
				values: options.values || {},
				sourceMeta: options.sourceMeta || {}
			};

			var title = (limits.schema_add_guided_title || 'Guided schema form') + ': ' + (schema.label || type);
			$('#seovaultseo-schema-guided-form-title').text(title);
			var $fields = $('[data-guided-form-fields]');
			$fields.html(buildGuidedFormHtml(schema, guidedState.values, guidedState.sourceMeta));
			applyGuidedConditionals($fields.find('form').first());
			clearGuidedErrors();
			$('[data-guided-setup-summary]').prop('hidden', true).empty();
			$('.seovaultseo-schema-guided-form-remove').prop('hidden', !guidedState.instanceId);
			$('.seovaultseo-schema-guided-form-save').text(limits.schema_guided_save || 'Save draft').prop('disabled', false);

			openNativeDialog(dialog);
			var focusField = options.focusField || '';
			var focused = false;
			if (focusField) {
				focused = focusGuidedField(focusField);
			}
			if (!focused) {
				var $focusables = getFocusable($(dialog));
				if ($focusables.length) {
					$focusables.first().trigger('focus');
				} else {
					$('#seovaultseo-schema-guided-form-body').trigger('focus');
				}
			}
			guidedKeyHandler = trapFocus(dialog, requestCloseGuidedForm);
			document.addEventListener('keydown', guidedKeyHandler, true);
		}

		function focusGuidedField(fieldKey) {
			if (!fieldKey) {
				return false;
			}
			var $wrap = $('[data-guided-field-wrap="' + fieldKey + '"]');
			var $input = $wrap.find(':input:visible').filter(':not([type="hidden"])').first();
			if (!$input.length) {
				$input = $('[data-guided-field="' + fieldKey + '"]').filter(':input').first();
			}
			if (!$input.length) {
				$input = $('[data-guided-field="' + fieldKey + '"]').find(':input').first();
			}
			if (!$input.length) {
				return false;
			}
			var el = $input.get(0);
			if (el && typeof el.scrollIntoView === 'function') {
				el.scrollIntoView({ block: 'center' });
			}
			$input.trigger('focus');
			return true;
		}

		function refreshSchemaHealthAfterGuidedChange() {
			if (typeof checkSchema === 'function') {
				checkSchema(true, false);
			}
		}

		function loadInstanceAndOpen(instanceId, type, focusField) {
			var cached = setupInstanceCache[String(instanceId)];
			if (cached && cached.fields) {
				openGuidedForm(type || cached.type, {
					instanceId: instanceId,
					values: cached.fields || {},
					sourceMeta: cached.source_meta || {},
					focusField: focusField || cached.first_field_key || ''
				});
				return;
			}

			$.post(ajaxUrl, {
				action: 'seovaultseo_schema_instances_list',
				nonce: limits.schema_instances_nonce || '',
				post_id: currentPostId()
			}).done(function(response) {
				if (!response || !response.success || !response.data || !response.data.instances) {
					window.alert(limits.schema_setup_load_failed || 'Could not load that schema draft. Please try again.');
					return;
				}
				updateContentHash(response.data.content_hash);
				var found = null;
				(response.data.instances || []).forEach(function(row) {
					if (row && String(row.id) === String(instanceId)) {
						found = row;
					}
					if (row && row.id) {
						setupInstanceCache[String(row.id)] = {
							instance_id: row.id,
							type: row.type,
							fields: row.fields || {},
							source_meta: row.source_meta || {},
							first_field_key: focusField || ''
						};
					}
				});
				if (!found) {
					window.alert(limits.schema_setup_load_failed || 'Could not load that schema draft. Please try again.');
					return;
				}
				openGuidedForm(type || found.type, {
					instanceId: found.id,
					values: found.fields || {},
					sourceMeta: found.source_meta || {},
					focusField: focusField || ''
				});
			}).fail(function() {
				window.alert(limits.schema_setup_load_failed || 'Could not load that schema draft. Please try again.');
			});
		}

		function removeSetupInstance(instanceId) {
			if (!instanceId) {
				return;
			}
			if (!window.confirm(limits.schema_setup_remove_confirm || limits.schema_guided_remove_confirm || 'Remove this schema draft from the post?')) {
				return;
			}
			$.post(ajaxUrl, {
				action: 'seovaultseo_schema_instances_delete',
				nonce: limits.schema_instances_nonce || '',
				post_id: currentPostId(),
				payload: JSON.stringify({
					instance_id: instanceId,
					content_hash: currentContentHash()
				})
			}).done(function(response) {
				if (!response || !response.success) {
					window.alert((response && response.data && response.data.message) || limits.schema_guided_save_failed || 'Could not save the schema draft.');
					return;
				}
				updateContentHash(response.data && response.data.content_hash);
				delete setupInstanceCache[String(instanceId)];
				if (guidedState.instanceId && String(guidedState.instanceId) === String(instanceId)) {
					guidedDirty = false;
					closeGuidedForm(true);
				}
				refreshSchemaHealthAfterGuidedChange();
			}).fail(function() {
				window.alert(limits.schema_guided_save_failed || 'Could not save the schema draft.');
			});
		}

		function closeGuidedForm(force) {
			if (!force && guidedDirty) {
				var ok = window.confirm(limits.schema_guided_unsaved || 'You have unsaved schema changes. Discard them?');
				if (!ok) {
					return false;
				}
			}
			var dialog = getGuidedFormDialog();
			if (guidedKeyHandler) {
				document.removeEventListener('keydown', guidedKeyHandler, true);
				guidedKeyHandler = null;
			}
			closeNativeDialog(dialog);
			guidedDirty = false;
			if (guidedReturnFocus && typeof guidedReturnFocus.focus === 'function') {
				guidedReturnFocus.focus();
			}
			guidedReturnFocus = null;
			return true;
		}

		function requestCloseGuidedForm() {
			closeGuidedForm(false);
		}

		function currentPostId() {
			var fromBox = parseInt($('.seovault-ai-meta-box').attr('data-post-id') || '0', 10);
			if (fromBox) {
				return fromBox;
			}
			return parseInt(limits.post_id || '0', 10) || 0;
		}

		function currentContentHash() {
			var dialog = getGuidedFormDialog();
			if (dialog && dialog.getAttribute('data-content-hash')) {
				return dialog.getAttribute('data-content-hash');
			}
			return limits.content_hash || '';
		}

		function updateContentHash(hash) {
			if (!hash) {
				return;
			}
			limits.content_hash = hash;
			var dialog = getGuidedFormDialog();
			if (dialog) {
				dialog.setAttribute('data-content-hash', hash);
			}
		}

		function saveGuidedForm() {
			var schema = guidedSchemas()[guidedState.type];
			if (!schema) {
				return;
			}
			var $root = $(getGuidedFormDialog());
			var fields = collectGuidedValues($root, schema.fields || []);
			var sourceMeta = collectVisibilityMeta($root);
			var $save = $('.seovaultseo-schema-guided-form-save');
			$save.prop('disabled', true).text(limits.schema_guided_saving || 'Saving…');
			clearGuidedErrors();

			var op = guidedState.instanceId ? 'seovaultseo_schema_instances_update' : 'seovaultseo_schema_instances_create';
			var payload = {
				type: guidedState.type,
				fields: fields,
				content_hash: currentContentHash(),
				source_meta: sourceMeta
			};
			if (guidedState.instanceId) {
				payload.instance_id = guidedState.instanceId;
			} else {
				payload.idempotency_key = 'guided-' + guidedState.type + '-' + String(Date.now());
			}

			$.post(ajaxUrl, {
				action: op,
				nonce: limits.schema_instances_nonce || '',
				post_id: currentPostId(),
				payload: JSON.stringify(payload)
			}).done(function(response) {
				if (!response || !response.success) {
					var message = (response && response.data && response.data.message) || limits.schema_guided_save_failed || 'Could not save the schema draft.';
					$('[data-guided-form-errors]').text(message).prop('hidden', false);
					applyGuidedFieldErrors((response && response.data && response.data.errors) || []);
					$save.prop('disabled', false).text(limits.schema_guided_save || 'Save draft');
					return;
				}
				guidedDirty = false;
				guidedState.instanceId = response.data.instance && response.data.instance.id ? response.data.instance.id : guidedState.instanceId;
				guidedState.values = (response.data.instance && response.data.instance.fields) || fields;
				guidedState.sourceMeta = sourceMeta;
				updateContentHash(response.data.content_hash);
				if (guidedState.instanceId) {
					setupInstanceCache[String(guidedState.instanceId)] = {
						instance_id: guidedState.instanceId,
						type: guidedState.type,
						fields: guidedState.values,
						source_meta: sourceMeta,
						first_field_key: ''
					};
				}
				setGuidedSummary(response.data.setup, response.data.readiness);
				if (response.data.setup && response.data.setup.blockers && response.data.setup.blockers.length) {
					applyGuidedFieldErrors(response.data.setup.blockers);
				}
				$('.seovaultseo-schema-guided-form-remove').prop('hidden', !guidedState.instanceId);
				$save.prop('disabled', false).text(limits.schema_guided_save || 'Save draft');
				refreshSchemaHealthAfterGuidedChange();
			}).fail(function() {
				$('[data-guided-form-errors]').text(limits.schema_guided_save_failed || 'Could not save the schema draft.').prop('hidden', false);
				$save.prop('disabled', false).text(limits.schema_guided_save || 'Save draft');
			});
		}

		function removeGuidedForm() {
			if (!guidedState.instanceId) {
				closeGuidedForm(true);
				return;
			}
			if (!window.confirm(limits.schema_guided_remove_confirm || 'Remove this schema draft from the post?')) {
				return;
			}
			$.post(ajaxUrl, {
				action: 'seovaultseo_schema_instances_delete',
				nonce: limits.schema_instances_nonce || '',
				post_id: currentPostId(),
				payload: JSON.stringify({
					instance_id: guidedState.instanceId,
					content_hash: currentContentHash()
				})
			}).done(function(response) {
				if (!response || !response.success) {
					var message = (response && response.data && response.data.message) || limits.schema_guided_save_failed || 'Could not save the schema draft.';
					$('[data-guided-form-errors]').text(message).prop('hidden', false);
					return;
				}
				updateContentHash(response.data && response.data.content_hash);
				guidedDirty = false;
				if (guidedState.instanceId) {
					delete setupInstanceCache[String(guidedState.instanceId)];
				}
				closeGuidedForm(true);
				refreshSchemaHealthAfterGuidedChange();
			});
		}

		function focusCustomJsonField() {
			closeAddPicker();
			var $details = $('#seovaultseo-schema-custom-json');
			if ($details.length && $details.get(0) && !$details.prop('open')) {
				$details.prop('open', true);
			}
			var $schema = $('details.seovault-ai-schema');
			if ($schema.length && $schema.get(0) && !$schema.prop('open')) {
				$schema.prop('open', true);
			}
			var $field = $('#seovault_schema_json');
			if (!$field.length) {
				return;
			}
			var el = $field.get(0);
			if (el && typeof el.scrollIntoView === 'function') {
				el.scrollIntoView({ block: 'center' });
			}
			$field.trigger('focus');
		}

		$(document).on('click', '.seovaultseo-schema-health-add', function(event) {
			event.preventDefault();
			openAddPicker();
		});

		$(document).on('click', '.seovaultseo-schema-add-picker-close', function(event) {
			event.preventDefault();
			closeAddPicker();
		});

		$(document).on('click', '.seovaultseo-schema-guided-form-close, .seovaultseo-schema-guided-form-cancel', function(event) {
			event.preventDefault();
			requestCloseGuidedForm();
		});

		$(document).on('click', '.seovaultseo-schema-guided-form-save', function(event) {
			event.preventDefault();
			saveGuidedForm();
		});

		$(document).on('click', '.seovaultseo-schema-guided-form-remove', function(event) {
			event.preventDefault();
			removeGuidedForm();
		});

		$(document).on('click', '#seovaultseo-schema-add-picker', function(event) {
			if (event.target === getAddPickerDialog()) {
				closeAddPicker();
			}
		});

		$(document).on('click', '#seovaultseo-schema-guided-form', function(event) {
			if (event.target === getGuidedFormDialog()) {
				requestCloseGuidedForm();
			}
		});

		$(document).on('input change', '#seovaultseo-schema-guided-form :input', function() {
			markGuidedDirty();
			var $form = $(this).closest('form.seovaultseo-schema-guided-form-engine');
			if ($form.length) {
				applyGuidedConditionals($form);
			}
		});

		$(document).on('click', '.seovaultseo-schema-guided-list-add', function(event) {
			event.preventDefault();
			var $list = $(this).closest('.seovaultseo-schema-guided-list, .seovaultseo-schema-guided-list-text');
			var $item = $list.find('.seovaultseo-schema-guided-list-item').last();
			if (!$item.length) {
				return;
			}
			var $clone = $item.clone(false);
			$clone.find(':input').val('');
			$item.after($clone);
			markGuidedDirty();
		});

		$(document).on('click', '.seovaultseo-schema-guided-list-remove', function(event) {
			event.preventDefault();
			var $list = $(this).closest('.seovaultseo-schema-guided-list, .seovaultseo-schema-guided-list-text');
			var $items = $list.find('.seovaultseo-schema-guided-list-item');
			if ($items.length <= 1) {
				$items.find(':input').val('');
			} else {
				$(this).closest('.seovaultseo-schema-guided-list-item').remove();
			}
			markGuidedDirty();
		});

		$(document).on('click', '.seovaultseo-schema-guided-media-select', function(event) {
			event.preventDefault();
			if (typeof wp === 'undefined' || !wp.media) {
				return;
			}
			var targetId = $(this).attr('data-guided-media-for');
			var frame = wp.media({
				title: limits.schema_guided_select_media || 'Select media',
				button: { text: limits.schema_guided_select_media || 'Select media' },
				multiple: false
			});
			frame.on('select', function() {
				var attachment = frame.state().get('selection').first().toJSON();
				var value = attachment && attachment.url ? attachment.url : (attachment && attachment.id ? String(attachment.id) : '');
				$('#' + targetId).val(value).trigger('change');
			});
			frame.open();
		});

		$(document).on('click', '.seovaultseo-schema-add-action', function(event) {
			event.preventDefault();
			var $btn = $(this);
			if ($btn.is(':disabled') || $btn.attr('aria-disabled') === 'true') {
				return;
			}
			var action = $btn.attr('data-schema-add-action') || '';
			var type = $btn.attr('data-schema-add-type') || '';
			var blockName = $btn.attr('data-schema-add-block') || '';

			if (action === 'focus_custom_json') {
				focusCustomJsonField();
				return;
			}
			if (action === 'insert_block') {
				closeAddPicker();
				var fallbackBlock = '';
				if (type === 'FAQPage') {
					fallbackBlock = limits.schema_faq_block_name || 'seovault/faq';
				} else if (type === 'HowTo') {
					fallbackBlock = limits.schema_howto_block_name || 'seovault/howto';
				}
				insertOrFocusSchemaBlock(blockName || fallbackBlock, type);
				return;
			}
			if (action === 'open_guided_form' || action === 'open_guided_placeholder') {
				closeAddPicker();
				openGuidedForm(type);
			}
		});

		/**
		 * Insert a SEOvault content block, or select the first existing one.
		 *
		 * @param {string} blockName Gutenberg block name (e.g. seovault/faq).
		 * @param {string} type      Schema type label for messaging.
		 */
		function insertOrFocusSchemaBlock(blockName, type, attributes) {
			blockName = String(blockName || '').trim();
			attributes = attributes && typeof attributes === 'object' ? attributes : null;
			var insertFailed =
				type === 'HowTo'
					? (limits.schema_howto_insert_failed || limits.schema_block_insert_failed || 'Could not insert the How-to block.')
					: type === 'FAQPage'
						? (limits.schema_faq_insert_failed || limits.schema_block_insert_failed || 'Could not insert the FAQ block.')
						: (limits.schema_block_insert_failed || 'Could not insert the schema block.');
			var classicEditor =
				type === 'HowTo'
					? (limits.schema_howto_classic_editor || limits.schema_block_classic_editor || 'The How-to block requires the block editor.')
					: type === 'FAQPage'
						? (limits.schema_faq_classic_editor || limits.schema_block_classic_editor || 'The FAQ block requires the block editor.')
						: (limits.schema_block_classic_editor || 'This schema block requires the block editor.');

			if (!blockName) {
				window.alert(insertFailed);
				return false;
			}

			if (!window.wp || !wp.data || !wp.blocks || typeof wp.blocks.createBlock !== 'function') {
				window.alert(classicEditor);
				return false;
			}

			var select = wp.data.select('core/block-editor');
			var dispatch = wp.data.dispatch('core/block-editor');
			if (!select || !dispatch || typeof select.getBlocks !== 'function') {
				window.alert(classicEditor);
				return false;
			}

			function findBlockClientId(blocks) {
				blocks = blocks || [];
				for (var i = 0; i < blocks.length; i++) {
					var block = blocks[i];
					if (!block) {
						continue;
					}
					if (block.name === blockName) {
						return block.clientId;
					}
					if (block.innerBlocks && block.innerBlocks.length) {
						var nested = findBlockClientId(block.innerBlocks);
						if (nested) {
							return nested;
						}
					}
				}
				return '';
			}

			var existingId = findBlockClientId(select.getBlocks());
			if (existingId && !attributes) {
				if (typeof dispatch.selectBlock === 'function') {
					dispatch.selectBlock(existingId);
				}
				scrollBlockIntoView(existingId);
				return true;
			}

			var created;
			try {
				created = attributes ? wp.blocks.createBlock(blockName, attributes) : wp.blocks.createBlock(blockName);
			} catch (err) {
				created = null;
			}
			if (!created || !created.clientId) {
				window.alert(insertFailed);
				return false;
			}

			if (typeof dispatch.insertBlock === 'function') {
				dispatch.insertBlock(created);
			} else if (typeof dispatch.insertBlocks === 'function') {
				dispatch.insertBlocks([created]);
			} else {
				window.alert(insertFailed);
				return false;
			}

			if (typeof dispatch.selectBlock === 'function') {
				dispatch.selectBlock(created.clientId);
			}
			scrollBlockIntoView(created.clientId);
			return true;
		}

		/**
		 * Best-effort scroll to a selected block in the canvas.
		 *
		 * @param {string} clientId Block client id.
		 */
		function scrollBlockIntoView(clientId) {
			window.setTimeout(function() {
				var el = document.querySelector('[data-block="' + clientId + '"]');
				if (el && typeof el.scrollIntoView === 'function') {
					el.scrollIntoView({ behavior: 'smooth', block: 'center' });
				}
			}, 50);
		}

		$(document).on('click', '.seovaultseo-schema-setup-complete, .seovaultseo-schema-entity-edit', function(event) {
			event.preventDefault();
			var $btn = $(this);
			var action = $btn.attr('data-schema-setup-action') || '';
			if (action === 'focus_custom_json') {
				focusCustomJsonField();
				return;
			}
			var instanceId = $btn.attr('data-instance-id') || '';
			var type = $btn.attr('data-schema-type') || '';
			var focusField = $btn.attr('data-focus-field') || '';
			if (!instanceId) {
				return;
			}
			var review = document.getElementById('seovaultseo-schema-review');
			if (review && review.open && typeof review.close === 'function') {
				review.close();
			}
			loadInstanceAndOpen(instanceId, type, focusField);
		});

		$(document).on('click', '.seovaultseo-schema-setup-remove', function(event) {
			event.preventDefault();
			var instanceId = $(this).attr('data-instance-id') || '';
			removeSetupInstance(instanceId);
		});

		$(window).on('beforeunload.seovaultGuidedForm', function() {
			if (guidedDirty && getGuidedFormDialog() && getGuidedFormDialog().open) {
				return limits.schema_guided_unsaved || 'You have unsaved schema changes.';
			}
		});

		// Deep-link / no-JS fallback bootstrap: open the guided form for Complete/Edit.
		if (limits.schema_setup_bootstrap && limits.schema_setup_bootstrap.instance_id) {
			var boot = limits.schema_setup_bootstrap;
			openGuidedForm(boot.type, {
				instanceId: boot.instance_id,
				values: boot.values || {},
				sourceMeta: boot.source_meta || {},
				focusField: boot.focus_field || ''
			});
		}

		// Gutenberg keeps classic side meta boxes mounted after a save. Refresh
		// from the saved graph once the non-autosave request completes.
		if (window.wp && wp.data && typeof wp.data.subscribe === 'function') {
			var wasSaving = false;
			var wasAutosaving = false;

			wp.data.subscribe(function() {
				var editor = wp.data.select('core/editor');
				if (!editor || typeof editor.isSavingPost !== 'function') {
					return;
				}

				var isSaving = editor.isSavingPost();
				var isAutosaving = typeof editor.isAutosavingPost === 'function' && editor.isAutosavingPost();
				var saveCompleted = wasSaving && !isSaving && !wasAutosaving;
				var saveSucceeded = typeof editor.didPostSaveRequestSucceed !== 'function' || editor.didPostSaveRequestSucceed();

				wasSaving = isSaving;
				wasAutosaving = isAutosaving;

				if (saveCompleted && saveSucceeded && getPanel().length) {
					checkSchema(true, false);
				}
			});
		}
	})();

	// ==========================================================================
	// Variable Picker
	// ==========================================================================

	var variables = (limits && limits.variables) ? limits.variables : null;

	if (variables && Object.keys(variables).length) {
		var $activeTarget = null;

		// Build picker dropdown once.
		var $dropdown = $('<div class="seovaultseo-var-picker-dropdown" style="display:none;"></div>');
		var searchPlaceholder = (limits && limits.search_variables) ? limits.search_variables : 'Search variables…';
		var $search = $('<input type="text" class="seovaultseo-var-picker-search" placeholder="' + searchPlaceholder.replace(/"/g, '&quot;') + '" />');
		var $list = $('<div class="seovaultseo-var-picker-list"></div>');

		$dropdown.append($search).append($list);
		$('body').append($dropdown);

		// Populate list items.
		function buildList(filter) {
			$list.empty();
			filter = (filter || '').toLowerCase();

			Object.keys(variables).forEach(function(id) {
				var v = variables[id];
				var label = v.name || id;
				if (filter && label.toLowerCase().indexOf(filter) === -1 && id.toLowerCase().indexOf(filter) === -1) {
					return;
				}
				var $item = $('<div class="seovaultseo-var-picker-item" data-variable="%' + v.variable + '%"></div>');
				$item.append('<span class="seovaultseo-var-picker-item-name">' + escapeHtml(label) + '</span>');
				$item.append('<span class="seovaultseo-var-picker-item-desc">' + escapeHtml(v.description || '') + '</span>');
				$list.append($item);
			});
		}

		buildList('');

		// Search filtering.
		$search.on('input', function() {
			buildList($(this).val());
		});

		// Item click — insert variable at cursor.
		$list.on('click', '.seovaultseo-var-picker-item', function() {
			var token = $(this).data('variable');
			if (!$activeTarget || !token) {
				return;
			}

			var $field = $activeTarget;
			var val = $field.val();
			var start = $field[0].selectionStart !== undefined ? $field[0].selectionStart : val.length;
			var end = $field[0].selectionEnd !== undefined ? $field[0].selectionEnd : val.length;

			$field.val(val.substring(0, start) + token + val.substring(end));
			$field.trigger('input');

			// Place cursor after inserted token.
			var newPos = start + token.length;
			$field.focus();
			if ($field[0].setSelectionRange) {
				$field[0].setSelectionRange(newPos, newPos);
			}

			closeDropdown();
		});

		// Open dropdown on button click.
		$('.seovaultseo-var-picker-btn').on('click', function(e) {
			e.preventDefault();
			var targetId = $(this).data('target');
			$activeTarget = $('#' + targetId);

			if ($dropdown.is(':visible') && $dropdown.data('current-target') === targetId) {
				closeDropdown();
				return;
			}

			$dropdown.data('current-target', targetId);

			var $btn = $(this);
			var offset = $btn.offset();
			var dropdownWidth = 280;
			var left = offset.left - dropdownWidth + $btn.outerWidth();
			var top = offset.top + $btn.outerHeight() + 4;

			// Keep within viewport.
			if (left < 0) {
				left = 0;
			}

			$dropdown.css({
				display: 'block',
				position: 'absolute',
				left: left + 'px',
				top: top + 'px',
				width: dropdownWidth + 'px',
				zIndex: 999999
			});

			$search.val('');
			buildList('');
			$search.focus();
		});

		// Close on outside click.
		$(document).on('click', function(e) {
			if (!$(e.target).closest('.seovaultseo-var-picker-dropdown, .seovaultseo-var-picker-btn').length) {
				closeDropdown();
			}
		});

		// Close on escape.
		$(document).on('keydown', function(e) {
			if (e.keyCode === 27 && $dropdown.is(':visible')) {
				closeDropdown();
			}
		});

		function closeDropdown() {
			$dropdown.hide();
			$dropdown.data('current-target', '');
		}
	}

	// ==========================================================================
	// Shared helpers — optimistic auto-values + SERP preview bridge
	// ==========================================================================

	var refreshSerpPreview = null;

	function stripEditorText(html) {
		if (!html) {
			return '';
		}
		var div = document.createElement('div');
		div.innerHTML = html;
		return (div.textContent || div.innerText || '').replace(/\s+/g, ' ').trim();
	}

	function truncateEditorText(text, maxLen) {
		if (!text || text.length <= maxLen) {
			return text || '';
		}
		return text.substring(0, maxLen - 3) + '...';
	}

	function buildOptimisticAutoValues(postTitle, postContent, postExcerpt) {
		var data = {};

		if (postTitle) {
			data.auto_title = postTitle;
		}

		var excerpt = (postExcerpt || '').trim();
		var source = excerpt || stripEditorText(postContent);
		if (source) {
			data.auto_description = truncateEditorText(source, 160);
		}

		return data;
	}

	// ==========================================================================
	// Real-Time Post Title Tracking
	// ==========================================================================

	(function() {
		var $seoTitleField = $('#seovault_seo_title');
		var $seoDescField = $('#seovault_seo_description');

		if (!$seoTitleField.length) {
			return;
		}

		var userEditedTitle = $seoTitleField.val().length > 0;
		$seoTitleField.on('input', function() {
			userEditedTitle = true;
		});

		var userEditedDesc = $seoDescField.val().length > 0;
		$seoDescField.on('input', function() {
			userEditedDesc = true;
		});

		function applyLiveAutoValues(data) {
			if (!data) {
				return;
			}

			if (!userEditedTitle && data.auto_title) {
				$seoTitleField.attr('placeholder', data.auto_title);
			}
			if (!userEditedDesc && data.auto_description) {
				$seoDescField.attr('placeholder', data.auto_description);
			}

			$(document).trigger('seovaultseo:auto-values-updated', [data]);

			if (refreshSerpPreview) {
				refreshSerpPreview();
			}
		}

		function resolveAutoAjax(postTitle, postContent, postExcerpt, postId) {
			$.ajax({
				url: limits.ajax_url,
				method: 'POST',
				data: {
					action: 'seovaultseo_resolve_auto',
					nonce: limits.nonce,
					post_id: postId,
					post_title: postTitle,
					post_content: postContent,
					post_excerpt: postExcerpt
				},
				success: function(response) {
					if (response.success && response.data) {
						applyLiveAutoValues(response.data);
					}
				}
			});
		}

		var debouncedResolveAjax = debounce(function(postTitle, postContent, postExcerpt, postId) {
			resolveAutoAjax(postTitle, postContent, postExcerpt, postId);
		}, 500);

		function handleEditorChange(postTitle, postContent, postExcerpt, postId) {
			applyLiveAutoValues(buildOptimisticAutoValues(postTitle, postContent, postExcerpt));
			debouncedResolveAjax(postTitle, postContent, postExcerpt, postId);
		}

		// --- Classic Editor: Listen to #title field ---
		var $titleField = $('#title');

		if ($titleField.length) {
			var resolveAuto = debounce(function() {
				var postTitle = $titleField.val();
				var postId = $('.seovault-ai-meta-box').data('post-id');
				var postExcerpt = $('#excerpt').val() || '';

				handleEditorChange(postTitle, '', postExcerpt, postId);
			}, 500);

			$titleField.on('input', resolveAuto);

			$('#excerpt').on('input', function() {
				var postId = $('.seovault-ai-meta-box').data('post-id');
				handleEditorChange($titleField.val(), getClassicContent(), $(this).val() || '', postId);
			});

			function getClassicContent() {
				if (typeof tinymce !== 'undefined') {
					var editor = tinymce.get('content');
					if (editor && !editor.isHidden()) {
						return editor.getContent();
					}
				}
				return $('#content').val() || '';
			}

			var debouncedContentResolve = debounce(function(content) {
				var postId = $('.seovault-ai-meta-box').data('post-id');
				handleEditorChange($titleField.val(), content, $('#excerpt').val() || '', postId);
			}, 500);

			$('#content').on('input', function() {
				debouncedContentResolve($(this).val() || '');
			});

			if (typeof tinymce !== 'undefined') {
				var bindTinyMCE = function(editor) {
					if (!editor || editor.id !== 'content') {
						return;
					}
					editor.on('input keyup', function() {
						debouncedContentResolve(editor.getContent());
					});
				};

				setTimeout(function() {
					var editor = tinymce.get('content');
					if (editor) {
						bindTinyMCE(editor);
					}
				}, 1000);

				$(document).on('tinymce-editor-init', function(event, editor) {
					bindTinyMCE(editor);
				});
			}
		}

		// --- Gutenberg Editor: Listen to core/editor title/content changes ---
		if (typeof wp !== 'undefined' && wp.data && wp.data.select) {
			var lastGutenbergTitle = '';
			var lastGutenbergContent = '';
			var lastGutenbergExcerpt = '';

			try {
				var select = wp.data.select('core/editor');

				if (select) {
					var debouncedGutenbergResolve = debounce(function(currentTitle, currentContent, currentExcerpt, postId) {
						resolveAutoAjax(currentTitle, currentContent, currentExcerpt, postId);
					}, 500);

					var checkGutenbergChanges = function() {
						var currentTitle = select.getEditedPostAttribute('title') || '';
						var currentContent = select.getEditedPostAttribute('content') || '';
						var currentExcerpt = select.getEditedPostAttribute('excerpt') || '';

						if (
							currentTitle !== lastGutenbergTitle ||
							currentContent !== lastGutenbergContent ||
							currentExcerpt !== lastGutenbergExcerpt
						) {
							lastGutenbergTitle = currentTitle;
							lastGutenbergContent = currentContent;
							lastGutenbergExcerpt = currentExcerpt;

							var postId = $('.seovault-ai-meta-box').data('post-id') || select.getCurrentPostId();

							applyLiveAutoValues(buildOptimisticAutoValues(currentTitle, currentContent, currentExcerpt));
							debouncedGutenbergResolve(currentTitle, currentContent, currentExcerpt, postId);
						}
					};

					wp.data.subscribe(checkGutenbergChanges);
				}
			} catch (e) {
				// Gutenberg not available or in classic editor — silently ignore.
			}
		}
	})();

	// ==========================================================================
	// SERP Preview — Live Google-style search result preview
	// ==========================================================================

	(function() {
		var $previewTitle = $('#seovaultseo-serp-preview-title');
		var $previewDesc  = $('#seovaultseo-serp-preview-description');
		var $seoTitle     = $('#seovault_seo_title');
		var $seoDesc      = $('#seovault_seo_description');

		if (!$previewTitle.length) {
			return;
		}

		var autoTitle = (limits && limits.auto_title) ? limits.auto_title : '';
		var autoDesc  = (limits && limits.auto_description) ? limits.auto_description : '';

		var titleMax = (limits && limits.title_limits) ? limits.title_limits.ideal_max : 60;
		var descMax  = (limits && limits.desc_limits) ? limits.desc_limits.ideal_max : 155;

		function updateSerpPreview() {
			var titleVal = $seoTitle.val();
			var descVal  = $seoDesc.val();
			var titlePlaceholder = $seoTitle.attr('placeholder') || '';
			var descPlaceholder = $seoDesc.attr('placeholder') || '';

			// Prefer live placeholders over page-load auto values when fields are empty.
			var displayTitle = titleVal || titlePlaceholder || autoTitle || '';
			var displayDesc  = descVal || descPlaceholder || autoDesc || '';

			$previewTitle.text(displayTitle);
			$previewDesc.text(displayDesc);

			if (displayTitle.length > titleMax) {
				$previewTitle.addClass('seovaultseo-serp-truncated');
			} else {
				$previewTitle.removeClass('seovaultseo-serp-truncated');
			}

			if (displayDesc.length > descMax) {
				$previewDesc.addClass('seovaultseo-serp-truncated');
			} else {
				$previewDesc.removeClass('seovaultseo-serp-truncated');
			}
		}

		refreshSerpPreview = updateSerpPreview;

		$seoTitle.on('input', updateSerpPreview);
		$seoDesc.on('input', updateSerpPreview);

		$(document).on('seovaultseo:auto-values-updated', function(event, data) {
			if (data && data.auto_title) {
				autoTitle = data.auto_title;
			}
			if (data && data.auto_description) {
				autoDesc = data.auto_description;
			}
			updateSerpPreview();
		});

		updateSerpPreview();
	})();

	// ==========================================================================
	// Focus Keyword Auto-Suggestion
	// ==========================================================================

	(function() {
		var $btn = $('#seovaultseo-suggest-keywords-btn');
		var $container = $('#seovaultseo-keyword-suggestions');
		var $keywordField = $('#seovault_focus_keyword');

		if (!$btn.length) {
			return;
		}

		var defaultBtnText = $btn.text();

		function getMetaBoxPostId() {
			var postId = $('.seovault-ai-meta-box').data('post-id');
			if (postId) {
				return postId;
			}

			if (typeof wp !== 'undefined' && wp.data && wp.data.select) {
				try {
					var select = wp.data.select('core/editor');
					if (select && select.getCurrentPostId) {
						return select.getCurrentPostId();
					}
				} catch (e) {
					// Ignore.
				}
			}

			return 0;
		}

		function getEditorTitle() {
			var postTitle = '';

			if ($('#title').length) {
				postTitle = $('#title').val() || '';
			}

			if (typeof wp !== 'undefined' && wp.data && wp.data.select) {
				try {
					var select = wp.data.select('core/editor');
					if (select) {
						postTitle = select.getEditedPostAttribute('title') || postTitle;
					}
				} catch (e) {
					// Ignore.
				}
			}

			return postTitle;
		}

		function getEditorContentForSuggest(postId) {
			// For saved posts, load content server-side via post_id to avoid
			// oversized Gutenberg block payloads breaking admin-ajax requests.
			if (postId) {
				return '';
			}

			if ($('#content').length) {
				if (typeof tinymce !== 'undefined') {
					var editor = tinymce.get('content');
					if (editor) {
						return editor.getContent();
					}
				}
				return $('#content').val() || '';
			}

			if (typeof wp !== 'undefined' && wp.data && wp.data.select) {
				try {
					var select = wp.data.select('core/editor');
					if (select) {
						return select.getEditedPostAttribute('content') || '';
					}
				} catch (e) {
					// Ignore.
				}
			}

			return '';
		}

		$btn.on('click', function() {
			var postId = getMetaBoxPostId();
			var postTitle = getEditorTitle();
			var postContent = getEditorContentForSuggest(postId);

			$btn.prop('disabled', true).text((limits && limits.analyzing) ? limits.analyzing : 'Analyzing...');
			$container.empty();

			var ajaxData = {
				action: 'seovaultseo_suggest_keywords',
				nonce: limits.nonce,
				post_id: postId,
				post_title: postTitle
			};

			if (postContent) {
				ajaxData.post_content = postContent;
			}

			$.ajax({
				url: limits.ajax_url,
				method: 'POST',
				dataType: 'json',
				data: ajaxData,
				success: function(response) {
					$btn.prop('disabled', false).text(defaultBtnText);

					if (response && response.success && response.data && Array.isArray(response.data.suggestions)) {
						renderSuggestions(response.data.suggestions);
					} else if (response && response.success) {
						renderSuggestions([]);
					} else {
						$container.html('<p class="description seovaultseo-keyword-error">' + (seovaultseo_metabox.keyword_load_failed || 'Could not load keyword suggestions.') + '</p>');
					}
				},
				error: function() {
					$btn.prop('disabled', false).text(defaultBtnText);
					$container.html('<p class="description seovaultseo-keyword-error">' + (seovaultseo_metabox.keyword_retry || 'Could not load keyword suggestions. Please try again.') + '</p>');
				}
			});
		});

		function renderSuggestions(suggestions) {
			$container.empty();

			if (!suggestions || !suggestions.length) {
				$container.append('<p class="description">' + (seovaultseo_metabox.no_keywords_found || 'No keywords found. Try adding more content.') + '</p>');
				return;
			}

			suggestions.forEach(function(keyword) {
				var $chip = $('<span class="seovaultseo-keyword-chip"></span>');
				$chip.text(keyword);
				$chip.on('click', function() {
					$keywordField.val(keyword).trigger('input');
				});
				$container.append($chip);
			});
		}
	})();

})(jQuery);
