'use strict';

function stringifyChoiceOptions(options) {
	return JSON.stringify(normalizeChoiceOptions(options));
}

function choiceOptionsFromField(field) {
	if (field && field.options) {
		return normalizeChoiceOptions(field.options);
	}

	if (field && field.field_options) {
		return parseChoiceOptions(field.field_options);
	}

	return defaultChoiceOptions();
}

function sanitizeOptionText(value) {
	return String(value || '').replace(/[<>]/g, '').replace(/\s+/g, ' ').trim();
}

function sanitizeOptionValue(value, index) {
	var safe = String(value || '')
		.toLowerCase()
		.replace(/javascript\s*:/gi, '')
		.replace(/data\s*:/gi, '')
		.replace(/[^a-z0-9_-]+/g, '_')
		.replace(/_+/g, '_')
		.replace(/^_+|_+$/g, '');

	return safe || 'option_' + (index + 1);
}

function renderOptionsTraitInput(elInput, options) {
	var rows = normalizeChoiceOptions(options);
	var html = '<div class="subin-gjs-options-trait__rows">';

	rows.forEach(function (option) {
		html += '<div class="subin-gjs-options-trait__row" data-subin-option-row>';
		html += '<label><span>Label</span><input type="text" data-subin-option-label value="' + escapeHtml(option.label) + '" /></label>';
		html += '<label><span>Value</span><input type="text" data-subin-option-value value="' + escapeHtml(option.value) + '" /></label>';
		html += '<div class="subin-gjs-options-trait__actions">';
		html += '<button type="button" class="button button-small" data-subin-option-move="up">Up</button>';
		html += '<button type="button" class="button button-small" data-subin-option-move="down">Down</button>';
		html += '<button type="button" class="button-link-delete" data-subin-option-remove>Remove</button>';
		html += '</div>';
		html += '</div>';
	});

	html += '</div><button type="button" class="button button-small" data-subin-option-add>Add option</button>';
	elInput.innerHTML = html;
}

function renderFieldInnerStyleTraitInput(elInput) {
	elInput.innerHTML = [
		fieldStyleGroup('Label style', [
			fieldStyleControl('data-subin-label-color', 'Text color', 'color'),
			fieldStyleControl('data-subin-label-font-size', 'Font size', 'number', 'px'),
			fieldStyleSelect('data-subin-label-font-weight', 'Font weight', [
				['', 'Default'],
				['400', 'Regular'],
				['500', 'Medium'],
				['600', 'Semibold'],
				['700', 'Bold'],
			]),
			fieldStyleControl('data-subin-label-line-height', 'Line height', 'number'),
			fieldStyleControl('data-subin-label-margin-bottom', 'Margin bottom', 'number', 'px'),
		]),
		fieldStyleGroup('Input style', [
			fieldStyleControl('data-subin-input-bg-color', 'Background', 'color'),
			fieldStyleControl('data-subin-input-text-color', 'Text color', 'color'),
			fieldStyleControl('data-subin-input-border-color', 'Border color', 'color'),
			fieldStyleControl('data-subin-input-border-width', 'Border width', 'number', 'px'),
			fieldStyleControl('data-subin-input-border-radius', 'Border radius', 'number', 'px'),
			fieldStyleSelect('data-subin-input-box-shadow', 'Shadow', [
				['', 'Default'],
				['none', 'None'],
				['0 4px 12px rgba(15, 23, 42, 0.12)', 'Subtle'],
				['0 10px 24px rgba(15, 23, 42, 0.16)', 'Raised'],
				['0 18px 36px rgba(15, 23, 42, 0.22)', 'Strong'],
			]),
			fieldStyleControl('data-subin-input-padding', 'Padding', 'text', 'px'),
			fieldStyleControl('data-subin-input-min-height', 'Min height', 'number', 'px'),
		]),
		fieldStyleGroup('Placeholder style', [
			fieldStyleControl('data-subin-placeholder-color', 'Placeholder color', 'color'),
		]),
		fieldStyleGroup('Help text style', [
			fieldStyleControl('data-subin-help-color', 'Text color', 'color'),
			fieldStyleControl('data-subin-help-font-size', 'Font size', 'number', 'px'),
			fieldStyleControl('data-subin-help-line-height', 'Line height', 'number'),
			fieldStyleControl('data-subin-help-margin-top', 'Margin top', 'number', 'px'),
		]),
	].join('');
}

function fieldStyleGroup(label, controls) {
	return '<fieldset class="subin-gjs-field-style-trait__group"><legend>' + escapeHtml(label) + '</legend>' + controls.join('') + '</fieldset>';
}

function fieldStyleControl(name, label, type, suffix) {
	return '<label class="subin-gjs-field-style-trait__control"><span>' + escapeHtml(label) + '</span><input type="' + escapeHtml(type || 'text') + '" data-subin-field-style="' + escapeHtml(name) + '" ' + (type === 'number' ? 'min="0" step="1" ' : '') + ' />' + (suffix ? '<em>' + escapeHtml(suffix) + '</em>' : '') + '</label>';
}

function fieldStyleSelect(name, label, options) {
	var html = '<label class="subin-gjs-field-style-trait__control"><span>' + escapeHtml(label) + '</span><select data-subin-field-style="' + escapeHtml(name) + '">';
	options.forEach(function (option) {
		html += '<option value="' + escapeHtml(option[0]) + '">' + escapeHtml(option[1]) + '</option>';
	});
	return html + '</select></label>';
}

function sanitizeFieldStyleTraitValue(control) {
	var name = control.getAttribute('data-subin-field-style') || '';
	var value = String(control.value || '').trim();

	if (!value) {
		return '';
	}

	if (control.type === 'color' || /color/.test(name)) {
		return /^#[0-9a-f]{6}$/i.test(value) ? value : '';
	}

	if (control.type === 'number') {
		return String(Math.max(0, Math.min(999, parseInt(value, 10) || 0)));
	}

	if (name === 'data-subin-label-font-weight') {
		return ['400', '500', '600', '700'].indexOf(value) !== -1 ? value : '';
	}

	if (name === 'data-subin-input-box-shadow') {
		return [
			'none',
			'0 4px 12px rgba(15, 23, 42, 0.12)',
			'0 10px 24px rgba(15, 23, 42, 0.16)',
			'0 18px 36px rgba(15, 23, 42, 0.22)',
		].indexOf(value) !== -1 ? value : '';
	}

	if (name === 'data-subin-input-padding') {
		return sanitizeCssBoxValue(value);
	}

	if (name === 'data-subin-label-line-height' || name === 'data-subin-help-line-height') {
		return /^\d+(?:\.\d{1,2})?$/.test(value) ? value : '';
	}

	return '';
}

function sanitizeCssBoxValue(value) {
	var parts = String(value || '').trim().split(/\s+/).slice(0, 4);

	if (!parts.length) {
		return '';
	}

	for (var i = 0; i < parts.length; i++) {
		if (!/^\d+(?:\.\d+)?(?:px|rem|em|%)?$/.test(parts[i])) {
			return '';
		}
		if (/^\d/.test(parts[i]) && !/(px|rem|em|%)$/.test(parts[i])) {
			parts[i] += 'px';
		}
	}

	return parts.join(' ');
}

function readOptionsTraitInput(elInput) {
	var rows = elInput.querySelectorAll('[data-subin-option-row]');
	var options = [];

	rows.forEach(function (row, index) {
		var labelInput = row.querySelector('[data-subin-option-label]');
		var valueInput = row.querySelector('[data-subin-option-value]');
		var label = labelInput ? sanitizeOptionText(labelInput.value) : '';
		var value = valueInput ? sanitizeOptionValue(valueInput.value || label, index) : sanitizeOptionValue(label, index);

		if (label || value) {
			options.push({
				label: label || value,
				value: value,
			});
		}
	});

	return normalizeChoiceOptions(options);
}

function moveOptionRow(row, direction) {
	if (!row || !row.parentNode) {
		return;
	}

	if (direction === 'up' && row.previousElementSibling) {
		row.parentNode.insertBefore(row, row.previousElementSibling);
	}

	if (direction === 'down' && row.nextElementSibling) {
		row.parentNode.insertBefore(row.nextElementSibling, row);
	}
}

function nextChoiceOption(elInput) {
	var next = elInput.querySelectorAll('[data-subin-option-row]').length + 1;

	return {
		label: 'Option ' + next,
		value: 'option_' + next,
	};
}

function setChoiceOptionsAttribute(component, options) {
	addComponentAttribute(component, 'data-subin-options', stringifyChoiceOptions(options));
}

function lockFieldPreviewChildren(component) {
	var children = component && component.components ? component.components() : null;

	if (!children || !children.forEach) {
		return;
	}

	children.forEach(function (child) {
		lockPreviewComponent(child);
	});
}

function lockContentPreviewChildren(component) {
	var children = component && component.components ? component.components() : null;

	if (!children || !children.forEach) {
		return;
	}

	children.forEach(function (child) {
		lockPreviewComponent(child);
	});
}

function lockPreviewComponent(component) {
	if (!component || !component.set) {
		return;
	}

	component.set({
		selectable: false,
		hoverable: false,
		draggable: false,
		droppable: false,
		editable: false,
		layerable: false,
		stylable: false,
		traits: [],
	});

	if (component.components && component.components().forEach) {
		component.components().forEach(function (child) {
			lockPreviewComponent(child);
		});
	}
}

function configureFieldComponentTraits(component) {
	var attributes = component.getAttributes ? component.getAttributes() : {};
	if (!attributes['data-subin-label'] && attributes['data-subin-field-label']) {
		component.addAttributes({
			'data-subin-label': attributes['data-subin-field-label'],
		});
		attributes = component.getAttributes ? component.getAttributes() : attributes;
	}
	if (attributes['data-subin-field-type'] === 'consent' && !attributes['data-subin-consent-text']) {
		component.addAttributes({
			'data-subin-consent-text': attributes['data-subin-label'] || attributes['data-subin-field-label'] || defaultFieldLabel('consent'),
		});
		attributes = component.getAttributes ? component.getAttributes() : attributes;
	}
	if (isChoiceField(attributes['data-subin-field-type']) && !hasValidChoiceOptions(attributes['data-subin-options'])) {
		component.addAttributes({
			'data-subin-options': stringifyChoiceOptions(defaultChoiceOptions()),
		});
		attributes = component.getAttributes ? component.getAttributes() : attributes;
	}
	component.set('traits', subinFieldTraits(attributes));
	updateFieldComponentPreview(component);
	updateFieldLayerName(component);
}

function configureContentComponentTraits(component, fallbackType) {
	var attributes = component.getAttributes ? component.getAttributes() : {};
	var componentName = attributes['data-subin-component'] || fallbackType || subinComponentNameFromType(component.get ? component.get('type') : '');

	if (componentName === 'image-block') {
		updateImageBlockPreview(component);
		if (component.__subinImageHandler && component.off) {
			component.off('change:attributes:data-subin-media-url change:attributes:data-subin-media-alt change:attributes', component.__subinImageHandler);
		}
		component.__subinImageHandler = function () {
			updateImageBlockPreview(component);
		};
		component.on('change:attributes:data-subin-media-url change:attributes:data-subin-media-alt change:attributes', component.__subinImageHandler);
		return;
	}

	if (['text-block', 'html-block', 'notice', 'privacy'].indexOf(String(componentName || '')) === -1) {
		return;
	}

	if (componentName === 'html-block' && !attributes['data-subin-html-content']) {
		component.addAttributes({
			'data-subin-html-content': componentContentValue(component, componentName),
		});
		attributes = component.getAttributes ? component.getAttributes() : attributes;
	}

	if (!attributes['data-subin-content'] && componentName !== 'html-block') {
		component.addAttributes({
			'data-subin-content': componentContentValue(component, componentName),
		});
		attributes = component.getAttributes ? component.getAttributes() : attributes;
	}

	component.set('traits', subinContentTraits(componentName).concat(isBackgroundMediaComponentData(componentName) ? [backgroundMediaTrait()] : []));
	updateContentComponentPreview(component);
	if (component.__subinContentHandler && component.off) {
		component.off('change:attributes:data-subin-content change:attributes:data-subin-html-content change:attributes', component.__subinContentHandler);
	}
	component.__subinContentHandler = function () {
		updateContentComponentPreview(component);
	};
	component.on('change:attributes:data-subin-content change:attributes:data-subin-html-content change:attributes', component.__subinContentHandler);
}

function updateImageBlockPreview(component) {
	var attributes = component && component.getAttributes ? component.getAttributes() : {};
	var url = sanitizeMediaUrl(attributes['data-subin-media-url'] || '');
	var alt = String(attributes['data-subin-media-alt'] || '').replace(/<[^>]*>/g, '').trim();

	if (!component || !component.components) {
		return;
	}

	if (url) {
		component.components('<img class="subin-gjs-image-preview" src="' + escapeHtml(url) + '" alt="' + escapeHtml(alt) + '" />');
	} else {
		component.components('<div class="subin-gjs-image-placeholder" aria-hidden="true">Image</div>');
	}

	lockContentPreviewChildren(component);
}

function updateFieldComponentPreview(component) {
	var attributes = component.getAttributes ? component.getAttributes() : {};
	var fieldType = attributes['data-subin-field-type'] || 'text';
	var label = attributes['data-subin-label'] || attributes['data-subin-field-label'] || defaultFieldLabel(fieldType);
	var consentText = attributes['data-subin-consent-text'] || label;
	var placeholder = attributes['data-subin-placeholder'] || '';
	var helpText = attributes['data-subin-help-text'] || '';
	var required = attributes['data-subin-required'] === '1' || attributes['data-subin-required'] === true;
	var fieldKey = attributes['data-subin-field-key'] || '';
	var defaultValue = attributes['data-subin-default-value'] || '';
	var options = parseChoiceOptions(attributes['data-subin-options']);

	component.components(subinFieldPreview(fieldType, label, placeholder, required, fieldKey, options, helpText, consentText, defaultValue));
	lockFieldPreviewChildren(component);
	applyFieldInnerStyles(component);
	updateFieldLayerName(component);
	scheduleGrapesJSPreviewStateRefresh(window.SUBINGrapesJSEditor);
}

function applyFieldInnerStyles(component) {
	var el = component && component.view && component.view.el;
	var attributes = component && component.getAttributes ? component.getAttributes() : {};

	if (!el) {
		return;
	}

	applyInlineStyles(el.querySelectorAll('.subin-field-label'), fieldInnerStyleMap(attributes, 'label'));
	applyInlineStyles(el.querySelectorAll('.subin-field-input,.subin-field-textarea,.subin-choice-row input,.subin-field-checkbox-box'), fieldInnerStyleMap(attributes, 'input'));
	applyInlineStyles(el.querySelectorAll('.subin-field-placeholder'), fieldInnerStyleMap(attributes, 'placeholder'));
	applyInlineStyles(el.querySelectorAll('.subin-field-help'), fieldInnerStyleMap(attributes, 'help'));
}

function applyInlineStyles(nodes, styles) {
	if (!nodes || !nodes.forEach) {
		return;
	}

	nodes.forEach(function (node) {
		Object.keys(styles).forEach(function (property) {
			node.style[property] = styles[property];
		});
	});
}

function fieldInnerStyleMap(attributes, part) {
	var styles = {};

	if (part === 'label') {
		addStyleValue(styles, 'color', attributes['data-subin-label-color']);
		addPixelStyleValue(styles, 'fontSize', attributes['data-subin-label-font-size']);
		addStyleValue(styles, 'fontWeight', attributes['data-subin-label-font-weight']);
		addStyleValue(styles, 'lineHeight', attributes['data-subin-label-line-height']);
		addPixelStyleValue(styles, 'marginBottom', attributes['data-subin-label-margin-bottom']);
	} else if (part === 'input') {
		addStyleValue(styles, 'backgroundColor', attributes['data-subin-input-bg-color']);
		addStyleValue(styles, 'color', attributes['data-subin-input-text-color']);
		addStyleValue(styles, 'borderColor', attributes['data-subin-input-border-color']);
		addPixelStyleValue(styles, 'borderWidth', attributes['data-subin-input-border-width']);
		addPixelStyleValue(styles, 'borderRadius', attributes['data-subin-input-border-radius']);
		addStyleValue(styles, 'boxShadow', attributes['data-subin-input-box-shadow']);
		addStyleValue(styles, 'padding', sanitizeCssBoxValue(attributes['data-subin-input-padding']));
		addPixelStyleValue(styles, 'minHeight', attributes['data-subin-input-min-height']);
	} else if (part === 'placeholder') {
		addStyleValue(styles, 'color', attributes['data-subin-placeholder-color']);
	} else if (part === 'help') {
		addStyleValue(styles, 'color', attributes['data-subin-help-color']);
		addPixelStyleValue(styles, 'fontSize', attributes['data-subin-help-font-size']);
		addStyleValue(styles, 'lineHeight', attributes['data-subin-help-line-height']);
		addPixelStyleValue(styles, 'marginTop', attributes['data-subin-help-margin-top']);
	}

	return styles;
}

function addStyleValue(styles, property, value) {
	if (value) {
		styles[property] = value;
	}
}

function addPixelStyleValue(styles, property, value) {
	var number = parseInt(value, 10);
	if (!isNaN(number) && number >= 0) {
		styles[property] = number + 'px';
	}
}

function updateContentComponentPreview(component) {
	var attributes = component.getAttributes ? component.getAttributes() : {};
	var componentName = attributes['data-subin-component'] || subinComponentNameFromType(component.get ? component.get('type') : '');

	if (['text-block', 'html-block', 'notice', 'privacy'].indexOf(String(componentName || '')) === -1) {
		return;
	}

	component.components(contentPreviewHtml(contentComponentAttributeValue(attributes, componentName), componentName));
	lockContentPreviewChildren(component);
}

function contentComponentAttributeValue(attributes, componentName) {
	if (componentName === 'html-block') {
		return attributes['data-subin-html-content'] || attributes['data-subin-content'] || '';
	}

	return attributes['data-subin-content'] || '';
}

function updateSubmitButtonPreview(component) {
	var attributes = component.getAttributes ? component.getAttributes() : {};
	var text = attributes['data-subin-button-text'] || componentPlainText(component) || 'Submit';
	component.components(escapeHtml(text));
	scheduleGrapesJSPreviewStateRefresh(window.SUBINGrapesJSEditor);
}

function componentContentValue(component, componentName) {
	var attributes = component && component.getAttributes ? component.getAttributes() : {};
	if (componentName === 'html-block' && attributes['data-subin-html-content']) {
		return attributes['data-subin-html-content'];
	}

	if (attributes['data-subin-content']) {
		return attributes['data-subin-content'];
	}

	if (componentName === 'html-block') {
		return componentInnerHtml(component) || '<p>Safe HTML content</p>';
	}

	return componentPlainText(component) || defaultContentText(componentName);
}

function defaultContentText(componentName) {
	if (componentName === 'notice') {
		return 'Important information for visitors.';
	}
	if (componentName === 'privacy') {
		return 'We use your details only to respond to this request.';
	}
	if (componentName === 'html-block') {
		return '<p>Safe HTML content</p>';
	}

	return 'Add a short line of supporting text.';
}

function contentPreviewHtml(value, componentName) {
	var content = sanitizeContentAttribute(value, componentName);

	if (componentName === 'html-block') {
		return content || '<p>Safe HTML content</p>';
	}

	return '<p>' + escapeHtml(content || defaultContentText(componentName)) + '</p>';
}

function sanitizeContentAttribute(value, componentName) {
	var content = String(value || '');

	if (componentName === 'html-block') {
		return sanitizeSafeHtml(content);
	}

	return content.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
}

function sanitizeSafeHtml(value) {
	var wrapper = document.createElement('div');
	var allowedTags = ['P', 'BR', 'STRONG', 'EM', 'B', 'I', 'SPAN', 'DIV', 'SMALL', 'UL', 'OL', 'LI', 'A', 'HR'];
	var walker;
	var nodes = [];

	wrapper.innerHTML = String(value || '')
		.replace(/<\s*(script|iframe|object|embed|form|input|button|textarea|select|style)\b[^>]*>[\s\S]*?<\s*\/\s*\1\s*>/gi, '')
		.replace(/<\s*(script|iframe|object|embed|form|input|button|textarea|select|style)\b[^>]*\/?>/gi, '');

	walker = document.createTreeWalker(wrapper, NodeFilter.SHOW_ELEMENT, null);
	while (walker.nextNode()) {
		nodes.push(walker.currentNode);
	}

	nodes.forEach(function (node) {
		var attributes;
		var replacement;

		if (allowedTags.indexOf(node.tagName) === -1) {
			replacement = document.createTextNode(node.textContent || '');
			node.parentNode.replaceChild(replacement, node);
			return;
		}

		attributes = Array.prototype.slice.call(node.attributes || []);
		attributes.forEach(function (attribute) {
			var name = attribute.name.toLowerCase();
			var val = attribute.value || '';

			if (name.indexOf('on') === 0 || name === 'style' || name === 'srcdoc') {
				node.removeAttribute(attribute.name);
				return;
			}

			if (node.tagName === 'A' && ['href', 'title', 'target', 'rel'].indexOf(name) !== -1) {
				if (name === 'href' && /^(javascript|data)\s*:/i.test(val)) {
					node.removeAttribute(attribute.name);
				}
				return;
			}

			if (name !== 'class' && name !== 'aria-label') {
				node.removeAttribute(attribute.name);
			}
		});
	});

	return wrapper.innerHTML;
}

function componentInnerHtml(component) {
	var el = component && component.view && component.view.el ? component.view.el : null;
	if (!el) {
		return '';
	}

	return el.innerHTML || '';
}

function componentPlainText(component) {
	var text = '';
	var components = component.components && component.components();

	if (components && components.forEach) {
		components.forEach(function (child) {
			if (child.get && child.get('content')) {
				text += ' ' + child.get('content');
			} else if (child.toHTML) {
				text += ' ' + child.toHTML().replace(/<[^>]*>/g, ' ');
			}
		});
	}

	return text.replace(/\s+/g, ' ').trim();
}

function commonBoxStyleProps() {
	return [
		'width',
		'max-width',
		'min-height',
		'height',
		'margin',
		'padding',
		'background-color',
		'border-width',
		'border-style',
		'border-color',
		'border-radius',
		'box-shadow',
	];
}

function typographyStyleProps() {
	return [
		'font-size',
		'font-weight',
		'line-height',
		'text-align',
		'color',
	];
}

function addSubmissionInboxPreviewStyles(editor) {
	var canvas;
	var doc;
	var style;

	if (!editor) {
		return;
	}
	if (editor.on && !editor.__subinPreviewStylesFrameBound) {
		editor.__subinPreviewStylesFrameBound = true;
		editor.on('canvas:frame:load', function () {
			addSubmissionInboxPreviewStyles(editor);
		});
	}

	canvas = editor.Canvas;
	doc = canvas && canvas.getDocument ? canvas.getDocument() : null;
	if (!doc || !doc.head || !doc.createElement) {
		return;
	}

	style = doc.querySelector
		? doc.querySelector('style[data-subin-preview-styles]')
		: null;
	if (!style) {
		style = doc.createElement('style');
		style.setAttribute('data-subin-preview-styles', '1');
		doc.head.appendChild(style);
	}
	style.textContent = submissionInboxPreviewCss();
}

function submissionInboxPreviewCss() {
	return [
		'html,body{min-height:100%;background:transparent;}',
		'body{margin:0;padding:28px;}',
		'.subin-gjs-form-preview{box-sizing:border-box;display:grid;gap:18px;width:100%;max-width:720px;margin:0 auto;padding:30px;color:#172033;background:#fff;border:1px solid #d8e2ee;border-radius:20px;box-shadow:0 14px 36px rgba(15,23,42,.075);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;}',
		'.subin-gjs-form-preview *,.subin-gjs-form-preview *:before,.subin-gjs-form-preview *:after{box-sizing:border-box;}',
			'.subin-gjs-form-preview > .subin-gjs-row{display:flex;flex-wrap:wrap;align-items:flex-start;align-content:flex-start;gap:0 16px;min-height:44px;padding:6px;border:1px solid transparent;border-radius:8px;background:transparent;transition:border-color .15s ease,background-color .15s ease;}',
			'.subin-gjs-form-preview:not(.subin-gjs-runtime-preview) > .subin-gjs-row:hover{border-color:rgba(37,99,235,.22);background:rgba(37,99,235,.025);}',
			'.subin-gjs-form-preview > .subin-gjs-row:empty{border-style:dashed;border-color:rgba(37,99,235,.28);background:rgba(37,99,235,.025);}',
			'.subin-gjs-form-preview > .subin-gjs-row:empty:before{content:"Drop field or block here";display:flex;align-items:center;min-height:36px;color:#94a3b8;font-size:13px;}',
		'.subin-gjs-form-preview .subin-gjs-row > .subin-form-field,.subin-gjs-form-preview .subin-gjs-row > .subin-content-block{flex:0 1 auto;width:100%;max-width:100%;min-width:min(220px,100%);margin:0;position:static!important;transform:none!important;}',
		'.subin-gjs-form-preview .subin-gjs-row > .subin-divider-block,.subin-gjs-form-preview .subin-gjs-row > .subin-privacy-block,.subin-gjs-form-preview .subin-gjs-row > .subin-section-block,.subin-gjs-form-preview .subin-gjs-row > .subin-spacer-block,.subin-gjs-form-preview .subin-gjs-row > .subin-image-block{min-width:100%;}',
		'.subin-gjs-form-preview .subin-gjs-row > .subin-submit-button{flex:0 1 auto;width:auto;min-width:140px;max-width:100%;margin:0;position:static!important;transform:none!important;}',
		'.subin-gjs-form-preview .subin-gjs-row > .subin-form-field *,.subin-gjs-form-preview .subin-gjs-row > .subin-content-block *,.subin-gjs-form-preview .subin-gjs-row > .subin-submit-button *{pointer-events:none;}',
		'.subin-gjs-form-preview.subin-gjs-runtime-preview .subin-gjs-row > .subin-form-field input,.subin-gjs-form-preview.subin-gjs-runtime-preview .subin-gjs-row > .subin-form-field textarea,.subin-gjs-form-preview.subin-gjs-runtime-preview .subin-gjs-row > .subin-form-field select,.subin-gjs-form-preview.subin-gjs-runtime-preview .subin-gjs-row > .subin-form-field label,.subin-gjs-form-preview.subin-gjs-runtime-preview .subin-gjs-row > .subin-submit-button{pointer-events:auto;}',
		'.gjs-placeholder{outline:2px solid rgba(37,99,235,.55)!important;outline-offset:2px!important;background:rgba(37,99,235,.10)!important;}',
		'.subin-gjs-form-preview .subin-content-block{min-width:0;color:#1f2933;line-height:1.55;overflow-wrap:anywhere;}',
		'.subin-gjs-form-preview .subin-content-block>:first-child{margin-top:0;}',
		'.subin-gjs-form-preview .subin-content-block>:last-child{margin-bottom:0;}',
		'.subin-gjs-form-preview .subin-form-title{margin:0 0 8px;font-size:24px;line-height:1.25;font-weight:700;letter-spacing:0;}',
		'.subin-gjs-form-preview .subin-form-description{margin:0;color:#52606d;line-height:1.5;}',
		'.subin-gjs-form-preview .subin-gjs-preview-state-message{margin:0;padding:12px 14px;border-radius:6px;font-size:14px;line-height:1.45;}',
		'.subin-gjs-form-preview .subin-gjs-preview-state-message--error{color:#8a2424;background:#fcebea;border:1px solid #f5c2c7;}',
		'.subin-gjs-form-preview .subin-gjs-preview-state-message--success{color:#135e2f;background:#edf7ed;border:1px solid #b7e2c2;}',
		'.subin-gjs-form-preview .subin-form-field{display:flex;flex-direction:column;gap:7px;min-width:0;}',
		'.subin-gjs-form-preview .subin-field-label{display:block;margin:0;font-weight:600;color:#1f2933;line-height:1.35;overflow-wrap:anywhere;}',
		'.subin-gjs-form-preview .subin-field-required{color:#b32d2e;}',
		'.subin-gjs-form-preview .subin-field-input,.subin-gjs-form-preview .subin-field-textarea{width:100%;min-height:44px;padding:10px 12px;color:#1f2933;background:#fff;border:1px solid #bcccdc;border-radius:6px;font:inherit;line-height:1.4;box-shadow:none;}',
		'.subin-gjs-form-preview .subin-form-field.is-error .subin-field-input,.subin-gjs-form-preview .subin-form-field.is-error .subin-field-textarea,.subin-gjs-form-preview .subin-form-field.is-error .subin-choice-row input,.subin-gjs-form-preview .subin-form-field.is-error .subin-field-checkbox-box{border-color:#b32d2e;box-shadow:0 0 0 1px rgba(179,45,46,.16);}',
		'.subin-gjs-form-preview .subin-field-input:disabled,.subin-gjs-form-preview .subin-field-textarea:disabled{opacity:1;color:#1f2933;background:#fff;}',
		'.subin-gjs-form-preview .subin-field-textarea{min-height:124px;}',
		'.subin-gjs-form-preview .subin-field-help{margin:0;color:#52606d;font-size:14px;line-height:1.45;}',
		'.subin-gjs-form-preview .subin-gjs-preview-field-error{margin:0;color:#b32d2e;font-size:13px;line-height:1.4;}',
		'.subin-gjs-form-preview .subin-field-placeholder{color:#8c8f94;}',
		'.subin-gjs-form-preview .subin-checkbox-field{display:flex;align-items:flex-start;gap:8px;color:#1f2933;line-height:1.45;}',
		'.subin-gjs-form-preview .subin-field-checkbox-box{flex:0 0 auto;width:16px;height:16px;margin-top:3px;background:#fff;border:1px solid #8c8f94;border-radius:3px;}',
		'.subin-gjs-form-preview .subin-field-options{display:grid;gap:8px;margin-top:2px;}',
		'.subin-gjs-form-preview .subin-choice-row{display:flex;align-items:center;gap:8px;margin:0;color:#1f2933;line-height:1.4;}',
		'.subin-gjs-form-preview .subin-choice-row input{flex:0 0 auto;width:16px;height:16px;margin:0;}',
		'.subin-gjs-form-preview .subin-choice-row input:disabled{opacity:1;}',
		'.subin-gjs-form-preview .subin-field-choice{display:flex;align-items:center;gap:8px;color:#1f2933;}',
		'.subin-gjs-form-preview .subin-field-choice-control{width:16px;height:16px;background:#fff;border:1px solid #8c8f94;border-radius:999px;}',
		'.subin-gjs-form-preview .subin-field-choice-control--checkbox{border-radius:3px;}',
		'.subin-gjs-form-preview .subin-hidden-field-preview{padding:9px 11px;color:#52606d;background:#f8fafc;border:1px dashed #bcccdc;border-radius:6px;font-size:13px;}',
		'.subin-gjs-form-preview .subin-submit-button{display:inline-flex;align-items:center;justify-content:center;min-height:44px;max-width:100%;padding:10px 18px;color:#fff;background:#1d4ed8;border:0;border-radius:6px;font:inherit;font-weight:600;line-height:1.3;text-align:center;cursor:pointer;}',
		'.subin-gjs-form-preview .subin-notice-block{padding:12px 14px;background:#f0f6fc;border:1px solid #c5d9ed;border-radius:6px;color:#17324d;}',
		'.subin-gjs-form-preview .subin-privacy-block{margin:0;color:#52606d;font-size:14px;}',
		'.subin-gjs-form-preview .subin-divider-block{margin:0;}',
		'.subin-gjs-form-preview .subin-divider-block hr{border:0;border-top:1px solid currentColor;opacity:.22;}',
		'.subin-gjs-form-preview .subin-section-block{padding:18px;margin:0;border:1px dashed #bcccdc;border-radius:8px;}',
		'.subin-gjs-form-preview .subin-gjs-spacer-preview,.subin-gjs-form-preview .subin-spacer-block{min-height:32px;}',
		'.subin-gjs-form-preview .subin-gjs-image-placeholder{display:flex;align-items:center;justify-content:center;min-height:120px;color:#52606d;background:#f8fafc;border:1px dashed #bcccdc;border-radius:6px;}',
		'.subin-gjs-form-preview .subin-gjs-image-preview{display:block;width:100%;max-width:100%;height:auto;border-radius:inherit;}',
		'.subin-gjs-form-preview .subin-gjs-logic-target{position:relative!important;}',
		'.subin-gjs-form-preview .subin-gjs-logic-target:after{position:absolute;z-index:20;top:-9px;right:8px;padding:3px 7px;border:1px solid #93c5fd;border-radius:999px;background:#eff6ff;color:#1d4ed8;content:"Logic " attr(data-subin-logic-marker);font-size:10px;font-weight:700;line-height:1.2;pointer-events:none;}',
		'.subin-gjs-form-preview .subin-gjs-logic-target--invalid:after{border-color:#fca5a5;background:#fef2f2;color:#b91c1c;}',
		'.subin-gjs-form-preview .subin-gjs-logic-hidden{display:none!important;}',
		'.subin-gjs-form-preview.subin-gjs-runtime-preview .subin-gjs-logic-target:after{display:none;}',
	].join('\n');
}

function buildSubmissionInboxBlocks() {
	return [
		subinFieldBlock('subin-field-text', 'Text field', 'text', 'text_field', 'Text field', 'Your answer', false, 'T'),
		subinFieldBlock('subin-field-email', 'Email field', 'email', 'email', 'Email address', 'you@example.com', false, '@'),
		subinFieldBlock('subin-field-phone', 'Phone field', 'phone', 'phone', 'Phone number', '(555) 010-1234', false, 'Ph'),
		subinFieldBlock('subin-field-date', 'Date field', 'date', 'date', 'Date', '', false, 'D'),
		subinFieldBlock('subin-field-textarea', 'Textarea field', 'textarea', 'message', 'Message', 'Write your message...', false, 'Tx'),
		subinFieldBlock('subin-field-select', 'Select field', 'select', 'select_field', 'Select field', 'Choose an option', false, 'v'),
		subinFieldBlock('subin-field-radio', 'Radio field', 'radio', 'radio_field', 'Radio field', '', false, 'O'),
		subinFieldBlock('subin-field-checkbox', 'Checkbox field', 'checkbox', 'checkbox_field', 'Checkbox field', '', false, 'X'),
		subinFieldBlock('subin-field-consent', 'Consent checkbox', 'consent', 'consent', 'I agree to be contacted about this request.', '', true, 'Ok'),
		subinFieldBlock('subin-field-hidden', 'Hidden field', 'hidden', 'hidden_field', 'Hidden field', '', false, 'H'),
		subinContentBlock('subin-text-block', 'Text block', 'subin-text-block', 'text-block', '<p>Add a short line of supporting text.</p>', 'Content', 'Aa'),
		subinContentBlock('subin-html-block', 'HTML block', 'subin-html-block', 'html-block', '<p><strong>Safe HTML content</strong></p>', 'Content', '</>'),
		subinContentBlock('subin-notice-block', 'Notice block', 'subin-notice-block', 'notice', '<p>Important information for visitors.</p>', 'Content', '!'),
		subinContentBlock('subin-divider-block', 'Divider', 'subin-divider-block', 'divider', '<hr />', 'Content', '-'),
		subinContentBlock('subin-privacy-block', 'Privacy note', 'subin-privacy-block', 'privacy', '<p>We use your details only to respond to this request.</p>', 'Content', 'i'),
		subinSubmitButtonBlock(),
		subinContentBlock('subin-section-block', 'Section', 'subin-section-block', 'section', '<p>Section content</p>', 'Layout', '[]'),
		subinContentBlock('subin-spacer-block', 'Spacer', 'subin-spacer-block', 'spacer', '<div class="subin-gjs-spacer-preview" aria-hidden="true"></div>', 'Layout', 'Sp'),
		subinContentBlock('subin-image-block', 'Image block', 'subin-image-block', 'image-block', '<div class="subin-gjs-image-placeholder" aria-hidden="true">Image</div>', 'Layout', 'Im'),
	];
}

function loadExistingSubmissionInboxForm(editor) {
	var formData = readSubmissionInboxFormData();

	if (!formData) {
		editor.__subinProjectLoadState = 'unavailable';
		return;
	}

	editor.__subinProjectLoadState = 'generated';
	addSubmissionInboxPreviewStyles(editor);

	if (hasSavedGrapesJSProject(formData.grapesjs_project)) {
		try {
			editor.loadProjectData(normalizeSubmissionInboxRowsInProject(formData.grapesjs_project));
			addSubmissionInboxPreviewStyles(editor);
			configureSubmissionInboxStylableComponents(editor, formData);
			editor.__subinProjectLoadState = 'saved';
			return;
		} catch (error) {
			editor.__subinProjectLoadState = 'recovery';
		}
	}

	prepareFormPreviewWrapper(editor, formData);
	editor.setComponents(buildSubmissionInboxFormComponents(formData));
	configureSubmissionInboxStylableComponents(editor, formData);
	applyFormWrapperStyle(editor, formData);
}

function hasSavedGrapesJSProject(project) {
	return project && typeof project === 'object' && !Array.isArray(project) && Object.keys(project).length > 0;
}

function initGrapesJSSave(editor) {
	var formData = readSubmissionInboxFormData();
	var saveButton = document.querySelector('[data-subin-grapesjs-save]');
	var status = document.querySelector('[data-subin-grapesjs-save-status]');
	var identitiesChanged;
	var initialMessage;
	var initialPhase;
	var loadState;
	var saveState;

	if (!saveButton || !formData || !formData.ajax || editor.__subinSaveState) {
		return;
	}

	identitiesChanged = ensureSubmissionInboxFieldIdentities(editor);
	loadState = editor.__subinProjectLoadState || 'saved';
	initialPhase = loadState === 'recovery'
		? 'error'
		: (
			identitiesChanged || loadState === 'generated'
				? 'dirty'
				: 'saved'
		);
	initialMessage = loadState === 'recovery'
		? 'Saved project could not be loaded. Review the fallback and save to replace it.'
		: '';
		saveState = {
			activeRequest: 0,
			autoSaveTimer: 0,
			button: saveButton,
			changeVersion: initialPhase === 'saved' ? 0 : 1,
			editor: editor,
			formData: formData,
			phase: initialPhase,
			publishedDirty: !!formData.is_dirty || initialPhase === 'dirty',
			publishedRevision: Math.max(0, parseInt(formData.published_revision || 0, 10) || 0),
			requestSequence: 0,
		revision: submissionInboxInitialRevision(formData),
		savedVersion: 0,
		status: status,
		suppressDirty: 0,
	};
	editor.__subinSaveState = saveState;

	editor.on('component:add', function (component) {
		if (!isSubmissionInboxFieldComponent(component)) {
			return;
		}

		ensureSubmissionInboxFieldIdentities(editor, component);
		markGrapesJSSaveDirty(saveState);
	});

	editor.on('updateBefore', function () {
		markGrapesJSSaveDirty(saveState);
	});

	saveButton.addEventListener('click', function () {
		saveGrapesJSProject(editor, formData, saveButton, saveState);
	});

	window.addEventListener('beforeunload', function (event) {
		if (!grapesJSSaveHasUnsavedChanges(saveState)) {
			return;
		}

		event.preventDefault();
		event.returnValue = '';
	});

	if (editor.clearDirtyCount) {
		editor.clearDirtyCount();
	}

		setGrapesJSSaveState(saveState, initialPhase, initialMessage);
		if (initialPhase === 'dirty') {
			scheduleGrapesJSAutoSave(saveState);
		}
	}

	function compactSubmissionInboxProjectStyles(project) {
		var entries = [];
		var byIdentity = Object.create(null);

		if (!project || typeof project !== 'object' || !Array.isArray(project.styles)) {
			return project;
		}

		project.styles.forEach(function (rule, index) {
			var entry;
			var identity;
			var key;

			if (!rule || typeof rule !== 'object' || Array.isArray(rule)) {
				entries.push({index: index, rule: rule});
				return;
			}

			identity = {};
			Object.keys(rule).sort().forEach(function (name) {
				identity[name] = normalizeSubmissionInboxStyleIdentity(rule[name]);
			});
			key = JSON.stringify(identity);
			entry = byIdentity[key];

			if (!entry) {
				entry = {index: index, rule: Object.assign({}, rule)};
				byIdentity[key] = entry;
				entries.push(entry);
				return;
			}

			entry.index = index;
			entry.rule = Object.assign({}, rule);
		});

		entries.sort(function (left, right) {
			return left.index - right.index;
		});

		return Object.assign({}, project, {
			styles: entries.map(function (entry) {
				return entry.rule;
			}),
		});
	}

	function normalizeSubmissionInboxStyleIdentity(value) {
		var normalized;

		if (Array.isArray(value)) {
			return value.map(normalizeSubmissionInboxStyleIdentity);
		}
		if (!value || typeof value !== 'object') {
			return value;
		}

		normalized = {};
		Object.keys(value).sort().forEach(function (key) {
			normalized[key] = normalizeSubmissionInboxStyleIdentity(value[key]);
		});
		return normalized;
	}

function saveGrapesJSProject(editor, formData, button, saveState) {
	var ajax = formData.ajax || {};
	var body = new window.URLSearchParams();
	var project;
	var requestId;
	var requestVersion;

		if (!saveState || saveState.phase === 'saving' || saveState.phase === 'conflict') {
			return window.Promise.resolve(null);
		}

	ensureSubmissionInboxFieldIdentities(editor);
	requestVersion = saveState.changeVersion;

		if (requestVersion === saveState.savedVersion && saveState.phase === 'saved') {
			return window.Promise.resolve(null);
	}

	try {
		project = editor.getProjectData();
		if (typeof window.submissionInboxLogicProjectData === 'function') {
			project = window.submissionInboxLogicProjectData(editor, project);
		}
		project = compactSubmissionInboxProjectStyles(project);
	} catch (error) {
			setGrapesJSSaveState(saveState, 'error', 'Unable to load editor project data.');
			return window.Promise.resolve(null);
	}

	body.set('action', ajax.action || 'subin_save_grapesjs_project');
	body.set('form_id', String(formData.id || ''));
	body.set('nonce', ajax.nonce || '');
		body.set('revision', saveState.revision);
		body.set('published_revision', String(saveState.publishedRevision || 0));
		body.set('project_json', JSON.stringify(project));

	requestId = ++saveState.requestSequence;
	saveState.activeRequest = requestId;
	setGrapesJSSaveState(saveState, 'saving');

		return window.fetch(ajax.url || window.ajaxurl, {
		method: 'POST',
		credentials: 'same-origin',
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
		},
		body: body.toString(),
	})
		.then(function (response) {
			return grapesJSResponsePayload(response).then(function (payload) {
				var data = grapesJSAjaxData(payload);
				var directSuccess = payload &&
					payload.success === undefined &&
					(
						submissionInboxOwn(data, 'project') ||
						submissionInboxOwn(data, 'revision') ||
						submissionInboxOwn(data, 'message')
					);
				var success = payload && (payload.success === true || directSuccess);

				if (!response.ok || !success) {
					throw grapesJSSaveError(response, payload);
				}

				return {
					data: data,
					payload: payload,
				};
			});
		})
		.then(function (responseData) {
			var data;
			var hasNewerChanges;
			var canonicalApplied = true;

			if (saveState.activeRequest !== requestId) {
				return;
			}

			data = responseData.data || {};

				if (submissionInboxOwn(data, 'revision')) {
					setSubmissionInboxRevision(saveState, data.revision);
				}
				if (submissionInboxOwn(data, 'published_revision')) {
					saveState.publishedRevision = Math.max(0, parseInt(data.published_revision || 0, 10) || 0);
					saveState.formData.published_revision = saveState.publishedRevision;
				}
				if (submissionInboxOwn(data, 'is_dirty')) {
					saveState.publishedDirty = !!data.is_dirty;
					saveState.formData.is_dirty = saveState.publishedDirty;
				}

			hasNewerChanges = saveState.changeVersion !== requestVersion;

			if (
				submissionInboxOwn(data, 'project') &&
				data.project &&
				typeof data.project === 'object' &&
				!Array.isArray(data.project)
			) {
				canonicalApplied = hasNewerChanges
					? reconcileSubmissionInboxCanonicalFieldIdentities(
						editor,
						data.project,
						saveState
					)
					: applySubmissionInboxCanonicalProject(
						editor,
						data.project,
						formData,
						saveState
					);
			}

			saveState.savedVersion = requestVersion;

			if (hasNewerChanges) {
				setGrapesJSSaveState(
					saveState,
					'dirty',
					canonicalApplied
						? 'Previous changes saved. Unsaved changes remain.'
						: 'Previous changes saved. Reload after saving the remaining changes.'
				);
				return;
			}

			if (!canonicalApplied) {
				setGrapesJSSaveState(
					saveState,
					'conflict',
					'Saved, but the editor could not apply the canonical project. Reload before editing.'
				);
				return;
			}

			saveState.savedVersion = saveState.changeVersion;

			if (editor.clearDirtyCount) {
				editor.clearDirtyCount();
			}

				setGrapesJSSaveState(
					saveState,
					'saved',
					submissionInboxOwn(data, 'message')
						? String(data.message || 'Draft saved.')
						: grapesJSAjaxMessage(responseData.payload) || 'Draft saved.'
				);
				return data;
			})
		.catch(function (error) {
			if (saveState.activeRequest !== requestId) {
				return;
			}

			if (error.data && submissionInboxOwn(error.data, 'revision')) {
				setSubmissionInboxRevision(saveState, error.data.revision);
			}

				setGrapesJSSaveState(
					saveState,
					isGrapesJSSaveConflict(error) ? 'conflict' : 'error',
					error.message || 'Draft could not be saved.'
				);
				return null;
			})
		.finally(function () {
				if (saveState.activeRequest === requestId) {
					saveState.activeRequest = 0;
				}
				if (saveState.changeVersion !== saveState.savedVersion && saveState.phase !== 'conflict') {
					scheduleGrapesJSAutoSave(saveState);
				}
			});
}

function submissionInboxOwn(object, key) {
	return !!object && Object.prototype.hasOwnProperty.call(object, key);
}

function grapesJSAjaxData(payload) {
	if (
		payload &&
		payload.data &&
		typeof payload.data === 'object' &&
		!Array.isArray(payload.data)
	) {
		return payload.data;
	}

	return payload && typeof payload === 'object' ? payload : {};
}

function grapesJSResponsePayload(response) {
	if (response && typeof response.text === 'function') {
		return response.text().then(function (raw) {
			if (!String(raw || '').trim()) {
				throw grapesJSInvalidResponseError(response);
			}
			try {
				return JSON.parse(raw);
			} catch (error) {
				throw grapesJSInvalidResponseError(response);
			}
		});
	}
	if (response && typeof response.json === 'function') {
		return response.json().catch(function () {
			throw grapesJSInvalidResponseError(response);
		});
	}

	return window.Promise.reject(grapesJSInvalidResponseError(response));
}

function grapesJSInvalidResponseError(response) {
	var status = response ? Math.max(0, parseInt(response.status || 0, 10) || 0) : 0;
	var error = new Error(
		status >= 500
			? 'The server could not process the Design Studio request. Reload the editor and try again.'
			: 'The server returned an invalid response. Reload the editor and try again.'
	);

	error.status = status;
	error.code = 'invalid_server_response';
	error.data = {};

	return error;
}

function grapesJSSaveError(response, payload) {
	var error = new Error(
		grapesJSAjaxMessage(payload) || 'Design Studio could not be saved.'
	);
	var data = grapesJSAjaxData(payload);

	error.status = response ? response.status : 0;
	error.code = String(data.code || (payload && payload.code) || '');
	error.data = data;

	return error;
}

function isGrapesJSSaveConflict(error) {
	return !!(
		error &&
		(
			error.status === 409 ||
			error.status === 412 ||
			/conflict|stale|revision/i.test(String(error.code || ''))
		)
	);
}

function submissionInboxInitialRevision(formData) {
	var ajax = formData && formData.ajax ? formData.ajax : {};
	var candidates = [
		formData && formData.revision,
		formData && formData.project_revision,
		ajax.revision,
	];

	for (var index = 0; index < candidates.length; index++) {
		if (candidates[index] !== undefined && candidates[index] !== null) {
			return String(candidates[index]);
		}
	}

	return '';
}

function setSubmissionInboxRevision(saveState, revision) {
	var normalized = revision === undefined || revision === null
		? ''
		: String(revision);

	saveState.revision = normalized;
	saveState.formData.revision = normalized;

	if (saveState.formData.ajax) {
		saveState.formData.ajax.revision = normalized;
	}
}

function markGrapesJSSaveDirty(saveState) {
	if (!saveState || saveState.suppressDirty > 0) {
		return;
	}

	saveState.changeVersion++;
	saveState.publishedDirty = true;
	saveState.formData.is_dirty = true;
	if (typeof invalidateSubmissionInboxPreflight === 'function') {
		invalidateSubmissionInboxPreflight(saveState);
	}

	if (saveState.phase === 'saving') {
			setGrapesJSSaveState(
				saveState,
				'saving',
				'Saving draft... New changes are pending.'
			);
		return;
	}

	if (saveState.phase === 'conflict') {
		return;
	}

	setGrapesJSSaveState(saveState, 'dirty');
	scheduleGrapesJSAutoSave(saveState);
}

function scheduleGrapesJSAutoSave(saveState) {
	if (!saveState || saveState.phase === 'conflict') {
		return;
	}

	window.clearTimeout(saveState.autoSaveTimer);
	saveState.autoSaveTimer = window.setTimeout(function () {
		saveState.autoSaveTimer = 0;
		if (saveState.phase !== 'saving' && saveState.changeVersion !== saveState.savedVersion) {
			saveGrapesJSProject(saveState.editor, saveState.formData, saveState.button, saveState);
		}
	}, 900);
}

function grapesJSSaveHasUnsavedChanges(saveState) {
	return !!saveState && (
		saveState.phase === 'saving' ||
		saveState.phase === 'conflict' ||
		saveState.changeVersion !== saveState.savedVersion
	);
}

function setGrapesJSSaveState(saveState, phase, message) {
	var labels = {
		conflict: 'This form was changed elsewhere. Reload before saving.',
		dirty: 'Saving draft shortly...',
		error: 'Draft could not be saved.',
		saved: 'Draft saved',
		saving: 'Saving draft...',
	};

	saveState.phase = phase;
	saveState.button.disabled =
		phase === 'saved' ||
		phase === 'saving' ||
		phase === 'conflict';
	saveState.button.setAttribute('data-subin-save-state', phase);

		setGrapesJSSaveStatus(
		saveState.status,
		message || labels[phase] || '',
			phase
		);
		if (typeof updateSubmissionInboxPublishingState === 'function') {
			updateSubmissionInboxPublishingState(saveState);
		}
	}

function grapesJSAjaxMessage(payload) {
	if (payload && payload.data && payload.data.message) {
		return payload.data.message;
	}

	if (payload && payload.message) {
		return payload.message;
	}

	return '';
}

function setGrapesJSSaveStatus(status, message, phase) {
	if (!status) {
		return;
	}

	status.textContent = message;
	status.setAttribute('data-subin-save-state', phase || '');
	status.classList.toggle(
		'subin-grapesjs-save-status--error',
		phase === 'error' || phase === 'conflict'
	);
	status.classList.toggle(
		'subin-grapesjs-save-status--success',
		phase === 'saved'
	);
}

function isSubmissionInboxFieldComponent(component) {
	var attributes = component && component.getAttributes
		? component.getAttributes()
		: {};
	var type = component && component.get ? component.get('type') : '';

	return (
		attributes['data-subin-component'] === 'field' ||
		subinComponentNameFromType(type) === 'field'
	);
}

function submissionInboxFieldComponents(editor) {
	var wrapper = editor && editor.getWrapper
		? editor.getWrapper()
		: null;
	var fields = [];

	if (!wrapper) {
		return fields;
	}

	if (isSubmissionInboxFieldComponent(wrapper)) {
		fields.push(wrapper);
	}

	walkComponents(
		wrapper.components && wrapper.components(),
		function (component) {
			if (isSubmissionInboxFieldComponent(component)) {
				fields.push(component);
			}
		}
	);

	return fields;
}

function ensureSubmissionInboxFieldIdentities(editor, preferredDuplicate) {
	var seenFieldIds = {};
	var seenKeys = {};
	var seenTempIds = {};
	var changed = false;
	var fieldComponents = submissionInboxFieldComponents(editor);

	if (
		preferredDuplicate &&
		fieldComponents.indexOf(preferredDuplicate) !== -1
	) {
		fieldComponents = fieldComponents
			.filter(function (component) {
				return component !== preferredDuplicate;
			})
			.concat([preferredDuplicate]);
	}

	fieldComponents.forEach(function (component) {
		var attributes = component.getAttributes
			? component.getAttributes()
			: {};
		var fieldId = String(
			parseInt(attributes['data-subin-field-id'], 10) || ''
		);
		var originalFieldId = fieldId;
		var duplicateFieldId = !!(
			fieldId &&
			seenFieldIds[fieldId]
		);
		var tempId = String(
			attributes['data-subin-temp-id'] || ''
		);
		var previousStyleIds = [
			String(attributes['data-subin-style-id'] || ''),
			originalFieldId ? 'field-' + originalFieldId : '',
			tempId,
		];
		var key = normalizeSubmissionInboxFieldKey(
			attributes['data-subin-field-key'] ||
			attributes['data-subin-field-type'] ||
			'field'
		);
		var uniqueKey = key;
		var suffix = 2;

		if (duplicateFieldId) {
			if (component.removeAttributes) {
				component.removeAttributes('data-subin-field-id');
			} else {
				component.addAttributes({
					'data-subin-field-id': '',
				});
			}

			fieldId = '';
			changed = true;
		}

		if (fieldId) {
			seenFieldIds[fieldId] = true;
		}

		if (!fieldId) {
			if (!tempId || seenTempIds[tempId]) {
				tempId = newSubmissionInboxTempId();
				component.addAttributes({
					'data-subin-temp-id': tempId,
				});
				changed = true;
			}

			while (seenKeys[uniqueKey]) {
				uniqueKey = key + '_' + suffix++;
			}

			if (uniqueKey !== attributes['data-subin-field-key']) {
				component.addAttributes({
					'data-subin-field-key': uniqueKey,
				});
				changed = true;
			}

		}

		if (
			migrateSubmissionInboxStyleTargets(
				editor,
				component,
				previousStyleIds,
				fieldId
					? String(
						attributes['data-subin-style-id'] ||
						('field-' + fieldId)
					)
					: tempId
			)
		) {
			changed = true;
		}

		if (tempId) {
			seenTempIds[tempId] = true;
		}

		seenKeys[fieldId ? key : uniqueKey] = true;
	});

	return changed;
}

function normalizeSubmissionInboxFieldKey(value) {
	var safe = String(value || '')
		.toLowerCase()
		.replace(/[^a-z0-9_-]+/g, '_')
		.replace(/_+/g, '_')
		.replace(/^_+|_+$/g, '');

	return safe || 'field';
}

function newSubmissionInboxTempId() {
	if (
		window.crypto &&
		typeof window.crypto.randomUUID === 'function'
	) {
		return 'subin-field-' + window.crypto.randomUUID();
	}

	return (
		'subin-field-' +
		Date.now() +
		'-' +
		Math.floor(Math.random() * 1000000000)
	);
}

function migrateSubmissionInboxStyleTargets(
	editor,
	component,
	previousStyleIds,
	nextStyleId
) {
	var nextClass = styleClassName(nextStyleId);
	var attributes = component.getAttributes
		? component.getAttributes()
		: {};
	var classes = component.getClasses ? component.getClasses() : [];
	var seen = {};
	var changed = false;

	(previousStyleIds || []).forEach(function (previousStyleId) {
		var identity = String(previousStyleId || '');
		var previousClass;

		if (!identity || seen[identity]) {
			return;
		}

		seen[identity] = true;
		previousClass = styleClassName(identity);

		if (previousClass === nextClass) {
			return;
		}

		if (classes.indexOf(previousClass) === -1) {
			return;
		}

		cloneSubmissionInboxStyleRules(
			editor,
			previousClass,
			nextClass
		);

		if (component.removeClass) {
			component.removeClass(previousClass);
		} else if (component.getClasses && component.setClass) {
			component.setClass(
				component.getClasses().filter(function (className) {
					return className !== previousClass;
				})
			);
		}
		changed = true;
	});

	if (String(attributes['data-subin-style-id'] || '') !== nextStyleId) {
		component.addAttributes({
			'data-subin-style-id': nextStyleId,
		});
		changed = true;
	}

	if (classes.indexOf(nextClass) === -1) {
		changed = true;
	}

	ensureUniqueStyleTarget(component, 'field');

	return changed;
}

function cloneSubmissionInboxStyleRules(
	editor,
	previousClass,
	nextClass
) {
	var css = editor && editor.Css ? editor.Css : null;
	var rules = css && css.getRules ? css.getRules() : null;
	var pattern = new RegExp(
		'\\.' +
		escapeSubmissionInboxRegExp(previousClass) +
		'(?![A-Za-z0-9_-])',
		'g'
	);
	var copies = [];

	if (
		!rules ||
		!rules.forEach ||
		!css.setRule ||
		!previousClass ||
		previousClass === nextClass
	) {
		return;
	}

	rules.forEach(function (rule) {
		var selector = rule && rule.getSelectorsString
			? rule.getSelectorsString()
			: '';
		var nextSelector;
		var style;
		var options = {};

		if (selector.indexOf('.' + previousClass) === -1) {
			return;
		}

		nextSelector = selector.replace(
			pattern,
			'.' + nextClass
		);

		if (nextSelector === selector) {
			return;
		}

		style = rule.getStyle
			? rule.getStyle()
			: (rule.get ? rule.get('style') : {});

		if (rule.get) {
			if (rule.get('atRuleType')) {
				options.atRuleType = rule.get('atRuleType');
			}
			if (rule.get('mediaText')) {
				options.atRuleParams = rule.get('mediaText');
			}
		}

		copies.push({
			options: options,
			selector: nextSelector,
			style: Object.assign({}, style || {}),
		});
	});

	copies.forEach(function (copy) {
		css.setRule(copy.selector, copy.style, copy.options);
	});
}

function escapeSubmissionInboxRegExp(value) {
	return String(value || '').replace(
		/[.*+?^$(){}|[\]\\]/g,
		'\\$&'
	);
}

function applySubmissionInboxCanonicalProject(
	editor,
	project,
	formData,
	saveState
) {
	var selected = editor && editor.getSelected
		? editor.getSelected()
		: null;
	var selectedIdentity = submissionInboxComponentIdentity(selected);
	var normalizedProject;

	try {
		normalizedProject = normalizeSubmissionInboxRowsInProject(project);
		saveState.suppressDirty++;

		editor.loadProjectData(normalizedProject);
		formData.grapesjs_project = normalizedProject;
		if (typeof window.hydrateSubmissionInboxLogic === 'function') {
			window.hydrateSubmissionInboxLogic(editor, normalizedProject, formData);
		}

		addSubmissionInboxPreviewStyles(editor);
		configureSubmissionInboxStylableComponents(editor, formData);
		ensureSubmissionInboxFieldIdentities(editor);
		restoreSubmissionInboxSelection(editor, selectedIdentity);
		scheduleGrapesJSPreviewStateRefresh(editor);

		return true;
	} catch (error) {
		return false;
	} finally {
		saveState.suppressDirty--;
	}
}

function reconcileSubmissionInboxCanonicalFieldIdentities(
	editor,
	project,
	saveState
) {
	var current = {
		byFieldId: {},
		byKey: {},
		byTempId: {},
	};

	submissionInboxFieldComponents(editor).forEach(function (component) {
		var attributes = component.getAttributes
			? component.getAttributes()
			: {};

		if (attributes['data-subin-temp-id']) {
			current.byTempId[
				String(attributes['data-subin-temp-id'])
			] = component;
		}

		if (attributes['data-subin-field-id']) {
			current.byFieldId[
				String(attributes['data-subin-field-id'])
			] = component;
		}

		if (attributes['data-subin-field-key']) {
			current.byKey[
				String(attributes['data-subin-field-key'])
			] = component;
		}
	});

	try {
		saveState.suppressDirty++;

		submissionInboxCanonicalFieldAttributes(project).forEach(
			function (attributes) {
				var tempId = String(attributes['data-subin-temp-id'] || '');
				var fieldId = String(attributes['data-subin-field-id'] || '');
				var fieldKey = String(attributes['data-subin-field-key'] || '');
				var component = fieldId ? current.byFieldId[fieldId] : null;
				var componentAttributes;
				var previousStyleIds;
				var next = {};

				if (!component && tempId) {
					component = current.byTempId[tempId];
				}

				if (!component && !tempId && !fieldId && fieldKey) {
					component = current.byKey[fieldKey];
				}

				/*
				 * An unmatched canonical field may have been deleted locally
				 * while the request was in flight. Keep that newer deletion.
				 */
				if (!component) {
					return;
				}

				componentAttributes = component.getAttributes
					? component.getAttributes()
					: {};
				previousStyleIds = [
					String(componentAttributes['data-subin-style-id'] || ''),
					String(componentAttributes['data-subin-temp-id'] || ''),
					componentAttributes['data-subin-field-id']
						? 'field-' + componentAttributes['data-subin-field-id']
						: '',
				];

				[
					'data-subin-field-id',
					'data-subin-style-id',
				].forEach(function (name) {
					if (
						submissionInboxOwn(attributes, name) &&
						attributes[name] !== null &&
						String(attributes[name]) !== ''
					) {
						next[name] = String(attributes[name]);
					}
				});

				component.addAttributes(next);
				migrateSubmissionInboxStyleTargets(
					editor,
					component,
					previousStyleIds,
					String(
						next['data-subin-style-id'] ||
						componentAttributes['data-subin-style-id'] ||
						(
							next['data-subin-field-id']
								? 'field-' + next['data-subin-field-id']
								: componentAttributes['data-subin-temp-id']
						)
					)
				);
				updateFieldLayerName(component);
			}
		);

		return true;
	} catch (error) {
		return false;
	} finally {
		saveState.suppressDirty--;
	}
}

function submissionInboxCanonicalFieldAttributes(project) {
	var fields = [];

	function visit(node) {
		if (Array.isArray(node)) {
			node.forEach(visit);
			return;
		}

		if (!node || typeof node !== 'object') {
			return;
		}

		if (
			node.attributes &&
			(
				node.attributes['data-subin-component'] === 'field' ||
				node.type === 'subin-field'
			)
		) {
			fields.push(node.attributes);
		}

		[
			'components',
			'pages',
			'frames',
			'component',
		].forEach(function (name) {
			if (node[name]) {
				visit(node[name]);
			}
		});
	}

	visit(project);

	return fields;
}

function submissionInboxComponentIdentity(component) {
	var attributes = component && component.getAttributes
		? component.getAttributes()
		: {};

	if (attributes['data-subin-temp-id']) {
		return 'temp:' + attributes['data-subin-temp-id'];
	}

	if (attributes['data-subin-field-id']) {
		return 'field:' + attributes['data-subin-field-id'];
	}

	if (component && component.getId) {
		return 'component:' + component.getId();
	}

	return '';
}

function restoreSubmissionInboxSelection(editor, identity) {
	var wrapper = editor && editor.getWrapper
		? editor.getWrapper()
		: null;
	var match = null;

	if (!identity || !wrapper) {
		return;
	}

	if (submissionInboxComponentIdentity(wrapper) === identity) {
		match = wrapper;
	}

	walkComponents(
		wrapper.components && wrapper.components(),
		function (component) {
			if (
				!match &&
				submissionInboxComponentIdentity(component) === identity
			) {
				match = component;
			}
		}
	);

	if (match && editor.select) {
		editor.select(match);
	}
}

function prepareFormPreviewWrapper(editor, formData) {
	var wrapper = editor.getWrapper();
	var formId = formData && formData.id ? String(formData.id) : 'preview';

	addClasses(wrapper, ['subin-gjs-form-preview', 'subin-gjs-form-flow', 'subin-form', 'subin-form-' + formId]);
	wrapper.addAttributes({
		'data-subin-component': 'form-wrapper',
		'data-subin-form-id': formId,
	});
	applyFormMessageAttributes(wrapper, formData);
	wrapper.set('stylable', subinWrapperStyleProps());
	wrapper.set('droppable', true);
	wrapper.set('name', 'Form');
	configureFormWrapperTraits(wrapper);
}

function configureSubmissionInboxStylableComponents(editor, formData) {
	var wrapper = editor.getWrapper();
	var fieldMap = buildEditorFieldMap(formData);

	if (wrapper) {
		configureComponentStylable(wrapper, fieldMap, formData);
		walkComponents(wrapper.components && wrapper.components(), function (component) {
			configureComponentStylable(component, fieldMap, formData);
		});
	}
}
