/**
 * 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 { MediaType, parseMediaType } from "./mediatype.mjs";
import {
	validateBoolean,
	validateInstance,
	validateString,
} from "./validate.mjs";
import { instanceSymbol } from "../constants.mjs";
export { DataUrl, parseDataURL };

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

/**
 * You can create an object via the monster namespace `new DataUrl()`.
 *
 * @license AGPLv3
 * @since 1.8.0
 * @copyright Volker Schukai
 * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
 * @see https://datatracker.ietf.org/doc/html/rfc2397
 */
class DataUrl extends Base {
	/**
	 *
	 * @param {String} content
	 * @param {String|MediaType} mediatype
	 * @param {boolean} base64=true
	 */
	constructor(content, mediatype, base64) {
		super();

		if (isString(mediatype)) {
			mediatype = parseMediaType(mediatype);
		}

		this[internal] = {
			content: validateString(content),
			mediatype: validateInstance(mediatype, MediaType),
			base64: validateBoolean(base64 === undefined ? true : base64),
		};
	}

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

	get content() {
		return this[internal].base64
			? atob(this[internal].content)
			: this[internal].content;
	}

	get mediatype() {
		return this[internal].mediatype;
	}

	/**
	 * Converts the current object to a URL instance.
	 *
	 * @return {URL} A URL instance created from the string representation of the object.
	 */
	toURL() {
		return new URL(this.toString());
	}

	/**
	 *
	 * @return {string}
	 * @see https://datatracker.ietf.org/doc/html/rfc2397
	 */
	toString() {
		let content = this[internal].content;

		if (this[internal].base64 === true) {
			content = `;base64,${content}`;
		} else {
			content = `,${encodeURIComponent(content)}`;
		}

		return `data:${this[internal].mediatype.toString()}${content}`;
	}
}

/**
 * You can call the function via the monster namespace `parseDataURL()`.
 *
 * Specification:
 *
 * ```
 * dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
 * mediatype  := [ type "/" subtype ] *( ";" parameter )
 * data       := *urlchar
 * parameter  := attribute "=" value
 * ```
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
 * @see https://datatracker.ietf.org/doc/html/rfc2397
 * @throws {TypeError} incorrect or missing data protocol
 * @throws {TypeError} malformed data url
 * @param {string} dataurl
 * @return {DataUrl}
 */
function parseDataURL(dataurl) {
	validateString(dataurl);

	dataurl = dataurl.trim();

	if (dataurl.substring(0, 5) !== "data:") {
		throw new TypeError("incorrect or missing data protocol");
	}

	dataurl = dataurl.substring(5);

	const p = dataurl.indexOf(",");
	if (p === -1) {
		throw new TypeError("malformed data url");
	}

	let content = dataurl.substring(p + 1);
	const mediatypeAndBase64 = dataurl.substring(0, p).trim();
	let mediatype = "text/plain;charset=US-ASCII";
	let base64Flag = false;

	if (mediatypeAndBase64 !== "") {
		mediatype = mediatypeAndBase64;
		if (mediatypeAndBase64.endsWith("base64")) {
			const i = mediatypeAndBase64.lastIndexOf(";");
			mediatype = mediatypeAndBase64.substring(0, i);
			base64Flag = true;
		} else {
			content = decodeURIComponent(content);
		}

		mediatype = parseMediaType(mediatype);
	} else {
		content = decodeURIComponent(content);
	}

	return new DataUrl(content, mediatype, base64Flag);
}
