/**
 * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
 * Node module: @schukai/monster
 *
 * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
 * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
 *
 * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
 * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
 * For more information about purchasing a commercial license, please contact Volker Schukai.
 *
 * SPDX-License-Identifier: AGPL-3.0
 */

import { instanceSymbol } from "../../../constants.mjs";
import {
	assembleMethodSymbol,
	registerCustomElement,
} from "../../../dom/customelement.mjs";
import { FilterDateRangeStyleSheet } from "../stylesheet/filter-date-range.mjs";
import { FilterControlsDefaultsStyleSheet } from "../stylesheet/filter-controls-defaults.mjs";
import { AbstractBase } from "./abstract-base.mjs";
import { positionPopper } from "../../form/util/floating-ui.mjs";
import { ATTRIBUTE_ROLE } from "../../../dom/constants.mjs";
import { getDocument } from "../../../dom/util.mjs";
import { STYLE_DISPLAY_MODE_BLOCK } from "../constants.mjs";
import { DeadMansSwitch } from "../../../util/deadmansswitch.mjs";
import {
	addAttributeToken,
	removeAttributeToken,
} from "../../../dom/attributes.mjs";
import { findTargetElementFromEvent } from "../../../dom/events.mjs";
import { getLocaleOfDocument } from "../../../dom/locale.mjs";

export { DateRange };

/**
 * @private
 * @type {symbol}
 */
const timerCallbackSymbol = Symbol("timerCallback");

// /**
//  * @private
//  * @type {symbol}
//  */
// const internalStateSymbol = Symbol("internalState");

/**
 * @private
 * @type {symbol}
 */
const radioInputsElementSymbol = Symbol("radioInputsElement");
/**
 * @private
 * @type {symbol}
 */
const inputInputsElementSymbol = Symbol("inputInputsElement");

/**
 * @private
 * @type {symbol}
 */
const selectInputsElementSymbol = Symbol("selectInputsElement");

/**
 * local symbol
 * @private
 * @type {symbol}
 */
const resizeObserverSymbol = Symbol("resizeObserver");

/**
 * @private
 * @type {symbol}
 */
const controlElementSymbol = Symbol("controlElement");

/**
 * local symbol
 * @private
 * @type {symbol}
 */
const closeEventHandler = Symbol("closeEventHandler");

/**
 * @private
 * @type {symbol}
 */
const inputElementSymbol = Symbol("inputElement");

/**
 * @private
 * @type {symbol}
 */
const formContainerElementSymbol = Symbol("formContainerElement");

/**
 * local symbol
 * @private
 * @type {symbol}
 */
const popperElementSymbol = Symbol("popperElement");

/**
 * local symbol
 * @private
 * @type {symbol}
 */
const arrowElementSymbol = Symbol("arrowElement");

/**
 * The date-range filter control is used to filter a data set by a date range.
 *
 * <img src="./images/data-range.png">
 *
 * Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library
 *
 * You can create this control either by specifying the HTML tag <monster-filter-date-range />` directly in the HTML or using
 * Javascript via the `document.createElement('monster-filter-date-range');` method.
 *
 * ```html
 * <monster-filter-date-range></monster-filter-date-range>
 * ```
 *
 * Or you can create this CustomControl directly in Javascript:
 *
 * ```js
 * import '@schukai/component-datatable/source/filter/date-range.mjs';
 * document.createElement('monster-filter-date-range');
 * ```
 *
 * @startuml data-range.png
 * skinparam monochrome true
 * skinparam shadowing false
 * HTMLElement <|-- CustomElement
 * CustomElement <|-- AbstractBase
 * AbstractBase <|-- DateRange
 * @enduml
 *
 * @copyright Volker Schukai
 * @summary A date range filter control
 */
class DateRange extends AbstractBase {
	// constructor() {
	//     super();
	//     this[internalStateSymbol] = {}
	// }

	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for(
			"@schukai/monster/components/filter/date-range@@instance",
		);
	}

	/**
	 *
	 * @return {FilterButton}
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();

		initControlReferences.call(this);
		initEventHandler.call(this);
	}

	/**
	 * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
	 *
	 * @param {*} value
	 */
	set value(value) {
		this[inputElementSymbol].value = value;
	}

	/**
	 * @return {*}
	 */
	get value() {
		return this[inputElementSymbol].value;
	}

	/**
	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
	 *
	 * The individual configuration values can be found in the table.
	 *
	 * @return {Object}
	 *
	 * @property {Object} templates
	 * @property {string} templates.main
	 * @property {Object} labels
	 * @property {Object} features
	 * @property {boolean} features.moreThan
	 * @property {boolean} features.within
	 * @property {boolean} features.today
	 */
	get defaults() {
		const d = Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},

			labels: getTranslations(),

			features: {
				moreThan: true,
				within: true,
				today: true,
			},

			popper: {
				placement: "bottom",
				middleware: ["flip", "offset:1"],
			},
		});

		return initOptionsFromArguments.call(this, d);
	}

	/**
	 *
	 * @return {CSSStyleSheet[]}
	 */
	static getCSSStyleSheet() {
		return [FilterControlsDefaultsStyleSheet, FilterDateRangeStyleSheet];
	}

	/**
	 *
	 * @return {string}
	 */
	static getTag() {
		return "monster-filter-date-range";
	}

	/**
	 * @return {void}
	 */
	connectedCallback() {
		super.connectedCallback();

		const document = getDocument();

		for (const [, type] of Object.entries(["click", "touch"])) {
			// close on outside ui-events
			document.addEventListener(type, this[closeEventHandler]);
		}

		updatePopper.call(this);
		attachResizeObserver.call(this);
	}

	/**
	 * @return {void}
	 */
	disconnectedCallback() {
		super.disconnectedCallback();

		// close on outside ui-events
		for (const [, type] of Object.entries(["click", "touch"])) {
			document.removeEventListener(type, this[closeEventHandler]);
		}

		disconnectResizeObserver.call(this);
	}

	/**
	 *
	 * @return {Monster.Datatable.Filter.Range}
	 */
	showDialog() {
		show.call(this);
		return this;
	}

	/**
	 *
	 * @return {Monster.Datatable.Filter.Range}
	 */
	hideDialog() {
		hide.call(this);
		return this;
	}

	/**
	 *
	 * @return {Monster.Datatable.Filter.Range}
	 */
	toggleDialog() {
		if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
			this.hideDialog();
		} else {
			this.showDialog();
		}
		return this;
	}
}

function getTranslations() {
	const locale = getLocaleOfDocument();
	switch (locale.language) {
		case "de":
			return {
				singleValue: "Wert",
				fromValue: "Von",
				toValue: "Bis",
				rangeFrom: "Von",
				rangeTo: "Bis",
				todayValue: "Heute",
				withinValue: "Innerhalb",
				months: "Monate",
				days: "Tage",
				years: "Jahre",
				weeks: "Wochen",
				moreThanValue: "Mehr als",
			};
		case "fr":
			return {
				singleValue: "Valeur",
				fromValue: "De",
				toValue: "À",
				rangeFrom: "De",
				rangeTo: "À",
				todayValue: "Aujourd'hui",
				withinValue: "Dans",
				months: "Mois",
				days: "Jours",
				years: "Années",
				weeks: "Semaines",
				moreThanValue: "Plus de",
			};
		case "sp":
			return {
				singleValue: "Valor",
				fromValue: "Desde",
				toValue: "Hasta",
				rangeFrom: "Desde",
				rangeTo: "Hasta",
				todayValue: "Hoy",
				withinValue: "Dentro de",
				months: "Meses",
				days: "Días",
				years: "Años",
				weeks: "Semanas",
				moreThanValue: "Más de",
			};
		case "it":
			return {
				singleValue: "Valore",
				fromValue: "Da",
				toValue: "A",
				rangeFrom: "Da",
				rangeTo: "A",
				todayValue: "Oggi",
				withinValue: "Entro",
				months: "Mesi",
				days: "Giorni",
				years: "Anni",
				weeks: "Settimane",
				moreThanValue: "Più di",
			};
		case "pl":
			return {
				singleValue: "Wartość",
				fromValue: "Od",
				toValue: "Do",
				rangeFrom: "Od",
				rangeTo: "Do",
				todayValue: "Dziś",
				withinValue: "W ciągu",
				months: "Miesiące",
				days: "Dni",
				years: "Lata",
				weeks: "Tygodnie",
				moreThanValue: "Więcej niż",
			};
		case "no":
			return {
				singleValue: "Verdi",
				fromValue: "Fra",
				toValue: "Til",
				rangeFrom: "Fra",
				rangeTo: "Til",
				todayValue: "I dag",
				withinValue: "Innen",
				months: "Måneder",
				days: "Dager",
				years: "År",
				weeks: "Uker",
				moreThanValue: "Mer enn",
			};
		case "dk":
			return {
				singleValue: "Værdi",
				fromValue: "Fra",
				toValue: "Til",
				rangeFrom: "Fra",
				rangeTo: "Til",
				todayValue: "I dag",
				withinValue: "Indenfor",
				months: "Måneder",
				days: "Dage",
				years: "År",
				weeks: "Uger",
				moreThanValue: "Mere end",
			};
		case "sw":
			return {
				singleValue: "Värde",
				fromValue: "Från",
				toValue: "Till",
				rangeFrom: "Från",
				rangeTo: "Till",
				todayValue: "Idag",
				withinValue: "Inom",
				months: "Månader",
				days: "Dagar",
				years: "År",
				weeks: "Veckor",
				moreThanValue: "Mer än",
			};
		default:
		case "en":
			return {
				singleValue: "Value",
				fromValue: "From",
				toValue: "To",
				rangeFrom: "From",
				rangeTo: "To",
				todayValue: "Today",
				withinValue: "Within",
				months: "Months",
				days: "Days",
				years: "Years",
				weeks: "Weeks",
				moreThanValue: "More than",
			};
	}
}

/**
 *
 * @param {HTMLElement} group
 * @param {string} type
 */
function updateMainFilterValueFromPopperChange(group, type) {
	function calculateTargetDate(diff) {
		if (isNaN(diff)) {
			diff = 0;
		}
		const currentDate = new Date();
		currentDate.setDate(currentDate.getDate() + diff);
		return currentDate.toISOString().slice(0, 10);
	}

	if (type === "single") {
		this[inputElementSymbol].value =
			group.querySelector("input[type=date]").value;
	} else if (type === "from") {
		this[inputElementSymbol].value =
			group.querySelector("input[type=date]").value + "-";
	} else if (type === "to") {
		this[inputElementSymbol].value =
			"-" + group.querySelector("input[type=date]").value;
	} else if (type === "from-to") {
		// this option contain two input fields, we check which one was changed and read the other one
		// the other field is in the same form group, for simplification we get both controls from the group here
		const group = this[formContainerElementSymbol].querySelectorAll(
			"input[type=radio][value=" + type + "] ~ input[type=date]",
		);
		let from = group[0].value;

		if (from === "" || from === null || from < 0) {
			from = 0;
		}

		let to = group[1].value;
		if (to === "" || to === null || to < 0) {
			to = 0;
		}

		let range;
		if (from === 0 && to === 0) {
			range = "";
		} else if (from === 0) {
			range = "-" + to;
		} else if (to === 0) {
			range = from + "-";
		} else if (from === to) {
			range = from;
		} else {
			range = from + "-" + to;
		}

		this[inputElementSymbol].value = range;
	} else if (type === "today") {
		this[inputElementSymbol].value = new Date().toISOString().slice(0, 10);
	} else if (type === "within" || type === "more-than") {
		const diffValue = parseInt(group.querySelector("input[type=number]").value);
		const duration = group.querySelector("select").value;

		let diffInDays;
		switch (duration) {
			case "days":
				diffInDays = diffValue;
				break;
			case "weeks":
				diffInDays = diffValue * 7;
				break;
			case "months":
				diffInDays = diffValue * 30;
				break;
			case "years":
				diffInDays = diffValue * 365;
				break;
			default:
				diffInDays = 0;
		}

		if (diffInDays === 0) {
			this[inputElementSymbol].value = "";
			return;
		}

		const suffix = type !== "within" ? "" : "-";
		const prefix = type !== "within" ? "-" : "";

		this[inputElementSymbol].value =
			suffix +
			calculateTargetDate(type === "within" ? diffInDays : -diffInDays) +
			prefix;
	}
}

/**
 * @private
 * @return {initEventHandler}
 */
function initEventHandler() {
	this[closeEventHandler] = (event) => {
		const path = event.composedPath();

		for (const [, element] of Object.entries(path)) {
			if (element === this) {
				return;
			}
		}
		hide.call(this);
	};

	this[inputElementSymbol].addEventListener("click", () => {
		this.toggleDialog();
	});

	this[formContainerElementSymbol].addEventListener("click", (event) => {
		const element = findTargetElementFromEvent(
			event,
			"data-monster-role",
			"range-type",
		);
		if (!element) {
			return;
		}

		const type = element.getAttribute("data-monster-range-type");
		const radio = this[formContainerElementSymbol].querySelector(
			"input[type=radio][value=" + type + "]",
		);
		if (!radio) {
			return;
		}

		radio.checked = true;

		if (type === "today") {
			this[inputElementSymbol].value = new Date().toISOString().slice(0, 10);
			return;
		}

		// enable input from this group and disable the other
		const group = this[formContainerElementSymbol].querySelectorAll(
			"input[type=radio][value=" +
				type +
				"] ~ input, input[type=radio][value=" +
				type +
				"] ~ select",
		);
		for (const [, element] of Object.entries(group)) {
			element.disabled = false;
		}

		const otherGroup = this[formContainerElementSymbol].querySelectorAll(
			"input[type=radio]:not([value=" +
				type +
				"]) ~ input, input[type=radio]:not([value=" +
				type +
				"]) ~ select",
		);
		for (const [, element] of Object.entries(otherGroup)) {
			element.disabled = true;
		}
	});

	// change the input value if the user change the value of the radio button
	this[inputElementSymbol].addEventListener("change", (event) => {
		queueMicrotask(() => {
			updatePopperInputsFromMainValue.call(this);
		});
	});

	// if the user change the value of on of the input fields, we should update the value of the input field
	this[formContainerElementSymbol].addEventListener("change", (event) => {
		const element = findTargetElementFromEvent(
			event,
			"data-monster-watch",
			"true",
		);
		if (!element) {
			return;
		}

		const group = findTargetElementFromEvent(
			event,
			"data-monster-role",
			"range-type",
		);
		if (!group) {
			return;
		}

		const type = group.getAttribute("data-monster-range-type");
		updateMainFilterValueFromPopperChange.call(this, group, type);
	});

	this.addEventListener("keydown", (event) => {
		// if key code esc than hide dialog
		if (event.key === "Escape") {
			hide.call(this);

			queueMicrotask(() => {
				this[inputElementSymbol].focus();
			});

			return;
		}

		const input = findTargetElementFromEvent(
			event,
			"data-monster-role",
			"input",
		);

		if (!input) {
			return;
		}

		// key code down should activate first radio with name="singleValue"
		if (event.key === "ArrowDown") {
			show.call(this);
		}
	});

	return this;
}

/**
 * @private
 */
function attachResizeObserver() {
	// against flickering
	this[resizeObserverSymbol] = new ResizeObserver((entries) => {
		if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
			try {
				this[timerCallbackSymbol].touch();
				return;
			} catch (e) {
				delete this[timerCallbackSymbol];
			}
		}

		this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
			updatePopper.call(this);
		});
	});

	requestAnimationFrame(() => {
		let parent = this.parentNode;
		while (!(parent instanceof HTMLElement) && parent !== null) {
			parent = parent.parentNode;
		}

		if (parent instanceof HTMLElement) {
			this[resizeObserverSymbol].observe(parent);
		}
	});
}

function disconnectResizeObserver() {
	if (this[resizeObserverSymbol] instanceof ResizeObserver) {
		this[resizeObserverSymbol].disconnect();
	}
}

/**
 * @private
 */
function hide() {
	this[popperElementSymbol].style.display = "none";
	removeAttributeToken(this[controlElementSymbol], "class", "open");
}

/**
 * @private
 * @this PopperButton
 */
function show() {
	if (this.getOption("disabled", false) === true) {
		return;
	}

	if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
		return;
	}

	this[popperElementSymbol].style.visibility = "hidden";
	this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;

	addAttributeToken(this[controlElementSymbol], "class", "open");

	// if value is "" than we should clear the form
	if (this[inputElementSymbol].value === "") {
		clearAndDisableFormGroups.call(this);
	}

	queueMicrotask(() => {
		updatePopperInputsFromMainValue.call(this);
		updatePopper.call(this);
	});
}

/**
 * @private
 */
function clearAndDisableFormGroups() {
	this[radioInputsElementSymbol].forEach((radio) => {
		radio.checked = false;
	});

	this[inputInputsElementSymbol].forEach((input) => {
		input.value = "";
		input.disabled = true;
	});

	this[selectInputsElementSymbol].forEach((select) => {
		select.value = "";
		select.disabled = true;
	});
}

/**
 * @private
 * @return {string}
 */
function guessRangeTypeFromCurrentValue() {
	const value = this[inputElementSymbol].value.trim();

	if (value === "") {
		return "empty";
	}

	const singleDateRegex = /^\d{4}-\d{2}-\d{2}$/;
	const fromDateRegex = /^\d{4}-\d{2}-\d{2}-$/;
	const toDateRegex = /^-\d{4}-\d{2}-\d{2}$/;
	const fromToDateRegex = /^\d{4}-\d{2}-\d{2}-\d{4}-\d{2}-\d{2}$/;

	if (singleDateRegex.test(value)) {
		return "single";
	}

	if (fromToDateRegex.test(value)) {
		return "from-to";
	}

	if (fromDateRegex.test(value)) {
		return "from";
	}

	if (toDateRegex.test(value)) {
		return "to";
	}

	return "invalid";
}

function getRangeTypeFromForm() {
	const group = this[formContainerElementSymbol].querySelector(
		"input[type=radio][name=rangeType]:checked",
	);
	if (!group) {
		return;
	}

	return group.value;
}

/**
 * function, that calculates starting from a date a range of days, months, weeks or years
 *
 * @example
 *
 * // if today is 2020-10-12
 * calculateDateRangeFromToday("2020-10-12", "days") // 0
 * calculateDateRangeFromToday("2020-10-13", "days") // 1
 * calculateDateRangeFromToday("2020-12-14", "days") // 63
 * calculateDateRangeFromToday("2020-10-12", "months") // 0
 * calculateDateRangeFromToday("2020-11-12", "months") // 1
 * calculateDateRangeFromToday("2021-10-12", "months") // 11
 *
 * @param {string} date
 * @param {string} unit (day, month, year, week)
 * @return {number} range
 */
function calculateDateRangeFromToday(date, unit) {
	// calculate the date of today -1
	const yesterday = new Date();
	yesterday.setDate(yesterday.getDate() - 1);

	const dateAsObj = new Date(date);

	const diff = dateAsObj.getTime() - yesterday.getTime();

	// return the rages in days, months, years or weeks
	switch (unit) {
		case "days":
			return Math.floor(diff / (1000 * 60 * 60 * 24));
		case "months":
			return Math.floor(diff / (1000 * 60 * 60 * 24 * 30));
		case "years":
			return Math.floor(diff / (1000 * 60 * 60 * 24 * 365));
		case "weeks":
			return Math.floor(diff / (1000 * 60 * 60 * 24 * 7));
	}

	return 0;
}

/**
 * @private
 */
function updatePopperInputsFromMainValue() {
	const featuresMoreThan = this.getOption("features.moreThan", false);
	const featuresWithin = this.getOption("features.within", false);

	let guessedValueType = guessRangeTypeFromCurrentValue.call(this);
	if (
		!guessedValueType ||
		guessedValueType === "empty" ||
		guessedValueType === "invalid"
	) {
		return;
	}

	const rangeType = getRangeTypeFromForm.call(this);
	if (guessedValueType === rangeType) {
		return;
	}

	let lastRangeUnit = "days";

	// check if type from form is within and current type is to
	// if so, then do not update the form
	if (
		guessedValueType === "to" &&
		rangeType === "within" &&
		featuresWithin === true
	) {
		guessedValueType = "within";
		lastRangeUnit = this[formContainerElementSymbol].querySelector(
			`input[name=rangeType][value=within] ~ select[name=withinValueUnit]`,
		).value;
	}

	// check if type from form is more than and current type is to
	// if so, then do not update the form
	if (
		guessedValueType === "from" &&
		rangeType === "more-than" &&
		featuresMoreThan === true
	) {
		return;
	}

	clearAndDisableFormGroups.call(this);

	let radio = this[formContainerElementSymbol].querySelector(
		`input[name=rangeType][value=${guessedValueType}]`,
	);
	let input;
	let select;

	switch (guessedValueType) {
		case "within":
			radio.checked = true;
			input = this[formContainerElementSymbol].querySelector(
				`input[name=rangeType][value=within] ~ input[name=withinValue]`,
			);
			select = this[formContainerElementSymbol].querySelector(
				`input[name=rangeType][value=within] ~ select[name=withinValueUnit]`,
			);

			input.disabled = false;
			select.disabled = false;

			const d1 = this[inputElementSymbol].value.substring(1);
			const range1 = calculateDateRangeFromToday(d1, lastRangeUnit);
			select.value = lastRangeUnit;
			input.value = range1;
			break;

		case "more-than":
			radio.checked = true;
			input = this[formContainerElementSymbol].querySelector(
				`input[name=rangeType][value=more-than] ~ input[name=moreThanValue]`,
			);
			select = this[formContainerElementSymbol].querySelector(
				`input[name=rangeType][value=more-than] ~ select[name=moreThanValueUnit]`,
			);

			input.disabled = false;
			select.disabled = false;

			const d2 = this[inputElementSymbol].value.substring(0, 10);
			const range2 = calculateDateRangeFromToday(d2, lastRangeUnit);
			select.value = lastRangeUnit;
			input.value = range2;
			break;

		case "single":
			// check if the date is today
			const today = new Date();
			const date = new Date(this[inputElementSymbol].value);

			const featuresToday = this.getOption("features.today", false);

			if (
				featuresToday &&
				today.getFullYear() === date.getFullYear() &&
				today.getMonth() === date.getMonth() &&
				today.getDate() === date.getDate()
			) {
				radio = this[formContainerElementSymbol].querySelector(
					`input[name=rangeType][value=today]`,
				);
				input = this[formContainerElementSymbol].querySelector(
					`input[name=rangeType][value=today] ~ input[name=todayValue]`,
				);
			} else {
				input = this[formContainerElementSymbol].querySelector(
					`input[name=rangeType][value=single] ~ input[name=singleValue]`,
				);
				input.disabled = false;
			}

			radio.checked = true;
			input.value = this[inputElementSymbol].value;

			break;
		case "from":
			radio.checked = true;
			const fromInput = this[formContainerElementSymbol].querySelector(
				`input[name=rangeType][value=from] ~ input[name=fromValue]`,
			);
			// the input value has an - suffix
			fromInput.value = this[inputElementSymbol].value.substring(
				0,
				this[inputElementSymbol].value.length - 1,
			);
			fromInput.disabled = false;

			break;
		case "to":
			radio.checked = true;
			const toInput = this[formContainerElementSymbol].querySelector(
				`input[name=rangeType][value=to] ~ input[name=toValue]`,
			);
			// the input value has an - prefix
			toInput.value = this[inputElementSymbol].value.substring(1);
			toInput.disabled = false;

			break;

		case "from-to":
			radio.checked = true;
			const fromToInput = this[formContainerElementSymbol].querySelector(
				`input[name=rangeType][value=from-to] ~ input[name=fromValue]`,
			);
			const toToInput = this[formContainerElementSymbol].querySelector(
				`input[name=rangeType][value=from-to] ~ input[name=toValue]`,
			);

			const fromToRegexWithNamedGroups =
				/(?<from>\d{4}-\d{2}-\d{2})-(?<to>\d{4}-\d{2}-\d{2})/;
			const groups = fromToRegexWithNamedGroups.exec(
				this[inputElementSymbol].value,
			);

			fromToInput.value = groups.groups.from;
			toToInput.value = groups.groups.to;

			fromToInput.disabled = false;
			toToInput.disabled = false;

			break;
	}
}

/**
 * @private
 * @return {Monster.Components.Datatable.Filter.Range}
 */
function initControlReferences() {
	if (!this.shadowRoot) {
		throw new Error("no shadow-root is defined");
	}

	this[controlElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}=control]`,
	);
	this[inputElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}=input]`,
	);
	this[popperElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}=popper]`,
	);
	this[arrowElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}=arrow]`,
	);
	this[formContainerElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}=form]`,
	);

	this[radioInputsElementSymbol] = this[
		formContainerElementSymbol
	].querySelectorAll(`input[name=rangeType]`);
	this[inputInputsElementSymbol] = this[
		formContainerElementSymbol
	].querySelectorAll(`input[name=rangeType] ~ input`);
	this[selectInputsElementSymbol] = this[
		formContainerElementSymbol
	].querySelectorAll(`input[name=rangeType] ~ select`);

	return this;
}

/**
 * @private
 * @param {object} options
 * @return {object}
 */
function initOptionsFromArguments(options) {
	return options;
}

/**
 * @private
 */
function updatePopper() {
	if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) {
		return;
	}

	if (this.getOption("disabled", false) === true) {
		return;
	}

	positionPopper.call(
		this,
		this[controlElementSymbol],
		this[popperElementSymbol],
		this.getOption("popper", {}),
	);
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
        <div data-monster-role="control" part="control">
            <input data-monster-attributes="disabled path:disabled | if:true, class path:classes.input"
                   data-monster-role="input"
                   part="button"
                   data-monster-replace="path:labels.button">

            <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1">
                <div data-monster-role="arrow"></div>
                <div part="filter" class="flex" data-monster-replace="path:filter">
                    <div class="form-container" data-monster-role="form">

                        <div data-monster-role="range-type" data-monster-range-type="today" data-monster-attributes="class path:features.moreThan | ?:form-group:hidden">
                            <input type="radio" name="rangeType" value="today" tabindex="0">
                            <label for="today"><span data-monster-replace="path:labels.todayValue"></span></label>
                            <input type="hidden" name="todayValue" disabled tabindex="0">
                        </div>
                        
                        <div data-monster-role="range-type" data-monster-range-type="within" data-monster-attributes="class path:features.within | ?:form-group:hidden">
                            <input type="radio" name="rangeType" value="within" tabindex="0">
                            <label for="within"><span
                                    data-monster-replace="path:labels.withinValue"></span></label>
                            <input data-monster-watch="true" type="number" name="withinValue" disabled
                                   tabindex="0">
                            <select data-monster-watch="true" name="withinValueUnit" disabled tabindex="0">
                                <option selected value="days" data-monster-replace="path:labels.days"></option>
                                <option value="weeks" data-monster-replace="path:labels.weeks"></option>
                                <option value="months" data-monster-replace="path:labels.months"></option>
                                <option value="years" data-monster-replace="path:labels.years"></option>
                            </select>
                        </div>
                        
                        <div data-monster-role="range-type" data-monster-range-type="more-than" data-monster-attributes="class path:features.moreThan | ?:form-group:hidden">
                            <input type="radio" name="rangeType" value="more-than" tabindex="0">
                            <label for="more-than"><span
                                    data-monster-replace="path:labels.moreThanValue"></span></label>
                            <input data-monster-watch="true" type="number" name="moreThanValue" disabled
                                   tabindex="0">
                            <select data-monster-watch="true" name="moreThanValueUnit" disabled tabindex="0">
                                <option selected value="days" data-monster-replace="path:labels.days"></option>
                                <option value="weeks" data-monster-replace="path:labels.weeks"></option>
                                <option value="months" data-monster-replace="path:labels.months"></option>
                                <option value="years" data-monster-replace="path:labels.years"></option>
                            </select>
                        </div>

                        <div class="form-group" data-monster-role="range-type" data-monster-range-type="single">
                            <input type="radio" name="rangeType" value="single" tabindex="0">
                            <label for="single"><span data-monster-replace="path:labels.singleValue"></span></label>
                            <input type="date" data-monster-watch="true" name="singleValue" disabled tabindex="0">
                        </div>
                        
                        <div class="form-group" data-monster-role="range-type" data-monster-range-type="from">
                            <input type="radio" name="rangeType" value="from" tabindex="0">
                            <label for="from"><span data-monster-replace="path:labels.fromValue"></span></label>
                            <input type="date" data-monster-watch="true" name="fromValue" disabled tabindex="0">
                        </div>
                        
                        <div class="form-group" data-monster-role="range-type" data-monster-range-type="to">
                            <input type="radio" name="rangeType" value="to" tabindex="0">
                            <label for="to"><span data-monster-replace="path:labels.toValue"></span></label>
                            <input type="date" data-monster-watch="true" name="toValue" disabled tabindex="0">
                        </div>
                        
                        <div class="form-group" data-monster-role="range-type" data-monster-range-type="from-to">
                            <input type="radio" name="rangeType" value="from-to" tabindex="0">
                            <label for="rangeFrom"><span data-monster-replace="path:labels.fromValue"></span></label>
                            <input type="date" data-monster-watch="true" name="fromValue" disabled tabindex="0">
                            <label for="rangeTo"><span data-monster-replace="path:labels.toValue"></span></label>
                            <input type="date" data-monster-watch="true" name="toValue" disabled tabindex="0">
                        </div>


                    </div>
                </div>
            </div>

    `;
}

registerCustomElement(DateRange);
