--core/safety.lua, combat-lockdown checks and deferred execution (backend).
--protected frames cannot be shown or hidden while InCombatLockdown() is true;
--changes to them must run through RunWhenSafe, which defers until combat ends.

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

local detailsFramework = DetailsFramework

private.Core = private.Core or {}

---@class FPSPP_Safety
local Safety = {}
private.Core.Safety = Safety

--actions waiting for combat to end; each entry is {Callback, arguments, count}.
Safety.queue = {}

--frame used only to receive the PLAYER_REGEN_ENABLED (combat end) event.
local eventFrame = CreateFrame("Frame")

--registers the combat-end handler; called once from OnInit.
function Safety:Initialize()
	eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
	--keep the handler short: just flush the deferred queue.
	eventFrame:SetScript("OnEvent", function()
		Safety:FlushQueue()
	end)
end

--returns true while the player is in combat (protected actions are blocked).
---@return boolean
function Safety:IsLocked()
	return InCombatLockdown()
end

--runs func now if out of combat, otherwise queues it until combat ends.
---@param func function
---@return boolean ranNow true if it ran immediately, false if it was deferred
function Safety:RunWhenSafe(func, ...)
	if InCombatLockdown() then
		--store the argument count so nil arguments survive the later unpack.
		self.queue[#self.queue + 1] = {Callback = func, arguments = {...}, count = select("#", ...)}
		return false
	end

	--detailsFramework:Dispatch wraps the call in xpcall so one failure cannot break callers.
	detailsFramework:Dispatch(func, ...)
	return true
end

--runs every queued action; called automatically after combat ends.
function Safety:FlushQueue()
	--an empty queue is a normal, expected state on most combat-end events.
	if #self.queue == 0 then
		return
	end

	--swap the queue out first so any action that re-queues lands in a fresh list.
	local pending = self.queue
	self.queue = {}
	for index, item in ipairs(pending) do
		detailsFramework:Dispatch(item.Callback, unpack(item.arguments, 1, item.count))
	end
end
