--core/unitframes_visual.lua, insecure visuals for the party/raid unit frames (backend).
--builds the Details Framework unit frame (health bar + ONE debuff icon row) under each secure click
--button, and refreshes it when the header (re)assigns a unit. the power bar, cast bar and border are
--disabled, so their events never register and their CPU is never spent. everything here is insecure
--and updates freely in combat; the secure button above it is the combat boundary.

local _ = nil
local addonName, private = ...

local detailsFramework = DetailsFramework

private.Core = private.Core or {}
private.Core.UnitFrames = private.Core.UnitFrames or {}

---@class FPSPP_UnitFrames_Visual
local Visual = {}
private.Core.UnitFrames.Visual = Visual

--per-unit events registered via RegisterUnitEvent so a frame only wakes for its own unit. this list
--deliberately omits every UNIT_POWER_* and UNIT_SPELLCAST_* event (those bars are disabled); UNIT_AURA
--is the only aura path and it drives the HARMFUL-only scan.
local UNIT_EVENTS = {
	"UNIT_AURA",
}

--builds the insecure DF unit frame and its single debuff icon row under a secure click button.
--invoked from the secure initialConfigFunction via groupHeader:CallMethod("CreateUnitFrame", name).
---@param clickButton table
---@return table
function Visual:CreateUnitFrame(clickButton)
	local profile = private.Core.Profile:Get().unit_frames

	--the expensive components are disabled here: this is where the CPU savings live.
	local unitFrameOptions = {
		ShowPowerBar = false,     --omit power bar (frequent UNIT_POWER_* traffic)
		ShowCastBar = false,      --omit cast bar (per-frame OnTick + UNIT_SPELLCAST_*)
		ShowBorder = false,
		ColorByAggro = false,
		ShowTargetOverlay = true, --cheap highlight when this unit is the player's target
		ShowUnitName = profile.show_unit_name,
		ClearUnitOnHide = false,  --we recycle buttons; do not let DF clear the unit on hide
		Width = profile.unit_width,
		Height = profile.unit_height,
	}

	--the health bar self-drives from its own unit events. drop the heal-prediction and shield overlays:
	--they each register an extra per-unit event (UNIT_HEAL_PREDICTION, UNIT_ABSORB_AMOUNT_CHANGED), and
	--this is the minimal-CPU build, so only UNIT_HEALTH/UNIT_MAXHEALTH remain. on Midnight (12.0) DF
	--renders the value secret-safe via SetStatusBarTexture+SetValue, so we do not own the bar texture.
	local healthBarOptions = {
		ShowHealingPrediction = false,
		ShowShields = false,
	}

	local unitFrame = detailsFramework:CreateUnitFrame(clickButton, clickButton:GetName() .. "UnitFrame", unitFrameOptions, healthBarOptions, {}, {})
	unitFrame:SetAllPoints()
	--mouse input belongs to the secure button above, not to the insecure visuals.
	unitFrame:EnableMouse(false)

	clickButton.unitFrame = unitFrame
	unitFrame.clickFrame = clickButton

	--DF's CreateUnitFrame creates the health bar but never anchors it (Initialize only positions the
	--power/cast bars). anchor it to fill the frame so the bar -- and the unit name and target overlay,
	--which are both anchored to the health bar -- actually render. on Midnight DF then drives the bar
	--texture/value itself once it is bound via SetUnit.
	unitFrame.healthBar:SetAllPoints()

	--DF's healthBar:Initialize hid the bar (SetUnit(nil)) and its SetUnit(realUnit) path never re-shows
	--it, so show it explicitly once (matches Plater_UnitFrames). without this the bar stays hidden and
	--only the name/overlay -- which are merely anchored to the bar's position -- appear.
	unitFrame.healthBar:Show()

	--DF creates unitName on the root frame, which sits BELOW the health bar's frame level, so the shown
	--bar now covers it. re-parent the name to the overlay frame (above the bar) and use an overlay draw
	--layer so it stays on top. mirrors Plater_UnitFrames.
	unitFrame.unitName:SetParent(unitFrame.overlayFrame)
	unitFrame.unitName:SetDrawLayer("overlay")

	--one debuff icon row (pooled icons), parented to the unit frame's overlay so it floats above the
	--health bar. no buff row and no big-aura row: those are the omitted aura costs.
	local iconRowOptions = {
		icon_width = profile.debuff_icon_size,
		icon_height = profile.debuff_icon_size,
		icon_padding = profile.debuff_icon_spacing,
		show_cooldown = false,
		show_text = false,
	}
	local debuffIcons = detailsFramework:CreateIconRowGeneric(unitFrame.overlayFrame, clickButton:GetName() .. "DebuffIcons", iconRowOptions)
	debuffIcons:SetPoint("bottomleft", unitFrame, "bottomleft", 1, 1)
	unitFrame.debuffIcons = debuffIcons

	--dispatch UNIT_AURA to the HARMFUL scan. DF's unit-frame OnEvent dispatches self[event], so mixing
	--this in is enough; the event is unregistered on recycle by UnRegisterUnitEvents (DF will not).
	unitFrame.UNIT_AURA = function()
		private.Core.UnitFrames.Auras:ScanUnitFrame(unitFrame)
	end

	--the stock DF UNIT_NAME_UPDATE handler refreshes the name but NOT the health-bar color. so a unit
	--whose class is not known yet when the frame is bound (just joined, or out of range) is drawn with
	--the fallback selection color -- blue for a friendly player -- and keeps it even after the class
	--info arrives. wrap the handler (DF registers the event via SetUnit) to also re-run the class
	--coloring once the name/class becomes known, which is exactly what UNIT_NAME_UPDATE signals.
	local baseNameUpdate = unitFrame.UNIT_NAME_UPDATE
	unitFrame.UNIT_NAME_UPDATE = function(frame, ...)
		if baseNameUpdate then
			baseNameUpdate(frame, ...)
		end

		frame:UpdateHealthColor()
	end

	--recycle (never destroy): drop the unit's events and aura state, mark the button reusable.
	unitFrame.OnHide = function()
		private.Core.UnitFrames.Visual:UnRegisterUnitEvents(unitFrame)
		private.Core.UnitFrames.Auras:UnRegisterUnitFrame(unitFrame)
		unitFrame.debuffIcons:ClearIcons()
		unitFrame.bInUse = false
	end

	return unitFrame
end

--refreshes a unit frame after the secure header assigns it a unit: registers it with the shared aura
--ticker and seeds the cache with an initial scan (UNIT_AURA only fires on later changes).
---@param unitFrame table
function Visual:UpdateUnitFrame(unitFrame)
	--DF's SetUnit already set unitFrame.guid; register for rendering and seed the debuff cache.
	private.Core.UnitFrames.Auras:RegisterUnitFrame(unitFrame)
	private.Core.UnitFrames.Auras:ScanUnitFrame(unitFrame)
	unitFrame.bInUse = true
end

--registers the per-unit events on the unit frame (UNIT_AURA only) via RegisterUnitEvent.
---@param unitFrame table
---@param unitId string
function Visual:RegisterUnitEvents(unitFrame, unitId)
	for index, eventName in ipairs(UNIT_EVENTS) do
		unitFrame:RegisterUnitEvent(eventName, unitId)
	end
end

--unregisters the per-unit events on the unit frame.
---@param unitFrame table
function Visual:UnRegisterUnitEvents(unitFrame)
	for index, eventName in ipairs(UNIT_EVENTS) do
		unitFrame:UnregisterEvent(eventName)
	end
end

--OnLoad of the secure button template (core/unitframes.xml). runs once when the secure header first
--creates a button, before the initialConfigFunction. installs the bridge the secure refreshUnitChange
--code calls back into to (re)bind the insecure visuals.
---@param clickButton table
function FPSPlusPlus_SecureUnitButton_OnLoad(clickButton) --[[GLOBAL]]
	--fire targeting on mouse-down for snappy clicks; the click action itself is a secure attribute.
	clickButton:RegisterForClicks("AnyDown")

	--called from the secure code via self:CallMethod("UpdateUnitFrameMethod", unitId). only string and
	--number arguments cross the secure -> insecure boundary, never tables; unitId is the unit token.
	clickButton.UpdateUnitFrameMethod = function(secureButton, unitId)
		local unitFrame = secureButton.unitFrame
		if unitId then
			private.Core.UnitFrames.Visual:RegisterUnitEvents(unitFrame, unitId)
			unitFrame:SetUnit(unitId)
			private.Core.UnitFrames.Visual:UpdateUnitFrame(unitFrame)
		else
			unitFrame:SetUnit(nil)
			private.Core.UnitFrames.Visual:UnRegisterUnitEvents(unitFrame)
		end
	end
end
