/**
 * 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 { CustomControl } from "../../../dom/customcontrol.mjs";
import { FilterControlsDefaultsStyleSheet } from "../stylesheet/filter-controls-defaults.mjs";
import { FilterStyleSheet } from "../stylesheet/filter.mjs";
import { getLocaleOfDocument } from "../../../dom/locale.mjs";
import { isString } from "../../../types/is.mjs";
import "../../form/input-group.mjs";

export { TextOperator };

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

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

/**
 * The FilterTextOperator component combines operator and input fields for text filters.
 *
 * @fragments /fragments/components/datatable/filter/text-operator
 *
 * @example /examples/components/datatable/filter-controls Filter controls
 *
 * @copyright Volker Schukai
 * @summary The FilterTextOperator component combines operator and query input.
 */
class TextOperator extends CustomControl {
	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for(
			"@schukai/monster/components/filter/text-operator@@instance",
		);
	}

	/**
	 * 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.
	 *
	 * @property {Object} templates Template definitions
	 * @property {string} templates.main Main template
	 * @property {Object} labels Text labels
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},
			features: {
				quoteValue: true,
			},
			quotes: {
				char: '"',
			},
			labels: getTranslations(),
		});
	}

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

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

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

	/**
	 * @return Array<CSSStyleSheet>
	 */
	static getCSSStyleSheet() {
		return [FilterControlsDefaultsStyleSheet, FilterStyleSheet];
	}

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

	/**
	 * @return {*}
	 */
	get value() {
		let query = `${this[inputElementSymbol].value || ""}`.trim();
		if (!query) {
			return "";
		}
		if (this.getOption("features.quoteValue")) {
			query = quoteValue(query, this.getOption("quotes.char"));
		}
		return `${this[operatorElementSymbol].value}${query}`;
	}
}

/**
 * @private
 * @returns {Object}
 */
function getTranslations() {
	const locale = getLocaleOfDocument();
	switch (locale.language) {
		case "de":
			return {
				contains: "Enthält",
				equals: "Gleich",
				notEqual: "Ungleich",
				startsWith: "Beginnt mit",
				endsWith: "Endet mit",
			};
		case "es":
			return {
				contains: "Contiene",
				equals: "Igual",
				notEqual: "Distinto",
				startsWith: "Empieza con",
				endsWith: "Termina con",
			};
		case "zh":
			return {
				contains: "包含",
				equals: "等于",
				notEqual: "不等于",
				startsWith: "开头为",
				endsWith: "结尾为",
			};
		case "hi":
			return {
				contains: "शामिल है",
				equals: "समान",
				notEqual: "समान नहीं",
				startsWith: "से शुरू",
				endsWith: "पर समाप्त",
			};
		case "bn":
			return {
				contains: "অন্তর্ভুক্ত",
				equals: "সমান",
				notEqual: "সমান নয়",
				startsWith: "দিয়ে শুরু",
				endsWith: "দিয়ে শেষ",
			};
		case "pt":
			return {
				contains: "Contém",
				equals: "Igual",
				notEqual: "Diferente",
				startsWith: "Começa com",
				endsWith: "Termina com",
			};
		case "ru":
			return {
				contains: "Содержит",
				equals: "Равно",
				notEqual: "Не равно",
				startsWith: "Начинается с",
				endsWith: "Заканчивается на",
			};
		case "ja":
			return {
				contains: "含む",
				equals: "等しい",
				notEqual: "等しくない",
				startsWith: "で始まる",
				endsWith: "で終わる",
			};
		case "pa":
			return {
				contains: "ਸ਼ਾਮਲ ਹੈ",
				equals: "ਬਰਾਬਰ",
				notEqual: "ਬਰਾਬਰ ਨਹੀਂ",
				startsWith: "ਨਾਲ ਸ਼ੁਰੂ",
				endsWith: "ਨਾਲ ਖਤਮ",
			};
		case "mr":
			return {
				contains: "समाविष्ट",
				equals: "समान",
				notEqual: "समान नाही",
				startsWith: "ने सुरू",
				endsWith: "ने समाप्त",
			};
		case "fr":
			return {
				contains: "Contient",
				equals: "Égal",
				notEqual: "Différent",
				startsWith: "Commence par",
				endsWith: "Se termine par",
			};
		case "it":
			return {
				contains: "Contiene",
				equals: "Uguale",
				notEqual: "Diverso",
				startsWith: "Inizia con",
				endsWith: "Finisce con",
			};
		case "nl":
			return {
				contains: "Bevat",
				equals: "Gelijk",
				notEqual: "Niet gelijk",
				startsWith: "Begint met",
				endsWith: "Eindigt met",
			};
		case "sv":
			return {
				contains: "Innehåller",
				equals: "Lika med",
				notEqual: "Inte lika",
				startsWith: "Börjar med",
				endsWith: "Slutar med",
			};
		case "pl":
			return {
				contains: "Zawiera",
				equals: "Równe",
				notEqual: "Nie równe",
				startsWith: "Zaczyna się od",
				endsWith: "Kończy się na",
			};
		case "da":
			return {
				contains: "Indeholder",
				equals: "Lig med",
				notEqual: "Ikke lig med",
				startsWith: "Starter med",
				endsWith: "Slutter med",
			};
		case "fi":
			return {
				contains: "Sisältää",
				equals: "Yhtä kuin",
				notEqual: "Ei yhtä kuin",
				startsWith: "Alkaa",
				endsWith: "Päättyy",
			};
		case "no":
			return {
				contains: "Inneholder",
				equals: "Lik",
				notEqual: "Ikke lik",
				startsWith: "Starter med",
				endsWith: "Slutter med",
			};
		case "cs":
			return {
				contains: "Obsahuje",
				equals: "Rovno",
				notEqual: "Nerovno",
				startsWith: "Začíná na",
				endsWith: "Končí na",
			};
		default:
		case "en":
			return {
				contains: "Contains",
				equals: "Equals",
				notEqual: "Not equal",
				startsWith: "Starts with",
				endsWith: "Ends with",
			};
	}
}

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

	this[operatorElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=operator]",
	);
	this[inputElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=query]",
	);
	return this;
}

/**
 * @private
 * @return {void}
 */
function updateOptions() {
	const labels = this.getOption("labels");
	const currentValue = this[operatorElementSymbol].value;

	const operators = [
		{ value: ":", label: labels.contains },
		{ value: "=", label: labels.equals },
		{ value: "!=", label: labels.notEqual },
		{ value: "^=", label: labels.startsWith },
		{ value: "$=", label: labels.endsWith },
	];

	this[operatorElementSymbol].innerHTML = "";
	for (const operator of operators) {
		const option = document.createElement("option");
		option.value = operator.value;
		option.textContent = operator.label;
		this[operatorElementSymbol].append(option);
	}

	if (currentValue) {
		this[operatorElementSymbol].value = currentValue;
	}
}

/**
 * @private
 * @param {string} value
 * @return {{operator: string, query: string}}
 */
function parseValue(value) {
	if (!value) {
		return { operator: ":", query: "" };
	}

	const operators = ["!=", "<>", "^=", "$=", "=", ":"];
	for (const op of operators) {
		if (value.startsWith(op)) {
			return {
				operator: op === "<>" ? "!=" : op,
				query: value.slice(op.length),
			};
		}
	}

	return { operator: ":", query: value };
}

/**
 * @private
 * @param {string} value
 * @param {string} quoteChar
 * @return {string}
 */
function quoteValue(value, quoteChar) {
	const char = isString(quoteChar) && quoteChar.length > 0 ? quoteChar[0] : '"';
	if (value.startsWith(char) && value.endsWith(char)) {
		return value;
	}
	return `${char}${value}${char}`;
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
        <div data-monster-role="control" part="control">
            <slot></slot>
            <monster-input-group>
                <select slot="prefix" data-monster-role="operator" name="operator"></select>
                <input type="search" data-monster-role="query" name="query" data-monster-attributes="value path:query">
            </monster-input-group>
        </div>
    `;
}

registerCustomElement(TextOperator);
