const HASH_EXTRACTOR = /^\#\/(?<view>\w+)(?:\/channel\/(?<channel_id>[A-Za-z\-]+)\/?)?/gm;
const UPDATE_CHANNELS_FREQUENCY = 60_000;

const APP = {
	user: undefined,
	user_servers: [],
	user_watches: [],

	_event_callbacks: {},

	on: function( event_name, callback ) {
		this._event_callbacks[ event_name ] = this._event_callbacks[ event_name ] ?? new Set();
		this._event_callbacks[event_name ].add( callback );

		if ( event_name === 'load' && this._loaded ) {
			setTimeout( callback, 0 );
		}

		return true;
	},

	off: function( event_name, callback ) {
		return this._event_callbacks[ event_name ]?.delete( callback );
	},

	_emit: function( event_name, event_data ) {
		const event_callbacks = this._event_callbacks[ event_name ];
		event_callbacks?.forEach( ( callback ) => {
			callback( event_data );
		});
	},

	check_if_logged_in: async function () {
		try {
			const session_response = await api.fetch("/api/users/me");

			if (!session_response.ok) {
				const error_body = await session_response.json();
				const error = error_body?.error;

				console.dir({
					error_body,
					error,
				});
				return;
			}

			const user = await session_response.json();
			this.login( user );
		} catch (error) {
			console.dir({
				error,
			});
		}
	},

	extract_url_hash_info: async function () {
		HASH_EXTRACTOR.lastIndex = 0; // ugh, need this to have this work on multiple exec calls
		const {
			groups: { view, channel_id },
		} = HASH_EXTRACTOR.exec(window.location.hash ?? "") ?? {
			groups: {},
		};

		if (!document.body.dataset.view || document.body.dataset.view !== view) {
			const previous = typeof document.body.dataset.view === 'string' ? document.body.dataset.view : undefined;
			if ( view ) {
				document.body.dataset.view = view;
			}
			else {
				delete document.body.dataset.view;
			}

			this._emit( 'view_changed', {
				previous,
				view,
				channel_id
			});
		}

		if (!document.body.dataset.channel || document.body.dataset.channel !== channel_id) {
			const previous = typeof document.body.dataset.channel === 'string' ? document.body.dataset.channel : undefined;
			if ( channel_id ) {
				document.body.dataset.channel = channel_id;
			}
			else {
				delete document.body.dataset.channel;
			}

			this._emit( 'channel_changed', {
				previous,
				channel_id
			});
		}
	},

	load: async function() {
		this._loaded = false;
		this.server = {
			name: document.title,
			url: window.location.origin ?? window.location.href,
			icon: '/icons/favicon-128x128.png',
			icon_background: undefined
		};

		this.suggested_servers = [];
		try {
			const server_info_response = await api.fetch( '/files/settings/settings.json' );
			if ( server_info_response.ok ) {
				const this_server_info = await server_info_response.json();

				this.server = {
					name: this_server_info.name ?? this.server.name,
					url: this_server_info.url ?? this.server.url,
					icon: this_server_info.icon ?? this.server.icon,
					icon_background: this_server_info.icon_background ?? this.server.icon_background
				};
			}

			const suggested_servers_response = await api.fetch( '/files/settings/suggested_servers.json' );
			if ( suggested_servers_response.ok ) {
				this.suggested_servers = await suggested_servers_response.json();
			}
		}
		catch( error ) {
			console.error( error );
		}

		window.addEventListener("locationchange", this.extract_url_hash_info.bind( this ));
		window.addEventListener("locationchange", this.CHANNELS.update );

		this.check_if_logged_in();
		this.extract_url_hash_info();
		this._emit( 'load', this );
		this._loaded = true;
	},

	update_user: async function( user ) {
		this.user = user;

		document.body.dataset.user = JSON.stringify(user);
		document.body.dataset.perms = user.permissions.join(":");

		this.CHANNELS.update();

		this.user_servers = [];
		try {
			const user_server_response = await api.fetch( `/files/users/${ user.id }/settings/servers.json` );
			this.user_servers = user_server_response.ok ? await user_server_response.json() : [];
		}
		catch( error ) {
			console.error( error );
		}

		this.user_watches = [];
		try {
			const user_watches_response = await api.fetch( `/api/users/${ user.id }/watches` );
			this.user_watches = user_watches_response.ok ? await user_watches_response.json() : [];
		}
		catch( error ) {
			console.error( error );
		}

		// TODO: show unread indicators based on watches
	},

	login: async function( user ) {
		await this.update_user( user );
		this._emit( 'user_logged_in', { user } );
	},

	logout: function() {
		delete document.body.dataset.user;
		delete document.body.dataset.perms;
		window.location = "/";

		this._emit( "user_logged_out", {});
	},

	USERS: {
		_evict_timeouts: {},
		_update_timeouts: {},
		get: async (id, force) => {
			if (force || !APP.USERS[id]) {
				APP.USERS[id] = await (await api.fetch(`/api/users/${id}`)).json();
			}

			if (!APP.USERS._update_timeouts[id]) {
				APP.USERS._update_timeouts[id] = setInterval(() => {
					APP.USERS.get(id, true);
				}, 1 * 60_000);
			}

			if (!force) {
				if (APP.USERS._evict_timeouts[id]) {
					clearTimeout(APP.USERS._evict_timeouts[id]);
				}

				APP.USERS._evict_timeouts[id] = setTimeout(() => {
					if (APP.USERS._update_timeouts[id]) {
						clearTimeout(APP.USERS._update_timeouts[id]);
						delete APP.USERS._update_timeouts[id];
					}

					delete APP.USERS[id];
				}, 10 * 60_000);
			}

			return APP.USERS[id];
		},
	},

	CHANNELS: {
		_last_channel_update: undefined,
		_update_channels_timeout: undefined,
		CHANNEL_LIST: [],

		update: async ( force = false ) => {
			const now = new Date();
			const time_since_last_update = now - (APP.CHANNELS._last_channel_update ?? 0);
			const sufficient_time_has_passed_since_last_update = time_since_last_update > UPDATE_CHANNELS_FREQUENCY / 2;
			if ( !force && !sufficient_time_has_passed_since_last_update ) {
				return;
			}

			if (APP.CHANNELS._update_channels_timeout) {
				clearTimeout(APP.CHANNELS._update_channels_timeout);
				APP.CHANNELS._update_channels_timeout = undefined;
			}

			try {
				const channels_response = await api.fetch("/api/channels");
				if (channels_response.ok) {
					const new_channels = await channels_response.json();
					APP.CHANNELS.CHANNEL_LIST = [...new_channels];

					APP._emit( 'channels_updated', {
						channels: APP.CHANNELS.CHANNEL_LIST
					});

					APP.CHANNELS._last_channel_update = now;
				}
			} catch (error) {
				console.error(error);
			}

			APP.CHANNELS._update_channels_timeout = setTimeout(
				APP.CHANNELS.update,
				UPDATE_CHANNELS_FREQUENCY,
			);

			// now that we have channels, make sure our url is all good
			APP.extract_url_hash_info();
		},
	},
};

document.addEventListener("DOMContentLoaded", APP.load.bind( APP ));
APP.on( 'view_changed', ( { view } ) => {
	if ( !view ) {
		window.location.hash = '/chat';
	}
});
