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


-- ==================================================================================
-- | Common patterns and utilities for Skada modules. This module provides reusable |
-- | functions to eliminate DRY violations across different Skada modules.	    |
-- ==================================================================================
local _, Skada	    = ...


local L		    = LibStub("AceLocale-3.0"):GetLocale("Skada", true)
local SecretHelper  = Skada.SecretHelper


local ModuleBase    = {}


Skada.ModuleBase    = ModuleBase


local pairs, ipairs = pairs, ipairs


-- ==============================================================
-- | Update a player list view (main module view). This handles |
-- | the common pattern for Damage, Healing, DamageTaken, etc.  |
-- ==============================================================	
function ModuleBase:UpdatePlayerList(win, set, options)

	if not set then return end


	local view = Skada.NativeAPI:GetSessionView(set, options.damageType)


	if not view then return end

	
	local sources	      = view.combatSources or {}
	local hasSecretAPI    = SecretHelper:HasSecretAPI()
	local cacheKey	      = "playerlist_" .. tostring(set.sessionType or 0) .. "_" .. tostring(options.damageType)
	local hasSecretValues = SecretHelper:DetectSecretsCached(cacheKey, sources, options.valueKey or "totalAmount")
	local totalValue      = 0


	if not hasSecretValues then

		for _, player in pairs(sources) do

			local value = player[options.valueKey] or player.totalAmount


			if value then totalValue = totalValue + (tonumber(value) or 0) end
		end
	end


	-- ===========================================
	-- | Update window metadata for secret state |
	-- ===========================================
	SecretHelper:UpdateWindowMetadata(win, hasSecretValues)


	local max = 0
	local nr  = 1


	if hasSecretValues then

		for _, player in pairs(sources) do

			local playerName = SecretHelper:GetPlayerName(player)


			if playerName then

				local rawValue = player[options.valueKey] or player.totalAmount
				local rawRate  = player[options.rateKey]  or player.rate
				local rate     = 0


				if not (hasSecretAPI and issecretvalue(rawRate)) then

					rate = tonumber(rawRate) or 0
				elseif options.getRateFunc then

					rate = options.getRateFunc(set, player)
				end


				local d = self:ReuseDatasetEntry(win.dataset, nr)


				d._is_nodata	= nil

				d.id		= "combat_" .. nr
				d.label		= playerName
				d.class		= SecretHelper:GetPlayerClass(player)
				d.role		= player.role
				d.order		= nr
				d.value		= SecretHelper:GetDisplayValue(rawValue, nr)

				
				local valueText = Skada:FormatNumberSecret(rawValue)
				local rateText  = Skada:FormatNumberSecret(rawRate or rate)


				if options.columns then

					Skada:FormatValueText(d, valueText, options.columns[1], rateText, options.columns[2], "", options.columns[3])
				else

					d.valuetext = valueText
				end


				nr = nr + 1
			end
		end


		win.metadata.maxvalue = 1000 - 1
	else

		for _, player in pairs(sources) do

			local playerName = player.name or player.unitName


			if playerName then

				local value = tonumber(player[options.valueKey] or player.totalAmount) or 0
				local rate  = tonumber(player[options.rateKey]  or player.rate)	       or 0

				
				if rate == 0 and options.getRateFunc then rate = options.getRateFunc(set, player) end


				local d		  = self:ReuseDatasetEntry(win.dataset, nr)


				d._is_nodata	  = nil

				d.id		  = player.sourceGUID or playerName
				d.label		  = playerName
				d.class		  = player.class or player.classFilename
				d.role		  = player.role
				d.order		  = nr
				d.value		  = value

				
				if value > max then max = value end


				local valueText	  = Skada:FormatNumber(value)
				local rateText	  = options.formatRate and options.formatRate(rate) or Skada:FormatNumber(rate)
				local percentText = (options.includePercent and totalValue > 0) and string.format("%02.1f%%", (value / totalValue) * 100) or nil


				if options.columns then

					Skada:FormatValueText(d, valueText, options.columns[1], rateText, options.columns[2], percentText, options.columns[3])
				else

					d.valuetext = valueText
				end


				nr = nr + 1
			end
		end


		win.metadata.maxvalue = (max > 0 and max or 1)
	end
end




-- ==============================================================================
-- | Update a simple player list without rates (e.g. Dispels, Interrupts, etc.) |
-- ==============================================================================
function ModuleBase:UpdateSimpleList(win, set, options)

	if not set then return end


	local view = Skada.NativeAPI:GetSessionView(set, options.damageType)


	if not view then return end


	local sources	      = view.combatSources or {}
	local hasSecretAPI    = SecretHelper:HasSecretAPI()
	local cacheKey	      = "simplelist_" .. tostring(set.sessionType or 0) .. "_" .. tostring(options.damageType)
	local hasSecretValues = SecretHelper:DetectSecretsCached(cacheKey, sources, options.valueKey or "totalAmount")


	SecretHelper:UpdateWindowMetadata(win, hasSecretValues)


	local max = 0
	local nr  = 1


	if hasSecretValues then

		for _, player in pairs(sources) do

			local playerName = SecretHelper:GetPlayerName(player)


			if playerName then

				local rawValue = player[options.valueKey] or player.totalAmount
				local isSecret = hasSecretAPI and rawValue and issecretvalue(rawValue)
				local value    = SecretHelper:SafeNumber(rawValue)


				if value > 0 or isSecret then


					local d	     = self:ReuseDatasetEntry(win.dataset, nr)

					d._is_nodata = nil
					d.id	     = "combat_" .. nr
					d.label	     = playerName
					d.class	     = SecretHelper:GetPlayerClass(player)
					d.role	     = player.role
					d.order	     = nr
					d.value	     = SecretHelper:GetDisplayValue(rawValue, nr)
					d.valuetext  = Skada:FormatNumberSecret(rawValue)


					nr = nr + 1
				end
			end
		end
	else

		for _, player in pairs(sources) do

			local playerName = player.name or player.unitName


			if playerName then

				local value = tonumber(player[options.valueKey] or player.totalAmount) or 0


				if value > 0 then

					local d	     = self:ReuseDatasetEntry(win.dataset, nr)


					d._is_nodata = nil

					d.id	     = player.sourceGUID or playerName
					d.label      = playerName
					d.class      = player.class or player.classFilename
					d.role       = player.role
					d.order      = nr
					d.value	     = value
					d.valuetext  = Skada:FormatNumber(value)
					
					if value > max then max = value end


					nr = nr + 1
				end
			end
		end


		max = (max > 0 and max or 1)
	end

	
	win.metadata.maxvalue = hasSecretValues and (1000 - 1) or max
end




-- ================================================================================================
-- | Update a spell list view (detail view). This handles the common pattern for spell breakdowns |
-- ================================================================================================
function ModuleBase:UpdateSpellList(win, playerid, set, damageType, options)

	options	       = options or {}


	local valueKey = options.valueKey or "totalAmount"
	local view     = Skada.NativeAPI:GetSessionView(set, damageType)
	local player   = Skada:find_player_in_session(view, playerid)


	if not player then player = Skada:find_player(set, playerid) end
	if not player then return				     end

	
	local realID = player.sourceGUID or player.guid or player.unitGUID or player.id or playerid
	local spells = Skada.NativeAPI:GetPlayerSpells(realID, view or set, damageType)

	
	if not spells then return end


	local hasSecretAPI    = SecretHelper:HasSecretAPI()
	local cacheKey	      = "spelllist_" .. tostring(set.sessionType or 0) .. "_" .. tostring(damageType)
	local hasSecretValues = SecretHelper:DetectSecretsCached(cacheKey, spells, valueKey)
	local totalValue      = 0


	if not hasSecretValues then

		for _, spell in pairs(spells) do

			if type(spell) == "table" then

				local value = spell[valueKey]


				if value then totalValue = totalValue + (tonumber(value) or 0) end
			end
		end
	end


	local max = 0
	local nr  = 1


	if hasSecretValues then

		for _, spell in pairs(spells) do

			if type(spell) == "table" or (hasSecretAPI and issecretvalue(spell)) then

				local rawValue = spell[valueKey]


				if rawValue then

					local isSecret = hasSecretAPI and issecretvalue(rawValue)


					if rawValue ~= 0 or isSecret then

						local spellID	= spell.spellID or 0
						local d		= self:ReuseDatasetEntry(win.dataset, nr)


						d._is_nodata	= nil
						d.id		= spellID


						local spellInfo = spellID > 0 and C_Spell.GetSpellInfo(spellID)


						d.label		= spellInfo and spellInfo.name or ("Spell " .. tostring(spellID))
						d.value		= SecretHelper:GetDisplayValue(rawValue, nr)
						d.valuetext	= Skada:FormatNumberSecret(rawValue)
						d.icon		= Skada:GetSpellIcon(spellID)


						nr = nr + 1
					end
				end
			end
		end


		win.metadata.maxvalue = 1000 - 1
	else

		for _, spell in pairs(spells) do

			if type(spell) == "table" then

				local value = tonumber(spell[valueKey]) or 0


				if value ~= 0 then

					local spellID	= spell.spellID or 0
					local d		= self:ReuseDatasetEntry(win.dataset, nr)


					d._is_nodata	= nil
					d.id		= spellID


					local spellInfo = spellID > 0 and C_Spell.GetSpellInfo(spellID)


					d.label = spellInfo and spellInfo.name or ("Spell " .. tostring(spellID))
					d.value = value


					if value > max then max = value end


					if totalValue > 0 then

						d.valuetext = Skada:FormatNumber(value) .. string.format(" (%02.1f%%)", (value / totalValue) * 100)
					else

						d.valuetext = Skada:FormatNumber(value)
					end


					d.icon = Skada:GetSpellIcon(spellID)
					nr     = nr + 1
				end
			end
		end


		win.metadata.maxvalue = (max > 0 and max or 1)
	end
end




function ModuleBase:GetSetTotal(set, damageType, options)

	if not set then return 0 end


	options = options or {}


	local valueKey	   = options.valueKey or "totalAmount"
	local sourcesKey   = options.sourcesKey or "combatSources"
	local view	   = Skada.NativeAPI:GetSessionView(set, damageType)


	if not view then return 0 end


	local total	   = 0
	local sources	   = view[sourcesKey] or view.combatSources or {}
	local hasSecretAPI = SecretHelper:HasSecretAPI()


	for _, p in pairs(sources) do

		local value = p[valueKey] or p.totalAmount


		if value then

			if hasSecretAPI and issecretvalue(value) then return value end


			total = total + (tonumber(value) or 0)
		end
	end


	return total
end




function ModuleBase:GetPlayerRate(set, player, rateType)

	return Skada.NativeAPI:GetPlayerRate(set, player, rateType)
end




function ModuleBase:GetRaidRate(set, rateType)

	return Skada.NativeAPI:GetRaidRate(set, rateType)
end




function ModuleBase:CreatePlayerTooltip(options)

	return function(win, id, label, tooltip)


		local set = win:get_selected_set()


		if not set then return end


		local view = Skada.NativeAPI:GetSessionView(set, options.damageType)


		if not view then return end


		local player = Skada:find_player_in_session(view, id)


		if not player then player = Skada:find_player(set, id) end
		if not player then return end


		local totaltime = Skada:GetSetTime(view)
		local rawValue  = player[options.valueKey] or player.totalAmount or 0
		local rawRate   = player[options.rateKey] or player.amountPerSecond or 0


		tooltip:AddLine((player.name or label) .. " - " .. options.labelRate)
		tooltip:AddDoubleLine(L["Segment time"], totaltime .. "s", 1, 1, 1, 1, 1, 1)
		tooltip:AddDoubleLine(options.labelDamage, Skada:FormatNumberSecret(rawValue), 1, 1, 1, 1, 1, 1)
		tooltip:AddDoubleLine(options.labelRate, Skada:FormatNumberSecret(rawRate), 1, 1, 1, 1, 1, 1)


		local realID = player.sourceGUID or player.guid or player.unitGUID or player.id or id
		local spells = Skada.NativeAPI:GetPlayerSpells(realID, view, options.damageType)


		if spells then

			local sorted	    = {}
			local spellValueKey = options.spellValueKey or "totalAmount"

			
			for _, s in pairs(spells) do

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

					local rawAmount = s[spellValueKey]


					if rawAmount then table.insert(sorted, s) end
				end
			end

			
			table.sort(sorted,

					function(a, b)

						return SecretHelper:SafeNumber(a[spellValueKey]) > SecretHelper:SafeNumber(b[spellValueKey])
					end

				  )


			if #sorted > 0 then

				tooltip:AddLine(" ")
				tooltip:AddLine(L["Top Spells"])


				for i = 1, math.min(3, #sorted) do

					local s = sorted[i]


					do

						local spellID	= s.spellID or 0
						local spellInfo = spellID > 0 and C_Spell.GetSpellInfo(spellID)
						local name	= spellInfo and spellInfo.name or ("Spell " .. tostring(spellID))
						local rawVal	= s[spellValueKey] or 0


						if SecretHelper:HasSecretAPI() and issecretvalue(rawVal) then

							tooltip:AddDoubleLine(name, Skada:FormatNumberSecret(rawVal), 1, 1, 1, 1, 1, 1)
						else

							local val	= tonumber(rawVal) or 0
							local playerVal = SecretHelper:SafeNumber(rawValue)
							local percent	= playerVal > 0 and (val / playerVal) * 100 or 0


							tooltip:AddDoubleLine(name, Skada:FormatNumber(val) .. " (" .. string.format("%02.1f%%", percent) .. ")", 1, 1, 1, 1, 1, 1)
						end
					end
				end
			end
		end
	end
end




-- =============================================
-- | Simple tooltip handler without top spells |
-- =============================================
function ModuleBase:CreateSimpleTooltip(labelTotal)

	return function(win, id, label, tooltip)


		local set = win:get_selected_set()


		if not set then return end


		local player = Skada:find_player(set, id)


		if player then

			local rawValue = player.totalAmount or 0


			tooltip:AddDoubleLine(labelTotal, Skada:FormatNumberSecret(rawValue), 1, 1, 1, 1, 1, 1)
		end
	end
end




-- =====================================================================================================================
-- | Damage share tooltip handler (post-tooltip). Shows what percentage of total damage/healing the player contributed |
-- =====================================================================================================================
function ModuleBase:CreateDamageShareTooltip(options)

	return function(win, id, label, tooltip)


		local set = win:get_selected_set()


		if not set then return end


		local player = Skada:find_player(set, id)


		if not player then return end


		local rawPlayerValue = player.totalAmount or 0


		if SecretHelper:HasSecretAPI() and issecretvalue(rawPlayerValue) then return end


		local playerValue = tonumber(rawPlayerValue) or 0
		local totalValue  = 0
		local view	  = Skada.NativeAPI:GetSessionView(set, options.damageType)


		if view then

			local sources = view.combatSources or view.participants or {}


			for _, p in pairs(sources) do totalValue = totalValue + SecretHelper:SafeNumber(p.totalAmount) end
		end


		if totalValue > 0 then

			local percent = (playerValue / totalValue) * 100


			tooltip:AddDoubleLine(options.labelShare, ("%02.1f%%"):format(percent), 255, 255, 255, 255, 255, 255)
		end
	end
end




-- ====================================================
-- | Format set summary with value and optional rate. |
-- ====================================================
function ModuleBase:FormatSetSummary(datasetItem, set, options)

	local total = self:GetSetTotal(set, options.damageType, {valueKey = options.valueKey})


	if options.rateType then

		local rate = self:GetRaidRate(set, options.rateType)


		Skada:FormatValueText(datasetItem, Skada:FormatNumberSecret(total), options.columns[1],	Skada:FormatNumberSecret(rate), options.columns[2])
	else

		Skada:FormatValueText(datasetItem, Skada:FormatNumberSecret(total), true)
	end
end




-- =============================================================
-- | Standard feeds for a module (Personal rate and Raid rate) |
-- =============================================================
function ModuleBase:AddStandardFeeds(feedNamePersonal, feedNameRaid, rateType, label, labelRaid)

	Skada:AddFeed(feedNamePersonal,

				function()

					if Skada.current then

						local player = Skada:find_player(Skada.current, UnitGUID("player"))


						if player then

							local rate = self:GetPlayerRate(Skada.current, player, rateType)


							return string.format("%s %s", Skada:FormatNumberSecret(rate), label or "")
						end
					end
				end

		     )


	Skada:AddFeed(feedNameRaid,

				function()

					if Skada.current then

						local rate = self:GetRaidRate(Skada.current, rateType)


						return string.format("%s %s", Skada:FormatNumberSecret(rate), labelRaid or "")
					end
				end

		     )
end




function ModuleBase:RemoveStandardFeeds(feedNamePersonal, feedNameRaid)

	Skada:RemoveFeed(feedNamePersonal)
	Skada:RemoveFeed(feedNameRaid)
end




function ModuleBase:ReuseDatasetEntry(dataset, index)

	local entry = dataset[index]


	if entry then

		for k, v in pairs(entry) do entry[k] = nil end


		return entry
	else

		entry	       = {}
		dataset[index] = entry


		return entry
	end
end