-- KiraUI-Plugin_UUF.lua
-- UnhaltedUnitFrames player absorb tweaks (mirrors the ElvUI absorb build):
--   * the absorb value text is rendered as a SHORT number. We register our own
--     [kira:absorbshort] tag and force it onto whichever player text slot uses an
--     absorb tag (read from UUF's saved profile), so the text you already have
--     becomes short regardless of which absorb tag variant it used.
--   * the player's absorb texture attaches to the right of current health and grows
--     right; at full HP the overflow reverses back across the health bar
--
-- UUF registers its oUF object globally under its X-oUF name ("UnhaltedUnitFrames")
-- and spawns the player frame with the global name "UUF_Player".

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

local CreateFrame = CreateFrame
local C_Timer = C_Timer
local pairs = pairs
local ipairs = ipairs
local pcall = pcall
local hooksecurefunc = hooksecurefunc
local UnitExists = UnitExists
local UnitGetTotalAbsorbs = UnitGetTotalAbsorbs
local UnitName = UnitName
local GetRealmName = GetRealmName
local AbbreviateLargeNumbers = AbbreviateLargeNumbers or AbbreviateNumbers
local AbbreviateNumbers = AbbreviateNumbers
local C_StringUtil = C_StringUtil
local strlen = strlen or string.len
local sformat = string.format

----------------------------------------------------------------------
-- Settings (lazy-created on the shared KiraUIChangesDB)
----------------------------------------------------------------------
local DEFAULTS = { enable = false, shorten = false }

local function GetCfg() return ns.ModuleCfg('uufAbsorb', DEFAULTS) end
ns.UUFGetCfg = GetCfg
-- Resolved now if UUF already loaded, and re-checked at login (load order varies).
ns.hasUUF = type(_G.UnhaltedUnitFrames) == 'table'

----------------------------------------------------------------------
-- Abbreviate a (possibly SECRET) absorb amount, e.g. 72K / 237K / 2.4M.
----------------------------------------------------------------------
local function ShortNum(value)
	-- The amount is SECRET; our own %f formatting is blocked on secret values, so we
	-- rely on Blizzard's abbreviators, which are secret-aware. AbbreviateNumbers uses
	-- a lower threshold (~1000) than AbbreviateLargeNumbers (which only abbreviates
	-- secret values at ~100k), so prefer it.
	if AbbreviateNumbers then
		local ok, r = pcall(AbbreviateNumbers, value)
		if ok and r then return r end
	end
	local ok, r = pcall(AbbreviateLargeNumbers, value)
	if ok and r then return r end
	return sformat('%s', value)
end

-- Short AND blank at zero (secret-safe gate via the WrapString/hyperlink trick).
local function AbsorbShortHideZero(unit)
	if not unit or not UnitExists(unit) then return '' end
	local amount = UnitGetTotalAbsorbs(unit) or 0
	local short = ShortNum(amount)
	local gate = C_StringUtil.TruncateWhenZero(amount)
	return C_StringUtil.StripHyperlinks(C_StringUtil.WrapString(gate, short .. '|H', '|h|h'))
end

----------------------------------------------------------------------
-- Find the player's tag config in UUF's saved variables, so we can locate which
-- text slot uses an absorb tag (the tag string is only stored in the profile DB).
----------------------------------------------------------------------
local function PlayerTagDB()
	local db = _G.UUFDB
	if type(db) ~= 'table' or type(db.profiles) ~= 'table' then return nil end
	local profileName
	local cname = UnitName and UnitName('player')
	local realm = GetRealmName and GetRealmName()
	if db.profileKeys and cname and realm then
		profileName = db.profileKeys[cname .. ' - ' .. realm]
	end
	if db.global and db.global.UseGlobalProfile then
		profileName = db.global.GlobalProfile or db.global.GlobalProfileName or profileName
	end
	local prof = profileName and db.profiles[profileName]
	if not prof then prof = db.profiles['Default'] end
	local units = prof and prof.Units
	local pl = units and units.player
	return pl and pl.Tags
end

----------------------------------------------------------------------
-- Make the player's absorb text render SHORT.
-- 1) Override UUF's built-in absorb tag methods (covers [absorbs], :abbr, :truncate).
-- 2) Force our own [kira:absorbshort] onto whichever player slot uses an absorb tag,
--    which is reliable regardless of binding/timing.
-- 3) Hook the frame's Tag method so a later UUF re-tag is swapped back to ours.
----------------------------------------------------------------------
local hookedTag = false
local function ShortenAbsorbTags(oUF, player)
	local M = oUF.Tags.Methods

	if not oUF.__kiraAbsorbShort then
		M['absorbs'] = function(unit)
			if not unit or not UnitExists(unit) then return '' end
			return ShortNum(UnitGetTotalAbsorbs(unit) or 0)
		end
		M['absorbs:abbr'] = M['absorbs']
		M['absorbs:truncate'] = AbsorbShortHideZero
		M['kira:absorbshort'] = AbsorbShortHideZero
		oUF.Tags.Events['kira:absorbshort'] = 'UNIT_ABSORB_AMOUNT_CHANGED'
		oUF.__kiraAbsorbShort = true
		if oUF.Tags.RefreshMethods then
			for _, t in ipairs({ 'absorbs', 'absorbs:abbr', 'absorbs:truncate' }) do
				pcall(function() oUF.Tags:RefreshMethods(t) end)
			end
		end
	end

	if not player or not player.Tag or not player.Tags then return end

	-- Force our short tag onto the absorb slot(s).
	local tagsDB = PlayerTagDB()
	if tagsDB then
		for slot, t in pairs(tagsDB) do
			if type(t) == 'table' and type(t.Tag) == 'string' and t.Tag:find('absorbs', 1, true) then
				local fs = player.Tags[slot]
				if fs then pcall(function() player:Tag(fs, '[kira:absorbshort]') end) end
			end
		end
	end

	-- Durability: if UUF re-tags an absorb fontstring later, swap it back to ours.
	if not hookedTag then
		hookedTag = true
		hooksecurefunc(player, 'Tag', function(self, fs, ts)
			if self.__kiraRetagging then return end
			if type(ts) == 'string' and ts:find('absorbs', 1, true) and ts ~= '[kira:absorbshort]' then
				self.__kiraRetagging = true
				pcall(function() self:Tag(fs, '[kira:absorbshort]') end)
				self.__kiraRetagging = false
			end
		end)
	end
end

----------------------------------------------------------------------
-- Absorb texture geometry.
-- No frame width/height is ever read (those come back SECRET in restricted content
-- and would error in SetWidth/SetHeight); geometry is implied entirely by anchors.
----------------------------------------------------------------------
local function CreateOver(player)
	local health = player.Health
	local da = player.HealthPrediction.damageAbsorb
	local clip = CreateFrame('Frame', nil, health)
	clip:SetClipsChildren(true)
	local bar = CreateFrame('StatusBar', nil, clip)
	local tex = da:GetStatusBarTexture()
	if tex and tex.GetTexture then bar:SetStatusBarTexture(tex:GetTexture()) end
	bar:SetStatusBarColor(da:GetStatusBarColor())
	bar.Clip = clip
	bar:SetFrameLevel(health:GetFrameLevel() + 1)
	bar:Hide()
	clip:Hide()
	return bar
end

-- Re-assert the attach-and-grow-right anchoring on the main absorb bar. UUF can
-- re-apply its own position (e.g. RIGHT, which reverse-fills from the right edge)
-- on its updates, so we force ours on EVERY heal-prediction update. Below 100% HP
-- the shield grows from the current-health edge to the right (clipped to the health
-- bar). At full health the empty area is zero, so this bar is invisible and the
-- over-absorb shows the reverse.
local function AnchorDA(player)
	local hp = player.HealthPrediction
	local da = hp and hp.damageAbsorb
	if not da then return end
	local health = player.Health
	health:SetClipsChildren(true)
	da:ClearAllPoints()
	da:SetPoint('TOP', health, 'TOP', 0, 0)
	da:SetPoint('BOTTOM', health, 'BOTTOM', 0, 0)
	if health:GetReverseFill() then
		da:SetPoint('RIGHT', health:GetStatusBarTexture(), 'LEFT', 0, 0)
		da:SetReverseFill(true)
	else
		da:SetPoint('LEFT', health:GetStatusBarTexture(), 'RIGHT', 0, 0)
		da:SetReverseFill(false)
	end
	da:Show()
end

local function UpdateOver(player)
	AnchorDA(player)
	local hp = player.HealthPrediction
	local da, oa = hp.damageAbsorb, hp.overDamageAbsorb
	if not da or not oa then return end
	oa:SetMinMaxValues(da:GetMinMaxValues())
	oa:SetValue(da:GetValue())
	oa.Clip:Show()
	oa:Show()
	da:Show()
end

local function ApplyGeometry(player)
	local hp = player.HealthPrediction
	if not hp or not hp.damageAbsorb then return false end
	local health = player.Health
	AnchorDA(player)

	local reversed = health:GetReverseFill()
	local oa = hp.overDamageAbsorb or CreateOver(player)
	hp.overDamageAbsorb = oa
	oa.Clip:ClearAllPoints()
	oa:ClearAllPoints()
	-- The over bar spans the FULL health rect (both corners anchored => full width,
	-- no SetWidth needed, which would error on the SECRET width). The clip limits it
	-- to the filled (health) region, so only the overflow beyond the empty area
	-- shows - which at full HP is the whole shield reversed across the bar.
	oa:SetPoint('TOPLEFT', health, 'TOPLEFT', 0, 0)
	oa:SetPoint('BOTTOMRIGHT', health, 'BOTTOMRIGHT', 0, 0)
	if reversed then
		oa.Clip:SetPoint('TOPRIGHT', health, 'TOPRIGHT', 0, 0)
		oa.Clip:SetPoint('BOTTOMLEFT', health:GetStatusBarTexture(), 'BOTTOMLEFT', 0, 0)
		oa:SetReverseFill(false)
	else
		oa.Clip:SetPoint('TOPLEFT', health, 'TOPLEFT', 0, 0)
		oa.Clip:SetPoint('BOTTOMRIGHT', health:GetStatusBarTexture(), 'BOTTOMRIGHT', 0, 0)
		oa:SetReverseFill(true)
	end

	-- oUF sets damageAbsorb's value each update then calls PostUpdate; route our
	-- refresh through it so our anchoring always wins.
	hp.PostUpdate = function(_, unit) UpdateOver(player) end
	return true
end

----------------------------------------------------------------------
-- Apply
----------------------------------------------------------------------
local function Apply()
	local oUF = _G.UnhaltedUnitFrames
	if type(oUF) ~= 'table' or not oUF.Tags then return end
	local cfg = GetCfg()
	local player = _G.UUF_Player

	if cfg.shorten then pcall(ShortenAbsorbTags, oUF, player) end

	if cfg.enable and player and player.Health then
		pcall(ApplyGeometry, player)
		if player.HealthPrediction and player.HealthPrediction.ForceUpdate then
			pcall(function() player.HealthPrediction:ForceUpdate() end)
		end
	end

	if player and player.UpdateTags then pcall(function() player:UpdateTags() end) end
end
ns.UUFApply = function() C_Timer.After(0, Apply) end

----------------------------------------------------------------------
-- Drivers (deferred: the UUF oUF object/frame may load after this addon)
----------------------------------------------------------------------
local f = CreateFrame('Frame')
f:RegisterEvent('PLAYER_LOGIN')
f:RegisterEvent('PLAYER_ENTERING_WORLD')
f:RegisterEvent('PLAYER_SPECIALIZATION_CHANGED')
f:SetScript('OnEvent', function(_, event, unit)
	if event == 'PLAYER_SPECIALIZATION_CHANGED' and unit ~= 'player' then return end
	if type(_G.UnhaltedUnitFrames) == 'table' then ns.hasUUF = true end
	C_Timer.After(event == 'PLAYER_SPECIALIZATION_CHANGED' and 0.3 or 0.5, Apply)
	C_Timer.After(2.0, Apply)
end)
