local addon, ns = ...

local LSM = LibStub("LibSharedMedia-3.0")

ns.addonName = addon
ns.mediapath = "Interface\\AddOns\\rOrbs\\media\\"

-- PlayerModel clips 3D to its own rectangle. Viewport = holder size + 2*pad per axis.
-- Minimum pad (px); fraction of holder width per side (stacked +30% vs prior 1.30x → ~1.69x total width).
ns.ORB_MODEL_VIEW_PAD = 8
ns.ORB_MODEL_VIEW_PAD_FRAC = 0.345  -- per side → total width ≈ holder * (1 + 2*0.345) ≈ 1.69x

function ns.ApplyOrbModelAnchors(model, holder, fillShiftY)
  if not model or not holder then return end
  fillShiftY = tonumber(fillShiftY) or 0
  local w = holder:GetWidth()
  if not w or w <= 0 then w = 64 end
  local minPad = ns.ORB_MODEL_VIEW_PAD or 8
  local frac = ns.ORB_MODEL_VIEW_PAD_FRAC or 0.8
  local pad = math.max(minPad, w * frac)
  model:ClearAllPoints()
  model:SetPoint("TOPLEFT", holder, "TOPLEFT", -pad, pad + fillShiftY)
  model:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", pad, -pad + fillShiftY)
end

-- Wie rOrbs_alteversion: Modell sitzt im holder unter clipFrame (FrameLevel über dem Fill).
-- Kein separates Viewport-Frame auf derselben Ebene wie der Fill (sonst liegt die Füll-Textur vorn).
-- Füll-Kopplung: vertikaler Offset am Modell-Rahmen (ApplyOrbModelAnchors) + Template AdjustByValue (3D-Pan).
-- rOrbsDB.tmplModelDropWithFill == false: kein Zusatz-Offset.
-- rOrbsDB.tmplModelDropFrac: Stärke 0..1 (Anteil der Orb-Höhe), Standard 0.38.
function ns.UpdateOrbModelWithFillRatio(orb, ratio)
  if not orb or not orb.model then return end
  local model = orb.model
  local holder = orb.modelHolder or orb.modelScrollChild
  if not holder then holder = orb end
  if rOrbsDB and rOrbsDB.tmplModelDropWithFill == false then
    ns.ApplyOrbModelAnchors(model, holder, 0)
    return
  end
  local r = tonumber(ratio)
  if not r then r = 1 end
  if r < 0 then r = 0 elseif r > 1 then r = 1 end
  local h = orb:GetHeight()
  if not h or h <= 0 then h = 256 end
  local frac = tonumber(rOrbsDB and rOrbsDB.tmplModelDropFrac) or 0.38
  local shiftY = - (1 - r) * h * frac
  ns.ApplyOrbModelAnchors(model, holder, shiftY)
end

-- Alte Signatur (orb, model, ratio) — drittes Argument wird ignoriert außer orb.model.
function ns.ApplyOrbModelFillDropShift(orb, model, ratio)
  ns.UpdateOrbModelWithFillRatio(orb, ratio)
end

-- Verstecktes FontString wie bei ns.UpdateLowHPGlow (core/tags.lua).
function ns.GetOrbFillRatioScratch()
  local s = ns._orbFillRatioScratchFS
  if s then return s end
  local f = CreateFrame("Frame", nil, UIParent)
  f:SetSize(1, 1)
  f:Hide()
  s = f:CreateFontString(nil, "ARTWORK")
  s:SetFont("Fonts\\FRIZQT__.TTF", 10)
  ns._orbFillRatioScratchFS = s
  return s
end

-- 1) Verhältnis aus sichtbarer Füllhöhe (fillTex vs. Bar) — gleicher Effekt wie der Balken, meist pcall-sicher.
-- 2) Fallback: UnitHealthPercent / UnitPowerPercent über Scratch-String (wie tags.lua).
function ns.GetOrbFillRatioForViewport(orb)
  if not orb then return 1 end
  local unit = orb.unit
  if unit and UnitExists(unit) and UnitIsDeadOrGhost(unit) then return 0 end

  if orb.fill then
    local tex = orb.fill:GetStatusBarTexture()
    if tex then
      local ok, r = pcall(function()
        local bh = orb.fill:GetHeight()
        local fh = tex:GetHeight()
        if not bh or bh <= 0 then return nil end
        if not fh or fh < 0 then return nil end
        return fh / bh
      end)
      if ok and r ~= nil and r == r then
        r = math.max(0, math.min(1, r))
        return r
      end
    end
  end

  if not unit or not UnitExists(unit) then return 1 end

  local scratch = ns.GetOrbFillRatioScratch()
  local isPower = (orb.type == "POWER") or (orb._orbType == "POWER")

  if isPower then
    if type(UnitPowerPercent) ~= "function" then return 1 end
    -- UnitPowerType: 1. Rückgabe = numerischer Enum (für UnitPowerPercent), 2. = Token-String z.B. "MANA"
    local powerType = select(1, UnitPowerType(unit))
    if powerType == nil then return 1 end
    local pct
    if CurveConstants and CurveConstants.ScaleTo100 then
      pct = UnitPowerPercent(unit, powerType, false, CurveConstants.ScaleTo100)
    else
      pct = UnitPowerPercent(unit, powerType, false, true)
    end
    if type(pct) ~= "number" then return 1 end
    scratch:SetFormattedText("%.10f", pct)
    local n = tonumber(scratch:GetText())
    if not n then return 1 end
    if n > 1.0001 then n = n / 100 end
    if n < 0 then n = 0 elseif n > 1 then n = 1 end
    return n
  end

  if type(UnitHealthPercent) ~= "function" then return 1 end
  local pct
  if CurveConstants and CurveConstants.ScaleTo100 then
    pct = UnitHealthPercent(unit, true, CurveConstants.ScaleTo100)
  else
    pct = UnitHealthPercent(unit, true, true)
  end
  if type(pct) ~= "number" then return 1 end
  scratch:SetFormattedText("%.10f", pct)
  local n = tonumber(scratch:GetText())
  if not n then return 1 end
  if n > 1.0001 then n = n / 100 end
  if n < 0 then n = 0 elseif n > 1 then n = 1 end
  return n
end

-- Namespace containers
ns.cfg  = {}
ns.unit = CreateFrame("Frame")
ns.func = CreateFrame("Frame")
ns.player = {}

-- Register additional LSM fonts shipped with the addon
LSM:Register("font", "Enchanted Land", ns.mediapath .. "Enchanted_Land.otf")

rOrbs.defaults = { profile = {} }

-- Power bar colours (keyed by power token, e.g. "MANA", "ENERGY", "RAGE" …)
rOrbs.defaults.profile.powercolors = {
    MANA        = { r = 0.00, g = 0.44, b = 0.87 },
    RAGE        = { r = 0.78, g = 0.25, b = 0.25 },
    FOCUS       = { r = 0.78, g = 0.61, b = 0.43 },
    ENERGY      = { r = 1.00, g = 0.91, b = 0.00 },
    COMBO_POINTS= { r = 1.00, g = 0.96, b = 0.41 },
    RUNES       = { r = 0.55, g = 0.57, b = 0.61 },
    RUNIC_POWER = { r = 0.00, g = 0.82, b = 1.00 },
    SOUL_SHARDS = { r = 0.50, g = 0.32, b = 0.55 },
    LUNAR_POWER = { r = 0.30, g = 0.52, b = 0.90 },
    HOLY_POWER  = { r = 0.95, g = 0.90, b = 0.60 },
    MAELSTROM   = { r = 0.00, g = 0.50, b = 1.00 },
    CHI         = { r = 0.71, g = 0.90, b = 0.76 },
    ARCANE_CHARGES={ r= 0.22, g = 0.39, b = 0.98 },
    FURY        = { r = 0.79, g = 0.26, b = 0.99 },
    PAIN        = { r = 1.00, g = 0.61, b = 0.00 },
    ESSENCE     = { r = 0.00, g = 0.80, b = 1.00 },
}

-- Colorswitcher: controls health bar foreground/background colouring
rOrbs.defaults.profile.colorswitcher = {
    classcolored        = true,
    useBrightForeground = false,   -- true = class color on bar, dark on bg; false = reversed
    bright = { r = 0.30, g = 0.65, b = 0.30, a = 1.0 },
    dark   = { r = 0.15, g = 0.15, b = 0.15, a = 0.9 },
}

-- Highlight opacity multiplier for the health bar gloss
rOrbs.defaults.profile.highlightMultiplier = 0.4

rOrbs.defaults.profile.fonts = {
    textFont   = "Enchanted Land",
    numberFont = "Enchanted Land",
}

-- Frame dragging settings
rOrbs.defaults.profile.framesUserplaced = true
rOrbs.defaults.profile.framesLocked     = false

rOrbs.defaults.profile.units = {
    player = {
        show  = true,
        size  = 176,
        scale = 1,
        pos   = { a1 = "BOTTOM", a2 = "BOTTOM", af = "UIParent", x = -16, y = 600 },
        health = { frequentUpdates = true },
        power  = { frequentUpdates = true },
        absorb = { show = true },
    },
    target = {
        show  = true,
        size  = 80,
        scale = 1,
        pos   = { a1 = "BOTTOM", a2 = "BOTTOM", af = "UIParent", x = 264, y = 150 },
    },
    targetHealth = {
        show  = true,
        size  = 160,
        scale = 1,
        pos   = { a1 = "BOTTOM", a2 = "BOTTOM", af = "UIParent", x = 264, y = 2 },
        health = { frequentUpdates = true },
        absorb = { show = true },
    },
    targettarget = {
        show  = true,
        size  = 120,
        scale = 1,
        pos   = { a1 = "BOTTOM", a2 = "BOTTOM", af = "UIParent", x = 420, y = 2 },
        health = { frequentUpdates = true },
        absorb = { show = true },
    },
    pet = {
        show  = true,
        size  = 100,
        scale = 1,
        pos   = { a1 = "BOTTOM", a2 = "BOTTOM", af = "UIParent", x = -264, y = 2 },
        health = { frequentUpdates = true },
    },
    party = {
        show       = true,
        scale      = 1.0,
        width      = 185,
        height     = 64,
        vertwidth  = 64,
        vertheight = 185,
        vertical   = false,
        fontSize   = 11,
        pos = { a1 = "TOPLEFT", a2 = "TOPLEFT", af = "UIParent", x = 100, y = -200 },
        health = {
            texture        = "Interface\\AddOns\\rOrbs\\media\\statusbar",
            frequentUpdates = true,
            fontSize       = 11,
            textPos        = "CENTER",
            x              = 0,
            y              = 0,
            tag            = "[diablo:curhp]",
        },
        power = {
            texture        = "Interface\\AddOns\\rOrbs\\media\\statusbar",
            frequentUpdates = true,
        },
        portrait = {
            show  = true,
            use3D = false,
            width = 52,
        },
        auras = {
            show                   = true,
            showBuffs              = false,
            hideBuffs              = true,
            size                   = 14,
            number                 = 4,
            disableCooldownText    = false,
            showDebuffType         = true,
            onlyShowPlayerDebuffs  = false,
            onlyDispellable        = false,
            whitelist              = {},
            blacklist              = {},
        },
        aurawatch = {
            show          = false,
            size          = 13,
            alpha         = 1,
            onlyShowPresent = true,
            pos           = { a1 = "RIGHT", x = 5, y = 0 },
            spellIds      = { ALL = {}, DRUID = {}, PRIEST = {}, PALADIN = {}, SHAMAN = {} },
        },
        healprediction = {
            show        = true,
            texture     = "Interface\\AddOns\\rOrbs\\media\\statusbar",
            maxoverflow = 1.05,
            color = {
                myself = { r = 0.3, g = 1.0, b = 0.3, a = 0.4 },
                other  = { r = 0.3, g = 0.3, b = 1.0, a = 0.4 },
            },
        },
        totalabsorb = {
            show    = true,
            texture = "Interface\\AddOns\\rOrbs\\media\\statusbar",
            color   = {
                bar = { r = 0.7, g = 0.9, b = 1.0, a = 0.5 },
            },
        },
        alpha = {
            notinrange = 0.4,
        },
        attributes = {
            visibility      = "[@raid6,exists] hide; show",
            showPlayer      = false,
            showSolo        = false,
            showParty       = true,
            showRaid        = false,
            VerticalAnchor  = "TOP",
            yoffset         = 5,
            HorizontalAnchor = "RIGHT",
            hideInArena     = true,
        },
    },
}

-- GUI defaults stored inside rOrbsDB
rOrbs.defaults.gui = {
    minimap    = { hide = false },
    debug      = false,
    frameLocked = false,
}

ns.defaults = rOrbs.defaults

-- Stubs (filled in after GUI loads)
function rOrbs:AddConfig() end
function rOrbs:TriggerReload() end

-- Load config after VARIABLES_LOADED
local loader = CreateFrame("Frame")
loader:RegisterEvent("VARIABLES_LOADED")
loader:SetScript("OnEvent", function(self, event)
    self:UnregisterEvent("VARIABLES_LOADED")

    -- ── SavedVariable: rOrbsDB (GUI & position settings) ──────────────
    if not rOrbsDB then
        rOrbsDB = {}
    end
    -- Merge GUI defaults into rOrbsDB without overwriting saved values
    local guiDef = rOrbs.defaults.gui
    if rOrbsDB.minimap == nil    then rOrbsDB.minimap     = { hide = false } end
    if rOrbsDB.debug == nil      then rOrbsDB.debug        = false           end
    -- Standard: „gesperrte“ GUI (Overlay nur mit Shift/Alt); gespeicherten Wert nicht überschreiben
    if rOrbsDB.frameLocked == nil then rOrbsDB.frameLocked = true end

    -- Target orb settings
    if rOrbsDB.targetModelShow   == nil then rOrbsDB.targetModelShow   = true  end
    if rOrbsDB.targetModelAlpha  == nil then rOrbsDB.targetModelAlpha  = 1     end
    if rOrbsDB.targetPortraitShow == nil then rOrbsDB.targetPortraitShow = false end
    if rOrbsDB.targetClassColor  == nil then rOrbsDB.targetClassColor  = true  end
    if rOrbsDB.targetRingShow    == nil then rOrbsDB.targetRingShow    = true  end
    if rOrbsDB.targetRingRotate  == nil then rOrbsDB.targetRingRotate  = false end
    if rOrbsDB.targetRingSpeed   == nil then rOrbsDB.targetRingSpeed   = 30    end
    if rOrbsDB.targetRingDir     == nil then rOrbsDB.targetRingDir     = "CW"  end
    if rOrbsDB.targetOrbEnabled  == nil then rOrbsDB.targetOrbEnabled  = true  end

    -- Pet orb settings
    if rOrbsDB.petOrbEnabled     == nil then rOrbsDB.petOrbEnabled     = true  end
    if rOrbsDB.petRingShow       == nil then rOrbsDB.petRingShow       = true  end
    if rOrbsDB.petNameShow       == nil then rOrbsDB.petNameShow       = true  end
    if rOrbsDB.petNameFontSize   == nil then rOrbsDB.petNameFontSize   = 16    end
    if rOrbsDB.petNamePos        == nil then rOrbsDB.petNamePos        = "TOP" end
    if rOrbsDB.petNameOffY       == nil then rOrbsDB.petNameOffY       = -8    end

    -- Target-of-Target orb settings
    if rOrbsDB.totOrbEnabled     == nil then rOrbsDB.totOrbEnabled     = false end
    if rOrbsDB.totModelShow      == nil then rOrbsDB.totModelShow      = false end
    if rOrbsDB.totModelAlpha     == nil then rOrbsDB.totModelAlpha     = 1     end
    if rOrbsDB.totPortraitShow   == nil then rOrbsDB.totPortraitShow   = false end
    if rOrbsDB.totClassColor     == nil then rOrbsDB.totClassColor     = true  end
    if rOrbsDB.totRingShow       == nil then rOrbsDB.totRingShow       = true  end
    if rOrbsDB.totNameShow       == nil then rOrbsDB.totNameShow       = true  end
    if rOrbsDB.totNameFontSize   == nil then rOrbsDB.totNameFontSize   = 18    end
    if rOrbsDB.totNamePos        == nil then rOrbsDB.totNamePos        = "TOP" end
    if rOrbsDB.totNameClassColor == nil then rOrbsDB.totNameClassColor = false end
    if rOrbsDB.totNameOffY       == nil then rOrbsDB.totNameOffY       = -8    end

    -- Health / Power ring + figure visibility
    if rOrbsDB.healthRingShow  == nil then rOrbsDB.healthRingShow  = true end
    if rOrbsDB.powerRingShow   == nil then rOrbsDB.powerRingShow   = true end
    if rOrbsDB.healthAngelShow      == nil then rOrbsDB.healthAngelShow      = true          end
    if rOrbsDB.powerDemonShow        == nil then rOrbsDB.powerDemonShow        = true          end
    if rOrbsDB.runePointsShow        == nil then rOrbsDB.runePointsShow        = true          end
    if rOrbsDB.runePointsBGShow      == nil then rOrbsDB.runePointsBGShow      = true          end
    -- Rune layout: "arc" (angle slider) or "horizontal" (free row). Migrate legacy 8-way presets once.
    if not rOrbsDB._runeArcLayoutMigrated then
        local p = rOrbsDB.runePointsPosition
        local legacy = {
            inside_top     = { a = 90,  o = false },
            outside_top    = { a = 90,  o = true  },
            inside_bottom  = { a = 270, o = false },
            outside_bottom = { a = 270, o = true  },
            inside_left    = { a = 180, o = false },
            outside_left   = { a = 180, o = true  },
            inside_right   = { a = 0,   o = false },
            outside_right  = { a = 0,   o = true  },
        }
        if p and p ~= "horizontal" and p ~= "arc" and legacy[p] then
            rOrbsDB.runeArcAngle    = legacy[p].a
            rOrbsDB.runeArcOutside  = legacy[p].o
            rOrbsDB.runePointsPosition = "arc"
        elseif p == nil then
            rOrbsDB.runePointsPosition = "arc"
        elseif p ~= "horizontal" and p ~= "arc" then
            rOrbsDB.runePointsPosition = "arc"
        end
        rOrbsDB._runeArcLayoutMigrated = true
    end
    if rOrbsDB.runePointsPosition    == nil then rOrbsDB.runePointsPosition    = "arc" end
    if rOrbsDB.runeArcAngle          == nil then rOrbsDB.runeArcAngle          = 90 end
    if rOrbsDB.runeArcOutside        == nil then rOrbsDB.runeArcOutside        = true end

    -- Figure position & scale defaults (from SavedVariables reference)
    if rOrbsDB.angelScale == nil then rOrbsDB.angelScale = 0.95 end
    if rOrbsDB.angelOffX  == nil then rOrbsDB.angelOffX  = 57   end
    if rOrbsDB.angelOffY  == nil then rOrbsDB.angelOffY  = 10   end
    if rOrbsDB.demonScale == nil then rOrbsDB.demonScale = 1.0  end
    if rOrbsDB.demonOffX  == nil then rOrbsDB.demonOffX  = -74  end
    if rOrbsDB.demonOffY  == nil then rOrbsDB.demonOffY  = 4    end

    -- Diablo Health figure (health orb, configurable texture)
    if rOrbsDB.diabloHealthShow    == nil then rOrbsDB.diabloHealthShow    = true                    end
    if rOrbsDB.diabloHealthTexture == nil then rOrbsDB.diabloHealthTexture = "diablo_health"         end
    if rOrbsDB.diabloHealthScale   == nil then rOrbsDB.diabloHealthScale   = 1.0                     end
    if rOrbsDB.diabloHealthOffX    == nil then rOrbsDB.diabloHealthOffX    = 0                       end
    if rOrbsDB.diabloHealthOffY    == nil then rOrbsDB.diabloHealthOffY    = 0                       end

    -- Font defaults
    if rOrbsDB["font_HEALTH"]        == nil then rOrbsDB["font_HEALTH"]        = "Enchanted Land" end
    if rOrbsDB["font_POWER"]         == nil then rOrbsDB["font_POWER"]         = "Enchanted Land" end
    if rOrbsDB["font_TARGET_HEALTH"] == nil then rOrbsDB["font_TARGET_HEALTH"] = "Enchanted Land" end

    -- Pet font defaults
    if rOrbsDB["font_PET_HEALTH"]          == nil then rOrbsDB["font_PET_HEALTH"]          = "Enchanted Land" end
    if rOrbsDB["fontSize_PET_HEALTH"]      == nil then rOrbsDB["fontSize_PET_HEALTH"]      = 22 end
    if rOrbsDB["textShow_PET_HEALTH"]      == nil then rOrbsDB["textShow_PET_HEALTH"]      = true end
    if rOrbsDB["textHover_PET_HEALTH"]     == nil then rOrbsDB["textHover_PET_HEALTH"]     = true end
    if rOrbsDB["textAlpha_PET_HEALTH"]     == nil then rOrbsDB["textAlpha_PET_HEALTH"]     = 0.8  end
    if rOrbsDB["glossTex_PET_HEALTH"]      == nil then rOrbsDB["glossTex_PET_HEALTH"]      = "classic" end

    -- ToT font defaults
    if rOrbsDB["font_TOT_HEALTH"]         == nil then rOrbsDB["font_TOT_HEALTH"]         = "Enchanted Land" end
    if rOrbsDB["fontSize_TOT_HEALTH"]     == nil then rOrbsDB["fontSize_TOT_HEALTH"]     = 28 end
    if rOrbsDB["textShow_TOT_HEALTH"]     == nil then rOrbsDB["textShow_TOT_HEALTH"]     = true end
    if rOrbsDB["textHover_TOT_HEALTH"]    == nil then rOrbsDB["textHover_TOT_HEALTH"]    = true end
    if rOrbsDB["textAlpha_TOT_HEALTH"]    == nil then rOrbsDB["textAlpha_TOT_HEALTH"]    = 0.8  end
    if rOrbsDB["glossTex_TOT_HEALTH"]     == nil then rOrbsDB["glossTex_TOT_HEALTH"]     = "classic" end

    -- Font size defaults
    if rOrbsDB["fontSize_HEALTH"]        == nil then rOrbsDB["fontSize_HEALTH"]        = 35 end
    if rOrbsDB["fontSize_POWER"]         == nil then rOrbsDB["fontSize_POWER"]         = 35 end
    if rOrbsDB["fontSize_TARGET_HEALTH"] == nil then rOrbsDB["fontSize_TARGET_HEALTH"] = 34 end

    -- Text show/hover/alpha defaults
    if rOrbsDB["textShow_HEALTH"]        == nil then rOrbsDB["textShow_HEALTH"]        = true end
    if rOrbsDB["textShow_POWER"]         == nil then rOrbsDB["textShow_POWER"]         = true end
    if rOrbsDB["textShow_TARGET_HEALTH"] == nil then rOrbsDB["textShow_TARGET_HEALTH"] = true end
    if rOrbsDB["textHover_HEALTH"]       == nil then rOrbsDB["textHover_HEALTH"]       = true end
    if rOrbsDB["textHover_POWER"]        == nil then rOrbsDB["textHover_POWER"]        = true end
    if rOrbsDB["textHover_TARGET_HEALTH"]== nil then rOrbsDB["textHover_TARGET_HEALTH"]= true end
    if rOrbsDB["textAlpha_HEALTH"]       == nil then rOrbsDB["textAlpha_HEALTH"]       = 0.8  end
    if rOrbsDB["textAlpha_POWER"]        == nil then rOrbsDB["textAlpha_POWER"]        = 0.8  end
    if rOrbsDB["textAlpha_TARGET_HEALTH"]== nil then rOrbsDB["textAlpha_TARGET_HEALTH"]= 0.8  end
    if rOrbsDB["textMode_HEALTH"]        == nil then rOrbsDB["textMode_HEALTH"]        = "BOTH" end

    -- Target name text defaults
    if rOrbsDB["targetNameShow"]       == nil then rOrbsDB["targetNameShow"]       = true  end
    if rOrbsDB["targetNameFontSize"]   == nil then rOrbsDB["targetNameFontSize"]   = 22    end
    if rOrbsDB["targetNamePos"]        == nil then rOrbsDB["targetNamePos"]        = "TOP" end
    if rOrbsDB["targetNameClassColor"] == nil then rOrbsDB["targetNameClassColor"] = false end
    if rOrbsDB["targetNameOffY"]       == nil then rOrbsDB["targetNameOffY"]       = -8    end

    -- Cast Ring defaults
    if rOrbsDB.castRingEnabled        == nil then rOrbsDB.castRingEnabled        = true    end
    if rOrbsDB.castRingRingScale      == nil then rOrbsDB.castRingRingScale      = 1.0     end
    if rOrbsDB.castRingUseClassColor  == nil then rOrbsDB.castRingUseClassColor  = false   end
    if rOrbsDB.castRingCastR          == nil then rOrbsDB.castRingCastR          = 1.0     end
    if rOrbsDB.castRingCastG          == nil then rOrbsDB.castRingCastG          = 0.15    end
    if rOrbsDB.castRingCastB          == nil then rOrbsDB.castRingCastB          = 0.05    end
    if rOrbsDB.castRingChanR          == nil then rOrbsDB.castRingChanR          = 0.3     end
    if rOrbsDB.castRingChanG          == nil then rOrbsDB.castRingChanG          = 0.6     end
    if rOrbsDB.castRingChanB          == nil then rOrbsDB.castRingChanB          = 1.0     end
    if rOrbsDB.castRingBgMode         == nil then rOrbsDB.castRingBgMode         = "rose_bright" end
    if rOrbsDB.castRingBgAlpha        == nil then rOrbsDB.castRingBgAlpha        = 0.8     end
    if rOrbsDB.castRingFadeTime       == nil then rOrbsDB.castRingFadeTime       = 0.4     end
    if rOrbsDB.castRingBgR            == nil then rOrbsDB.castRingBgR            = 1.0     end
    if rOrbsDB.castRingBgG            == nil then rOrbsDB.castRingBgG            = 1.0     end
    if rOrbsDB.castRingBgB            == nil then rOrbsDB.castRingBgB            = 1.0     end
    if rOrbsDB.castRingBgClassColor   == nil then rOrbsDB.castRingBgClassColor   = false   end

    -- Player Combat Icon: size & position offset (relative to auto-position)
    if rOrbsDB.playerCombatIconSize  == nil then rOrbsDB.playerCombatIconSize  = 28 end
    if rOrbsDB.playerCombatIconOffX  == nil then rOrbsDB.playerCombatIconOffX  = 0  end
    if rOrbsDB.playerCombatIconOffY  == nil then rOrbsDB.playerCombatIconOffY  = 0  end
    -- Player Resting Icon: size & position offset
    if rOrbsDB.playerRestingIconSize == nil then rOrbsDB.playerRestingIconSize = 28 end
    if rOrbsDB.playerRestingIconOffX == nil then rOrbsDB.playerRestingIconOffX = 0  end
    if rOrbsDB.playerRestingIconOffY == nil then rOrbsDB.playerRestingIconOffY = 0  end
    if rOrbsDB.playerRestingIconColor == nil then rOrbsDB.playerRestingIconColor = { r = 1, g = 1, b = 1 } end
    -- Target Combat Icon: size & position offset
    if rOrbsDB.targetCombatIconSize  == nil then rOrbsDB.targetCombatIconSize  = 28 end
    if rOrbsDB.targetCombatIconOffX  == nil then rOrbsDB.targetCombatIconOffX  = 0  end
    if rOrbsDB.targetCombatIconOffY  == nil then rOrbsDB.targetCombatIconOffY  = 0  end

    -- Orb template defaults
    if rOrbsDB["tmplShow_HEALTH"]        == nil then rOrbsDB["tmplShow_HEALTH"]        = true  end
    if rOrbsDB["tmplShow_POWER"]         == nil then rOrbsDB["tmplShow_POWER"]         = true  end
    if rOrbsDB["tmplShow_TARGET_HEALTH"] == nil then rOrbsDB["tmplShow_TARGET_HEALTH"] = false end
    if rOrbsDB["tmplShow_PET_HEALTH"]    == nil then rOrbsDB["tmplShow_PET_HEALTH"]    = false end
    if rOrbsDB["tmplOpacity_HEALTH"]        == nil then rOrbsDB["tmplOpacity_HEALTH"]        = 1 end
    if rOrbsDB["tmplOpacity_POWER"]         == nil then rOrbsDB["tmplOpacity_POWER"]         = 1 end
    if rOrbsDB["tmplOpacity_TARGET_HEALTH"] == nil then rOrbsDB["tmplOpacity_TARGET_HEALTH"] = 1 end
    if rOrbsDB["tmplScale_HEALTH"]        == nil then rOrbsDB["tmplScale_HEALTH"]        = 1 end
    if rOrbsDB["tmplScale_POWER"]         == nil then rOrbsDB["tmplScale_POWER"]         = 1 end
    if rOrbsDB["tmplScale_TARGET_HEALTH"] == nil then rOrbsDB["tmplScale_TARGET_HEALTH"] = 1 end
    if rOrbsDB["tmplScale_TOT_HEALTH"]     == nil then rOrbsDB["tmplScale_TOT_HEALTH"]     = 1 end
    if rOrbsDB["tmplScale_PET_HEALTH"]    == nil then rOrbsDB["tmplScale_PET_HEALTH"]    = 1 end
    if rOrbsDB["tmplColors_HEALTH"] == nil then rOrbsDB["tmplColors_HEALTH"] = false end
    if rOrbsDB["tmplColors_POWER"]  == nil then rOrbsDB["tmplColors_POWER"]  = true  end
    if rOrbsDB["orbTemplate_HEALTH"]        == nil then rOrbsDB["orbTemplate_HEALTH"]        = "red-portal"          end
    if rOrbsDB["orbTemplate_POWER"]         == nil then rOrbsDB["orbTemplate_POWER"]         = "blue-magic-tornado"  end
    if rOrbsDB["orbTemplate_TARGET_HEALTH"] == nil then rOrbsDB["orbTemplate_TARGET_HEALTH"] = "blue-swirly"         end
    if rOrbsDB["orbTemplate_PET_HEALTH"]    == nil then rOrbsDB["orbTemplate_PET_HEALTH"]    = "blue-swirly"         end

    -- Gloss texture selection (new/classic)
    if rOrbsDB["glossTex_HEALTH"]        == nil then rOrbsDB["glossTex_HEALTH"]        = "new"     end
    if rOrbsDB["glossTex_POWER"]         == nil then rOrbsDB["glossTex_POWER"]         = "new"     end
    if rOrbsDB["glossTex_TARGET_HEALTH"] == nil then rOrbsDB["glossTex_TARGET_HEALTH"] = "classic" end

    -- Smooth bar defaults
    if rOrbsDB["smoothBar_HEALTH"] == nil then rOrbsDB["smoothBar_HEALTH"] = true end
    if rOrbsDB["smoothSpeed"]      == nil then rOrbsDB["smoothSpeed"]      = 10   end

    if not rOrbsDB.frameScales    then rOrbsDB.frameScales    = {} end
    if not rOrbsDB.framePositions then rOrbsDB.framePositions = {} end

    -- Orb-Drag sperren (SHIFT/ALT + Linksklick) & Snap-to-Grid — Standard: aus (frei verschiebbar)
    if rOrbsDB.orbDragLocked == nil then rOrbsDB.orbDragLocked = false end
    if rOrbsDB.snapToGrid     == nil then rOrbsDB.snapToGrid     = false end

    -- ── Apply profile defaults → ns.cfg ───────────────────────────────
    ns.cfg = {}
    for k, v in pairs(rOrbs.defaults.profile) do
        ns.cfg[k] = v
    end

    -- Restore frame lock state from SavedVariables
    ns.cfg.framesUserplaced = true
    ns.cfg.framesLocked     = rOrbsDB.frameLocked or false

    -- Party frame SavedVariable defaults
    if rOrbsDB.partyEnabled     == nil then rOrbsDB.partyEnabled     = true  end
    if rOrbsDB.partyVertical    == nil then rOrbsDB.partyVertical    = false end
    if rOrbsDB.partyScale       == nil then rOrbsDB.partyScale       = 1.0  end
    if rOrbsDB.partyRangeAlpha  == nil then rOrbsDB.partyRangeAlpha  = 0.4  end
    if rOrbsDB.partyShowDebuffs == nil then rOrbsDB.partyShowDebuffs = true  end
    if rOrbsDB.partyShowBuffs   == nil then rOrbsDB.partyShowBuffs   = false end
    if rOrbsDB.partyShowPlayer  == nil then rOrbsDB.partyShowPlayer  = false end
    if rOrbsDB.partyHideInRaid  == nil then rOrbsDB.partyHideInRaid  = true  end
    if rOrbsDB.partyHideArena   == nil then rOrbsDB.partyHideArena   = true  end
    if rOrbsDB.partyHealPred    == nil then rOrbsDB.partyHealPred    = true  end
    if rOrbsDB.partyTotalAbsorb == nil then rOrbsDB.partyTotalAbsorb = true  end
    if rOrbsDB.partyAuraSize    == nil then rOrbsDB.partyAuraSize    = 14    end

    -- Party frame layout (pixel dimensions)
    if rOrbsDB.partyPortraitSize == nil then rOrbsDB.partyPortraitSize = 95  end
    if rOrbsDB.partyHpHeight     == nil then rOrbsDB.partyHpHeight     = 18  end
    if rOrbsDB.partyHpWidth      == nil then rOrbsDB.partyHpWidth      = 90  end
    if rOrbsDB.partyPwrHeight    == nil then rOrbsDB.partyPwrHeight    = 7   end
    if rOrbsDB.partyPwrWidth     == nil then rOrbsDB.partyPwrWidth     = 76  end
    if rOrbsDB.partyBarGap       == nil then rOrbsDB.partyBarGap       = 2   end
    if rOrbsDB.partyPortrait3D   == nil then rOrbsDB.partyPortrait3D   = false end  -- 2D default (3D blockiert Klicks)
    if rOrbsDB.partyHpTextFormat  == nil then rOrbsDB.partyHpTextFormat  = "value" end  -- "value" or "percent"
    if rOrbsDB.partyNameHeight    == nil then rOrbsDB.partyNameHeight    = 22  end  -- NAME_H: gap between top of frame and portrait
    -- Artwork: independent position/size relative to HP-bar center
    if rOrbsDB.partyArtW         == nil then rOrbsDB.partyArtW         = 160 end
    if rOrbsDB.partyArtH         == nil then rOrbsDB.partyArtH         = 55  end
    if rOrbsDB.partyArtX         == nil then rOrbsDB.partyArtX         = 0   end  -- X offset from HP-bar center
    if rOrbsDB.partyArtY         == nil then rOrbsDB.partyArtY         = 0   end  -- Y offset from HP-bar center
    if rOrbsDB.partyPosX         == nil then rOrbsDB.partyPosX         = nil end  -- nil = use cfg default
    if rOrbsDB.partyPosY         == nil then rOrbsDB.partyPosY         = nil end

    -- Apply saved position/size overrides to unit config
    local unitKeys = { "player", "targetHealth", "target", "targettarget", "pet" }
    for _, uKey in ipairs(unitKeys) do
        if ns.cfg.units and ns.cfg.units[uKey] then
            local savedX    = rOrbsDB["pos_"  .. uKey .. "_x"]
            local savedY    = rOrbsDB["pos_"  .. uKey .. "_y"]
            local savedSize = rOrbsDB["size_" .. uKey]
            if savedX    then ns.cfg.units[uKey].pos.x = savedX    end
            if savedY    then ns.cfg.units[uKey].pos.y = savedY    end
            if savedSize then ns.cfg.units[uKey].size  = savedSize end
        end
    end

    -- Apply saved font overrides (always set, falling back to "Enchanted Land" default)
    local healthFont = rOrbsDB["font_HEALTH"] or "Enchanted Land"
    local powerFont  = rOrbsDB["font_POWER"]  or "Enchanted Land"
    local targetFont = rOrbsDB["font_TARGET_HEALTH"] or "Enchanted Land"
    if LSM:Fetch("font", healthFont) then
        ns.cfg.fonts.textFont = healthFont
    else
        ns.cfg.fonts.textFont = "Enchanted Land"
    end
    if LSM:Fetch("font", powerFont) then
        ns.cfg.fonts.numberFont = powerFont
    else
        ns.cfg.fonts.numberFont = "Enchanted Land"
    end
    -- targetFont is handled directly in target-health.lua

    -- Apply saved party settings to ns.cfg
    if ns.cfg.units and ns.cfg.units.party then
        local pcfg = ns.cfg.units.party
        pcfg.show                          = rOrbsDB.partyEnabled     == true
        pcfg.vertical                      = rOrbsDB.partyVertical    == true
        pcfg.scale                         = rOrbsDB.partyScale       or 1.0
        pcfg.alpha.notinrange              = rOrbsDB.partyRangeAlpha  or 0.4
        pcfg.auras.show                    = rOrbsDB.partyShowDebuffs ~= false
        pcfg.auras.showBuffs               = rOrbsDB.partyShowBuffs   == true
        pcfg.auras.hideBuffs               = not (rOrbsDB.partyShowBuffs == true)
        pcfg.auras.size                    = rOrbsDB.partyAuraSize    or 14
        pcfg.attributes.showPlayer         = rOrbsDB.partyShowPlayer  == true
        pcfg.attributes.visibility         = (rOrbsDB.partyHideInRaid ~= false)
                                             and "[@raid6,exists] hide; show"
                                             or  "show"
        pcfg.attributes.hideInArena        = rOrbsDB.partyHideArena   ~= false
        pcfg.healprediction.show           = rOrbsDB.partyHealPred    ~= false
        pcfg.totalabsorb.show              = rOrbsDB.partyTotalAbsorb ~= false
        if rOrbsDB.partyPosX then pcfg.pos.x = rOrbsDB.partyPosX end
        if rOrbsDB.partyPosY then pcfg.pos.y = rOrbsDB.partyPosY end
    end

    -- Debug flag
    ns.debug = rOrbsDB.debug == true

    -- Spawn orbs
    C_Timer.After(0, function()
        ns:Init()
    end)

    -- NOTE: rOrbsDB is SavedVariablesPerCharacter, so each character
    -- automatically has their own independent settings. No profile
    -- auto-apply is needed on login.
end)
