--[[
	Cerebro - Tooltip Injector Module
	
	PURPOSE:
	Injects Cerebro-relevant information into native WoW tooltips.
	
	1. Player Unit Tooltips - Shows EPGP summary (PR, EP/GP) when hovering a friendly player.
	2. Item Tooltips - Shows GP cost breakdown (Normal/Heroic/Mythic) for equippable items.
	
	SECURITY:
	Read-only. No mutations, no officer checks required.
	
	EXTERNAL DEPENDENCIES:
	- Models/Player.lua (GetEP, GetRawGP, GetPR)
	- Modules/EPGP.lua (GetItemGP, GetSlotBaseGP)
	- WoW API: GetItemInfoInstant, TooltipDataProcessor
]]--

-- luacheck: globals TooltipDataProcessor Enum C_TooltipInfo PLAYER_DIFFICULTY1 PLAYER_DIFFICULTY2 PLAYER_DIFFICULTY6

local AddonName, _PrivateTable = ...
local Cerebro = LibStub("AceAddon-3.0"):GetAddon(AddonName)
local TooltipInjector = Cerebro:NewModule("TooltipInjector", "AceEvent-3.0")

function TooltipInjector:OnEnable()
	-- Unit tooltips: Use UPDATE_MOUSEOVER_UNIT event instead of TooltipDataProcessor.
	-- WoW 12.0 Midnight runs TooltipDataProcessor callbacks in a tainted execution context
	-- where ALL values (data.guid, tooltip:GetUnit(), even captured upvalues) are secret.
	-- C_Timer.After(0) doesn't help because the captured tooltip reference carries taint.
	-- The UPDATE_MOUSEOVER_UNIT event runs in a fully untainted context where standard
	-- unit APIs (UnitIsPlayer, UnitName, etc.) work without restriction.
	self:RegisterEvent("UPDATE_MOUSEOVER_UNIT")

	-- Item tooltips via TooltipDataProcessor (items are not affected by unit taint)
	if TooltipDataProcessor and TooltipDataProcessor.AddTooltipPostCall then
		TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Item, function(tooltip, data)
			self:OnTooltipSetItem(tooltip, data)
		end)
	end
end

--[[=============================================
	Player Unit Tooltip
	Shows EPGP summary when hovering a friendly player.
	Uses UPDATE_MOUSEOVER_UNIT event to avoid WoW 12.0 taint.
===============================================]]

-- Guard tag to prevent duplicate injection if event fires multiple times
local CEREBRO_TOOLTIP_TAG = "CerebroEPGPInjected"

function TooltipInjector:UPDATE_MOUSEOVER_UNIT()
	-- All unit APIs are safe here — we're in an untainted event handler
	if not UnitIsPlayer("mouseover") then return end
	if not UnitIsFriend("player", "mouseover") then return end

	local name, realm = UnitName("mouseover")
	if not name then return end

	-- Build full name for DB lookup
	local fullName = (realm and realm ~= "") and (name .. "-" .. realm) or name
	local targetName = Cerebro:GetFullPlayerName(fullName)
	if not targetName then return end

	-- Ensure GameTooltip is visible and not already injected
	if not GameTooltip:IsVisible() then return end
	if GameTooltip[CEREBRO_TOOLTIP_TAG] then return end

	-- Guard: Only show EPGP info for players already tracked in our database.
	-- Without this check, Player:New() -> GetData() -> GetPlayerData() auto-creates
	-- a Pending record for ANY friendly player we hover, polluting the DB with
	-- random nearby players who have nothing to do with our guild or raids.
	if not Cerebro.db.global.players[targetName] then return end

	-- Add EPGP Info
	local player = Cerebro.Player:New(targetName)
	local ep = player:GetEP()
	local gp = player:GetRawGP()
	local pr = player:GetPR()

	GameTooltip:AddLine(" ")
	GameTooltip:AddLine("|cffffd700Cerebro:|r")
	GameTooltip:AddDoubleLine("PR:", string.format("|cffffffff%.2f|r", pr))
	GameTooltip:AddDoubleLine("EP / GP:", string.format("|cffffffff%d / %d|r", ep, gp))

	-- luacheck: ignore 122
	GameTooltip[CEREBRO_TOOLTIP_TAG] = true
	GameTooltip:Show()
end

-- Clear injection tag when tooltip hides (so next mouseover can inject fresh)
GameTooltip:HookScript("OnHide", function(self)
	self[CEREBRO_TOOLTIP_TAG] = nil
end)

--[[=============================================
	Item Tooltip
	Shows GP cost for all three raid difficulties
	on any equippable item.
===============================================]]

function TooltipInjector:OnTooltipSetItem(tooltip, _data)
	-- Only inject into the main GameTooltip (not comparison tooltips, embedded tooltips, etc.)
	if tooltip ~= GameTooltip then return end

	-- Get item link from the tooltip
	-- Use C_TooltipInfo if available (Retail 10.0+), or fallback to GetItem
	local itemLink
	if tooltip.GetTooltipData then
		local data = tooltip:GetTooltipData()
		if data and data.type == Enum.TooltipDataType.Item then
			-- Use the raw hyperlink if available (safer?) or reconstruct from ID?
			-- Actually if it's a Pattern, the tooltip type is Item.
			-- Let's trust the data.hyperlink if present.
			itemLink = data.hyperlink
		end
	end
	
	if not itemLink then
		-- Fallback for classic clients or nil data
		local _, link = tooltip:GetItem()
		itemLink = link
	end

	if not itemLink then return end

	-- Check user preference for tooltip visibility
	local mode = Cerebro.db.global.config.ui.tooltipMode or "always"
	if mode == "never" then return end
	if mode == "raid" and not IsInRaid() then return end

	-- Ensure EPGP module is loaded
	local EPGP = Cerebro.modules.EPGP
	if not EPGP then return end

	-- Parse item info
	local _, _, _, itemEquipLoc, _, classID, subclassID = C_Item.GetItemInfoInstant(itemLink)
	local isToken, tokenSlot = Cerebro:IsItemToken(itemLink)
	local isCurio = Cerebro:IsCurio(itemLink)

	-- Check for collectible item types (Toy, Pet, Decor) that have no equip location
	local isToy = false
	if C_ToyBox and C_ToyBox.GetToyInfo then
		local itemID = Cerebro:GetItemIDFromLink(itemLink)
		if itemID then
			local _, toyName = C_ToyBox.GetToyInfo(itemID)
			isToy = (toyName ~= nil)
		end
	end
	local isPet = (classID == 17) or (classID == 15 and subclassID == 2)
	local isMount = (classID == 15 and subclassID == 5)
	local isDecor = (classID == 20)
	local isCosmetic = C_Item.IsCosmeticItem and C_Item.IsCosmeticItem(itemLink)
	local isCollectible = isToy or isPet or isMount or isDecor or isCurio or isCosmetic

	-- Initial validity check: Must be equippable, a token, or a collectible
	if (not itemEquipLoc or itemEquipLoc == "") and not isToken and not isCollectible then return end

	-- Determine Slot key for GP lookup
	local slotKey = itemEquipLoc
	if isToken then
		if tokenSlot and Cerebro.TokenSlotToEquipLoc and Cerebro.TokenSlotToEquipLoc[tokenSlot] then
			slotKey = Cerebro.TokenSlotToEquipLoc[tokenSlot]
		else
			-- Fallback removed: Don't show tooltip if token slot is unknown
			return
		end
	end

	-- Use ItemMixin to handle async loading if needed
	local item = Item:CreateFromItemLink(itemLink)
	if item:IsItemEmpty() then return end

	item:ContinueOnItemLoad(function()
		-- Re-verify item link (tooltip might have changed)
		if not tooltip or not tooltip.GetItem then return end
		local _, currentLink = tooltip:GetItem()
		if currentLink ~= itemLink then return end
		
		-- Priority 1: Use TooltipData.id (Confirmed by user to be the most accurate for recipes)
		local finalID = nil
		if tooltip.GetTooltipData then
			local tData = tooltip:GetTooltipData()
			if tData and tData.id then
				finalID = tData.id
			end
		end

		-- Priority 2: Fallback to manual extraction from link
		if not finalID then
			finalID = Cerebro:GetItemIDFromLink(itemLink)
		end

		-- Verify classID is available (from outer scope via GetItemInfoInstant)
		if not classID then return end
		
		-- Handle overrides for special items
		if classID == 9 then
			slotKey = "INVTYPE_RECIPE"
		elseif isCurio then
			slotKey = "Curio"
		elseif isToy then
			slotKey = "INVTYPE_TOY"
		elseif isPet then
			slotKey = "INVTYPE_PET"
		elseif isMount then
			slotKey = "INVTYPE_MOUNT"
		elseif isDecor then
			slotKey = "INVTYPE_DECOR"
		elseif isCosmetic then
			slotKey = "INVTYPE_COSMETIC"
		end

		local baseGP = EPGP:GetSlotBaseGP(slotKey)
		if baseGP == 0 then return end
		
		-- Determine Difficulty
		-- item:itemID:enchant:gem1:gem2:gem3:gem4:suffixID:uniqueID:linkLevel:specID:upgradeID:difficultyID
		local difficultyID = 14 -- Default to Normal
		local parts = { strsplit(":", itemLink) }
		
		-- Standard position for Difficulty ID is index 13
		if parts[13] and parts[13] ~= "" then
			difficultyID = tonumber(parts[13]) or 14
		end
		
		-- Fallback: Use C_TooltipInfo if available (Modern Retail API) to scan for text to determine difficulty
		-- This checks for localized difficulty strings in the tooltip text if the Difficulty ID is missing or default (14/Normal).
		if tooltip.GetTooltipData then
			local tData = tooltip:GetTooltipData()
			if tData and tData.lines then
				for i, line in ipairs(tData.lines) do
					local text = line.leftText
					if text then
						-- Check Mythic first (Higher prio)
						if (PLAYER_DIFFICULTY6 and string.find(text, PLAYER_DIFFICULTY6)) then 
							difficultyID = 16 
							break 
						end
						-- Check Heroic
						if (PLAYER_DIFFICULTY2 and string.find(text, PLAYER_DIFFICULTY2)) then 
							difficultyID = 15 
							break 
						end
					end
				end
			end
		end

		-- Calculate GP for this specific difficulty
		-- Pass finalID (resolved Recipe ID) to ensure EPGP uses the correct item data
		local gp = EPGP:GetItemGP(itemLink, difficultyID, nil, nil, finalID)
		
		if gp and gp > 0 then
			-- Get friendly slot name for display
			local slotName = Cerebro:GetFriendlySlotName(slotKey)
			if isToken and slotKey == "Token" then slotName = "Token" end

			-- Check if line already exists to prevent duplicates (async callback might fire late)
			-- Simple check: Does format string match last line?
			-- Better: Just trust the link check above.
			
			-- Check for duplicate slot multiplier (e.g., 2H Weapon at 50%)
			local dupMultipliers = Cerebro.db.global.config
				and Cerebro.db.global.config.epgp
				and Cerebro.db.global.config.epgp.duplicateSlotMultipliers
			local dupMult = dupMultipliers and dupMultipliers[slotKey]
			
			-- Display formatted line: Cerebro(Slot): [Value] GP or [Reduced] GP
			tooltip:AddLine(" ")
			if dupMult and dupMult > 0 and dupMult < 1 then
				local reducedGP = math.floor(gp * dupMult)
				-- Gold "Cerebro(", White "Slot", Gold "):", then full GP "or" reduced GP
				tooltip:AddLine(string.format("|cffffd700Cerebro(|r|cffffffff%s|r|cffffd700):|r %s GP or %s GP",
					slotName, Cerebro:FormatNumber(gp), Cerebro:FormatNumber(reducedGP)))
			else
				-- Gold "Cerebro(", White "Slot", Gold "):"
				tooltip:AddLine(string.format("|cffffd700Cerebro(|r|cffffffff%s|r|cffffd700):|r %s GP", slotName, Cerebro:FormatNumber(gp)))
			end
			tooltip:Show()
		end
	end)
end -- End of function (async block replaces the rest)
-- Remove rest of function to avoid duplication/errors since we moved logic inside callback


