-- ==================================================================================================================== --
--															--
--	File			NativeAPI.lua										--
--															--
--															--
--	Author			Zarnivoop						| Basis, thx	|		--
--															--
--										| Version	   :	 12.0.1 |	--
--										| Curse-Project-ID :	  17718 |	--
--										| Wago-ID	   :   Q6aEVa6W |	--
--										| License	   :	  MIT/X |	--
--															--
--															--
--				Baltha							| Revision	|		--
--										| Version	   :	 12.0.5 |	--
--										| Curse-Project-ID :	1188318 |	--
--										| License	   :	  MIT/X |	--
--															--
--															--
--										| Initial Version  : 2024-11-18 |	--
--										| Current Version  : 2026-05-17 |	--
--										|		   : Release	|	--
--															--
--															--
--	Path ( Revision )	"..\World of Warcraft\_retail_\Interface\AddOns\Skada\Misc\Changelog\ChangeLog.txt"	--
--															--
-- ==================================================================================================================== --


local _, Skada	= ...


local NativeAPI = {}
Skada.NativeAPI = NativeAPI


			NativeAPI.cache =

					{
						results	  = {}		,
						available = true	,
						ttl	  = 0.5		,
						lastCheck = 0

					}


	     NativeAPI.sessionTypeCache =

					{
						current		  = nil		,
						total		  = nil		,
						currentDamageType = 0		,
						totalDamageType	  = 0

					}


if not C_DamageMeter then error("Skada: C_DamageMeter API not available! This version requires WoW 12.0.0+") return end


local function getKeys(t)

	local keys = {}


	if type(t) == "table" then

		for k, _ in pairs(t) do	table.insert(keys, tostring(k))	end
	end


	return table.concat(keys, ", ")
end




function NativeAPI:GetSessionSource(sourceGUID, sessionType, damageType)

	if not sourceGUID				then return nil							   end
	if Skada.Simulation and Skada.Simulation.active then return Skada.Simulation:GetMockSource(sourceGUID, damageType) end
	

	local now   = GetTime()
	local cache = self.cache
	

	if now - cache.lastCheck > cache.ttl then

		cache.lastCheck = now
		cache.available = C_DamageMeter.IsDamageMeterAvailable()


		for key, entry in pairs(cache.results) do

			if entry.expires and now > entry.expires then cache.results[key] = nil end
		end
	end


	if not cache.available then return nil end


	local isSecret = issecretvalue and issecretvalue(sourceGUID)
	local cacheKey


	if not isSecret then

		cacheKey = "source_" .. tostring(sessionType) .. "_" .. tostring(damageType) .. "_" .. tostring(sourceGUID)


		local cached = cache.results[cacheKey]


		if cached and now < cached.expires then	return cached.value end
	end


	local success, result = pcall(C_DamageMeter.GetCombatSessionSourceFromType, sessionType, damageType, sourceGUID)


	if success and result then

		if not isSecret then cache.results[cacheKey] = {value = result, expires = now + cache.ttl} end


		return result 
	end

	
	local viewKey	 = "view_" .. tostring(sessionType) .. "_" .. tostring(damageType)
	local cachedView = cache.results[viewKey]
	local view


	if cachedView and now < cachedView.expires then

		view = cachedView.value
	else

		local success_view, res_view = pcall(C_DamageMeter.GetCombatSessionFromType, sessionType, damageType)


		view		       = success_view and res_view or false
		cache.results[viewKey] = {value = view, expires = now + cache.ttl}
	end


	if view and view.sessionID then

		local success_id, res_id = pcall(C_DamageMeter.GetCombatSessionSourceFromID, view.sessionID, sourceGUID)


		result = success_id and res_id or nil
	end


	if not isSecret then cache.results[cacheKey] = {value = result, expires = now + cache.ttl} end


	return result
end




function NativeAPI:GetSessionView(set, damageType)

	if not set then return nil end


	local sessionType = set.sessionType or (set == Skada.total and 0 or 1)


	if Skada.Simulation and Skada.Simulation.active and not InCombatLockdown() then	return Skada.Simulation:GetMockSession(sessionType, damageType)	end


	local now   = GetTime()
	local cache = self.cache


	if now - cache.lastCheck > cache.ttl then

		cache.lastCheck = now
		cache.available = C_DamageMeter.IsDamageMeterAvailable()


		for key, entry in pairs(cache.results) do

			if entry.expires and now > entry.expires then cache.results[key] = nil end
		end
	end


	if not cache.available then return set end


	local cacheKey = "view_" .. tostring(sessionType) .. "_" .. tostring(damageType)
	local cached   = cache.results[cacheKey]


	if cached and now < cached.expires then	return cached.value or set end


	local success, result = pcall(C_DamageMeter.GetCombatSessionFromType, sessionType, damageType)


	if success and result then

		cache.results[cacheKey] = {value = result, expires = now + cache.ttl}


		return result
	end


	cache.results[cacheKey] = {value = false, expires = now + cache.ttl}


	return set
end




function NativeAPI:GetPlayerRate(set, player, rateType)

	if not player then return 0 end


	return player.amountPerSecond or player.rate or 0
end




function NativeAPI:GetRaidRate(set, rateType)

	if not set then return 0 end


	local val = set.amountPerSecond or set.rate


	if val then return val end


	local totalRate = 0
	local sources   = set.combatSources or set.participants or {}


	for _, p in pairs(sources) do

		local prate = p.amountPerSecond or p.rate


		if not prate then prate = self:GetPlayerRate(set, p, rateType) end


		if issecretvalue and issecretvalue(prate) then return prate end


		totalRate = totalRate + (tonumber(prate) or 0)
	end


	return totalRate
end




local function DebugLogSource(source)

	Skada:Debug("GetPlayerSpells source type:", type(source))
	Skada:Debug("GetPlayerSpells source keys:", getKeys(source))


	for k, v in pairs(source) do

		if type(v) == "table" then

			Skada:Debug("  " .. k .. " (table) keys:", getKeys(v))


			local count = 0


			for _, item in pairs(v) do

				count = count + 1


				if count <= 3 then

					Skada:Debug("    Item " .. count .. " type:", type(item))


					if type(item) == "table" then Skada:Debug("    Item " .. count .. " keys:", getKeys(item)) end
				else

					break
				end
			end
		else

			Skada:Debug("  " .. k .. ":", type(v), v)
		end
	end
end




function NativeAPI:GetPlayerSpells(playerID, set, damageType)

	local sessionType = set and set.sessionType or 1
	local source	  = self:GetSessionSource(playerID, sessionType, damageType)


	if not source then

		if Skada.db.profile.debug then Skada:Debug("GetPlayerSpells: No source found for", playerID, "damageType", damageType) end


		return nil
	end


	if Skada.db.profile.debug then DebugLogSource(source) end


	local spellFields = {"combatSpells", "spells", "abilities", "damageSpells", "healingSpells"}


	for _, field in ipairs(spellFields) do

		local val = source[field]


		if val and (type(val) == "table" or (issecretvalue and issecretvalue(val))) then

			if Skada.db.profile.debug then Skada:Debug("Found spells in field:", field) end


			return val
		end
	end


	local itemCount		= 0
	local success, isSecret = pcall(function() return issecretvalue and issecretvalue(source) end)


	if success and isSecret then return source end


	if type(source) ~= "table" then	return nil end


	local looksLikeSpellsArray = true


	for _, v in pairs(source) do

		itemCount	= itemCount + 1


		local isVTable  = type(v) == "table"
		local isVSecret = issecretvalue and issecretvalue(v)


		if not isVTable and not isVSecret then

			looksLikeSpellsArray = false


			break
		end


		if isVTable and (not v.spellID and not v.abilityID and not v.totalAmount) then

			looksLikeSpellsArray = false


			break
		end
	end


	if looksLikeSpellsArray and itemCount > 0 then

		if Skada.db.profile.debug then Skada:Debug("Source appears to be spells array with", itemCount, "items") end


		return source
	end


	if Skada.db.profile.debug then Skada:Debug("No spells found in source")	end


	return nil
end




function NativeAPI:GetPlayerSpell(playerID, set, damageType, spellID)

	local spells = self:GetPlayerSpells(playerID, set, damageType)


	if spells then

		for _, spell in pairs(spells) do

			if type(spell) == "table" and spell.spellID == spellID then return spell end
		end
	end


	return nil
end




function NativeAPI:IsAvailable()

	local available = C_DamageMeter.IsDamageMeterAvailable()


	return available
end




function NativeAPI:GetAvailableSessions()

	local success, result = pcall(C_DamageMeter.GetAvailableCombatSessions)


	if success and result then

		return result
	else

		return {}
	end
end




function NativeAPI:GetSessionByID(sessionID)

	if not sessionID then return nil end


	local success, result = pcall(C_DamageMeter.GetCombatSessionFromID, sessionID)


	if success then

		return result
	else

		return nil
	end
end




function NativeAPI:GetSessionByType(sessionType)

	if not sessionType then return nil end


	local args = {}


	if type(sessionType) == "number" then

		args.sessionType = sessionType
		args.type	 = 0
	elseif type(sessionType) == "table" then

		args = sessionType
	else

		return nil
	end


	local success, result = pcall(C_DamageMeter.GetCombatSessionFromType, args.sessionType, args.type)


	if success then

		return result
	else

		return nil
	end
end




function NativeAPI:TestSessionTypes()

	-- ======================================
	-- |					|
	-- | Enum.DamageMeterSessionType values |
	-- |					|
	-- |  0 = Overall			|
	-- |  1 = Current			|
	-- |  2 = Expired			|
	-- |					|
	-- |					|
	-- | Enum.DamageMeterType values	|
	-- |					|
	-- |  0 = DamageDone			|
	-- |  1 = Dps				|
	-- |  2 = HealingDone			|
	-- |  3 = Hps				|
	-- |  4 = Absorbs			|
	-- |  5 = Interrupts			|
	-- |  6 = Dispels			|
	-- |  7 = DamageTaken			|
	-- |  8 = AvoidableDamageTaken		|
	-- |  9 = Deaths			|
	-- | 10 = EnemyDamageTaken		|
	-- |					|
	-- ======================================
	local sessionTypes = {0, 1, 2}


	for i, sessionType in ipairs(sessionTypes) do

		for damageType = 0, 8 do

			     local args =

					{

					    sessionType = sessionType		,
						   type	= damageType

					}


			local success, result = pcall(C_DamageMeter.GetCombatSessionFromType, args.sessionType, args.type)


			if success and result then

				if sessionType == 1 then

					self.sessionTypeCache.current		= sessionType
					self.sessionTypeCache.currentDamageType = damageType
				elseif sessionType == 0 then

					self.sessionTypeCache.total		 = sessionType
					self.sessionTypeCache.totalDamageType	 = damageType
				end


				break
			end
		end
	end
end




function NativeAPI:GetSessionByParams(sessionType, damageType)

	if Skada.Simulation and Skada.Simulation.active and not InCombatLockdown() then	return Skada.Simulation:GetMockSession(sessionType, damageType)	end


	local success, result = pcall(C_DamageMeter.GetCombatSessionFromType, sessionType, damageType)


	if success and result then return result end


	if Enum and Enum.DamageMeterSessionType then

		local enumSessionType


		if sessionType == 1 and Enum.DamageMeterSessionType.Current then

			enumSessionType = Enum.DamageMeterSessionType.Current
		elseif sessionType == 0 and Enum.DamageMeterSessionType.Overall then

			enumSessionType = Enum.DamageMeterSessionType.Overall
		end


		if enumSessionType then

			local enumDamageType  = damageType


			if Enum.DamageMeterType then

				  local typeMap =

						{

							[0]	= Enum.DamageMeterType.DamageDone			,
						--	[1]	= Enum.DamageMeterType.DPS				,
							[2]	= Enum.DamageMeterType.HealingDone			,
						--	[3]	= Enum.DamageMeterType.HPS				,
						--	[4]	= Enum.DamageMeterType.Absorbs				,
							[5]	= Enum.DamageMeterType.Interrupts			,
							[6]	= Enum.DamageMeterType.Dispels				,
							[7]	= Enum.DamageMeterType.DamageTaken			,
						--	[8]	= Enum.DamageMeterType.AvoidableDamageTaken	 	,
						--	[9]	= Enum.DamageMeterType.Deaths				,
						--	[10]	= Enum.DamageMeterType.EnemyDamageTaken

						}


				enumDamageType = typeMap[damageType] or damageType
			end


			success, result = pcall(C_DamageMeter.GetCombatSessionFromType, enumSessionType, enumDamageType)


			if success and result then return result end
		end
	end


	return nil
end




function NativeAPI:GetCurrentSession()

	return self:GetSessionByParams(self.sessionTypeCache.current or 1, self.sessionTypeCache.currentDamageType or 0)
end




function NativeAPI:GetTotalSession()

	return self:GetSessionByParams(self.sessionTypeCache.total or 0, self.sessionTypeCache.totalDamageType or 0)
end




function NativeAPI:GetDamageTakenSession()

	return self:GetSessionByParams(1, 7)
end




function NativeAPI:GetDispelsSession()

	return self:GetSessionByParams(1, 6)
end




function NativeAPI:GetInterruptsSession()

	return self:GetSessionByParams(1, 5)
end




function NativeAPI:GetHealingDoneSession()

	return self:GetSessionByParams(1, 2)
end




function NativeAPI:ResetAllSessions()

	local success, result = pcall(C_DamageMeter.ResetAllCombatSessions)


	return success
end




function NativeAPI:DumpAPI()

	print("=== C_DamageMeter API Dump ===")
	print("IsDamageMeterAvailable():", self:IsAvailable())
	print("\nAvailable Sessions:")


	local sessions = self:GetAvailableSessions()


	for i, session in ipairs(sessions) do

		print("  Session", i, ":")


		for k, v in pairs(session) do print("    " .. tostring(k) .. ":", tostring(v)) end
	end


	print("\nCurrent Session:")


	local current = self:GetCurrentSession()


	if current then

		for k, v in pairs(current) do

			if type(v) == "table" then

				print("  " .. k .. ": (table with", #v, "entries)")
			else

				print("  " .. k .. ":", tostring(v))
			end
		end
	else

		print("  No current session")
	end

	
	print("=== End Dump ===")
end


return NativeAPI