import isValidReplacement from './is-valid-replacement.mjs';

const CSS_CLASS_LOADED = 'js-blank-pseudo';

// form control elements selector
function isFormControlElement(element) {
	if (element.nodeName === 'INPUT' || element.nodeName === 'SELECT' || element.nodeName === 'TEXTAREA') {
		return true;
	}

	return false;
}

function createNewEvent(eventName) {
	let event;

	if (typeof(Event) === 'function') {
		event = new Event(eventName, { bubbles: true });
	} else {
		event = document.createEvent('Event');
		event.initEvent(eventName, true, false);
	}

	return event;
}

function generateHandler(replaceWith) {
	let selector;
	let remove;
	let add;

	if (replaceWith[0] === '.') {
		selector = replaceWith.slice(1);
		remove = (el) => el.classList.remove(selector);
		add = (el) => el.classList.add(selector);
	} else {
		// A bit naive
		selector = replaceWith.slice(1, -1);
		remove = (el) => el.removeAttribute(selector, '');
		add = (el) => el.setAttribute(selector, '');
	}

	return function handleInputOrChangeEvent(event) {
		const element = event.target;
		if (!isFormControlElement(element)) {
			return;
		}

		const isSelect = element.nodeName === 'SELECT';
		const hasValue = isSelect
			? !!element.options[element.selectedIndex]?.value
			: !!element.value;

		if (hasValue) {
			remove(element);
		} else {
			add(element);
		}
	};
}

// observe changes to the "selected" property on an HTML Element
function observeSelectedOfHTMLElement(HTMLElement) {
	const descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'selected');
	const nativeSet = descriptor.set;

	descriptor.set = function set(value) { // eslint-disable-line no-unused-vars
		nativeSet.apply(this, arguments);

		const event = createNewEvent('change');
		this.parentElement.dispatchEvent(event);
	};

	Object.defineProperty(HTMLElement.prototype, 'selected', descriptor);
}

// observe changes to the "value" property on an HTML Element
function observeValueOfHTMLElement(HTMLElement, handler) {
	const descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'value');
	const nativeSet = descriptor.set;

	descriptor.set = function set() {
		nativeSet.apply(this, arguments);
		handler({ target: this });
	};

	Object.defineProperty(HTMLElement.prototype, 'value', descriptor);
}

export default function cssBlankPseudoInit(opts) {
	// configuration
	const options = {
		force: false,
		replaceWith: '[blank]',
	};

	if (typeof opts !== 'undefined' && 'force' in opts) {
		options.force = opts.force;
	}

	if (typeof opts !== 'undefined' && 'replaceWith' in opts) {
		options.replaceWith = opts.replaceWith;
	}

	if (!isValidReplacement(options.replaceWith)) {
		throw new Error(`${options.replaceWith} is not a valid replacement since it can't be applied to single elements.`);
	}

	try {
		document.querySelector(':blank');

		if (!options.force) {
			return;
		}
	} catch (_) {}

	const handler = generateHandler(options.replaceWith);
	const bindEvents = () => {
		if (document.body) {
			document.body.addEventListener('change', handler);
			document.body.addEventListener('input', handler);
		}
	};
	const updateAllCandidates = () => {
		Array.prototype.forEach.call(
			document.querySelectorAll('input, select, textarea'),
			node => {
				handler({ target: node });
			},
		);
	};

	if (document.body) {
		bindEvents();
	} else {
		window.addEventListener('load', bindEvents);
	}

	if (document.documentElement.className.indexOf(CSS_CLASS_LOADED) === -1) {
		document.documentElement.className += ` ${CSS_CLASS_LOADED}`;
	}

	observeValueOfHTMLElement(self.HTMLInputElement, handler);
	observeValueOfHTMLElement(self.HTMLSelectElement, handler);
	observeValueOfHTMLElement(self.HTMLTextAreaElement, handler);
	observeSelectedOfHTMLElement(self.HTMLOptionElement);

	// conditionally update all form control elements
	updateAllCandidates();

	if (typeof self.MutationObserver !== 'undefined') {
		// conditionally observe added or unobserve removed form control elements
		new MutationObserver(mutationsList => {
			mutationsList.forEach(mutation => {
				Array.prototype.forEach.call(
					mutation.addedNodes || [],
					node => {
						if (node.nodeType === 1 && isFormControlElement(node)) {
							handler({ target: node });
						}
					},
				);
			});
		}).observe(document, { childList: true, subtree: true });
	} else {
		const handleOnLoad = () => updateAllCandidates();

		window.addEventListener('load', handleOnLoad);
		window.addEventListener('DOMContentLoaded', handleOnLoad);
	}
}
