(function () {
	function addCSRFToken(xhr) {
		const token = document
			.querySelector('meta[name="csrf-token"]')
			?.getAttribute("content");
		xhr.setRequestHeader("X-CSRF-Token", token);
	}

	var hangersInitCallbacks = [];

	function onHangersInit(callback) {
		hangersInitCallbacks[hangersInitCallbacks.length] = callback;
	}

	function hangersInit() {
		for (var i = 0; i < hangersInitCallbacks.length; i++) {
			try {
				hangersInitCallbacks[i]();
			} catch (error) {
				console.error(error);
			}
		}
	}

	/*

    Hanger groups

  */

	var hangerGroups = [];

	$(".closet-hangers-group").each(function () {
		var el = $(this);
		var lists = [];

		el.find("div.closet-list").each(function () {
			var el = $(this);
			var id = el.attr("data-id");
			if (id) {
				lists[lists.length] = {
					id: parseInt(id, 10),
					label: el.find("h4").text(),
				};
			}
		});

		hangerGroups[hangerGroups.length] = {
			label: el.find("h3").text(),
			lists: lists,
			owned: el.attr("data-owned") == "true",
		};
	});

	$(".closet-hangers-group span.toggle").live("click", function () {
		$(this).closest(".closet-hangers-group").toggleClass("hidden");
	});

	var hangersElQuery = "#closet-hangers";
	var hangersEl = $(hangersElQuery);

	/*

    Compare with Your Items

  */

	$("#toggle-compare").click(function () {
		hangersEl.toggleClass("comparing");
	});

	// Read the item IDs of trade matches from the meta tags.
	const ownedIds =
		document
			.querySelector("meta[name=trade-matches-owns]")
			?.getAttribute("value")
			?.split(",") ?? [];
	const wantedIds =
		document
			.querySelector("meta[name=trade-matches-wants]")
			?.getAttribute("value")
			?.split(",") ?? [];

	// Apply the `user-owns` and `user-wants` classes to the relevant entries.
	// This both provides immediate visual feedback, and sets up "Compare with
	// Your Items" to toggle to just them!
	//
	// NOTE: The motivation here is caching: this allows us to share a cache of
	// the closet list contents across all users, without `user-owns` or
	// `user-wants` classes for one specific user getting cached and reused.
	const hangerEls = document.querySelectorAll("#closet-hangers .object");
	for (const hangerEl of hangerEls) {
		const itemId = hangerEl.getAttribute("data-item-id");
		if (ownedIds.includes(itemId)) {
			hangerEl.classList.add("user-owns");
		}
		if (wantedIds.includes(itemId)) {
			hangerEl.classList.add("user-wants");
		}
	}

	/*

    Hanger forms

  */

	var body = $(document.body).addClass("js");
	if (!body.hasClass("current-user")) return false;

	// When we get hangers HTML, add the controls. We do this in JS rather than
	// in the HTML for caching, since otherwise the requests can take forever.
	// If there were another way to add hangers, then we'd have to worry about
	// that, but, right now, the only way to create a new hanger from this page
	// is through the autocompleter, which reinitializes anyway. Geez, this thing
	// is begging for a rewrite, but today we're here for performance.
	$("#closet-hanger-update-tmpl").template("updateFormTmpl");
	$("#closet-hanger-destroy-tmpl").template("destroyFormTmpl");
	onHangersInit(function () {
		// Super-lame hack to get the user ID from where it already is :/
		var currentUserId = itemsSearchForm.data("current-user-id");
		$("#closet-hangers .closet-hangers-group").each(function () {
			var groupEl = $(this);
			var owned = groupEl.data("owned");

			groupEl.find("div.closet-list").each(function () {
				var listEl = $(this);
				var listId = listEl.data("id");

				listEl.find("div.object").each(function () {
					var hangerEl = $(this);
					var hangerId = hangerEl.data("id");
					var quantityEl = hangerEl.find("div.quantity");
					var quantity = hangerEl.data("quantity");

					// Ooh, this part is weird. We only want the name to be linked, so
					// lift everything else out.
					var checkboxId = "hanger-selected-" + hangerId;
					var label = $("<label />", { for: checkboxId });
					var link = hangerEl.children("a");
					link.children(":not(.name)").detach().appendTo(label);
					link.detach().appendTo(label);
					var checkbox = $("<input />", {
						type: "checkbox",
						id: checkboxId,
					}).appendTo(hangerEl);
					label.appendTo(hangerEl);

					// I don't usually like to _blank things, but it's too easy to click
					// the text when you didn't mean to and lose your selection work.
					link.attr("target", "_blank");

					$.tmpl("updateFormTmpl", {
						user_id: currentUserId,
						closet_hanger_id: hangerId,
						quantity: quantity,
						list_id: listId,
						owned: owned,
					}).appendTo(quantityEl);

					$.tmpl("destroyFormTmpl", {
						user_id: currentUserId,
						closet_hanger_id: hangerId,
					}).appendTo(hangerEl);
				});
			});
		});
	});

	$.fn.liveDraggable = function (opts) {
		this.live("mouseover", function () {
			if (!$(this).data("init")) {
				$(this).data("init", true).draggable(opts);
			}
		});
	};

	$.fn.disableForms = function () {
		return this.data("formsDisabled", true)
			.find("input")
			.attr("disabled", "disabled")
			.end();
	};

	$.fn.enableForms = function () {
		return this.data("formsDisabled", false)
			.find("input")
			.removeAttr("disabled")
			.end();
	};

	$.fn.hasChanged = function () {
		return this.attr("data-previous-value") != this.val();
	};

	$.fn.revertValue = function () {
		return this.each(function () {
			var el = $(this);
			el.val(el.attr("data-previous-value"));
		});
	};

	$.fn.storeValue = function () {
		return this.each(function () {
			var el = $(this);
			el.attr("data-previous-value", el.val());
		});
	};

	$.fn.insertIntoSortedList = function (list, compare) {
		var newChild = this,
			inserted = false;
		list.children().each(function () {
			if (compare(newChild, $(this)) < 1) {
				newChild.insertBefore(this);
				inserted = true;
				return false;
			}
		});
		if (!inserted) newChild.appendTo(list);
		return this;
	};

	function handleSaveError(xhr, action) {
		try {
			var data = $.parseJSON(xhr.responseText);
		} catch (e) {
			var data = {};
		}

		if (typeof data.errors != "undefined") {
			$.jGrowl("Error " + action + ": " + data.errors.join(", "));
		} else {
			$.jGrowl("We had trouble " + action + " just now. Try again?");
		}
	}

	function objectRemoved(objectWrapper) {
		objectWrapper.hide(250, function () {
			objectWrapper.remove();
			updateBulkActions();
		});
	}

	function compareItemsByName(a, b) {
		return a.find("span.name").text().localeCompare(b.find("span.name").text());
	}

	function findList(owned, id, item) {
		if (id) {
			return $("#closet-list-" + id);
		} else {
			return $(
				".closet-hangers-group[data-owned=" +
					owned +
					"] div.closet-list.unlisted",
			);
		}
	}

	function updateListHangersCount(el) {
		el.attr("data-hangers-count", el.find("div.object").length);
	}

	function moveItemToList(item, owned, listId) {
		var newList = findList(owned, listId, item);
		var oldList = item.closest("div.closet-list");
		var hangersWrapper = newList.find("div.closet-list-hangers");
		item.insertIntoSortedList(hangersWrapper, compareItemsByName);
		updateListHangersCount(oldList);
		updateListHangersCount(newList);
	}

	function submitUpdateForm(form) {
		if (form.data("loading")) return false;
		var quantityEl = form.children("input[name=closet_hanger[quantity]]");
		var ownedEl = form.children("input[name=closet_hanger[owned]]");
		var listEl = form.children("input[name=closet_hanger[list_id]]");
		var listChanged = ownedEl.hasChanged() || listEl.hasChanged();
		if (listChanged || quantityEl.hasChanged()) {
			var objectWrapper = form.closest(".object").addClass("loading");
			var newQuantity = quantityEl.val();
			var quantitySpan = objectWrapper.find(".quantity span").text(newQuantity);
			objectWrapper.attr("data-quantity", newQuantity);
			var data = form.serialize(); // get data before disabling inputs
			objectWrapper.disableForms();
			form.data("loading", true);
			if (listChanged)
				moveItemToList(objectWrapper, ownedEl.val(), listEl.val());
			$.ajax({
				url: form.attr("action") + ".json",
				type: "post",
				data: data,
				dataType: "json",
				beforeSend: addCSRFToken,
				complete: function (data) {
					if (quantityEl.val() == 0) {
						objectRemoved(objectWrapper);
					} else {
						objectWrapper.removeClass("loading").enableForms();
					}
					form.data("loading", false);
				},
				success: function () {
					// Now that the move was successful, let's merge it with any
					// conflicting hangers
					var id = objectWrapper.attr("data-item-id");
					var conflictingHanger = findList(
						ownedEl.val(),
						listEl.val(),
						objectWrapper,
					)
						.find("div[data-item-id=" + id + "]")
						.not(objectWrapper);
					if (conflictingHanger.length) {
						var conflictingQuantity = parseInt(
							conflictingHanger.attr("data-quantity"),
							10,
						);

						var currentQuantity = parseInt(newQuantity, 10);

						var mergedQuantity = conflictingQuantity + currentQuantity;

						quantitySpan.text(mergedQuantity);
						quantityEl.val(mergedQuantity);
						objectWrapper.attr("data-quantity", mergedQuantity);

						conflictingHanger.remove();
					}

					quantityEl.storeValue();
					ownedEl.storeValue();
					listEl.storeValue();

					updateBulkActions();
				},
				error: function (xhr) {
					quantityEl.revertValue();
					ownedEl.revertValue();
					listEl.revertValue();
					if (listChanged)
						moveItemToList(objectWrapper, ownedEl.val(), listEl.val());
					quantitySpan.text(quantityEl.val());

					handleSaveError(xhr, "updating the quantity");
				},
			});
		}
	}

	$(hangersElQuery + " form.closet-hanger-update").live("submit", function (e) {
		e.preventDefault();
		submitUpdateForm($(this));
	});

	function editableInputs() {
		return $(hangersElQuery).find(
			"input[name=closet_hanger[quantity]], " +
				"input[name=closet_hanger[owned]], " +
				"input[name=closet_hanger[list_id]]",
		);
	}

	$(hangersElQuery + "input[name=closet_hanger[quantity]]")
		.live("change", function () {
			submitUpdateForm($(this).parent());
		})
		.storeValue();

	onHangersInit(function () {
		editableInputs().storeValue();
	});

	$(hangersElQuery + " div.object")
		.live("mouseleave", function () {
			submitUpdateForm($(this).find("form.closet-hanger-update"));
		})
		.liveDraggable({
			appendTo: "#closet-hangers",
			distance: 20,
			helper: "clone",
			revert: "invalid",
		});

	$(hangersElQuery + " form.closet-hanger-destroy").live(
		"submit",
		function (e) {
			e.preventDefault();
			var form = $(this);
			var button = form.children("input[type=submit]").val("Removing…");
			var objectWrapper = form.closest(".object").addClass("loading");
			var data = form.serialize(); // get data before disabling inputs
			objectWrapper.addClass("loading").disableForms();
			$.ajax({
				url: form.attr("action") + ".json",
				type: "post",
				data: data,
				dataType: "json",
				beforeSend: addCSRFToken,
				complete: function () {
					button.val("Remove");
				},
				success: function () {
					objectRemoved(objectWrapper);
				},
				error: function () {
					objectWrapper.removeClass("loading").enableForms();
					$.jGrowl("Error removing item. Try again?");
				},
			});
		},
	);

	$(hangersElQuery + " .select-all").live("click", function (e) {
		var checkboxes = $(this)
			.closest(".closet-list")
			.find(".object input[type=checkbox]");

		var allChecked = true;
		checkboxes.each(function () {
			if (!this.checked) {
				allChecked = false;
				return false;
			}
		});

		checkboxes.attr("checked", !allChecked);

		updateBulkActions(); // setting the checked prop doesn't fire change events
	});

	function getCheckboxes() {
		return $(hangersElQuery + " input[type=checkbox]");
	}

	function getCheckedIds() {
		var checkedIds = [];
		getCheckboxes()
			.filter(":checked")
			.each(function () {
				if (this.checked) checkedIds.push(this.id);
			});
		return checkedIds;
	}

	getCheckboxes().live("change", updateBulkActions);

	function updateBulkActions() {
		var checkedCount = getCheckboxes().filter(":checked").length;
		$(".bulk-actions").attr("data-target-count", checkedCount);
		$(".bulk-actions-target-count").text(checkedCount);
	}

	$(".bulk-actions-move-all").bind("submit", function (e) {
		// TODO: DRY
		e.preventDefault();
		var form = $(this);
		var data = form.serializeArray();
		data.push({
			name: "return_to",
			value: window.location.pathname + window.location.search,
		});

		var checkedBoxes = getCheckboxes().filter(":checked");
		checkedBoxes.each(function () {
			data.push({
				name: "ids[]",
				value: $(this).closest(".object").attr("data-id"),
			});
		});

		$.ajax({
			url: form.attr("action"),
			type: form.attr("method"),
			data: data,
			beforeSend: addCSRFToken,
			success: function (html) {
				var doc = $(html);
				hangersEl.html(doc.find("#closet-hangers").html());
				hangersInit();
				updateBulkActions(); // don't want to maintain checked; deselect em all
				doc
					.find(".flash")
					.hide()
					.insertBefore(hangersEl)
					.show(500)
					.delay(5000)
					.hide(250);
				itemsSearchField.val("");
			},
			error: function (xhr) {
				handleSaveError(xhr, "moving these items");
			},
		});
	});

	$(".bulk-actions-remove-all").bind("submit", function (e) {
		e.preventDefault();
		var form = $(this);
		var hangerIds = [];
		var checkedBoxes = getCheckboxes().filter(":checked");
		var hangerEls = $();
		checkedBoxes.each(function () {
			hangerEls = hangerEls.add($(this).closest(".object"));
		});
		hangerEls.each(function () {
			hangerIds.push($(this).attr("data-id"));
		});
		$.ajax({
			url: form.attr("action") + ".json?" + $.param({ ids: hangerIds }),
			type: "delete",
			dataType: "json",
			beforeSend: addCSRFToken,
			success: function () {
				objectRemoved(hangerEls);
			},
			error: function () {
				$.jGrowl("Error removing items. Try again?");
			},
		});
	});

	$(".bulk-actions-deselect-all").bind("click", function (e) {
		getCheckboxes().filter(":checked").attr("checked", false);
		updateBulkActions();
	});

	function maintainCheckboxes(fn) {
		var checkedIds = getCheckedIds();

		fn();

		checkedIds.forEach(function (id) {
			document.getElementById(id).checked = true;
		});
		updateBulkActions();
	}

	/*

    Search, autocomplete

  */

	var itemsSearchForm = $("#closet-hangers-items-search[data-current-user-id]");
	var itemsSearchField = itemsSearchForm.children("input[name=q]");

	itemsSearchField.autocomplete({
		select: function (e, ui) {
			if (ui.item.is_item) {
				// Let the autocompleter finish up this search before starting a new one
				setTimeout(function () {
					itemsSearchField.autocomplete("search", ui.item);
				}, 0);
			} else {
				var item = ui.item.item;
				var group = ui.item.group;

				itemsSearchField.addClass("loading");

				var closetHanger = {
					owned: group.owned,
					list_id: ui.item.list ? ui.item.list.id : "",
				};

				if (!item.hasHanger) closetHanger.quantity = 1;

				$.ajax({
					url:
						"/user/" +
						itemsSearchForm.data("current-user-id") +
						"/items/" +
						item.id +
						"/closet_hangers",
					type: "post",
					data: {
						closet_hanger: closetHanger,
						return_to: window.location.pathname + window.location.search,
					},
					beforeSend: addCSRFToken,
					complete: function () {
						itemsSearchField.removeClass("loading");
					},
					success: function (html) {
						var doc = $(html);
						maintainCheckboxes(function () {
							hangersEl.html(doc.find("#closet-hangers").html());
							hangersInit();
						});
						doc
							.find(".flash")
							.hide()
							.insertBefore(hangersEl)
							.show(500)
							.delay(5000)
							.hide(250);
						itemsSearchField.val("");
					},
					error: function (xhr) {
						handleSaveError(xhr, "adding the item");
					},
				});
			}
		},
		source: function (input, callback) {
			if (typeof input.term == "string") {
				// user-typed query
				$.getJSON("/items.json?q=" + input.term, function (data) {
					var output = [];
					var items = data.items;
					for (var i in items) {
						items[i].label = items[i].name;
						items[i].is_item = true;
						output[output.length] = items[i];
					}
					callback(output);
				});
			} else {
				// item was chosen, now choose a group to insert
				var groupInserts = [],
					group;
				var item = input.term,
					itemEl,
					occupiedGroups,
					hasHanger;
				for (var i in hangerGroups) {
					group = hangerGroups[i];
					itemEl = $(
						".closet-hangers-group[data-owned=" +
							group.owned +
							"] div.object[data-item-id=" +
							item.id +
							"]",
					);
					occupiedGroups = itemEl.closest(".closet-list");
					hasHanger = occupiedGroups.filter(".unlisted").length > 0;

					groupInserts[groupInserts.length] = {
						group: group,
						item: item,
						label: item.label,
						hasHanger: hasHanger,
					};

					for (var i = 0; i < group.lists.length; i++) {
						hasHanger =
							occupiedGroups.filter("[data-id=" + group.lists[i].id + "]")
								.length > 0;
						groupInserts[groupInserts.length] = {
							group: group,
							item: item,
							label: item.label,
							list: group.lists[i],
							hasHanger: hasHanger,
						};
					}
				}
				callback(groupInserts);
			}
		},
	});

	var autocompleter = itemsSearchField.data("autocomplete");

	autocompleter._renderItem = function (ul, item) {
		var li = $("<li></li>").data("item.autocomplete", item);
		if (item.is_item) {
			// these are items from the server
			$("#autocomplete-item-tmpl").tmpl({ item_name: item.label }).appendTo(li);
		} else if (item.list) {
			// these are list inserts
			var listName = item.list.label;
			if (item.hasHanger) {
				$("#autocomplete-already-in-collection-tmpl")
					.tmpl({ collection_name: listName })
					.appendTo(li);
			} else {
				$("#autocomplete-add-to-list-tmpl")
					.tmpl({ list_name: listName })
					.appendTo(li);
			}
			li.addClass("closet-list-autocomplete-item");
		} else {
			// these are group inserts
			var groupName = item.group.label;
			if (!item.hasHanger) {
				$("#autocomplete-add-to-group-tmpl")
					.tmpl({ group_name: groupName.replace(/\s+$/, "") })
					.appendTo(li);
			} else {
				$("#autocomplete-already-in-collection-tmpl")
					.tmpl({ collection_name: groupName })
					.appendTo(li);
			}
			li.addClass("closet-hangers-group-autocomplete-item");
		}
		return li.appendTo(ul);
	};

	/*

    Contact Neopets username form

  */

	var contactEl = $("#closet-hangers-contact");
	var contactForm = contactEl.children("form");
	var contactField = contactForm.children("select");

	var contactAddOption = $("<option/>", {
		text: contactField.attr("data-new-text"),
		value: -1,
	});
	contactAddOption.appendTo(contactField);
	var currentUserId = $("meta[name=current-user-id]").attr("content");

	function submitContactForm() {
		var data = contactForm.serialize();
		contactForm.disableForms();
		$.ajax({
			url: contactForm.attr("action") + ".json",
			type: "post",
			data: data,
			dataType: "json",
			beforeSend: addCSRFToken,
			complete: function () {
				contactForm.enableForms();
			},
			error: function (xhr) {
				handleSaveError(xhr, "saving Neopets username");
			},
		});
	}

	contactField.change(function (e) {
		if (contactField.val() < 0) {
			var newUsername = $.trim(
				prompt(contactField.attr("data-new-prompt"), ""),
			);
			if (newUsername) {
				$.ajax({
					url: "/user/" + currentUserId + "/neopets-connections",
					type: "POST",
					data: { neopets_connection: { neopets_username: newUsername } },
					dataType: "json",
					beforeSend: addCSRFToken,
					success: function (connection) {
						var newOption = $("<option/>", {
							text: newUsername,
							value: connection.id,
						});
						newOption.insertBefore(contactAddOption);
						contactField.val(connection.id);
						submitContactForm();
					},
				});
			}
		} else {
			submitContactForm();
		}
	});

	/*

    Closet list droppable

  */

	onHangersInit(function () {
		$("div.closet-list").droppable({
			accept: "div.object",
			activate: function () {
				$(this)
					.find(".closet-list-content")
					.animate({ opacity: 0, height: 100 }, 250);
			},
			activeClass: "droppable-active",
			deactivate: function () {
				$(this)
					.find(".closet-list-content")
					.css("height", "auto")
					.animate({ opacity: 1 }, 250);
			},
			drop: function (e, ui) {
				var form = ui.draggable.find("form.closet-hanger-update");
				form
					.find("input[name=closet_hanger[list_id]]")
					.val(this.getAttribute("data-id"));
				form
					.find("input[name=closet_hanger[owned]]")
					.val($(this).closest(".closet-hangers-group").attr("data-owned"));
				submitUpdateForm(form);
			},
		});
	});

	/*

    Visibility Descriptions

  */

	function updateVisibilityDescription() {
		var descriptions = $(this)
			.closest(".visibility-form")
			.find("ul.visibility-descriptions");

		descriptions.children("li.current").removeClass("current");
		descriptions
			.children("li[data-id=" + $(this).val() + "]")
			.addClass("current");
	}

	function visibilitySelects() {
		return $("form.visibility-form select");
	}

	visibilitySelects().live("change", updateVisibilityDescription);

	onHangersInit(function () {
		visibilitySelects().each(updateVisibilityDescription);
	});

	/*

    Help

  */

	$("#toggle-help").click(function () {
		$("#closet-hangers-help").toggleClass("hidden");
	});

	/*

    Share URL

  */

	$("#closet-hangers-share-box")
		.mouseover(function () {
			$(this).focus();
		})
		.mouseout(function () {
			$(this).blur();
		});

	/*

    Initialize

  */

	hangersInit();
})();