/*
This file is part of »zeitbild«.

Copyright 2025 'kcf' <fenris@folksprak.org>

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

»zeitbild« 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with »zeitbild«. If not, see <http://www.gnu.org/licenses/>.
 */


namespace _zeitbild.repository.calendar
{
	
	/**
	 */
	type type_core_row = {
		name : string;
		hue : int;
		access_public : boolean;
		access_level_default : int;
		resource_id : int;
	};
	
	
	/**
	 */
	type type_access_attributed_group_row = {
		// calendar_id : int;
		group_id : int;
		level : int;
	};
	
	
	/**
	 */
	type type_access_attributed_group_preview = {
		group_id : int;
		level : int;
	};
	
	
	/**
	 */
	type type_access_attributed_user_row = {
		// calendar_id : int;
		user_id : int;
		level : int;
	};
	
	
	/**
	 */
	type type_access_attributed_user_preview = {
		user_id : int;
		level : int;
	};
	
	
	/**
	 */
	type type_dispersal = {
		core_row : type_core_row;
		access_attributed_group_rows : Array<type_access_attributed_group_row>;
		access_attributed_user_rows : Array<type_access_attributed_user_row>;
	};
	
	
	/**
	 */
	const hue_scaling : int = 0xFFFF;
	
	
	/**
	 */
	var _core_store : (
		null
		|
		lib_plankton.storage.type_store<
			_zeitbild.type_calendar_id,
			Record<string, any>,
			{},
			lib_plankton.storage.type_sql_table_autokey_search_term,
			Record<string, any>
		>
	) = null;
	
	
	/**
	 */
	var _access_attributed_group_chest : (
		null
		|
		lib_plankton.storage.type_chest<
			Array<any>,
			Record<string, any>,
			lib_plankton.database.type_description_create_table,
			lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
			Record<string, any>
		>
	) = null;
	
	
	/**
	 */
	var _access_attributed_user_chest : (
		null
		|
		lib_plankton.storage.type_chest<
			Array<any>,
			Record<string, any>,
			lib_plankton.database.type_description_create_table,
			lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
			Record<string, any>
		>
	) = null;
	
	
	/**
	 */
	function get_core_store(
	)
	: lib_plankton.storage.type_store<
		_zeitbild.type_calendar_id,
		Record<string, any>,
		{},
		lib_plankton.storage.type_sql_table_autokey_search_term,
		Record<string, any>
	>
	{
		if (_core_store === null)
		{
			_core_store = lib_plankton.storage.sql_table_autokey_store(
				{
					"database_implementation": _zeitbild.database.get_implementation(),
					"table_name": "calendars",
					"key_name": "id",
				}
			);
		}
		else
		{
			// do nothing
		}	
		return _core_store;
	}
	
	
	/**
	 */
	function get_access_attributed_group_chest(
	) : lib_plankton.storage.type_chest<
		Array<any>,
		Record<string, any>,
		lib_plankton.database.type_description_create_table,
		lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
		Record<string, any>
	>
	{
		if (_access_attributed_group_chest === null)
		{
			_access_attributed_group_chest = lib_plankton.storage.sql_table_common.chest(
				{
					"database_implementation": _zeitbild.database.get_implementation(),
					"table_name": "calendar_access_attributed_group",
					"key_names": ["calendar_id","group_id"],
				}
			);
		}
		else
		{
			// do nothing
		}
		return _access_attributed_group_chest;
	}
	
	
	/**
	 */
	function get_access_attributed_user_chest(
	)
	: lib_plankton.storage.type_chest<
		Array<any>,
		Record<string, any>,
		lib_plankton.database.type_description_create_table,
		lib_plankton.storage.sql_table_common.type_sql_table_common_search_term,
		Record<string, any>
	>
	{
		if (_access_attributed_user_chest === null)
		{
			_access_attributed_user_chest = lib_plankton.storage.sql_table_common.chest(
				{
					"database_implementation": _zeitbild.database.get_implementation(),
					"table_name": "calendar_access_attributed_user",
					"key_names": ["calendar_id","user_id"],
				}
			);
		}
		else
		{
			// do nothing
		}
		return _access_attributed_user_chest;
	}
	
	
	/**
	 */
	function encode_access_level(
		access_level : _zeitbild.enum_access_level
	) : int
	{
		return (
			[
				_zeitbild.enum_access_level.none,
				_zeitbild.enum_access_level.view,
				_zeitbild.enum_access_level.edit,
				_zeitbild.enum_access_level.admin,
			].indexOf(access_level)
		);
	}
	
	
	/**
	 */
	function decode_access_level(
		access_level_encoded : int
	) : _zeitbild.enum_access_level
	{
		return (
			[
				_zeitbild.enum_access_level.none,
				_zeitbild.enum_access_level.view,
				_zeitbild.enum_access_level.edit,
				_zeitbild.enum_access_level.admin,
			][access_level_encoded]
		);
	}
	
	
	/**
	 */
	function encode(
		object : _zeitbild.type_calendar_object
	)
	: type_dispersal
	{
		return {
			"core_row": {
				"name": object.name,
				"hue": Math.floor(object.hue * hue_scaling),
				"access_public": object.access.public,
				"access_level_default": encode_access_level(object.access.default_level),
				"resource_id": object.resource_id,
			},
			"access_attributed_group_rows": (
				lib_plankton.map.dump(object.access.attributed_group)
				.map(
					({"key": group_id, "value": level}) => ({
						// "calendar_id": calendar_id,
						"group_id": group_id,
						"level": encode_access_level(level),
					})
				)
			),
			"access_attributed_user_rows": (
				lib_plankton.map.dump(object.access.attributed_user)
				.map(
					({"key": user_id, "value": level}) => ({
						// "calendar_id": calendar_id,
						"user_id": user_id,
						"level": encode_access_level(level),
					})
				)
			),
		};
	}
	
	
	/**
	 */
	function decode(
		dispersal : type_dispersal
	)
	: _zeitbild.type_calendar_object
	{
		return {
			"name": dispersal.core_row.name,
			"hue": (dispersal.core_row.hue / hue_scaling),
			"access": {
				"public": dispersal.core_row.access_public,
				"default_level": decode_access_level(dispersal.core_row.access_level_default),
				"attributed_group": lib_plankton.map.hashmap.implementation_map(
					lib_plankton.map.hashmap.make<_zeitbild.type_group_id, _zeitbild.enum_access_level>(
						x => x.toFixed(0),
						{
							"pairs": (
								dispersal.access_attributed_group_rows
								.map(
									(access_attributed_group_row) => ({
										// "calendar_id": access_attributed_group_row["calendar_id"],
										// "key": access_attributed_group_row["preview"]["user_id"],
										"key": access_attributed_group_row.group_id,
										// "value": decode_access_level(access_attributed_group_row["preview"]["level"]),
										"value": decode_access_level(access_attributed_group_row.level),
									})
								)
							),
						}
					)
				),
				"attributed_user": lib_plankton.map.hashmap.implementation_map(
					lib_plankton.map.hashmap.make<_zeitbild.type_user_id, _zeitbild.enum_access_level>(
						x => x.toFixed(0),
						{
							"pairs": (
								dispersal.access_attributed_user_rows
								.map(
									(access_attributed_user_row) => ({
										// "calendar_id": access_attributed_user_row["calendar_id"],
										// "key": access_attributed_user_row["preview"]["user_id"],
										"key": access_attributed_user_row.user_id,
										// "value": decode_access_level(access_attributed_user_row["preview"]["level"]),
										"value": decode_access_level(access_attributed_user_row.level),
									})
								)
							),
						}
					)
				),
			},
			"resource_id": dispersal.core_row.resource_id,
		};
	}
	
	
	/**
	 */
	export async function read(
		id : _zeitbild.type_calendar_id
	)
	: Promise<_zeitbild.type_calendar_object>
	{
		const core_row : type_core_row = ((await get_core_store().read(id)) as type_core_row);
		const access_attributed_group_rows : Array<type_access_attributed_group_row> = await (
			get_access_attributed_group_chest().search(
				{
					"expression": "(calendar_id = $calendar_id)",
					"arguments": {
						"calendar_id": id,
					}
				}
			)
			.then(
				(hits) => Promise.resolve<Array<type_access_attributed_group_row>>(
					hits
					.map(
						hit => (
							{
								// "calendar_id": null,
								"group_id": hit.preview.group_id,
								"level": hit.preview.level,
							}
						)
					)
				)
			)
		);
		const access_attributed_user_rows : Array<type_access_attributed_user_row> = await (
			get_access_attributed_user_chest().search(
				{
					"expression": "(calendar_id = $calendar_id)",
					"arguments": {
						"calendar_id": id,
					}
				}
			)
			.then(
				(hits) => Promise.resolve<Array<type_access_attributed_user_row>>(
					hits
					.map(
						hit => (
							{
								// "calendar_id": null,
								"user_id": hit.preview.user_id,
								"level": hit.preview.level,
							}
						)
					)
				)
			)
		);
		const dispersal : type_dispersal = {
			"core_row": core_row,
			"access_attributed_group_rows": access_attributed_group_rows,
			"access_attributed_user_rows": access_attributed_user_rows,
		};
		const calendar_object : _zeitbild.type_calendar_object = decode(dispersal);
		return calendar_object;
	}
	
	
	/**
	 */
	export async function create(
		calendar_object : _zeitbild.type_calendar_object
	) : Promise<_zeitbild.type_calendar_id>
	{
		const dispersal : type_dispersal = encode(calendar_object);
		const core_store = get_core_store();
		const calendar_id : _zeitbild.type_calendar_id = await core_store.create(
			dispersal.core_row
		);
		for await (const access_attributed_group_row of dispersal.access_attributed_group_rows)
		{
			get_access_attributed_group_chest().write(
				[calendar_id, access_attributed_group_row.group_id],
				{"level": access_attributed_group_row.level}
			);
		}
		for await (const access_attributed_user_row of dispersal.access_attributed_user_rows)
		{
			get_access_attributed_user_chest().write(
				[calendar_id, access_attributed_user_row.user_id],
				{"level": access_attributed_user_row.level}
			);
		}
		await lib_plankton.cache.clear(_zeitbild.cache_regular);
		return Promise.resolve<_zeitbild.type_calendar_id>(calendar_id);
	}
	
	
	/**
	 */
	export async function update(
		calendar_id : _zeitbild.type_calendar_id,
		calendar_object : _zeitbild.type_calendar_object
	) : Promise<void>
	{
		const dispersal : type_dispersal = encode(calendar_object);
		// core
		{
			const core_store = get_core_store();
			await core_store.update(
				calendar_id,
				dispersal.core_row
			);
		}
		// attributed:group
		{
			const access_attributed_group_chest = get_access_attributed_group_chest();
			const hits : Array<{key : Array<any>; preview : type_access_attributed_group_row;}> = (
				(await access_attributed_group_chest.search(
					{
						"expression": "(calendar_id = $calendar_id)",
						"arguments": {
							"calendar_id": calendar_id,
						}
					}
				))
				.map(
					hit => (
						{
							"key": hit.key,
							"preview": {
								"group_id": hit.preview["group_id"],
								"level": hit.preview["level"],
							}
						}
					)
				)
			);
			const contrast = lib_plankton.list.contrast<
				{key : Array<any>; preview : type_access_attributed_group_row;},
				type_access_attributed_group_row
			>(
				hits,
				hit => hit.preview.group_id.toFixed(0),
				dispersal.access_attributed_group_rows,
				row => row.group_id.toFixed(0)
			);
			// delete
			for await (const entry of contrast.only_left)
			{
				await access_attributed_group_chest.delete(
					[calendar_id, entry.left.preview.group_id]
				);
			}
			// update
			for await (const entry of contrast.both)
			{
				await access_attributed_group_chest.write(
					[calendar_id, entry.right.group_id],
					{"level": entry.right.level}
				);
			}
			// create
			for await (const entry of contrast.only_right)
			{
				await access_attributed_group_chest.write(
					[calendar_id, entry.right.group_id],
					{"level": entry.right.level}
				);
			}
		}
		// attributed:user
		{
			const access_attributed_user_chest = get_access_attributed_user_chest();
			const hits : Array<{key : Array<any>; preview : type_access_attributed_user_row;}> = (
				(await access_attributed_user_chest.search(
					{
						"expression": "(calendar_id = $calendar_id)",
						"arguments": {
							"calendar_id": calendar_id,
						}
					}
				))
				.map(
					hit => (
						{
							"key": hit.key,
							"preview": {
								"user_id": hit.preview["user_id"],
								"level": hit.preview["level"],
							}
						}
					)
				)
			);
			const contrast = lib_plankton.list.contrast<
				{key : Array<any>; preview : type_access_attributed_user_row;},
				type_access_attributed_user_row
			>(
				hits,
				hit => hit.preview.user_id.toFixed(0),
				dispersal.access_attributed_user_rows,
				row => row.user_id.toFixed(0)
			);
			// delete
			for await (const entry of contrast.only_left)
			{
				await access_attributed_user_chest.delete(
					[calendar_id, entry.left.preview.user_id]
				);
			}
			// update
			for await (const entry of contrast.both)
			{
				await access_attributed_user_chest.write(
					[calendar_id, entry.right.user_id],
					{"level": entry.right.level}
				);
			}
			// create
			for await (const entry of contrast.only_right)
			{
				await access_attributed_user_chest.write(
					[calendar_id, entry.right.user_id],
					{"level": entry.right.level}
				);
			}
		}
		await lib_plankton.cache.clear(_zeitbild.cache_regular);
		return Promise.resolve<void>(undefined);
	}
	
	
	/**
	 * @todo remove events from resource?
	 * @todo remove resource
	 */
	export async function delete_(
		calendar_id : _zeitbild.type_calendar_id
	)
	: Promise<void>
	{
		await lib_plankton.cache.clear(_zeitbild.cache_regular);
		const core_store = get_core_store();
		const access_attributed_user_chest = get_access_attributed_user_chest();
		// attributed:user
		{
			const chest = get_access_attributed_user_chest();
			const hits : Array<
				{
					key : Array<any>;
					preview : Record<string, any>;
				}
			> = await chest.search(
				{
					"expression": "(calendar_id = $calendar_id)",
					"arguments": {
						"calendar_id": calendar_id,
					}
				}
			);
			for (const hit of hits)
			{
				await chest.delete(hit.key);
			}
		}
		// attributed:group
		{
			const chest = get_access_attributed_group_chest();
			const hits : Array<
				{
					key : Array<any>;
					preview : Record<string, any>;
				}
			> = await chest.search(
				{
					"expression": "(calendar_id = $calendar_id)",
					"arguments": {
						"calendar_id": calendar_id,
					}
				}
			);
			for (const hit of hits)
			{
				await chest.delete(hit.key);
			}
		}
		// core
		{
			await core_store.delete(
				calendar_id
			);
		}
		return Promise.resolve<void>(undefined);
	}
	
	
	/**
	 */
	type type_overview_entry = {
		id : _zeitbild.type_calendar_id;
		name : string;
		hue : float;
		access_level : _zeitbild.enum_access_level;
	}
	
	
	/**
	 */
	export async function overview(
		user_id : (null | _zeitbild.type_user_id)
	)
	: Promise<
		Array<
			type_overview_entry
		>
	>
	{
		type type_data = {
			hits_core : Array<
				{
					key : int;
					preview : Record<string, any>;
				}
			>;
			hits_access_attributed_group : Array<
				{
					key : int;
					preview : Record<string, any>;
				}
			>;
			hits_access_attributed_user : Array<
				{
					key : int;
					preview : Record<string, any>;
				}
			>;
		};
		return lib_plankton.cache.get_complex<any, Array<type_overview_entry>>(
			_zeitbild.cache_regular,
			"calendar_overview",
			{
				"user_id": user_id,
			},
			60,
			async () => lib_plankton.call.convey(
				{
					"hits_core": await get_core_store().search(
						{
							"expression": "TRUE",
							"arguments": {}
						}
					),
					"hits_access_attributed_group": await get_access_attributed_group_chest().search(
						(user_id === null)
						?
						{
							"expression": "TRUE",
							"arguments": {}
						}
						:
						{
							"expression": "(group_id IN (SELECT group_id FROM user_groups WHERE (user_id = $user_id)))",
							"arguments": {"user_id": user_id}
						}
					),
					"hits_access_attributed_user": await get_access_attributed_user_chest().search(
						(user_id === null)
						?
						{
							"expression": "TRUE",
							"arguments": {}
						}
						:
						{
							"expression": "(user_id = $user_id)",
							"arguments": {"user_id": user_id}
						}
					),
				},
				[
					// transform
					(data : type_data) => data.hits_core.map(
						(hit_core) => {
							const calendar_id : _zeitbild.type_calendar_id = hit_core.key;
							return {
								"id": calendar_id,
								"name": hit_core.preview["name"],
								"hue": (hit_core.preview["hue"] / hue_scaling),
								"access_level": _zeitbild.access_level_determine_raw(
									hit_core.preview["access_public"],
									(
										(user_id === null)
										?
										null
										:
										{
											"default": decode_access_level(hit_core.preview["access_level_default"]),
											"group": lib_plankton.call.convey(
												data.hits_access_attributed_group,
												[
													(x : Array<{key : int; preview : Record<string, any>}>) => x.filter(
														hit_access_attributed_group => (
															(hit_access_attributed_group.preview.calendar_id === calendar_id)
														)
													),
													(x : Array<{key : int; preview : Record<string, any>}>) => x.map(
														hit_access_attributed_group => hit_access_attributed_group.preview.level
													),
													(x : Array<int>) => x.map(
														decode_access_level
													),
												]
											),
											"user": lib_plankton.call.convey(
												data.hits_access_attributed_user,
												[
													(x : Array<{key : int; preview : Record<string, any>}>) => x.filter(
														hits_access_attributed_user => (
															(hits_access_attributed_user.preview.calendar_id === calendar_id)
														)
													),
													(x : Array<{key : int; preview : Record<string, any>}>) => x.map(
														hits_access_attributed_user => hits_access_attributed_user.preview.level
													),
													(x : Array<int>) => x.map(
														decode_access_level
													),
													(x : Array<_zeitbild.enum_access_level>) => (x[0] ?? null),
												]
											),
										}
									)
								),
							};
						}
					),
					// only keep visible calendars
					(x : Array<type_overview_entry>) => x.filter(
						(row) => (
							! _zeitbild.access_level_order(
								row.access_level,
								_zeitbild.enum_access_level.none
							)
						)
					),
					// sort by access level and name
					(x : Array<type_overview_entry>) => lib_plankton.list.sorted<type_overview_entry>(
						x,
						{
							"compare_element": lib_plankton.order.order_lexicographic_pair_wrapped<type_overview_entry, _zeitbild.enum_access_level, int>(
								row => row.access_level,
								row => row.id,
								{
									"order_first": (a, b) => _zeitbild.access_level_order(b, a),
									"order_second": (a, b) => (a <= b)
								}
							),
						}
					),
				]
			)
		);
	}
	
}

