--[[
	Cerebro - My Progress UI (Native Frame Edition)
	Executive Dashboard + LFG-Style History
	
	IMPORTANT: All native frames are tracked in self.nativeFrames and cleaned up on tab switch.
]]--

local AddonName, PrivateTable = ...
local Cerebro = LibStub("AceAddon-3.0"):GetAddon(AddonName)
local AceGUI = LibStub("AceGUI-3.0")
local MyProgress = Cerebro:NewModule("MyProgress", "AceEvent-3.0")

-- Visual Constants
local ROW_HEIGHT = 40

function MyProgress:OnInitialize()
	self.showAll = false
	self.nativeFrames = {} -- Track all native frames for cleanup
end

function MyProgress:OnEnable()
	self:RegisterMessage("Cerebro_EPGP_Update", "RefreshIfVisible")
	self:RegisterMessage("Cerebro_MPlus_Update", "RefreshIfVisible")
end

function MyProgress:RefreshIfVisible()
	-- If My Progress tab is currently visible, redraw it
	local UI = Cerebro:GetModule("UI", true)
	if UI and UI.tabGroup and UI.currentTab == "myprogress" then
		UI:SelectTab(UI.tabGroup, "myprogress")
	end
end

-- Cleanup all native frames
function MyProgress:CleanupNativeFrames()
	for _, frame in ipairs(self.nativeFrames or {}) do
		if frame and frame.Hide then
			frame:Hide()
			frame:SetParent(nil)
			frame:ClearAllPoints()
		end
	end
	self.nativeFrames = {}
end

-- Track a frame for cleanup
function MyProgress:TrackFrame(frame)
	if frame then
		table.insert(self.nativeFrames, frame)
	end
	return frame
end

--[[=============================================
	Draw Interface
===============================================]]

function MyProgress:Draw(container)
	-- CRITICAL: Clean up any leftover frames from previous draw
	self:CleanupNativeFrames()
	
	container:ReleaseChildren()
	
	-- Use simple group (no scroll) for outer container
	local outer = AceGUI:Create("SimpleGroup")
	outer:SetLayout("Flow")
	outer:SetFullWidth(true)
	outer:SetFullHeight(true)
	container:AddChild(outer)
	
	-- Set cleanup callback
	outer:SetCallback("OnRelease", function()
		self:CleanupNativeFrames()
	end)

	-- 1. Hero Section (Fixed Height Native Frame)
	self:DrawHeroSection(outer)

	-- Spacer
	local spacer = AceGUI:Create("Label")
	spacer:SetText(" ") 
	spacer:SetFullWidth(true)
	outer:AddChild(spacer)

	-- 2. M+ History Section (has its own internal scroll)
	self:DrawMPlusHistory(outer)
end

--[[=============================================
	Hero Section (Revised Layout)
===============================================]]

function MyProgress:DrawHeroSection(container)
	-- Container for the Native Frame
	local wrapper = AceGUI:Create("SimpleGroup")
	wrapper:SetLayout("Fill")
	wrapper:SetFullWidth(true)
	wrapper:SetHeight(120)
	container:AddChild(wrapper)
	
	local frame = wrapper.frame
	local custom = self:TrackFrame(CreateFrame("Frame", nil, frame, "BackdropTemplate"))
	custom:SetAllPoints()
	
	-- Cleanup hook
	wrapper:SetCallback("OnRelease", function() 
		self:CleanupNativeFrames()
	end)

	local myName = Cerebro:GetFullPlayerName(UnitName("player"))
	local player = Cerebro.Player:New(myName)
	local playerData = Cerebro:GetPlayerData(myName) or { ep = 0, gp = 200, pr = 0 }
	
	-- 1. 3D Portrait (Left)
	local model = self:TrackFrame(CreateFrame("PlayerModel", nil, custom, "BackdropTemplate"))
	model:SetSize(180, 120)
	model:SetPoint("LEFT", 10, 0)
	model:SetUnit("player")
	model:SetPortraitZoom(0.9)
	model:SetCamDistanceScale(0.8)
	
	-- 2. Stats Block (Right of Model)
	local nextX = 200
	
	-- KPI Tiles in Equation Format: EP ÷ GP = PR
	local pr = player:GetPR()
	local ep = playerData.ep or 0
	local gp = playerData.gp or 200
	
	-- Tooltip definitions
	local tooltipEP = "Effort Points\n\nEarned by raiding, killing bosses,\nand completing Mythic+ dungeons.\n\n|cff00ff00Higher EP = more contribution|r"
	local tooltipGP = "Gear Points\n\nIncreases when you receive loot.\n\n|cffff8800Higher GP = more gear received|r"
	local tooltipPR = "Priority Rating\n\nCalculated as EP ÷ GP.\n\n|cffFFD700Higher PR = higher loot priority|r"
	
	-- EP Tile (first in equation)
	local tileEP = self:CreateStatTile(custom, "EP", tostring(ep), {1, 1, 1}, tooltipEP)
	tileEP:SetPoint("TOPLEFT", nextX, -10)
	
	-- Division symbol (vertically centered with tiles)
	local divSymbol = custom:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
	divSymbol:SetPoint("LEFT", tileEP, "RIGHT", 8, 0)
	divSymbol:SetText("|cffFFD700÷|r")
	
	-- GP Tile (anchored to same baseline as EP)
	local tileGP = self:CreateStatTile(custom, "GP", tostring(gp), {0.5, 0.5, 0.5}, tooltipGP)
	tileGP:SetPoint("LEFT", divSymbol, "RIGHT", 8, 0)
	
	-- Equals symbol (vertically centered with tiles)
	local eqSymbol = custom:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
	eqSymbol:SetPoint("LEFT", tileGP, "RIGHT", 8, 0)
	eqSymbol:SetText("|cffFFD700=|r")
	
	-- PR Tile (result - slightly larger and emphasized)
	local tilePR = self:CreateStatTile(custom, "PR", string.format("%.2f", pr), {1, 0.82, 0}, tooltipPR, true)
	tilePR:SetPoint("LEFT", eqSymbol, "RIGHT", 8, 0)
	
	-- PR Rank display (right of PR tile)
	-- Huge "Rank" label
	local prRank = self:CalculatePRRank(myName)
	local prRankLabel = custom:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
	prRankLabel:SetPoint("TOPLEFT", tilePR, "TOPRIGHT", 15, -2)
	prRankLabel:SetText("|cffFFD700Rank|r")
	
	-- Massive Rank Number (1.5x Huge)
	local prRankValue = custom:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
	prRankValue:SetPoint("TOP", prRankLabel, "BOTTOM", 0, -4)
	prRankValue:SetText("|cffFFD700" .. prRank .. "|r")
	
	-- Force larger size (32pt)
	local fontPath = GameFontNormalHuge:GetFont()
	prRankValue:SetFont(fontPath, 32, "OUTLINE")
	
	-- Name/Rank (Far Right)
	local name = UnitName("player")
	local _, guildRankName = GetGuildInfo("player")
	guildRankName = guildRankName or "No Guild"
	
	local nameFS = custom:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
	nameFS:SetPoint("TOPRIGHT", custom, "TOPRIGHT", -20, -15)
	nameFS:SetText(Cerebro:GetClassColorString(name) .. name .. "|r")
	
	local rankFS = custom:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
	rankFS:SetPoint("TOPRIGHT", nameFS, "BOTTOMRIGHT", 0, -2)
	rankFS:SetText(guildRankName)
	
	if Cerebro:IsOfficer() then
		local badge = custom:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
		badge:SetPoint("TOPRIGHT", rankFS, "BOTTOMRIGHT", 0, -2)
		badge:SetText("|cffFFD700[Officer]|r")
	end
	
	-- Readiness Bar (Aligned under the equation tiles)
	local epgp = Cerebro:GetModule("EPGP")
	local readyCur = epgp:GetReadiness(myName)
	local readyCap = Cerebro.db.global.config.epgp.readinessCap or 1000
	local readyPct = (readyCap > 0) and math.min(readyCur / readyCap, 1.0) or 0
	
	-- Calculate bar width to match tile span: EP(70) + div(20) + GP(70) + eq(20) + PR(80) + spacing = ~285
	local bar = self:TrackFrame(CreateFrame("StatusBar", nil, custom, "BackdropTemplate"))
	bar:SetSize(285, 24)  -- 50% taller (was 16)
	bar:SetPoint("TOPLEFT", tileEP, "BOTTOMLEFT", 0, -8)
	bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
	bar:SetStatusBarColor(0, 1, 0)
	bar:SetMinMaxValues(0, 1)
	bar:SetValue(readyPct)
	
	-- Bar BG
	local bbg = bar:CreateTexture(nil, "BACKGROUND")
	bbg:SetAllPoints()
	bbg:SetColorTexture(0.2, 0.2, 0.2, 0.5)
	
	-- Bar Border
	bar:SetBackdrop({edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 8})
	
	local barText = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
	barText:SetPoint("CENTER", 0, 0)
	barText:SetText(string.format("Readiness: %d%% (%d/%d)", readyPct * 100, readyCur, readyCap))
	
	-- Hover tooltip for readiness bar
	bar:EnableMouse(true)
	bar:SetScript("OnEnter", function(self)
		GameTooltip:SetOwner(self, "ANCHOR_TOP")
		GameTooltip:AddLine("Readiness")
		GameTooltip:AddLine(" ")
		GameTooltip:AddLine("EP earned from Mythic+ and other", 1, 1, 1, true)
		GameTooltip:AddLine("activities outside of raid.", 1, 1, 1, true)
		GameTooltip:AddLine(" ")
		GameTooltip:AddLine("|cffff8800Resets weekly.|r", 1, 1, 1, true)
		GameTooltip:AddLine(" ")
		GameTooltip:AddDoubleLine("Current:", readyCur, 1, 1, 1, 0.2, 1, 0.2)
		GameTooltip:AddDoubleLine("Cap:", readyCap, 1, 1, 1, 1, 0.82, 0)
		GameTooltip:Show()
	end)
	bar:SetScript("OnLeave", function()
		GameTooltip:Hide()
	end)
end

function MyProgress:CreateStatTile(parent, label, value, color, tooltip, emphasized)
	local f = self:TrackFrame(CreateFrame("Frame", nil, parent, "BackdropTemplate"))
	
	-- Emphasized tiles (PR) are slightly larger
	local width = emphasized and 80 or 70
	local height = emphasized and 55 or 50
	f:SetSize(width, height)
	
	-- Border color: gold for emphasized, gray for normal
	local borderR, borderG, borderB = 0.4, 0.4, 0.4
	if emphasized then
		borderR, borderG, borderB = 1, 0.82, 0  -- Gold border
	end
	
	f:SetBackdrop({
		bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
		edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
		edgeSize = 12,
		insets = { left = 2, right = 2, top = 2, bottom = 2 }
	})
	f:SetBackdropColor(0.1, 0.1, 0.1, 0.8)
	f:SetBackdropBorderColor(borderR, borderG, borderB, 1)
	
	local v = f:CreateFontString(nil, "OVERLAY", emphasized and "GameFontNormalHuge" or "GameFontNormalLarge")
	v:SetPoint("CENTER", 0, 5)
	v:SetText(value)
	v:SetTextColor(unpack(color))
	
	local l = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
	l:SetPoint("BOTTOM", 0, 5)
	l:SetText(string.upper(label))
	l:SetTextColor(0.6, 0.6, 0.6)
	
	-- Add tooltip on hover
	if tooltip then
		f:EnableMouse(true)
		f:SetScript("OnEnter", function(self)
			GameTooltip:SetOwner(self, "ANCHOR_TOP")
			GameTooltip:AddLine(tooltip)
			GameTooltip:Show()
		end)
		f:SetScript("OnLeave", function()
			GameTooltip:Hide()
		end)
	end
	
	return f
end

--[[=============================================
	M+ History (Native Row Injection)
===============================================]]

function MyProgress:DrawMPlusHistory(container)
	-- Calculate if we need scroll or not
	local MPlus = Cerebro:GetModule("MythicPlus")
	local totalRuns = 0
	if Cerebro.db.global.mplusRuns then
		totalRuns = #Cerebro.db.global.mplusRuns -- Count ALL runs including test runs
	end
	
	local limit = self.showAll and 100 or 5
	local history = MPlus:GetHistory(limit, true)
	local needScroll = self.showAll and #history > 5
	
	-- Main group
	local group = AceGUI:Create("InlineGroup")
	group:SetTitle("Recent Mythic+ Runs")
	group:SetFullWidth(true)
	if needScroll then
		group:SetFullHeight(true)
		group:SetLayout("Fill")
	else
		group:SetLayout("List")
	end
	container:AddChild(group)
	
	-- Cleanup hook
	group:SetCallback("OnRelease", function()
		self:CleanupNativeFrames()
	end)
	
	-- Choose container for rows: scroll or direct
	local rowContainer
	if needScroll then
		local scroll = AceGUI:Create("ScrollFrame")
		scroll:SetLayout("Flow")
		scroll:SetFullWidth(true)
		scroll:SetFullHeight(true)
		group:AddChild(scroll)
		rowContainer = scroll
	else
		rowContainer = group
	end

	-- Sync timestamp
	local syncTime = Cerebro.db.global.syncStatus and Cerebro.db.global.syncStatus.lastOfficerSync
	local syncText = syncTime and self:FormatRelativeTime(syncTime) or "Unknown"
	
	local syncLabel = AceGUI:Create("Label")
	syncLabel:SetText("|cff888888Last Synced: " .. syncText .. "|r")
	syncLabel:SetFullWidth(true)
	rowContainer:AddChild(syncLabel)

	-- Native Header Row
	self:CreateRowFrame(rowContainer, nil, true)

	if #history == 0 then
		local label = AceGUI:Create("Label")
		label:SetText("\n|cff888888No runs recorded yet.\nComplete a dungeon to see history.|r")
		label:SetFullWidth(true)
		rowContainer:AddChild(label)
		return 
	end

	for i, run in ipairs(history) do
		self:CreateRowFrame(rowContainer, run, false, (i % 2 == 0))
	end
	
	-- Show All / Show Less button (OUTSIDE scroll, added to outer container)
	if not self.showAll and totalRuns > 5 then
		local btn = AceGUI:Create("Button")
		btn:SetText("Show All (" .. totalRuns .. " runs)")
		btn:SetWidth(150)
		btn:SetCallback("OnClick", function()
			self.showAll = true
			local UI = Cerebro:GetModule("UI", true)
			if UI and UI.tabGroup then 
				UI:SelectTab(UI.tabGroup, "myprogress")
			end
		end)
		container:AddChild(btn)
	elseif self.showAll then
		local btn = AceGUI:Create("Button")
		btn:SetText("Show Less")
		btn:SetWidth(100)
		btn:SetCallback("OnClick", function()
			self.showAll = false
			local UI = Cerebro:GetModule("UI", true)
			if UI and UI.tabGroup then 
				UI:SelectTab(UI.tabGroup, "myprogress")
			end
		end)
		container:AddChild(btn)
	end
end

function MyProgress:CreateRowFrame(container, run, isHeader, isStripe)
	-- 1. AceGUI Wrapper to reserve space in the layout
	local wrapper = AceGUI:Create("SimpleGroup")
	wrapper:SetLayout("Fill")
	wrapper:SetFullWidth(true)
	wrapper:SetHeight(isHeader and 24 or ROW_HEIGHT)
	container:AddChild(wrapper)

	-- 2. Native Frame (tracked for cleanup)
	local f = self:TrackFrame(CreateFrame("Frame", nil, wrapper.frame, "BackdropTemplate"))
	f:SetAllPoints()

	-- Background Stripe
	if isStripe and not isHeader then
		local bg = f:CreateTexture(nil, "BACKGROUND")
		bg:SetAllPoints()
		bg:SetColorTexture(1, 1, 1, 0.05)
	end

	if isHeader then
		-- Header Row
		local fsDung = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
		fsDung:SetPoint("LEFT", 10, 0)
		fsDung:SetText("|cffFFD700Level / Dungeon|r")
		
		local fsGrp = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
		fsGrp:SetPoint("LEFT", 220, 0)
		fsGrp:SetText("|cffFFD700Party|r")

		local fsRes = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
		fsRes:SetPoint("RIGHT", -10, 0)
		fsRes:SetText("|cffFFD700Result|r")
		return
	end

	-- == DATA ROW ==
	
	-- A. Level Badge
	local level = run.level or 0
	local levelColor = "|cffFFFFFF"
	if level >= 20 then levelColor = "|cffFF8000" -- Orange
	elseif level >= 15 then levelColor = "|cffA335EE" -- Purple
	elseif level >= 10 then levelColor = "|cff0070DD" -- Blue
	end
	
	local lvlFS = f:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
	lvlFS:SetPoint("LEFT", 10, 0)
	lvlFS:SetText(levelColor .. "+" .. level .. "|r")
	
	-- B. Dungeon Name (with fallback for simulated runs)
	local mapName = nil
	
	-- Lookup table for M+ dungeons (works for simulated runs too)
	local dungeonNames = {
		[2660] = "Ara-Kara",
		[2662] = "The Dawnbreaker",
		[2651] = "The Stonevault",
		[2649] = "Priory",
		[2669] = "Floodgate",
		[2773] = "Eco-Dome",
		[2287] = "Halls of Atonement",
		[2441] = "Tazavesh: Streets",
		-- Old Shadowlands (legacy test data)
		[375] = "Mists",
		[376] = "Necrotic Wake",
		[377] = "De Other Side",
		[378] = "Halls of Atonement",
		[379] = "Plaguefall",
	}
	
	if run.mapID and run.mapID > 0 then
		-- Priority 1: Stored mapName (from test runs)
		if run.mapName and run.mapName ~= "" then
			mapName = run.mapName
		end
		
		-- Priority 2: Lookup table
		if not mapName or mapName == "" then
			mapName = dungeonNames[run.mapID]
		end
		
		-- Priority 3: WoW API (for real runs)
		if not mapName or mapName == "" then
			mapName = C_ChallengeMode.GetMapUIInfo(run.mapID)
		end
	end
	
	mapName = mapName or "Unknown"
	
	local nameFS = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
	nameFS:SetPoint("LEFT", lvlFS, "RIGHT", 10, 0)
	nameFS:SetWidth(140)
	nameFS:SetText(mapName)
	nameFS:SetJustifyH("LEFT")
	nameFS:SetWordWrap(false)
	
	-- C. Party Icons
	local startX = 220
	local iconSize = 24
	
	-- Sort group: Tank > Healer > Damage
	local roleOrder = { TANK = 1, HEALER = 2, HEALS = 2, DAMAGER = 3, DPS = 3, DMG = 3 }
	local sortedGroup = {}
	for _, m in ipairs(run.group or {}) do table.insert(sortedGroup, m) end
	table.sort(sortedGroup, function(a, b) 
		return (roleOrder[a.role] or 4) < (roleOrder[b.role] or 4) 
	end)
	
	for i, member in ipairs(sortedGroup) do
		local iframe = self:TrackFrame(CreateFrame("Frame", nil, f))
		iframe:SetSize(iconSize, iconSize)
		iframe:SetPoint("LEFT", startX + ((i - 1) * (iconSize + 4)), 0)
		
		-- LFG-Style Composite Icon: Class-colored circle + Role icon (Phase 5 Bulletproof)
		
		-- 1. Background circle with class color (using reliable solid circle texture)
		local bgCircle = iframe:CreateTexture(nil, "BACKGROUND")
		bgCircle:SetAllPoints()
		bgCircle:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
		
		-- Apply class color to the circle (normalize class name to uppercase)
		local classKey = string.upper(member.class or "PRIEST")
		local cc = RAID_CLASS_COLORS and RAID_CLASS_COLORS[classKey]
		if cc then
			bgCircle:SetVertexColor(cc.r, cc.g, cc.b, 1)
		else
			bgCircle:SetVertexColor(0.5, 0.5, 0.5, 1) -- Gray fallback
		end
		
		-- 2. Role icon on top (15x15 per Phase 5 - centered)
		local roleIcon = iframe:CreateTexture(nil, "ARTWORK")
		roleIcon:SetSize(15, 15)
		roleIcon:SetPoint("CENTER")
		
		-- Use SetAtlas for exact LFG role icons (no baked-in background)
		-- Use SetAtlas for exact LFG role icons (no baked-in background)
		local role = member.role
		if role == "TANK" then
			roleIcon:SetAtlas("roleicon-tiny-tank")
		elseif role == "HEALER" or role == "HEALS" then
			roleIcon:SetAtlas("roleicon-tiny-healer")
		else
			roleIcon:SetAtlas("roleicon-tiny-dps")
		end
		
		-- Tooltip
		iframe:SetScript("OnEnter", function(self)
			GameTooltip:SetOwner(self, "ANCHOR_TOP")
			local colorStr = member.class and RAID_CLASS_COLORS and RAID_CLASS_COLORS[member.class] 
				and "|c" .. RAID_CLASS_COLORS[member.class].colorStr or "|cffFFFFFF"
			GameTooltip:AddLine(colorStr .. (member.name or "Unknown") .. "|r")
			
			local roleDisplay = member.role or "DPS"
			if roleDisplay == "DAMAGER" or roleDisplay == "DMG" then roleDisplay = "DPS" end
			if roleDisplay == "HEALER" then roleDisplay = "HEALS" end
			
			GameTooltip:AddLine(roleDisplay, 1, 1, 1)
			if member.mplusRating then
				GameTooltip:AddLine("M+ Rating: " .. member.mplusRating, 0.5, 0.5, 0.5)
			end
			GameTooltip:Show()
		end)
		iframe:SetScript("OnLeave", function() GameTooltip:Hide() end)
	end

	-- D. Result (Right Aligned)
	-- Status Icon
	local statusIcon = run.success 
		and "|TInterface\\RaidFrame\\ReadyCheck-Ready:20|t" 
		or "|TInterface\\RaidFrame\\ReadyCheck-NotReady:20|t"
	
	local statusFS = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
	statusFS:SetPoint("RIGHT", -10, 0)
	statusFS:SetText(statusIcon)
	
	-- EP Award (wrapped in button for tooltip)
	local epBtn = self:TrackFrame(CreateFrame("Button", nil, f))
	epBtn:SetSize(80, 20)
	epBtn:SetPoint("RIGHT", statusFS, "LEFT", -10, 0)
	
	local epFS = epBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	epFS:SetAllPoints()
	local displayEP = run.awardedEP or run.totalEP or 0
	local capSuffix = run.wasCapped and " |cffff8800(Cap)|r" or ""
	epFS:SetText("|cff00ff00+" .. displayEP .. " EP|r" .. capSuffix)
	
	-- Phase 6: EP Breakdown Tooltip (using AddDoubleLine for left-right alignment)
	epBtn:SetScript("OnEnter", function(self)
		GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
		GameTooltip:AddLine("EP Breakdown", 1, 1, 1)
		GameTooltip:AddLine(" ")
		GameTooltip:AddDoubleLine("Base Reward:", run.baseEP or 0, 1, 1, 1, 1, 1, 1)
		GameTooltip:AddDoubleLine("Keystone Level (+" .. (run.level or 0) .. "):", run.levelEP or 0, 1, 1, 1, 0.2, 1, 0.2)
		GameTooltip:AddDoubleLine("Time/Guild Bonus:", run.bonusEP or 0, 1, 1, 1, 1, 0.82, 0)
		GameTooltip:AddLine(" ")
		local tooltipEP = run.awardedEP or run.totalEP or 0
		local tooltipCapNote = run.wasCapped and " (Capped)" or ""
		GameTooltip:AddDoubleLine("Awarded:", tooltipEP .. " EP" .. tooltipCapNote, 1, 1, 1, 0.2, 1, 0.2)
		if run.wasCapped and run.totalEP then
			GameTooltip:AddDoubleLine("Calculated:", (run.totalEP or 0) .. " EP", 0.5, 0.5, 0.5, 0.5, 0.5, 0.5)
		end
		GameTooltip:AddLine(" ")
		GameTooltip:AddDoubleLine("Status:", run.status or "Pending", 0.5, 0.5, 0.5, 0.5, 0.5, 0.5)
		GameTooltip:Show()
	end)
	epBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
	
	-- Time
	local mins = math.floor((run.duration or 0) / 60)
	local secs = (run.duration or 0) % 60
	local timeStr = string.format("%d:%02d", mins, secs)
	if run.upgrade and run.upgrade > 0 then 
		timeStr = timeStr .. " (+" .. run.upgrade .. ")" 
	end
	
	local timeFS = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
	timeFS:SetPoint("RIGHT", epBtn, "LEFT", -15, 0)
	timeFS:SetText(timeStr)
	
	-- Simulated badge
	if run.simulated then
		local simFS = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
		simFS:SetPoint("LEFT", nameFS, "RIGHT", 5, 0)
		simFS:SetText("|cff888888[Test]|r")
	end
end

function MyProgress:FormatRelativeTime(timestamp)
	if not timestamp or timestamp == 0 then return "Unknown" end
	local diff = time() - timestamp
	if diff < 60 then return "Just now"
	elseif diff < 3600 then return math.floor(diff / 60) .. " min ago"
	elseif diff < 86400 then return math.floor(diff / 3600) .. " hr ago"
	else return math.floor(diff / 86400) .. " days ago" end
end

function MyProgress:CalculatePRRank(targetFullName)
	local players = Cerebro:GetAllPlayers()
	
	-- Filter by visible ranks (same logic as StandingsFrame)
	local visibleRanks = nil
	if Cerebro.db and Cerebro.db.global and Cerebro.db.global.config
		and Cerebro.db.global.config.sync then
		visibleRanks = Cerebro.db.global.config.sync.visibleRanks
	end
	
	local filtered = {}
	for _, p in ipairs(players) do
		local include = true
		if visibleRanks then
			local rankIndex = Cerebro:GetPlayerGuildRankIndex(p.name)
			if rankIndex == nil or not visibleRanks[rankIndex] then
				include = false
			end
		end
		if include then
			table.insert(filtered, p)
		end
	end
	
	-- Stable sort: PR descending, then name ascending (matches StandingsFrame)
	table.sort(filtered, function(a, b)
		local prA, prB = (a.pr or 0), (b.pr or 0)
		if prA ~= prB then return prA > prB end
		return (a.name or ""):lower() < (b.name or ""):lower()
	end)
	for i, p in ipairs(filtered) do
		if p.name == targetFullName then return i end
	end
	return "?"
end
