/**
 * 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 { addAttributeToken } from "../../dom/attributes.mjs";
import {
	ATTRIBUTE_ERRORMESSAGE,
	ATTRIBUTE_ROLE,
} from "../../dom/constants.mjs";
import { CustomControl } from "../../dom/customcontrol.mjs";
import {
	assembleMethodSymbol,
	getSlottedElements,
	registerCustomElement,
} from "../../dom/customelement.mjs";
import { isFunction } from "../../types/is.mjs";
import { FieldSetStyleSheet } from "./stylesheet/field-set.mjs";
import "../layout/collapse.mjs";
import "./toggle-switch.mjs";
import { getLocaleOfDocument } from "../../dom/locale.mjs";
import { addErrorAttribute } from "../../dom/error.mjs";
import { InvalidStyleSheet } from "./stylesheet/invalid.mjs";

export { FieldSet };

/**
 * @private
 * @type {symbol}
 */
const fieldSetElementSymbol = Symbol("fieldSetElement");

/**
 * @private
 * @type {symbol}
 */
const collapseElementSymbol = Symbol("collapseElement");

/**
 * @private
 * @type {symbol}
 */
const extendedSwitchSymbol = Symbol("extendedSwitch");

/**
 * @private
 * @type {symbol}
 */
const headerElementSymbol = Symbol("headerElement");

/**
 * @private
 * @type {symbol}
 */
const toggleSwitchElementSymbol = Symbol("toggleSwitchElement");

/**
 * @private
 * @type {symbol}
 */
const extendedSwitchElementSymbol = Symbol("extendedSwitchElement");

/**
 * A field set control that can be used to group form elements.
 *
 * @fragments /fragments/components/form/field-set/
 *
 * @example /examples/components/form/field-set-simple Shipping field set
 * @example /examples/components/form/field-set-preferences Compact settings field set
 *
 * @since 3.65.0
 * @copyright Volker Schukai
 * @summary A grouped form field set with shared legend, structure and consistent spacing for related controls.
 */
class FieldSet extends CustomControl {
	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for("@schukai/monster/components/form/fieldset@@instance");
	}

	/**
	 * @return {Components.Form.FieldSet
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();
		initControlReferences.call(this);
		initEventHandler.call(this);
		updateExtendedFields.call(this);
		updateColumns.call(this);
		return this;
	}

	/**
	 * 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 Label definitions
	 * @property {Object} actions Callbacks
	 * @property {string} actions.click="throw Error" Callback when clicked
	 * @property {Object} features Features
	 * @property {boolean} features.multipleColumns=true Multiple columns
	 * @property {Object} classes CSS classes
	 * @property {boolean} disabled=false Disabled state
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},
			labels: getTranslations(),
			classes: {
				content: "collapse-alignment",
			},
			disabled: false,
			features: {
				multipleColumns: true,
			},
			actions: {
				click: () => {},
			},
			value: null,
		});
	}

	/**
	 *
	 * @return {string}
	 */
	static getTag() {
		return "monster-field-set";
	}

	/**
	 *
	 * @return {CSSStyleSheet[]}
	 */
	static getCSSStyleSheet() {
		return [FieldSetStyleSheet, InvalidStyleSheet];
	}

	/**
	 * The FieldSet.click() method simulates a click on the internal element.
	 *
	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click}
	 */
	click() {
		if (this.getOption("disabled") === true) {
			return;
		}

		if (
			this[fieldSetElementSymbol] &&
			isFunction(this[fieldSetElementSymbol].click)
		) {
			this[fieldSetElementSymbol].click();
		}
	}

	/**
	 * The Button.focus() method sets focus on the internal element.
	 *
	 * @param {Object} options
	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus}
	 */
	focus(options) {
		if (this.getOption("disabled") === true) {
			return;
		}

		if (
			this[fieldSetElementSymbol] &&
			isFunction(this[fieldSetElementSymbol].focus)
		) {
			this[fieldSetElementSymbol].focus(options);
		}
	}

	/**
	 * The Button.blur() method removes focus from the internal element.
	 */
	blur() {
		if (
			this[fieldSetElementSymbol] &&
			isFunction(this[fieldSetElementSymbol].blur)
		) {
			this[fieldSetElementSymbol].blur();
		}
	}

	/**
	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
	 * @return {boolean}
	 */
	static get formAssociated() {
		return true;
	}

	/**
	 * The current value of the form control.
	 *
	 * ```js
	 * e = document.querySelector('monster-field-set');
	 * console.log(e.value)
	 * ```
	 *
	 * @property {string}
	 */
	get value() {
		return this.getOption("value");
	}

	/**
	 * Set the value of the form control.
	 *
	 * ```
	 * e = document.querySelector('monster-field-set');
	 * e.value=1
	 * ```
	 *
	 * @property {string} value
	 * @throws {Error} unsupported type
	 */
	set value(value) {
		this.setOption("value", value);
		try {
			this?.setFormValue(this.value);
		} catch (e) {
			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
		}
	}
}

/**
 * @private
 * @returns {object}
 */
function getTranslations() {
	const locale = getLocaleOfDocument();
	switch (locale.language) {
		case "de":
			return {
				toggleSwitchOn: "✔",
				toggleSwitchOff: "✖",
				toggleSwitchLabel: "Erweitern",
				title: "",
			};
		case "fr":
			return {
				toggleSwitchOn: "✔",
				toggleSwitchOff: "✖",
				toggleSwitchLabel: "Développer",
				title: "",
			};
		case "sp":
			return {
				toggleSwitchOn: "✔",
				toggleSwitchOff: "✖",
				toggleSwitchLabel: "Expandir",
				title: "",
			};
		case "it":
			return {
				toggleSwitchOn: "✔",
				toggleSwitchOff: "✖",
				toggleSwitchLabel: "Espandi",
				title: "",
			};
		case "pl":
			return {
				toggleSwitchOn: "✔",
				toggleSwitchOff: "✖",
				toggleSwitchLabel: "Rozwiń",
				title: "",
			};
		case "no":
			return {
				toggleSwitchOn: "✔",
				toggleSwitchOff: "✖",
				toggleSwitchLabel: "Utvid",
				title: "",
			};
		case "dk":
			return {
				toggleSwitchOn: "✔",
				toggleSwitchOff: "✖",
				toggleSwitchLabel: "Udvid",
				title: "",
			};
		case "sw":
			return {
				toggleSwitchOn: "✔",
				toggleSwitchOff: "✖",
				toggleSwitchLabel: "Expandera",
				title: "",
			};
		default:
		case "en":
			return {
				toggleSwitchOn: "✔",
				toggleSwitchOff: "✖",
				toggleSwitchLabel: "Expand",
				title: "",
			};
	}
}

/**
 * @private
 */
function updateExtendedFields() {
	const nodes = getSlottedElements.call(this, "", "extended");
	if (nodes.size > 0) {
		this[extendedSwitchSymbol].classList.remove("hidden");
	} else {
		this[extendedSwitchSymbol].classList.add("hidden");
	}
}

/**
 * @private
 */
function updateColumns() {
	if (this.getOption("features.multipleColumns") !== true) {
		this[fieldSetElementSymbol].classList.remove("multiple-columns");
		return;
	}

	this[fieldSetElementSymbol].classList.add("multiple-columns");
}

/**
 * @private
 * @return {initEventHandler}
 * @fires monster-field-set-clicked
 */
function initEventHandler() {
	this[toggleSwitchElementSymbol].setOption(
		"labels.toggleSwitchOn",
		this.getOption("labels.toggleSwitchOn"),
	);
	this[toggleSwitchElementSymbol].setOption(
		"labels.toggleSwitchOff",
		this.getOption("labels.toggleSwitchOff"),
	);

	this[toggleSwitchElementSymbol].setOption("actions.on", () => {
		this[collapseElementSymbol].open();
	});

	this[toggleSwitchElementSymbol].setOption("actions.off", () => {
		this[collapseElementSymbol].close();
	});

	return this;
}

/**
 * @private
 */
function initControlReferences() {
	this[fieldSetElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}="control"]`,
	);

	this[extendedSwitchElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}="extended-switch"]`,
	);

	this[collapseElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}="collapse"]`,
	);

	this[headerElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}="header"]`,
	);

	this[extendedSwitchSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}="extended-switch"]`,
	);

	this[toggleSwitchElementSymbol] = this.shadowRoot.querySelector(
		`monster-toggle-switch`,
	);
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
        <div data-monster-role="control" part="control">
            <div data-monster-role="header" part="header">
                <div data-monster-replace="path:labels.title" data-monster-role="title" part="title"></div>
                <div data-monster-role="extended-switch" part="extended-switch">
                    <label data-monster-replace="path:labels.toggleSwitchLabel"></label>
                    <monster-toggle-switch></monster-toggle-switch>
                </div>
            </div>
            <div data-monster-role="container" part="container">
                <div data-monster-attributes="class path:classes.content"
                    part="content">
                    <slot></slot>
                </div>
                <monster-collapse data-monster-role="collapse" part="collapse">
                    <slot name="extended"></slot>
                </monster-collapse>
            </div>
        </div>`;
}

registerCustomElement(FieldSet);
