local L = DBM_GUI_L

---@class DBMGUI
local DBM_GUI = {
	tabs	= {},
	panels	= {}
}
_G.DBM_GUI = DBM_GUI

local isRetail = WOW_PROJECT_ID == (WOW_PROJECT_MAINLINE or 1)

local next, type, pairs, strsplit, tonumber, tostring, ipairs, tinsert, tsort, mfloor, slower = next, type, pairs, strsplit, tonumber, tostring, ipairs, table.insert, table.sort, math.floor, string.lower
local CreateFrame, C_Timer, GameFontNormal, GameFontNormalSmall, GameFontHighlight, GameFontHighlightSmall, ChatFontNormal, UIParent = CreateFrame, C_Timer, GameFontNormal, GameFontNormalSmall, GameFontHighlight, GameFontHighlightSmall, ChatFontNormal, UIParent
local RAID_DIFFICULTY1, RAID_DIFFICULTY2, RAID_DIFFICULTY3, RAID_DIFFICULTY4, PLAYER_DIFFICULTY1, PLAYER_DIFFICULTY2, PLAYER_DIFFICULTY3, PLAYER_DIFFICULTY6, PLAYER_DIFFICULTY_TIMEWALKER, CHALLENGE_MODE, ALL, CLOSE, SPECIALIZATION = RAID_DIFFICULTY1, RAID_DIFFICULTY2, RAID_DIFFICULTY3, RAID_DIFFICULTY4, PLAYER_DIFFICULTY1, PLAYER_DIFFICULTY2, PLAYER_DIFFICULTY3, PLAYER_DIFFICULTY6, PLAYER_DIFFICULTY_TIMEWALKER, CHALLENGE_MODE, ALL, CLOSE, SPECIALIZATION
local DBM, DBM_OPTION_SPACER = DBM, DBM_OPTION_SPACER
local playerName, realmName, playerLevel = UnitName("player"), GetRealmName(), UnitLevel("player")

StaticPopupDialogs["IMPORTPROFILE_ERROR"] = {
	text = "There are one or more errors importing this profile. Please see the chat for more information. Would you like to continue and reset found errors to default?",
	button1 = "Import and fix",
	button2 = "No",
	OnAccept = function(self)
		self.importFunc()
	end,
	timeout = 0,
	whileDead = true,
	hideOnEscape = true,
	preferredIndex = 3,
}

local challengeModeIds = {
	[2] = 960, -- Temple of the Jade Serpent
	[56] = 961, -- Stormstout Brewery
	[57] = 962, -- Gate of the Setting Sun
	[58] = 959, -- Shado-Pan Monastery
	[59] = 1011, -- Siege of Niuzao Temple
	[60] = 994, -- Mogu'shan Palace
	[76] = 1007, -- Scholomance
	[77] = 1001, -- Scarlet Halls
	[78] = 1004, -- Scarlet Monastery
	[161] = 1209, -- Skyreach
	[163] = 1175, -- Bloodmaul Slag Mines
	[164] = 1182, -- Auchindoun
	[165] = 1176, -- Shadowmoon Burial Grounds
	[166] = 1208, -- Grimrail Depot
	[167] = 1358, -- Upper Blackrock Spire
	[168] = 1279, -- The Everbloom
	[169] = 1195, -- Iron Docks
	[197] = 1456, -- Eye of Azshara
	[198] = 1466, -- Darkheart Thicket
	[199] = 1501, -- Black Rook Hold
	[200] = 1477, -- Halls of Valor
	[206] = 1458, -- Neltharion's Lair
	[207] = 1493, -- Vault of the Wardens
	[208] = 1492, -- Maw of Souls
	[209] = 1516, -- The Arcway
	[210] = 1571, -- Court of Stars
	[227] = 1651, -- Return to Karazhan: Lower
	[233] = 1677, -- Cathedral of Eternal Night
	[234] = 1651, -- Return to Karazhan: Upper
	[239] = 1753, -- Seat of the Triumvirate
	[244] = 1763, -- Atal'Dazar
	[245] = 1754, -- Freehold
	[246] = 1771, -- Tol Dagor
	[247] = 1594, -- The MOTHERLODE!!
	[248] = 1862, -- Waycrest Manor
	[249] = 1762, -- Kings' Rest
	[250] = 1877, -- Temple of Sethraliss
	[251] = 1841, -- The Underrot
	[252] = 1864, -- Shrine of the Storm
	[353] = 1822, -- Siege of Boralus
	[369] = 2097, -- Operation: Mechagon - Junkyard
	[370] = 2097, -- Operation: Mechagon - Workshop
	[375] = 2290, -- Mists of Tirna Scithe
	[376] = 2286, -- The Necrotic Wake
	[377] = 2291, -- De Other Side
	[378] = 2287, -- Halls of Atonement
	[379] = 2289, -- Plaguefall
	[380] = 2284, -- Sanguine Depths
	[381] = 2285, -- Spires of Ascension
	[382] = 2293, -- Theater of Pain
	[391] = 2441, -- Tazavesh: Streets of Wonder
	[392] = 2441, -- Tazavesh: So'leah's Gambit
	[399] = 2521, -- Ruby Life Pools
	[400] = 2516, -- The Nokhud Offensive
	[401] = 2515, -- The Azure Vault
	[402] = 2526, -- Algeth'ar Academy
	[403] = 2451, -- Uldaman: Legacy of Tyr
	[404] = 2519, -- Neltharus
	[405] = 2520, -- Brackenhide Hollow
	[406] = 2527, -- Halls of Infusion
	[438] = 657, -- The Vortex Pinnacle
	[456] = 643, -- Throne of the Tides
	[463] = 2579, -- Dawn of the Infinite: Galakrond's Fall
	[464] = 2579, -- Dawn of the Infinite: Murozond's Rise
	[499] = 2649, -- Priory of the Sacred Flame
	[500] = 2648, -- The Rookery
	[501] = 2652, -- The Stonevault
	[502] = 2669, -- City of Threads
	[503] = 2660, -- Ara-Kara, City of Echoes
	[504] = 2651, -- Darkflame Cleft
	[505] = 2662, -- The Dawnbreaker
	[506] = 2661, -- Cinderbrew Meadery
	[507] = 670, -- Grim Batol
	[525] = 2773, -- Operation: Floodgate
	[541] = 725, -- The Stonecore
	[542] = 2830, -- Eco-Dome Al'dani
	[556] = 658, -- Pit of Saron
	[557] = 2805, -- Windrunner Spire
	[558] = 2811, -- Magisters' Terrace
	[559] = 2915, -- Nexus-Point Xenas
	[560] = 2874, -- Maisara Caverns
}

do
	local soundsRegistered = false

	function DBM_GUI:MixinSharedMedia3(mediatype, mediatable)
		if not LibStub or not LibStub("LibSharedMedia-3.0", true) then
			return mediatable
		end
		if not soundsRegistered then
			local LSM = LibStub("LibSharedMedia-3.0")
			soundsRegistered = true
			-- Embedded Sound Clip media
			LSM:Register("sound", "AirHorn (DBM)", [[Interface\AddOns\DBM-Core\sounds\AirHorn.ogg]])
			LSM:Register("sound", "Jaina: Beware", [[Interface\AddOns\DBM-Core\sounds\SoundClips\beware.ogg]])
			LSM:Register("sound", "Jaina: Beware (reverb)", [[Interface\AddOns\DBM-Core\sounds\SoundClips\beware_with_reverb.ogg]])
			LSM:Register("sound", "Thrall: That's Incredible!", [[Interface\AddOns\DBM-Core\sounds\SoundClips\incredible.ogg]])
			LSM:Register("sound", "Saurfang: Don't Die", [[Interface\AddOns\DBM-Core\sounds\SoundClips\dontdie.ogg]])
			-- Blakbyrd
			LSM:Register("sound", "Blakbyrd Alert 1", [[Interface\AddOns\DBM-Core\sounds\BlakbyrdAlerts\Alert1.ogg]])
			LSM:Register("sound", "Blakbyrd Alert 2", [[Interface\AddOns\DBM-Core\sounds\BlakbyrdAlerts\Alert2.ogg]])
			LSM:Register("sound", "Blakbyrd Alert 3", [[Interface\AddOns\DBM-Core\sounds\BlakbyrdAlerts\Alert3.ogg]])
			-- User Media
			if DBM.Options.CustomSounds >= 1 then
				LSM:Register("sound", "DBM: Custom 1", [[Interface\AddOns\DBM-CustomSounds\Custom1.ogg]])
			end
			if DBM.Options.CustomSounds >= 2 then
				LSM:Register("sound", "DBM: Custom 2", [[Interface\AddOns\DBM-CustomSounds\Custom2.ogg]])
			end
			if DBM.Options.CustomSounds >= 3 then
				LSM:Register("sound", "DBM: Custom 3", [[Interface\AddOns\DBM-CustomSounds\Custom3.ogg]])
			end
			if DBM.Options.CustomSounds >= 4 then
				LSM:Register("sound", "DBM: Custom 4", [[Interface\AddOns\DBM-CustomSounds\Custom4.ogg]])
			end
			if DBM.Options.CustomSounds >= 5 then
				LSM:Register("sound", "DBM: Custom 5", [[Interface\AddOns\DBM-CustomSounds\Custom5.ogg]])
			end
			if DBM.Options.CustomSounds >= 6 then
				LSM:Register("sound", "DBM: Custom 6", [[Interface\AddOns\DBM-CustomSounds\Custom6.ogg]])
			end
			if DBM.Options.CustomSounds >= 7 then
				LSM:Register("sound", "DBM: Custom 7", [[Interface\AddOns\DBM-CustomSounds\Custom7.ogg]])
			end
			if DBM.Options.CustomSounds >= 8 then
				LSM:Register("sound", "DBM: Custom 8", [[Interface\AddOns\DBM-CustomSounds\Custom8.ogg]])
			end
			if DBM.Options.CustomSounds >= 9 then
				LSM:Register("sound", "DBM: Custom 9", [[Interface\AddOns\DBM-CustomSounds\Custom9.ogg]])
				if DBM.Options.CustomSounds > 9 then
					DBM.Options.CustomSounds = 9
				end
			end
		end
		-- Sort LibSharedMedia keys alphabetically (case-insensitive)
		local hashtable = LibStub("LibSharedMedia-3.0", true):HashTable(mediatype)
		local keytable = {}
		for k in next, hashtable do
			tinsert(keytable, k)
		end
		tsort(keytable, function(a, b)
			return a:lower() < b:lower()
		end);
		-- DBM values (mediatable) first, LibSharedMedia values (sorted alphabetically) afterwards
		local result = mediatable
		for i = 1, #result do
			if mediatype == "statusbar" then
				result[i].texture = true
			elseif mediatype == "font" then
				result[i].font = true
			elseif mediatype == "sound" then
				result[i].sound = true
			end
		end
		for i = 1, #keytable do
			if mediatype ~= "sound" or (keytable[i] ~= "None" and keytable[i] ~= "NPCScan") then
				local v = hashtable[keytable[i]]
				-- Filter duplicates
				local insertme = true
				for _, v2 in next, result do
					if slower(v2.value) == slower(v) then
						insertme = false
						break
					end
				end
				if insertme then
					local ins = {
						text	= keytable[i],
						value	= v
					}
					if mediatype == "statusbar" then
						ins.texture = true
					elseif mediatype == "font" then
						ins.font = true
					elseif mediatype == "sound" then--and type(v) == "string" and v:lower():find("addons")
						ins.sound = true
					end
					if ins.texture or ins.font or ins.sound then
						tinsert(result, ins)
					end
				end
			end
		end
		return result
	end
end

do
	local LibSerialize = LibStub("LibSerialize")
	local LibDeflate = LibStub("LibDeflate")

	local canWeWork = LibStub and LibStub("LibDeflate", true) and LibStub("LibSerialize", true)
	local popupFrame

	local function createPopupFrame()
		---@class DBMPopupFrame: Frame, BackdropTemplate
		popupFrame = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
		popupFrame:SetFrameStrata("DIALOG")
		popupFrame:SetFrameLevel(_G["DBM_GUI_OptionsFrame"]:GetFrameLevel() + 10)
		popupFrame:SetSize(512, 512)
		popupFrame:SetPoint("CENTER")
		popupFrame.backdropInfo = {
			bgFile		= "Interface\\DialogFrame\\UI-DialogBox-Background", -- 131071
			edgeFile	= "Interface\\DialogFrame\\UI-DialogBox-Border", -- 131072
			tile		= true,
			tileSize	= 32,
			edgeSize	= 32,
			insets		= { left = 8, right = 8, top = 8, bottom = 8 }
		}
		popupFrame:ApplyBackdrop()
		popupFrame:SetMovable(true)
		popupFrame:EnableMouse(true)
		popupFrame:RegisterForDrag("LeftButton")
		popupFrame:SetScript("OnDragStart", popupFrame.StartMoving)
		popupFrame:SetScript("OnDragStop", popupFrame.StopMovingOrSizing)
		popupFrame:Hide()
		popupFrame.text = ""

		---@class DBMPopupFrameBackdrop: Frame, BackdropTemplate
		local backdrop = CreateFrame("Frame", nil, popupFrame, "BackdropTemplate")
		backdrop.backdropInfo = {
			bgFile		= "Interface\\ChatFrame\\ChatFrameBackground",
			edgeFile	= "Interface\\Tooltips\\UI-Tooltip-Border",
			tile		= true,
			tileSize	= 16,
			edgeSize	= 16,
			insets		= { left = 3, right = 3, top = 5, bottom = 3 }
		}
		backdrop:ApplyBackdrop()
		backdrop:SetBackdropColor(0.1, 0.1, 0.1, 0.6)
		backdrop:SetBackdropBorderColor(0.4, 0.4, 0.4)
		backdrop:SetPoint("TOPLEFT", 15, -15)
		backdrop:SetPoint("BOTTOMRIGHT", -40, 40)

		---@class DBMPopupFrameScrollFrame: ScrollFrame
		local scrollFrame = CreateFrame("ScrollFrame", nil, popupFrame, "UIPanelScrollFrameTemplate")
		scrollFrame:SetPoint("TOPLEFT", 15, -22)
		scrollFrame:SetPoint("BOTTOMRIGHT", -40, 45)

		local input = CreateFrame("EditBox", nil, scrollFrame)
		input:SetTextInsets(7, 7, 3, 3)
		input:SetFontObject(ChatFontNormal)
		input:SetMultiLine(true)
		input:EnableMouse(true)
		input:SetAutoFocus(false)
		input:SetMaxBytes(0)
		input:SetScript("OnMouseUp", function(self)
			self:HighlightText()
		end)
		input:SetScript("OnEscapePressed", function(self)
			self:ClearFocus()
		end)
		input:HighlightText()
		input:SetFocus()
		scrollFrame:SetScrollChild(input)
		input:ClearAllPoints()
		input:SetPoint("TOPLEFT", scrollFrame, "TOPLEFT")
		input:SetPoint("BOTTOMRIGHT", scrollFrame, "BOTTOMRIGHT")
		input:SetWidth(452)

		---@class DBMPopupFrameImportButton: Button
		local import = CreateFrame("Button", nil, popupFrame, "UIPanelButtonTemplate")
		import:SetPoint("BOTTOMRIGHT", -120, 13)
		import:SetFrameLevel(import:GetFrameLevel() + 1)
		import:SetSize(100, 20)
		import:SetText(L.Import)
		import:SetScript("OnClick", function()
			if popupFrame:VerifyImport(input:GetText()) then
				input:ClearFocus()
				popupFrame:Hide()
			end
		end)
		popupFrame.import = import

		local close = CreateFrame("Button", nil, popupFrame, "UIPanelButtonTemplate")
		close:SetPoint("LEFT", import, "RIGHT", 5, 0)
		close:SetFrameLevel(close:GetFrameLevel() + 1)
		close:SetSize(100, 20)
		close:SetText(CLOSE)
		close:SetScript("OnClick", function()
			input:ClearFocus()
			popupFrame:Hide()
		end)

		input:SetScript("OnChar", function()
			if not import:IsShown() then
				input:SetText(popupFrame.text)
				input:HighlightText()
			end
		end)

		function popupFrame:SetText(text)
			input:SetText(text)
			self.text = text
		end
	end

	function DBM_GUI:CreateExportProfile(export)
		if not canWeWork then
			DBM:AddMsg("Missing required libraries to export.")
			return
		end
		if not popupFrame then
			createPopupFrame()
		end
		popupFrame.import:Hide()
		popupFrame:SetText(LibDeflate:EncodeForPrint(LibDeflate:CompressDeflate(LibSerialize:Serialize(export), {level = 9})))
		popupFrame:Show()
	end

	function DBM_GUI:CreateImportProfile(importFunc)
		if not canWeWork then
			DBM:AddMsg("Missing required libraries to export.")
			return
		end
		if not popupFrame then
			createPopupFrame()
		end
		function popupFrame:VerifyImport(import)
			local success, deserialized = LibSerialize:Deserialize(LibDeflate:DecompressDeflate(LibDeflate:DecodeForPrint(import)))
			if not success then
				DBM:AddMsg("Failed to deserialize")
				return false
			end
			importFunc(deserialized)
			return true
		end
		popupFrame.import:Show()
		popupFrame:SetText("")
		popupFrame:Show()
	end
end

do
	local framecount = 0

	function DBM_GUI:GetNewID()
		framecount = framecount + 1
		return framecount
	end

	function DBM_GUI:GetCurrentID()
		return framecount
	end
end

local UpdateCurrentSeason
local firstLoad = true
function DBM_GUI:ShowHide(forceshow)
	if firstLoad then
		UpdateCurrentSeason()
		firstLoad = false
	end
	local optionsFrame = _G["DBM_GUI_OptionsFrame"]
	if forceshow == true then
		self:UpdateModList()
		optionsFrame:Show()
	elseif forceshow == false then
		optionsFrame:Hide()
	else
		if optionsFrame:IsShown() then
			optionsFrame:Hide()
		else
			self:UpdateModList()
			optionsFrame:Show()
		end
	end
end

local catbutton, lastButton, addSpacer
local function addOptions(mod, catpanel, v)
	if v == DBM_OPTION_SPACER then
		addSpacer = true
	else
		lastButton = catbutton
		if v.line then
			catbutton = catpanel:CreateLine(v.text)
		elseif type(mod.Options[v]) == "boolean" then
			if mod.Options[v .. "TColor"] then
				catbutton = catpanel:CreateCheckButton(mod.localization.options[v], true, nil, nil, nil, mod, v, nil, true)
			elseif mod.Options[v .. "SWSound"] then
				catbutton = catpanel:CreateCheckButton(mod.localization.options[v], true, nil, nil, nil, mod, v)
			else
				catbutton = catpanel:CreateCheckButton(mod.localization.options[v], true)
			end
			catbutton:SetScript("OnShow", function(self)
				self:SetChecked(mod.Options[v])
			end)
			catbutton:SetScript("OnClick", function()
				mod.Options[v] = not mod.Options[v]
				if mod.optionFuncs and mod.optionFuncs[v] then
					mod.optionFuncs[v]()
				end
			end)
		elseif mod.dropdowns and mod.dropdowns[v] then
			local dropdownOptions = {}
			for _, val in ipairs(mod.dropdowns[v]) do
				tinsert(dropdownOptions, {
					text	= mod.localization.options[val],
					value	= val
				})
			end
			--title, values, vartype, var, callfunc, width, height, parent
			catbutton = catpanel:CreateDropdown(mod.localization.options[v], dropdownOptions, mod, v, function(value)
				mod.Options[v] = value
				if mod.optionFuncs and mod.optionFuncs[v] then
					mod.optionFuncs[v]()
				end
			end, nil, 40)
			if not addSpacer then
				if lastButton then
					catbutton:SetPoint("TOPLEFT", lastButton, "BOTTOMLEFT", 0, -12)
				else
					catbutton:SetPoint("TOPLEFT", catpanel:GetLastObj(), "BOTTOMLEFT", -14, -18)
				end
			end
		end
		if addSpacer then
			catbutton:SetPoint("TOPLEFT", lastButton, "BOTTOMLEFT", 0, -6)
			addSpacer = false
		end
	end
end

local isFirstModPanel = true
---@param mod DBMMod
function DBM_GUI:CreateBossModPanel(mod, isTestView)
	local panel = isTestView and mod.testPanel or mod.panel
	if not panel then
		DBM:AddMsg("Couldn't create boss mod panel for " .. mod.localization.general.name)
		return false
	end

	local extraOffset = 0
	if isTestView then
		extraOffset = extraOffset + DBM_GUI:AddModTestOptionsAbove(panel, mod)
	end
	if not DBM:IsPostMidnight() then
		local iconstat = panel.frame:CreateFontString("DBM_GUI_Mod_Icons" .. mod.localization.general.name, "ARTWORK")
		iconstat:SetPoint("TOP", panel.frame, 0, -10 - extraOffset)
		iconstat:SetFontObject(GameFontNormal)
		iconstat:SetText(L.IconsInUse)
		for i = 1, 8 do
			local icon = panel.frame:CreateTexture()
			icon:SetTexture(137009) -- "Interface\\TargetingFrame\\UI-RaidTargetingIcons.blp"
			icon:SetPoint("TOP", panel.frame, 81 - (i * 18), -26 - extraOffset)
			icon:SetSize(16, 16)
			if not mod.usedIcons or not mod.usedIcons[i] then
				icon:SetAlpha(0.25)
				icon:SetDesaturated(true)
			end
			if		i == 1 then		icon:SetTexCoord(0,		0.25,	0,		0.25)
			elseif	i == 2 then		icon:SetTexCoord(0.25,	0.5,	0,		0.25)
			elseif	i == 3 then		icon:SetTexCoord(0.5,	0.75,	0,		0.25)
			elseif	i == 4 then		icon:SetTexCoord(0.75,	1,		0,		0.25)
			elseif	i == 5 then		icon:SetTexCoord(0,		0.25,	0.25,	0.5)
			elseif	i == 6 then		icon:SetTexCoord(0.25,	0.5,	0.25,	0.5)
			elseif	i == 7 then		icon:SetTexCoord(0.5,	0.75,	0.25,	0.5)
			elseif	i == 8 then		icon:SetTexCoord(0.75,	1,		0.25,	0.5)
--			elseif	i == 9 then		icon:SetTexCoord(0,		0.25,	0.5,	0.75)
--			elseif	i == 10 then	icon:SetTexCoord(0.25,	0.5,	0.5,	0.75)
--			elseif	i == 11 then	icon:SetTexCoord(0.5,	0.75,	0.5,	0.75)
--			elseif	i == 12 then	icon:SetTexCoord(0.75,	1,		0.5,	0.75)
--			elseif	i == 13 then	icon:SetTexCoord(0,		0.25,	0.75,	1)
--			elseif	i == 14 then	icon:SetTexCoord(0.25,	0.5,	0.75,	1)
--			elseif	i == 15 then	icon:SetTexCoord(0.5,	0.75,	0.75,	1)
--			elseif	i == 16 then	icon:SetTexCoord(0.75,	1,		0.75,	1)
			end
		end
	end

	local reset = panel:CreateButton(L.Mod_Reset, 155, 28, nil, GameFontNormalSmall)
	reset.myheight = 40
	reset:SetPoint("TOPRIGHT", panel.frame, "TOPRIGHT", -24, -2 - extraOffset)
	reset:SetScript("OnClick", function()
		DBM:LoadModDefaultOption(mod)
	end)
	if not isTestView and not DBM:IsPostMidnight() then
		local playground = panel:CreateButton(L.EnterTestMode, 155, 28, nil, GameFontNormalSmall)
		playground.myheight = 0
		playground:SetPoint("TOPLEFT", reset, "BOTTOMLEFT", 0, -2)
		playground:SetScript("OnClick", function()
			mod.showTestUI = true
			DBM_GUI:UpdateModList()
			DBM_GUI_OptionsFrame:LoadAndShowFrame(mod.testPanel.frame)
		end)
	end
	local button = panel:CreateCheckButton(L.Mod_Enabled:format("|n|cFFFFFFFF" .. mod.localization.general.name), true)
	button:SetChecked(mod.Options.Enabled)
	button:SetPoint("TOPLEFT", panel.frame, "TOPLEFT", 8, -14 - extraOffset)
	button:SetScript("OnClick", function()
		mod:Toggle()
	end)

	if mod.addon then
		for spellID, options in getmetatable(mod.groupOptions).__pairs(mod.groupOptions) do
			if spellID:find("^line") then
				panel:CreateLine(options)
			else
				local title, desc, _, icon
				local usedSpellID, hasPrivate
				if mod.groupOptions[spellID] and mod.groupOptions[spellID].customKeys then
					usedSpellID = mod.groupOptions[spellID].customKeys--Color coding would be done in customKeys, not here
				end
				if mod.groupOptions[spellID] and mod.groupOptions[spellID].hasPrivate then
					hasPrivate = true
				end
				if mod.groupOptions[spellID].title then--Custom title, it's a bogus spellId, so we completely ignore it and bundle with localized custom title
					title, desc, icon = mod.groupOptions[spellID].title, L.CustomOptions, 136116
				elseif tonumber(spellID) then
					spellID = tonumber(spellID)
					if spellID then--Because LuaLS doesn't understand tonumber(spellID) as a nil check
						if spellID < 0 then
							title, desc, _, icon = DBM:EJ_GetSectionInfo(-spellID)
						else
							local _title = DBM:GetSpellName(spellID)
							if _title then
								title, desc, icon = _title, tonumber(spellID), DBM:GetSpellTexture(spellID or 0)
							end
						end
					end
				elseif spellID:find("^ej") then
					title, desc, _, icon = DBM:EJ_GetSectionInfo(spellID:gsub("ej", ""))
				elseif spellID:find("^at") then
					_, title, _, _, _, _, _, desc, _, icon = GetAchievementInfo(spellID:gsub("at", ""))
					if not title then--Core has debug for spell and EJ, but this calls WoW API directly
						DBM:Debug("|cffff0000Invalid call to GetAchievementInfo for achievementID: |r"..spellID:gsub("at", ""))
					end
				end
				if not title then--Spell/EJ section/achievement not found - typo/removed/ptr or beta mod on live
					title, desc, icon = spellID, L.NoDescription, 136116
				end
				if not usedSpellID then
					usedSpellID = "|Hgarrmission:DBM:wacopy:"..spellID.."|h|cff69ccf0"..spellID.."|r|h"
				end
				local catpanel = panel:CreateAbility(title, icon, usedSpellID, hasPrivate)
				if desc then
					catpanel:CreateSpellDesc(desc)
				end
				catbutton, lastButton, addSpacer = nil, nil, nil
				for _, v in ipairs(options) do
					addOptions(mod, catpanel, v)
				end
			end
		end
	end

	local scannedCategories = {}
	for _, catident in pairs(mod.categorySort) do
		local category = mod.optionCategories[catident]
		if not scannedCategories[catident] and category then
			scannedCategories[catident] = true
			local catpanel = panel:CreateArea(mod.localization.cats[catident])
			catbutton, lastButton, addSpacer = nil, nil, nil
			for _, v in ipairs(category) do
				addOptions(mod, catpanel, v)
			end
		end
	end
	-- For some reason the options aren't loaded in properly if the very first mod view you load is a test view
	-- But just forcing a call to show fixes this
	if isFirstModPanel and isTestView then
		DBM_GUI:ShowHide(true)
	end
	isFirstModPanel = true
end

local function GetSpecializationGroup()
	if isRetail then
		return GetSpecialization() or 1
	else
		local numTabs = GetNumTalentTabs()
		local highestPointsSpent, currentSpecGroup = 0, 1
		if MAX_TALENT_TABS then
			for i=1, MAX_TALENT_TABS do
				if ( i <= numTabs ) then
					local _, _, _, _, pointsSpent = GetTalentTabInfo(i)
					if pointsSpent > highestPointsSpent then
						highestPointsSpent = pointsSpent
						currentSpecGroup = i
					end
				end
			end
		end
		return currentSpecGroup
	end
end

function DBM_GUI:CreateBossModTab(addon, panel, subtab)
	if not panel then
		error("Panel is nil", 2)
	end

	if panel.loadButton then
		panel.loadButton:Hide()
		panel.loadButton.headline:Hide()
	end

	local modProfileArea
	if not subtab then
		local modProfileDropdown = {}
		modProfileArea = panel:CreateArea(L.Area_ModProfile)
		modProfileArea.frame:SetPoint("TOPLEFT", 10, -25)
		local resetButton = modProfileArea:CreateButton(L.ModAllReset, 200, 20)
		resetButton:SetPoint("TOPLEFT", 10, -14)
		resetButton:SetScript("OnClick", function()
			DBM:LoadAllModDefaultOption(addon.modId)
		end)
		for charname, charTable in pairs(_G[addon.modId:gsub("-", "") .. "_AllSavedVars"] or {}) do
			for _, optionTable in pairs(charTable) do
				if type(optionTable) == "table" then
					for i = 0, 4 do
						if optionTable[i] then
							tinsert(modProfileDropdown, {
								text	= (i == 0 and charname .. " (" .. ALL.. ")") or charname .. " (" .. SPECIALIZATION .. i .. "-" .. (charTable["talent" .. i] or "") .. ")",
								value	= charname .. "|" .. tostring(i)
							})
						end
					end
					break
				end
			end
		end

		local resetStatButton = modProfileArea:CreateButton(L.ModAllStatReset, 200, 20)
		resetStatButton.myheight = 0
		resetStatButton:SetPoint("LEFT", resetButton, "RIGHT", 40, 0)
		resetStatButton:SetScript("OnClick", function()
			DBM:ClearAllStats(addon.modId)
		end)

		local refresh

		local copyModProfile = modProfileArea:CreateDropdown(L.SelectModProfileCopy, modProfileDropdown, nil, nil, function(value)
			local name, profile = strsplit("|", value)
			DBM:CopyAllModOption(addon.modId, name, tonumber(profile))
			C_Timer.After(0.05, refresh)
		end, 100)
		local isNewDropdown = copyModProfile.mytype == "dropdown2"
		copyModProfile:SetPoint("TOPLEFT", isNewDropdown and 15 or -7, -54)
		copyModProfile:SetScript("OnShow", function()
			copyModProfile.value = nil
			copyModProfile.text = nil
			if not isNewDropdown then
				_G[copyModProfile:GetName() .. "Text"]:SetText("")
			end
		end)

		local copyModSoundProfile = modProfileArea:CreateDropdown(L.SelectModProfileCopySound, modProfileDropdown, nil, nil, function(value)
			local name, profile = strsplit("|", value)
			DBM:CopyAllModTypeOption(addon.modId, name, tonumber(profile), "SWSound")
			C_Timer.After(0.05, refresh)
		end, 100)
		copyModSoundProfile.myheight = 0
		copyModSoundProfile:SetPoint("LEFT", copyModProfile, "RIGHT", 27, 0)
		copyModSoundProfile:SetScript("OnShow", function()
			copyModSoundProfile.value = nil
			copyModSoundProfile.text = nil
			if not isNewDropdown then
				_G[copyModSoundProfile:GetName() .. "Text"]:SetText("")
			end
		end)

		local copyModNoteProfile = modProfileArea:CreateDropdown(L.SelectModProfileCopyNote, modProfileDropdown, nil, nil, function(value)
			local name, profile = strsplit("|", value)
			DBM:CopyAllModTypeOption(addon.modId, name, tonumber(profile), "SWNote")
			C_Timer.After(0.05, refresh)
		end, 100)
		copyModNoteProfile.myheight = 0
		copyModNoteProfile:SetPoint("LEFT", copyModSoundProfile, "RIGHT", 27, 0)
		copyModNoteProfile:SetScript("OnShow", function()
			copyModNoteProfile.value = nil
			copyModNoteProfile.text = nil
			if not isNewDropdown then
				_G[copyModNoteProfile:GetName() .. "Text"]:SetText("")
			end
		end)

		local deleteModProfile = modProfileArea:CreateDropdown(L.SelectModProfileDelete, modProfileDropdown, nil, nil, function(value)
			local name, profile = strsplit("|", value)
			DBM:DeleteAllModOption(addon.modId, name, tonumber(profile))
			C_Timer.After(0.05, refresh)
		end, 100)
		deleteModProfile.myheight = 60
		deleteModProfile:SetPoint("TOPLEFT", copyModSoundProfile, "BOTTOMLEFT", 0, isNewDropdown and -15 or -10)
		deleteModProfile:SetScript("OnShow", function()
			deleteModProfile.value = nil
			deleteModProfile.text = nil
			if not isNewDropdown then
				_G[deleteModProfile:GetName() .. "Text"]:SetText("")
			end
		end)

		function refresh()
			copyModProfile:GetScript("OnShow")()
			copyModSoundProfile:GetScript("OnShow")()
			copyModNoteProfile:GetScript("OnShow")()
			deleteModProfile:GetScript("OnShow")()
		end

		-- Start import/export
		local function actuallyImport(importTable)
			local profileID = playerLevel > 9 and DBM_UseDualProfile and GetSpecializationGroup() or 0
			for _, id in ipairs(DBM.ModLists[addon.modId]) do
				_G[addon.modId:gsub("-", "") .. "_AllSavedVars"][playerName .. "-" .. realmName][id][profileID] = importTable[id]
				---@diagnostic disable-next-line: inject-field
				DBM:GetModByName(id).Options = importTable[id]
			end
			DBM:AddMsg("Profile imported.")
		end

		local importExportProfilesArea = panel:CreateArea(L.Area_ImportExportProfile)
		local importExportText = importExportProfilesArea:CreateText(L.ImportExportInfo, nil, true)
		local exportProfile = importExportProfilesArea:CreateButton(L.ButtonExportProfile, 120, 20, function()
			local exportProfile = {}
			local profileID = playerLevel > 9 and DBM_UseDualProfile and GetSpecializationGroup() or 0
			for _, id in ipairs(DBM.ModLists[addon.modId]) do
				exportProfile[id] = _G[addon.modId:gsub("-", "") .. "_AllSavedVars"][playerName .. "-" .. realmName][id][profileID]
			end
			DBM_GUI:CreateExportProfile(exportProfile)
		end)
		exportProfile:SetPoint("TOPLEFT", importExportText, "BOTTOMLEFT", 0, -12)
		local importProfile = importExportProfilesArea:CreateButton(L.ButtonImportProfile, 120, 20, function()
			DBM_GUI:CreateImportProfile(function(importTable)
				local errors = {}
				for id, table in pairs(importTable) do
					-- Check if sound packs are missing
					for settingName, settingValue in pairs(table) do
						local ending = settingName:sub(-6):lower()
						if ending == "cvoice" or ending == "wsound" then -- CVoice or SWSound (s is ignored so we only have to sub once)
							if type(settingValue) == "string" and settingValue:lower() ~= "none" and not DBM:ValidateSound(settingValue, true, true) then
								tinsert(errors, id .. "-" .. settingName)
							end
						end
					end
				end
				-- Create popup confirming if they wish to continue (and therefor resetting to default)
				if #errors > 0 then
					local popup = StaticPopup_Show("IMPORTPROFILE_ERROR")
					if popup then
						popup.importFunc = function()
							local modOptions = {}
							for _, soundSetting in ipairs(errors) do
								local modID, settingName = soundSetting:match("([^-]+)-([^-]+)")
								if not modOptions[modID] then
									modOptions[modID] = DBM:GetModByName(modID).DefaultOptions
								end
								importTable[modID][settingName] = modOptions[modID][settingName]
							end
							actuallyImport(importTable)
						end
					end
				else
					actuallyImport(importTable)
				end
			end)
		end)
		importProfile.myheight = 12
		importProfile:SetPoint("LEFT", exportProfile, "RIGHT", 2, 0)
	end

	if addon.noStatistics then
		return
	end

	local ptext = panel:CreateText(L.BossModLoaded:format(subtab and addon.subTabs[subtab] or addon.name), nil, nil, nil, "CENTER")
	ptext:SetPoint("TOPLEFT", panel.frame, "TOPLEFT", 10, modProfileArea and -255 or -10)

	local singleLine, doubleLine, noHeaderLine = 0, 0, 0
	local area = panel:CreateArea()
	area.frame.isStats = true
	area.frame:SetPoint("TOPLEFT", 10, modProfileArea and -270 or -25)

	local statOrder = {
		"follower", "story", "lfr", "normal", "normal25", "heroic", "heroic25", "mythic", "challenge", "timewalker", "duos"
	}

	for _, mod in ipairs(DBM.Mods) do
		if mod.modId == addon.modId and (not subtab or subtab == mod.subTab) and not mod.isTrashMod and not mod.noStatistics then
			if not mod.stats then
				local defaultStats = DBM:CreateDefaultModStats()
				mod.stats = defaultStats or {}
			end

			--Create Frames
			local statSplit, statCount = {}, 0
			for stat in (mod.statTypes or mod.addon.statTypes):gmatch("%s?([^%s,]+)%s?,?") do
				statSplit[stat] = true
				statCount = statCount + 1
			end

			if statCount == 0 then
				DBM:AddMsg("No statTypes available for " .. mod.modId)
				return -- No stats available for this? Possibly a bug
			end

			local Title			= area:CreateText(mod.localization.general.name, nil, nil, GameFontHighlight)

			local function CreateText(text, header)
				local frame = area:CreateText(text or "", nil, nil, header and GameFontHighlightSmall or GameFontNormalSmall)
				frame:Hide()
				return frame
			end

			local sections = {}
			for i = 1, 6 do
				local section = {}
				section.header	= CreateText(nil, true)
				section.text1	= CreateText(L.Statistic_Kills)
				section.text2	= CreateText(L.Statistic_Wipes)
				section.text3	= CreateText(L.Statistic_BestKill)
				section.value1	= CreateText()
				section.value2	= CreateText()
				section.value3	= CreateText()
				if i == 1 then
					section.header:SetPoint("TOPLEFT", Title, "BOTTOMLEFT", 20, -5)
				elseif i == 4 then
					section.header:SetPoint("TOPLEFT", sections[1].text3, "BOTTOMLEFT", -20, -5)
				else
					section.header:SetPoint("LEFT", sections[i - 1].header, "LEFT", 150, 0)
				end
				section.text1:SetPoint("TOPLEFT", section.header, "BOTTOMLEFT", 20, -5)
				section.text2:SetPoint("TOPLEFT", section.text1, "BOTTOMLEFT", 0, -5)
				section.text3:SetPoint("TOPLEFT", section.text2, "BOTTOMLEFT", 0, -5)
				section.value1:SetPoint("TOPLEFT", section.text1, "TOPLEFT", 80, 0)
				section.value2:SetPoint("TOPLEFT", section.text2, "TOPLEFT", 80, 0)
				section.value3:SetPoint("TOPLEFT", section.text3, "TOPLEFT", 80, 0)
				section.header.OldSetText = section.header.SetText
				section.header.SetText = function(self, text)
					self:OldSetText(text)
					self:Show()
					section.text1:Show()
					section.text2:Show()
					section.text3:Show()
					section.value1:Show()
					section.value2:Show()
					section.value3:Show()
				end
				sections[i] = section
			end

			local statTypes = {
				follower	= DBM_CORE_L.FOLLOWER,--no PLAYER_DIFFICULTY entry yet
				story		= DBM_CORE_L.STORY,--no PLAYER_DIFFICULTY entry yet
				lfr25		= PLAYER_DIFFICULTY3,
				normal		= mod.addon.minExpansion < 5 and not DBM:IsSeasonal("SeasonOfDiscovery") and RAID_DIFFICULTY1 or PLAYER_DIFFICULTY1,
				normal25	= RAID_DIFFICULTY2,
				heroic		= mod.addon.minExpansion < 5 and not DBM:IsSeasonal("SeasonOfDiscovery") and RAID_DIFFICULTY3 or PLAYER_DIFFICULTY2,
				heroic25	= RAID_DIFFICULTY4,
				mythic		= PLAYER_DIFFICULTY6,
				challenge	= (mod.addon.minExpansion < 6 and not mod.upgradedMPlus) and CHALLENGE_MODE or PLAYER_DIFFICULTY6 .. "+",
				timewalker	= PLAYER_DIFFICULTY_TIMEWALKER,
				duos		= DBM_CORE_L.DUOS
			}
			if (mod.addon.type == "PARTY" or mod.addon.type == "SCENARIO") or -- Fixes dungeons being labled incorrectly
				(mod.addon.type == "RAID" and statSplit["timewalker"]) or -- Fixes raids with timewalker being labled incorrectly
				(mod.instanceId == 369) then -- Fixes SoO being labled incorrectly
				statTypes.normal = PLAYER_DIFFICULTY1
				statTypes.heroic = PLAYER_DIFFICULTY2
			end

			local lastArea = 0

			for _, statType in ipairs(statOrder) do
				if statSplit[statType] then
					if statType == "lfr" then
						statType = "lfr25" -- Because Myst stores stats weird
					end
					if lastArea == 2 and statCount == 4 then -- Use top1, top2, bottom1, bottom2
						lastArea = 3
					end
					lastArea = lastArea + 1
					local section = sections[lastArea]
					section.header:SetText(statTypes[statType])
					local kills, pulls, bestRank, bestTime = mod.stats[statType .. "Kills"] or 0, mod.stats[statType .. "Pulls"] or 0, mod.stats[statType .. "BestRank"] or 0, mod.stats[statType .. "BestTime"]
					section.value1:SetText(kills)
					section.value2:SetText(pulls - kills)
					if bestRank > 0 then--Used by "challenge" and "normal" for M+, Delves, and SoD raids
						section.value3:SetText(bestTime and ("%d:%02d (%d)"):format(mfloor(bestTime / 60), bestTime % 60, bestRank) or "-")
					else
						section.value3:SetText(bestTime and ("%d:%02d"):format(mfloor(bestTime / 60), bestTime % 60) or "-")
					end
				end
			end
			Title:SetPoint("TOPLEFT", area.frame, "TOPLEFT", 10, -10 - (L.FontHeight * 5 * noHeaderLine) - (L.FontHeight * 6 * singleLine) - (L.FontHeight * 10 * doubleLine))
			if statCount == 1 then
				sections[1].header:Hide()
				sections[1].text1:SetPoint("TOPLEFT", Title, "BOTTOMLEFT", 20, -5)
				noHeaderLine = noHeaderLine + 1
				area.frame:SetHeight(area.frame:GetHeight() + L.FontHeight * 5)
			elseif statCount < 4 then
				singleLine = singleLine + 1
				area.frame:SetHeight(area.frame:GetHeight() + L.FontHeight * 6)
			else
				doubleLine = doubleLine + 1
				area.frame:SetHeight(area.frame:GetHeight() + L.FontHeight * 10)
			end
		end
	end
end
do
	local subTabId = 0

	local currentSeasons = {}
	function UpdateCurrentSeason()
		if not C_ChallengeMode or not C_ChallengeMode.GetMapTable then
			return
		end
		local seasonCategory = DBM_GUI:CreateNewPanel(L.TabCategory_CURRENT_SEASON, "PARTY")
		local seasonCategoryTab = DBM_GUI.tabs[3].buttons[#DBM_GUI.tabs[3].buttons]
		local hasAnyMod = false
		for _, challengeMap in ipairs(C_ChallengeMode.GetMapTable()) do
			local challengeMode = challengeModeIds[challengeMap]
			local id = challengeMode
			--For handling zones like Warfront: Arathi - Alliance
			local mapName = GetRealZoneText(id):trim() or id
			for w in string.gmatch(mapName, " - ") do
				if w:trim() ~= "" then
					mapName = w
					break
				end
			end
			if not currentSeasons[mapName] then
				local modId
				for _, addon in ipairs(DBM.AddOns) do
					if addon.modId ~= "DBM-Affixes" and addon.type == "PARTY" then
						for _, mapId in ipairs(addon.mapId) do
							if mapId == id then
								modId = addon.modId
								break
							end
						end
					end
				end
				if modId then
					currentSeasons[mapName] = seasonCategory:CreateNewPanel(mapName, "PARTY", false, nil, true, modId, true)
					hasAnyMod = true
				end
			end
		end
		if C_AddOns.DoesAddOnExist("DBM-Affixes") then
			local affixAddon
			for _, addon in ipairs(DBM.AddOns) do
				if addon.modId == "DBM-Affixes" then
					affixAddon = addon
					break
				end
			end
			if affixAddon then
				currentSeasons["MPlusAffixes"] = seasonCategory:CreateNewPanel("MPlusAffixes", "PARTY", false, affixAddon.name, false, "DBM-Affixes", true)
				hasAnyMod = true
			end
		end
		if not hasAnyMod then
			seasonCategoryTab.hidden = true
		end
	end

    local expansions = {"CLASSIC", "BC", "WOTLK", "CATA", "MOP", "WOD", "LEG", "BFA", "SHADOWLANDS", "DRAGONFLIGHT", "WARWITHIN", "MIDNIGHT"}

	function DBM_GUI:UpdateModList()
		for _, addon in ipairs(DBM.AddOns) do
			if not addon.panel then
				local customName
				--Auto truncate Raid, Dungeon, and World boss mods to only display expansion name in list
				if addon.type == "RAID" or addon.type == "PARTY" or addon.type == "WORLDBOSS" then
					customName = _G["EXPANSION_NAME" .. (tIndexOf(expansions, addon.category:upper()) or 99) - 1]
				end
				-- Create a Panel for "Naxxramas" "Eye of Eternity" ...
				addon.panel = DBM_GUI:CreateNewPanel(addon.name or "Error: No-modId", addon.type, false, customName, true, addon.modId)
				if addon.modId == "DBM-Affixes" then -- If affixes, hide second general entry (as it's under Current Season)
					DBM_GUI.tabs[3].buttons[#DBM_GUI.tabs[3].buttons].hidden = true
				end

				if not C_AddOns.IsAddOnLoaded(addon.modId) then
					local autoLoadFrame = CreateFrame("Frame", nil, addon.panel.frame)
					autoLoadFrame:SetScript("OnShow", function()
						if not addon.attemptedAutoLoad then
							addon.attemptedAutoLoad = true
							DBM:LoadMod(addon, true)
						end
					end)
					local button = addon.panel:CreateButton(L.Button_LoadMod, 200, 30)
					button.addon = addon
					button.headline = addon.panel:CreateText(L.BossModLoad_now, 350, nil, nil, "CENTER")
					button.headline:SetHeight(50)
					button.headline:SetPoint("CENTER", button, "CENTER", 0, 80)

					button:SetScript("OnClick", function(self)
						DBM:LoadMod(self.addon, true)
					end)
					button:SetPoint("CENTER", 0, -20)
					---@diagnostic disable-next-line: inject-field
					addon.panel.loadButton = button
				else
					DBM_GUI:CreateBossModTab(addon, addon.panel)
				end
			end

			if addon.panel and addon.subTabs and C_AddOns.IsAddOnLoaded(addon.modId) then
				if not addon.subPanels then
					addon.subPanels = {}
				end

				for k, v in pairs(addon.subTabs) do
					if not addon.subPanels[k] then
						subTabId = subTabId + 1
						addon.subPanels[k] = currentSeasons[v] or addon.panel:CreateNewPanel("SubTab" .. subTabId, addon.type, false, v, addon.modId)
						DBM_GUI:CreateBossModTab(addon, addon.subPanels[k], k)
					end
				end
			end

			for _, v in ipairs(DBM.Mods) do
				---@class DBMMod
				local mod = v
				if mod.modId == addon.modId then
					if not addon.subTabs or (addon.subPanels and (addon.subPanels[mod.subTab] or mod.subTab == 0)) then
						if addon.subTabs and addon.subPanels[mod.subTab] then
							mod.panel = mod.panel or addon.subPanels[mod.subTab]:CreateNewPanel(mod.id or "Error: DBM.Mods", addon.type, nil, mod.localization.general.name)
							if mod.showTestUI then
								mod.testPanel = mod.testPanel or addon.subPanels[mod.subTab]:CreateNewPanel(mod.id or "Error: DBM.Mods", addon.type, nil, L.TestModEntry:format(mod.localization.general.name), nil, nil, nil, true)
							end
						else
							mod.panel = mod.panel or currentSeasons[mod.id] or addon.panel:CreateNewPanel(mod.id or "Error: DBM.Mods", addon.type, nil, mod.localization.general.name)
							if mod.showTestUI then
								mod.testPanel = mod.testPanel or currentSeasons[mod.id] or addon.panel:CreateNewPanel(mod.id or "Error: DBM.Mods", addon.type, nil, L.TestModEntry:format(mod.localization.general.name), nil, nil, nil, true)
							end
						end
					end
				end
			end
		end
		local optionsFrame = _G["DBM_GUI_OptionsFrame"]
		if optionsFrame:IsShown() then
			optionsFrame:Hide()
			optionsFrame:Show()
		end
	end
end
