/*
Espe | Ein schlichtes Werkzeug zur Mitglieder-Verwaltung | Backend
Copyright (C) 2024 Christian Fraß

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public 
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later 
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied 
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see 
<https://www.gnu.org/licenses/>. 
 */

namespace _espe.service.member
{
	
	/**
	 */
	var _hooks_change : Array<() => void> = [];
	
	
	/**
	 */
	export function listen_change(
		procedure : () => void
	) : void
	{
		_hooks_change.push(procedure);
	}
	
	
	/**
	 */
	function signal_change(
	) : void
	{
		_hooks_change.forEach(
			procedure => {
				procedure();
			}
		);
	}
	
	
	/**
	 */
	function validate_password(
		password : string
	) : Array<{incident : string; details : Record<string, any>}>
	{
		return _espe.helper.password.validate(
			_espe.conf.get().settings.password_policy,
			password
		);
	}
	
	
	/**
	 */
	function generate_password(
	) : string
	{
		return _espe.helper.password.generate(
			_espe.conf.get().settings.password_policy
		);
	}
	
	
	/**
	 * ermittelt den Anmelde-Name des Mitglieds
	 */
	export function name_login(
		object : _espe.type.member_object
	) : string
	{
		return lib_plankton.string.coin(
			"{{object}}{{extension}}",
			{
				"object": lib_plankton.call.convey(
					object.name_real_value,
					[
						(x : string) => x.toLowerCase(),
						(x : string) => x.replace(new RegExp(" ", "g"), "."),
						(x : string) => x.replace(new RegExp("[äÄ]", "g"), "ae"),
						(x : string) => x.replace(new RegExp("[öÖ]", "g"), "oe"),
						(x : string) => x.replace(new RegExp("[üÜ]", "g"), "ue"),
						(x : string) => x.replace(new RegExp("[ß]", "g"), "ss"),
						(x : string) => x.replace(new RegExp("[^0-9a-z-\.]", "g"), "_"),
					]
				),
				"extension": (
					(object.name_real_index <= 1)
					? ""
					: ("." + object.name_real_index.toFixed(0))
				),
			}
		);
	}
	
	
	/**
	 * ermittelt den Anzeige-Name des Mitglieds
	 */
	export function name_display(
		object : _espe.type.member_object
	) : string
	{
		return object.name_real_value;
	}
	
	
	/**
	 * ermittelt die verschleierte E-Mail-Adresse des Mitglieds
	 */
	export function email_address_veiled(
		object : _espe.type.member_object
	) : (null | string)
	{
		return (
			(object.membership_number === null)
			? null
			: lib_plankton.string.coin(
				"{{prefix}}{{membership_number}}@{{domain}}",
				{
					"prefix": _espe.conf.get().settings.misc.prefix_for_veiled_email_addresses,
					"membership_number": object.membership_number,
					"domain": _espe.conf.get().settings.organisation.domain,
				}
			)
		);
	}
	
	
	/**
	 * ermittelt die namentliche E-Mail-Adresse des Mitglieds
	 */
	export function email_address_nominal(
		object : _espe.type.member_object
	) : string
	{
		return lib_plankton.string.coin(
			"{{user}}@{{domain}}",
			{
				"user": name_login(object),
				"domain": _espe.conf.get().settings.organisation.domain,
			}
		);
	}
	
	
	/**
	 * ermittelt die allgemein zu verwendende E-Mail-Adresse des Mitglieds
	 */
	export function email_address(
		object : _espe.type.member_object
	) : (null | string)
	{
		return (
			object.email_use_nominal_address
			? email_address_nominal(object)
			: (
				object.email_use_veiled_address
				? email_address_veiled(object)
				: object.email_address_private
			)
		);
	}
	
	
	/**
	 * ermittelt das Passwort-Abbild anhand des tatsächlichen Passworts
	 */
	function password_image(
		password : (null | string)
	) : Promise<(null | string)>
	{
		return (
			(
				(! (password === null))
				&&
				(! (password === ""))
			)
			? _espe.helpers.bcrypt_compute(password)
			: Promise.resolve<(null | string)>(null)
		);
	}
	
	
	/**
	 */
	async function send_activation_email(
		member_object : _espe.type.member_object,
		options : {
			password ?: (null | string);
		} = {}
	) : Promise<void>
	{
		options = Object.assign(
			{
				"password": null,
			},
			options
		);
		if (! member_object.enabled) {
			// do nothing
		}
		else {
			if (member_object.email_address_private === null) {
				// do nothing
			}
			else {
				await _espe.helpers.email_send(
					[
						member_object.email_address_private,
					],
					lib_plankton.string.coin(
						"{{head}} | {{core}}",
						{
							"head": _espe.conf.get().settings.organisation.name,
							"core": lib_plankton.translate.get("email.activation.subject"),
						}
					),
					lib_plankton.string.coin(
						lib_plankton.translate.get("email.activation.body"),
						{
							"name_display": name_display(member_object),
							"name_login": name_login(member_object),
							"url": (_espe.conf.get().settings.connections.login_url ?? "--"),
							"password_info": (
								(
									(options.password === undefined)
									||
									(options.password === null)
								)
								? ""
								: lib_plankton.string.coin(
									lib_plankton.translate.get("email.activation.password_info"),
									{
										"password": options.password,
									}
								)
							),
						}
					)
				);
			}
		}
	}
	
	
	/**
	 * gibt die vollständigen Daten aller Mitglieder aus
	 */
	export async function dump(
	) : Promise<
		Array<
			{
				id : _espe.type.member_id;
				object : _espe.type.member_object;
			}
		>
	>
	{
		return _espe.repository.member.dump();
	}

	
	/**
	 * gibt eine Auflistung aller Mitgliedr aus
	 */
	export async function list(
		search_term : (null | string)
	) : Promise<
		Array<
			{
				id : _espe.type.member_id;
				preview : {
					membership_number : string;
					name_real_value : string;
					name_real_index : int;
				};
			}
		>
	>
	{
		return _espe.repository.member.list(search_term);
	}
	
	
	
	/**
	 * gibt die Angaben eines bestimmten Mitglieds aus
	 */
	export function get(
		id : _espe.type.member_id
	) : Promise<_espe.type.member_object>
	{
		return _espe.repository.member.read(id);
	}
	
	
	/**
	 * legt ein Mitglied an
	 */
	export async function project(
		data : {
			membership_number : (null | string);
			name_real_value : string;
			email_address_private : (null | string);
			groups : Array<string>;
		},
		{
			"silent": silent = false,
		} : {
			silent ?: boolean;	
		} = {
		}
	) : Promise<_espe.type.member_id>
	{
		const name_real_index : int = await _espe.service.name_index.next(data.name_real_value);
		const object : _espe.type.member_object = {
			"membership_number": data.membership_number,
			"name_real_value": data.name_real_value,
			"name_real_index": name_real_index,
			"email_address_private": data.email_address_private,
			"registered": false,
			"enabled": true,
			"email_use_veiled_address": false,
			"email_use_nominal_address": false,
			"email_redirect_to_private_address": false,
			"email_allow_sending": false,
			"password_image": null,
			"password_change_last_attempt": null,
			"password_change_token": null,
			"groups": data.groups,
		};
		const id : _espe.type.member_id = await _espe.repository.member.create(object);
		if (silent) {
			// do nothing
		}
		else {
			signal_change();
		}
		return id;
	}
	
	
	/**
	 * sendet an ein Mitglied eine E-Mail mit Aufforderung zur Registrierung
	 */
	export async function summon(
		member_id : _espe.type.member_id,
		url_template : string
	) : Promise<(null | string)>
	{
		_espe.helpers.frontend_url_check();
		const member_object : _espe.type.member_object = await get(member_id);
		if (member_object.email_address_private === null) {
			return null;
		}
		else {
			const url : (null | string) = _espe.helpers.frontend_url_get(
				url_template,
				{
					"verification": await _espe.helpers.verification_get(member_id),
				}
			);
			if (url === null) {
				// do nothing
			}
			else {
				await _espe.helpers.email_send(
					[
						member_object.email_address_private,
					],
					lib_plankton.string.coin(
						"{{head}} | {{core}}",
						{
							"head": _espe.conf.get().settings.organisation.name,
							"core": lib_plankton.translate.get("email.summon.subject"),
						}
					),
					lib_plankton.string.coin(
						lib_plankton.translate.get("email.summon.body"),
						{
							"name": name_display(member_object),
							"url": url,
							"remark": (
								(_espe.conf.get().settings.summon_email.remark === null)
								? ""
								: (_espe.conf.get().settings.summon_email.remark + "\n\n")
							),
						}
					)
				);
			}
			return url;
		}
	}
	
	
	/**
	 * gibt Daten über ein Mitglied aus, die relevant für die Registrierung sind
	 */
	export async function info(
		member_id : _espe.type.member_id
	) : Promise<
		(
			null
			|
			{
				name_real_value : string;
				name_real_index : int;
				name_login : string;
				email_address_veiled : (null | string);
				email_address_nominal : string;
			}
		)
	>
	{
		const member_object : _espe.type.member_object = await _espe.repository.member.read(member_id);
		if (! member_object.registered) {
			return {
				"name_real_value": member_object.name_real_value,
				"name_real_index": member_object.name_real_index,
				"name_login": name_login(member_object),
				"email_address_veiled": email_address_veiled(member_object),
				"email_address_nominal": email_address_nominal(member_object),
			};
		}
		else {
			return null;
		}
	}
	
	
	/**
	 * führt die Registrierung für ein Mitglied durch
	 */
	export async function register(
		member_id : _espe.type.member_id,
		data : {
			email_use_veiled_address : boolean;
			email_use_nominal_address : boolean;
			email_redirect_to_private_address : boolean;
			password : (null | string);
		},
		options : {
			notification_target_url_template ?: (null | string);
		} = {}
	) : Promise<Array<{incident : string; details : Record<string, any>;}>>
	{
		options = Object.assign(
			{
				"notification_target_url_template": null,
			},
			options
		);
		
		const member_object : _espe.type.member_object = await get(member_id);
		
		let flaws : Array<{incident : string; details : Record<string, any>;}> = [];
		let password_value : string;
		let password_generated : boolean;
		if (member_object.registered) {
			flaws.push({"incident": "already_registered", "details": {}});
			password_value = "";
			password_generated = false;
		}
		else {
			if (
				(data.password !== null)
				&&
				(data.password !== "")
			) {
				flaws = flaws.concat(
					validate_password(data.password)
					.map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details}))
				);
				password_value = data.password;
				password_generated = false;
			}
			else {
				password_value = generate_password();
				password_generated = true;
			}
		}
		
		if (flaws.length > 0) {
			// do nothing
		}
		else {
			member_object.email_use_veiled_address = data.email_use_veiled_address;
			member_object.email_use_nominal_address = data.email_use_nominal_address;
			member_object.email_redirect_to_private_address = data.email_redirect_to_private_address;
			member_object.password_image = await password_image(password_value);
			member_object.registered = true;
			await _espe.repository.member.update(member_id, member_object);
			signal_change();
			{
				const url : (null | string) = (
					(
						(options.notification_target_url_template === undefined)
						||
						(options.notification_target_url_template === null)
					)
					? null
					: _espe.helpers.frontend_url_get(
						options.notification_target_url_template,
						{
							"id": member_id.toFixed(0),
						}
					)
				);
				/*await*/ _espe.service.admin.notify_all(
					lib_plankton.string.coin(
						"{{head}} | {{core}}",
						{
							"head": _espe.conf.get().settings.organisation.name,
							"core": lib_plankton.translate.get("email.registration.subject"),
						}
					),
					lib_plankton.string.coin(
						lib_plankton.translate.get("email.registration.body"),
						{
							"name_display": name_display(member_object),
							"url": (url ?? "?"),
						}
					)
				);
			}
			/*await*/ send_activation_email(member_object, {"password": password_generated ? password_value : null});
		}
		
		return Promise.resolve(flaws);
	}
	
	
	/**
	 * ändert bestimmte Daten des Mitglied
	 */
	export async function modify(
		member_id : _espe.type.member_id,
		data : {
			email_address_private : (null | string);
			registered : boolean;
			enabled : boolean;
			groups : lib_plankton.pod.type_pod<Array<string>>;
		}
	) : Promise<void>
	{
		const member_object_old : _espe.type.member_object = await get(member_id);
		const member_object_new : _espe.type.member_object = {
			"membership_number": member_object_old.membership_number,
			"name_real_value": member_object_old.name_real_value,
			"name_real_index": member_object_old.name_real_index,
			"email_address_private": data.email_address_private,
			"registered": data.registered,
			"enabled": data.enabled,
			"email_use_veiled_address": member_object_old.email_use_veiled_address,
			"email_use_nominal_address": member_object_old.email_use_nominal_address,
			"email_redirect_to_private_address": member_object_old.email_redirect_to_private_address,
			"email_allow_sending": member_object_old.email_allow_sending,
			"password_image": member_object_old.password_image,
			"password_change_last_attempt": member_object_old.password_change_last_attempt,
			"password_change_token": member_object_old.password_change_token,
			"groups": (
				lib_plankton.pod.is_filled<Array<string>>(data.groups)
				? lib_plankton.pod.cull<Array<string>>(data.groups)
				: member_object_old.groups
			),
		};
		await _espe.repository.member.update(member_id, member_object_new);
		signal_change();
		/*await*/ // send_activation_email(member_object_new);
	}
	
	
	/**
	 */
	export async function remove(
		id : _espe.type.member_id
	) : Promise<void>
	{
		await _espe.repository.member.delete_(id);
		signal_change();
	}
	
	
	/**
	 * bereitet eine Passwort-Rücksetzung für Mitglieder vor
	 * 
	 * @todo Zwangs-Pause falls Abklingzeit noch nicht vorbei um vorzugaukeln, dass es geklappt hat?
	 */
	export async function password_change_initialize(
		identifier : string,
		url_template : string
	) : Promise<void>
	{
		_espe.helpers.frontend_url_check();
		const now : int = lib_plankton.base.get_current_timestamp(true);
		const cooldown_time : int = _espe.conf.get().settings.password_change.cooldown_time;
		const member_ids : Array<_espe.type.member_id> = await (
			(await _espe.repository.member.dump())
			.filter(
				member_entry => (
					member_entry.object.registered
					&&
					member_entry.object.enabled
					&&
					(
						(
							(! (member_entry.object.email_address_private === null))
							&&
							(member_entry.object.email_address_private === identifier)
						)
						||
						(name_login(member_entry.object) === identifier)
					)
				)
			)
			.map(
				member_entry => member_entry.id
			)
		);
		for await (const member_id of member_ids) {
			const member_object_old : _espe.type.member_object = await _espe.repository.member.read(member_id);
			if (
				(! (member_object_old.password_change_last_attempt === null))
				&&
				((now - member_object_old.password_change_last_attempt) <= cooldown_time)
			) {
				lib_plankton.log.notice(
					"member_password_change_cooldown_not_over",
					{
						"member_id": member_id,
						"last_attempt": member_object_old.password_change_last_attempt,
						"now": now,
					}
				);
				// do nothing
			}
			else {
				if (member_object_old.email_address_private === null) {
					lib_plankton.log.notice(
						"member_password_change_impossible_due_to_missing_private_email_address",
						{
							"member_id": member_id,
						}
					);
					// do nothing
				}
				else {
					// keine echte Verifizierung, der Algorithmus ist aber der passende
					const token : string = await _espe.helpers.verification_get(Math.floor(Math.random() * (1 << 24)));
					const member_object_new : _espe.type.member_object = {
						"membership_number": member_object_old.membership_number,
						"name_real_value": member_object_old.name_real_value,
						"name_real_index": member_object_old.name_real_index,
						"email_address_private": member_object_old.email_address_private,
						"registered": member_object_old.registered,
						"enabled": member_object_old.enabled,
						"email_use_veiled_address": member_object_old.email_use_veiled_address,
						"email_use_nominal_address": member_object_old.email_use_nominal_address,
						"email_redirect_to_private_address": member_object_old.email_redirect_to_private_address,
						"email_allow_sending": member_object_old.email_allow_sending,
						"password_image": member_object_old.password_image,
						"password_change_last_attempt": now,
						"password_change_token": token,
						"groups": member_object_old.groups,
					};
					await _espe.repository.member.update(member_id, member_object_new);
					// signal_change();
					// do NOT wait in order to reduce information for potential attackers
					const url : (null | string) = _espe.helpers.frontend_url_get(
						url_template,
						{
							"id": member_id.toFixed(0),
							"token": token,
						}
					);
					if (url === null) {
						// do nothing
					}
					else {
						/*await*/ _espe.helpers.email_send(
							[
								member_object_old.email_address_private,
							],
							lib_plankton.string.coin(
								"{{head}} | {{core}}",
								{
									"head": _espe.conf.get().settings.organisation.name,
									"core": lib_plankton.translate.get("email.password_change.initialization.subject"),
								}
							),
							lib_plankton.string.coin(
								lib_plankton.translate.get("email.password_change.initialization.body"),
								{
									"name": name_display(member_object_old),
									"url": url,
								}
							)
						);
					}
				}
			}
		}
	}
	
	
	/**
	 * führt eine Passwort-Rücksetzung für ein Mitglied durch
	 * 
	 * @todo zeitliche Begrenzung?
	 */
	export async function password_change_execute(
		member_id : _espe.type.member_id,
		token : string,
		password_new : string
	) : Promise<Array<{incident : string; details : Record<string, any>;}>>
	{
		const member_object_old : _espe.type.member_object = await _espe.repository.member.read(member_id);
		if (member_object_old.email_address_private === null) {
			return Promise.reject(new Error("private e-mail address missing"));
		}
		else {
			let flaws : Array<{incident : string; details : Record<string, any>;}> = [];
			if (
				(member_object_old.password_change_token === null)
				||
				(! (token === member_object_old.password_change_token))
			) {
				lib_plankton.log.notice(
					"member_password_change_token_invalid",
					{
						"member_id": member_id,
						"token_sent": token,
					}
				);
				flaws.push({"incident": "token_invalid", "details": {}});
			}
			else {
				flaws = flaws.concat(
					validate_password(password_new)
					.map(flaw => ({"incident": ("password_" + flaw.incident), "details": flaw.details}))
				);
				if (flaws.length > 0) {
					// do nothing
				}
				else {
					const member_object_new : _espe.type.member_object = {
						"membership_number": member_object_old.membership_number,
						"name_real_value": member_object_old.name_real_value,
						"name_real_index": member_object_old.name_real_index,
						"email_address_private": member_object_old.email_address_private,
						"registered": member_object_old.registered,
						"enabled": member_object_old.enabled,
						"email_use_veiled_address": member_object_old.email_use_veiled_address,
						"email_use_nominal_address": member_object_old.email_use_nominal_address,
						"email_redirect_to_private_address": member_object_old.email_redirect_to_private_address,
						"email_allow_sending": member_object_old.email_allow_sending,
						"password_image": await password_image(password_new),
						"password_change_last_attempt": member_object_old.password_change_last_attempt,
						"password_change_token": null,
						"groups": member_object_old.groups,
					};
					await _espe.repository.member.update(member_id, member_object_new);
					signal_change();
					await _espe.helpers.email_send(
						[
							member_object_old.email_address_private,
						],
						lib_plankton.string.coin(
							"{{head}} | {{core}}",
							{
								"head": _espe.conf.get().settings.organisation.name,
								"core": lib_plankton.translate.get("email.password_change.execution.subject"),
							}
						),
						lib_plankton.string.coin(
							lib_plankton.translate.get("email.password_change.execution.body"),
							{
								"name": name_display(member_object_old),
							}
						)
					);
				}
			}
			return flaws;
		}
	}
	
	
	/**
	 * @todo check validity (e.g. username characters)
	 */
	export async function export_authelia_user_data(
		options : {
			custom_data ?: (null | Array<_espe.type.member_object>);
		} = {}
	) : Promise<any>
	{
		options = Object.assign(
			{
				"custom_data": null,
			},
			options
		);
		
		type type_entry = {
			disabled : boolean;
			displayname : string;
			email : string;
			groups : Array<string>;
			password : string;
		};
		return lib_plankton.call.convey(
			(
				(
					(options.custom_data !== undefined)
					&&
					(options.custom_data !== null)
				)
				? (options.custom_data.map((member_object, index) => ({"id": index, "object": member_object})))
				: await dump()
			),
			[
				(x : Array<any>) => x.map(
					entry => Object.assign(
						entry,
						{"email_address": email_address(entry.object)}
					)
				),
				(x : Array<any>) => x.filter(
					entry => (
						entry.object.registered
						&&
						(
							(entry.object.password_image !== null)
							&&
							(entry.object.password_image !== "")
						)
						&&
						(entry.email_address !== null)
					)
				),
				(x : Array<any>) => x.map(
					entry => ([
						name_login(entry.object),
						{
							"disabled": (! entry.object.enabled),
							"displayname": name_display(entry.object),
							"email": entry.email_address,
							"groups": entry.object.groups,
							"password": entry.object.password_image,
						}
					])
				),
				Object.fromEntries,
				(x : Record<string, type_entry>) => ({"users": x}),
			]
		);
	}
	
	
	/**
	 */
	export async function export_authelia_user_file(
		options : {
			custom_data ?: (null | Array<_espe.type.member_object>);
		} = {}
	) : Promise<string>
	{
		options = Object.assign(
			{
				"custom_data": null,
			},
			options
		);
		
		const nm_yaml = require("yaml");
		return nm_yaml.stringify(await export_authelia_user_data(options));
	}
	
	
	/**
	 */
	export async function output_authelia_file(
		output_parameters : {
			path : string;
		}
	) : Promise<void>
	{
		await lib_plankton.file.write(
			output_parameters.path,
			await _espe.service.member.export_authelia_user_file()
		);
	}
	
	
	/**
	 */
	export async function output_http(
		output_parameters : {
			scheme : ("http" | "https");
			host : string;
			path : string;
			method : ("get" | "post" | "put" | "patch" | "delete" | "head" | "options");
			query : (null | string);
			headers : Record<string,string>;
		}
	) : Promise<void>
	{
		const http_request : lib_plankton.http.type_request = {
			"scheme": output_parameters.scheme,
			"host": output_parameters.host,
			"path": output_parameters.path,
			"version": "HTTP/1.1",
			"method": (
				(
					{
						"get": lib_plankton.http.enum_method.get,
						"post": lib_plankton.http.enum_method.post,
						"put": lib_plankton.http.enum_method.put,
						"patch": lib_plankton.http.enum_method.patch,
						"delete": lib_plankton.http.enum_method.delete,
						"head": lib_plankton.http.enum_method.head,
						"options": lib_plankton.http.enum_method.options,
					}[output_parameters.method]
				)
				??
				lib_plankton.http.enum_method.post
			),
			"query": output_parameters.query,
			"headers": output_parameters.headers,
			"body": lib_plankton.json.encode(await _espe.service.member.dump()),
		};
		const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(
			http_request
		);
		if (! ((http_response.status_code >= 200) && (http_response.status_code < 400))) {
			lib_plankton.log.warning(
				"output_http_failed",
				{
					"http_request": http_request,
					"http_response_status_code": http_response.status_code,
				}
			);
		}
		else {
			// do nothing
		}
	}
	
	
	/**
	 */
	export async function output_arc(
		output_parameters : {
			http_scheme : ("http" | "https");
			http_host : string;
			http_port : int;
			hash_salt : string;
		}
	) : Promise<void>
	{
		const timestamp : float = lib_plankton.base.get_current_timestamp();
		const auth : string = lib_plankton.sha256.get(
			timestamp.toFixed(0)
			+
			output_parameters.hash_salt
		);
		const http_request : lib_plankton.http.type_request = {
			"scheme": output_parameters.http_scheme,
			"host": lib_plankton.string.coin(
				"{{host}}:{{port}}",
				{
					"host": output_parameters.http_host,
					"port": output_parameters.http_port.toFixed(0),
				}
			),
			"path": "/users/set",
			"version": "HTTP/1.1",
			"method": lib_plankton.http.enum_method.put,
			"query": lib_plankton.string.coin(
				"?timestamp={{timestamp}}&auth={{auth}}",
				{
					"timestamp": timestamp.toFixed(0),
					"auth": auth,
				}
			),
			"headers": {
				"Content-Type": "application/json",
			},
			// @ts-ignore
			"body": Buffer.from(
				lib_plankton.json.encode(
					(await _espe.service.member.export_authelia_user_data())
				),
				"utf8"
			),
		};
		const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(
			http_request
		);
		if (http_response.status_code !== 200) {
			lib_plankton.log.warning(
				"output_arc_failed",
				{
					"http_response_status_code": http_response.status_code,
				}
			);
		}
		else {
			// do nothing
		}
	}
	
}

