-- [ GLOW CONTROLLER ] -------------------------------------------------------------------------------
-- Single owner for all glow ops — no consumer touches LCG directly.
local _, Orbit = ...
local Engine = Orbit.Engine
local Constants = Orbit.Constants
local LCG = LibStub("LibOrbitGlow-1.0", true)

Engine.GlowController = {}
local GC = Engine.GlowController

-- A registered typeName is a pack glow (lowercase, played via lib.Proc); engine glows (Capitalized, e.g. Pixel) go via lib.Show.
local function IsRegistryGlow(typeName)
    return type(typeName) == "string" and LCG and LCG.IsGlowRegistered and LCG:IsGlowRegistered(typeName)
end

local function DoShow(frame, typeName, options)
    if IsRegistryGlow(typeName) then
        LCG.Proc:Loop(frame, { glow = typeName, shape = "square", color = options.color, key = options.key, frameLevel = options.frameLevel, loopDuration = options.speed })
    else
        LCG.Show(frame, typeName, options)
    end
end

local function DoHide(frame, typeName, glowKey)
    if IsRegistryGlow(typeName) then
        LCG.Proc:Clear(frame, { glow = typeName, key = glowKey })
    else
        LCG.Hide(frame, typeName, glowKey)
    end
end

-- [ GEOMETRY INJECTION ] ----------------------------------------------------------------------------
local function InjectGeometry(options)
    if not options then return end
    options.pixelScale = Engine.Pixel:GetScale()
end

-- [ STATE HELPERS ] ---------------------------------------------------------------------------------
local function GetState(frame)
    if not frame._orbitGlow then frame._orbitGlow = { active = {} } end
    return frame._orbitGlow
end

-- [ CORE SHOW / HIDE ] ------------------------------------------------------------------------------
function GC:Show(frame, glowKey, typeName, options)
    -- Empty typeName means "no glow" (e.g. an unmapped enum like Blizzard resolves to ""); never forward it — lib.Show errors on an unknown type.
    if not frame or not LCG or not typeName or typeName == "" then return end
    local state = GetState(frame)
    local entry = state.active[glowKey]
    
    if options and not options.frameLevel then 
        options.frameLevel = Constants.Levels.IconGlow
    end
    
    InjectGeometry(options)
    local hash = Engine.GlowUtils:GetOptionsHash(options)
    if entry and entry.typeName == typeName and entry.hash == hash then return end
    if entry then DoHide(frame, entry.typeName, glowKey) end
    DoShow(frame, typeName, options)
    state.active[glowKey] = { typeName = typeName, hash = hash }
end

function GC:Hide(frame, glowKey)
    if not frame or not LCG then return end
    local state = frame._orbitGlow
    if not state then return end
    local entry = state.active[glowKey]
    if not entry then return end
    DoHide(frame, entry.typeName, glowKey)
    state.active[glowKey] = nil
end

function GC:IsActive(frame, glowKey)
    local state = frame and frame._orbitGlow
    if not state then return false end
    return state.active[glowKey] ~= nil
end

function GC:GetActiveType(frame, glowKey)
    local state = frame and frame._orbitGlow
    if not state then return nil end
    local entry = state.active[glowKey]
    return entry and entry.typeName
end

-- [ NATIVE SUPPRESSION ] ----------------------------------------------------------------------------
local function KillNativeAnimations(overlay)
    if overlay.animIn and overlay.animIn:IsPlaying() then overlay.animIn:Stop() end
    if overlay.animOut and overlay.animOut:IsPlaying() then overlay.animOut:Stop() end
    if overlay.ProcStartAnim and overlay.ProcStartAnim:IsPlaying() then overlay.ProcStartAnim:Stop() end
    if overlay.ProcLoopAnim and overlay.ProcLoopAnim:IsPlaying() then overlay.ProcLoopAnim:Stop() end
end

local function SuppressOverlay(overlay)
    if overlay:GetAlpha() ~= 0 then overlay:SetAlpha(0) end
    if overlay:IsShown() then overlay:Hide() end
    KillNativeAnimations(overlay)
end

local function HookNativeOverlay(overlay, conditionFunc)
    if overlay._orbitGlowHooked then return end
    hooksecurefunc(overlay, "SetAlpha", function(self, alpha)
        if alpha ~= 0 and conditionFunc() then self:SetAlpha(0) end
    end)
    hooksecurefunc(overlay, "Show", function(self)
        if conditionFunc() then self:SetAlpha(0) end
    end)
    if overlay.animIn then
        hooksecurefunc(overlay.animIn, "Play", function(self)
            if conditionFunc() then self:Stop() end
        end)
    end
    overlay._orbitGlowHooked = true
end

function GC:SuppressNative(button, suppress)
    if not button then return end
    local state = GetState(button)
    state.suppressNative = suppress or nil
    local overlay = button.overlay or button.SpellActivationAlert
    if not overlay then return end
    HookNativeOverlay(overlay, function() return state.suppressNative end)
    if suppress then SuppressOverlay(overlay) end
end

-- [ PROC GLOW ] -------------------------------------------------------------------------------------
local PROC_KEY = "orbitProc"

function GC:ShowProc(button, optionsLookup, prefix, defaultColor)
    if not button or not LCG then return end
    local typeName, options, hash, suppress = Engine.GlowUtils:BuildOptionsFromLookup(optionsLookup, prefix, defaultColor, PROC_KEY)
    self:SuppressNative(button, suppress)
    if not typeName or not options then self:HideProc(button); return end
    self:Show(button, PROC_KEY, typeName, options)
end

function GC:HideProc(button)
    if not button then return end
    self:SuppressNative(button, false)
    self:Hide(button, PROC_KEY)
end

-- [ PANDEMIC GLOW ] ---------------------------------------------------------------------------------
local PANDEMIC_KEY = "orbitPandemic"

local function GetOrCreateWrapper(icon)
    local state = GetState(icon)
    local w = state.pandemicWrapper
    local iw, ih = icon:GetSize()
    local scale = icon:GetEffectiveScale()
    local snW, snH = Engine.Pixel:Snap(iw, scale), Engine.Pixel:Snap(ih, scale)
    if w then
        local ww, wh = w:GetSize()
        if ww ~= snW or wh ~= snH then
            w:SetSize(snW, snH)
            local entry = state.active[PANDEMIC_KEY]
            if entry then
                DoHide(w, entry.typeName, PANDEMIC_KEY)
                state.active[PANDEMIC_KEY] = nil
            end
        end
        return w
    end
    w = CreateFrame("Frame", nil, icon)
    w:SetPoint("CENTER", icon, "CENTER")
    w:SetSize(snW, snH)
    w:SetFrameLevel(icon:GetFrameLevel())
    w:SetAlpha(0)
    state.pandemicWrapper = w
    return w
end

function GC:ShowPandemic(frame, typeName, options, alpha)
    if not frame or not LCG or not typeName then return end
    local wrapper = GetOrCreateWrapper(frame)
    wrapper:SetAlpha(alpha or 1)
    local state = GetState(frame)
    
    if options and not options.frameLevel then 
        options.frameLevel = Constants.Levels.IconGlow
    end
    
    InjectGeometry(options)
    local hash = Engine.GlowUtils:GetOptionsHash(options)
    local entry = state.active[PANDEMIC_KEY]
    if entry and entry.typeName == typeName and entry.hash == hash then return end
    if entry then DoHide(wrapper, entry.typeName, PANDEMIC_KEY) end
    DoShow(wrapper, typeName, options)
    state.active[PANDEMIC_KEY] = { typeName = typeName, hash = hash }
end

function GC:ShowPandemicAlpha(frame, alpha)
    if not frame then return end
    local state = frame._orbitGlow
    if not state or not state.pandemicWrapper then return end
    state.pandemicWrapper:SetAlpha(alpha or 1)
end

function GC:HidePandemic(frame)
    if not frame then return end
    local state = frame._orbitGlow
    if not state or not state.pandemicWrapper then return end
    state.pandemicWrapper:SetAlpha(0)
end

function GC:StopPandemic(frame)
    if not frame or not LCG then return end
    local state = frame._orbitGlow
    if not state then return end
    local entry = state.active[PANDEMIC_KEY]
    if entry then
        if state.pandemicWrapper then
            DoHide(state.pandemicWrapper, entry.typeName, PANDEMIC_KEY)
        end
    end
    state.active[PANDEMIC_KEY] = nil
    if state.pandemicWrapper then state.pandemicWrapper:SetAlpha(0) end
end

-- [ PRELOAD ] ---------------------------------------------------------------------------------------
function GC:PreLoad(typeName, count)
    if LCG and LCG.PreLoad then LCG.PreLoad(typeName, count) end
end
