local S = minetest.get_translator("lzr_level_select")
local F = minetest.formspec_escape
local FS = function(...) return minetest.formspec_escape(S(...)) end

-- Text color for completed level
local COLOR_LEVEL_COMPLETED = "#00FF00"
local COLOR_DIFFICULTY_COMPLETED = "#20FF20"

-- Directory separator
local DIR_DELIM = "/"

lzr_level_select = {}

local current_core_level_selection = nil
local is_showing_all_core_levels = false

local current_custom_level_selection = nil
local current_custom_level_pack_selection = "__singleton"
local current_custom_singleton_level_selection = nil

local current_stats_level_pack_selection = "__core"

local custom_levels
local custom_level_packs = {}

-- Programmatically, select a level in a level pack.
-- Change will take effect the next time the level selection formspec
-- is opened.
lzr_level_select.select_level = function(level_number, level_data)
	if level_data.name == "__core" then
		current_core_level_selection = level_number
	elseif level_data.name == "__singleton" or level_data.name == nil then
		current_custom_level_pack_selection = "__singleton"
		current_custom_level_selection = 1
		current_custom_singleton_level_selection = string.sub(level_data[1].filename, 1, -5)
	else
		current_custom_level_pack_selection = level_data.name
		current_custom_level_selection = level_number
	end
end

local message_form = function(player, message)
	return "formspec_version[7]size[8,3.5]"..
	"textarea[0.5,0.5;7,1.8;;;"..F(message).."]"..
	"button_exit[3,2.5;2,0.6;okay;"..FS("OK").."]"
end

local update_custom_level_packs = function(skip_core, level_pack_selection)
	local packnames = lzr_levels.get_level_pack_names()
	local mypacks = {}
	custom_level_packs = {}
	table.insert(custom_level_packs, "__singleton")
	table.insert(mypacks, FS("Single levels"))
	local dropdown_idx
	local pi = 1 -- counted pack index
	for p=1, #packnames do
		local packname = packnames[p]
		if (not skip_core) or packname ~= "__core" then
			pi = pi + 1
			if packname == level_pack_selection then
				dropdown_idx = pi
			end
			table.insert(custom_level_packs, packname)
			local pack = lzr_levels.get_level_pack(packname)
			local title = pack.title or packname
			table.insert(mypacks, minetest.formspec_escape(title))
		end
	end
	if level_pack_selection == "__singleton" then
		dropdown_idx = 1
	end
	if not dropdown_idx then
		dropdown_idx = 1
	end
	return mypacks, dropdown_idx
end

lzr_level_select.open_dialog = function(player, level_set)
	local caption
	if level_set == "core" then
		caption = S("Select level:")
	elseif level_set == "custom" then
		caption = S("Select level pack:")
	else
		minetest.log("error", "[lzr_level_select] open_dialog called with unknown level_set!")
		return false
	end

	-- Immediately send a loading formspec for the custom levels list for a more responsive UI.
	-- If there are a ton of custom levels, generating the level list can take a while.
	if level_set == "custom" then
		minetest.show_formspec(player:get_player_name(), "lzr_level_select:loading",
			-- The loading formspec should match the final level list as closely
			-- as possible to prevent the formspec to "jump around"
			"formspec_version[7]size[6,10]"..
			"label[0.5,0.4;"..caption.."]"..
			"box[0.5,0.7;5,0.6;#00000080]"..
			"label[0.55,1;"..FS("Loading …").."]"..
			"label[0.5,1.65;"..FS("Select level:").."]"..
			"box[0.5,2;5,5.9;#00000080]"..
			"label[0.6,2.25;"..FS("Loading …").."]")
	end


	local form = "formspec_version[7]size[6,10]"..
		"label[0.5,0.4;"..F(caption).."]"..
		"button_exit[1.5,8.5;3,1;okay;"..FS("Start").."]"

	local mypacks
	local form_levellist

	-- Columns: color, level-solved icon, level name
	local columns = "tablecolumns[color;image,0=lzr_level_select_level_uncompleted.png,1=lzr_level_select_level_completed.png,2=lzr_level_select_level_singleton.png;text]"

	if level_set == "custom" then
		-- Level pack dropdown and start of levels table
		local mypacks, dropdown_idx = update_custom_level_packs(true, current_custom_level_pack_selection)

		local mypacks_str = table.concat(mypacks, ",")
		form = form ..
		"dropdown[0.5,0.7;5,0.6;levelpack;"..mypacks_str..";"..dropdown_idx..";true]"

		form_levellist =
		"label[0.5,1.65;"..FS("Select level:").."]"..
		columns..
		"table[0.5,2;5,5.9;levellist;"
	else
		-- No level pack dropdown for core levels;
		-- start of levels table
		form_levellist =
		columns..
		"table[0.5,0.8;5,7.5;levellist;"
	end

	if level_set == "core" then
		local level_data = lzr_levels.get_level_pack("__core")
		local completed_levels = lzr_levels.get_completed_levels(level_data)
		-- Select initial level, if no level is selected
		if not current_core_level_selection then
			-- Select first unsolved level
			for l=1, #level_data do
				local level_number = l
				local filename = level_data[level_number].filename
				local level_id = string.sub(filename, 1, -5)
				if not completed_levels[level_id] then
					current_core_level_selection = l
					break
				end
			end
			-- If all levels are solved, select last level
			if not current_core_level_selection then
				current_core_level_selection = #level_data
			end
		end

		-- Diffculty buttons
		local difficulty_names = {
				--~ Difficulty name for tutorial levels
			[0] = { S("Landlubber"),
				S("Learn how to play") },
				--~ Difficulty name for very easy levels (low pirate rank)
			[1] = { S("Swabby"),
				S("Very easy levels") },
				--~ Difficulty name for easy levels (pirate rank)
			[2] = { S("Boatswain"),
				S("Easy levels") },
				--~ Difficulty name for medium levels (pirate rank)
			[3] = { S("First Mate"),
				--~ Levels of medium difficulty
				S("Medium levels") },
				--~ Difficulty name for hard levels (second-highest pirate rank)
			[4] = { S("Quartermaster"),
				--~ Difficult levels
				S("Hard levels") },
				--~ Difficulty name for very hard levels (highest pirate rank)
			[5] = { S("Captain"),
				--~ Very difficult levels
				S("Very hard levels") },
		}
		local difficulty_style = function(difficulty, selected)
			local form = "style[difficulty_"..difficulty..";noclip=true]"
			if selected then
				form = form .. "style[difficulty_"..difficulty..";bgimg=lzr_gui_button_current.png;bgimg_middle=4]" ..
					"style[difficulty_"..difficulty..":hovered;bgimg=lzr_gui_button_current_hover.png]" ..
					"style[difficulty_"..difficulty..":pressed;bgimg=lzr_gui_button_current_pressed.png]"
			end
			return form
		end
		local y = 1.65
		local current_difficulty
		if current_core_level_selection then
			current_difficulty = lzr_levels.get_level_difficulty(current_core_level_selection, level_data) or 0
		end

		local bwidth
		local winfo = minetest.get_player_window_information(player:get_player_name())
		-- Adjust difficulty button width based on window height.
		-- This check only applies when opening the formspec. Already open
		-- formspecs will NOT dynamically change.
		if winfo and (winfo.size.y < 500 * winfo.real_gui_scaling) then
			bwidth = 5
		elseif winfo and (winfo.size.y < 700 * winfo.real_gui_scaling) then
			bwidth = 4
		else
			bwidth = 3
		end
		for d=0, 5 do
			local difficulty_completed = lzr_levels.is_difficulty_completed(level_data, d)
			local dname = difficulty_names[d][1]
			if difficulty_completed then
				form = form .. "style[difficulty_"..d..";textcolor="..COLOR_DIFFICULTY_COMPLETED.."]"
			end
			-- Button style for difficulty button
			local highlight = not is_showing_all_core_levels and current_difficulty == d
			form = form .. difficulty_style(d, highlight)
			form = form .. "style[difficulty_"..d..";noclip=true]" ..
				"button["..(-bwidth)..","..y..";"..bwidth..",0.7;difficulty_"..d..";"..F(dname).."]" ..
				"tooltip[difficulty_"..d..";"..F(difficulty_names[d][2]).."]"
			y = y + 0.7
		end

		-- 'All levels' button
		y = y + 0.5
		local all_completed = lzr_levels.are_all_levels_completed(level_data)
		if all_completed then
			form = form .. "style[difficulty_all;textcolor="..COLOR_DIFFICULTY_COMPLETED.."]"
		end
		form = form .. difficulty_style("all", is_showing_all_core_levels) ..
			"button["..(-bwidth)..","..y..";"..bwidth..",0.7;difficulty_all;"..FS("All levels").."]"
	end

	local list = {}
	local entry_header = ""
	if level_set == "core" then
		-- Built-in level pack
		local level_data = lzr_levels.get_level_pack("__core")
		local completed_levels = lzr_levels.get_completed_levels(level_data)
		local filtered_levels = {}

		if is_showing_all_core_levels then
			for ll=1, #level_data do
				table.insert(filtered_levels, ll)
			end
		else
			local difficulty = lzr_levels.get_level_difficulty(current_core_level_selection, level_data)
			for d=1, #level_data.difficulties[difficulty] do
				local level_number = level_data.difficulties[difficulty][d]
				table.insert(filtered_levels, level_number)
			end
		end
		for l=1, #filtered_levels do
			local level_number = filtered_levels[l]
			local filename = level_data[level_number].filename
			local level_id = string.sub(filename, 1, -5)
			if completed_levels[level_id] then
				entry_header = COLOR_LEVEL_COMPLETED .. ",1"
			else
				entry_header = ",0"
			end
			table.insert(list, entry_header..","..F(lzr_levels.get_level_name(level_number, level_data, true)))
		end
	else
		if current_custom_level_pack_selection == "__singleton" then
			-- Pseudo-levelpack containing single unsorted levels (editor levels)
			local list_levels = lzr_editor.get_custom_levels(true)
			custom_levels = {}
			entry_header = ",2"
			local list_index = 0
			for l=1, #list_levels do
				local levelname = list_levels[l].id
				-- Hide the autosave level because it's not meant
				-- for playing
				if levelname ~= lzr_globals.AUTOSAVE_NAME then
					table.insert(custom_levels, levelname)

					local proper_name = list_levels[l].name
					table.insert(list, entry_header..","..F(proper_name))
					list_index = list_index + 1

					if levelname == current_custom_singleton_level_selection then
						current_custom_level_selection = list_index
					end
				end
			end
			if not current_custom_level_selection then
				current_custom_level_selection = 1
			end
		else
			-- Custom level pack
			local level_data = lzr_levels.get_level_pack(current_custom_level_pack_selection)
			local completed_levels = lzr_levels.get_completed_levels(level_data)
			for l=1, #level_data do
				local filename = level_data[l].filename
				local level_id = string.sub(filename, 1, -5)
				if completed_levels[level_id] then
					entry_header = COLOR_LEVEL_COMPLETED..",1"
				else
					-- Select first incomplete custom level in pack,
					-- if nothing was selected before
					if not current_custom_level_selection then
						current_custom_level_selection = l
					end
					entry_header  = ",0"
				end
				table.insert(list, entry_header..","..F(lzr_levels.get_level_name(l, level_data, true)))
			end
			-- Select last level if still nothing is selected
			if not current_custom_level_selection then
				current_custom_level_selection = #level_data
			end
		end
	end
	local selected_row
	if level_set == "core" then
		local level_data = lzr_levels.get_level_pack("__core")
		if current_core_level_selection then
			if is_showing_all_core_levels then
				selected_row = current_core_level_selection
			else
				selected_row = lzr_levels.get_level_position_in_difficulty(current_core_level_selection, level_data)
			end
		end
	else
		selected_row = current_custom_level_selection
	end
	if not selected_row then
		selected_row = 1
	end

	local list_str = table.concat(list, ",")
	form_levellist = form_levellist .. list_str .. ";"..selected_row.."]"
	local formname
	if level_set == "core" then
		if #list == 0 then
			form = message_form(player, S("There are no levels."))
		else
			form = form .. form_levellist
		end
		formname = "lzr_level_select:levellist"
	else
		if #list == 0 then
			local level_packs = lzr_levels.get_level_pack_names()
			if #level_packs <= 1 then
				form = message_form(player,
					S("There are no level packs or custom single levels.").."\n"..
					S("Install a level pack or use the level editor to build your own levels."))
			elseif current_custom_level_pack_selection == "__singleton" then
				form = form .. "textarea[0.5,1.7;5,5;;;"..
				FS("There are no custom single levels.").."\n"..
				FS("Install one from your friends or use the level editor to build your own levels.").."]"
			else
				form = form .. "textarea[0.5,1.7;5,5;;;"..
				FS("This level pack is empty.").."]"
			end
		else
			form = form .. form_levellist
		end
		formname = "lzr_level_select:levellist_custom"
	end
	minetest.show_formspec(player:get_player_name(), formname, form)
end

local check_custom_level = function(level_basename, level_csv_file, levels_path, order_path, level_num, player)
	minetest.log("action", "[lzr_level_select] Checking custom level: "..level_basename)
	local pname = player:get_player_name()

	local custom_level_data, error_type, error_detail_1, error_detail_2 = lzr_levels.analyze_levels(level_csv_file, levels_path, nil, order_path, true)
	if custom_level_data then
		custom_level_data.is_singleton = true
		-- Load level with metadata (preferred)
		return custom_level_data
	elseif error_type == "load_error" then
		-- Fallback when CSV file could not be read
		custom_level_data = lzr_levels.create_fallback_level_data(level_basename, levels_path)
		if not custom_level_data then
			local form = message_form(player, S("Level could not be loaded. No level CSV file was found, and the fallback mechanism failed as well."))
			minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
			minetest.log("action", "[lzr_level_select] Player tried to load broken custom level '"..level_basename.."'. Missing CSV file, and the fallback failed as well.")
			return
		else
			custom_level_data.is_singleton = true
			minetest.log("action", "[lzr_level_select] No CSV file found for custom level '"..level_basename.."'. Suggesting fallback.")
			return custom_level_data
		end
	elseif error_type == "csv_error" then
		local form = message_form(player, S("The level could not be loaded. Invalid syntax of level CSV file."))
		minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
		minetest.log("action", "[lzr_level_select] Player tried to load broken custom level '"..level_basename.."'. Invalid CSV syntax.")
	elseif error_type == "bad_schematic" then
		local error_append = ""
		if error_detail_2 then
			local reason
			if error_detail_2 == "file_nonexistant" then
				reason = S("The level schematic file could not be found.")
			elseif error_detail_2 == "no_teleporter" then
				reason = S("There’s no start block for the player to start on.")
			elseif error_detail_2 == "too_many_teleporters" then
				reason = S("There’s more than one start block.")
			elseif error_detail_2 == "too_many_parrot_spawners" then
				reason = S("There’s more than one Goldie the Parrot.")
			elseif error_detail_2 == "barriers" then
				reason = S("There’s a barrier or barrier-like node in the level.")
			elseif error_detail_2 == "gold_block" then
				reason = S("There’s a bare gold block in the level.")
			elseif error_detail_2 == "burning_barricade" then
				reason = S("There’s a burning barricade in the level.")
			elseif error_detail_2 == "ignited_bomb" then
				reason = S("There’s an ignited bomb in the level.")
			elseif error_detail_2 == "plant_on_ground" then
				reason = S("The level has a rooted plant that isn’t submerged in water.")
			elseif error_detail_2 == "schematic_load_error" then
				reason = S("Error while loading schematic file.")
			elseif error_detail_2 == "too_many_show_controls_flags" then
				--~ A level flag is an advanced on/off level setting
				reason = S("The level settings contain multiple ‘show controls’ level flags.")
			elseif error_detail_2 == "pre_collected" then
				reason = S("The treasure in a chest is exposed.")
			elseif error_detail_2 == "no_treasures" then
				reason = S("There isn’t any treasure to collect.")
			elseif error_detail_2 == "unknown_node" then
				reason = S("The level contains an unknown block. This is probably because you’re playing an old game version. Please update your game and try again.")
			else
				reason = error_detail_2
			end
			--~ Reason shown on level loading error
			error_append = "\n\n" .. S("Reason: @1", reason)
		end
		if not error_detail_1 then
			error_detail_1 = "<UNKNOWN>"
		end
		local form = message_form(player, S("The level “@1” is unplayable.", error_detail_1)..error_append)
		minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
		minetest.log("action", "[lzr_level_select] Player tried to load broken custom level '"..level_basename.."'. Bad schematic.")
	else
		local form = message_form(player, S("The level could not be loaded due to an unknown error."))
		minetest.show_formspec(pname, "lzr_level_select:custom_load_error", form)
		minetest.log("error", "[lzr_level_select] Unknown error while loading custom level '"..level_basename.."'. Aborting.")
	end
end

lzr_level_select.open_stats_dialog = function(player)
	local level_packs_names = lzr_levels.get_level_pack_names()

	local mypacks, dropdown_idx = update_custom_level_packs(false, current_stats_level_pack_selection)
	local mypacks_str = table.concat(mypacks, ",")

	local form = "formspec_version[7]size[6,10]"..
		"box[0,0;6,0.8;#00000080]"..
		"label[0.5,0.4;"..FS("Installed level packs").."]"..
		"label[0.5,1.3;"..FS("Select level pack:").."]"..
		"dropdown[0.5,1.5;5,0.6;levelpack;"..mypacks_str..";"..dropdown_idx..";true]"..
		"button_exit[1.5,8.5;3,1;okay;"..FS("Okay").."]"..
		"box[0.5,2.3;5,5.9;#00000020]"..
		"hypertext[0.5,2.3;5,5.9;info;"

	local info
	local color = "#00ff00"
	local H = minetest.hypertext_escape
	if current_stats_level_pack_selection == "__singleton" then
		local count = lzr_editor.count_custom_levels(true)
		--~ path of a Luanti world directory
		local path = "<"..S("WORLD PATH")..">"..DIR_DELIM.."levels"

		info =
			"<bigger>"..H(S("Single levels")).."</bigger>\n"..
			"<big>"..H(S("Description")).."</big>\n"..
				H(S("Single, unsorted levels that don’t belong to any level pack.")).."\n"..
			"<big>"..H(S("Stats")).."</big>\n"..
				--~ @1 = number of levels
				H(S("• Levels: @1", count)).."\n\n"..

			"<big>"..H(S("File location")).."</big>\n"..
				H(S("Single levels are stored in:")).."\n"..
				"<mono>"..H(path).."</mono>"
	else
		local level_data = lzr_levels.get_level_pack(current_stats_level_pack_selection)
		local treasures_total = lzr_levels.count_total_treasures(level_data)
		local treasures_found = lzr_levels.count_total_collected_treasures(level_data)
		local treasures_percent = string.format("%d", (treasures_found / treasures_total) * 100)

		local levels_total = #level_data
		local levels_completed = lzr_levels.count_completed_levels(level_data)

		local levels_percent = string.format("%d", (levels_completed / levels_total) * 100)

		local title = level_data.title or level_data.name
		local pack_description = level_data.description or S("(no description provided)")

		-- Core levels: Show hidden parrots stat if at least one found
		local stat_hidden_parrots = ""
		if current_stats_level_pack_selection == "__core" then
			local hidden_parrots_found = lzr_parrot_npc.count_found_hidden_parrots()
			if hidden_parrots_found > 0 then
				local hidden_parrots_total = 0
				-- count hidden parrots in levels
				for l=1, #level_data do
					local level = level_data[l]
					if level.hidden_parrot_pos then
						hidden_parrots_total = hidden_parrots_total + 1
					end
				end
				if hidden_parrots_total > 0 then
					local hidden_parrots_percent = string.format("%d", (hidden_parrots_found / hidden_parrots_total) * 100)
					stat_hidden_parrots = H(S("• Hidden parrots: @1 (@2 found, @3%)", hidden_parrots_total, hidden_parrots_found, hidden_parrots_percent)).."\n"
				end
			end
		end

		info =
			"<bigger>"..H(title).."</bigger>\n"..
			"<big>"..H(S("Description")).."</big>\n"..
				H(pack_description).."\n"..
			"<big>"..H(S("Stats")).."</big>\n"..
				--~ @1 = total number of levels, @2 = number of completed levels, @3 percentage of level completion
				H(S("• Levels: @1 (@2 completed, @3%)", levels_total, levels_completed, levels_percent)).."\n"..
				--~ @1 = total number of gold blocks, @2 = number of found gold blocks, @3 percentage of found gold blocks
				H(S("• Gold blocks: @1 (@2 found, @3%)", treasures_total, treasures_found, treasures_percent)).."\n"..
				stat_hidden_parrots..
				"\n"..

			"<big>"..H(S("Mod")).."</big>\n"..
				"<mono>"..H(level_data.mod_origin).."</mono>"

		local privs = minetest.get_player_privs(player:get_player_name())
		if minetest.settings:get_bool("lzr_debug", true) or privs.server or privs.debug then
			info = info .."\n"..
				--~ For the technical level pack identifier
				"<big>"..H(S("Level pack ID")).."</big>\n"..
				"<mono>"..H(level_data.name).."</mono>"
		end
	end

	form = form .. F(info) .. "]"

	minetest.show_formspec(player:get_player_name(), "lzr_level_select:stats", form)
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname == "lzr_level_select:levellist" then
		if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
			return
		end
		local level_data = lzr_levels.get_level_pack("__core")
		if fields.okay then
			if current_core_level_selection then
				lzr_levels.start_level(current_core_level_selection, level_data)

			end
		elseif fields.levellist then
			local expl = minetest.explode_table_event(fields.levellist)
			if expl.type == "CHG" or expl.type == "DCL" then
				local level_number
				if is_showing_all_core_levels then
					level_number = expl.row
				else
					local difficulty = lzr_levels.get_level_difficulty(current_core_level_selection or 1, level_data)
					level_number = lzr_levels.get_level_number_by_difficulty(expl.row, level_data, difficulty)
				end
				current_core_level_selection = level_number
				if expl.type == "DCL" then
					if level_number then
						lzr_levels.start_level(level_number, level_data)
						minetest.close_formspec(player:get_player_name(), "lzr_level_select:levellist")
					end
				end
			elseif expl.type == "INV" then
				current_core_level_selection = nil
			end
		elseif fields.difficulty_all then
			-- 'All levels' button
			is_showing_all_core_levels = true
			lzr_level_select.open_dialog(player, "core")
		else
			-- Difficulty buttons
			for d=0, 5 do
				if fields["difficulty_"..d] then
					local level_number
					if is_showing_all_core_levels and current_core_level_selection then
						local diff = lzr_levels.get_level_difficulty(current_core_level_selection, level_data)
						if diff == d then
							local pos = lzr_levels.get_level_position_in_difficulty(current_core_level_selection, level_data)
							if pos then
								level_number = current_core_level_selection
							end
						end
					end
					if not level_number then
						-- Select first unsolved level in this difficulty
						for l=1, #level_data.difficulties[d] do
							local tempnum = level_data.difficulties[d][l]
							local filename = level_data[tempnum].filename
							local level_id = string.sub(filename, 1, -5)
							local completed_levels = lzr_levels.get_completed_levels(level_data)
							if not completed_levels[level_id] then
								level_number = tempnum
								break
							end
						end
						-- Select last level if all are solved
						if not level_number then
							level_number = level_data.difficulties[d][#level_data.difficulties[d]]
						end
					end

					is_showing_all_core_levels = false
					current_core_level_selection = level_number
					lzr_level_select.open_dialog(player, "core")
				end
			end
		end
	elseif formname == "lzr_level_select:levellist_custom" then
		if lzr_gamestate.get_state() ~= lzr_gamestate.MENU then
			return
		end
		local function try_start_level()
			if custom_levels and current_custom_level_selection then
				if current_custom_level_pack_selection == "__singleton" then
					local level = custom_levels[current_custom_level_selection]
					if level then
						current_custom_singleton_level_selection = level
						local csv = minetest.get_worldpath().."/levels/"..level..".csv"
						local levels_path = minetest.get_worldpath().."/levels"
						local custom_level_data = check_custom_level(level, csv, levels_path, nil, 1, player)
						if custom_level_data then
							lzr_levels.start_level(1, custom_level_data)
						end
					end
				else
					local level_data = lzr_levels.get_level_pack(current_custom_level_pack_selection)
					local basename = level_data[current_custom_level_selection].name
					local custom_level_data = check_custom_level(basename, level_data.level_data_file, level_data.schematic_path, level_data.level_order_file, current_custom_level_selection, player)
					if custom_level_data then
						lzr_levels.start_level(current_custom_level_selection, level_data)
					end
				end
			end
		end
		if fields.okay then
			try_start_level()
		elseif fields.levellist then
			local expl = minetest.explode_table_event(fields.levellist)
			if expl.type == "CHG" then
				current_custom_level_selection = expl.row
				if current_custom_level_pack_selection == "__singleton" then
					if custom_levels then
						local level = custom_levels[current_custom_level_selection]
						if level then
							current_custom_singleton_level_selection = level
						end
					end
				end
			elseif expl.type == "DCL" then
				current_custom_level_selection = expl.row
				try_start_level()
				minetest.close_formspec(player:get_player_name(), "lzr_level_select:levellist_custom")
			elseif expl.type == "INV" then
				current_custom_level_selection = nil
				current_custom_singleton_level_selection = nil
			end
		elseif fields.levelpack then
			local idx = tonumber(fields.levelpack)
			local prev_custom_level_pack_selection = current_custom_level_pack_selection
			current_custom_level_pack_selection = custom_level_packs[idx]
			if prev_custom_level_pack_selection ~= current_custom_level_pack_selection then
				current_custom_level_selection = nil
			end
			lzr_level_select.open_dialog(player, "custom")
		end
	elseif formname == "lzr_level_select:stats" then
		if fields.levelpack then
			local idx = tonumber(fields.levelpack)
			current_stats_level_pack_selection = custom_level_packs[idx]
			if not fields.quit then
				lzr_level_select.open_stats_dialog(player)
			end
		end
	end
end)

minetest.register_chatcommand("level", {
	privs = { server = true },
	description = S("Go to level"),
	params = S("[<level pack>] <level number>"),
	func = function(name, param)
		if lzr_gamestate.get_state() == lzr_gamestate.LEVEL_TEST then
			return false, S("Not possible during the level solution test!")
		end
		if lzr_gamestate.is_loading() then
			return false, S("Can’t start a level while loading!")
		end
		-- Parse arguments. level argument may be relative (tilde notation)
		local pack, level = string.match(param, "([a-zA-Z0-9_]+) (~?-?[0-9]*)")

		local current_data = lzr_levels.get_current_level_data()
		local current_pack
		if current_data then
			current_pack = current_data.name
		end
		-- Select level pack. Defaults to the current level pack
		-- if playing one or __core if playing no level pack
		-- (singleton levels don't count)
		if not pack then
			if current_pack then
				pack = current_pack
			else
				pack = "__core"
			end
			level = string.match(param, "(~?-?[0-9]*)")
		end
		if not level then
			return false
		end
		-- Handle relative level argument (e.g. "~1" to move to the next level)
		local is_relative_level = string.sub(level, 1, 1) == "~"
		if is_relative_level then
			local current_level = lzr_levels.get_current_level()
			-- Relative level argument is only allowed if playing in a level
			-- and the chosen level pack is the same as the current one.
			if not current_level then
				return false, S("Level argument can’t be relative when not playing in a level.")
			end
			if current_pack ~= pack then
				return false, S("Level argument can’t be relative when choosing a different level pack.")
			end
			level = minetest.parse_relative_number(level, current_level)
		else
			level = tonumber(level)
		end
		if not level then
			return false
		end

		local level_data = lzr_levels.get_level_pack(pack)
		if not level_data then
			return false, S("Level pack “@1” doesn’t exist.", pack)
		end
		if level < 1 or level > #level_data then
			--~ @1, @2, and @3 are level numbers
			return false, S("Level @1 does not exist in this level pack. Available levels range from @2 to @3.", level, 1, #level_data)
		end
		lzr_levels.start_level(level, level_data)
		return true
	end,

})

minetest.register_chatcommand("level_packs", {
	description = S("Display information and stats about the installed level packs"),
	params = "",
	func = function(name, param)
		local player = minetest.get_player_by_name(name)
		if not player then
			return false, S("No player.")
		end
		lzr_level_select.open_stats_dialog(player)
		return true
	end,
})

lzr_levels.register_on_level_start(function(level_pack_id, level_number)
	if lzr_gamestate.get_state() == lzr_gamestate.LEVEL_TEST then
		return
	end
	if not level_pack_id or level_pack_id == "__singleton" then
		return
	end
	local level_data = lzr_levels.get_level_pack(level_pack_id)
	lzr_level_select.select_level(level_number, level_data)
end)
