---@diagnostic disable: undefined-field, undefined-global
local _, namespace = ...

local WTUI = namespace.WildThingsUI
local Theme = namespace.Theme

--[[
Central self-hiding glow/pulse driver (DRAGUX-02). A single OnUpdate, gated to
~60fps, dispatches every registered updateFn. StartGlow/StopGlow are keyed by a
composite owner+id (one owner may hold several distinct glows). The driver frame
is created lazily on first use, Shows on the 0->1 registration transition, and
Hides itself the instant the live count reaches 0 -- this self-hide is the
load-bearing DoS bound for this file (see 22-01-PLAN.md must_haves).

Do NOT add any per-frame/per-widget OnUpdate outside this one driver.
]]

local DRIVER_GATE = 0.016 -- ~60fps ceiling

-- Dense parallel arrays (1.._glowCount) + an owner->id->index lookup map, mirroring
-- UI/Drag.lua's module-local-state style (no class/OOP wrapper).
local _glowOwners = {}
local _glowIds = {}
local _glowFns = {}
local _glowIndex = {} -- [owner][id] = array index
local _glowCount = 0

local _driverFrame
local _driverAccum = 0

-- Dispatch loop: walk by index, re-checking the current slot after each updateFn so
-- a StopGlow swap-remove happening INSIDE an updateFn never skips or crashes the
-- iteration (the entry that got swapped into slot i is picked up on the same pass).
local function DriverOnUpdate(self, elapsed)
    local dt = _driverAccum + elapsed
    if dt < DRIVER_GATE then
        _driverAccum = dt
        return
    end
    _driverAccum = 0

    local i = 1
    while i <= _glowCount do
        local owner = _glowOwners[i]
        local fn = _glowFns[i]
        if fn then fn(owner, dt) end
        if _glowOwners[i] == owner then
            i = i + 1
        end
    end

    if _glowCount == 0 then
        self:Hide()
    end
end

local function ArmDriver()
    if not _driverFrame then
        _driverFrame = CreateFrame("Frame")
        _driverFrame:Hide()
        _driverFrame:SetScript("OnUpdate", DriverOnUpdate)
    end
    _driverAccum = 0
    _driverFrame:Show()
end

-- WTUI:StartGlow(owner, id, updateFn)
-- Registers (or, if owner+id already registered, idempotently updates the
-- updateFn for) a glow. updateFn(owner, dt) is called every ~60fps tick while
-- registered. Arms the driver on the 0->1 live-count transition.
function WTUI:StartGlow(owner, id, updateFn)
    if not owner or id == nil then return end

    local ownerMap = _glowIndex[owner]
    local existingIdx = ownerMap and ownerMap[id]
    if existingIdx then
        _glowFns[existingIdx] = updateFn
        return
    end

    _glowCount = _glowCount + 1
    _glowOwners[_glowCount] = owner
    _glowIds[_glowCount] = id
    _glowFns[_glowCount] = updateFn

    if not ownerMap then
        ownerMap = {}
        _glowIndex[owner] = ownerMap
    end
    ownerMap[id] = _glowCount

    if _glowCount == 1 then
        ArmDriver()
    end
end

-- WTUI:StopGlow(owner, id)
-- O(1) swap-remove. Safe no-op when owner+id is not registered (guard clause).
function WTUI:StopGlow(owner, id)
    local ownerMap = _glowIndex[owner]
    local idx = ownerMap and ownerMap[id]
    if not idx then return end

    local lastIdx = _glowCount
    local lastOwner, lastId = _glowOwners[lastIdx], _glowIds[lastIdx]

    _glowOwners[idx] = lastOwner
    _glowIds[idx] = lastId
    _glowFns[idx] = _glowFns[lastIdx]
    _glowIndex[lastOwner][lastId] = idx

    _glowOwners[lastIdx] = nil
    _glowIds[lastIdx] = nil
    _glowFns[lastIdx] = nil
    ownerMap[id] = nil

    _glowCount = lastIdx - 1

    if _glowCount == 0 and _driverFrame then
        _driverFrame:Hide()
    end
end

-- WTUI:BorderPulse()
-- Returns a fresh updateFn for WTUI:StartGlow(owner, id, WTUI:BorderPulse()) that
-- oscillates owner's WTUI border alpha (WTUI:AddRoundedBorder/SetBorderColor -- the
-- same border mechanism the grid slots already use, see UI/Functions.lua) via a sine
-- wave, using the GLOW_* Theme constants (min/max alpha, speed).
--
-- The CALLER must show the border for the duration of the glow: owners whose border is
-- hidden by default (every Button) otherwise pulse an invisible texture, which is exactly
-- how RosterScreen's two glows sat broken. See GroupGrid.lua:159-169 for the pattern.
-- Never a looping
-- WoW animation object -- the v2.0 Phase-9 snap-back trap (looping transform
-- animations revert to base transform on finish; only alpha supports finishing at
-- its final value) -- and never a new texture.
function WTUI:BorderPulse()
    local elapsedTotal = 0
    return function(owner, dt)
        if not owner.SetBorderColor then return end
        elapsedTotal = elapsedTotal + dt

        local range = (Theme.GLOW_ALPHA_MAX - Theme.GLOW_ALPHA_MIN) / 2
        local mid = Theme.GLOW_ALPHA_MIN + range
        local alpha = mid + range * math.sin(elapsedTotal * Theme.GLOW_SPEED)

        local c = Theme.GLOW_COLOR
        owner:SetBorderColor(c.r, c.g, c.b, alpha)
    end
end
