
local Details = 		_G.Details
local addonName, Details222 = ...
local Loc = LibStub("AceLocale-3.0"):GetLocale( "Details" )
---@framework
local detailsFramework = DetailsFramework
local _

local UnitGUID = UnitGUID
local UnitGroupRolesAssigned = DetailsFramework.UnitGroupRolesAssigned
local GetNumGroupMembers = GetNumGroupMembers
local GetSpellInfo = Details222.GetSpellInfo
local select = select
local floor = floor

local CONST_INSPECT_ACHIEVEMENT_DISTANCE = 1 --Compare Achievements, 28 yards
local CONST_SPELLBOOK_GENERAL_TABID = 1
local CONST_SPELLBOOK_CLASSSPELLS_TABID = 2

local GetItemInfo = C_Item and C_Item.GetItemInfo or GetItemInfo

local storageDebug = false --remember to turn this to false!

function Details:UpdateGears()
	Details:UpdateParser()
	Details:UpdateControl()
	Details:UpdateCombat()
end

---@alias raid_difficulty_eng_name_lowercase "normal" | "heroic" | "mythic" | "raidfinder"

--health and max heal cahces

------------------------------------------------------------------------------------------------------------

function Details:SetDeathLogLimit(limitAmount) --the temporary limit function is inside the parser as it need to deal with upvalues
	if (limitAmount and type(limitAmount) == "number" and limitAmount >= 8) then
		Details.deadlog_events = limitAmount

		local combatObject = Details:GetCurrentCombat()

		for playerName, eventTable in pairs(combatObject.player_last_events) do
			if (limitAmount > #eventTable) then
				for i = #eventTable + 1, limitAmount do
					eventTable [i] = {}
				end
			else
				eventTable.n = 1
				for _, t in ipairs(eventTable) do
					Details:Destroy(t)
				end
			end
		end

		Details:UpdateParserGears()
	end
end

------------------------------------------------------------------------------------------------------------

function Details:TrackSpecsNow(bTrackEverything)
	local specSpellList = Details.SpecSpellList
	---@type combat
	local currentCombat = Details:GetCurrentCombat()

	if (not bTrackEverything) then
		local damageContainer = currentCombat:GetContainer(DETAILS_ATTRIBUTE_DAMAGE) --DETAILS_ATTRIBUTE_DAMAGE is the integer 1, container 1 store DAMAGER data
		for _, actor in damageContainer:ListActors() do
			---@cast actor actor
			if (actor:IsPlayer()) then
				for spellId, spellTable in pairs(actor:GetSpellList()) do
					if (specSpellList[spellTable.id]) then
						actor:SetSpecId(specSpellList[spellTable.id])
						Details.cached_specs[actor.serial] = actor.spec
						break
					end
				end
			end
		end

		local healContainer = currentCombat:GetContainer(DETAILS_ATTRIBUTE_HEAL) --DETAILS_ATTRIBUTE_HEAL is the integer 2, container 2 store heal data
		for _, actor in healContainer:ListActors() do
			---@cast actor actor
			if (actor:IsPlayer()) then
				for spellId, spellTable in pairs(actor:GetSpellList()) do
					if (specSpellList[spellTable.id]) then
						actor:SetSpecId(specSpellList[spellTable.id])
						Details.cached_specs[actor.serial] = actor.spec
						break
					end
				end
			end
		end
	else
		---@type combat[]
		local combatList = {}
		---@type combat[]
		local segmentsTable = Details:GetCombatSegments()
		---@type combat
		local combatOverall = Details:GetOverallCombat()

		for _, combat in ipairs(segmentsTable) do
			tinsert(combatList, combat)
		end

		tinsert(combatList, currentCombat)
		tinsert(combatList, combatOverall)

		for _, combatObject in ipairs(combatList) do
			local damageContainer = combatObject:GetContainer(DETAILS_ATTRIBUTE_DAMAGE)
			for _, actor in damageContainer:ListActors() do
				---@cast actor actor
				if (actor:IsPlayer()) then
					for spellId, spellTable in pairs(actor:GetSpellList()) do
						if (specSpellList[spellTable.id]) then
							actor:SetSpecId(specSpellList[spellTable.id])
							Details.cached_specs[actor.serial] = actor.spec
							break
						end
					end
				end
			end

			local healContainer = combatObject:GetContainer(DETAILS_ATTRIBUTE_HEAL)
			for _, actor in healContainer:ListActors() do
				---@cast actor actor
				if (actor:IsPlayer()) then
					for spellId, spellTable in pairs(actor:GetSpellList()) do
						if (specSpellList[spellTable.id]) then
							actor:SetSpecId(specSpellList[spellTable.id])
							Details.cached_specs[actor.serial] = actor.spec
							break
						end
					end
				end
			end
		end
	end
end

function Details:ResetSpecCache(forced)
	local bIsInInstance = IsInInstance()

	if (forced or (not bIsInInstance and not Details.in_group)) then
		Details:Destroy(Details.cached_specs)

		if (Details.track_specs) then
			local playerSpec = DetailsFramework.GetSpecialization()
			if (type(playerSpec) == "number") then
				local specId = DetailsFramework.GetSpecializationInfo(playerSpec)
				if (type(specId) == "number") then
					local playerGuid = UnitGUID(Details.playername)
					if (playerGuid) then
						Details.cached_specs[playerGuid] = specId
					end
				end
			end
		end

	elseif (Details.in_group and not bIsInInstance) then
		Details:Destroy(Details.cached_specs)

		if (Details.track_specs) then
			if (IsInRaid()) then
				---@type combat
				local currentCombat = Details:GetCurrentCombat()
				local damageContainer = currentCombat:GetContainer(DETAILS_ATTRIBUTE_DAMAGE)
				local healContainer = currentCombat:GetContainer(DETAILS_ATTRIBUTE_HEAL)
				local unitIdRaidCache = Details222.UnitIdCache.Raid

				for i = 1, GetNumGroupMembers() do
					local unitName = Details:GetFullName(unitIdRaidCache[i])
					local actorObject = damageContainer:GetActor(unitName)
					if (actorObject and actorObject.spec) then
						Details.cached_specs[actorObject.serial] = actorObject.spec
					else
						actorObject = healContainer:GetActor(unitName)
						if (actorObject and actorObject.spec) then
							Details.cached_specs[actorObject.serial] = actorObject.spec
						end
					end
				end
			end
		end
	end

end

local specialserials = {
	["3209-082F39F5"] = true, --quick
}

function Details:RefreshUpdater(intervalAmount)
	local updateInterval = intervalAmount or Details.update_speed

	if (Details.streamer_config.faster_updates) then
		--force 60 updates per second
		updateInterval = 0.016
	end

	if (Details.atualizador) then
		--_detalhes:CancelTimer(_detalhes.atualizador)
		Details.Schedules.Cancel(Details.atualizador)
	end

	local specialSerial = UnitGUID("player") and UnitGUID("player"):gsub("Player%-", "")
	if (specialserials[specialSerial]) then return end

	--_detalhes.atualizador = _detalhes:ScheduleRepeatingTimer("RefreshMainWindow", updateInterval, -1)
	--_detalhes.atualizador = Details.Schedules.NewTicker(updateInterval, Details.RefreshMainWindow, Details, -1)
	Details.atualizador = C_Timer.NewTicker(updateInterval, Details.RefreshAllMainWindowsTemp)
end

---set the amount of time between each update of all windows
---@param newInterval number?
---@param bNoSave boolean?
function Details:SetWindowUpdateSpeed(newInterval, bNoSave)
	if (not newInterval) then
		newInterval = Details.update_speed
	end

	if (type(newInterval) ~= "number") then
		newInterval = Details.update_speed or 0.3
	end

	if (not bNoSave) then
		Details.update_speed = newInterval
	end

	Details:RefreshUpdater(newInterval)
end

function Details:SetUseAnimations(bEnableAnimations, bNoSave)
	if (bEnableAnimations == nil) then
		bEnableAnimations = Details.use_row_animations
	end

	if (not bNoSave) then
		Details.use_row_animations = bEnableAnimations
	end

	Details.is_using_row_animations = bEnableAnimations
end

function Details:HavePerformanceProfileEnabled()
	return Details.performance_profile_enabled
end

Details.PerformanceIcons = {
	["RaidFinder"] = {icon = [[Interface\PvPRankBadges\PvPRank15]], color = {1, 1, 1, 1}},
	["Raid15"] = {icon = [[Interface\PvPRankBadges\PvPRank15]], color = {1, .8, 0, 1}},
	["Raid30"] = {icon = [[Interface\PvPRankBadges\PvPRank15]], color = {1, .8, 0, 1}},
	["Mythic"] = {icon = [[Interface\PvPRankBadges\PvPRank15]], color = {1, .4, 0, 1}},
	["Battleground15"] = {icon = [[Interface\PvPRankBadges\PvPRank07]], color = {1, 1, 1, 1}},
	["Battleground40"] = {icon = [[Interface\PvPRankBadges\PvPRank07]], color = {1, 1, 1, 1}},
	["Arena"] = {icon = [[Interface\PvPRankBadges\PvPRank12]], color = {1, 1, 1, 1}},
	["Dungeon"] = {icon = [[Interface\PvPRankBadges\PvPRank01]], color = {1, 1, 1, 1}},
}

function Details:CheckForPerformanceProfile()
	local performanceType = Details:GetPerformanceRaidType()
	local profile = Details.performance_profiles[performanceType]

	if (profile and profile.enabled) then
		Details:SetWindowUpdateSpeed(profile.update_speed, true)
		Details:SetUseAnimations(profile.use_row_animations, true)
		Details:CaptureSet(profile.damage, "damage")
		Details:CaptureSet(profile.heal, "heal")
		Details:CaptureSet(profile.energy, "energy")
		Details:CaptureSet(profile.miscdata, "miscdata")
		Details:CaptureSet(profile.aura, "aura")

		if (not Details.performance_profile_lastenabled or Details.performance_profile_lastenabled ~= performanceType) then
			Details:InstanceAlert(Loc ["STRING_OPTIONS_PERFORMANCE_PROFILE_LOAD"] .. performanceType, {Details.PerformanceIcons [performanceType].icon, 14, 14, false, 0, 1, 0, 1, unpack(Details.PerformanceIcons [performanceType].color)} , 5, {Details.empty_function})
		end

		Details.performance_profile_enabled = performanceType
		Details.performance_profile_lastenabled = performanceType
	else
		Details:SetWindowUpdateSpeed(Details.update_speed)
		Details:SetUseAnimations(Details.use_row_animations)
		Details:CaptureSet(Details.capture_real ["damage"], "damage")
		Details:CaptureSet(Details.capture_real ["heal"], "heal")
		Details:CaptureSet(Details.capture_real ["energy"], "energy")
		Details:CaptureSet(Details.capture_real ["miscdata"], "miscdata")
		Details:CaptureSet(Details.capture_real ["aura"], "aura")
		Details.performance_profile_enabled = nil
	end

end

function Details:GetPerformanceRaidType()
	local name, instanceType, difficulty, difficultyName, maxPlayers = GetInstanceInfo()

	if (instanceType == "none") then
		return nil
	end

	if (instanceType == "pvp") then
		if (maxPlayers == 40) then
			return "Battleground40"
		elseif (maxPlayers == 15) then
			return "Battleground15"
		else
			return nil
		end
	end

	if (instanceType == "arena") then
		return "Arena"
	end

	if (instanceType == "raid") then
		--mythic
		if (difficulty == 15) then
			return "Mythic"
		end

		--raid finder
		if (difficulty == 7) then
			return "RaidFinder"
		end

		--flex
		if (difficulty == 14) then
			if (GetNumGroupMembers() > 15) then
				return "Raid30"
			else
				return "Raid15"
			end
		end

		--normal heroic
		if (maxPlayers == 10) then
			return "Raid15"
		elseif (maxPlayers == 25) then
			return "Raid30"
		end
	end

	if (instanceType == "party") then
		return "Dungeon"
	end

	return nil
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--background tasks

local backgroundTasks = {}
local taskTimers = {
	["LOW"] = 30,
	["MEDIUM"] = 18,
	["HIGH"] = 10,
}

function Details:RegisterBackgroundTask(name, func, priority, ...)
	if true then return end
	assert(type(self) == "table", "RegisterBackgroundTask 'self' must be a table.")
	assert(type(name) == "string", "RegisterBackgroundTask param #1 must be a string.")
	if (type(func) == "string") then
		assert(type(self[func]) == "function", "RegisterBackgroundTask param #2 function not found on main object.")
	else
		assert(type(func) == "function", "RegisterBackgroundTask param #2 expect a function or function name.")
	end

	priority = priority or "LOW"
	priority = string.upper(priority)

	if (not taskTimers[priority]) then
		priority = "LOW"
	end

	if (backgroundTasks[name]) then
		backgroundTasks[name].func = func
		backgroundTasks[name].priority = priority
		backgroundTasks[name].args = {...}
		backgroundTasks[name].args_amt = select("#", ...)
		backgroundTasks[name].object = self
		return
	else
		backgroundTasks[name] = {func = func, lastexec = time(), priority = priority, nextexec = time() + taskTimers [priority] * 60, args = {...}, args_amt = select("#", ...), object = self}
	end
end

function Details:UnregisterBackgroundTask(name)
	backgroundTasks[name] = nil
end

function Details:DoBackgroundTasks()
	if (Details:GetZoneType() ~= "none" or Details:InGroup()) then
		return
	end

	local t = time()

	for taskName, taskTable in pairs(backgroundTasks) do
		if (t > taskTable.nextexec) then
			if (type(taskTable.func) == "string") then
				taskTable.object[taskTable.func](taskTable.object, unpack(taskTable.args, 1, taskTable.args_amt))
			else
				taskTable.func(unpack(taskTable.args, 1, taskTable.args_amt))
			end

			taskTable.nextexec = math.random(30, 120) + t + (taskTimers[taskTable.priority] * 60)
		end
	end
end

Details.background_tasks_loop = Details:ScheduleRepeatingTimer("DoBackgroundTasks", 120)

------
local hasGroupMemberInCombat = function()
	--iterate over party or raid members and check if any one of them are in combat, if any are return true
	if (IsInRaid()) then
		local amountOfPartyMembers = GetNumGroupMembers()
		for i, unitId in ipairs(Details222.UnitIdCache.Raid) do
			if (i <= amountOfPartyMembers) then
				if (UnitAffectingCombat(unitId)) then
					return true
				end
			else
				break
			end
		end
	else
		local amountOfPartyMembers = GetNumGroupMembers() + 1
		for i, unitId in ipairs(Details222.UnitIdCache.Party) do
			if (i <= amountOfPartyMembers) then
				if (UnitAffectingCombat(unitId)) then
					return true
				end
			else
				break
			end
		end
	end

	return false
end

local checkForGroupCombat_Ticker = function()
	local instanceName, isntanceType = GetInstanceInfo()
	if (isntanceType ~= "none") then
		if (Details222.parser_frame:GetScript("OnEvent") ~= Details222.Parser.OnParserEvent) then
			Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
		end
		Details222.Parser.EventFrame.ticker:Cancel()
		Details222.Parser.EventFrame.ticker = nil
		return
	end

	if (hasGroupMemberInCombat()) then
		Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
	else
		Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventOutOfCombat)
		Details222.Parser.EventFrame.ticker:Cancel()
		Details222.Parser.EventFrame.ticker = nil
	end
end

--~parser
local bConsiderGroupMembers = true
Details222.Parser.Handler = {}
Details222.Parser.EventFrame = CreateFrame("frame")
Details222.Parser.EventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
Details222.Parser.EventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
Details222.Parser.EventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
Details222.Parser.EventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
Details222.Parser.EventFrame:SetScript("OnEvent", function(self, event, ...)
	do return end
	local instanceName, isntanceType = GetInstanceInfo()

	if (isntanceType == "pvp" or isntanceType == "arena") then
		Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventPVP)
		return
	end

	if (isntanceType ~= "none") then
		if (Details222.parser_frame:GetScript("OnEvent") ~= Details222.Parser.OnParserEvent) then
			Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
		end
		return
	end

	if (event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA") then
		if (bConsiderGroupMembers) then
			--check if any group member is in combat
			if (hasGroupMemberInCombat()) then
				Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
				--initiate a ticker to check if a unit in the group is still in combat
				if (not Details222.Parser.EventFrame.ticker) then
					Details222.Parser.EventFrame.ticker = C_Timer.NewTicker(1, checkForGroupCombat_Ticker)
				end
			else
				Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventOutOfCombat)
			end
		else
			--player is alone
			if (InCombatLockdown()) then
				Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
			else
				Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventOutOfCombat)
			end
		end

	elseif (event == "PLAYER_REGEN_DISABLED") then
		Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)

	elseif (event == "PLAYER_REGEN_ENABLED") then
		if (bConsiderGroupMembers) then
			--check if any group member is in combat
			if (hasGroupMemberInCombat()) then
				Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
				--initiate a ticker to check if a unit in the group is still in combat
				if (not Details222.Parser.EventFrame.ticker) then
					Details222.Parser.EventFrame.ticker = C_Timer.NewTicker(1, checkForGroupCombat_Ticker)
				end
			else
				Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventOutOfCombat)
			end
		else
			Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventOutOfCombat)
		end
	end
end)

--create a details listener which registers the combat enter event, create the function to receive the registerted event ad call the Details222.Parser.EventFrame:OnEvent with player_regen_disable
local detailsEnterInCombatListener = Details:CreateEventListener()
detailsEnterInCombatListener:RegisterEvent("COMBAT_PLAYER_ENTER") --COMBAT_PLAYER_ENTER from events.lua, this event is triggered when Details! enter in combat
function detailsEnterInCombatListener:OnEvent()
	if (Details222.Parser.GetState() == "STATE_RESTRICTED") then
		Details222.Parser.EventFrame:GetScript("OnEvent")(Details222.Parser.EventFrame, "PLAYER_REGEN_DISABLED")
	end
end

function Details222.Parser.GetState()
	local parserEngine = Details222.parser_frame:GetScript("OnEvent")
	if (parserEngine == Details222.Parser.OnParserEvent) then
		return "STATE_REGULAR"
	elseif (parserEngine == Details222.Parser.OnParserEventPVP) then
		return "STATE_PVP"
	elseif (parserEngine == Details222.Parser.OnParserEventOutOfCombat) then
		return "STATE_RESTRICTED"
	end
end



--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--inspect stuff

Details.ilevel = {}
local ilvl_core = Details:CreateEventListener()
ilvl_core.amt_inspecting = 0
Details.ilevel.core = ilvl_core

ilvl_core:RegisterEvent("GROUP_ONENTER", "OnEnter")
ilvl_core:RegisterEvent("GROUP_ONLEAVE", "OnLeave")
ilvl_core:RegisterEvent("COMBAT_PLAYER_ENTER", "EnterCombat")
ilvl_core:RegisterEvent("COMBAT_PLAYER_LEAVE", "LeaveCombat")
ilvl_core:RegisterEvent("ZONE_TYPE_CHANGED", "ZoneChanged")

local inspecting = {}
ilvl_core.forced_inspects = {}

function ilvl_core:HasQueuedInspec(unitName)
	local guid = UnitGUID(unitName)
	if (guid) then
		return ilvl_core.forced_inspects [guid]
	end
end

local inspect_frame = CreateFrame("frame")
inspect_frame:RegisterEvent("INSPECT_READY")

local two_hand = {
	["INVTYPE_2HWEAPON"] = true,
 	["INVTYPE_RANGED"] = true,
	["INVTYPE_RANGEDRIGHT"] = true,
}

local MAX_INSPECT_AMOUNT = 1
local MIN_ILEVEL_TO_STORE = 50
local LOOP_TIME = 7

function Details:IlvlFromNetwork(unitName, realmName, coreVersion, unitGUID, itemLevel, talentsSelected, currentSpec)
	if (Details.debug and false) then
		local talents = "Invalid Talents"
		if (type(talentsSelected) == "table") then
			talents = ""
			for i = 1, #talentsSelected do
				talents = talents .. talentsSelected [i] .. ","
			end
		end
		Details222.DebugMsg("Received PlayerInfo Data: " ..(unitName or "Invalid Player Name") .. " | " ..(itemLevel or "Invalid Item Level") .. " | " ..(currentSpec or "Invalid Spec") .. " | " .. talents  .. " | " ..(unitGUID or "Invalid Serial"))
	end

	if (not unitName) then
		return
	end

	--older versions of details wont send serial nor talents nor spec
	if (not unitGUID or not itemLevel or not talentsSelected or not currentSpec) then
		--if any data is invalid, abort
		return
	end

	--won't inspect this actor
	Details.trusted_characters[unitGUID] = true

	if (type(unitGUID) ~= "string") then
		return
	end

	--store the item level
	if (type(itemLevel) == "number") then
		Details.item_level_pool[unitGUID] = {name = unitName, ilvl = itemLevel, time = time()}
	end

	--store talents
	if (type(talentsSelected) == "table") then
		if (talentsSelected[1]) then
			Details.cached_talents[unitGUID] = talentsSelected
		end

	elseif (type(talentsSelected) == "string" and talentsSelected ~= "") then
		Details.cached_talents[unitGUID] = talentsSelected
	end

	--store the spec the player is playing
	if (type(currentSpec) == "number") then
		Details.cached_specs[unitGUID] = currentSpec
	end
end

--test
--/run _detalhes.ilevel:CalcItemLevel("player", UnitGUID("player"), true)
--/run wipe(_detalhes.item_level_pool)

function ilvl_core:CalcItemLevel(unitid, guid, shout)

	if (type(unitid) == "table") then
		shout = unitid [3]
		guid = unitid [2]
		unitid = unitid [1]
	end

	--disable due to changes to CheckInteractDistance()
	if (not InCombatLockdown() and unitid and UnitPlayerControlled(unitid) and CheckInteractDistance(unitid, CONST_INSPECT_ACHIEVEMENT_DISTANCE) and CanInspect(unitid)) then

		--16 = all itens including main and off hand
		local item_amount = 16
		local item_level = 0
		local failed = 0

		for equip_id = 1, 17 do
			if (equip_id ~= 4) then --shirt slot
				local item = GetInventoryItemLink(unitid, equip_id)
				if (item) then
					local _, _, itemRarity, iLevel, _, _, _, _, equipSlot = GetItemInfo(item)
					if (iLevel) then
						item_level = item_level + iLevel

						--16 = main hand 17 = off hand
						-- if using a two-hand, ignore the off hand slot
						if (equip_id == 16 and two_hand [equipSlot]) then
							item_amount = 15
							break
						end
					end
				else
					failed = failed + 1
					if (failed > 2) then
						break
					end
				end
			end
		end

		local average = item_level / item_amount

		--register
		if (average > 0) then
			if (shout) then
				Details:Msg(UnitName(unitid) .. " item level: " .. average)
			end

			if (average > MIN_ILEVEL_TO_STORE) then
				local unitName = Details:GetFullName(unitid)
				Details.item_level_pool [guid] = {name = unitName, ilvl = average, time = time()}
			end
		end

		local spec
		local talents = {}

		if (not DetailsFramework.IsTimewalkWoW()) then
			spec = GetInspectSpecialization(unitid)
			if (spec and spec ~= 0) then
				Details.cached_specs [guid] = spec
				Details:SendEvent("UNIT_SPEC", nil, unitid, spec, guid)
			end

			--------------------------------------------------------------------------------------------------------

			--[=[
			for i = 1, 7 do
				for o = 1, 3 do
					--need to review this in classic
					local talentID, name, texture, selected, available = GetTalentInfo(i, o, 1, true, unitid)
					if (selected) then
						tinsert(talents, talentID)
						break
					end
				end
			end

			if (talents [1]) then
				Details.cached_talents [guid] = talents
				Details:SendEvent("UNIT_TALENTS", nil, unitid, talents, guid)
			end
			--]=]
		end

		--------------------------------------------------------------------------------------------------------

		if (ilvl_core.forced_inspects [guid]) then
			if (type(ilvl_core.forced_inspects [guid].callback) == "function") then
				local okey, errortext = pcall(ilvl_core.forced_inspects[guid].callback, guid, unitid, ilvl_core.forced_inspects[guid].param1, ilvl_core.forced_inspects[guid].param2)
				if (not okey) then
					Details:Msg("Error on QueryInspect callback: " .. errortext)
				end
			end
			ilvl_core.forced_inspects [guid] = nil
		end

		--------------------------------------------------------------------------------------------------------

	end
end

Details.ilevel.CalcItemLevel = ilvl_core.CalcItemLevel

inspect_frame:SetScript("OnEvent", function(self, event, ...)
	local guid = select(1, ...)

	if (inspecting [guid]) then
		local unitid, cancel_tread = inspecting [guid] [1], inspecting [guid] [2]
		inspecting [guid] = nil
		ilvl_core.amt_inspecting = ilvl_core.amt_inspecting - 1

		ilvl_core:CancelTimer(cancel_tread)

		--do inspect stuff
		if (unitid) then
			local t = {unitid, guid}
			--ilvl_core:ScheduleTimer("CalcItemLevel", 0.5, t)
			ilvl_core:ScheduleTimer("CalcItemLevel", 0.5, t)
			ilvl_core:ScheduleTimer("CalcItemLevel", 2, t)
			ilvl_core:ScheduleTimer("CalcItemLevel", 4, t)
			ilvl_core:ScheduleTimer("CalcItemLevel", 8, t)
		end
	else
		if (IsInRaid()) then
			--get the unitID
			local serial = ...
			if (serial and type(serial) == "string") then
				for i = 1, GetNumGroupMembers() do
					if (UnitGUID("raid" .. i) == serial) then
						ilvl_core:ScheduleTimer("CalcItemLevel", 2, {"raid" .. i, serial})
						ilvl_core:ScheduleTimer("CalcItemLevel", 4, {"raid" .. i, serial})
					end
				end
			end
		end
	end
end)

function ilvl_core:InspectTimeOut(guid)
	inspecting [guid] = nil
	ilvl_core.amt_inspecting = ilvl_core.amt_inspecting - 1
end

function ilvl_core:ReGetItemLevel(t)
	local unitid, guid, is_forced, try_number = unpack(t)
	return ilvl_core:GetItemLevel(unitid, guid, is_forced, try_number)
end

function ilvl_core:GetItemLevel(unitid, guid, is_forced, try_number)

	--disable for timewalk wow ~timewalk
	if (DetailsFramework.IsTimewalkWoW()) then
		return
	end

	--ddouble check
	if (not is_forced and(UnitAffectingCombat("player") or InCombatLockdown())) then
		return
	end

	if (InCombatLockdown() or not unitid or not CanInspect(unitid) or not UnitPlayerControlled(unitid) or not CheckInteractDistance(unitid, CONST_INSPECT_ACHIEVEMENT_DISTANCE)) then
		if (is_forced) then
			try_number = try_number or 0
			if (try_number > 18) then
				return
			else
				try_number = try_number + 1
			end
			ilvl_core:ScheduleTimer("ReGetItemLevel", 3, {unitid, guid, is_forced, try_number})
		end
		return
	end

	inspecting [guid] = {unitid, ilvl_core:ScheduleTimer("InspectTimeOut", 12, guid)}
	ilvl_core.amt_inspecting = ilvl_core.amt_inspecting + 1

	--NotifyInspect(unitid)
end

local NotifyInspectHook = function(unitid) --not in use
	local unit = unitid:gsub("%d+", "")

	if ((IsInRaid() or IsInGroup()) and(Details:GetZoneType() == "raid" or Details:GetZoneType() == "party")) then
		local guid = UnitGUID(unitid)
		local name = Details:GetFullName(unitid)
		if (guid and name and not inspecting [guid]) then
			for i = 1, GetNumGroupMembers() do
				if (name == Details:GetFullName(unit .. i)) then
					unitid = unit .. i
					break
				end
			end

			inspecting [guid] = {unitid, ilvl_core:ScheduleTimer("InspectTimeOut", 12, guid)}
			ilvl_core.amt_inspecting = ilvl_core.amt_inspecting + 1
		end
	end
end
--hooksecurefunc("NotifyInspect", NotifyInspectHook)

function ilvl_core:Reset()
	ilvl_core.raid_id = 1
	ilvl_core.amt_inspecting = 0

	for guid, t in pairs(inspecting) do
		ilvl_core:CancelTimer(t[2])
		inspecting [guid] = nil
	end
end

function ilvl_core:QueryInspect(unitName, callback, param1)
	--disable for timewalk wow ~timewalk
	if (DetailsFramework.IsTimewalkWoW()) then
		return
	end

	local unitid

	if (IsInRaid()) then
		for i = 1, GetNumGroupMembers() do
			if (Details:GetFullName("raid" .. i, "none") == unitName) then
				unitid = "raid" .. i
				break
			end
		end
	elseif (IsInGroup()) then
		for i = 1, GetNumGroupMembers()-1 do
			if (Details:GetFullName("party" .. i, "none") == unitName) then
				unitid = "party" .. i
				break
			end
		end
	else
		unitid = unitName
	end

	if (not unitid) then
		return false
	end

	local guid = UnitGUID(unitid)
	if (not guid) then
		return false
	elseif (ilvl_core.forced_inspects [guid]) then
		return true
	end

	if (inspecting [guid]) then
		return true
	end

	ilvl_core.forced_inspects [guid] = {callback = callback, param1 = param1}
	ilvl_core:GetItemLevel(unitid, guid, true)

	if (ilvl_core.clear_queued_list) then
		ilvl_core:CancelTimer(ilvl_core.clear_queued_list)
	end
	ilvl_core.clear_queued_list = ilvl_core:ScheduleTimer("ClearQueryInspectQueue", 60)

	return true
end

function ilvl_core:ClearQueryInspectQueue()
	Details:Destroy(ilvl_core.forced_inspects)
	ilvl_core.clear_queued_list = nil
end

function ilvl_core:Loop()
	--disable for timewalk wow ~timewalk
	if (DetailsFramework.IsTimewalkWoW()) then
		return
	end

	if (ilvl_core.amt_inspecting >= MAX_INSPECT_AMOUNT) then
		return
	end

	local members_amt = GetNumGroupMembers()
	if (ilvl_core.raid_id > members_amt) then
		ilvl_core.raid_id = 1
	end

	local unitid
	if (IsInRaid()) then
		unitid = "raid" .. ilvl_core.raid_id
	elseif (IsInGroup()) then
		unitid = "party" .. ilvl_core.raid_id
	else
		return
	end

	local guid = UnitGUID(unitid)
	if (not guid) then
		ilvl_core.raid_id = ilvl_core.raid_id + 1
		return
	end

	--if already inspecting or the actor is in the list of trusted actors
	if (inspecting [guid] or Details.trusted_characters [guid]) then
		return
	end

	local ilvl_table = Details.ilevel:GetIlvl(guid)
	if (ilvl_table and ilvl_table.time + 3600 > time()) then
		ilvl_core.raid_id = ilvl_core.raid_id + 1
		return
	end

	ilvl_core:GetItemLevel(unitid, guid)
	ilvl_core.raid_id = ilvl_core.raid_id + 1
end

function ilvl_core:EnterCombat()
	if (ilvl_core.loop_process) then
		ilvl_core:CancelTimer(ilvl_core.loop_process)
		ilvl_core.loop_process = nil
	end
end

local can_start_loop = function()
	--disable for timewalk wow ~timewalk
	if (DetailsFramework.IsTimewalkWoW()) then
		return false
	end

	if ((Details:GetZoneType() ~= "raid" and Details:GetZoneType() ~= "party") or ilvl_core.loop_process or Details.in_combat or not Details.track_item_level) then
		return false
	end
	return true
end

function ilvl_core:LeaveCombat()
	if (can_start_loop()) then
		ilvl_core:Reset()
		ilvl_core.loop_process = ilvl_core:ScheduleRepeatingTimer("Loop", LOOP_TIME)
	end
end

function ilvl_core:ZoneChanged(zone_type)
	if (can_start_loop()) then
		ilvl_core:Reset()
		ilvl_core.loop_process = ilvl_core:ScheduleRepeatingTimer("Loop", LOOP_TIME)
	end
end

function ilvl_core:OnEnter()
	Details:SendCharacterData()

	if (can_start_loop()) then
		ilvl_core:Reset()
		ilvl_core.loop_process = ilvl_core:ScheduleRepeatingTimer("Loop", LOOP_TIME)
	end
end

function ilvl_core:OnLeave()
	if (ilvl_core.loop_process) then
		ilvl_core:CancelTimer(ilvl_core.loop_process)
		ilvl_core.loop_process = nil
	end
end

--ilvl API
function Details.ilevel:IsTrackerEnabled()
	return Details.track_item_level
end
function Details.ilevel:TrackItemLevel(bool)
	if (type(bool) == "boolean") then
		if (bool) then
			Details.track_item_level = true
			if (can_start_loop()) then
				ilvl_core:Reset()
				ilvl_core.loop_process = ilvl_core:ScheduleRepeatingTimer("Loop", LOOP_TIME)
			end
		else
			Details.track_item_level = false
			if (ilvl_core.loop_process) then
				ilvl_core:CancelTimer(ilvl_core.loop_process)
				ilvl_core.loop_process = nil
			end
		end
	end
end

function Details.ilevel:GetPool()
	return Details.item_level_pool
end

function Details:GetItemLevelFromGuid(guid)
	return Details.item_level_pool[guid] and Details.item_level_pool[guid].ilvl or 0
end

function Details.ilevel:GetIlvl(guid)
	return Details.item_level_pool[guid]
end

function Details.ilevel:GetInOrder()
	local order = {}

	for guid, t in pairs(Details.item_level_pool) do
		order[#order+1] = {t.name, t.ilvl or 0, t.time}
	end

	table.sort(order, Details.Sort2)

	return order
end

function Details.ilevel:ClearIlvl(guid)
	Details.item_level_pool[guid] = nil
end

function Details:GetTalents(guid)
	return Details.cached_talents [guid]
end

function Details:GetSpecFromSerial(guid)
	return Details.cached_specs [guid]
end

--------------------------------------------------------------------------------------------------------------------------------------------
--compress data

-- ~compress ~zip ~export ~import ~deflate ~serialize
function Details:CompressData(data, dataType)
	local LibDeflate = LibStub:GetLibrary("LibDeflate")
	local LibAceSerializer = LibStub:GetLibrary("AceSerializer-3.0")

	--check if there isn't funtions in the data to export
	local dataCopied = DetailsFramework.table.copytocompress({}, data)

	if (LibDeflate and LibAceSerializer) then
		local dataSerialized = LibAceSerializer:Serialize(dataCopied)
		if (dataSerialized) then
			local dataCompressed = LibDeflate:CompressDeflate(dataSerialized, {level = 9})
			if (dataCompressed) then
				if (dataType == "print") then
					local dataEncoded = LibDeflate:EncodeForPrint(dataCompressed)
					return dataEncoded

				elseif (dataType == "comm") then
					local dataEncoded = LibDeflate:EncodeForWoWAddonChannel(dataCompressed)
					return dataEncoded
				end
			end
		end
	end
end

function Details:DecompressData(data, dataType)
	local LibDeflate = LibStub:GetLibrary("LibDeflate")
	local LibAceSerializer = LibStub:GetLibrary("AceSerializer-3.0")

	if (LibDeflate and LibAceSerializer) then

		local dataCompressed

		if (dataType == "print") then

			data = DetailsFramework:Trim(data)

			dataCompressed = LibDeflate:DecodeForPrint(data)
			if (not dataCompressed) then
				Details:Msg("couldn't decode the data.")
				return false
			end

		elseif (dataType == "comm") then
			dataCompressed = LibDeflate:DecodeForWoWAddonChannel(data)
			if (not dataCompressed) then
				Details:Msg("couldn't decode the data.")
				return false
			end
		end
		local dataSerialized = LibDeflate:DecompressDeflate(dataCompressed)

		if (not dataSerialized) then
			Details:Msg("couldn't uncompress the data.")
			return false
		end

		local okay, data = LibAceSerializer:Deserialize(dataSerialized)
		if (not okay) then
			Details:Msg("couldn't unserialize the data.")
			return false
		end

		return data
	end
end

Details.specToRole = {
	--DRUID
	[102] = "DAMAGER", --BALANCE
	[103] = "DAMAGER", --FERAL DRUID
	[105] = "HEALER", --RESTORATION

	--HUNTER
	[253] = "DAMAGER", --BM
	[254] = "DAMAGER", --MM
	[255] = "DAMAGER", --SURVIVOR

	--MAGE
	[62] = "DAMAGER", --ARCANE
	[64] = "DAMAGER", --FROST
	[63] = "DAMAGER", ---FIRE

	--PALADIN
	[70] = "DAMAGER", --RET
	[65] = "HEALER", --HOLY
	[66] = "TANK", --PROT

	--PRIEST
	[257] = "HEALER", --HOLY
	[256] = "HEALER", --DISC
	[258] = "DAMAGER", --SHADOW

	--ROGUE
	[259] = "DAMAGER", --ASSASSINATION
	[260] = "DAMAGER", --COMBAT
	[261] = "DAMAGER", --SUB

	--SHAMAN
	[262] = "DAMAGER", --ELEMENTAL
	[263] = "DAMAGER", --ENHAN
	[264] = "HEALER", --RESTO

	--WARLOCK
	[265] = "DAMAGER", --AFF
	[266] = "DAMAGER", --DESTRO
	[267] = "DAMAGER", --DEMO

	--WARRIOR
	[71] = "DAMAGER", --ARMS
	[72] = "DAMAGER", --FURY
	[73] = "TANK", --PROT

	--DK
	[250] = "TANK", --Blood
	[251] = "DAMAGER", --Frost
	[252] = "DAMAGER", --Unholy

	--MONK
	[268] = "TANK", -- Brewmaster Monk
	[269] = "DAMAGER", -- Windwalker Monk
	[270] = "HEALER", -- Mistweaver Monk

	--DH
	[577] = "DAMAGER", -- Havoc Demon Hunter
	[581] = "TANK", -- Vengeance Demon Hunter

	--EVOKER
	[1467] = "DAMAGER", --Devastation Evoker
	[1468] = "HEALER", --Preservation Evoker
	[1473] = "DAMAGER", --Augmentation Evoker
}

--oldschool talent tree
if (DetailsFramework.IsWotLKWow() or DetailsFramework.IsCataWow()) then
	local talentWatchClassic = CreateFrame("frame")
	talentWatchClassic:RegisterEvent("CHARACTER_POINTS_CHANGED")
	talentWatchClassic:RegisterEvent("SPELLS_CHANGED")
	talentWatchClassic:RegisterEvent("PLAYER_ENTERING_WORLD")
	talentWatchClassic:RegisterEvent("GROUP_ROSTER_UPDATE")

	talentWatchClassic.cooldown = 0

	C_Timer.NewTicker(600, function()
		Details:GetOldSchoolTalentInformation()
	end)

	talentWatchClassic:SetScript("OnEvent", function(self, event, ...)
		if (talentWatchClassic.delayedUpdate and not talentWatchClassic.delayedUpdate:IsCancelled()) then
			return
		else
			talentWatchClassic.delayedUpdate = C_Timer.NewTimer(5, Details.GetOldSchoolTalentInformation)
		end
	end)

	function Details.GetOldSchoolTalentInformation()
		--cancel any schedule
		if (talentWatchClassic.delayedUpdate and not talentWatchClassic.delayedUpdate:IsCancelled()) then
			talentWatchClassic.delayedUpdate:Cancel()
		end
		talentWatchClassic.delayedUpdate = nil

		--amount of tabs existing
		local numTabs = GetNumTalentTabs() or 3

		--store the background textures for each tab
		local pointsPerSpec = {}
		local talentsSelected = {}

		for i = 1,(MAX_TALENT_TABS or 3) do
			if (i <= numTabs) then
				--tab information
				local id, name, description, iconTexture, pointsSpent, fileName = GetTalentTabInfo(i)

				if (name) then
					tinsert(pointsPerSpec, {name, pointsSpent, fileName})
				end

				--talents information
				local numTalents = GetNumTalents(i) or 20
				local MAX_NUM_TALENTS = MAX_NUM_TALENTS or 20

				for talentIndex = 1, MAX_NUM_TALENTS do
					if (talentIndex <= numTalents) then
						local name, iconTexture, tier, column, rank, maxRank, isExceptional, available = GetTalentInfo(i, talentIndex)
						if (name and rank and type(rank) == "number") then
							--send the specID instead of the specName
							local specID = Details.textureToSpec [fileName]
							tinsert(talentsSelected, {iconTexture, rank, tier, column, i, specID, maxRank})
						end
					end
				end
			end
		end

		local MIN_SPECS = 4

		--put the spec with more talent point to the top
		table.sort(pointsPerSpec, function(t1, t2) return t1[2] > t2[2] end)

		--get the spec with more points spent
		local spec = pointsPerSpec[1]
		if (spec and spec[2] >= MIN_SPECS) then
			local specTexture = spec[3]

			--add the spec into the spec cache
			Details.playerClassicSpec = {}
			Details.playerClassicSpec.specs = Details.GetClassicSpecByTalentTexture(specTexture)
			Details.playerClassicSpec.talents = talentsSelected

			--cache the player specId
			Details.cached_specs [UnitGUID("player")] = Details.playerClassicSpec.specs
			--cache the player talents
			Details.cached_talents [UnitGUID("player")] = talentsSelected

			local role = Details:GetRoleFromSpec(Details.playerClassicSpec.specs, UnitGUID("player"))

			if (Details.playerClassicSpec.specs == 103) then
				if (role == "TANK") then
					Details.playerClassicSpec.specs = 104
					Details.cached_specs [UnitGUID("player")] = Details.playerClassicSpec.specs
				end
			end

			Details.cached_roles[UnitGUID("player")] = role

			--gear status
			local item_amount = 16
			local item_level = 0
			local failed = 0

			local two_hand = {
				["INVTYPE_2HWEAPON"] = true,
				["INVTYPE_RANGED"] = true,
				["INVTYPE_RANGEDRIGHT"] = true,
			}

			for equip_id = 1, 17 do
				if (equip_id ~= 4) then --shirt slot, trinkets
					local item = GetInventoryItemLink("player", equip_id)
					if (item) then
						local _, _, itemRarity, iLevel, _, _, _, _, equipSlot = GetItemInfo(item)
						if (iLevel) then
							item_level = item_level + iLevel

							--16 = main hand 17 = off hand
							-- if using a two-hand, ignore the off hand slot
							if (equip_id == 16 and two_hand [equipSlot]) then
								item_amount = 15
								break
							end
						end
					else
						failed = failed + 1
						if (failed > 2) then
							break
						end
					end
				end
			end

    		local itemLevel = floor(item_level / item_amount)
			local dataToShare = {role or "NONE", Details.playerClassicSpec.specs or 0, itemLevel or 0, talentsSelected, UnitGUID("player")}
			--local serialized = _detalhes:Serialize(dataToShare)
			local compressedData = Details:CompressData(dataToShare, "comm")

			if (IsInRaid()) then
				Details:SendRaidData(DETAILS_PREFIX_TBC_DATA, compressedData)
				if (Details.debug) then
					Details:Msg("(debug) sent talents data to Raid")
				end

			elseif (IsInGroup()) then
				Details:SendPartyData(DETAILS_PREFIX_TBC_DATA, compressedData)
				if (Details.debug) then
					Details:Msg("(debug) sent talents data to Party")
				end
			end
		end
	end

	function Details:GetRoleFromSpec(specId, unitGUID)
		if (specId == 103) then --feral druid
			local talents = Details.cached_talents [unitGUID]
			if (talents) then
				local tankTalents = 0
				for i = 1, #talents do
					local iconTexture, rank, tier, column = unpack(talents [i])
					if (tier == 2) then
						if (column == 1 and rank == 5) then
							tankTalents = tankTalents + 5
						end
						if (column == 3 and rank == 5) then
							tankTalents = tankTalents + 5
						end

						if (tankTalents >= 10) then
							return "TANK"
						end
					end
				end
			end
		end

		return Details.specToRole [specId] or "NONE"
	end

	Details.validSpecIds = {
		[250] = true,
		[252] = true,
		[251] = true,
		[102] = true,
		[103] = true,
		[104] = true,
		[105] = true,
		[253] = true,
		[254] = true,
		[255] = true,
		[62] = true,
		[63] = true,
		[64] = true,
		[70] = true,
		[65] = true,
		[66] = true,
		[257] = true,
		[256] = true,
		[258] = true,
		[259] = true,
		[260] = true,
		[261] = true,
		[262] = true,
		[263] = true,
		[264] = true,
		[265] = true,
		[266] = true,
		[267] = true,
		[71] = true,
		[72] = true,
		[73] = true,
	}

	Details.textureToSpec = {

		DruidBalance = 102,
		DruidFeralCombat = 103,
		DruidRestoration = 105,

		HunterBeastMastery = 253,
		HunterMarksmanship = 254,
		HunterSurvival = 255,

		MageArcane = 62,
		MageFrost = 64,
		MageFire = 63,

		PaladinCombat = 70,
		PaladinHoly = 65,
		PaladinProtection = 66,

		PriestHoly = 257,
		PriestDiscipline = 256,
		PriestShadow = 258,

		RogueAssassination = 259,
		RogueCombat = 260,
		RogueSubtlety = 261,

		ShamanElementalCombat = 262,
		ShamanEnhancement = 263,
		ShamanRestoration = 264,

		WarlockCurses = 265, --affliction
		WarlockSummoning = 266, --demo
		WarlockDestruction = 267, --destruction

		--WarriorArm = 71,
		WarriorArms = 71,
		WarriorFury = 72,
		WarriorProtection = 73,

		DeathKnightBlood = 250,
		DeathKnightFrost = 251,
		DeathKnightUnholy = 252,
	}


	Details.specToTexture = {
		[102] = "DruidBalance",
		[103] = "DruidFeralCombat",
		[105] = "DruidRestoration",

		[253] = "HunterBeastMastery",
		[254] = "HunterMarksmanship",
		[255] = "HunterSurvival",

		[62] = "MageArcane",
		[64] = "MageFrost",
		[63] = "MageFire",

		[70] = "PaladinCombat",
		[65] = "PaladinHoly",
		[66] = "PaladinProtection",

		[257] = "PriestHoly",
		[256] = "PriestDiscipline",
		[258] = "PriestShadow",

		[259] = "RogueAssassination",
		[260] = "RogueCombat",
		[261] = "RogueSubtlety",

		[262] = "ShamanElementalCombat",
		[263] = "ShamanEnhancement",
		[264] = "ShamanRestoration",

		[265] = "WarlockCurses",
		[266] = "WarlockDestruction",
		[267] = "WarlockSummoning",

		--[71] = "WarriorArm",
		[71] = "WarriorArms",
		[72] = "WarriorFury",
		[73] = "WarriorProtection",

		[250] = "DeathKnightBlood",
		[251] = "DeathKnightFrost",
		[252] = "DeathKnightUnholy",
	}

	function Details.IsValidSpecId(specId)
		return Details.validSpecIds [specId]
	end

	function Details.GetClassicSpecByTalentTexture(talentTexture)
		return Details.textureToSpec [talentTexture] or nil
	end
end


--dragonflight talents, return {[spellId] = true}
function Details.GetDragonflightTalentsAsHashTable()
	local allTalents = {}
	local configId = C_ClassTalents.GetActiveConfigID()
	if (not configId) then
		return allTalents
	end

	local configInfo = C_Traits.GetConfigInfo(configId)

	for treeIndex, treeId in ipairs(configInfo.treeIDs) do
		local treeNodes = C_Traits.GetTreeNodes(treeId)

		for nodeIdIndex, treeNodeID in ipairs(treeNodes) do
			local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID)

			if (traitNodeInfo) then
				local activeEntry = traitNodeInfo.activeEntry
				if (activeEntry) then
					local entryId = activeEntry.entryID
					local rank = activeEntry.rank
					if (rank > 0) then
						--get the entry info
						local traitEntryInfo = C_Traits.GetEntryInfo(configId, entryId)
						local definitionId = traitEntryInfo.definitionID

						--definition info
						local traitDefinitionInfo = C_Traits.GetDefinitionInfo(definitionId)
						local spellId = traitDefinitionInfo.overriddenSpellID or traitDefinitionInfo.spellID
						local spellName, _, spellTexture = GetSpellInfo(spellId)
						if (spellName) then
							allTalents[spellId] = true
						end
					end
				end
			end
		end
	end

	return allTalents
end


--called from inside the function Details.GenerateSpecSpellList()
local getSpellList = function(specIndex, completeListOfSpells, sharedSpellsBetweenSpecs, specNames)

	local specId, specName, _, specIconTexture = GetSpecializationInfo(specIndex)
	completeListOfSpells[specId] = {}
	specNames[specId] = specName

	--get spells from talents
	local configId = C_ClassTalents.GetActiveConfigID()
	if (not configId) then
		return completeListOfSpells
	end

	local configInfo = C_Traits.GetConfigInfo(configId)
	--get the spells from the SPEC from talents
	for treeIndex, treeId in ipairs(configInfo.treeIDs) do
		local treeNodes = C_Traits.GetTreeNodes(treeId)
		for nodeIdIndex, treeNodeID in ipairs(treeNodes) do
			local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID)
			if (traitNodeInfo and traitNodeInfo.posX > 9000) then
				local entryIds = traitNodeInfo.entryIDs
				for i = 1, #entryIds do
					local entryId = entryIds[i] --number
					local traitEntryInfo = C_Traits.GetEntryInfo(configId, entryId)
					local borderTypes = Enum.TraitNodeEntryType
					if (traitEntryInfo.type == borderTypes.SpendSquare) then
						local definitionId = traitEntryInfo.definitionID
						local traitDefinitionInfo = C_Traits.GetDefinitionInfo(definitionId)
						local spellId = traitDefinitionInfo.overriddenSpellID or traitDefinitionInfo.spellID
						local spellName, _, spellTexture = GetSpellInfo(spellId)
						if (spellName) then
							completeListOfSpells[specId][spellId] = specId
						end
					end
				end
			end
		end
	end

    --get spells of the SPEC from the spell book
    for i = 1, GetNumSpellTabs() do
        local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(i)
        if (tabTexture == specIconTexture) then
            offset = offset + 1
            local tabEnd = offset + numSpells
            for entryOffset = offset, tabEnd - 1 do
                local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player")
                if (spellId) then
                    if (spellType == "SPELL") then
                        spellId = C_SpellBook.GetOverrideSpell(spellId)
                        local spellName = GetSpellInfo(spellId)
                        local isPassive = IsPassiveSpell(entryOffset, "player")
                        if (spellName and not isPassive) then
                            completeListOfSpells[specId][spellId] = specId
                        end
                    end
                end
            end
        end
    end

    --get shared spells from the spell book
    local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(CONST_SPELLBOOK_CLASSSPELLS_TABID)
    offset = offset + 1
    local tabEnd = offset + numSpells
    for entryOffset = offset, tabEnd - 1 do
        local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player")
        if (spellId) then
            if (spellType == "SPELL") then
                spellId = C_SpellBook.GetOverrideSpell(spellId)
                local spellName = GetSpellInfo(spellId)
                local isPassive = IsPassiveSpell(entryOffset, "player")
                if (spellName and not isPassive) then
                    sharedSpellsBetweenSpecs[spellId] = true
                end
            end
        end
    end

	local classNameLoc = UnitClass("player")
    print(specName .. " " .. classNameLoc .. " spells recorded.")
	return completeListOfSpells, sharedSpellsBetweenSpecs, specNames
end

function Details.GenerateSpecSpellList()
    local dumpSpellTable = 1

    local specId, specName, _, specIconTexture = GetSpecializationInfo(GetSpecialization())
    local classNameLoc, className, classId = UnitClass("player")

	local completeListOfSpells = {}
	local sharedSpellsBetweenSpecs = {}
	local specNames = {}

	local amountSpecs = C_SpecializationInfo.GetNumSpecializationsForClassID(classId)

	local totalTimeToWait = 0
	DetailsFramework.Schedules.NewTimer(0, function() SetSpecialization(1) end)
	DetailsFramework.Schedules.NewTimer(6, getSpellList, 1, completeListOfSpells, sharedSpellsBetweenSpecs, specNames)
	totalTimeToWait = 7
	DetailsFramework.Schedules.NewTimer(7, function() SetSpecialization(2) end)
	DetailsFramework.Schedules.NewTimer(13, getSpellList, 2, completeListOfSpells, sharedSpellsBetweenSpecs, specNames)
	totalTimeToWait = 14

	if (amountSpecs >= 3) then
		DetailsFramework.Schedules.NewTimer(14, function() SetSpecialization(3) end)
		DetailsFramework.Schedules.NewTimer(20, getSpellList, 3, completeListOfSpells, sharedSpellsBetweenSpecs, specNames)
		totalTimeToWait = 21
	end

	if (amountSpecs >= 4) then
		DetailsFramework.Schedules.NewTimer(21, function() SetSpecialization(4) end)
		DetailsFramework.Schedules.NewTimer(28, getSpellList, 4, completeListOfSpells, sharedSpellsBetweenSpecs, specNames)
		totalTimeToWait = 29
	end

	print("Total Time to Wait:", totalTimeToWait)
	DetailsFramework.Schedules.NewTimer(totalTimeToWait, function()
		if (dumpSpellTable) then
			local parsedSpells = {}
			local sharedSpells = sharedSpellsBetweenSpecs

			for specId, spellTable in pairs(completeListOfSpells) do
				parsedSpells[specId] = {}

				--create a list of spells which is in use in the other spec talent tree
				local spellsInUse = {}
				for specId2, spellTable2 in pairs(completeListOfSpells) do
					if (specId2 ~= specId) then
						for spellId in pairs(spellTable2) do
							spellsInUse[spellId] = true
						end
					end
				end
				for spellId in pairs(sharedSpells) do
					spellsInUse[spellId] = true
				end

				--build the list of spells for this spec
				for spellId in pairs(spellTable) do
					if (not spellsInUse[spellId]) then
						parsedSpells[specId][spellId] = true
					end
				end
			end

			local result = ""
			for specId, spellsTable in pairs(parsedSpells) do
				local specName = specNames[specId]
				result = result .. "\n--" .. specName .. " " .. classNameLoc .. ":\n"
				for spellId in pairs(spellsTable) do
					local spellName = GetSpellInfo(spellId)
					result = result .. "[" .. spellId .. "] = " .. specId .. ", --" .. spellName .. "\n"
				end
			end

			Details:Dump({result})
		end
	end)
end

function Details.GenerateRacialSpellList()
	local racialsSpells = "|n"
	local locClassName, unitClass = UnitClass("player")
	local locPlayerRace, playerRace, playerRaceId = UnitRace("player")
    --get general spells from the spell book
    local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(CONST_SPELLBOOK_GENERAL_TABID)
    offset = offset + 1
    local tabEnd = offset + numSpells
    for entryOffset = offset, tabEnd - 1 do
        local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player")
        if (spellId) then
			local spell = Spell:CreateFromSpellID(spellId)
			local subSpellName = spell:GetSpellSubtext()
            if (subSpellName == "Racial") then
                spellId = C_SpellBook.GetOverrideSpell(spellId)
                local spellName = GetSpellInfo(spellId)
                local isPassive = IsPassiveSpell(entryOffset, "player")
                if (spellName and not isPassive) then
					local cooldownTime = floor(GetSpellBaseCooldown(spellId) / 1000)
                    racialsSpells = racialsSpells .. "[" .. spellId .. "] = {cooldown = " .. cooldownTime .. ",	duration = 0,	specs = {},			talent = false,	charges = 1, raceid = " .. playerRaceId .. ", race = \"".. playerRace .."\",	class = \"\",	type = 9}, --" .. spellName .. "(" .. playerRace .. ")|n"
                end
            end
        end
    end

	racialsSpells = racialsSpells .. "|n"
	dumpt(racialsSpells)
end

--fill the passed table with spells from talents and spellbook, affect only the active spec
function Details.FillTableWithPlayerSpells(completeListOfSpells)
	local GetItemStats = C_Item.GetItemStats
	local GetSpellInfo = GetSpellInfo or function(spellID)
		if not spellID then return nil end

		local spellInfo = C_Spell.GetSpellInfo(spellID)
		if spellInfo then
			return spellInfo.name, nil, spellInfo.iconID, spellInfo.castTime, spellInfo.minRange,
					spellInfo.maxRange, spellInfo.spellID, spellInfo.originalIconID
		end
	end
	local GetSpellTabInfo = GetSpellTabInfo or (function(tabLine)
		if not tabLine then return nil end
		local skillLine = C_SpellBook.GetSpellBookSkillLineInfo(tabLine)
		if skillLine then
			return skillLine.name, skillLine.iconID, skillLine.itemIndexOffset,
			skillLine.numSpellBookItems, skillLine.isGuild, skillLine.specID
		end
	end)

	local GetSpellBookItemInfo = C_SpellBook and C_SpellBook.GetSpellBookItemType or GetSpellBookItemInfo
	local IsPassiveSpell = C_SpellBook and C_SpellBook.IsSpellBookItemPassive or IsPassiveSpell
	local GetNumSpellTabs = C_SpellBook and C_SpellBook.GetNumSpellBookSkillLines or GetNumSpellTabs
	local spellBookPlayerEnum = Enum.SpellBookSpellBank and Enum.SpellBookSpellBank.Player or "player"
	local HasPetSpells = C_SpellBook and C_SpellBook.HasPetSpells or HasPetSpells
	local GetOverrideSpell = C_Spell and  C_Spell.GetOverrideSpell or C_SpellBook.GetOverrideSpell
	local GetSpellBookItemName = C_SpellBook and C_SpellBook.GetSpellBookItemName or GetSpellBookItemName 
	local spellBookPetEnum = Enum.SpellBookSpellBank and Enum.SpellBookSpellBank.Pet or "pet"

	local GetSpellCharges = GetSpellCharges or function(spellId)
		local chargesInfo = C_Spell.GetSpellCharges(spellId)
		if (chargesInfo) then
			return chargesInfo.currentCharges, chargesInfo.maxCharges, chargesInfo.cooldownStartTime, chargesInfo.cooldownDuration, chargesInfo.chargeModRate
		end
	end

    local specId, specName, _, specIconTexture = GetSpecializationInfo(GetSpecialization())
    local locPlayerRace, playerRace, playerRaceId = UnitRace("player")
    local generalIndex = Enum.SpellBookSkillLineIndex and Enum.SpellBookSkillLineIndex.General or CONST_SPELLBOOK_GENERAL_TABID
    local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(generalIndex) --CONST_SPELLBOOK_GENERAL_TABID

    if (not offset) then
        return completeListOfSpells
    end

    offset = offset + 1

	--get spells from the Spec spellbook
    for i = 1, GetNumSpellTabs() do --called "lines" in new v11 api
        local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(i)
		--print(tabName)
        --if (tabTexture == specIconTexture) then
            offset = offset + 1
            local tabEnd = offset + numSpells
            for entryOffset = offset, tabEnd - 1 do
                local spellType, spellId = GetSpellBookItemInfo(entryOffset, spellBookPlayerEnum)
                if (spellId) then
					--print(GetSpellInfo(spellId))
                    if (spellType == "SPELL" or spellType == 1) then
                        --print(tabName, tabTexture == specIconTexture, offset, tabEnd,spellType, spellId)
                        spellId = GetOverrideSpell(spellId)
                        local spellName = GetSpellInfo(spellId)
                        local bIsPassive = IsPassiveSpell(entryOffset, spellBookPlayerEnum)
                        if (spellName and not bIsPassive) then
                            completeListOfSpells[spellId] = true
                        end
                    end
                end
            end
        --end
    end

    --get class shared spells from the spell book
    local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(CONST_SPELLBOOK_CLASSSPELLS_TABID)
    offset = offset + 1
    local tabEnd = offset + numSpells
    for entryOffset = offset, tabEnd - 1 do
        local spellType, spellId = GetSpellBookItemInfo(entryOffset, spellBookPlayerEnum)
        if (spellId) then
            if (spellType == "SPELL" or spellType == 1) then
                spellId = GetOverrideSpell(spellId)
                local spellName = GetSpellInfo(spellId)
                local bIsPassive = IsPassiveSpell(entryOffset, spellBookPlayerEnum)

                if (spellName and not bIsPassive) then
                    completeListOfSpells[spellId] = true
                end
            end
        end
    end

    local getNumPetSpells = function()
        --'HasPetSpells' contradicts the name and return the amount of pet spells available instead of a boolean
        return HasPetSpells()
    end

    --get pet spells from the pet spellbook
    local numPetSpells = getNumPetSpells()
    if (numPetSpells) then
        for i = 1, numPetSpells do
            local spellName, _, unmaskedSpellId = GetSpellBookItemName(i, spellBookPetEnum)
            if (unmaskedSpellId) then
                unmaskedSpellId = GetOverrideSpell(unmaskedSpellId)
                local bIsPassive = IsPassiveSpell(i, spellBookPetEnum)
                if (spellName and not bIsPassive) then
                    completeListOfSpells[unmaskedSpellId] = true
                end
            end
        end
    end

    --dumpt(completeListOfSpells)
    return completeListOfSpells
end

function Details.SavePlayTimeOnClass()
	local className = select(2, UnitClass("player"))
	if (className) then
		--played time by  expansion
		local expansionLevel = GetExpansionLevel()

		local expansionTable = Details.class_time_played[expansionLevel]
		if (not expansionTable) then
			expansionTable = {}
			Details.class_time_played[expansionLevel] = expansionTable
		end

		local playedTime = expansionTable[className] or 0
		expansionTable[className] = playedTime + GetTime() - Details.GetStartupTime()
	end
end

function Details.GetPlayTimeOnClass()
	local className = select(2, UnitClass("player"))
	if (className) then
		--played time by  expansion
		local expansionLevel = GetExpansionLevel()

		local expansionTable = Details.class_time_played[expansionLevel]
		if (not expansionTable) then
			expansionTable = {}
			Details.class_time_played[expansionLevel] = expansionTable
		end

		local playedTime = expansionTable[className]
		if (playedTime) then
			playedTime = playedTime +(GetTime() - Details.GetStartupTime())
			return playedTime
		end
	end
	return 0
end

function Details.GetPlayTimeOnClassString()
    local playedTime = Details.GetPlayTimeOnClass()
    local days = floor(playedTime / 86400) .. " days"
    playedTime = playedTime % 86400
    local hours = floor(playedTime / 3600) .. " hours"
    playedTime = playedTime % 3600
    local minutes = floor(playedTime / 60) .. " minutes"

	local expansionLevel = GetExpansionLevel()
	local expansionName = _G["EXPANSION_NAME" .. GetExpansionLevel()]

    return "|cffffff00Time played this class(" .. expansionName .. "): " .. days .. " " .. hours .. " " .. minutes
end

--[=[
hooksecurefunc("ChatFrame_DisplayTimePlayed", function()
	if (Details.played_class_time) then
		C_Timer.After(0, function()
			local expansionName = _G["EXPANSION_NAME" .. GetExpansionLevel()]
			for fontString in ChatFrame1.fontStringPool:EnumerateActive() do
				if (fontString:GetText() and fontString:GetText():find(expansionName)) then
					return
				end
			end

			local levelText = TIME_PLAYED_LEVEL and TIME_PLAYED_LEVEL:gsub("%%s", "") or ""
			for fontString in ChatFrame1.fontStringPool:EnumerateActive() do
				if (fontString:GetText() and fontString:GetText():find(levelText)) then
					print(Details.GetPlayTimeOnClassString() .. " \ncommand: /details playedclass")
					break
				end
			end
		end)
	end
end)
--]=]

--game freeze prevention, there are people calling UpdateAddOnMemoryUsage() making the game client on the end user to freeze, this is bad, really bad.
--Details! replace the function call with one that do the same thing, but warns the player if the function freezes the client too many times.
--this feature is disabled by default, to enable it, type /run Details.check_stuttering = true and reload the game
local stutterCounter = 0
local bigStutterCounter = 0
local UpdateAddOnMemoryUsage_Original = _G.UpdateAddOnMemoryUsage
Details.UpdateAddOnMemoryUsage_Original = _G.UpdateAddOnMemoryUsage

Details.UpdateAddOnMemoryUsage_Custom = function()
	local currentTime = debugprofilestop()
	UpdateAddOnMemoryUsage_Original()
	local deltaTime = debugprofilestop() - currentTime

	if (deltaTime > 16) then
		local callStack = debugstack(2, 0, 4)
		--ignore if is coming from the micro menu tooltip
		if (callStack:find("MainMenuBarPerformanceBarFrame_OnEnter")) then
			return
		end

		if (deltaTime >= 500) then
			bigStutterCounter = bigStutterCounter + 1
			if (bigStutterCounter >= 6) then
				Details:Msg("an addon made your game freeze for more than a half second, use '/details perf' to know more.")
				bigStutterCounter = -10000 --make this msg appear only once
			end
		end

		stutterCounter = stutterCounter + 1
		local stutterDegree = 0
		if (stutterCounter > 60) then
			if (deltaTime < 48) then
				Details:Msg("some addon may be causing small framerate stuttering, use '/details perf' to know more.")
				stutterDegree = 1

			elseif (deltaTime <= 100) then
				Details:Msg("some addon may be causing framerate drops, use '/details perf' to know more.")
				stutterDegree = 2

			else
				Details:Msg("some addon might be causing performance issues, use '/details perf' to know more.")
				stutterDegree = 3
			end

			stutterCounter = -10000  --make this msg appear only once
		end

		Details.performanceData = {
			deltaTime = deltaTime,
			callStack = callStack,
			culpritFunc = "_G.UpdateAddOnMemoryUsage()",
			culpritDesc = "Calculates memory usage of addons",
		}
	end
end

Details.performanceData = {
	deltaTime = 0,
	callStack = "",
	culpritFunc = "",
	culpritDesc = "",
}

function Details:HandleRogueCombatSpecIconByGameVersion()
	local _, _, _, patchVersion = GetBuildInfo()
	if (patchVersion >= 70000) then --Legion
		--rogue combat is a rogue outlaw
		local rogueCombatCoords = Details.class_specs_coords[260]
		rogueCombatCoords[1] = 0
		rogueCombatCoords[2] = 64 / 512
		rogueCombatCoords[3] = 384 / 512
		rogueCombatCoords[4] = 448 / 512

		--new versions of the game has a different icon for assassination
		local rogueAssassinationCoords = Details.class_specs_coords[259]
		rogueAssassinationCoords[1] = 64 / 512
		rogueAssassinationCoords[2] = 128 / 512
		rogueAssassinationCoords[3] = 384 / 512
		rogueAssassinationCoords[4] = 448 / 512
	end
end

function CopyText(text) --[[GLOBAL]]
	if (not Details.CopyTextField) then
		Details.CopyTextField = CreateFrame("Frame", "DetailsCopyText", UIParent, "BackdropTemplate")
		Details.CopyTextField:SetHeight(14)
		Details.CopyTextField:SetWidth(120)
		Details.CopyTextField:SetPoint("center", UIParent, "center")
		Details.CopyTextField:SetBackdrop(backdrop)

		DetailsFramework:ApplyStandardBackdrop(Details.CopyTextField)

		tinsert(UISpecialFrames, "DetailsCopyText")

		Details.CopyTextField.textField = CreateFrame("editbox", nil, Details.CopyTextField, "BackdropTemplate")
		Details.CopyTextField.textField:SetPoint("topleft", Details.CopyTextField, "topleft")
		Details.CopyTextField.textField:SetAutoFocus(false)
		Details.CopyTextField.textField:SetFontObject("GameFontHighlightSmall")
		Details.CopyTextField.textField:SetAllPoints()
		Details.CopyTextField.textField:EnableMouse(true)

		Details.CopyTextField.textField:SetScript("OnEnterPressed", function()
			Details.CopyTextField.textField:ClearFocus()
			Details.CopyTextField:Hide()
		end)

		Details.CopyTextField.textField:SetScript("OnEscapePressed", function()
			Details.CopyTextField.textField:ClearFocus()
			Details.CopyTextField:Hide()
		end)

		Details.CopyTextField.textField:SetScript("OnChar", function()
			Details.CopyTextField.textField:ClearFocus()
			Details.CopyTextField:Hide()
		end)
	end

	C_Timer.After(0.1, function()
		Details.CopyTextField:Show()
		Details.CopyTextField.textField:SetFocus()
		Details.CopyTextField.textField:SetText(text)
		Details.CopyTextField.textField:HighlightText()
	end)
end


-------------------------------------------------------------------------
--> cache maintenance

function Details222.Cache.DoMaintenance()
	local currentTime = time()
	local delay = (86400 * 2) --2 days

	if (currentTime > Details.latest_spell_pool_access + delay) then
		local spellIdPoolBackup = DetailsFramework.table.copy({}, Details.spell_pool)

		Details:Destroy(Details.spell_pool)

		--preserve ignored spells spellId
		for spellId in pairs(Details.spellid_ignored) do
			Details.spell_pool[spellId] = spellIdPoolBackup[spellId]
		end

		Details.latest_spell_pool_access = currentTime
		Details:Destroy(spellIdPoolBackup)
	end

	if (currentTime > Details.latest_npcid_pool_access + delay) then
		local npcIdPoolBackup = DetailsFramework.table.copy({}, Details.npcid_pool)

		Details:Destroy(Details.npcid_pool)

		--preserve ignored npcs npcId
		for npcId in pairs(Details.npcid_ignored) do
			Details.npcid_pool[npcId] = npcIdPoolBackup[npcId]
		end
		Details.latest_npcid_pool_access = currentTime
		Details:Destroy(npcIdPoolBackup)
	end

	if (currentTime > Details.latest_encounter_spell_pool_access + delay) then
		Details:Destroy(Details.encounter_spell_pool)
		Details.latest_encounter_spell_pool_access = currentTime
	end

	if (Details.boss_mods_timers and Details.boss_mods_timers.latest_boss_mods_access) then
		if (currentTime > Details.boss_mods_timers.latest_boss_mods_access + delay) then
			Details:Destroy(Details.boss_mods_timers.encounter_timers_bw)
			Details:Destroy(Details.boss_mods_timers.encounter_timers_dbm)
			Details.boss_mods_timers.latest_boss_mods_access = currentTime
		end
	end

	--latest_shield_spellid_cache_access
	--shield_spellid_cache
end