/**
 * GoValid Form Builder — drag-and-drop form builder logic.
 *
 * Uses SortableJS for drag-and-drop, jQuery for DOM.
 *
 * @package GoValid_QR
 */

/* global jQuery, Sortable, govalidFormBuilder, govalidQR */
(function ($) {
	'use strict';

	var cfg    = govalidFormBuilder;
	var fields = []; // Array of field objects in canvas order.
	var activeFieldIndex = -1;
	var formId   = cfg.formId || 0;
	var formData = null; // Loaded form data.

	var fieldTypeIcons = {
		text:      'dashicons-editor-textcolor',
		textarea:  'dashicons-editor-paragraph',
		email:     'dashicons-email',
		phone:     'dashicons-phone',
		number:    'dashicons-calculator',
		url:       'dashicons-admin-links',
		select:    'dashicons-arrow-down-alt2',
		radio:     'dashicons-marker',
		checkbox:  'dashicons-yes',
		file:      'dashicons-upload',
		camera:    'dashicons-camera',
		date:      'dashicons-calendar-alt',
		time:      'dashicons-clock',
		heading:   'dashicons-heading',
		separator: 'dashicons-minus',
		whatsapp:  'dashicons-smartphone',
		linkedin:  'dashicons-linkedin',
		facebook:  'dashicons-facebook-alt',
		instagram: 'dashicons-instagram',
		twitter:   'dashicons-twitter',
		youtube:   'dashicons-video-alt3',
		tiktok:    'dashicons-controls-play',
		telegram:  'dashicons-airplane',
		orcid:            'dashicons-id-alt',
		scopus:           'dashicons-book-alt',
		google_scholar:   'dashicons-welcome-learn-more',
		researchgate:     'dashicons-clipboard',
		academia:         'dashicons-building',
		pubmed:           'dashicons-heart',
		web_of_science:   'dashicons-analytics',
		semantic_scholar: 'dashicons-search',
		gender:        'dashicons-admin-users',
		image_banner:  'dashicons-format-image',
		rating:        'dashicons-star-filled',
		signature:     'dashicons-edit-page',
		page_break:    'dashicons-editor-break'
	};

	var fieldTypeLabels = {
		text: 'Text', textarea: 'Textarea', email: 'Email', phone: 'Phone',
		number: 'Number', url: 'URL', select: 'Select', radio: 'Radio', checkbox: 'Checkbox',
		file: 'File Upload', camera: 'Camera / Selfie', date: 'Date', time: 'Time',
		heading: 'Heading', separator: 'Divider',
		whatsapp: 'WhatsApp', linkedin: 'LinkedIn', facebook: 'Facebook', instagram: 'Instagram',
		twitter: 'X / Twitter', youtube: 'YouTube', tiktok: 'TikTok', telegram: 'Telegram',
		orcid: 'ORCID', scopus: 'Scopus', google_scholar: 'Google Scholar',
		researchgate: 'ResearchGate', academia: 'Academia.edu', pubmed: 'PubMed',
		web_of_science: 'Web of Science', semantic_scholar: 'Semantic Scholar',
		gender: 'Sex / Gender', image_banner: 'Image Banner',
		rating: 'Rating',
		signature: 'Signature',
		page_break: 'Page Break'
	};

	// Non-input types that don't have label/required/placeholder.
	var decorativeTypes = ['heading', 'separator', 'image_banner', 'page_break'];
	// Types that need options textarea.
	var optionTypes = ['select', 'radio', 'checkbox', 'gender'];
	// Types with placeholder support.
	var placeholderTypes = ['text', 'textarea', 'email', 'phone', 'number', 'url',
		'whatsapp', 'linkedin', 'facebook', 'instagram', 'twitter', 'youtube',
		'tiktok', 'telegram', 'orcid', 'scopus', 'google_scholar', 'researchgate',
		'academia', 'pubmed', 'web_of_science', 'semantic_scholar'];

	// Default placeholders (examples) for each field type.
	var defaultPlaceholders = {
		text:             'e.g. John Doe',
		textarea:         'e.g. Enter your message here...',
		email:            'e.g. john@example.com',
		phone:            'e.g. +1 234 567 8900',
		number:           'e.g. 42',
		url:              'e.g. https://example.com',
		date:             'e.g. 2026-01-15',
		time:             'e.g. 14:30',
		whatsapp:         'e.g. +62 812 3456 7890',
		linkedin:         'e.g. https://linkedin.com/in/johndoe',
		facebook:         'e.g. https://facebook.com/johndoe',
		instagram:        'e.g. @johndoe',
		twitter:          'e.g. @johndoe',
		youtube:          'e.g. https://youtube.com/@channel',
		tiktok:           'e.g. @johndoe',
		telegram:         'e.g. @johndoe',
		orcid:            'e.g. 0000-0002-1825-0097',
		scopus:           'e.g. 57200000001',
		google_scholar:   'e.g. https://scholar.google.com/citations?user=XXXXX',
		researchgate:     'e.g. https://researchgate.net/profile/John-Doe',
		academia:         'e.g. https://university.academia.edu/JohnDoe',
		pubmed:           'e.g. https://pubmed.ncbi.nlm.nih.gov/?term=John+Doe',
		web_of_science:   'e.g. https://webofscience.com/wos/author/record/ABC-1234-2020',
		semantic_scholar: 'e.g. https://semanticscholar.org/author/12345678'
	};

	/* ------------------------------------------------------------------
	 * Init
	 * ----------------------------------------------------------------*/

	$(document).ready(function () {
		initPaletteDrag();
		initCanvasSortable();
		bindPropertyEvents();
		bindActions();
		initPaletteSearch();

		if (formId) {
			loadForm(formId);
		}
	});

	/* ------------------------------------------------------------------
	 * Palette search filter
	 * ----------------------------------------------------------------*/

	function initPaletteSearch() {
		$('#govalid-fb-palette-search').on('input', function () {
			var query = $(this).val().toLowerCase().trim();
			if (!query) {
				$('.govalid-fb-palette-item, .govalid-fb-palette-category').show();
				return;
			}
			$('.govalid-fb-palette-category').each(function () {
				var $cat = $(this);
				var catVisible = false;
				$cat.find('.govalid-fb-palette-item').each(function () {
					var match = ($(this).data('search') || '').indexOf(query) !== -1;
					$(this).toggle(match);
					if (match) catVisible = true;
				});
				$cat.toggle(catVisible);
			});
		});
	}

	/* ------------------------------------------------------------------
	 * Load existing form
	 * ----------------------------------------------------------------*/

	function loadForm(id) {
		$.ajax({
			url: cfg.restUrl + 'forms/' + id,
			method: 'GET',
			beforeSend: function (xhr) {
				xhr.setRequestHeader('X-WP-Nonce', cfg.nonce);
			},
			success: function (data) {
				formData = data;
				$('#govalid-fb-title').val(data.title || '');

				// Status badge.
				if (data.status === 'published') {
					$('#govalid-fb-status-badge')
						.text('Published')
						.removeClass('govalid-fb-badge--draft')
						.addClass('govalid-fb-badge--published');
				}

				// Settings.
				var s = data.settings || {};
				if (s.submit_text) {
					$('#govalid-fb-setting-submit-text').val(s.submit_text);
				}
				if (s.success_message) {
					$('#govalid-fb-setting-success-msg').val(s.success_message);
				}
				if (s.email_notification) {
					$('#govalid-fb-setting-email-notification').prop('checked', true);
					$('#govalid-fb-notif-email-wrap').show();
				}
				if (s.notification_email) {
					$('#govalid-fb-setting-notif-email').val(s.notification_email);
				}

				// Fields.
				if (data.fields && data.fields.length) {
					data.fields.forEach(function (f) {
						var opts = null;
						if (f.options && Array.isArray(f.options)) {
							opts = f.options;
						}
						fields.push({
							id:               f.id || 0,
							type:             f.type,
							label:            f.label,
							placeholder:      f.placeholder || '',
							required:         !!parseInt(f.required, 10),
							options:          opts,
							validation:       f.validation || null,
							conditional_logic: f.conditional_logic || null
						});
					});
					renderCanvas();
				}

				// Show shortcode bar if published.
				if (data.status === 'published') {
					showShortcodeBar(id);
				}
			}
		});
	}

	/* ------------------------------------------------------------------
	 * Palette drag (clone to canvas)
	 * ----------------------------------------------------------------*/

	function initPaletteDrag() {
		// Init Sortable on each category so only individual items are draggable.
		var cats = document.querySelectorAll('.govalid-fb-palette-category');
		cats.forEach(function (cat) {
			new Sortable(cat, {
				group: {
					name: 'form-fields',
					pull: 'clone',
					put: false
				},
				sort: false,
				draggable: '.govalid-fb-palette-item',
				animation: 150,
				onEnd: function () {
					// Palette items stay in palette; clone goes to canvas.
				}
			});
		});
	}

	/* ------------------------------------------------------------------
	 * Canvas sortable (receives from palette + reorders)
	 * ----------------------------------------------------------------*/

	function initCanvasSortable() {
		new Sortable(document.getElementById('govalid-fb-canvas'), {
			group: {
				name: 'form-fields',
				put: true
			},
			handle: '.govalid-fb-field-handle',
			animation: 200,
			ghostClass: 'sortable-ghost',
			chosenClass: 'sortable-chosen',
			onAdd: function (evt) {
				// A palette item was dropped onto canvas.
				var type = evt.item.getAttribute('data-type');

				// Remove the cloned palette element from canvas.
				evt.item.remove();

				var defaultOpts = null;
				if (type === 'gender') {
					defaultOpts = ['Male', 'Female'];
				} else if (optionTypes.indexOf(type) !== -1) {
					defaultOpts = ['Option 1', 'Option 2'];
				}

				var defaultValidation = null;
				if (type === 'separator') {
					defaultValidation = { line_style: 'solid', height: 16 };
				} else if (type === 'rating') {
					defaultValidation = { max_stars: 5 };
				} else if (type === 'signature') {
					defaultValidation = { canvas_height: 200 };
				} else if (type === 'page_break') {
					defaultValidation = { step_label: '' };
				}

				var newField = {
					id:               0,
					type:             type,
					label:            fieldTypeLabels[type] || type,
					placeholder:      defaultPlaceholders[type] || '',
					required:         false,
					options:          defaultOpts,
					validation:       defaultValidation,
					conditional_logic: null
				};

				// Insert at the correct position.
				fields.splice(evt.newIndex, 0, newField);
				renderCanvas();
				selectField(evt.newIndex);
			},
			onEnd: function (evt) {
				if (evt.from === evt.to && evt.oldIndex !== evt.newIndex) {
					// Reorder.
					var moved = fields.splice(evt.oldIndex, 1)[0];
					fields.splice(evt.newIndex, 0, moved);

					// Update active index.
					if (activeFieldIndex === evt.oldIndex) {
						activeFieldIndex = evt.newIndex;
					} else if (activeFieldIndex >= 0) {
						// Recalculate.
						activeFieldIndex = -1;
					}
					renderCanvas();
				}
			}
		});
	}

	/* ------------------------------------------------------------------
	 * Render canvas
	 * ----------------------------------------------------------------*/

	function renderCanvas() {
		var $canvas = $('#govalid-fb-canvas');
		var $empty  = $('#govalid-fb-empty-msg');

		// Remove existing field elements (keep empty msg).
		$canvas.find('.govalid-fb-field').remove();

		if (!fields.length) {
			$empty.show();
			return;
		}

		$empty.hide();

		fields.forEach(function (field, index) {
			// Special rendering for page_break.
			if (field.type === 'page_break') {
				var pbLabel = (field.validation && field.validation.step_label) || '';
				var $pbEl = $(
					'<div class="govalid-fb-field govalid-fb-field--pagebreak' +
					(index === activeFieldIndex ? ' govalid-fb-field--active' : '') +
					'" data-index="' + index + '">' +
					'<span class="govalid-fb-field-handle dashicons dashicons-move"></span>' +
					'<div class="govalid-fb-pagebreak-divider">' +
					'<hr /><span class="govalid-fb-pagebreak-label">' +
					escHtml(pbLabel || 'Page Break') + '</span><hr />' +
					'</div></div>'
				);
				$pbEl.on('click', function () { selectField(index); });
				$canvas.append($pbEl);
				return;
			}

			var icon = fieldTypeIcons[field.type] || 'dashicons-admin-generic';
			var required = field.required ? '<span class="govalid-fb-required-dot">*</span>' : '';

			var $el = $(
				'<div class="govalid-fb-field' + (index === activeFieldIndex ? ' govalid-fb-field--active' : '') + '" data-index="' + index + '">' +
					'<span class="govalid-fb-field-handle dashicons dashicons-move"></span>' +
					'<span class="govalid-fb-field-icon dashicons ' + icon + '"></span>' +
					'<div class="govalid-fb-field-info">' +
						'<span class="govalid-fb-field-label">' + escHtml(field.label) + ' ' + required + '</span>' +
						'<span class="govalid-fb-field-type">' + escHtml(fieldTypeLabels[field.type] || field.type) + '</span>' +
					'</div>' +
				'</div>'
			);

			$el.on('click', function () {
				selectField(index);
			});

			$canvas.append($el);
		});
	}

	/* ------------------------------------------------------------------
	 * Select / deselect field
	 * ----------------------------------------------------------------*/

	function selectField(index) {
		activeFieldIndex = index;
		renderCanvas();
		showProperties(index);
	}

	function showProperties(index) {
		var field = fields[index];
		if (!field) return;

		$('.govalid-fb-props-empty').hide();
		$('#govalid-fb-props-form').show();

		$('#govalid-fb-prop-label').val(field.label);
		$('#govalid-fb-prop-placeholder').val(field.placeholder || '');
		$('#govalid-fb-prop-required').prop('checked', field.required);

		// Show/hide placeholder.
		if (placeholderTypes.indexOf(field.type) !== -1) {
			$('#govalid-fb-prop-placeholder-wrap').show();
		} else {
			$('#govalid-fb-prop-placeholder-wrap').hide();
		}

		// Show/hide required (not for decorative).
		if (decorativeTypes.indexOf(field.type) !== -1) {
			$('#govalid-fb-prop-required-wrap').hide();
		} else {
			$('#govalid-fb-prop-required-wrap').show();
		}

		// Show/hide options.
		if (optionTypes.indexOf(field.type) !== -1) {
			$('#govalid-fb-prop-options-wrap').show();
			$('#govalid-fb-prop-options').val((field.options || []).join('\n'));
		} else {
			$('#govalid-fb-prop-options-wrap').hide();
		}

		// Separator settings.
		if (field.type === 'separator') {
			$('#govalid-fb-prop-separator-wrap').show();
			var v = field.validation || {};
			$('#govalid-fb-prop-separator-style').val(v.line_style || 'solid');
			$('#govalid-fb-prop-separator-height').val(v.height !== undefined ? v.height : 16);
		} else {
			$('#govalid-fb-prop-separator-wrap').hide();
		}

		// Image banner settings.
		if (field.type === 'image_banner') {
			$('#govalid-fb-prop-banner-wrap').show();
			var bv = field.validation || {};
			$('#govalid-fb-prop-banner-url').val(bv.image_url || '');
			if (bv.image_url) {
				$('#govalid-fb-prop-banner-img').attr('src', bv.image_url);
				$('#govalid-fb-prop-banner-preview').show();
			} else {
				$('#govalid-fb-prop-banner-preview').hide();
			}
		} else {
			$('#govalid-fb-prop-banner-wrap').hide();
		}

		// Rating settings.
		if (field.type === 'rating') {
			$('#govalid-fb-prop-rating-wrap').show();
			var rv = field.validation || {};
			$('#govalid-fb-prop-rating-max').val(rv.max_stars !== undefined ? rv.max_stars : 5);
		} else {
			$('#govalid-fb-prop-rating-wrap').hide();
		}

		// Signature settings.
		if (field.type === 'signature') {
			$('#govalid-fb-prop-signature-wrap').show();
			var sv = field.validation || {};
			$('#govalid-fb-prop-signature-height').val(sv.canvas_height !== undefined ? sv.canvas_height : 200);
		} else {
			$('#govalid-fb-prop-signature-wrap').hide();
		}

		// Page break settings.
		if (field.type === 'page_break') {
			$('#govalid-fb-prop-pagebreak-wrap').show();
			var pbv = field.validation || {};
			$('#govalid-fb-prop-pagebreak-label').val(pbv.step_label || '');
		} else {
			$('#govalid-fb-prop-pagebreak-wrap').hide();
		}

		// Conditional logic.
		var cl = field.conditional_logic;
		if (cl && cl.enabled) {
			$('#govalid-fb-prop-conditional-enabled').prop('checked', true);
			$('#govalid-fb-conditional-config').show();
			$('#govalid-fb-cond-action').val(cl.action || 'show');
			$('#govalid-fb-cond-operator').val(cl.operator || 'equals');
			$('#govalid-fb-cond-value').val(cl.value || '');
		} else {
			$('#govalid-fb-prop-conditional-enabled').prop('checked', false);
			$('#govalid-fb-conditional-config').hide();
		}

		// Populate conditional field dropdown (other fields).
		var $condField = $('#govalid-fb-cond-field');
		$condField.empty();
		fields.forEach(function (f, i) {
			if (i !== index && decorativeTypes.indexOf(f.type) === -1) {
				$condField.append('<option value="' + i + '">' + escHtml(f.label) + '</option>');
			}
		});
		if (cl && cl.field_index !== undefined) {
			$condField.val(cl.field_index);
		}
	}

	function hideProperties() {
		activeFieldIndex = -1;
		$('#govalid-fb-props-form').hide();
		$('.govalid-fb-props-empty').show();
		renderCanvas();
	}

	/* ------------------------------------------------------------------
	 * Property change handlers
	 * ----------------------------------------------------------------*/

	function bindPropertyEvents() {
		$('#govalid-fb-prop-label').on('input', function () {
			if (activeFieldIndex < 0) return;
			fields[activeFieldIndex].label = $(this).val();
			renderCanvas();
		});

		$('#govalid-fb-prop-placeholder').on('input', function () {
			if (activeFieldIndex < 0) return;
			fields[activeFieldIndex].placeholder = $(this).val();
		});

		$('#govalid-fb-prop-required').on('change', function () {
			if (activeFieldIndex < 0) return;
			fields[activeFieldIndex].required = $(this).is(':checked');
			renderCanvas();
		});

		$('#govalid-fb-prop-options').on('input', function () {
			if (activeFieldIndex < 0) return;
			fields[activeFieldIndex].options = $(this).val().split('\n').filter(function (o) { return o.trim(); });
		});

		$('#govalid-fb-prop-conditional-enabled').on('change', function () {
			if ($(this).is(':checked')) {
				$('#govalid-fb-conditional-config').show();
			} else {
				$('#govalid-fb-conditional-config').hide();
				if (activeFieldIndex >= 0) {
					fields[activeFieldIndex].conditional_logic = null;
				}
			}
		});

		// Update conditional logic on change.
		$('#govalid-fb-cond-action, #govalid-fb-cond-field, #govalid-fb-cond-operator, #govalid-fb-cond-value').on('change input', function () {
			if (activeFieldIndex < 0 || !$('#govalid-fb-prop-conditional-enabled').is(':checked')) return;
			fields[activeFieldIndex].conditional_logic = {
				enabled:     true,
				action:      $('#govalid-fb-cond-action').val(),
				field_index: parseInt($('#govalid-fb-cond-field').val(), 10),
				operator:    $('#govalid-fb-cond-operator').val(),
				value:       $('#govalid-fb-cond-value').val()
			};
		});

		$('#govalid-fb-remove-field').on('click', function () {
			if (activeFieldIndex < 0) return;
			if (!confirm(cfg.i18n.confirmRemove)) return;
			fields.splice(activeFieldIndex, 1);
			hideProperties();
		});

		// Separator settings.
		$('#govalid-fb-prop-separator-style, #govalid-fb-prop-separator-height').on('change input', function () {
			if (activeFieldIndex < 0 || fields[activeFieldIndex].type !== 'separator') return;
			fields[activeFieldIndex].validation = fields[activeFieldIndex].validation || {};
			fields[activeFieldIndex].validation.line_style = $('#govalid-fb-prop-separator-style').val();
			fields[activeFieldIndex].validation.height = parseInt($('#govalid-fb-prop-separator-height').val(), 10) || 16;
		});

		// Rating settings.
		$('#govalid-fb-prop-rating-max').on('change input', function () {
			if (activeFieldIndex < 0 || fields[activeFieldIndex].type !== 'rating') return;
			fields[activeFieldIndex].validation = fields[activeFieldIndex].validation || {};
			fields[activeFieldIndex].validation.max_stars = parseInt($(this).val(), 10) || 5;
		});

		// Signature settings.
		$('#govalid-fb-prop-signature-height').on('change input', function () {
			if (activeFieldIndex < 0 || fields[activeFieldIndex].type !== 'signature') return;
			fields[activeFieldIndex].validation = fields[activeFieldIndex].validation || {};
			fields[activeFieldIndex].validation.canvas_height = parseInt($(this).val(), 10) || 200;
		});

		// Page break settings.
		$('#govalid-fb-prop-pagebreak-label').on('input', function () {
			if (activeFieldIndex < 0 || fields[activeFieldIndex].type !== 'page_break') return;
			fields[activeFieldIndex].validation = fields[activeFieldIndex].validation || {};
			fields[activeFieldIndex].validation.step_label = $(this).val();
		});

		// Image banner URL.
		$('#govalid-fb-prop-banner-url').on('input', function () {
			if (activeFieldIndex < 0 || fields[activeFieldIndex].type !== 'image_banner') return;
			var url = $(this).val();
			fields[activeFieldIndex].validation = fields[activeFieldIndex].validation || {};
			fields[activeFieldIndex].validation.image_url = url;
			if (url) {
				$('#govalid-fb-prop-banner-img').attr('src', url);
				$('#govalid-fb-prop-banner-preview').show();
			} else {
				$('#govalid-fb-prop-banner-preview').hide();
			}
		});

		// Image banner — WP Media Library browse.
		$('#govalid-fb-prop-banner-browse').on('click', function () {
			var frame = wp.media({
				title: 'Select Image',
				multiple: false,
				library: { type: 'image' }
			});
			frame.on('select', function () {
				var attachment = frame.state().get('selection').first().toJSON();
				$('#govalid-fb-prop-banner-url').val(attachment.url).trigger('input');
			});
			frame.open();
		});

		// Email notification toggle.
		$('#govalid-fb-setting-email-notification').on('change', function () {
			$('#govalid-fb-notif-email-wrap').toggle($(this).is(':checked'));
		});
	}

	/* ------------------------------------------------------------------
	 * Save / Publish actions
	 * ----------------------------------------------------------------*/

	function bindActions() {
		$('#govalid-fb-save').on('click', function () {
			saveForm('draft');
		});

		$('#govalid-fb-publish').on('click', function () {
			saveForm('published');
		});

		$('#govalid-fb-copy-shortcode').on('click', function () {
			var text = $('#govalid-fb-shortcode-text').text();
			navigator.clipboard.writeText(text).then(function () {
				var $btn = $('#govalid-fb-copy-shortcode');
				$btn.text(cfg.i18n.copied);
				setTimeout(function () { $btn.text('Copy'); }, 1500);
			});
		});
	}

	function saveForm(status) {
		var title = $('#govalid-fb-title').val().trim() || cfg.i18n.untitled;

		var settings = {
			submit_text:        $('#govalid-fb-setting-submit-text').val(),
			success_message:    $('#govalid-fb-setting-success-msg').val(),
			email_notification: $('#govalid-fb-setting-email-notification').is(':checked'),
			notification_email: $('#govalid-fb-setting-notif-email').val()
		};

		var payload = {
			title:    title,
			status:   status,
			settings: settings,
			fields:   fields.map(function (f) {
				return {
					type:              f.type,
					label:             f.label,
					placeholder:       f.placeholder || '',
					required:          f.required,
					options:           f.options,
					validation:        f.validation,
					conditional_logic: f.conditional_logic
				};
			})
		};

		var $saveBtn = status === 'published' ? $('#govalid-fb-publish') : $('#govalid-fb-save');
		var origText = $saveBtn.html();
		$saveBtn.prop('disabled', true).html('<span class="dashicons dashicons-update govalid-spin" style="margin-top:4px;"></span> ' + cfg.i18n.saving);

		var method = formId ? 'PUT' : 'POST';
		var url    = formId ? cfg.restUrl + 'forms/' + formId : cfg.restUrl + 'forms';

		$.ajax({
			url: url,
			method: method,
			contentType: 'application/json',
			data: JSON.stringify(payload),
			beforeSend: function (xhr) {
				xhr.setRequestHeader('X-WP-Nonce', cfg.nonce);
			},
			success: function (data) {
				formId = data.id;
				formData = data;

				// Update URL without reload (so subsequent saves do PUT).
				if (window.history.replaceState) {
					window.history.replaceState(null, '', cfg.builderUrl + '&form_id=' + formId);
				}

				// Update status badge.
				if (status === 'published') {
					$('#govalid-fb-status-badge')
						.text('Published')
						.removeClass('govalid-fb-badge--draft')
						.addClass('govalid-fb-badge--published');
					showShortcodeBar(formId);
				}

				// Reload fields (to get server-assigned IDs).
				if (data.fields) {
					fields = data.fields.map(function (f) {
						var opts = null;
						if (f.options && Array.isArray(f.options)) {
							opts = f.options;
						}
						return {
							id:               f.id || 0,
							type:             f.type,
							label:            f.label,
							placeholder:      f.placeholder || '',
							required:         !!parseInt(f.required, 10),
							options:          opts,
							validation:       f.validation || null,
							conditional_logic: f.conditional_logic || null
						};
					});
					renderCanvas();
				}

				showNotice(status === 'published' ? cfg.i18n.published : cfg.i18n.saved, 'success');
			},
			error: function (xhr) {
				var msg = (xhr.responseJSON && xhr.responseJSON.message) || 'Error saving form.';
				showNotice(msg, 'error');
			},
			complete: function () {
				$saveBtn.prop('disabled', false).html(origText);
			}
		});
	}

	/* ------------------------------------------------------------------
	 * Helpers
	 * ----------------------------------------------------------------*/

	function showShortcodeBar(id) {
		$('#govalid-fb-shortcode-text').text('[govalid_form id="' + id + '"]');
		$('#govalid-fb-shortcode-bar').show();
	}

	function showNotice(message, type) {
		var $notice = $('<div class="notice notice-' + type + ' is-dismissible" style="max-width:none;"><p>' + escHtml(message) + '</p></div>');
		$('.govalid-form-builder-wrap .govalid-page-header').after($notice);
		setTimeout(function () { $notice.fadeOut(function () { $notice.remove(); }); }, 3000);
	}

	function escHtml(str) {
		var div = document.createElement('div');
		div.appendChild(document.createTextNode(str || ''));
		return div.innerHTML;
	}

})(jQuery);
