local _, private = ...

local isRetail = WOW_PROJECT_ID == (WOW_PROJECT_MAINLINE or 1)

local L = DBM_CORE_L

local LibStub = _G["LibStub"]
local LibLatency, LibDurability = LibStub("LibLatency", true), LibStub("LibDurability", true)

local function Pull(timer)
	local LFGTankException = isRetail and IsPartyLFG() and UnitGroupRolesAssigned("player") == "TANK"--Tanks in LFG need to be able to send pull timer even if someone refuses to pass lead. LFG locks roles so no one can abuse this.
	if (DBM:GetRaidRank() == 0 and IsInGroup() and not LFGTankException) or select(2, IsInInstance()) == "pvp" or IsEncounterInProgress() then
		return DBM:AddMsg(L.ERROR_NO_PERMISSION)
	end
	if timer > 0 and timer < 3 then
		return DBM:AddMsg(L.TIME_TOO_SHORT)
	end
	local targetName = (UnitExists("target") and UnitIsEnemy("player", "target")) and UnitName("target") or nil--Filter non enemies in case player isn't targetting bos but another player/pet
	if targetName then
		private.sendSync("PT", timer .. "\t" .. DBM:GetCurrentArea() .. "\t" .. targetName)
	else
		private.sendSync("PT", timer .. "\t" .. DBM:GetCurrentArea())
	end
end

local function Break(timer)
	if IsInGroup() and (DBM:GetRaidRank() == 0 or (isRetail and IsPartyLFG())) or IsEncounterInProgress() or select(2, IsInInstance()) == "pvp" then--No break timers if not assistant or if it's dungeon/raid finder/BG
		DBM:AddMsg(L.ERROR_NO_PERMISSION)
		return
	end
	if timer > 60 then
		DBM:AddMsg(L.BREAK_USAGE)
		return
	end
	private.sendSync("BT", timer * 60)
end

local ShowLag, ShowDurability
do
	local tconcat, tinsert, tsort = table.concat, table.insert, table.sort

	local function SortLag(v1, v2)
		return (v1.worldlag or 0) < (v2.worldlag or 0)
	end

	function ShowLag()
		local sortLag, noLagResponse = {}, {}
		for _, v in pairs(DBM:GetRaidRoster()) do
			tinsert(sortLag, v)
		end
		tsort(sortLag, SortLag)
		DBM:AddMsg(L.LAG_HEADER)
		for _, v in ipairs(sortLag) do
			local name = v.name
			local playerColor = RAID_CLASS_COLORS[DBM:GetRaidClass(name)]
			if playerColor then
				name = ("|r|cff%.2x%.2x%.2x%s|r|cff%.2x%.2x%.2x"):format(playerColor.r * 255, playerColor.g * 255, playerColor.b * 255, name, 0.41 * 255, 0.8 * 255, 0.94 * 255)
			end
			if v.worldlag then
				DBM:AddMsg(L.LAG_ENTRY:format(name, v.worldlag, v.homelag), false)
			else
				tinsert(noLagResponse, v.name)
			end
		end
		if #noLagResponse > 0 then
			DBM:AddMsg(L.LAG_FOOTER:format(tconcat(noLagResponse, ", ")), false)
		end
	end

	if LibLatency then
		LibLatency:Register("DBM", function(homelag, worldlag, sender)
			if not sender then
				return
			end
			local player = DBM:GetRaidRoster()[sender]
			if player then
				player.homelag = homelag
				player.worldlag = worldlag
			end
		end)
	end

	local function SortDurability(v1, v2)
		return (v1.durpercent or 0) < (v2.durpercent or 0)
	end

	function ShowDurability()
		local sortDur, noDurResponse = {}, {}
		for _, v in pairs(DBM:GetRaidRoster()) do
			tinsert(sortDur, v)
		end
		tsort(sortDur, SortDurability)
		DBM:AddMsg(L.DUR_HEADER)
		for _, v in ipairs(sortDur) do
			local name = v.name
			local playerColor = RAID_CLASS_COLORS[DBM:GetRaidClass(name)]
			if playerColor then
				name = ("|r|cff%.2x%.2x%.2x%s|r|cff%.2x%.2x%.2x"):format(playerColor.r * 255, playerColor.g * 255, playerColor.b * 255, name, 0.41 * 255, 0.8 * 255, 0.94 * 255)
			end
			if v.durpercent then
				DBM:AddMsg(L.DUR_ENTRY:format(name, v.durpercent, v.durbroken), false)
			else
				tinsert(noDurResponse, v.name)
			end
		end
		if #noDurResponse > 0 then
			DBM:AddMsg(L.LAG_FOOTER:format(tconcat(noDurResponse, ", ")), false)
		end
	end

	if LibDurability then
		LibDurability:Register("DBM", function(percent, broken, sender)
			if not sender then
				return
			end
			local player = DBM:GetRaidRoster()[sender]
			if player then
				player.durpercent = percent
				player.durbroken = broken
			end
		end)
	end
end

if not _G["BigWigs"] then
	--Register pull and break slash commands for BW converts, if BW isn't loaded
	--This shouldn't raise an issue since BW SHOULD load before DBM in any case they are both present.
	SLASH_DEADLYBOSSMODSPULL1 = "/pull"
	SlashCmdList["DEADLYBOSSMODSPULL"] = function(msg)
		Pull(tonumber(msg) or 10)
	end
	SLASH_DEADLYBOSSMODSBREAK1 = "/break"
	SlashCmdList["DEADLYBOSSMODSBREAK"] = function(msg)
		Break(tonumber(msg) or 10)
	end
end

SLASH_DEADLYBOSSMODSRPULL1 = "/rpull"
SlashCmdList["DEADLYBOSSMODSRPULL"] = function()
	Pull(30)
end

local trackedHudMarkers = {}
SLASH_DEADLYBOSSMODS1 = "/dbm"
SlashCmdList["DEADLYBOSSMODS"] = function(msg)
	local cmd = msg:lower()
	if cmd == "ver" or cmd == "version" then
		DBM:ShowVersions(false)
	elseif cmd == "ver2" or cmd == "version2" then
		DBM:ShowVersions(true)
	elseif cmd == "unlock" or cmd == "move" then
		DBT:ShowMovableBar()
	elseif cmd == "help2" then
		for _, v in ipairs(L.SLASHCMD_HELP2) do
			DBM:AddMsg(v)
		end
	elseif cmd == "help" then
		for _, v in ipairs(L.SLASHCMD_HELP) do
			DBM:AddMsg(v)
		end
	elseif cmd:sub(1, 13) == "timer endloop" then
		DBM:CreatePizzaTimer(time, "", nil, nil, nil, true)
	elseif cmd:sub(1, 5) == "timer" then
		local time, text = msg:match("^%w+ ([%d:]+) (.+)$")
		if not time and not text then
			for _, v in ipairs(L.TIMER_USAGE) do
				DBM:AddMsg(v)
			end
			return
		end
		local min, sec = string.split(":", time)
		min = tonumber(min or "") or 0
		sec = tonumber(sec or "")
		if min and not sec then
			sec = min
			min = 0
		end
		DBM:CreatePizzaTimer(min * 60 + sec, text)
	elseif cmd:sub(1, 6) == "ltimer" then
		local time, text = msg:match("^%w+ ([%d:]+) (.+)$")
		if not time and not text then
			DBM:AddMsg(L.PIZZA_ERROR_USAGE)
			return
		end
		local min, sec = string.split(":", time)
		min = tonumber(min or "") or 0
		sec = tonumber(sec or "")
		if min and not sec then
			sec = min
			min = 0
		end
		DBM:CreatePizzaTimer(min * 60 + sec, text, nil, nil, true)
	elseif cmd:sub(1, 15) == "broadcast timer" then--Standard Timer
		local difficultyIndex = DBM:GetCurrentDifficulty()
		local permission = true
		if DBM:GetRaidRank() == 0 or difficultyIndex == 7 or difficultyIndex == 17 then
			DBM:AddMsg(L.ERROR_NO_PERMISSION)
			permission = false
		end
		local time, text = msg:match("^%w+ %w+ ([%d:]+) (.+)$")
		if not time and not text then
			DBM:AddMsg(L.PIZZA_ERROR_USAGE)
			return
		end
		local min, sec = string.split(":", time)
		min = tonumber(min or "") or 0
		sec = tonumber(sec or "")
		if min and not sec then
			sec = min
			min = 0
		end
		DBM:CreatePizzaTimer(min * 60 + sec, text, permission)
	elseif cmd:sub(1, 16) == "broadcast ltimer" then
		local difficultyIndex = DBM:GetCurrentDifficulty()
		local permission = true
		if DBM:GetRaidRank() == 0 or difficultyIndex == 7 or difficultyIndex == 17 then
			DBM:AddMsg(L.ERROR_NO_PERMISSION)
			permission = false
		end
		local time, text = msg:match("^%w+ %w+ ([%d:]+) (.+)$")
		if not time and not text then
			DBM:AddMsg(L.PIZZA_ERROR_USAGE)
			return
		end
		local min, sec = string.split(":", time)
		min = tonumber(min or "") or 0
		sec = tonumber(sec or "")
		if min and not sec then
			sec = min
			min = 0
		end
		DBM:CreatePizzaTimer(min * 60 + sec, text, permission, nil, true)
	elseif cmd:sub(0,5) == "break" then
		Break(tonumber(cmd:sub(6)) or 5)
	elseif cmd:sub(1, 4) == "pull" then
		Pull(tonumber(cmd:sub(5)) or 10)
	elseif cmd:sub(1, 5) == "rpull" then
		Pull(30)
	elseif cmd:sub(1, 3) == "lag" then
		if not LibLatency then
			DBM:AddMsg(L.UPDATE_REQUIRES_RELAUNCH)
			return
		end
		LibLatency:RequestLatency()
		DBM:AddMsg(L.LAG_CHECKING)
		C_Timer.After(5, ShowLag)
	elseif cmd:sub(1, 10) == "durability" then
		if not LibDurability then
			DBM:AddMsg(L.UPDATE_REQUIRES_RELAUNCH)
			return
		end
		LibDurability:RequestDurability()
		DBM:AddMsg(L.DUR_CHECKING)
		C_Timer.After(5, ShowDurability)
	elseif cmd:sub(1, 3) == "hud" then
		if DBM:HasMapRestrictions() then
			DBM:AddMsg(L.NO_HUD)
			return
		end
		local hudType, target, duration = string.split(" ", cmd:sub(4):trim())
		if hudType == "" then
			for _, v in ipairs(L.HUD_USAGE) do
				DBM:AddMsg(v)
			end
			return
		end
		local hudDuration = tonumber(duration) or 1200 -- If no duration defined. 20 minutes to cover even longest of fights
		if type(hudType) == "string" and hudType:trim() ~= "" then
			if hudType == "hide" then
				for _, name in ipairs(trackedHudMarkers) do
					DBM.HudMap:FreeEncounterMarkerByTarget(12345, name)
				end
				table.wipe(trackedHudMarkers)
				return
			end
			if not target then
				DBM:AddMsg(L.HUD_INVALID_TARGET)
				return
			end
			local uId
			if target == "target" and UnitExists("target") then
				uId = "target"
			elseif isRetail and target == "focus" and UnitExists("focus") then
				uId = "focus"
			else -- Try to use it as player name
				uId = DBM:GetRaidUnitId(target)
			end
			if not uId then
				DBM:AddMsg(L.HUD_INVALID_TARGET)
				return
			end
			if UnitIsUnit("player", uId) and not DBM.Options.DebugMode then -- Don't allow hud to self, except if debug mode is enabled, then hud to self useful for testing
				DBM:AddMsg(L.HUD_INVALID_SELF)
				return
			end
			local targetName = UnitName(uId)
			if hudType == "arrow" then
				local playerName = UnitName("player")
				local _, targetClass = UnitClass(uId)
				local color2 = RAID_CLASS_COLORS[targetClass]
				local m1 = DBM.HudMap:RegisterRangeMarkerOnPartyMember(12345, "party", playerName, 0.1, hudDuration, 0, 1, 0, 1, nil, false):Appear()
				local m2 = DBM.HudMap:RegisterRangeMarkerOnPartyMember(12345, "party", targetName, 0.75, hudDuration, color2.r, color2.g, color2.b, 1, nil, false):Appear()
				tinsert(trackedHudMarkers, playerName)
				tinsert(trackedHudMarkers, targetName)
				m2:EdgeTo(m1, nil, hudDuration, 0, 1, 0, 1)
				DBM:AddMsg(L.HUD_SUCCESS:format(DBM:strFromTime(hudDuration)))
			elseif hudType == "dot" then
				local _, targetClass = UnitClass(uId)
				local color2 = RAID_CLASS_COLORS[targetClass]
				DBM.HudMap:RegisterRangeMarkerOnPartyMember(12345, "party", targetName, 0.75, hudDuration, color2.r, color2.g, color2.b, 1, nil, false):Appear()
				tinsert(trackedHudMarkers, targetName)
				DBM:AddMsg(L.HUD_SUCCESS:format(DBM:strFromTime(hudDuration)))
			elseif hudType == "green" then
				DBM.HudMap:RegisterRangeMarkerOnPartyMember(12345, "highlight", targetName, 3.5, hudDuration, 0, 1, 0, 0.5, nil, false):Pulse(0.5, 0.5)
				tinsert(trackedHudMarkers, targetName)
				DBM:AddMsg(L.HUD_SUCCESS:format(DBM:strFromTime(hudDuration)))
			elseif hudType == "red" then
				DBM.HudMap:RegisterRangeMarkerOnPartyMember(12345, "highlight", targetName, 3.5, hudDuration, 1, 0, 0, 0.5, nil, false):Pulse(0.5, 0.5)
				tinsert(trackedHudMarkers, targetName)
				DBM:AddMsg(L.HUD_SUCCESS:format(DBM:strFromTime(hudDuration)))
			elseif hudType == "yellow" then
				DBM.HudMap:RegisterRangeMarkerOnPartyMember(12345, "highlight", targetName, 3.5, hudDuration, 1, 1, 0, 0.5, nil, false):Pulse(0.5, 0.5)
				tinsert(trackedHudMarkers, targetName)
				DBM:AddMsg(L.HUD_SUCCESS:format(DBM:strFromTime(hudDuration)))
			elseif hudType == "blue" then
				DBM.HudMap:RegisterRangeMarkerOnPartyMember(12345, "highlight", targetName, 3.5, hudDuration, 0, 0, 1, 0.5, nil, false):Pulse(0.5, 0.5)
				tinsert(trackedHudMarkers, targetName)
				DBM:AddMsg(L.HUD_SUCCESS:format(DBM:strFromTime(hudDuration)))
			elseif hudType == "icon" then
				local icon = GetRaidTargetIndex(uId)
				if not icon then
					DBM:AddMsg(L.HUD_INVALID_ICON)
					return
				end
				DBM.HudMap:RegisterRangeMarkerOnPartyMember(12345, DBM:IconNumToString(icon):lower(), targetName, 3.5, hudDuration, 1, 1, 1, 0.5, nil, false):Pulse(0.5, 0.5)
				tinsert(trackedHudMarkers, targetName)
				DBM:AddMsg(L.HUD_SUCCESS:format(DBM:strFromTime(hudDuration)))
			else
				DBM:AddMsg(L.HUD_INVALID_TYPE)
			end
		end
	elseif cmd:sub(1, 5) == "arrow" then
		if DBM:HasMapRestrictions() then
			DBM:AddMsg(L.NO_ARROW)
			return
		end
		local x, y, z = string.split(" ", cmd:sub(6):trim())
		local xNum, yNum, zNum = tonumber(x or ""), tonumber(y or ""), tonumber(z or "")
		if xNum and yNum then
			DBM.Arrow:ShowRunTo(xNum, yNum, 0)
			return
		elseif type(x) == "string" and x:trim() ~= "" then
			local subCmd = x:trim()
			if subCmd== "hide" then
				DBM.Arrow:Hide()
				return
			elseif subCmd == "move" then
				DBM.Arrow:Move()
				return
			elseif subCmd == "target" then
				DBM.Arrow:ShowRunTo("target")
				return
			elseif isRetail and subCmd == "focus" then
				DBM.Arrow:ShowRunTo("focus")
				return
			elseif subCmd == "map" then
				DBM.Arrow:ShowRunTo(yNum, zNum, 0, nil, true)
				return
			elseif DBM:GetRaidUnitId(subCmd) then
				DBM.Arrow:ShowRunTo(subCmd)
				return
			end
		end
		for _, v in ipairs(L.ARROW_ERROR_USAGE) do
			DBM:AddMsg(v)
		end
	elseif cmd:sub(1, 10) == "debuglevel" then
		local level = tonumber(cmd:sub(11)) or 1
		if level < 1 or level > 3 then
			DBM:AddMsg("Invalid Value. Debug Level must be between 1 and 3.")
			return
		end
		DBM.Options.DebugLevel = level
		DBM:AddMsg("Debug Level is " .. level)
	elseif cmd:sub(1, 5) == "debug" then
		DBM.Options.DebugMode = not DBM.Options.DebugMode
		DBM:AddMsg("Debug Message is " .. (DBM.Options.DebugMode and "ON" or "OFF"))
		private:GetModule("DevTools"):OnDebugToggle()
	elseif cmd:sub(1, 8) == "whereiam" or cmd:sub(1, 8) == "whereami" then
		local x, y, _, map = UnitPosition("player")
		local mapID = C_Map.GetBestMapForUnit("player")
		if DBM:HasMapRestrictions() then
			DBM:AddMsg(("Location Information\nYou are at zone %u (%s).\nLocal Map ID %u (%s)"):format(map, GetRealZoneText(map), mapID, GetZoneText()))
		else
			local pos = C_Map.GetPlayerMapPosition(mapID, "player")
			DBM:AddMsg(("Location Information\nYou are at zone %u (%s): x=%f, y=%f.\nLocal Map ID %u (%s): x=%f, y=%f"):format(map, GetRealZoneText(map), x, y, mapID, GetZoneText(), pos.x, pos.y))
		end
	elseif cmd:sub(1, 7) == "request" then
		DBM:Unschedule(DBM.RequestTimers)
		DBM:RequestTimers(1)
		DBM:RequestTimers(2)
		DBM:RequestTimers(3)
	elseif cmd:sub(1, 6) == "silent" then
		DBM.Options.SilentMode = not DBM.Options.SilentMode
		DBM:AddMsg(L.SILENTMODE_IS .. (DBM.Options.SilentMode and "ON" or "OFF"))
	elseif cmd:sub(1, 10) == "musicstart" then
		DBM:TransitionToDungeonBGM(true)
	elseif cmd:sub(1, 9) == "musicstop" then
		DBM:TransitionToDungeonBGM(false, true)
	elseif cmd:sub(1, 9) == "infoframe" then
		if DBM.InfoFrame:IsShown() then
			DBM.InfoFrame:Hide()
		else
			DBM.InfoFrame:Show(5, "test")
		end
	elseif cmd:sub(1, 10) == "aggroframe" then
		if DBM.InfoFrame:IsShown() then
			DBM.InfoFrame:Hide()
		else
			DBM.InfoFrame:SetHeader(L.INFOFRAME_AGGRO)
			DBM.InfoFrame:Show(7, "playeraggro", 1)
		end
	else
		DBM:LoadGUI()
	end
end
