/*
This file is part of »dali«.

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

»dali« 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.

»dali« 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 »dali«. If not, see <http://www.gnu.org/licenses/>.
 */


namespace _dali.model
{
	
	/**
	 */
	type type_state = {
		groups : Array<
			{
				id : _dali.type_group_id;
				object : _dali.type_group_object;
			}
		>;
		users : Array<
			{
				id : _dali.type_user_id;
				name : string;
			}
		>;
		settings : {
			use_fallback_for_date_and_time_inputs : boolean;
			weekview_vertical_default : boolean;
		};
		calendars : (
			null
			|
			lib_plankton.map.type_map<
				_dali.type_calendar_id,
				{
					reduced : _dali.type_calendar_object_reduced;
					complete : (null | _dali.type_calendar_object);
				}
			>
		);
		events : lib_plankton.map.type_map<
			_dali.type_event_key,
			_dali.type_event_object_extended
		>;
		covered_dates : lib_plankton.set.type_set<
			lib_plankton.pit.type_date
		>;
	};
	
	
	/**
	 */
	let _state : (null | type_state) = null;
	
	
	/**
	 */
	let _listeners_reset : Array<((priviliged ?: boolean) => Promise<void>)> = [];
	
	
	/**
	 */
	function make_date_set(
		{
			"elements": elements = [],
		}
		:
		{
			elements ?: Array<lib_plankton.pit.type_date>;
		}
		=
		{
		}
	)
	: lib_plankton.set.type_set<lib_plankton.pit.type_date>
	{
		return lib_plankton.set.hashset.implementation_set(
			lib_plankton.set.hashset.make(
				x => lib_plankton.pit.date_format(x),
				{
					/**
					 * @todo häääh!?
					 */
					"elements": elements.filter(x => (x !== null)),
				}
			)
		);
	}
	
	
	/**
	 */
	function timeframe_to_date_set(
		timeframe : {
			from : lib_plankton.pit.type_pit;
			to : lib_plankton.pit.type_pit;
		}
	)
	: lib_plankton.set.type_set<lib_plankton.pit.type_date>
	{
		if (! lib_plankton.pit.is_before(timeframe.from, timeframe.to))
		{
			throw (new Error("invalid timeframe"));
		}
		else
		{
			const heuristic : int = Math.ceil((timeframe.to - timeframe.from) / (60 * 60 * 24));
			let pit_current : lib_plankton.pit.type_pit = timeframe.from;
			return make_date_set(
				{
					"elements": (
						lib_plankton.list.sequence(heuristic)
						.map(
							(offset) => lib_plankton.call.convey(
								timeframe.from,
								[
									x => lib_plankton.pit.shift_day(x, offset),
									x => lib_plankton.pit.to_datetime(x),
									x => x.date,
								]
							)
						)
					),
				}
			);
		}
	}
	
	
	/**
	 */
	export async function group_list(
	)
	: Promise<
		Array<
			{
				id : _dali.type_group_id;
				object : _dali.type_group_object;
			}
		>
	>
	{
		return Promise.resolve(_state.groups);
	}
	
	
	/**
	 */
	export async function user_list(
	)
	: Promise<
		Array<
			{
				id : _dali.type_user_id;
				name : string;
			}
		>
	>
	{
		return Promise.resolve(_state.users);
	}
	
	
	/**
	 */
	export async function settings(
	)
	: Promise<
		_dali.type_user_settings
	>
	{
		return Promise.resolve(_state.settings);
	}
	
	
	/**
	 */
	async function sync_calendars(
	)
	: Promise<void>
	{
		const data = await _dali.backend.calendar_list();
		lib_plankton.map.clear(_state.calendars);
		data.forEach(
			entry => {
				_state.calendars.set(
					entry.id,
					{
						"reduced": {
							"name": entry.name,
							"hue": entry.hue,
							"access_level": _dali.access_level_decode(entry.access_level),
						},
						"complete": null,
					}
				);
			}
		);
	}
	
	
	/**
	 */
	export function calendar_list(
	)
	: Promise<
		Array<
			_dali.type_calendar_object_reduced_with_id
		>
	>
	{
		return Promise.resolve(
			lib_plankton.map.dump(_state.calendars)
			.map(
				pair => (
					{
						"id": pair.key,
						"name": pair.value.reduced.name,
						"hue": pair.value.reduced.hue,
						"access_level": pair.value.reduced.access_level,
					}
				)
			)
		);
	}
	
	
	/**
	 */
	export async function calendar_get(
		calendar_id : _dali.type_calendar_id
	)
	: Promise<_dali.type_calendar_object>
	{
		const value = _state.calendars.get(calendar_id);
		if (value.complete === null)
		{
			const data = await _dali.backend.calendar_get(calendar_id);
			const calendar_object : _dali.type_calendar_object = {
				"name": data.name,
				"hue": data.hue,
				"access": {
					"public": data.access.public,
					"default_level": _dali.access_level_decode(data.access.default_level),
					"attributed_group": lib_plankton.map.hashmap.implementation_map(
						lib_plankton.map.hashmap.make(
							x => x.toFixed(0),
							{
								"pairs": (
									data.access.attributed_group
									.map(
										(entry) => (
											{
												"key": entry.group_id,
												"value": _dali.access_level_decode(entry.level),
											}
										)
									)
								),
							}
						)
					),
					"attributed_user": lib_plankton.map.hashmap.implementation_map(
						lib_plankton.map.hashmap.make(
							x => x.toFixed(0),
							{
								"pairs": (
									data.access.attributed_user
									.map(
										(entry) => (
											{
												"key": entry.user_id,
												"value": _dali.access_level_decode(entry.level),
											}
										)
									)
								),
							}
						)
					),
				},
				"resource_id": data.resource_id,
			};
			value.complete = calendar_object;
			/**
			 * @todo set in map?
			 */
			return calendar_object;
		}
		else
		{
			return value.complete;
		}
	}
	
	
	/**
	 */
	export async function calendar_add(
		calendar_object : _dali.type_calendar_object
	)
	: Promise<void>
	{
		const calendar_id : _dali.type_calendar_id = await _dali.backend.calendar_add(
			{
				"name": calendar_object.name,
				"hue": calendar_object.hue,
				"access": {
					"public": calendar_object.access.public,
					"default_level": _dali.access_level_encode(calendar_object.access.default_level),
					"attributed_group": (
						lib_plankton.map.dump(calendar_object.access.attributed_group)
						.map(
							(pair) => (
								{
									"group_id": pair.key,
									"level": _dali.access_level_encode(pair.value),
								}
							)
						)
					),
					"attributed_user": (
						lib_plankton.map.dump(calendar_object.access.attributed_user)
						.map(
							(pair) => (
								{
									"user_id": pair.key,
									"level": _dali.access_level_encode(pair.value),
								}
							)
						)
					),
				},
				/**
				 * @todo
				 */
				"resource": {
					"kind": "local",
					"data": {
						"events": [],
					}
				},
			}
		);
		const calendar_object_reduced : _dali.type_calendar_object_reduced = {
			"name": calendar_object.name,
			"hue": calendar_object.hue,
			"access_level": _dali.enum_access_level.admin,
		};
		_state.calendars.set(
			calendar_id,
			{
				"reduced": calendar_object_reduced,
				"complete": calendar_object,
			}
		);
	}
	
	
	/**
	 */
	export async function calendar_change(
		calendar_id : _dali.type_calendar_id,
		calendar_object : _dali.type_calendar_object
	)
	: Promise<void>
	{
		/*await */ _dali.backend.calendar_change(
			calendar_id,
			{
				"name": calendar_object.name,
				"hue": calendar_object.hue,
				"access": {
					"public": calendar_object.access.public,
					"default_level": _dali.access_level_encode(calendar_object.access.default_level),
					"attributed_group": (
						lib_plankton.map.dump(calendar_object.access.attributed_group)
						.map(
							(pair) => ({
								"group_id": pair.key,
								"level": _dali.access_level_encode(pair.value),
							})
						)
					),
					"attributed_user": (
						lib_plankton.map.dump(calendar_object.access.attributed_user)
						.map(
							(pair) => ({
								"user_id": pair.key,
								"level": _dali.access_level_encode(pair.value),
							})
						)
					),
				},
			}
		);
		const calendar_object_reduced : _dali.type_calendar_object_reduced = {
			"name": calendar_object.name,
			"hue": calendar_object.hue,
			/**
			 * @todo
			 */
			"access_level": _dali.enum_access_level.admin,
		};
		_state.calendars.set(
			calendar_id,
			{
				"reduced": calendar_object_reduced,
				"complete": calendar_object,
			}
		);
		{
			lib_plankton.map.clear(_state.events);
			lib_plankton.set.clear(_state.covered_dates);
			notify_reset();
		}
	}
	
	
	/**
	 */
	export async function calendar_remove(
		calendar_id : _dali.type_calendar_id
	)
	: Promise<void>
	{
		/*await */ _dali.backend.calendar_remove(calendar_id);
		_state.calendars.delete(calendar_id);
		{
			lib_plankton.map.clear(_state.events);
			lib_plankton.set.clear(_state.covered_dates);
			notify_reset();
		}
	}
	
	
	/**
	 * @todo do NOT export?
	 * @todo clear?
	 * @todo heed calendar_ids
	 * @todo mutex
	 * @todo only update outside timeframe
	 */
	export async function sync_events(
		timeframe : {
			from : lib_plankton.pit.type_pit;
			to : lib_plankton.pit.type_pit;
		},
		{
			"calendar_ids": calendar_ids = null,
		}
		:
		{
			calendar_ids ?: (null | Array<_dali.type_calendar_id>);
		}
		=
		{
		}
	)
	: Promise<void>
	{
		const queried_dates : lib_plankton.set.type_set<lib_plankton.pit.type_date> = timeframe_to_date_set(
			timeframe
		);
		const new_dates : lib_plankton.set.type_set<lib_plankton.pit.type_date> = lib_plankton.set.difference(
			() => make_date_set(),
			queried_dates,
			_state.covered_dates
		);
		if (lib_plankton.set.empty(new_dates))
		{
			// do nothing
		}
		else
		{
			const result = await _dali.backend.events(
				timeframe.from,
				timeframe.to,
				{
					"calendar_ids": calendar_ids,
				}
			);
			result.forEach(
				entry => {
					_state.events.set(
						entry.hash,
						{
							"key": entry.hash,
							"calendar_id": entry.calendar_id,
							"calendar_name": entry.calendar_name,
							"hue": entry.hue,
							"access_level": _dali.access_level_decode(entry.access_level),
							"event_id": entry.event_id,
							"event_object": entry.event_object
						}
					);
				}
			);
			_state.covered_dates = lib_plankton.set.union(
				() => make_date_set(),
				_state.covered_dates,
				new_dates
			);
		}
	}
	
	
	/**
	 */
	export function event_list(
	)
	: Promise<Array<_dali.type_event_object_extended>>
	{
		return Promise.resolve(lib_plankton.map.values(_state.events));
	}
	
	
	/**
	 */
	export async function event_get(
		event_key : _dali.type_event_key
	)
	: Promise<_dali.type_event_object_extended>
	{
		return Promise.resolve(_state.events.get(event_key));
	}
	
	
	/**
	 */
	export async function event_add(
		calendar_id : _dali.type_calendar_id,
		event_object : _dali.type_event_object
	)
	: Promise<void>
	{
		/**
		 * @todo do NOT wait?
		 */
		const result = await _dali.backend.calendar_event_add(
			calendar_id,
			event_object
		);
		const event_id : _dali.type_local_resource_event_id = result.local_resource_event_id;
		const event_key : _dali.type_event_key = result.hash;
		const value = _state.calendars.get(calendar_id);
		const calendar_object_reduced : _dali.type_calendar_object_reduced = value.reduced;
		_state.events.set(
			event_key,
			{
				"key": event_key,
				"calendar_id": calendar_id,
				"calendar_name": calendar_object_reduced.name,
				"hue": calendar_object_reduced.hue,
				"access_level": calendar_object_reduced.access_level,
				"event_id": event_id,
				"event_object": event_object,
			}
		);
	}
	
	
	/**
	 */
	export async function event_change(
		event_key : _dali.type_event_key,
		event_object : _dali.type_event_object
	)
	: Promise<void>
	{
		const event_object_extended_old : _dali.type_event_object_extended = _state.events.get(event_key);
		const event_object_extended_new : _dali.type_event_object_extended = {
			"key": event_object_extended_old.key,
			"calendar_id": event_object_extended_old.calendar_id,
			"calendar_name": event_object_extended_old.calendar_name,
			"hue": event_object_extended_old.hue,
			"access_level": event_object_extended_old.access_level,
			"event_id": event_object_extended_old.event_id,
			"event_object": event_object,
		};
		_state.events.set(
			event_key,
			event_object_extended_new
		);
		/*await */_dali.backend.calendar_event_change(
			event_object_extended_old.calendar_id,
			event_object_extended_old.event_id,
			event_object
		);
	}
	
	
	/**
	 */
	export async function event_remove(
		event_key : _dali.type_event_key
	)
	: Promise<void>
	{
		const event_object_extended : _dali.type_event_object_extended = _state.events.get(event_key);
		_state.events.delete(event_key);
		/*await */_dali.backend.calendar_event_remove(
			event_object_extended.calendar_id,
			event_object_extended.event_id
		);
	}
	
	
	/**
	 */
	export function listen_reset(
		action : ((priviliged ?: boolean) => Promise<void>)
	)
	: void
	{
		_listeners_reset.push(action);
	}
	
	
	/**
	 */
	async function notify_reset(
		priviliged ?: boolean
	)
	: Promise<void>
	{
		for (const action of _listeners_reset)
		{
			await action(priviliged);
		}
	}
	
	
	/**
	 */
	export async function initialize(
	)
	: Promise<void>
	{
		_state = {
			"groups": [],
			"users": [],
			"settings": {
				"use_fallback_for_date_and_time_inputs": false,
				"weekview_vertical_default": false,
			},
			"calendars": lib_plankton.map.hashmap.implementation_map(
				lib_plankton.map.hashmap.make(
					calendar_id => calendar_id.toFixed(0)
				)
			),
			"events": lib_plankton.map.hashmap.implementation_map(
				lib_plankton.map.hashmap.make(
					event_key => event_key
				)
			),
			"covered_dates": make_date_set(
			),
		};
		
		_dali.listen_login(
			async () => {
				_state.groups = (
					(await _dali.backend.group_list())
					.map(
						entry => (
							{
								"id": entry.id,
								"object": {
									"name": entry.name,
									"label": entry.label,
								}
							}
						)
					)
				);
				_state.users = await _dali.backend.user_list();
				_state.settings = (await _dali.backend.user_get()).settings;
				await sync_calendars();
				lib_plankton.map.clear(_state.events);
				lib_plankton.set.clear(_state.covered_dates);
				notify_reset(true);
			}
		);
		_dali.listen_logout(
			async () => {
				_state.groups = [];
				_state.users = [];
				await sync_calendars();
				lib_plankton.map.clear(_state.events);
				lib_plankton.set.clear(_state.covered_dates);
				notify_reset(false);
			}
		);
		
		await sync_calendars();
		// await sync_events();
	}
	
}
