import { decodeEncodedWords, BDecode, BEncode, QPDecode, decodeText } from 'Mime/Encoding';
import { addressparser } from 'Mime/Address';

export function ParseMime(text)
{
	class MimePart
	{
/*
		constructor() {
			this.id = 0;
			this.start = 0;
			this.end = 0;
			this.parts = [];
			this.bodyStart = 0;
			this.bodyEnd = 0;
			this.boundary = '';
			this.bodyText = '';
			// https://datatracker.ietf.org/doc/html/rfc2822#section-3.6
			// https://datatracker.ietf.org/doc/html/rfc4021
			this.headers = {
				// Required
				date = null,
				from = [], // mailbox-list
				// Optional
				sender          = [], // mailbox MUST occur with multi-address
				'reply-to'      = [], // address-list
				to              = [], // address-list
				cc              = [], // address-list
				bcc             = [], // address-list
				'message-id'    = '', // msg-id SHOULD be present
				'in-reply-to'   = '', // 1*msg-id SHOULD occur in some replies
				references      = '', // 1*msg-id SHOULD occur in some replies
				subject         = '', // unstructured
				// Optional unlimited
				comments        = [], // unstructured
				keywords        = [], // phrase *("," phrase)
				// https://datatracker.ietf.org/doc/html/rfc2822#section-3.6.6
				'resent-date'   = [],
				'resent-from'   = [],
				'resent-sender' = [],
				'resent-to'     = [],
				'resent-cc'     = [],
				'resent-bcc'    = [],
				'resent-msg-id' = [],
				// https://datatracker.ietf.org/doc/html/rfc2822#section-3.6.7
				trace           = [],
				'return-path'   = '', // angle-addr
				received        = [],
				// optional others outside RFC2822
				'mime-version'              = '', // RFC2045
				'content-transfer-encoding' = '',
				'content-type'              = '',
				'delivered-to'              = [], // RFC9228 addr-spec
				'authentication-results'    = '', // dkim, spf, dmarc
				'dkim-signature'            = '',
				'x-rspamd-queue-id'         = '',
				'x-rspamd-action'           = '',
				'x-spamd-bar'               = '',
				'x-rspamd-server'           = '',
				'x-spamd-result'            = '',
				'x-remote-address'          = '',
				// etc.
			};
		}
*/

		header(name) {
			return this.headers?.[name];
		}

		headerValue(name) {
			return this.header(name)?.value;
		}

		get raw() {
			return text.slice(this.start, this.end);
		}

		get bodyRaw() {
			return text.slice(this.bodyStart, this.bodyEnd);
		}

		get body() {
			let body = this.bodyRaw,
				charset = this.header('content-type')?.params.charset,
				encoding = this.headerValue('content-transfer-encoding')?.toLowerCase();
			if ('quoted-printable' == encoding) {
				body = QPDecode(body);
			} else if ('base64' == encoding) {
				body = BDecode(body.replace(/\r?\n/g, ''));
			}
			return decodeText(charset, body);
		}

		get dataUrl() {
			let body = this.bodyRaw,
				encoding = this.headerValue('content-transfer-encoding')?.toLowerCase();
			if ('base64' == encoding) {
				body = body.replace(/\r?\n/g, '');
			} else {
				if ('quoted-printable' == encoding) {
					body = QPDecode(body);
				}
				body = BEncode(body);
			}
			return 'data:' + this.headerValue('content-type') + ';base64,' + body;
		}

		forEach(fn) {
			fn(this);
			this.parts.forEach(part => part.forEach(fn));
		}

		getByContentType(type) {
			if (type == this.headerValue('content-type')?.toLowerCase()) {
				return this;
			}
			let i = 0, p = this.parts, part;
			for (i; i < p.length; ++i) {
				if ((part = p[i].getByContentType(type))) {
					return part;
				}
			}
		}
	}

	// mailbox-list or address-list
	const lists = ['from','reply-to','to','cc','bcc'];

	const ParsePart = (mimePart, start_pos = 0, id = '') =>
	{
		let part = new MimePart,
			head = mimePart.match(/^[\s\S]+?\r?\n\r?\n/)?.[0],
			headers = {};
		if (id) {
			part.id = id;
			part.start = start_pos;
			part.end = start_pos + mimePart.length;
		}
		part.parts = [];

		// get headers
		if (head) {
			head.replace(/\r?\n\s+/g, ' ').split(/\r?\n/).forEach(header => {
				let match = header.match(/^([^:]+):\s*([^;]+)/),
					params = {};
				if (match) {
					[...header.matchAll(/;\s*([^;=]+)=\s*"?([^;"]+)"?/g)].forEach(param =>
						params[param[1].trim().toLowerCase()] = param[2].trim()
					);
					let field = match[1].trim().toLowerCase();
					if (lists.includes(field)) {
						match[2] = addressparser(match[2]);
					} else if ('keywords' === field) {
						match[2] = match[2].split(',').forEach(entry => decodeEncodedWords(entry.trim()));
						match[2] = (headers[field]?.value || []).concat(match[2]);
					} else {
						match[2] = decodeEncodedWords(match[2].trim());
						if ('comments' === field) {
							match[2] = (headers[field]?.value || []).push(match[2]);
						}
					}
					headers[field] = {
						value: match[2],
						params: params
					};
				}
			});

			// get body
			part.bodyStart = start_pos + head.length;
			part.bodyEnd = start_pos + mimePart.length;

			// get child parts
			let boundary = headers['content-type']?.params.boundary;
			if (boundary) {
				part.boundary = boundary;
				let regex = new RegExp('(?:^|\r?\n)--' + RegExp.escape(boundary) + '(?:--)?(?:\r?\n|$)', 'g'),
					body = mimePart.slice(head.length),
					bodies = body.split(regex),
					pos = part.bodyStart;
				[...body.matchAll(regex)].forEach(([boundary], index) => {
					if (!index) {
						// Mostly something like: "This is a multi-part message in MIME format."
						part.bodyText = bodies[0];
					}
					// Not the end?
					if ('--' != boundary.trim().slice(-2)) {
						pos += bodies[index].length + boundary.length;
						part.parts.push(ParsePart(bodies[1+index], pos, ((id ? id + '.' : '') + (1+index))));
					}
				});
			}

			part.headers = headers;
		}

		return part;
	};

	return ParsePart(text);
}
