/**
 * 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 { Base } from "./base.mjs";
import { isString } from "./is.mjs";
import { validateArray, validateString } from "./validate.mjs";
import { instanceSymbol } from "../constants.mjs";
export { MediaType, parseMediaType };

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

/**
 * @typedef {Object} Parameter
 * @property {string} key
 * @property {string} value
 */

/**
 * You can create an object via the monster namespace `new MediaType()`.
 *
 * @license AGPLv3
 * @since 1.8.0
 * @copyright Volker Schukai
 */
class MediaType extends Base {
	/**
	 *
	 * @param {String} type
	 * @param {String} subtype
	 * @param {Parameter[]} parameter
	 */
	constructor(type, subtype, parameter) {
		super();

		this[internal] = {
			type: validateString(type).toLowerCase(),
			subtype: validateString(subtype).toLowerCase(),
			parameter: [],
		};

		if (parameter !== undefined) {
			this[internal]["parameter"] = validateArray(parameter);
		}
	}

	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 * @since 2.1.0
	 */
	static get [instanceSymbol]() {
		return Symbol.for("@schukai/monster/types/media-type");
	}

	/**
	 * @return {String}
	 */
	get type() {
		return this[internal].type;
	}

	/**
	 * @return {String}
	 */
	get subtype() {
		return this[internal].subtype;
	}

	// /**
	//  * @return {Parameter[]}
	//  */
	// get parameter() {
	// 	return this[internal].parameter;
	// }

	/**
	 *
	 *
	 * @return {Map}
	 */
	get parameter() {
		const result = new Map();

		this[internal]["parameter"].forEach((p) => {
			let value = p.value;

			// internally special values are partly stored with quotes, this function removes them.
			if (value.startsWith('"') && value.endsWith('"')) {
				value = value.substring(1, value.length - 1);
			}

			result.set(p.key, value);
		});

		return result;
	}

	/**
	 *
	 * @return {string}
	 */
	toString() {
		const parameter = [];
		for (const a of this[internal].parameter) {
			parameter.push(`${a.key}=${a.value}`);
		}

		return `${this[internal].type}/${this[internal].subtype}${
			parameter.length > 0 ? `;${parameter.join(";")}` : ""
		}`;
	}
}

/**
 * You can call the function via the monster namespace `parseMediaType()`.
 *
 * ```
 * <script type="module">
 * import {Monster} from '@schukai/monster/source/monster.mjs';
 * console.log(parseMediaType())
 * </script>
 * ```
 *
 * Alternatively, you can also integrate this function individually.
 *
 * ```
 * <script type="module">
 * import {parseMediaType} from '@schukai/monster/source/types/dataurl.mjs';
 * console.log(parseMediaType())
 * </script>
 * ```
 *
 * Specification:
 *
 * ```
 * dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
 * mediatype  := [ type "/" subtype ] *( ";" parameter )
 * data       := *urlchar
 * parameter  := attribute "=" value
 * ```
 *
 * @param {String} mediatype
 * @return {MediaType}
 * @see https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
 * @throws {TypeError} the mimetype can not be parsed
 * @throws {TypeError} blank value is not allowed
 * @throws {TypeError} malformed data url
 */
function parseMediaType(mediatype) {
	const regex =
		/(?<type>[A-Za-z]+|\*)\/(?<subtype>([a-zA-Z0-9.\+_\-]+)|\*|)(?<parameter>\s*;\s*([a-zA-Z0-9]+)\s*(=\s*("?[A-Za-z0-9_\-]+"?))?)*/g;
	const result = regex.exec(validateString(mediatype));

	const groups = result?.["groups"];
	if (groups === undefined) {
		throw new TypeError("the mimetype can not be parsed");
	}

	const type = groups?.["type"];
	const subtype = groups?.["subtype"];
	const parameter = groups?.["parameter"];

	if (subtype === "" || type === "") {
		throw new TypeError("blank value is not allowed");
	}

	return new MediaType(type, subtype, parseParameter(parameter));
}

/**
 * @private
 * @license AGPLv3
 * @since 1.18.0
 * @param {String} parameter
 * @return {Parameter[]|undefined}
 */
function parseParameter(parameter) {
	if (!isString(parameter)) {
		return undefined;
	}

	const result = [];

	parameter.split(";").forEach((entry) => {
		entry = entry.trim();
		if (entry === "") {
			return;
		}

		const kv = entry.split("=");

		const key = validateString(kv?.[0]).trim();
		const value = validateString(kv?.[1]).trim();

		// if values are quoted, they remain so internally
		result.push({
			key: key,
			value: value,
		});
	});

	return result;
}
