-- KiraUI-Plugin_SecondaryStats.lua
-- Secondary-stats readout as a native module: a
-- stacked list of Crit / Haste / Mastery / Versatility / Leech percentages, plus
-- a Durability % row. Colours match the original WA. Configurable text size and
-- X/Y position. Plain stat APIs (not secret-flagged), so it's Midnight-safe.

local addonName, ns = ...
local _G = _G

local CreateFrame = CreateFrame
local C_Timer = C_Timer
local UIParent = UIParent
local format = string.format
local ipairs = ipairs
local mmax = math.max
local issecretvalue = issecretvalue
local function secret(v) return issecretvalue and issecretvalue(v) end

ns.hasSecondaryStats = true

----------------------------------------------------------------------
-- Stat sources
----------------------------------------------------------------------
-- Mirrors EllesmereUI's secondary-stat readout. In 12.0 these values can be
-- SECRET, but string.format("%.2f", secret) still renders fine - the ONLY thing
-- that hard-errors is doing arithmetic on a secret, so Vers() avoids the addition
-- when either part is secret (using the rating alone, like EllesmereUI does).
-- Note: '_G.Fn and Fn(...)' only tests the function's existence, never the
-- secret result, so it cannot taint.
local function Crit()    return _G.GetCritChance and GetCritChance('player') end
local function Haste()   return _G.UnitSpellHaste and UnitSpellHaste('player') end
local function Mastery() return _G.GetMasteryEffect and GetMasteryEffect() end
local function Vers()
	local id = _G.CR_VERSATILITY_DAMAGE_DONE or 29
	local r = _G.GetCombatRatingBonus and GetCombatRatingBonus(id)
	local b = _G.GetVersatilityBonus and GetVersatilityBonus(id)
	if secret(r) or secret(b) then return r end
	return (r or 0) + (b or 0)
end
local function Durability()
	local cur, max = 0, 0
	local get = _G.GetInventoryItemDurability
	if get then
		for slot = 1, 18 do
			local c, m = get(slot)
			if c and m and not secret(c) and not secret(m) and m > 0 then
				cur = cur + c
				max = max + m
			end
		end
	end
	if max == 0 then return 100 end
	return cur / max * 100
end

-- Durability colour: red (low) -> orange -> yellow -> green (high), matching the
-- status-colour reference (25% red, 50% orange, 75% yellow, 100% green).
local DURA_STOPS = {
	{ 25,  0.88, 0.11, 0.11 }, -- red
	{ 50,  1.00, 0.50, 0.00 }, -- orange
	{ 75,  1.00, 0.84, 0.00 }, -- yellow
	{ 100, 0.19, 0.82, 0.19 }, -- green
}
local function DuraColor(pct)
	local first, last = DURA_STOPS[1], DURA_STOPS[#DURA_STOPS]
	if pct <= first[1] then return first[2], first[3], first[4] end
	if pct >= last[1] then return last[2], last[3], last[4] end
	for i = 2, #DURA_STOPS do
		local a, b = DURA_STOPS[i - 1], DURA_STOPS[i]
		if pct <= b[1] then
			local t = (pct - a[1]) / (b[1] - a[1])
			return a[2] + (b[2] - a[2]) * t, a[3] + (b[3] - a[3]) * t, a[4] + (b[4] - a[4]) * t
		end
	end
	return last[2], last[3], last[4]
end

-- Colours from the WA (hex): Crit E01C1C, Hast D5C700, Mast 9256FF, Vers BFBFBF.
-- decimals = digits after the % value; Dur uses a dynamic colour by its value.
local ROWS = {
	{ label = 'Crit', color = { 0.878, 0.110, 0.110 }, value = Crit,    decimals = 2 },
	{ label = 'Hast', color = { 0.835, 0.780, 0.000 }, value = Haste,   decimals = 2 },
	{ label = 'Mast', color = { 0.573, 0.337, 1.000 }, value = Mastery, decimals = 2 },
	{ label = 'Vers', color = { 0.749, 0.749, 0.749 }, value = Vers,    decimals = 2 },
	{ label = 'Dur',  value = Durability, decimals = 0, colorFn = DuraColor },
}

----------------------------------------------------------------------
-- Settings
----------------------------------------------------------------------
local DEFAULTS = {
	enable = false,
	lock = false,
	fontSize = 16,
	x = 300,
	y = 0,
}

local function GetCfg() return ns.ModuleCfg('secondaryStats', DEFAULTS) end
ns.SSGetCfg = GetCfg

local Round = ns.Round

local function IsLoaded(name)
	local f = (_G.C_AddOns and _G.C_AddOns.IsAddOnLoaded) or _G.IsAddOnLoaded
	return f and f(name)
end

local function Font()
	-- Prefer a LibSharedMedia "Expressway" only if it's actually registered
	-- (noDefault = true returns nil instead of the fallback font).
	local lsm = ns.LSM()
	local path = lsm and lsm.Fetch and lsm:Fetch('font', 'Expressway', true)
	if not path and IsLoaded('EllesmereUI') then
		-- EllesmereUI ships the real Expressway face - match it exactly.
		path = 'Interface\\AddOns\\EllesmereUI\\media\\fonts\\Expressway.ttf'
	end
	-- addon-wide font first, this module's Expressway preference as fallback
	return ns.GetFont(path or _G.STANDARD_TEXT_FONT or 'Fonts\\FRIZQT__.TTF')
end

----------------------------------------------------------------------
-- Frame
----------------------------------------------------------------------
local frame
local fontStrings = {}

local function EnsureFrame()
	if frame then return end
	frame = CreateFrame('Frame', 'KiraUISecondaryStats', UIParent)
	frame:SetFrameStrata('MEDIUM')
	frame:SetMovable(true)
	frame:EnableMouse(false)
	frame:RegisterForDrag('RightButton')
	ns.MakeDraggable(frame, GetCfg, 'x', 'y')

	for i in ipairs(ROWS) do
		local fs = frame:CreateFontString(nil, 'OVERLAY')
		fs:SetJustifyH('LEFT')
		fontStrings[i] = fs
	end
	frame:Hide()
end

local function UpdateValues()
	if not frame then return end
	for i, def in ipairs(ROWS) do
		local fs = fontStrings[i]
		local v = def.value()
		-- Only normalise a genuine nil to 0; never compare/coerce a secret (that
		-- would taint). A secret number formats fine straight through %.2f.
		if not secret(v) and v == nil then v = 0 end
		if def.decimals == 0 then
			fs:SetText(format('%s: %d%%', def.label, Round(v)))
		else
			fs:SetText(format('%s: %.2f%%', def.label, v))
		end
		if def.colorFn then
			fs:SetTextColor(def.colorFn(v))
		else
			fs:SetTextColor(def.color[1], def.color[2], def.color[3])
		end
	end
end

local function Apply()
	EnsureFrame()
	local cfg = GetCfg()
	local font, size = Font(), cfg.fontSize or 16
	local line = size + 4

	for i, def in ipairs(ROWS) do
		local fs = fontStrings[i]
		fs:SetFont(font, size, 'OUTLINE')
		fs:ClearAllPoints()
		fs:SetPoint('TOPLEFT', frame, 'TOPLEFT', 0, -(i - 1) * line)
	end

	UpdateValues()

	-- A fontstring showing a SECRET stat returns a secret GetStringWidth(); doing
	-- mmax/arithmetic on that taints. Test secret() first (safe), skip those, and
	-- fall back to a fixed minimum width so the panel never collapses.
	local w = 0
	for _, fs in ipairs(fontStrings) do
		local sw = fs:GetStringWidth()
		if not secret(sw) then w = mmax(w, sw) end
	end
	frame:SetSize(mmax(150, w), #ROWS * line)
	frame:ClearAllPoints()
	frame:SetPoint('CENTER', UIParent, 'CENTER', cfg.x or 0, cfg.y or 0)

	frame:SetShown(cfg.enable and true or false)
	frame:EnableMouse(cfg.enable and not cfg.lock or false)
end
ns.SSApply = function() C_Timer.After(0, Apply) end

----------------------------------------------------------------------
-- Events (coalesced refresh)
----------------------------------------------------------------------
local pending = false
local function Refresh()
	if pending then return end
	pending = true
	C_Timer.After(0.1, function()
		pending = false
		UpdateValues()
	end)
end

local evt = CreateFrame('Frame')
evt:RegisterEvent('PLAYER_ENTERING_WORLD')
evt:RegisterEvent('COMBAT_RATING_UPDATE')
evt:RegisterEvent('UPDATE_INVENTORY_DURABILITY')
evt:RegisterEvent('PLAYER_EQUIPMENT_CHANGED')
evt:RegisterUnitEvent('UNIT_AURA', 'player')
evt:SetScript('OnEvent', function(_, event)
	if event == 'PLAYER_ENTERING_WORLD' then
		C_Timer.After(0.3, Apply)
	else
		Refresh()
	end
end)
