var collectionHelper = function() {

	var monitortimer = null;
	var monitorduration = 1000;
	var returned_data = new Array();
	var return_callback = null;
	var update_timer = null;
	var notify = false;

	async function scanFiles(cmd) {
		player.updatingcollection = true;
		collectionHelper.disableCollectionUpdates();
		collectionHelper.prepareForLiftOff(language.gettext("label_updating"));
		collectionHelper.markWaitFileList(language.gettext("label_updating"));
		uiHelper.prepareCollectionUpdate();
		debug.mark("PLAYER","Scanning Files With",cmd,"on",prefs.player_backend);
		await player.controller.do_command_list([[cmd]]);
		while (player.status.updating_db) {
			debug.debug('GENERAL','Still updating collection');
			await new Promise(t => setTimeout(t, 1000));
			if (prefs.player_backend == 'mpd') {
				await player.controller.do_command_list([]);
			}
		}
		debug.info('GENERAL','Player rescan is complete');
		loadFileBrowser();
		// Collection updates are done completely asynchronously. All this does is start it off, it does not wait for
		// it to complete, since browsers retry automatically after some time (2 minutes seems to be the norm)
		// and there's no way to stop that.
		// We rely on the update monitor to keep polling the server until it gets 'RompR Is Done'
		// at which point it will load the collection.
		debug.mark('GENERAL', 'Initiating Collection Rebuild');
		try {
			var response = await fetch(
				'api/collection/?rebuild=yes',
				{
					signal: AbortSignal.timeout(prefs.collection_load_timeout),
					cache: 'no-store',
					priority: 'low'
				}
			);
			if (response.ok) {
				debug.info('GENERAL','Collection Rebuild has Started. Polling From Here.');
			} else {
				var t = await response.text();
				var msg = t ? t : response.statusText;
				throw new Error(language.gettext('error_collectionupdate')+'<br />'+msg);
			}
		} catch (err) {
			debug.error('GENERAL','Collection Rebuild Did Not Work!',err);
			infobar.error(err.message);
			doneUpdate();
			return false;
		}
		var data = {current: 'Preparing'};
		while (data.current != 'RompR Is Done') {
			try {
				var response = await fetch(
					'utils/checkupdateprogress.php',
					{ cache: 'no-store' }
				);
				if (response.ok) {
					var data = await response.json();
					debug.debug("UPDATE",data);
					if (data.current != 'RomoR Is Done')
						$('#updatemonitor').html(data.current);
				} else {
					throw new Error(response.statusText);
				}
			} catch (err) {
				debug.warn('UPDATE', 'Failed to get update stats', err);
			}
			await new Promise(t => setTimeout(t, monitorduration));
		}
		debug.info('GENERAL', 'Collection Update Finished');
		infobar.notify(language.gettext('label_updatedone'));
		doneUpdate();
	}

	function doneUpdate() {
		infobar.removenotify(notify);
		loadCollection();
		collectionHelper.enableCollectionUpdates();
		player.updatingcollection = false;
	}

	async function loadCollection() {
		if (!prefs.hide_albumlist) {
			var albums = 'api/collection/?item='+collectionHelper.collectionKey('a');
			debug.info("GENERAL","Loading Collection from URL",albums);
			try {
				var response = await fetch(
					albums,
					{
						signal: AbortSignal.timeout(prefs.collection_load_timeout),
						cache: 'no-store',
						priority: 'high'
					}
				);
				var data = await response.text();
				if (response.ok) {
					debug.log('GENERAL','Collection Loaded');
					$("#collection").html(data);
					if ($('#emptycollection').length > 0) {
						$('#collectionbuttons').show();
						prefs.save({ collectionconbuttons_isopen: true });
						$('[name="donkeykong"]').makeFlasher({flashtime: 0.5, repeats: 3});
					}
					data = null;
					$("#collection").doThingsAfterDisplayingListOfAlbums().scootTheAlbums();
					loadAudiobooks();
				} else {
					var msg = data ? data : response.statusText;
					throw new Error(msg);
				}
			} catch (err) {
				collectionHelper.disableCollectionUpdates();
				var html = '<p align="center"><b><font color="red">'+language.gettext('label_update_error')+'</font></b></p>';
				html += '<p align="center">'+err.message+'</p>';
				html += '<p align="center"><a href="https://fatg3erman.github.io/RompR/Troubleshooting#very-large-collections" target="_blank">Read The Troubleshooting Docs</a></p>';
				$("#collection").html(html);
				debug.error("PLAYER","Failed to generate collection",err);
			}
		} else {
			loadAudiobooks();
		}
	}

	function check_init_tasks() {
		if (!player.collectionLoaded) {
			player.collectionLoaded = true;
			startBackgroundInitTasks.doNextTask();
		}
	}

	function loadAudiobooks() {
		if (!prefs.hide_audiobooklist) {
			fetch(
				'api/collection/?item='+collectionHelper.collectionKey('z'),
				{
					signal: AbortSignal.timeout(prefs.collection_load_timeout),
					cache: 'no-store',
					priority: 'high'
				}
			)
			.then(response => response.text())
			.then(data => {
				$("#audiobooks").html(data)
				$("#audiobooks").doThingsAfterDisplayingListOfAlbums().scootTheAlbums();
				uiHelper.doCollectionStripyStuff();
				check_init_tasks();
			})
			.catch(err => { debug.warn('INIT', 'Could not load audiobooks', err) });
		} else {
			check_init_tasks();
		}
	}

	function loadFileBrowser() {
		if (prefs.hide_filelist) {
			return false;
		}
		var files = 'api/dirbrowser/';
		debug.info("GENERAL","Loading File Browser from URL",files);
		$("#filecollection").load(files);
	}

	function updateUIElements() {

		if (dbQueue.queuelength() > 0) {
			debug.info("UI","Deferring updates due to outstanding requests");
			clearTimeout(update_timer);
			update_timer = setTimeout(updateUIElements, 500);
			return;
		}

		returned_data.forEach(function(rdata, index) {

			if (rdata && rdata.hasOwnProperty('deletedalbums')) {
				$.each(rdata.deletedalbums, function(i, v) {
					uiHelper.removeFromCollection(v);
				});
			}

			if (rdata && rdata.hasOwnProperty('deletedartists')) {
				$.each(rdata.deletedartists, function(i, v) {
					uiHelper.removeFromCollection(v);
				});
			}

			if (rdata && rdata.hasOwnProperty('modifiedalbums')) {
				$('#emptycollection').remove();
				$.each(rdata.modifiedalbums, function(i,v) {
					// We remove and replace any modified albums, as they may have a new date or albumartist which would cause
					// them to appear elsewhere in the collection. First remove the dropdown if it exists and replace its contents
					uiHelper.insertAlbum(v);
				});
			}

			if (rdata && rdata.hasOwnProperty('modifiedartists')) {
				$('#emptycollection').remove();
				$.each(rdata.modifiedartists, function(i,v) {
					try {
						// The only thing to do with artists is to add them in if they don't exist
						// NOTE. Do this AFTER inserting new albums, because if we're doing albumbyartist with banners showing
						// then the insertAfter logic will be wrong if we've already inserted the artist banner.
						if ($('.openmenu[name="'+v.id+'"]').length == 0 && $('#'+v.id).length == 0) {
							uiHelper.insertArtist(v);
						}
					} catch (err) {

					}
				});
			}

			if (rdata && rdata.hasOwnProperty('addedtracks') && rdata.addedtracks.length > 0) {
				$.each(rdata.addedtracks, function(i, v) {
					if (v.albumindex !== null && v.trackuri != '' && prefs.chooser != 'searcher') {
						// (Ignore if it went into the wishlist) Also don't do it if we're looking
						// at the search pane as it's just annoying to tag tracks from there and have it keep
						// switching to the collection
						debug.log("INSERTED",v);
						layoutProcessor.displayCollectionInsert(v);
					}
				});
			} else {
				infobar.markCurrentTrack();
			}

			if (rdata && rdata.hasOwnProperty('deletedwishlist') && rdata.deletedwishlist.length > 0) {
				$.each(rdata.deletedwishlist, function(i, v) {
					if ($('#wtrack_'+v.toString()).next().hasClass('wishlist_results')) {
						$('#wtrack_'+v.toString()).next().remove();
					}
					$('#wtrack_'+v.toString()).remove();
				});
			}

			// If we had an insertAtStart for collection, it'll be inserted before the stats display
			// so just pop that out and back in again.
			$('#fothergill').detach().prependTo($('#collection'));
			$('#mingus').detach().prependTo($('#audiobooks'));

			if (rdata && rdata.hasOwnProperty('stats')) {
				// stats is another html fragment which is the contents of the
				// statistics box at the top of the collection
				$("#fothergill").html($(rdata.stats).children());
			}

			if (rdata && rdata.hasOwnProperty('bookstats')) {
				// stats is another html fragment which is the contents of the
				// statistics box at the top of the collection
				$("#mingus").html($(rdata.bookstats).children());
			}

			returned_data[index] = null;

		});

		if (return_callback) {
			return_callback();
			return_callback = null;
		}

		uiHelper.doCollectionStripyStuff();

	}

	return {

		rejigDoodahs: function(panel, visible) {
			if (visible) {
				switch (panel) {
					case 'albumlist':
						loadCollection();
						break;

					case 'filelist':
						loadFileBrowser();
						break;

					case 'audiobooklist':
						loadAudiobooks();
						break;
				}
			}
		},

		doUpdateCollection: function() {
			collectionHelper.checkCollection(true, false);
		},

		doRescanCollection: function() {
			collectionHelper.checkCollection(true, true);
		},

		disableCollectionUpdates: function() {
			$('button[name="donkeykong"]').off('click').css('opacity', '0.2');
			$('button[name="dinkeyking"]').off('click').css('opacity', '0.2');
		},

		enableCollectionUpdates: function() {
			$('button[name="donkeykong"]').off('click').on('click', collectionHelper.doUpdateCollection).css('opacity', '');
			$('button[name="dinkeyking"]').off('click').on('click', collectionHelper.doRescanCollection).css('opacity', '');
		},

		forceCollectionReload: function() {
			debug.info("COLLECTION", "Forcing Collection reload");
			collection_status = 0;
			collectionHelper.displayCollection();
			if (notify) {
				infobar.removenotify(notify);
			}
		},

		prepareForLiftOff: function(text) {
			notify = infobar.permnotify(text);
			$("#collection").clearOut().empty().doSomethingUseful(text);
			$("#audiobooks").clearOut().empty();
			var x = $('<div>',{ id: 'updatemonitor', class: 'tiny', style: 'padding-left:1em;margin-right:1em'}).insertAfter($('#spinner_collection'));
		},

		markWaitFileList: function(text) {
			$("#filecollection").empty().doSomethingUseful(text);
		},

		collectionKey: function(w) {
			switch (w) {
				case 'b':
					return w+prefs.actuallysortresultsby+'root';
					break;
				default:
					return w+prefs.sortcollectionby+'root';
					break;
			}
		},

		checkCollection: function(update, rescan) {
			debug.mark("COLLECTION", "checking collection. collection_status is",collection_status);
			if (update && player.updatingcollection) {
				infobar.error(language.gettext('error_nocol'));
				return;
			}
			if (prefs.updateeverytime && prefs.player_backend == prefs.collection_player) {
				debug.info("COLLECTION","Updating Collection due to preference");
				update = true;
			} else {
				if (!prefs.hide_albumlist && collection_status == 1 && prefs.player_backend == prefs.collection_player) {
					debug.info("COLLECTION","Updating Collection because it is out of date");
					collection_status = 0;
					update = true;
				}
			}
			if (update) {
				debug.mark('GENERAL','We are going to update the collection');
				$("#searchresultholder").clearOut().empty();
				scanFiles(rescan ? 'rescan' : 'update');
			} else {
				collectionHelper.displayCollection();
			}
		},

		displayCollection: function() {
			loadCollection();
			loadFileBrowser();
		},

		updateCollectionDisplay: function(rdata, callback) {
			// rdata contains HTML fragments to insert into the collection
			// Otherwise we would have to reload the entire collection panel every time,
			// which would cause any opened dropdowns to be mysteriously closed,
			// which would just look shit.
			debug.debug("COLLECTION","Update Display",structuredClone(rdata));
			if (rdata) {
				clearTimeout(update_timer);
				returned_data.push(rdata);
				return_callback = callback;
				update_timer = setTimeout(updateUIElements, 500);
			}
		}
	}
}();
