'use strict';

(function (root) {
	var LOGIC_VERSION = 1;
	var LOGIC_HISTORY_PROPERTY = '__subinLogicHistory';
	var LOGIC_OPERATORS = [
		'is',
		'is_not',
		'contains',
		'not_contains',
		'empty',
		'not_empty',
		'before',
		'after',
	];
	var LOGIC_OPERATOR_LABELS = {
		is: 'is',
		is_not: 'is not',
		contains: 'contains',
		not_contains: 'does not contain',
		empty: 'is empty',
		not_empty: 'is not empty',
		before: 'is before',
		after: 'is after',
	};

	function submissionInboxLogicCore() {
		var core = root && (root.SubinConditionalLogic || root.SubmissionInboxLogic);

		return core && (typeof core.sanitize === 'function' || typeof core.normalizeConfig === 'function') ? core : null;
	}

	function emptySubmissionInboxLogicConfig() {
		return {
			version: LOGIC_VERSION,
			rules: [],
		};
	}

	function cloneSubmissionInboxLogic(value) {
		try {
			return JSON.parse(JSON.stringify(value));
		} catch (error) {
			return null;
		}
	}

	function normalizeSubmissionInboxLogicConfig(config) {
		var core = submissionInboxLogicCore();
		var source = cloneSubmissionInboxLogic(config || emptySubmissionInboxLogicConfig());
		var normalized;

		if (!core || !source || typeof source !== 'object' || Array.isArray(source)) {
			return emptySubmissionInboxLogicConfig();
		}

		try {
			if (typeof core.validate === 'function' && core.validate(source).length) {
				return normalizeSubmissionInboxLogicDraft(source);
			}
			normalized = typeof core.sanitize === 'function' ? core.sanitize(source) : core.normalizeConfig(source);
		} catch (error) {
			return normalizeSubmissionInboxLogicDraft(source);
		}

		if (!normalized || typeof normalized !== 'object' || !Array.isArray(normalized.rules)) {
			return normalizeSubmissionInboxLogicDraft(source);
		}

		return cloneSubmissionInboxLogic(normalized) || emptySubmissionInboxLogicConfig();
	}

	function normalizeSubmissionInboxLogicDraft(config) {
		var rules = Array.isArray(config && config.rules) ? config.rules : [];
		var conditionCount = 0;
		var conditionLimit = submissionInboxLogicMaxTotalConditions() + 1;

		return {
			version: LOGIC_VERSION,
			rules: rules.slice(0, 200).filter(function (rule) {
				return rule && typeof rule === 'object' && !Array.isArray(rule);
			}).map(function (rule) {
				var target = rule.target && typeof rule.target === 'object' ? rule.target : {};
				var groups = Array.isArray(rule.groups) ? rule.groups : [];
				var id = sanitizeSubmissionInboxLogicDraftIdentifier(rule.id);

				return {
					id: id,
					enabled: normalizeSubmissionInboxLogicDraftBoolean(rule.enabled),
					action: ['show', 'hide'].indexOf(rule.action) !== -1 ? rule.action : '',
					target: {
						type: ['field', 'row', 'block'].indexOf(target.type) !== -1 ? target.type : '',
						id: sanitizeSubmissionInboxLogicDraftIdentifier(target.id),
					},
					groups: groups.slice(0, 20).filter(function (group) {
						return group && typeof group === 'object' && !Array.isArray(group);
					}).map(function (group) {
						var conditions = Array.isArray(group.conditions) ? group.conditions : [];
						var remaining = Math.max(0, conditionLimit - conditionCount);
						var retainedConditions = conditions.slice(0, Math.min(50, remaining));

						conditionCount += retainedConditions.length;

						return {
							match: ['all', 'any'].indexOf(group.match) !== -1 ? group.match : '',
							conditions: retainedConditions.map(function (condition) {
								condition = condition && typeof condition === 'object' && !Array.isArray(condition) ? condition : {};
								var valueState = {nodes: 0};

								return {
									field: sanitizeSubmissionInboxLogicDraftIdentifier(condition.field),
									operator: LOGIC_OPERATORS.indexOf(condition.operator) !== -1 ? condition.operator : '',
									value: normalizeSubmissionInboxLogicDraftValue(condition.value, 0, valueState),
								};
							}),
						};
					}),
				};
			}),
		};
	}

	function normalizeSubmissionInboxLogicDraftBoolean(value) {
		if (value === undefined) {
			return true;
		}
		if (value === true || value === 1) {
			return true;
		}
		return typeof value === 'string' &&
			['1', 'true', 'yes', 'on'].indexOf(value.trim().toLowerCase()) !== -1;
	}

	function normalizeSubmissionInboxLogicDraftValue(value, depth, state) {
		var output;
		var index;

		if (depth > 10) {
			return '';
		}
		if (Array.isArray(value)) {
			output = [];
			for (index = 0; index < value.length && state.nodes < 100; index++) {
				state.nodes++;
				output.push(normalizeSubmissionInboxLogicDraftValue(value[index], depth + 1, state));
			}
			return output;
		}
		if (value === null || typeof value === 'boolean' || (typeof value === 'number' && isFinite(value))) {
			return value;
		}
		if (value === undefined || typeof value === 'object' || typeof value === 'function' || typeof value === 'symbol') {
			return '';
		}

		return Array.from(String(value)).slice(0, 2048).join('');
	}

	function submissionInboxLogicMaxTotalConditions() {
		var core = submissionInboxLogicCore();
		var limit = parseInt(core && core.MAX_TOTAL_CONDITIONS, 10);

		return isFinite(limit) && limit > 0
			? limit
			: 1000;
	}

	function sanitizeSubmissionInboxLogicDraftIdentifier(value) {
		return String(value === undefined || value === null ? '' : value)
			.trim()
			.replace(/[^A-Za-z0-9_.:-]/g, '')
			.slice(0, 128);
	}

	function submissionInboxLogicDocument() {
		if (root && root.document) {
			return root.document;
		}

		return typeof document !== 'undefined' ? document : null;
	}

	function initSubmissionInboxLogic(editor, formData) {
		var core = submissionInboxLogicCore();
		var state;
		var project;

		if (!editor || !core) {
			return null;
		}

		if (editor.__subinLogicState) {
			state = editor.__subinLogicState;
			if (formData) {
				state.formData = formData;
			}
			return state;
		}

		project = formData && formData.grapesjs_project && typeof formData.grapesjs_project === 'object'
			? formData.grapesjs_project
			: {};
		state = {
			boundCanvasBodies: [],
			config: normalizeSubmissionInboxLogicConfig(project.subinLogic),
			editor: editor,
			formData: formData || null,
			panel: findSubmissionInboxLogicPanel(),
			renderTimer: 0,
		};
		editor.__subinLogicState = state;

		prepareSubmissionInboxLogicNavigation();
		bindSubmissionInboxLogicHistory(state);
		bindSubmissionInboxLogicPanel(state);
		bindSubmissionInboxLogicEditor(state);
		syncSubmissionInboxLogicFormData(state);
		renderSubmissionInboxLogicPanel(state);
		refreshSubmissionInboxLogicPreview(editor);

		return state;
	}

	function findSubmissionInboxLogicPanel() {
		var doc = submissionInboxLogicDocument();

		return doc && doc.querySelector ? doc.querySelector('[data-subin-studio-panel="logic"]') : null;
	}

	function prepareSubmissionInboxLogicNavigation() {
		var doc = submissionInboxLogicDocument();
		var button = doc && doc.querySelector ? doc.querySelector('[data-subin-studio-nav="logic"]') : null;
		var soon;

		if (!button) {
			return;
		}

		if (button.classList) {
			button.classList.remove('subin-studio-rail-button--muted');
		}
		soon = button.querySelector ? button.querySelector('em') : null;
		if (soon && soon.parentNode) {
			soon.parentNode.removeChild(soon);
		}
		button.removeAttribute('title');
	}

	function bindSubmissionInboxLogicEditor(state) {
		var editor = state.editor;

		if (!editor || !editor.on || state.editorEventsBound) {
			return;
		}

		state.editorEventsBound = true;
		['component:add', 'component:remove', 'component:update'].forEach(function (eventName) {
			editor.on(eventName, function () {
				if (eventName === 'component:update' && state.syncingHistory) {
					return;
				}
				scheduleSubmissionInboxLogicRender(state);
				refreshSubmissionInboxLogicPreview(editor);
			});
		});
		['load', 'project:load', 'canvas:frame:load'].forEach(function (eventName) {
			editor.on(eventName, function () {
				bindSubmissionInboxLogicCanvas(state);
				bindSubmissionInboxLogicHistory(state);
				scheduleSubmissionInboxLogicRender(state);
				refreshSubmissionInboxLogicPreview(editor);
			});
		});
		['run:core:preview', 'run:preview'].forEach(function (eventName) {
			editor.on(eventName, function () {
				logicSetTimeout(function () {
					refreshSubmissionInboxLogicPreview(editor, null, true);
				}, 0);
			});
		});
		['stop:core:preview', 'stop:preview'].forEach(function (eventName) {
			editor.on(eventName, function () {
				refreshSubmissionInboxLogicPreview(editor, null, false);
			});
		});

		bindSubmissionInboxLogicCanvas(state);
	}

	function bindSubmissionInboxLogicCanvas(state) {
		var canvas = state.editor && state.editor.Canvas;
		var body = canvas && canvas.getBody ? canvas.getBody() : null;
		var listener;

		if (!body || !body.addEventListener || state.boundCanvasBodies.indexOf(body) !== -1) {
			return;
		}

		listener = function () {
			if (state.editor.__subinInteractivePreviewActive) {
				refreshSubmissionInboxLogicPreview(state.editor);
			}
		};
		body.addEventListener('input', listener, true);
		body.addEventListener('change', listener, true);
		state.boundCanvasBodies.push(body);
	}

	function logicSetTimeout(callback, delay) {
		if (root && typeof root.setTimeout === 'function') {
			return root.setTimeout(callback, delay);
		}

		callback();
		return 0;
	}

	function scheduleSubmissionInboxLogicRender(state) {
		if (!state || state.renderTimer) {
			return;
		}

		state.renderTimer = logicSetTimeout(function () {
			state.renderTimer = 0;
			renderSubmissionInboxLogicPanel(state);
		}, 0);
	}

	function bindSubmissionInboxLogicPanel(state) {
		var panel = state.panel;

		if (!panel || panel.__subinLogicPanelBound) {
			return;
		}

		panel.__subinLogicPanelBound = true;
		panel.addEventListener('click', function (event) {
			handleSubmissionInboxLogicClick(state, event);
		});
		panel.addEventListener('change', function (event) {
			var isLiveValue = event.target && event.target.matches && event.target.matches('input[type="text"][data-subin-logic-bind="value"], textarea[data-subin-logic-bind="value"]');
			handleSubmissionInboxLogicChange(state, event, !isLiveValue);
		});
		panel.addEventListener('input', function (event) {
			handleSubmissionInboxLogicChange(state, event, false);
		});
	}

	function handleSubmissionInboxLogicClick(state, event) {
		var target = event.target;
		var button = target && target.closest ? target.closest('[data-subin-logic-action]') : null;
		var action;
		var indexes;
		var rule;

		if (!button) {
			return;
		}

		event.preventDefault();
		action = button.getAttribute('data-subin-logic-action') || '';
		indexes = submissionInboxLogicIndexes(button);

		if (action === 'add-rule') {
			state.config.rules.push(buildSubmissionInboxLogicRule(state));
			commitSubmissionInboxLogicChange(state, true);
			return;
		}

		rule = state.config.rules[indexes.rule];
		if (!rule) {
			return;
		}

		if (action === 'remove-rule') {
			state.config.rules.splice(indexes.rule, 1);
		} else if (action === 'add-group') {
			rule.groups.push(buildSubmissionInboxLogicGroup(state));
		} else if (action === 'remove-group' && rule.groups[indexes.group]) {
			rule.groups.splice(indexes.group, 1);
		} else if (action === 'add-condition' && rule.groups[indexes.group]) {
			rule.groups[indexes.group].conditions.push(buildSubmissionInboxLogicCondition(state));
		} else if (action === 'remove-condition' && rule.groups[indexes.group]) {
			rule.groups[indexes.group].conditions.splice(indexes.condition, 1);
		} else if (action === 'select-target') {
			selectSubmissionInboxLogicTarget(state, rule.target);
			return;
		} else {
			return;
		}

		commitSubmissionInboxLogicChange(state, true);
	}

	function handleSubmissionInboxLogicChange(state, event, render) {
		var control = event.target;
		var binding = control && control.getAttribute ? control.getAttribute('data-subin-logic-bind') : '';
		var indexes;
		var rule;
		var group;
		var condition;
		var value;

		if (!binding) {
			return;
		}

		indexes = submissionInboxLogicIndexes(control);
		rule = state.config.rules[indexes.rule];
		if (!rule) {
			return;
		}

		if (control.type === 'checkbox') {
			value = !!control.checked;
		} else if (control.multiple && control.options) {
			value = Array.prototype.slice.call(control.options).filter(function (option) {
				return !!option.selected;
			}).map(function (option) {
				return String(option.value || '');
			});
		} else {
			value = String(control.value || '');
		}
		if (binding === 'value' && control.getAttribute('data-subin-logic-value-format') === 'json-array') {
			try {
				var parsedValue = JSON.parse(value);
				value = Array.isArray(parsedValue) ? parsedValue : value;
			} catch (error) {
				// Keep the in-progress text so editing an invalid JSON list is non-destructive.
			}
		}
		if (binding === 'enabled') {
			rule.enabled = !!value;
		} else if (binding === 'action') {
			rule.action = value;
		} else if (binding === 'target') {
			rule.target = parseSubmissionInboxLogicTarget(value);
		} else {
			group = rule.groups[indexes.group];
			if (!group) {
				return;
			}
			if (binding === 'match') {
				group.match = value;
			} else {
				condition = group.conditions[indexes.condition];
				if (!condition) {
					return;
				}
				if (binding === 'field') {
					condition.field = value;
					var selectedField = collectSubmissionInboxLogicComponents(state.editor).filter(function (descriptor) {
						return descriptor.type === 'field' && descriptor.id === value;
					})[0] || null;
					var allowedOperators = submissionInboxLogicOperatorsForField(selectedField);
					if (allowedOperators.indexOf(condition.operator) === -1) {
						condition.operator = allowedOperators[0] || 'is';
					}
				} else if (binding === 'operator') {
					condition.operator = value;
				} else if (binding === 'value') {
					condition.value = value;
				} else {
					return;
				}
			}
		}

		if (render) {
			rememberSubmissionInboxLogicFocus(state, control);
		}
		commitSubmissionInboxLogicChange(state, render);
	}

	function submissionInboxLogicIndexes(element) {
		var card = element.closest ? element.closest('[data-subin-logic-rule-index]') : null;
		var group = element.closest ? element.closest('[data-subin-logic-group-index]') : null;
		var condition = element.closest ? element.closest('[data-subin-logic-condition-index]') : null;

		return {
			rule: parseInt(card ? card.getAttribute('data-subin-logic-rule-index') : '-1', 10),
			group: parseInt(group ? group.getAttribute('data-subin-logic-group-index') : '-1', 10),
			condition: parseInt(condition ? condition.getAttribute('data-subin-logic-condition-index') : '-1', 10),
		};
	}

	function rememberSubmissionInboxLogicFocus(state, control) {
		var indexes;
		var binding;

		if (!state || !control || !control.getAttribute) {
			return;
		}

		indexes = submissionInboxLogicIndexes(control);
		binding = control.getAttribute('data-subin-logic-bind') || '';
		if (!binding || !/^[a-z_]+$/.test(binding)) {
			return;
		}

		state.focusAfterRender = {
			binding: binding,
			condition: indexes.condition,
			group: indexes.group,
			rule: indexes.rule,
			selectionEnd: typeof control.selectionEnd === 'number' ? control.selectionEnd : null,
			selectionStart: typeof control.selectionStart === 'number' ? control.selectionStart : null,
			scrollTop: control.scrollTop || 0,
		};
	}

	function restoreSubmissionInboxLogicFocus(state) {
		var focus = state && state.focusAfterRender;
		var scope;
		var control;

		if (!focus || !state.panel || !state.panel.querySelector) {
			return;
		}
		state.focusAfterRender = null;

		scope = state.panel.querySelector('[data-subin-logic-rule-index="' + String(focus.rule) + '"]');
		if (scope && focus.group >= 0) {
			scope = scope.querySelector('[data-subin-logic-group-index="' + String(focus.group) + '"]');
		}
		if (scope && focus.condition >= 0) {
			scope = scope.querySelector('[data-subin-logic-condition-index="' + String(focus.condition) + '"]');
		}
		control = scope && scope.querySelector
			? scope.querySelector('[data-subin-logic-bind="' + focus.binding + '"]')
			: null;
		if (!control) {
			return;
		}
		if (typeof control.focus === 'function') {
			control.focus();
		}
		if (
			focus.selectionStart !== null &&
			focus.selectionEnd !== null &&
			typeof control.setSelectionRange === 'function'
		) {
			control.setSelectionRange(focus.selectionStart, focus.selectionEnd);
		}
		if (typeof control.scrollTop === 'number') {
			control.scrollTop = focus.scrollTop;
		}
	}

	function commitSubmissionInboxLogicChange(state, render) {
		state.config = normalizeSubmissionInboxLogicConfig(state.config);
		syncSubmissionInboxLogicFormData(state);
		syncSubmissionInboxLogicHistory(state, false);
		markSubmissionInboxLogicDirty(state);
		if (render) {
			renderSubmissionInboxLogicPanel(state);
		} else {
			updateSubmissionInboxLogicSummaries(state);
		}
		refreshSubmissionInboxLogicPreview(state.editor);
	}

	function markSubmissionInboxLogicDirty(state) {
		var editor = state.editor;
		var copy = cloneSubmissionInboxLogic(state.config);

		if (typeof root.markGrapesJSSaveDirty === 'function' && editor.__subinSaveState) {
			root.markGrapesJSSaveDirty(editor.__subinSaveState);
		} else if (editor && editor.trigger) {
			editor.trigger('updateBefore');
		}
		if (editor && editor.trigger) {
			editor.trigger('subin:logic:change', copy);
		}
	}

	function syncSubmissionInboxLogicFormData(state) {
		var formData = state && state.formData;

		if (!formData) {
			return;
		}
		if (!formData.grapesjs_project || typeof formData.grapesjs_project !== 'object' || Array.isArray(formData.grapesjs_project)) {
			formData.grapesjs_project = {};
		}
		formData.grapesjs_project.subinLogic = cloneSubmissionInboxLogic(state.config);
	}

	function bindSubmissionInboxLogicHistory(state) {
		var editor = state && state.editor;
		var model = editor && editor.getWrapper ? editor.getWrapper() : null;

		if (!model || !model.on || !model.get || !model.set || state.historyModel === model) {
			return;
		}
		if (state.historyModel && state.historyModel.off && state.historyListener) {
			state.historyModel.off('change:' + LOGIC_HISTORY_PROPERTY, state.historyListener);
		}

		state.historyListener = function (changedModel) {
			var serialized;
			var parsed;
			var nextConfig;

			if (state.syncingHistory) {
				return;
			}
			serialized = changedModel && changedModel.get ? changedModel.get(LOGIC_HISTORY_PROPERTY) : '';
			if (typeof serialized !== 'string') {
				return;
			}
			try {
				parsed = JSON.parse(serialized);
			} catch (error) {
				return;
			}
			nextConfig = normalizeSubmissionInboxLogicConfig(parsed);
			if (JSON.stringify(nextConfig) === JSON.stringify(state.config)) {
				return;
			}

			state.config = nextConfig;
			syncSubmissionInboxLogicFormData(state);
			renderSubmissionInboxLogicPanel(state);
			refreshSubmissionInboxLogicPreview(editor);
			if (editor && editor.trigger) {
				editor.trigger('subin:logic:change', cloneSubmissionInboxLogic(state.config));
			}
		};
		state.historyModel = model;
		model.on('change:' + LOGIC_HISTORY_PROPERTY, state.historyListener);
		syncSubmissionInboxLogicHistory(state, true);
	}

	function syncSubmissionInboxLogicHistory(state, skipUndo) {
		var model = state && state.historyModel;
		var editor = state && state.editor;
		var undoManager = editor && editor.UndoManager;
		var serialized;
		var apply;

		if (!model || !model.get || !model.set) {
			return;
		}
		serialized = JSON.stringify(state.config);
		if (model.get(LOGIC_HISTORY_PROPERTY) === serialized) {
			return;
		}

		apply = function () {
			model.set(LOGIC_HISTORY_PROPERTY, serialized);
		};
		state.syncingHistory = true;
		try {
			if (skipUndo && undoManager && typeof undoManager.skip === 'function') {
				undoManager.skip(apply);
			} else {
				apply();
			}
		} finally {
			state.syncingHistory = false;
		}
	}

	function stripSubmissionInboxLogicHistory(value, depth) {
		if (!value || typeof value !== 'object' || depth > 30) {
			return;
		}
		if (!Array.isArray(value)) {
			delete value[LOGIC_HISTORY_PROPERTY];
		}
		Object.keys(value).forEach(function (key) {
			stripSubmissionInboxLogicHistory(value[key], depth + 1);
		});
	}

	function buildSubmissionInboxLogicRule(state) {
		var descriptors = collectSubmissionInboxLogicComponents(state.editor);
		var targets = descriptors.filter(function (descriptor) {
			return descriptor.target;
		});
		var selected = state.editor && state.editor.getSelected ? state.editor.getSelected() : null;
		var selectedDescriptor = descriptorForSubmissionInboxLogicComponent(selected, 0, true);
		var target = selectedDescriptor && selectedDescriptor.target ? selectedDescriptor : firstAvailableSubmissionInboxLogicTarget(targets, state.config.rules);

		return {
			id: submissionInboxLogicUniqueId('rule'),
			enabled: true,
			action: 'show',
			target: target ? {type: target.type, id: target.id} : {type: 'field', id: ''},
			groups: [buildSubmissionInboxLogicGroup(state)],
		};
	}

	function firstAvailableSubmissionInboxLogicTarget(targets, rules) {
		var used = Object.create(null);

		(rules || []).forEach(function (rule) {
			if (rule && rule.target) {
				used[submissionInboxLogicTargetKey(rule.target)] = true;
			}
		});

		return targets.filter(function (descriptor) {
			return !used[descriptor.key];
		})[0] || targets[0] || null;
	}

	function buildSubmissionInboxLogicGroup(state) {
		return {
			match: 'all',
			conditions: [buildSubmissionInboxLogicCondition(state)],
		};
	}

	function buildSubmissionInboxLogicCondition(state) {
		var field = collectSubmissionInboxLogicComponents(state.editor).filter(function (descriptor) {
			return descriptor.type === 'field';
		})[0];

		return {
			field: field ? field.id : '',
			operator: 'is',
			value: '',
		};
	}

	function submissionInboxLogicUniqueId(prefix) {
		if (root.crypto && typeof root.crypto.randomUUID === 'function') {
			return prefix + '-' + root.crypto.randomUUID();
		}

		return prefix + '-' + Date.now() + '-' + Math.floor(Math.random() * 1000000);
	}

	function parseSubmissionInboxLogicTarget(value) {
		var divider = String(value || '').indexOf(':');

		if (divider < 1) {
			return {type: 'field', id: ''};
		}

		return {
			type: value.slice(0, divider),
			id: value.slice(divider + 1),
		};
	}

	function submissionInboxLogicTargetKey(target) {
		return target && target.type && target.id ? String(target.type) + ':' + String(target.id) : '';
	}

	function renderSubmissionInboxLogicPanel(state) {
		var panel = state.panel;
		var descriptors;
		var targets;
		var fields;
		var validation;
		var html;

		if (!panel) {
			return;
		}

		descriptors = collectSubmissionInboxLogicComponents(state.editor);
		targets = descriptors.filter(function (descriptor) { return descriptor.target; });
		fields = descriptors.filter(function (descriptor) { return descriptor.type === 'field'; });
		validation = validateSubmissionInboxLogicConfig(state.config, descriptors);
		html = [
			'<div class="subin-studio-panel-heading">',
			'<div><span class="subin-studio-eyebrow">Logic</span><h3>Conditional rules</h3></div>',
			'<p>Show or hide an element when visitor answers match your conditions.</p>',
			'</div>',
			'<div class="subin-logic-toolbar">',
			'<button type="button" class="button button-primary" data-subin-logic-action="add-rule">Add rule</button>',
			'</div>',
			'<div class="subin-logic-rules" data-subin-logic-rules>',
		].join('');

		if (!state.config.rules.length) {
			html += [
				'<div class="subin-studio-coming-soon subin-logic-empty">',
				'<span class="dashicons dashicons-randomize" aria-hidden="true"></span>',
				'<strong>No conditional rules yet</strong>',
				'<p>Add a rule to control a field, row, or content block.</p>',
				'</div>',
			].join('');
		} else {
			state.config.rules.forEach(function (rule, ruleIndex) {
				html += renderSubmissionInboxLogicRule(rule, ruleIndex, targets, fields, validation.rules[ruleIndex] || []);
			});
		}

		html += '</div>';
		panel.innerHTML = html;
		restoreSubmissionInboxLogicFocus(state);
	}

	function renderSubmissionInboxLogicRule(rule, ruleIndex, targets, fields, errors) {
		var disabled = rule.enabled === false ? '' : ' checked="checked"';
		var html = [
			'<article class="subin-studio-embed-card subin-logic-card" data-subin-logic-rule-index="', String(ruleIndex), '">',
			'<header class="subin-logic-card__header">',
			'<label><input type="checkbox" data-subin-logic-bind="enabled"', disabled, ' /> Enabled</label>',
			'<strong>Rule ', String(ruleIndex + 1), '</strong>',
			'<button type="button" class="button-link-delete" data-subin-logic-action="remove-rule">Remove</button>',
			'</header>',
			'<div class="subin-logic-rule-line">',
			'<label><span>Action</span><select data-subin-logic-bind="action">',
			logicOption('', 'Choose an action', rule.action),
			logicOption('show', 'Show', rule.action),
			logicOption('hide', 'Hide', rule.action),
			'</select></label>',
			'<label><span>Target</span><select data-subin-logic-bind="target">',
			renderSubmissionInboxLogicTargetOptions(targets, rule.target),
			'</select></label>',
			'<button type="button" class="button" data-subin-logic-action="select-target">Select target</button>',
			'</div>',
			'<p class="subin-logic-summary" data-subin-logic-summary>',
			escapeSubmissionInboxLogicHtml(submissionInboxLogicRuleSummary(rule, targets, fields)),
			'</p>',
	].join('');

		(rule.groups || []).forEach(function (group, groupIndex) {
			html += renderSubmissionInboxLogicGroup(group, groupIndex, fields, (rule.groups || []).length);
		});

		html += [
			'<button type="button" class="button" data-subin-logic-action="add-group">Add OR group</button>',
			'<div data-subin-logic-validation>', renderSubmissionInboxLogicErrors(errors), '</div>',
			'</article>',
		].join('');

		return html;
	}

	function renderSubmissionInboxLogicGroup(group, groupIndex, fields, groupCount) {
		var html = [
			'<fieldset class="subin-logic-group" data-subin-logic-group-index="', String(groupIndex), '">',
			'<legend>', groupIndex ? 'OR group ' + String(groupIndex + 1) : 'When', '</legend>',
			'<div class="subin-logic-group__toolbar">',
			'<label><span>Match</span><select data-subin-logic-bind="match">',
			logicOption('', 'Choose how to match', group.match),
			logicOption('all', 'All conditions', group.match),
			logicOption('any', 'Any condition', group.match),
			'</select></label>',
			groupCount > 1 ? '<button type="button" class="button-link-delete" data-subin-logic-action="remove-group">Remove group</button>' : '',
			'</div>',
		].join('');

		(group.conditions || []).forEach(function (condition, conditionIndex) {
			html += renderSubmissionInboxLogicCondition(condition, conditionIndex, fields, (group.conditions || []).length);
		});

		html += '<button type="button" class="button" data-subin-logic-action="add-condition">Add condition</button></fieldset>';
		return html;
	}

	function renderSubmissionInboxLogicCondition(condition, conditionIndex, fields, conditionCount) {
		var field = fields.filter(function (candidate) { return candidate.id === condition.field; })[0] || null;
		var operators = submissionInboxLogicOperatorsForField(field);
		var html = [
			'<div class="subin-logic-condition" data-subin-logic-condition-index="', String(conditionIndex), '">',
			'<label><span>Field</span><select data-subin-logic-bind="field">',
			renderSubmissionInboxLogicFieldOptions(fields, condition.field),
			'</select></label>',
			'<label><span>Operator</span><select data-subin-logic-bind="operator">',
		].join('');

		html += logicOption('', 'Choose an operator', condition.operator);
		operators.forEach(function (operator) {
			html += logicOption(operator, LOGIC_OPERATOR_LABELS[operator], condition.operator);
		});
		html += '</select></label>';
		html += renderSubmissionInboxLogicValueControl(condition, field);
		if (conditionCount > 1) {
			html += '<button type="button" class="button-link-delete" data-subin-logic-action="remove-condition">Remove condition</button>';
		}
		html += '</div>';

		return html;
	}

	function renderSubmissionInboxLogicValueControl(condition, field) {
		var noValue = condition.operator === 'empty' || condition.operator === 'not_empty';
		var options = field && field.options ? field.options : [];
		var values = Array.isArray(condition.value) ? condition.value.map(String) : [String(condition.value || '')];
		var missing;
		var html;

		if (noValue) {
			return '<span class="subin-logic-value subin-logic-value--unused">No value needed</span>';
		}

		if (options.length) {
			html = '<label class="subin-logic-value"><span>' + (Array.isArray(condition.value) ? 'Values' : 'Value') + '</span><select data-subin-logic-bind="value"' + (Array.isArray(condition.value) ? ' multiple="multiple" size="' + String(Math.min(4, Math.max(2, options.length))) + '"' : '') + '>';
			if (!Array.isArray(condition.value)) {
				html += logicOption('', 'Choose a value', condition.value);
			}
			options.forEach(function (option) {
				html += logicOption(option.value, option.label, condition.value);
			});
			missing = values.filter(function (current) {
				return current && !options.some(function (option) { return option.value === current; });
			});
			missing.forEach(function (current) {
				html += logicOption(current, current + ' (missing)', condition.value);
			});
			return html + '</select></label>';
		}

		if (Array.isArray(condition.value)) {
			return '<label class="subin-logic-value"><span>Values (JSON list)</span><textarea rows="3" data-subin-logic-bind="value" data-subin-logic-value-format="json-array">' + escapeSubmissionInboxLogicHtml(JSON.stringify(condition.value)) + '</textarea></label>';
		}

		return '<label class="subin-logic-value"><span>Value</span><input type="text" data-subin-logic-bind="value" value="' + escapeSubmissionInboxLogicAttribute(condition.value || '') + '" /></label>';
	}

	function renderSubmissionInboxLogicTargetOptions(targets, target) {
		var current = submissionInboxLogicTargetKey(target);
		var html = logicOption('', 'Choose an element', current);

		targets.forEach(function (descriptor) {
			html += logicOption(descriptor.key, descriptor.label, current);
		});

		return html;
	}

	function renderSubmissionInboxLogicFieldOptions(fields, selected) {
		var html = logicOption('', 'Choose a field', selected);

		fields.forEach(function (field) {
			html += logicOption(field.id, field.label, selected);
		});

		return html;
	}

	function logicOption(value, label, selected) {
		var selectedValues = Array.isArray(selected) ? selected.map(String) : [String(selected || '')];

		return '<option value="' + escapeSubmissionInboxLogicAttribute(value) + '"' +
			(selectedValues.indexOf(String(value)) !== -1 ? ' selected="selected"' : '') +
			'>' + escapeSubmissionInboxLogicHtml(label) + '</option>';
	}

	function renderSubmissionInboxLogicErrors(errors) {
		if (!errors || !errors.length) {
			return '<p class="subin-logic-validation is-valid" role="status" aria-live="polite">Rule is valid.</p>';
		}

		return '<ul class="subin-logic-validation is-invalid" role="status" aria-live="polite"><li>' + errors.map(escapeSubmissionInboxLogicHtml).join('</li><li>') + '</li></ul>';
	}

	function updateSubmissionInboxLogicSummaries(state) {
		var panel = state.panel;
		var descriptors;
		var targets;
		var fields;
		var validation;

		if (!panel || !panel.querySelectorAll) {
			return;
		}

		descriptors = collectSubmissionInboxLogicComponents(state.editor);
		targets = descriptors.filter(function (descriptor) { return descriptor.target; });
		fields = descriptors.filter(function (descriptor) { return descriptor.type === 'field'; });
		validation = validateSubmissionInboxLogicConfig(state.config, descriptors);
		panel.querySelectorAll('[data-subin-logic-rule-index]').forEach(function (card, index) {
			var summary = card.querySelector ? card.querySelector('[data-subin-logic-summary]') : null;
			var validationHost = card.querySelector ? card.querySelector('[data-subin-logic-validation]') : null;
			var nextValidation = renderSubmissionInboxLogicErrors(validation.rules[index] || []);

			if (summary && state.config.rules[index]) {
				summary.textContent = submissionInboxLogicRuleSummary(state.config.rules[index], targets, fields);
			}
			if (validationHost && validationHost.innerHTML !== nextValidation) {
				validationHost.innerHTML = nextValidation;
			}
		});
	}

	function submissionInboxLogicRuleSummary(rule, targets, fields) {
		var targetKey = submissionInboxLogicTargetKey(rule.target);
		var target = targets.filter(function (candidate) { return candidate.key === targetKey; })[0];
		var action = rule.action === 'hide' ? 'Hide' : (rule.action === 'show' ? 'Show' : 'Choose an action for');
		var groups = (rule.groups || []).map(function (group) {
			var joiner = group.match === 'any' ? ' OR ' : ' AND ';
			var conditions = (group.conditions || []).map(function (condition) {
				var field = fields.filter(function (candidate) { return candidate.id === condition.field; })[0];
				var operator = LOGIC_OPERATOR_LABELS[condition.operator] || condition.operator;
				var suffix = condition.operator === 'empty' || condition.operator === 'not_empty' ? '' : ' "' + String(condition.value || '') + '"';

				return (field ? field.label : 'Missing field') + ' ' + operator + suffix;
			});

			return '(' + (conditions.join(joiner) || 'incomplete condition') + ')';
		});

		return action + ' ' + (target ? target.label : 'missing target') + ' when ' + (groups.join(' OR ') || 'conditions are incomplete') + '.';
	}

	function validateSubmissionInboxLogicConfig(config, descriptors) {
		var core = submissionInboxLogicCore();
		var targets = Object.create(null);
		var fields = Object.create(null);
		var componentCounts = Object.create(null);
		var knownTargets = {field: [], row: [], block: []};
		var rules = (config && config.rules) || [];
		var errors = rules.map(function () { return []; });
		var dependencyGraph = Object.create(null);

		(descriptors || []).forEach(function (descriptor) {
			componentCounts[descriptor.key] = (componentCounts[descriptor.key] || 0) + 1;
			if (descriptor.target) {
				targets[descriptor.key] = descriptor;
				if (knownTargets[descriptor.type] && knownTargets[descriptor.type].indexOf(descriptor.id) === -1) {
					knownTargets[descriptor.type].push(descriptor.id);
				}
			}
			if (descriptor.type === 'field') {
				fields[descriptor.id] = descriptor;
			}
		});

		rules.forEach(function (rule, ruleIndex) {
			var targetKey = submissionInboxLogicTargetKey(rule.target);
			var groups = rule.groups || [];

			if (rule.enabled === false) {
				return;
			}

			if (!targetKey || !targets[targetKey]) {
				errors[ruleIndex].push('Choose an existing target.');
			} else if (componentCounts[targetKey] > 1) {
				errors[ruleIndex].push('The target identity is duplicated in the form.');
			}
			if (!groups.length) {
				errors[ruleIndex].push('Add at least one condition group.');
			}

			groups.forEach(function (group) {
				if (!group.conditions || !group.conditions.length) {
					errors[ruleIndex].push('Each group needs at least one condition.');
					return;
				}
				group.conditions.forEach(function (condition) {
					var sourceField = condition.field ? fields[condition.field] : null;

					if (!condition.field || !fields[condition.field]) {
						errors[ruleIndex].push('Choose an existing source field.');
					} else if (
						targets[targetKey] &&
						fields[condition.field].position >= targets[targetKey].position
					) {
						errors[ruleIndex].push('Choose a source field placed before the target.');
					}
					if (LOGIC_OPERATORS.indexOf(condition.operator) === -1) {
						errors[ruleIndex].push('Choose a supported operator.');
					}
					if (['empty', 'not_empty'].indexOf(condition.operator) === -1 && String(condition.value || '') === '') {
						errors[ruleIndex].push('Enter a comparison value.');
					}
					if (
						sourceField &&
						sourceField.options &&
						sourceField.options.length &&
						['is', 'is_not'].indexOf(condition.operator) !== -1 &&
						core &&
						typeof core.normalizeValue === 'function'
					) {
						var allowedValues = sourceField.options.map(function (option) { return option.value; });
						var comparisonValues = core.normalizeValue(condition.value);

						if (comparisonValues.some(function (value) { return allowedValues.indexOf(value) === -1; })) {
							errors[ruleIndex].push('Choose a value that still exists for this field.');
						}
					}
					if (rule.target && rule.target.type === 'field' && rule.target.id && condition.field) {
						dependencyGraph[rule.target.id] = dependencyGraph[rule.target.id] || [];
						dependencyGraph[rule.target.id].push(condition.field);
					}
				});
			});
		});

		if (core && typeof core.validate === 'function') {
			core.validate(config).forEach(function (error) {
				var match = /^rules\.(\d+)/.exec(String(error.path || ''));
				var index = match ? parseInt(match[1], 10) : -1;
				var message = String(error.message || 'Conditional rule is invalid.');

				if (errors[index]) {
					errors[index].push(message);
				} else if (errors.length) {
					errors[0].push(message);
				}
			});
			rules.forEach(function (rule, ruleIndex) {
				if (rule.enabled === false) {
					return;
				}

				core.validate({version: 1, rules: [rule]}, Object.keys(fields), knownTargets).forEach(function (error) {
					var message = String(error.message || 'Conditional rule is invalid.');

					errors[ruleIndex].push(message);
				});
			});
		}

		var cyclicFields = submissionInboxLogicCyclicFields(dependencyGraph);
		rules.forEach(function (rule, ruleIndex) {
			if (rule.target && rule.target.type === 'field' && cyclicFields[rule.target.id]) {
				errors[ruleIndex].push('Circular field dependency detected.');
			}
			errors[ruleIndex] = errors[ruleIndex].filter(function (message, index, list) {
				return list.indexOf(message) === index;
			});
		});

		return {
			valid: errors.every(function (messages) { return messages.length === 0; }),
			rules: errors,
		};
	}

	function submissionInboxLogicCyclicFields(graph) {
		var visiting = Object.create(null);
		var visited = Object.create(null);
		var cyclic = Object.create(null);
		var stack = [];

		function visit(field) {
			var cycleIndex;

			if (visiting[field]) {
				cycleIndex = stack.indexOf(field);
				stack.slice(cycleIndex < 0 ? 0 : cycleIndex).forEach(function (current) {
					cyclic[current] = true;
				});
				cyclic[field] = true;
				return;
			}
			if (visited[field]) {
				return;
			}

			visiting[field] = true;
			stack.push(field);
			(graph[field] || []).forEach(visit);
			stack.pop();
			delete visiting[field];
			visited[field] = true;
		}

		Object.keys(graph).forEach(visit);
		return cyclic;
	}

	function submissionInboxLogicOperatorsForField(field) {
		var type = field ? field.fieldType : '';
		var operators = ['is', 'is_not'];

		if (['text', 'textarea', 'email', 'phone', 'hidden'].indexOf(type) !== -1) {
			operators.push('contains', 'not_contains');
		}
		operators.push('empty', 'not_empty');
		if (type === 'date') {
			operators.push('before', 'after');
		}

		return operators;
	}

	function collectSubmissionInboxLogicComponents(editor) {
		var wrapper = editor && editor.getWrapper ? editor.getWrapper() : null;
		var descriptors = [];
		var position = 0;
		var rowPosition = 0;

		if (!wrapper) {
			return descriptors;
		}

		walkSubmissionInboxLogicComponents(wrapper.components ? wrapper.components() : [], function (component) {
			var descriptor = descriptorForSubmissionInboxLogicComponent(component, position++, true);

			if (descriptor) {
				if (descriptor.type === 'row') {
					descriptor.label = submissionInboxLogicRowLabel(component, rowPosition);
					rowPosition += 1;
				}
				descriptors.push(descriptor);
			}
		});

		return descriptors;
	}

	function walkSubmissionInboxLogicComponents(components, callback) {
		if (!components || !components.forEach) {
			return;
		}

		components.forEach(function (component) {
			callback(component);
			walkSubmissionInboxLogicComponents(component.components ? component.components() : [], callback);
		});
	}

	function descriptorForSubmissionInboxLogicComponent(component, position, ensureIdentity) {
		var attributes;
		var subinType;
		var descriptor;

		if (!component || !component.getAttributes) {
			return null;
		}

		attributes = component.getAttributes() || {};
		subinType = attributes['data-subin-component'] || componentSubmissionInboxLogicType(component);
		if (subinType === 'form-wrapper' || subinType === 'submit-button' || !subinType) {
			return null;
		}

		if (subinType === 'field') {
			descriptor = {
				component: component,
				fieldType: String(attributes['data-subin-field-type'] || 'text'),
				id: String(attributes['data-subin-field-key'] || attributes['data-subin-field-id'] || attributes['data-subin-temp-id'] || ''),
				label: String(attributes['data-subin-label'] || attributes['data-subin-field-label'] || attributes['data-subin-field-key'] || 'Field ' + String(position + 1)),
				options: parseSubmissionInboxLogicOptions(attributes['data-subin-options']),
				target: true,
				type: 'field',
			};
			descriptor.label += descriptor.label !== descriptor.id ? ' [' + descriptor.id + ']' : '';
		} else if (subinType === 'row') {
			descriptor = {
				component: component,
				id: String(attributes['data-subin-row-id'] || attributes['data-subin-style-id'] || ''),
				label: submissionInboxLogicRowLabel(component, position),
				options: [],
				target: true,
				type: 'row',
			};
		} else {
			descriptor = {
				component: component,
				id: String(attributes['data-subin-logic-id'] || attributes['data-subin-style-id'] || ''),
				label: submissionInboxLogicBlockLabel(component, subinType, position),
				options: [],
				target: true,
				type: 'block',
			};

			if (!descriptor.id && ensureIdentity && component.addAttributes) {
				descriptor.id = submissionInboxLogicUniqueId('block');
				component.addAttributes({'data-subin-logic-id': descriptor.id});
			}
		}

		if (!descriptor.id) {
			return null;
		}

		descriptor.key = descriptor.type + ':' + descriptor.id;
		descriptor.position = position;
		descriptor.element = submissionInboxLogicComponentElement(component);
		return descriptor;
	}

	function componentSubmissionInboxLogicType(component) {
		var type = component.get ? component.get('type') : '';
		var map = {
			'subin-field': 'field',
			'subin-row': 'row',
			'subin-submit-button': 'submit-button',
			'subin-text-block': 'text-block',
			'subin-html-block': 'html-block',
			'subin-notice-block': 'notice',
			'subin-divider-block': 'divider',
			'subin-privacy-block': 'privacy',
			'subin-section-block': 'section',
			'subin-spacer-block': 'spacer',
			'subin-image-block': 'image-block',
			wrapper: 'form-wrapper',
		};

		return map[type] || '';
	}

	function submissionInboxLogicRowLabel(component, position) {
		var labels = [];
		var children = component && component.components ? component.components() : [];
		var base = 'Row ' + String(position + 1);

		walkSubmissionInboxLogicComponents(children, function (child) {
			var attributes;
			var type;
			var label;

			if (!child || !child.getAttributes) {
				return;
			}
			attributes = child.getAttributes() || {};
			type = attributes['data-subin-component'] || componentSubmissionInboxLogicType(child);
			if (type !== 'field') {
				return;
			}
			label = String(attributes['data-subin-label'] || attributes['data-subin-field-label'] || attributes['data-subin-field-key'] || '').trim();
			if (label && labels.indexOf(label) === -1) {
				labels.push(label);
			}
		});

		return labels.length
			? base + ' - ' + labels.slice(0, 2).join(', ') + (labels.length > 2 ? ' +' + String(labels.length - 2) + ' more' : '')
			: base;
	}

	function submissionInboxLogicBlockLabel(component, subinType, position) {
		var name = component.get ? component.get('name') : '';

		if (name) {
			return String(name);
		}

		return String(subinType || 'Block').replace(/-/g, ' ').replace(/^./, function (letter) {
			return letter.toUpperCase();
		}) + ' ' + String(position + 1);
	}

	function submissionInboxLogicComponentElement(component) {
		if (component.getEl) {
			return component.getEl();
		}

		return component.view && component.view.el ? component.view.el : null;
	}

	function parseSubmissionInboxLogicOptions(value) {
		var parsed = value;

		if (typeof parsed === 'string') {
			try {
				parsed = JSON.parse(parsed);
			} catch (error) {
				return [];
			}
		}
		if (!Array.isArray(parsed)) {
			return [];
		}

		return parsed.map(function (option) {
			if (option && typeof option === 'object') {
				return {
					label: String(option.label !== undefined ? option.label : option.value || ''),
					value: String(option.value !== undefined ? option.value : option.label || ''),
				};
			}

			return {label: String(option), value: String(option)};
		}).filter(function (option) {
			return option.value !== '' || option.label !== '';
		});
	}

	function selectSubmissionInboxLogicTarget(state, target) {
		var key = submissionInboxLogicTargetKey(target);
		var descriptor = collectSubmissionInboxLogicComponents(state.editor).filter(function (candidate) {
			return candidate.key === key;
		})[0];

		if (descriptor && state.editor && state.editor.select) {
			state.editor.select(descriptor.component);
		}
	}

	function submissionInboxLogicProjectData(editor, project) {
		var state = editor && editor.__subinLogicState;
		var output = project && typeof project === 'object' && !Array.isArray(project) ? project : {};

		if (!submissionInboxLogicCore()) {
			stripSubmissionInboxLogicHistory(output, 0);
			return output;
		}
		if (!state) {
			if (output.subinLogic) {
				output.subinLogic = normalizeSubmissionInboxLogicConfig(output.subinLogic);
			}
			stripSubmissionInboxLogicHistory(output, 0);
			return output;
		}

		output.subinLogic = cloneSubmissionInboxLogic(state.config);
		stripSubmissionInboxLogicHistory(output, 0);
		if (state.formData) {
			state.formData.grapesjs_project = output;
		}
		return output;
	}

	function hydrateSubmissionInboxLogic(editor, project, formData) {
		var state;
		var sourceProject = project;

		if (!editor || !submissionInboxLogicCore()) {
			return null;
		}

		if (project && project.grapesjs_project && !formData) {
			formData = project;
			sourceProject = project.grapesjs_project;
		}
		state = editor.__subinLogicState || initSubmissionInboxLogic(editor, formData || null);
		if (!state) {
			return null;
		}
		if (formData) {
			state.formData = formData;
		}
		if (!sourceProject || typeof sourceProject !== 'object' || Array.isArray(sourceProject)) {
			sourceProject = state.formData && state.formData.grapesjs_project ? state.formData.grapesjs_project : {};
		}

		var nextConfig = normalizeSubmissionInboxLogicConfig(sourceProject.subinLogic);
		if (JSON.stringify(nextConfig) === JSON.stringify(state.config)) {
			syncSubmissionInboxLogicFormData(state);
			refreshSubmissionInboxLogicPreview(editor);
			return cloneSubmissionInboxLogic(state.config);
		}

		state.config = nextConfig;
		syncSubmissionInboxLogicFormData(state);
		syncSubmissionInboxLogicHistory(state, true);
		renderSubmissionInboxLogicPanel(state);
		refreshSubmissionInboxLogicPreview(editor);
		return cloneSubmissionInboxLogic(state.config);
	}

	function collectSubmissionInboxLogicValues(editor, visibleOnly) {
		var values = Object.create(null);

		collectSubmissionInboxLogicComponents(editor).filter(function (descriptor) {
			return descriptor.type === 'field' && (!visibleOnly || submissionInboxLogicFieldIsVisible(descriptor));
		}).forEach(function (field) {
			values[field.id] = submissionInboxLogicFieldValue(field);
		});

		return values;
	}

	function submissionInboxLogicFieldIsVisible(field) {
		var element = field && field.element;
		var node;

		if (!element) {
			return true;
		}
		if (element.hidden || (element.classList && element.classList.contains('subin-gjs-logic-hidden'))) {
			return false;
		}
		if (element.closest && element.closest('.subin-gjs-logic-hidden')) {
			return false;
		}
		for (node = element.parentElement; node; node = node.parentElement) {
			if (node.hidden || (node.classList && node.classList.contains('subin-gjs-logic-hidden'))) {
				return false;
			}
		}
		return true;
	}

	function submissionInboxLogicFieldValue(field) {
		var element = field.element;
		var controls;
		var checked;

		if (!element || !element.querySelectorAll) {
			return '';
		}
		controls = Array.prototype.slice.call(element.querySelectorAll('[data-subin-preview-control]'));
		if (!controls.length) {
			var attributes = field.component.getAttributes ? field.component.getAttributes() : {};
			return String(attributes['data-subin-default-value'] || '');
		}

		if (field.fieldType === 'checkbox') {
			return controls.filter(function (control) { return !!control.checked; }).map(function (control) {
				return submissionInboxLogicChoiceValue(control, field.options[controls.indexOf(control)]);
			});
		}
		if (field.fieldType === 'radio') {
			checked = controls.filter(function (control) { return !!control.checked; })[0];
			return checked ? submissionInboxLogicChoiceValue(checked, field.options[controls.indexOf(checked)]) : '';
		}
		if (field.fieldType === 'consent') {
			return controls[0].checked ? '1' : '';
		}

		return controls[0].value === undefined || controls[0].value === null ? '' : String(controls[0].value);
	}

	function submissionInboxLogicChoiceValue(control, option) {
		if (option) {
			return option.value;
		}
		if (control.value && control.value !== 'on') {
			return String(control.value);
		}

		return '';
	}

	function refreshSubmissionInboxLogicPreview(editor, values, forceActive) {
		var state = editor && editor.__subinLogicState;
		var core = submissionInboxLogicCore();
		var descriptors;
		var targetMap = Object.create(null);
		var targetCounts = Object.create(null);
		var invalidTargets = Object.create(null);
		var validation;
		var previewConfig;
		var documentInvalid = false;
		var active;
		var suppliedValues = values && typeof values === 'object';
		var visibility = {};
		var previousSignature = '';
		var converged = false;
		var failed = false;
		var nextValues;
		var signature;
		var limit;
		var pass;

		if (!state || !core || (typeof core.evaluate !== 'function' && typeof core.isTargetVisible !== 'function' && typeof core.targetVisible !== 'function')) {
			return null;
		}

		descriptors = collectSubmissionInboxLogicComponents(editor);
		validation = validateSubmissionInboxLogicConfig(state.config, descriptors);
		previewConfig = {version: LOGIC_VERSION, rules: []};
		if (typeof core.validate === 'function') {
			documentInvalid = core.validate(state.config).some(function (error) {
				return !/^rules\.\d+/.test(String(error.path || ''));
			});
		}
		descriptors.forEach(function (descriptor) {
			targetMap[descriptor.key] = targetMap[descriptor.key] || [];
			targetMap[descriptor.key].push(descriptor);
			clearSubmissionInboxLogicElementState(descriptor.element);
		});
		(state.config.rules || []).forEach(function (rule, ruleIndex) {
			var key = submissionInboxLogicTargetKey(rule.target);

			if (rule.enabled === false) {
				return;
			}
			targetCounts[key] = (targetCounts[key] || 0) + 1;
			if (documentInvalid || (validation.rules[ruleIndex] && validation.rules[ruleIndex].length)) {
				invalidTargets[key] = true;
			} else {
				previewConfig.rules.push(rule);
			}
		});

		Object.keys(targetCounts).forEach(function (key) {
			var matches = targetMap[key] || [];
			var rules = state.config.rules.filter(function (rule) {
				return rule.enabled !== false && submissionInboxLogicTargetKey(rule.target) === key;
			});

			matches.forEach(function (descriptor) {
				markSubmissionInboxLogicElement(descriptor.element, rules, matches.length > 1 || !!invalidTargets[key]);
			});
		});

		active = forceActive === undefined ? !!editor.__subinInteractivePreviewActive : !!forceActive;
		values = suppliedValues ? values : collectSubmissionInboxLogicValues(editor, false);
		if (!active) {
			return values;
		}

		limit = suppliedValues ? 1 : Math.max(2, previewConfig.rules.length + 2);
		for (pass = 0; pass < limit; pass++) {
			try {
				visibility = typeof core.evaluate === 'function' ? core.evaluate(previewConfig, values) : {};
			} catch (error) {
				visibility = {};
				failed = true;
			}

			Object.keys(targetCounts).forEach(function (key) {
				var matches = targetMap[key] || [];
				var descriptor = matches[0];
				var rules;
				var visible = true;

				if (matches.length !== 1) {
					return;
				}
				if (invalidTargets[key]) {
					visible = true;
				} else if (typeof core.evaluate === 'function') {
					visible = !visibility[descriptor.type] ||
						!Object.prototype.hasOwnProperty.call(visibility[descriptor.type], descriptor.id) ||
						visibility[descriptor.type][descriptor.id] !== false;
				} else if (typeof core.isTargetVisible === 'function') {
					visible = core.isTargetVisible(descriptor.type, descriptor.id, previewConfig, values) !== false;
				} else {
					rules = previewConfig.rules.filter(function (rule) {
						return rule.enabled !== false && submissionInboxLogicTargetKey(rule.target) === key;
					});
					visible = rules.every(function (rule) {
						return core.targetVisible(rule, values) !== false;
					});
				}
				setSubmissionInboxLogicElementVisible(descriptor.element, visible);
			});

			if (failed || suppliedValues) {
				converged = !failed;
				break;
			}

			nextValues = collectSubmissionInboxLogicValues(editor, true);
			signature = JSON.stringify(visibility) + '|' + JSON.stringify(nextValues);
			values = nextValues;
			if (signature === previousSignature) {
				converged = true;
				break;
			}
			previousSignature = signature;
		}

		if (!converged) {
			Object.keys(targetCounts).forEach(function (key) {
				var matches = targetMap[key] || [];
				if (matches.length === 1) {
					setSubmissionInboxLogicElementVisible(matches[0].element, true);
				}
			});
		}

		return values;
	}

	function clearSubmissionInboxLogicElementState(element) {
		if (!element) {
			return;
		}
		if (element.classList) {
			element.classList.remove('subin-gjs-logic-target');
			element.classList.remove('subin-gjs-logic-target--invalid');
			element.classList.remove('subin-gjs-logic-hidden');
		}
		if (element.removeAttribute) {
			element.removeAttribute('data-subin-logic-marker');
			element.removeAttribute('data-subin-logic-summary');
			element.removeAttribute('aria-hidden');
		}
		element.hidden = false;
	}

	function markSubmissionInboxLogicElement(element, rules, invalid) {
		if (!element) {
			return;
		}
		if (element.classList) {
			element.classList.add('subin-gjs-logic-target');
			element.classList.toggle('subin-gjs-logic-target--invalid', !!invalid);
		}
		if (element.setAttribute) {
			element.setAttribute('data-subin-logic-marker', String(rules.length));
			element.setAttribute('data-subin-logic-summary', rules.length === 1 ? '1 conditional rule' : String(rules.length) + ' conditional rules');
		}
	}

	function setSubmissionInboxLogicElementVisible(element, visible) {
		if (!element) {
			return;
		}
		element.hidden = !visible;
		if (element.classList) {
			element.classList.toggle('subin-gjs-logic-hidden', !visible);
		}
		if (element.setAttribute && !visible) {
			element.setAttribute('aria-hidden', 'true');
		} else if (element.removeAttribute) {
			element.removeAttribute('aria-hidden');
		}
	}

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

	function escapeSubmissionInboxLogicAttribute(value) {
		return escapeSubmissionInboxLogicHtml(value).replace(/`/g, '&#096;');
	}

	root.initSubmissionInboxLogic = initSubmissionInboxLogic;
	root.submissionInboxLogicProjectData = submissionInboxLogicProjectData;
	root.hydrateSubmissionInboxLogic = hydrateSubmissionInboxLogic;
	root.refreshSubmissionInboxLogicPreview = refreshSubmissionInboxLogicPreview;
	root.SubmissionInboxLogicEditor = {
		collectComponents: collectSubmissionInboxLogicComponents,
		collectValues: collectSubmissionInboxLogicValues,
		commitChange: commitSubmissionInboxLogicChange,
		emptyConfig: emptySubmissionInboxLogicConfig,
		normalizeConfig: normalizeSubmissionInboxLogicConfig,
		ruleSummary: submissionInboxLogicRuleSummary,
		updateSummaries: updateSubmissionInboxLogicSummaries,
		validateConfig: validateSubmissionInboxLogicConfig,
	};
})(window);
