local addonName, ns = ...
_G.rOrbs = ns -- make global so other files see the same object

-- Handle Blizzard player frame only after SavedVariables are available.
-- Doing this at file load can run before rOrbsDB is initialized.
local function ApplyBlizzardPlayerFrameSetting()
  if not PlayerFrame then return end
  if rOrbsDB and (rOrbsDB.showBlizzardUnitFrames == true or ns._blizzTempVisible == true) then
    PlayerFrame:Show()
    PlayerFrame:SetAlpha(1)
    return
  end
  PlayerFrame:Hide()
  PlayerFrame:SetAlpha(0)
  if not PlayerFrame._rOrbsHideHooked then
    PlayerFrame:HookScript("OnShow", function(self)
      if (rOrbsDB and rOrbsDB.showBlizzardUnitFrames == true) or ns._blizzTempVisible == true then return end
      if not InCombatLockdown() then self:Hide() end
    end)
    PlayerFrame._rOrbsHideHooked = true
  end
end

local function FadeFrameAlpha(frame, toAlpha, duration, hideWhenDone)
  if not frame then return end
  duration = duration or 0.15
  if frame._rOrbsFadeAG then
    frame._rOrbsFadeAG:Stop()
  end
  local ag = frame._rOrbsFadeAG
  if not ag then
    ag = frame:CreateAnimationGroup()
    if ag.SetToFinalAlpha then
      ag:SetToFinalAlpha(true)
    end
    local a = ag:CreateAnimation("Alpha")
    a:SetOrder(1)
    frame._rOrbsFadeAG = ag
    frame._rOrbsFadeAnim = a
    ag:SetScript("OnFinished", function(g)
      if frame and frame.SetAlpha then
        if g._hideWhenDone then
          frame:SetAlpha(0)
        else
          frame:SetAlpha(1)
        end
      end
      if g._hideWhenDone and frame and frame.Hide then
        frame:Hide()
      end
      g._hideWhenDone = nil
    end)
  end
  local anim = frame._rOrbsFadeAnim
  local from = frame:GetAlpha() or 0
  anim:SetDuration(duration)
  anim:SetFromAlpha(from)
  anim:SetToAlpha(toAlpha)
  ag._hideWhenDone = hideWhenDone == true
  if toAlpha > 0 and not frame:IsShown() then
    frame:Show()
  end
  ag:Play()
end

function ns.SetBlizzardUnitFramesTemporaryVisible(visible)
  if not rOrbsDB then return end
  if rOrbsDB.showBlizzardUnitFrames == true then return end
  ns._blizzTempVisible = visible and true or false
  local duration = tonumber(rOrbsDB.blizzardFramesHoldFadeTime) or 0.15
  local playerFrame = PlayerFrame
  local targetFrame = TargetFrame
  if visible then
    if playerFrame then FadeFrameAlpha(playerFrame, 1, duration, false) end
    if targetFrame then FadeFrameAlpha(targetFrame, 1, duration, false) end
  else
    if playerFrame then FadeFrameAlpha(playerFrame, 0, duration, true) end
    if targetFrame then FadeFrameAlpha(targetFrame, 0, duration, true) end
  end
end

do
  local keyListener = CreateFrame("Frame", nil, UIParent)
  keyListener._activeHoldKey = nil
  keyListener._lastPollDown = nil
  keyListener:EnableKeyboard(true)
  keyListener:SetPropagateKeyboardInput(true)
  keyListener:SetScript("OnKeyDown", function(_, key)
    if not rOrbsDB or rOrbsDB.showBlizzardUnitFrames == true then return end
    if GetCurrentKeyBoardFocus() then return end
    local bind = rOrbsDB.blizzardFramesHoldKey
    if bind and key == bind and keyListener._activeHoldKey == nil then
      keyListener._activeHoldKey = key
      ns.SetBlizzardUnitFramesTemporaryVisible(true)
    end
  end)
  keyListener:SetScript("OnKeyUp", function(_, key)
    if not rOrbsDB or rOrbsDB.showBlizzardUnitFrames == true then return end
    if keyListener._activeHoldKey and key == keyListener._activeHoldKey then
      keyListener._activeHoldKey = nil
      ns.SetBlizzardUnitFramesTemporaryVisible(false)
    end
  end)
  keyListener:SetScript("OnUpdate", function()
    if not rOrbsDB or rOrbsDB.showBlizzardUnitFrames == true then return end
    local k = keyListener._activeHoldKey
    if not k then return end
    local isDown = IsKeyDown and IsKeyDown(k) or false
    if keyListener._lastPollDown ~= isDown then
      keyListener._lastPollDown = isDown
    end
    if not isDown then
      keyListener._activeHoldKey = nil
      keyListener._lastPollDown = nil
      ns.SetBlizzardUnitFramesTemporaryVisible(false)
    end
  end)
end

do
  local playerFrameInit = CreateFrame("Frame")
  playerFrameInit:RegisterEvent("VARIABLES_LOADED")
  playerFrameInit:RegisterEvent("PLAYER_ENTERING_WORLD")
  playerFrameInit:SetScript("OnEvent", function(_, event)
    if event == "VARIABLES_LOADED" then
      ApplyBlizzardPlayerFrameSetting()
      C_Timer.After(0, ApplyBlizzardPlayerFrameSetting)
    elseif event == "PLAYER_ENTERING_WORLD" then
      C_Timer.After(0, ApplyBlizzardPlayerFrameSetting)
    end
  end)
end

-- Global helper to strip secret value taint (12.0+)
-- pcall(string.format) creates a NEW untainted string
function rOrbs_CleanNumber(v)
  if v == nil then return 0 end
  local ok, result = pcall(string.format, "%.10g", v)
  if ok then return tonumber(result) or 0 end
  return 0
end

-- all your init stuff
local LSM = LibStub("LibSharedMedia-3.0")
local mediaCallbacks = {}
local initCallbacks = {}
local configLoaded = false

LSM.RegisterCallback(ns, "LibSharedMedia_Registered", function(name, mediaType, key)
  if configLoaded then
    for _, callback in pairs(mediaCallbacks) do
      callback(name, mediaType, key)
    end
  end
end)

function ns.RegisterInit(nameOrFunc, maybeFunc)
    local name, func

    if type(nameOrFunc) == "function" then
        func = nameOrFunc
        -- debug library is not available in WoW Retail; use a simple counter instead
        name = "callback#" .. (#initCallbacks + 1)
    else
        name = tostring(nameOrFunc) -- force string in case someone passed a table
        func = maybeFunc
    end

    local wrapped = function()
        local ok, err = xpcall(func, debugstack)
        if not ok then
            error(("Init callback failed:\n%s"):format(err))
        end
    end

    table.insert(initCallbacks, { name = name, func = wrapped })
end


function ns:Init()
    local errors = {}

    for _, cb in ipairs(initCallbacks) do
        local ok, err = xpcall(cb.func, debugstack)
        if not ok then
            table.insert(errors, ("Init callback failed:\n%s"):format( err))
        end
    end

    -- After running everything, dump errors (if any) through Blizzard's handler
    if #errors > 0 then
        for _, msg in ipairs(errors) do
            geterrorhandler()(msg)
        end
    end

    wipe(initCallbacks)
end

function rOrbs:CheckClientVersion(version)
    if WOW_PROJECT_ID ~= version then return false
    else return true
    end
end

function ns:ListenForMediaChange(callback)
  table.insert(mediaCallbacks, callback)
end

-- DEBUG: Toggle individual target orb layers
-- /rorbsdebug 1..10 to toggle layers, /rorbsdebug to show status
-- /rorbsdebug info to print detailed layer info
-- Fix: SpellActivationOverlayFrame sometimes gets stuck invisible (Blizzard bug).
-- We reset all overlays on PLAYER_ENTERING_WORLD and periodically check for stuck frames.
do
  local spellOverlayFixer = CreateFrame("Frame")
  local CHECK_INTERVAL = 2.0  -- check every 2 seconds
  local elapsed = 0

  local function ResetStuckOverlays()
    if SpellActivationOverlay and SpellActivationOverlay.FreeAllOverlays then
      SpellActivationOverlay:FreeAllOverlays()
    end
    -- Also manually hide any stuck overlay frames
    if SpellActivationOverlayFrame then
      for i = 1, SpellActivationOverlayFrame:GetNumChildren() do
        local child = select(i, SpellActivationOverlayFrame:GetChildren())
        if child and child:IsShown() then
          local alpha = child:GetAlpha()
          -- A stuck overlay has alpha 0 or near 0 but IsShown() returns true
          if alpha ~= nil and alpha < 0.01 then
            child:Hide()
          end
        end
      end
    end
  end

  spellOverlayFixer:RegisterEvent("PLAYER_ENTERING_WORLD")
  spellOverlayFixer:RegisterEvent("PLAYER_LOGIN")
  spellOverlayFixer:SetScript("OnEvent", function(self, event)
    -- Small delay so Blizzard frames finish initializing
    C_Timer.After(1, ResetStuckOverlays)
  end)

  spellOverlayFixer:SetScript("OnUpdate", function(self, dt)
    elapsed = elapsed + dt
    if elapsed >= CHECK_INTERVAL then
      elapsed = 0
      if SpellActivationOverlayFrame then
        for i = 1, SpellActivationOverlayFrame:GetNumChildren() do
          local child = select(i, SpellActivationOverlayFrame:GetChildren())
          if child and child:IsShown() then
            local alpha = child:GetAlpha()
            if alpha ~= nil and alpha < 0.01 then
              child:Hide()
            end
          end
        end
      end
    end
  end)
end

-- ================================================================
-- VEHICLE HIDE: Orbs ausblenden wenn Spieler ein Fahrzeug betritt
-- Frames: Player-Orbs + Angel/Demon + Combat/Resting Icons
-- ================================================================
do
  local vehicleFrame = CreateFrame("Frame")
  vehicleFrame:RegisterUnitEvent("UNIT_ENTERED_VEHICLE", "player")
  vehicleFrame:RegisterUnitEvent("UNIT_EXITED_VEHICLE",  "player")
  vehicleFrame:RegisterEvent("PLAYER_ENTERING_WORLD")

  local function SetOrbsVisible(visible)
    -- Player-Orb-Parent (enthält Health + Power Orb als Kinder)
    local playerFrame = _G["rOrbsPlayerFrame"]
    if playerFrame then
      if visible then playerFrame:Show() else playerFrame:Hide() end
    end
    -- Figuren + Standalone-Icons: nur ausblenden per Hide(); beim Wieder-Einblenden
    -- kein blindes Show() — das würde deaktivierte Icons und mode=="none" Figuren falsch anzeigen.
    local frames = {
      _G["rOrbsAngelFrame"],
      _G["rOrbsDemonFrame"],
      _G["rOrbsPlayerCombatFrame"],
      _G["rOrbsPlayerLeaderFrame"],
      ns.AngelFrame,
      ns.DemonFrame,
      ns.PlayerCombatIcon,
      ns.PlayerRestingIcon,
      ns.PlayerLeaderIcon,
    }
    local seen = {}
    if not visible then
      for _, f in ipairs(frames) do
        if f and not seen[f] then
          seen[f] = true
          f:Hide()
        end
      end
      return
    end
    if ns.ApplyFigureMode then ns.ApplyFigureMode() end
    if ns.ApplyPowerFigureMode then ns.ApplyPowerFigureMode() end
    if ns.RefreshStandalonePlayerIcons then
      ns.RefreshStandalonePlayerIcons()
    else
      C_Timer.After(0, function()
        if ns.ApplyFigureMode then ns.ApplyFigureMode() end
        if ns.ApplyPowerFigureMode then ns.ApplyPowerFigureMode() end
        if ns.RefreshStandalonePlayerIcons then ns.RefreshStandalonePlayerIcons() end
      end)
    end
  end

  vehicleFrame:SetScript("OnEvent", function(self, event, unit)
    if event == "UNIT_ENTERED_VEHICLE" then
      SetOrbsVisible(false)
    elseif event == "UNIT_EXITED_VEHICLE" then
      SetOrbsVisible(true)
    elseif event == "PLAYER_ENTERING_WORLD" then
      -- Kleines Delay damit alle Frames initialisiert sind
      C_Timer.After(0.5, function()
        if UnitInVehicle("player") then
          SetOrbsVisible(false)
        else
          SetOrbsVisible(true)
        end
      end)
    end
  end)
end

-- ================================================================
-- BLIZZARD BOSS-ZIELRAHMEN (z. B. Tiefen-Szenarien) ausblenden
-- oUF:DisableBlizzard("boss") läuft normalerweise nur bei oUF:Spawn("boss").
-- rOrbs nutzt keine Boss-UnitFrames — der Standard-Rahmen wäre sonst redundant.
-- ================================================================
do
  local function tryDisableBlizzardBoss()
    local oUF = ns.oUF
    if oUF and oUF.DisableBlizzard then
      oUF:DisableBlizzard("boss")
    end
  end

  ns:RegisterInit(function()
    tryDisableBlizzardBoss()
    local f = CreateFrame("Frame")
    f:RegisterEvent("PLAYER_ENTERING_WORLD")
    f:SetScript("OnEvent", function()
      C_Timer.After(0, tryDisableBlizzardBoss)
    end)
  end)
end

SLASH_RORBSDEBUG1 = "/rorbsdebug"
SlashCmdList["RORBSDEBUG"] = function(msg)
  local orb = _G["rOrbsTargetHealthOrb"]
  if not orb then print("|cffff0000[rOrbs Debug]|r Target health orb not found") return end
  
  local layers = {
    { name = "bg",         obj = orb.bg,         desc = "1: Background (orb_back2)" },
    { name = "fill",       obj = orb.fill,       desc = "2: Fill StatusBar FRAME (the actual bar)", isFrame = true },
    { name = "fillTex",    obj = orb.fillTex,     desc = "3: Fill Texture object" },
    { name = "clipFrame",  obj = orb.clipFrame,   desc = "4: Clip Frame", isFrame = true },
    { name = "galaxies",   obj = orb.galaxies,    desc = "5: Galaxies (animated)", isTable = true },
    { name = "bubbles",    obj = orb.bubbles,     desc = "6: Bubbles (animated)", isTable = true },
    { name = "spark",      obj = orb.spark,        desc = "7: Spark" },
    { name = "lowHP",      obj = orb.lowHP,        desc = "8: Low HP Glow" },
    { name = "highlight",  obj = orb.highlight,    desc = "9: Gloss (orb_gloss)" },
    { name = "overlay",    obj = orb.overlay,      desc = "10: Overlay Frame (parent of gloss/skull/lowHP)", isFrame = true },
  }
  
  -- Store overlay ref for debug
  if not orb.overlay then
    -- Find overlay by child search
    for _, child in ipairs({orb:GetChildren()}) do
      local n = child:GetName()
      if n and n:find("Overlay") then
        orb.overlay = child
        layers[10].obj = child
        break
      end
    end
  end
  
  if msg == "info" then
    print("|cff00ff00[rOrbs Debug]|r === DETAILED INFO ===")
    -- Fill StatusBar info
    if orb.fill then
      local min, max = orb.fill:GetMinMaxValues()
      local val = orb.fill:GetValue()
      print(string.format("  Fill StatusBar: value=%.2f, min=%.2f, max=%.2f", val or 0, min or 0, max or 0))
      print(string.format("  Fill FrameLevel: %d, Strata: %s", orb.fill:GetFrameLevel(), orb.fill:GetFrameStrata()))
      local ft = orb.fill:GetStatusBarTexture()
      if ft then
        local r, g, b, a = ft:GetVertexColor()
        print(string.format("  FillTex VertexColor: r=%.2f g=%.2f b=%.2f a=%.2f", r or 0, g or 0, b or 0, a or 0))
        print(string.format("  FillTex DrawLayer: %s, Alpha: %.2f", ft:GetDrawLayer() or "?", ft:GetAlpha()))
        print(string.format("  FillTex Size: %.1f x %.1f", ft:GetWidth(), ft:GetHeight()))
      end
    end
    -- Highlight info
    if orb.highlight then
      local r, g, b, a = orb.highlight:GetVertexColor()
      print(string.format("  Gloss VertexColor: r=%.2f g=%.2f b=%.2f a=%.2f", r or 0, g or 0, b or 0, a or 0))
      local dl, sl = orb.highlight:GetDrawLayer()
      print(string.format("  Gloss DrawLayer: %s-%d, Alpha: %.2f, Blend: %s", dl or "?", sl or 0, orb.highlight:GetAlpha(), orb.highlight:GetBlendMode()))
    end
    -- Background info
    if orb.bg then
      local r, g, b, a = orb.bg:GetVertexColor()
      print(string.format("  BG VertexColor: r=%.2f g=%.2f b=%.2f a=%.2f", r or 0, g or 0, b or 0, a or 0))
      print(string.format("  BG Desaturated: %s", tostring(orb.bg:IsDesaturated())))
    end
    -- Orb frame info
    print(string.format("  Orb FrameLevel: %d", orb:GetFrameLevel()))
    -- List ALL textures on orb
    print("  --- All textures on orb button ---")
    for i, region in ipairs({orb:GetRegions()}) do
      if region.GetTexture then
        local tex = region:GetTexture()
        local texStr = tex and tostring(tex) or "nil"
        local texName = type(tex) == "string" and (tex:match("([^\\]+)$") or tex) or texStr
        local dl, sl = region:GetDrawLayer()
        local r, g, b, a = region:GetVertexColor()
        print(string.format("    [%d] tex=%s layer=%s-%d rgba=%.1f,%.1f,%.1f,%.1f vis=%s alpha=%.2f blend=%s", 
          i, texName, dl or "?", sl or 0, r, g, b, a, tostring(region:IsShown()), region:GetAlpha(), region:GetBlendMode()))
      end
    end
    -- List ALL child frames and THEIR textures
    print("  --- All child frames + their textures ---")
    local function dumpFrame(frame, indent)
      local n = frame:GetName() or "unnamed"
      print(string.format("%s[Frame] %s FL=%d vis=%s strata=%s type=%s", indent, n, frame:GetFrameLevel(), tostring(frame:IsShown()), frame:GetFrameStrata(), frame:GetObjectType()))
      for j, region in ipairs({frame:GetRegions()}) do
        if region.GetTexture then
          local tex = region:GetTexture()
          local texStr = tex and tostring(tex) or "nil"
          local texName = type(tex) == "string" and (tex:match("([^\\]+)$") or tex) or texStr
          local dl, sl = region:GetDrawLayer()
          local r, g, b, a = region:GetVertexColor()
          print(string.format("%s  [Tex %d] %s layer=%s-%d rgba=%.1f,%.1f,%.1f,%.1f vis=%s alpha=%.2f blend=%s", 
            indent, j, texName, dl or "?", sl or 0, r, g, b, a, tostring(region:IsShown()), region:GetAlpha(), region:GetBlendMode()))
        elseif region.GetText then
          print(string.format("%s  [Font %d] text='%s' vis=%s", indent, j, tostring(region:GetText()), tostring(region:IsShown())))
        end
      end
      for _, child in ipairs({frame:GetChildren()}) do
        dumpFrame(child, indent .. "    ")
      end
    end
    for _, child in ipairs({orb:GetChildren()}) do
      dumpFrame(child, "    ")
    end
    return
  end
  
  local num = tonumber(msg)
  if num and layers[num] then
    local layer = layers[num]
    if layer.isTable then
      local first = layer.obj and layer.obj[1]
      if first then
        local visible = first:IsShown()
        for _, t in pairs(layer.obj) do
          if visible then t:Hide() else t:Show() end
        end
        print("|cff00ff00[rOrbs Debug]|r", layer.desc, visible and "|cffff0000HIDDEN|r" or "|cff00ff00SHOWN|r")
      end
    elseif layer.isFrame then
      if layer.obj then
        local visible = layer.obj:IsShown()
        if visible then layer.obj:Hide() else layer.obj:Show() end
        print("|cff00ff00[rOrbs Debug]|r", layer.desc, visible and "|cffff0000HIDDEN|r" or "|cff00ff00SHOWN|r")
      else
        print("|cffff0000[rOrbs Debug]|r", layer.desc, "= NIL (not found)")
      end
    else
      if layer.obj then
        local visible = layer.obj:IsShown()
        if visible then layer.obj:Hide() else layer.obj:Show() end
        print("|cff00ff00[rOrbs Debug]|r", layer.desc, visible and "|cffff0000HIDDEN|r" or "|cff00ff00SHOWN|r")
      end
    end
  else
    print("|cff00ff00[rOrbs Debug]|r Target Health Orb Layers:")
    for i, layer in ipairs(layers) do
      local shown = "?"
      if layer.isTable then
        local first = layer.obj and layer.obj[1]
        shown = first and (first:IsShown() and "SHOWN" or "HIDDEN") or "nil"
      elseif layer.obj then
        shown = layer.obj:IsShown() and "SHOWN" or "HIDDEN"
      else
        shown = "nil"
      end
      print(string.format("  |cffffff00%d|r = %s [%s]", i, layer.desc, shown))
    end
    print("  |cffffff00info|r = Print detailed layer info")
    print("Usage: /rorbsdebug <1-10|info>")
  end
end
