(function (window, document) {
	'use strict';

	var targetSelector = '[data-subin-logic-target]';
	var disabledByLogicProperty = '__subinDisabledByLogic';
	var formObserver = null;

	function engine() {
		return window.SubinConditionalLogic || null;
	}

	function parseConfig(form) {
		var raw = form && form.getAttribute ? form.getAttribute('data-subin-logic') : '';
		var parsed;

		if (!raw) {
			return {version: 1, rules: []};
		}

		try {
			parsed = JSON.parse(raw);
		} catch (error) {
			return {version: 1, rules: []};
		}

		return engine() && engine().sanitize
			? engine().sanitize(parsed)
			: {version: 1, rules: []};
	}

	function parseTarget(value) {
		var match = String(value || '').match(/^(field|row|block):([A-Za-z0-9_.:-]{1,128})$/);

		return match ? {type: match[1], id: match[2]} : null;
	}

	function fieldKey(control) {
		var match = String(control && control.name ? control.name : '').match(/^subin_fields\[([A-Za-z0-9_-]+)\](?:\[\])?$/);

		return match ? match[1] : '';
	}

	function targetVisible(visibility, target) {
		return !target ||
			!visibility[target.type] ||
			!Object.prototype.hasOwnProperty.call(visibility[target.type], target.id) ||
			visibility[target.type][target.id] !== false;
	}

	function controlIsVisible(control, visibility, form) {
		var node = control && control.closest ? control.closest(targetSelector) : null;
		var target;

		while (node && node !== form) {
			target = parseTarget(node.getAttribute('data-subin-logic-target'));
			if (!targetVisible(visibility, target)) {
				return false;
			}
			node = node.parentElement && node.parentElement.closest
				? node.parentElement.closest(targetSelector)
				: null;
		}

		return true;
	}

	function collectValues(form, visibility) {
		var values = Object.create(null);
		var kinds = Object.create(null);
		var controls = form && form.querySelectorAll
			? Array.prototype.slice.call(form.querySelectorAll('input[name^="subin_fields["], select[name^="subin_fields["], textarea[name^="subin_fields["]'))
			: [];

		controls.forEach(function (control) {
			var key = fieldKey(control);
			var type = String(control.type || '').toLowerCase();
			var selected;

			if (!key || !controlIsVisible(control, visibility, form)) {
				return;
			}
			if (control.disabled && !control[disabledByLogicProperty]) {
				return;
			}

			if (type === 'checkbox') {
				kinds[key] = 'array';
				values[key] = Array.isArray(values[key]) ? values[key] : [];
				if (control.checked) {
					values[key].push(String(control.value || '1'));
				}
				return;
			}

			if (type === 'radio') {
				kinds[key] = 'scalar';
				if (control.checked) {
					values[key] = String(control.value || '');
				} else if (!Object.prototype.hasOwnProperty.call(values, key)) {
					values[key] = '';
				}
				return;
			}

			if (control.tagName === 'SELECT' && control.multiple) {
				selected = Array.prototype.slice.call(control.options || []).filter(function (option) {
					return option.selected;
				}).map(function (option) {
					return String(option.value || '');
				});
				kinds[key] = 'array';
				values[key] = selected;
				return;
			}

			if (kinds[key] !== 'array') {
				kinds[key] = 'scalar';
				values[key] = String(control.value || '');
			}
		});

		return values;
	}

	function visibilitySignature(visibility) {
		var rows = [];

		['field', 'row', 'block'].forEach(function (type) {
			Object.keys(visibility[type] || {}).sort().forEach(function (id) {
				rows.push(type + ':' + id + ':' + (visibility[type][id] ? '1' : '0'));
			});
		});

		return rows.join('|');
	}

	function calculateVisibility(form, config) {
		var evaluator = engine();
		var visibility = {};
		var previous = '';
		var current = '';
		var limit = Math.max(2, (config.rules || []).length + 2);
		var index;
		var converged = false;

		if (!evaluator || !evaluator.evaluate) {
			return visibility;
		}

		for (index = 0; index < limit; index++) {
			visibility = evaluator.evaluate(config, collectValues(form, visibility));
			current = visibilitySignature(visibility);
			if (current === previous) {
				converged = true;
				break;
			}
			previous = current;
		}

		return converged ? visibility : {};
	}

	function elementIsVisible(element, visibility, form) {
		var node = element;
		var target;

		while (node && node !== form) {
			if (node.matches && node.matches(targetSelector)) {
				target = parseTarget(node.getAttribute('data-subin-logic-target'));
				if (!targetVisible(visibility, target)) {
					return false;
				}
			}
			node = node.parentElement;
		}

		return true;
	}

	function applyVisibility(form, visibility, releaseInitialTargets) {
		var targets = form && form.querySelectorAll
			? Array.prototype.slice.call(form.querySelectorAll(targetSelector))
			: [];

		targets.forEach(function (element) {
			var visible = elementIsVisible(element, visibility, form);
			var initialGuarded = releaseInitialTargets === false && element.hasAttribute('data-subin-logic-initial-hidden');
			var renderedVisible = visible && !initialGuarded;

			element.hidden = !renderedVisible;
			element.toggleAttribute('data-subin-logic-hidden', !renderedVisible);
			if (renderedVisible) {
				element.removeAttribute('data-subin-logic-initial-hidden');
				element.removeAttribute('aria-hidden');
			} else {
				element.setAttribute('aria-hidden', 'true');
			}
		});

		Array.prototype.slice.call(form.querySelectorAll('button, input, select, textarea')).forEach(function (control) {
			var hidden = !!(control.closest && control.closest('[data-subin-logic-hidden]'));

			if (hidden && !control.disabled) {
				control[disabledByLogicProperty] = true;
				control.disabled = true;
			} else if (!hidden && control[disabledByLogicProperty]) {
				control.disabled = false;
				delete control[disabledByLogicProperty];
			}
		});
	}

	function refresh(form) {
		var state = form && form.__subinLogicState;
		var visibility;
		var raw;

		if (!state) {
			return {};
		}
		raw = form && form.getAttribute ? String(form.getAttribute('data-subin-logic') || '') : '';
		if (raw !== state.rawConfig) {
			state.rawConfig = raw;
			state.config = parseConfig(form);
		}

		visibility = calculateVisibility(form, state.config);
		state.visibility = visibility;
		applyVisibility(form, visibility, state.initialRevealReady);
		return visibility;
	}

	function initForm(form) {
		var config;

		if (!form) {
			return;
		}
		if (form.__subinLogicState) {
			refresh(form);
			return;
		}

		config = parseConfig(form);
		form.__subinLogicState = {
			config: config,
			rawConfig: String(form.getAttribute('data-subin-logic') || ''),
			visibility: {},
			initialRevealReady: document.readyState === 'complete'
		};
		form.addEventListener('input', function () {
			refresh(form);
		});
		form.addEventListener('change', function () {
			refresh(form);
		});
		form.addEventListener('reset', function () {
			window.setTimeout(function () {
				refresh(form);
			}, 0);
		});

		refresh(form);
		form.setAttribute('data-subin-logic-ready', '1');
	}

	function initWithin(root) {
		if (!root) {
			return;
		}
		if (root.matches && root.matches('form[data-subin-logic]')) {
			initForm(root);
		}
		if (root.querySelectorAll) {
			Array.prototype.slice.call(root.querySelectorAll('form[data-subin-logic]')).forEach(initForm);
		}
	}

	function initEventForm(event) {
		var form = event && event.target && event.target.closest
			? event.target.closest('form[data-subin-logic]')
			: null;

		if (form) {
			initForm(form);
		}
	}

	function observeForms() {
		if (
			formObserver ||
			typeof window.MutationObserver !== 'function' ||
			!document.documentElement
		) {
			return;
		}

		formObserver = new window.MutationObserver(function (records) {
			records.forEach(function (record) {
				if (record.type === 'attributes') {
					initWithin(record.target);
					return;
				}
				Array.prototype.slice.call(record.addedNodes || []).forEach(initWithin);
			});
		});
		formObserver.observe(document.documentElement, {
			attributeFilter: ['data-subin-logic'],
			attributes: true,
			childList: true,
			subtree: true
		});
	}

	function initAll() {
		Array.prototype.slice.call(document.querySelectorAll('form[data-subin-logic]')).forEach(initForm);
		observeForms();
	}

	function releaseInitialTargets() {
		Array.prototype.slice.call(document.querySelectorAll('form[data-subin-logic]')).forEach(function (form) {
			if (form.__subinLogicState) {
				form.__subinLogicState.initialRevealReady = true;
			}
			initForm(form);
		});
	}

	window.SubinFrontendLogic = {
		applyVisibility: applyVisibility,
		calculateVisibility: calculateVisibility,
		collectValues: collectValues,
		initAll: initAll,
		initForm: initForm,
		initWithin: initWithin,
		parseConfig: parseConfig,
		parseTarget: parseTarget,
		refresh: refresh
	};

	document.addEventListener('DOMContentLoaded', initAll);
	document.addEventListener('focusin', initEventForm, true);
	document.addEventListener('pointerdown', initEventForm, true);
	window.addEventListener('load', releaseInitialTargets);
	if (document.readyState !== 'loading') {
		initAll();
	}
}(window, document));
