/**
 * 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 { Pathfinder } from "../../data/pathfinder.mjs";
import {
	isBoolean,
	isString,
	isObject,
	isNumber,
	isArray,
	isFunction,
	isInteger,
} from "../../types/is.mjs";
import { extractKeys } from "./extract-keys.mjs";

export { initOptionsFromAttributes };

/**
 * Initializes the given options object based on the attributes of the current DOM element.
 * The function looks for attributes with the prefix 'data-monster-option-', and maps them to
 * properties in the options object. It supports both the historic compact form and kebab-case aliases
 * for camelCase option names.
 * For example, the attributes 'data-monster-option-url' and
 * 'data-monster-option-popper-content-overflow' map to 'url' and 'popper.contentOverflow'.
 *
 * With the mapping parameter, the attribute value can be mapped to a different value.
 * For example, the attribute 'data-monster-option-foo' maps to the 'bar' property in the options object.
 *
 * The mapping object would look like this:
 *
 * {
 *    'foo': (value) => value + 'bar'
 *    // the value of the attribute 'data-monster-option-foo' is appended with 'bar'
 *    // and assigned to the 'bar' property in the options object.
 *    // e.g. <div data-monster-option-foo="foo"></div>
 *    'bar.baz': (value) => value + 'bar'
 *    // the value of the attribute 'data-monster-option-bar-baz' is appended with 'bar'
 *    // and assigned to the 'bar.baz' property in the options object.
 *    // e.g. <div data-monster-option-bar-baz="foo"></div>
 * }
 *
 * Array values have to be separated by '::'.
 *
 * @since 3.38.0
 * @param {HTMLElement} element - The DOM element to be used as the source of the attributes.
 * @param {Object} options - The options object to be initialized.
 * @param {Object} mapping - A mapping between the attribute value and the property value.
 * @param {string} prefix - The prefix of the attributes to be considered.
 * @return {Object} - The initialized options object.
 * @this HTMLElement - The context of the DOM element.
 */
function initOptionsFromAttributes(
	element,
	options,
	mapping = {},
	prefix = "data-monster-option-",
) {
	if (!(element instanceof HTMLElement)) return options;
	if (!element.hasAttributes()) return options;

	const keyMap = extractKeys(options);
	const finder = new Pathfinder(options);

	element.getAttributeNames().forEach((name) => {
		if (!name.startsWith(prefix)) return;

		// check if the attribute name is a valid option.
		// the mapping between the attribute is simple. The dash is replaced by a dot.
		// e.g. data-monster-url => url
		const optionName = keyMap.get(name.substring(prefix.length).toLowerCase());
		if (!finder.exists(optionName)) return;

		if (element.hasAttribute(name)) {
			let value = element.getAttribute(name);
			if (
				mapping.hasOwnProperty(optionName) &&
				isFunction(mapping[optionName])
			) {
				value = mapping[optionName](value);
			}

			let optionValue = finder.getVia(optionName);
			if (optionValue === null || optionValue === undefined) {
				optionValue = value;
			}

			//const typeOfOptionValue = typeof optionValue;
			if (optionValue === null || optionValue === undefined) {
				value = null;
			} else if (isBoolean(optionValue)) {
				value = value === "true";
			} else if (isInteger(optionValue)) {
				value = Number(value);
			} else if (isNumber(optionValue)) {
				value = Number(value);
			} else if (isString(optionValue)) {
				value = String(value);
			} else if (isObject(optionValue)) {
				value = JSON.parse(value);
			} else if (isArray(optionValue)) {
				value = value.split("::");
			} else {
				value = optionValue;
			}

			finder.setVia(optionName, value);
		}
	});

	return options;
}
