-- =============================================================
--  rCastbar_GUI.lua
--  Author  : Imithat (WoWInterface.com) - selias2k (curseforge.com)
--  Version : 1.0.6
--  WoW     : Retail 12.0  (Interface 120001)
-- =============================================================

local L = rCastbar_L or {}  -- locale table (loaded before this file)

local C = {
    primary    = {0.451, 0.153, 0.851},
    primaryHi  = {0.620, 0.380, 0.960},
    primaryDim = {0.220, 0.070, 0.420},
    bg         = {0.05,  0.05,  0.05,  0.97},
    sidebar    = {0.040, 0.030, 0.055, 1.0},
    content    = {0.055, 0.040, 0.070, 0.97},
    border     = {0.30,  0.30,  0.30,  1.0},
    text       = {0.90,  0.90,  0.90},
    textDim    = {0.55,  0.55,  0.55},
    textBright = {1.00,  1.00,  1.00},
    toggleOff  = {0.10,  0.10,  0.12},
}

local FRAME_W    = 780
local FRAME_H    = 580
local TITLE_H    = 50
local SIDEBAR_W  = 155
local BOT_H      = 40
local PAD        = 14
local CONTENT_W  = FRAME_W - SIDEBAR_W - 10
local FONT       = "Fonts\\FRIZQT__.TTF"
local MEDIA      = "Interface\\AddOns\\rCastbar\\media\\"
local LOGO_RATIO = 1024 / 1536

local DEFAULT_COLORS = {
    player = {
        color        = { r = 0,   g = 0.5, b = 1,   a = 0.8 },
        bgColor      = { r = 0.1, g = 0.1, b = 0.1, a = 0.7 },
        failColor    = { r = 1,   g = 0,   b = 0,   a = 1.0 },
        successColor = { r = 0,   g = 0.8, b = 0,   a = 1.0 },
    },
    target = {
        color         = { r = 0,   g = 0.5, b = 1,   a = 0.7 },
        bgColor       = { r = 0.1, g = 0.1, b = 0.1, a = 1.0 },
        shieldColor   = { r = 0.5, g = 0.5, b = 0.5, a = 1.0 },
        shieldBgColor = { r = 0.1, g = 0.1, b = 0.1, a = 0.7 },
        failColor     = { r = 1,   g = 0,   b = 0,   a = 1.0 },
        successColor  = { r = 0,   g = 0.8, b = 0,   a = 1.0 },
    },
}

local function copyColor(src)
    return { r = src.r, g = src.g, b = src.b, a = src.a }
end

-- Low-level helpers

local function Tex(p, l) return p:CreateTexture(nil, l or "ARTWORK") end
local function FS(p, t)  return p:CreateFontString(nil, "OVERLAY", t or "GameFontNormal") end

local function FlatFrame(parent, r, g, b, a, br, bg_, bb, ba)
    local f = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    f:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    f:SetBackdropColor(r or 0,g or 0,b or 0,a or 1)
    f:SetBackdropBorderColor(br or r or 0,bg_ or g or 0,bb or b or 0,ba or a or 1)
    return f
end

local function MakeBorder(frame, r, g, b)
    local function Ln(a1,a2,horiz)
        local t=frame:CreateTexture(nil,"OVERLAY"); t:SetPoint(a1); t:SetPoint(a2)
        if horiz then t:SetHeight(1) else t:SetWidth(1) end
        t:SetColorTexture(r,g,b,1)
    end
    Ln("TOPLEFT","TOPRIGHT",true); Ln("BOTTOMLEFT","BOTTOMRIGHT",true)
    Ln("TOPLEFT","BOTTOMLEFT",false); Ln("TOPRIGHT","BOTTOMRIGHT",false)
end

local _fadeTicker
local function StopFade() if _fadeTicker then _fadeTicker:Cancel(); _fadeTicker=nil end end
local function GUIFadeIn(f)
    StopFade(); f:SetAlpha(0); f:Show(); local t=0
    _fadeTicker=C_Timer.NewTicker(0.016,function()
        t=t+0.016; f:SetAlpha(math.min(t/0.18,1)); if t>=0.18 then StopFade() end
    end)
end
local function GUIFadeOut(f)
    StopFade(); local t=0; local s=f:GetAlpha()
    _fadeTicker=C_Timer.NewTicker(0.016,function()
        t=t+0.016; local p=math.min(t/0.14,1); f:SetAlpha(s*(1-p))
        if p>=1 then StopFade(); f:Hide(); f:SetAlpha(1) end
    end)
end

local function SectionHeader(parent, text, anchor, x, y)
    local lbl=FS(parent,"GameFontNormal")
    lbl:SetPoint("TOPLEFT",anchor or parent,"TOPLEFT",x or 0,y or 0)
    lbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1); lbl:SetText(text)
    local line=Tex(parent,"BACKGROUND"); line:SetHeight(1)
    line:SetPoint("TOPLEFT",lbl,"BOTTOMLEFT",0,-2)
    line:SetPoint("TOPRIGHT",parent,"TOPRIGHT",-4,y or 0)
    line:SetGradient("HORIZONTAL",CreateColor(C.primary[1],C.primary[2],C.primary[3],0.7),CreateColor(0,0,0,0))
    return lbl
end

local BTN_PAD_X=18
local function StyledButton(parent,text,fixedW,h,onClick)
    local btn=CreateFrame("Button",nil,parent,"BackdropTemplate")
    btn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    btn:SetBackdropColor(C.primary[1]*0.18,C.primary[2]*0.08,C.primary[3]*0.18,1)
    btn:SetBackdropBorderColor(C.primary[1]*0.70,C.primary[2]*0.30,C.primary[3]*0.70,1)
    local lbl=FS(btn); lbl:SetPoint("CENTER")
    lbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1); lbl:SetText(text)
    local btnH=h or 26
    local function Sz()
        local w=fixedW; if not w then local sw=lbl:GetStringWidth(); w=(sw>0)and(sw+BTN_PAD_X)or 100 end
        btn:SetSize(w,btnH)
    end
    Sz(); btn:SetScript("OnShow",function(s) s:SetScript("OnShow",nil); Sz() end)
    btn:SetScript("OnEnter",function(s)
        s:SetBackdropColor(C.primary[1]*0.38,C.primary[2]*0.15,C.primary[3]*0.38,1)
        s:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
        lbl:SetTextColor(1,1,1,1)
    end)
    btn:SetScript("OnLeave",function(s)
        s:SetBackdropColor(C.primary[1]*0.18,C.primary[2]*0.08,C.primary[3]*0.18,1)
        s:SetBackdropBorderColor(C.primary[1]*0.70,C.primary[2]*0.30,C.primary[3]*0.70,1)
        lbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1)
    end)
    btn:SetScript("OnClick",onClick); return btn,lbl
end

local function DangerButton(parent,text,fixedW,h,onClick)
    local btn=CreateFrame("Button",nil,parent,"BackdropTemplate")
    btn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    btn:SetBackdropColor(0.55,0.08,0.08,1); btn:SetBackdropBorderColor(0.70,0.15,0.15,1)
    local lbl=FS(btn); lbl:SetPoint("CENTER"); lbl:SetTextColor(1,1,1,1); lbl:SetText(text)
    local btnH=h or 26
    local function Sz()
        local w=fixedW; if not w then local sw=lbl:GetStringWidth(); w=(sw>0)and(sw+BTN_PAD_X)or 100 end
        btn:SetSize(w,btnH)
    end
    Sz(); btn:SetScript("OnShow",function(s) s:SetScript("OnShow",nil); Sz() end)
    btn:SetScript("OnEnter",function(s) s:SetBackdropColor(0.80,0.14,0.14,1); s:SetBackdropBorderColor(0.95,0.25,0.25,1) end)
    btn:SetScript("OnLeave",function(s) s:SetBackdropColor(0.55,0.08,0.08,1); s:SetBackdropBorderColor(0.70,0.15,0.15,1) end)
    btn:SetScript("OnClick",onClick); return btn,lbl
end

local TOGGLE_W=38; local TOGGLE_H=19; local KNOB_SZ=13

local function ModernToggle(parent,label,anchor,xOfs,yOfs,getter,setter,desc)
    local rowH=desc and 52 or 30
    local con=CreateFrame("Frame",nil,parent); con:SetSize(CONTENT_W-20,rowH)
    con:SetPoint("TOPLEFT",anchor or parent,"TOPLEFT",xOfs or 0,yOfs or 0)
    local track=CreateFrame("Button",nil,con,"BackdropTemplate")
    track:SetSize(TOGGLE_W,TOGGLE_H)
    track:SetPoint("LEFT",con,"LEFT",0,desc and 8 or 0)
    track:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    local knob=CreateFrame("Frame",nil,track,"BackdropTemplate")
    knob:SetSize(KNOB_SZ,KNOB_SZ)
    knob:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    local lbl=FS(con)
    lbl:SetPoint("LEFT",track,"RIGHT",8,desc and 8 or 0)
    lbl:SetPoint("RIGHT",con,"RIGHT",-4,0)
    lbl:SetJustifyH("LEFT"); lbl:SetTextColor(unpack(C.text)); lbl:SetText(label)
    if desc then
        local d=FS(con,"GameFontHighlightSmall")
        d:SetPoint("TOPLEFT",lbl,"BOTTOMLEFT",0,-2); d:SetPoint("RIGHT",con,"RIGHT",-4,0)
        d:SetJustifyH("LEFT"); d:SetTextColor(unpack(C.textDim)); d:SetText(desc)
    end
    local function SetVisual(on)
        if on then
            track:SetBackdropColor(C.primary[1],C.primary[2],C.primary[3],1)
            track:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
            knob:ClearAllPoints(); knob:SetPoint("RIGHT",track,"RIGHT",-3,0)
            knob:SetBackdropColor(1,1,1,1); knob:SetBackdropBorderColor(0.85,0.85,1,1)
            lbl:SetTextColor(unpack(C.textBright))
        else
            track:SetBackdropColor(C.toggleOff[1],C.toggleOff[2],C.toggleOff[3],1)
            track:SetBackdropBorderColor(unpack(C.border))
            knob:ClearAllPoints(); knob:SetPoint("LEFT",track,"LEFT",3,0)
            knob:SetBackdropColor(0.50,0.50,0.55,1); knob:SetBackdropBorderColor(0.32,0.32,0.38,1)
            lbl:SetTextColor(unpack(C.text))
        end
    end
    local function Refresh() SetVisual(getter()==true or getter()==1) end; Refresh()
    track:SetScript("OnEnter",function()
        if getter()==true or getter()==1 then track:SetBackdropBorderColor(0.85,0.7,1,1)
        else track:SetBackdropBorderColor(C.primary[1]*0.75,C.primary[2]*0.5,C.primary[3]*0.75,1) end
    end)
    track:SetScript("OnLeave",function()
        if getter()==true or getter()==1 then
            track:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
        else track:SetBackdropBorderColor(unpack(C.border)) end
    end)
    track:SetScript("OnClick",function()
        local v=not(getter()==true or getter()==1); SetVisual(v); setter(v)
    end)
    con.Refresh=Refresh; return con
end

local function ModernSlider(parent,label,desc,minV,maxV,step,anchor,yOfs,getter,setter,fmt)
    local con=CreateFrame("Frame",nil,parent); con:SetSize(CONTENT_W-20,60)
    con:SetPoint("TOPLEFT",anchor or parent,"TOPLEFT",0,yOfs or 0)
    local lbl=FS(con); lbl:SetPoint("TOPLEFT",0,0); lbl:SetTextColor(unpack(C.text)); lbl:SetText(label)
    local valLbl=FS(con,"GameFontNormalSmall"); valLbl:SetPoint("TOPRIGHT",0,-1)
    valLbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1)
    if desc then
        local d=FS(con,"GameFontHighlightSmall")
        d:SetPoint("TOPLEFT",lbl,"BOTTOMLEFT",0,-1); d:SetTextColor(unpack(C.textDim)); d:SetText(desc)
    end
    local track=Tex(con,"BACKGROUND"); track:SetHeight(3)
    track:SetPoint("BOTTOMLEFT",con,"BOTTOMLEFT",6,14); track:SetPoint("BOTTOMRIGHT",con,"BOTTOMRIGHT",-6,14)
    track:SetColorTexture(0.15,0.10,0.22,1)
    local fill=Tex(con,"ARTWORK"); fill:SetHeight(3); fill:SetPoint("LEFT",track,"LEFT",0,0)
    fill:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.85)
    local sl=CreateFrame("Slider",nil,con); sl:SetHeight(18)
    sl:SetPoint("LEFT",track,"LEFT",0,0); sl:SetPoint("RIGHT",track,"RIGHT",0,0)
    sl:SetMinMaxValues(minV,maxV); sl:SetValueStep(step); sl:SetObeyStepOnDrag(true)
    sl:SetValue(getter()); sl:SetOrientation("HORIZONTAL")
    local thumb=Tex(sl,"OVERLAY"); thumb:SetSize(10,14)
    thumb:SetTexture("Interface\\Buttons\\WHITE8X8")
    thumb:SetVertexColor(C.primary[1],C.primary[2],C.primary[3],1)
    sl:SetThumbTexture(thumb)
    local function UpdateFill()
        local p=math.max(0,(sl:GetValue()-minV)/math.max(maxV-minV,0.001))
        local w=track:GetWidth(); if w and w>0 then fill:SetWidth(w*p) end
    end
    local function UpdateVal()
        local v=sl:GetValue(); valLbl:SetText(fmt and fmt(v) or string.format("%.2f",v))
        UpdateFill(); setter(v)
    end
    sl:SetScript("OnValueChanged",UpdateVal); sl:SetScript("OnSizeChanged",UpdateFill)
    sl:SetScript("OnEnter",function() thumb:SetVertexColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1) end)
    sl:SetScript("OnLeave",function() thumb:SetVertexColor(C.primary[1],C.primary[2],C.primary[3],1) end)
    UpdateVal(); con.slider=sl; con.Refresh=function() sl:SetValue(getter()); UpdateVal() end
    return con
end

local function ColorPickerRow(parent,label,getter,setter,anchor,yOfs)
    local con=CreateFrame("Frame",nil,parent); con:SetSize(CONTENT_W-20,28)
    con:SetPoint("TOPLEFT",anchor or parent,"TOPLEFT",0,yOfs or 0)
    local lbl=FS(con); lbl:SetPoint("LEFT",0,0); lbl:SetTextColor(unpack(C.text)); lbl:SetText(label)
    local box=CreateFrame("Button",nil,con,"BackdropTemplate")
    box:SetSize(48,20); box:SetPoint("RIGHT",0,0)
    box:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8",tile=false,edgeSize=1,
        insets={left=0,right=0,top=0,bottom=0}})
    box:SetBackdropBorderColor(unpack(C.border))
    local bbg=box:CreateTexture(nil,"OVERLAY"); bbg:SetAllPoints()
    local alphaStrip=box:CreateTexture(nil,"OVERLAY"); alphaStrip:SetSize(8,20); alphaStrip:SetPoint("RIGHT",0,0)
    local function UpdatePreview()
        local cc=getter()
        if cc then bbg:SetColorTexture(cc.r,cc.g,cc.b,1); alphaStrip:SetColorTexture(cc.r*0.3,cc.g*0.3,cc.b*0.3,cc.a or 1)
        else bbg:SetColorTexture(0.4,0.4,0.4,1) end
    end
    UpdatePreview()
    box:SetScript("OnEnter",function() box:SetBackdropBorderColor(C.primary[1],C.primary[2],C.primary[3],1) end)
    box:SetScript("OnLeave",function() box:SetBackdropBorderColor(unpack(C.border)) end)
    box:SetScript("OnClick",function()
        local cc=getter(); if not cc then return end
        local function Apply(r,g,b,a) setter({r=r,g=g,b=b,a=a}); UpdatePreview() end
        ColorPickerFrame:SetupColorPickerAndShow({
            r=cc.r,g=cc.g,b=cc.b,hasOpacity=true,opacity=1-(cc.a or 1),
            swatchFunc=function()
                local r,g,b=ColorPickerFrame:GetColorRGB(); local a=1-ColorPickerFrame:GetColorAlpha(); Apply(r,g,b,a)
            end,
            opacityFunc=function()
                local r,g,b=ColorPickerFrame:GetColorRGB(); local a=1-ColorPickerFrame:GetColorAlpha(); Apply(r,g,b,a)
            end,
            cancelFunc=function(prev) Apply(prev.r,prev.g,prev.b,1-(prev.opacity or 0)) end,
        })
    end)
    con.UpdatePreview=UpdatePreview; return con
end

local function MakeScrollContent(parent)
    local TRACK_W=7
    local sf=CreateFrame("ScrollFrame",nil,parent,"UIPanelScrollFrameTemplate")
    sf:SetPoint("TOPLEFT",parent,"TOPLEFT",2,-40); sf:SetPoint("BOTTOMRIGHT",parent,"BOTTOMRIGHT",-(TRACK_W+5),4)
    local sc=CreateFrame("Frame",nil,sf); sc:SetWidth(CONTENT_W-24); sc:SetHeight(2000); sf:SetScrollChild(sc)
    local sb=sf.ScrollBar
    if sb then
        if sb.ScrollUpButton   then sb.ScrollUpButton:SetAlpha(0);   sb.ScrollUpButton:Disable()   end
        if sb.ScrollDownButton then sb.ScrollDownButton:SetAlpha(0); sb.ScrollDownButton:Disable() end
        sb:SetWidth(TRACK_W)
        local tt=sb:CreateTexture(nil,"BACKGROUND"); tt:SetAllPoints(sb); tt:SetColorTexture(0.05,0.03,0.09,0.9)
        local thumb=sb.ThumbTexture or(sb.GetThumbTexture and sb:GetThumbTexture())
        if thumb then
            thumb:SetWidth(TRACK_W); thumb:SetTexture(nil)
            thumb:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.75)
        end
    end
    return sc,sf
end

local function LiveApplyColor(unit,colorKey)
    local bar=_G["rCastbar_"..(unit:sub(1,1):upper()..unit:sub(2))]
    if not bar then return end
    local cc=rCastbarDB[unit][colorKey]; if not cc then return end
    if colorKey=="color" then bar:SetStatusBarColor(cc.r,cc.g,cc.b,cc.a or 1)
    elseif colorKey=="bgColor" then if bar.bg then bar.bg:SetVertexColor(cc.r,cc.g,cc.b,cc.a or 1) end end
end

-- Helper: update the Text RIGHT anchor when showTime visibility changes
local function updateBarTextAnchor(bar, showTime)
    if not bar or not bar.Text then return end
    local rightOfs = showTime and -55 or -5
    bar.Text:ClearAllPoints()
    bar.Text:SetPoint("LEFT",  bar, "LEFT",  5,        0)
    bar.Text:SetPoint("RIGHT", bar, "RIGHT", rightOfs, 0)
end

local activeDropdown=nil
local function CloseDropdown() if activeDropdown then activeDropdown:Hide(); activeDropdown=nil end end

-- ======================================================
-- EDIT MODE (nCastbar-Style: Grid, X/Y, Pfeiltasten)
-- ======================================================
local editModeGrid, editModeTopBar, editModeKeyButton
local rCastbar_SelectedContainer = nil

local function GetUnitFromContainer(cont)
    if not cont or not cont.GetName then return nil end
    local n = cont:GetName() or ""
    if n:find("Player") then return "player" end
    if n:find("Target") then return "target" end
    return nil
end

local function UpdateAnchorPositionDisplay(cont)
    if not cont or not cont._editFrame then return end
    local ef = cont._editFrame
    if not ef.xText or not ef.yText then return end
    local point, relFrame, relPoint, x, y = cont:GetPoint()
    local xVal = string.format("%.0f", x or 0)
    local yVal = string.format("%.0f", y or 0)
    ef.xText:SetFormattedText("X = %s", xVal)
    ef.yText:SetFormattedText("Y = %s", yVal)
end

function rCastbar_NudgeSelected(dx, dy)
    local cont = rCastbar_SelectedContainer
    if not cont or not cont:IsShown() then return end
    local unit = GetUnitFromContainer(cont)
    if not unit or not rCastbarDB or not rCastbarDB[unit] then return end
    local cfg = rCastbarDB[unit]
    local point, relFrame, relPoint, x, y = cont:GetPoint()
    x, y = (x or 0) + dx, (y or 0) + dy
    cfg.x, cfg.y = math.floor(x + 0.5), math.floor(y + 0.5)
    cont:ClearAllPoints()
    cont:SetPoint(point or cfg.point, relFrame or _G[cfg.relFrame] or UIParent, relPoint or cfg.relPoint, cfg.x, cfg.y)
    UpdateAnchorPositionDisplay(cont)
end

local function CreateEditModeGrid()
    if editModeGrid then return editModeGrid end
    local grid = CreateFrame("Frame", "rCastbarEditGrid", UIParent)
    grid:SetAllPoints(UIParent); grid:SetFrameStrata("BACKGROUND"); grid:SetFrameLevel(1); grid:Hide()
    local dim = Tex(grid, "BACKGROUND"); dim:SetAllPoints(); dim:SetColorTexture(0,0,0,0.35)
    local cols, rows = 64, 36
    local w = GetScreenWidth() / cols
    local h = GetScreenHeight() / rows
    for i = 0, cols do
        local t = Tex(grid, "ARTWORK")
        if i == cols/2 then
            -- Mittellinie: gleiche Farbe wie rCastbar GUI
            t:SetColorTexture(C.primary[1], C.primary[2], C.primary[3], 0.75)
        else
            -- Restliche Linien: dezente Akzentfarbe
            t:SetColorTexture(C.primary[1], C.primary[2], C.primary[3], 0.12)
        end
        t:SetPoint("TOPLEFT", grid, "TOPLEFT", i * w - 1, 0)
        t:SetPoint("BOTTOMRIGHT", grid, "BOTTOMLEFT", i * w + 1, 0)
    end
    for i = 0, rows do
        local t = Tex(grid, "ARTWORK")
        if i == rows/2 then
            -- Mittellinie: gleiche Farbe wie rCastbar GUI
            t:SetColorTexture(C.primary[1], C.primary[2], C.primary[3], 0.75)
        else
            -- Restliche Linien: dezente Akzentfarbe
            t:SetColorTexture(C.primary[1], C.primary[2], C.primary[3], 0.12)
        end
        t:SetPoint("TOPLEFT", grid, "TOPLEFT", 0, -i * h + 1)
        t:SetPoint("BOTTOMRIGHT", grid, "TOPRIGHT", 0, -i * h - 1)
    end
    local wm = FS(grid, "GameFontNormalSmall")
    wm:SetFont(FONT, 11, "OUTLINE"); wm:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 6)
    wm:SetText("|cff7327d9r|rCastbar  –  EDIT MODE"); wm:SetTextColor(1,1,1,0.55)
    local topLabel = FS(grid, "GameFontNormalLarge")
    topLabel:SetPoint("TOP", UIParent, "TOP", 0, -40)
    topLabel:SetText("|cff7327d9r|rCastbar  –  EDIT MODE"); topLabel:SetTextColor(1, 1, 1, 0.9)
    editModeGrid = grid; return grid
end

local function ShowEditGrid() CreateEditModeGrid(); editModeGrid:Show() end
local function HideEditGrid() if editModeGrid then editModeGrid:Hide() end end

local function CreateEditModeTopBar()
    if editModeTopBar then return editModeTopBar end
    local bar = CreateFrame("Frame", "rCastbarEditModeTopBar", UIParent, "BackdropTemplate")
    bar:SetSize(260, 36); bar:SetPoint("TOP", UIParent, "TOP", 0, -80)
    bar:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false, edgeSize=1, insets={left=0,right=0,top=0,bottom=0}})
    bar:SetBackdropColor(0, 0, 0, 0.7)
    bar:SetBackdropBorderColor(C.primary[1], C.primary[2], C.primary[3], 1)
    local btn = CreateFrame("Button", nil, bar, "UIPanelButtonTemplate")
    btn:SetPoint("CENTER"); btn:SetSize(220, 22); btn:SetText((L["EditMode_Disable"] or ""):gsub("%s*|cff00ff00%[^%]]+%]|r", ""))
    btn:SetScript("OnClick", function()
        if InCombatLockdown() then return end
        local editBtn = _G.rCastbarEditBtn
        if editBtn and editBtn._editOn then
            editBtn._editOn = false
            if editBtn.editBtnLbl then editBtn.editBtnLbl:SetText(L["EditMode_Enable"]) end
            editBtn:SetBackdropColor(C.primary[1]*0.18,C.primary[2]*0.08,C.primary[3]*0.18,1)
            for _, cont in ipairs({_G.rCastbar_Player_Container, _G.rCastbar_Target_Container}) do
                if cont and cont._editFrame then cont._editFrame:Hide() end
            end
            HideEditGrid(); if editModeTopBar then editModeTopBar:Hide() end
            if editModeKeyButton then ClearOverrideBindings(editModeKeyButton) end
            if rCastbar_ShowPreview then rCastbar_ShowPreview(false) end
            -- Öffnen des Config-Frames: über Global-Name, da configFrame in diesem Closure evtl. nicht korrekt captured wird.
            local cf = _G["rCastbarConfigFrame"] or configFrame
            if cf then
                if cf.FadeIn then
                    cf:FadeIn()
                else
                    cf:Show()
                end
            end
        end
    end)
    editModeTopBar = bar; return bar
end

-- ======================================================
-- MAIN FRAME
-- ======================================================
local configFrame; local activeTabIdx=0

local function BuildConfigFrame()
    if configFrame then return configFrame end
    local frame=CreateFrame("Frame","rCastbarConfigFrame",UIParent,"BackdropTemplate")
    frame:SetSize(FRAME_W,FRAME_H); frame:SetPoint("CENTER")
    frame:SetMovable(true); frame:EnableMouse(true)
    frame:RegisterForDrag("LeftButton"); frame:SetScript("OnDragStart",frame.StartMoving); frame:SetScript("OnDragStop",frame.StopMovingOrSizing)
    frame:SetFrameStrata("DIALOG"); frame:SetClampedToScreen(true); frame:Hide()
    frame:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false,edgeSize=2,insets={left=0,right=0,top=0,bottom=0}})
    frame:SetBackdropColor(unpack(C.bg))
    frame:SetBackdropBorderColor(C.primary[1]*0.55,C.primary[2]*0.22,C.primary[3]*0.55,1)
    tinsert(UISpecialFrames,"rCastbarConfigFrame")
    frame:SetScript("OnHide",CloseDropdown)
    function frame:FadeIn()  GUIFadeIn(self)  end
    function frame:FadeOut() GUIFadeOut(self) end

    -- Title
    local titleBg=FlatFrame(frame,C.primary[1]*0.19,C.primary[2]*0.08,C.primary[3]*0.19,1,
        C.primary[1]*0.55,C.primary[2]*0.22,C.primary[3]*0.55,1)
    titleBg:SetPoint("TOPLEFT",frame,"TOPLEFT",2,-2); titleBg:SetPoint("TOPRIGHT",frame,"TOPRIGHT",-2,-2); titleBg:SetHeight(TITLE_H)
    local ta=Tex(titleBg); ta:SetWidth(3); ta:SetPoint("TOPLEFT",titleBg,"TOPLEFT",0,0); ta:SetPoint("BOTTOMLEFT",titleBg,"BOTTOMLEFT",0,0)
    ta:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],1)
    local titleIcon=Tex(titleBg,"OVERLAY"); titleIcon:SetSize(28,28)
    titleIcon:SetPoint("LEFT",titleBg,"LEFT",14,0)
    titleIcon:SetTexture(237017)
    titleIcon:SetTexCoord(0.08,0.92,0.08,0.92)
    local titleLbl=FS(titleBg,"GameFontNormalHuge"); titleLbl:SetPoint("LEFT",titleIcon,"RIGHT",8,0)
    titleLbl:SetTextColor(1,1,1,1); titleLbl:SetText("|cff7327d9r|rCastbar")
    local tgl=Tex(titleBg); tgl:SetHeight(2)
    tgl:SetPoint("BOTTOMLEFT",titleBg,"BOTTOMLEFT",0,0); tgl:SetPoint("BOTTOMRIGHT",titleBg,"BOTTOMRIGHT",0,0)
    tgl:SetGradient("HORIZONTAL",CreateColor(C.primary[1],C.primary[2],C.primary[3],1),CreateColor(0,0,C.primary[3]*0.1,0))
    local closeBtn=CreateFrame("Button",nil,titleBg); closeBtn:SetSize(28,28); closeBtn:SetPoint("RIGHT",titleBg,"RIGHT",-12,0)
    local closeBg=Tex(closeBtn,"BACKGROUND"); closeBg:SetAllPoints(); closeBg:SetColorTexture(C.primary[1]*0.5,C.primary[2]*0.15,C.primary[3]*0.5,0.9)
    MakeBorder(closeBtn,C.primary[1]*0.8,C.primary[2]*0.3,C.primary[3]*0.8)
    local closeLbl=closeBtn:CreateFontString(nil,"OVERLAY"); closeLbl:SetFont(FONT,16,"OUTLINE")
    closeLbl:SetPoint("CENTER",0,1); closeLbl:SetText("x"); closeLbl:SetTextColor(1,1,1,1)
    closeBtn:SetScript("OnEnter",function() closeBg:SetColorTexture(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1) end)
    closeBtn:SetScript("OnLeave",function() closeBg:SetColorTexture(C.primary[1]*0.5,C.primary[2]*0.15,C.primary[3]*0.5,0.9) end)
    closeBtn:SetScript("OnClick",function() frame:FadeOut() end)

    -- Sidebar
    local sidebar=FlatFrame(frame,C.sidebar[1],C.sidebar[2],C.sidebar[3],C.sidebar[4])
    sidebar:SetPoint("TOPLEFT",titleBg,"BOTTOMLEFT",2,0); sidebar:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",2,BOT_H+2); sidebar:SetWidth(SIDEBAR_W)
    local sbl=Tex(sidebar); sbl:SetWidth(1); sbl:SetPoint("TOPRIGHT",sidebar,"TOPRIGHT",0,0); sbl:SetPoint("BOTTOMRIGHT",sidebar,"BOTTOMRIGHT",0,0)
    sbl:SetColorTexture(C.primary[1]*0.35,C.primary[2]*0.15,C.primary[3]*0.55,1)

    -- Content area
    local contentBg=FlatFrame(frame,C.content[1],C.content[2],C.content[3],C.content[4],C.border[1]*0.5,C.border[2]*0.5,C.border[3]*0.5,C.border[4])
    contentBg:SetPoint("TOPLEFT",sidebar,"TOPRIGHT",1,0); contentBg:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-2,BOT_H+2)
    local contentHeader=FS(contentBg,"GameFontNormalLarge"); contentHeader:SetPoint("TOPLEFT",contentBg,"TOPLEFT",PAD,-12)
    contentHeader:SetTextColor(C.primary[1],C.primary[2],C.primary[3],0.70)
    local chLine=Tex(contentBg,"BACKGROUND"); chLine:SetHeight(1)
    chLine:SetPoint("TOPLEFT",contentBg,"TOPLEFT",PAD,-30); chLine:SetPoint("TOPRIGHT",contentBg,"TOPRIGHT",-PAD,-30)
    chLine:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.18)

    -- Bottom bar
    local bot=FlatFrame(frame,0.035,0.025,0.050,1,0.07,0.06,0.12,1)
    bot:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",2,2); bot:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-2,2); bot:SetHeight(BOT_H)
    local botLine=Tex(bot); botLine:SetHeight(1); botLine:SetPoint("TOPLEFT",bot,"TOPLEFT",0,0); botLine:SetPoint("TOPRIGHT",bot,"TOPRIGHT",0,0)
    botLine:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.28)

    -- GUI Scale
    do
        local STEP=0.05; local SMIN=0.5; local SMAX=2.0
        local function GS() return(rCastbarDB and rCastbarDB.guiScale)or 1.0 end
        local function AS(v) v=math.max(SMIN,math.min(SMAX,v)); v=math.floor(v/STEP+0.5)*STEP; v=math.floor(v*100+0.5)/100
            if rCastbarDB then rCastbarDB.guiScale=v end; frame:SetScale(v) end
        local scaleLbl=FS(bot,"GameFontHighlightSmall"); scaleLbl:SetPoint("LEFT",bot,"LEFT",10,0)
        scaleLbl:SetTextColor(unpack(C.textDim)); scaleLbl:SetText(L["GUI Scale"])
        local valBox=CreateFrame("EditBox",nil,bot,"BackdropTemplate"); valBox:SetSize(46,22)
        valBox:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
        valBox:SetBackdropColor(0.04,0.03,0.08,0.9); valBox:SetBackdropBorderColor(C.primary[1]*0.4,C.primary[2]*0.2,C.primary[3]*0.6,1)
        valBox:SetFont(FONT,11,""); valBox:SetTextColor(0.8,0.7,1,1); valBox:SetJustifyH("CENTER"); valBox:SetMaxLetters(5); valBox:SetAutoFocus(false)
        local function SyncBox() valBox:SetText(string.format("%.2f",GS())); frame:SetScale(GS()) end
        local function MkArr(sym,dir)
            local b=CreateFrame("Button",nil,bot,"BackdropTemplate"); b:SetSize(20,22)
            b:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",edgeSize=1,insets={left=1,right=1,top=1,bottom=1}})
            b:SetBackdropColor(0.06,0.04,0.10,1); b:SetBackdropBorderColor(C.primary[1]*0.45,C.primary[2]*0.20,C.primary[3]*0.65,1)
            local t2=b:CreateFontString(nil,"OVERLAY","GameFontNormal"); t2:SetAllPoints(); t2:SetText(sym)
            t2:SetTextColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1)
            b:SetScript("OnEnter",function(s) s:SetBackdropColor(0.12,0.06,0.20,1); s:SetBackdropBorderColor(C.primary[1],C.primary[2],C.primary[3],1) end)
            b:SetScript("OnLeave",function(s) s:SetBackdropColor(0.06,0.04,0.10,1); s:SetBackdropBorderColor(C.primary[1]*0.45,C.primary[2]*0.20,C.primary[3]*0.65,1); s.held=false end)
            local ticker
            b:SetScript("OnClick",function() AS(GS()+dir*STEP); SyncBox() end)
            b:SetScript("OnMouseDown",function() b.held=true; C_Timer.After(0.4,function() if not b.held then return end
                ticker=C_Timer.NewTicker(0.08,function() if b.held then AS(GS()+dir*STEP); SyncBox() else ticker:Cancel(); ticker=nil end end) end) end)
            b:SetScript("OnMouseUp",function() b.held=false; if ticker then ticker:Cancel(); ticker=nil end end)
            return b
        end
        local btnMinus=MkArr("<",-1); btnMinus:SetPoint("LEFT",scaleLbl,"RIGHT",8,0)
        valBox:SetPoint("LEFT",btnMinus,"RIGHT",2,0); local btnPlus=MkArr(">",1); btnPlus:SetPoint("LEFT",valBox,"RIGHT",2,0)
        valBox:SetScript("OnMouseWheel",function(_,d) AS(GS()+d*STEP); SyncBox() end)
        local function CT()
            local v=tonumber(valBox:GetText()); if v then AS(v) end; SyncBox(); valBox:ClearFocus()
        end
        valBox:SetScript("OnEnterPressed",CT); valBox:SetScript("OnTabPressed",CT)
        valBox:SetScript("OnEditFocusLost",function() C_Timer.After(0,CT) end)
        valBox:SetScript("OnEscapePressed",function() SyncBox(); valBox:ClearFocus() end)
        frame:HookScript("OnShow",SyncBox); frame:HookScript("OnHide",function() valBox:ClearFocus() end); SyncBox()
    end

    local closeBot=StyledButton(bot,L["Close"],nil,26,function() frame:FadeOut() end)
    closeBot:SetPoint("RIGHT",bot,"RIGHT",-10,0)

    -- Tabs
    local TAB_DEFS={
        {id="general", label=L["General"], icon="Interface\\Icons\\INV_Misc_Note_06"},
        {id="player",  label=L["Player"],  icon="Interface\\Icons\\Achievement_Character_Human_Male"},
        {id="target",  label=L["Target"],  icon="Interface\\Icons\\Ability_Warrior_OffensiveStance"},
    }
    local TAB_H=40; local TAB_GAP=1; local tabs={}; local tabPanels={}

    local function SelectTab(idx)
        activeTabIdx=idx
        for i,tab in ipairs(tabs) do
            local sel=(i==idx)
            if sel then tab.accent:Show(); tab.bgTex:SetColorTexture(C.primary[1]*0.15,C.primary[2]*0.08,C.primary[3]*0.20,1)
                tab.icon:SetVertexColor(1,0.90,1,1); tab.lbl:SetTextColor(1,0.96,1,1)
            else tab.accent:Hide(); tab.bgTex:SetColorTexture(C.sidebar[1],C.sidebar[2],C.sidebar[3],1)
                tab.icon:SetVertexColor(0.45,0.38,0.55,1); tab.lbl:SetTextColor(0.65,0.58,0.75,1) end
            tabPanels[i]:SetShown(sel)
        end
        contentHeader:SetText(TAB_DEFS[idx].label)
    end

    for i,td in ipairs(TAB_DEFS) do
        local tab=CreateFrame("Button",nil,sidebar); tab:SetHeight(TAB_H)
        tab:SetPoint("TOPLEFT",sidebar,"TOPLEFT",0,-(i-1)*(TAB_H+TAB_GAP)); tab:SetPoint("TOPRIGHT",sidebar,"TOPRIGHT",-1,-(i-1)*(TAB_H+TAB_GAP))
        tab.bgTex=Tex(tab,"BACKGROUND"); tab.bgTex:SetAllPoints(); tab.bgTex:SetColorTexture(C.sidebar[1],C.sidebar[2],C.sidebar[3],1)
        tab.accent=Tex(tab,"OVERLAY"); tab.accent:SetWidth(3); tab.accent:SetPoint("TOPLEFT",tab,"TOPLEFT",0,0); tab.accent:SetPoint("BOTTOMLEFT",tab,"BOTTOMLEFT",0,0)
        tab.accent:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],1); tab.accent:Hide()
        local sep=Tex(tab,"BORDER"); sep:SetHeight(1); sep:SetPoint("BOTTOMLEFT",tab,"BOTTOMLEFT",0,0); sep:SetPoint("BOTTOMRIGHT",tab,"BOTTOMRIGHT",0,0); sep:SetColorTexture(0.06,0.04,0.10,1)
        tab.icon=Tex(tab,"OVERLAY"); tab.icon:SetSize(18,18); tab.icon:SetPoint("LEFT",tab,"LEFT",14,0); tab.icon:SetTexture(td.icon); tab.icon:SetVertexColor(0.45,0.38,0.55,1)
        tab.lbl=FS(tab,"GameFontNormalSmall"); tab.lbl:SetPoint("LEFT",tab.icon,"RIGHT",8,0); tab.lbl:SetPoint("RIGHT",tab,"RIGHT",-6,0)
        tab.lbl:SetJustifyH("LEFT"); tab.lbl:SetTextColor(0.65,0.58,0.75,1); tab.lbl:SetText(td.label)
        local ii=i
        tab:SetScript("OnEnter",function(s) if activeTabIdx~=ii then s.bgTex:SetColorTexture(0.07,0.04,0.12,1)
            s.lbl:SetTextColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1); s.icon:SetVertexColor(C.primary[1],C.primary[2],C.primary[3],1) end end)
        tab:SetScript("OnLeave",function(s) if activeTabIdx~=ii then s.bgTex:SetColorTexture(C.sidebar[1],C.sidebar[2],C.sidebar[3],1)
            s.lbl:SetTextColor(0.65,0.58,0.75,1); s.icon:SetVertexColor(0.45,0.38,0.55,1) end end)
        tab:SetScript("OnClick",function() SelectTab(ii) end); tabs[i]=tab
        local panel=CreateFrame("Frame",nil,contentBg)
        panel:SetPoint("TOPLEFT",contentBg,"TOPLEFT",0,0); panel:SetPoint("BOTTOMRIGHT",contentBg,"BOTTOMRIGHT",0,0); panel:Hide()
        tabPanels[i]=panel
    end

    configFrame=frame; frame._tabs=tabs; frame._tabPanels=tabPanels; frame._SelectTab=SelectTab
    return frame,tabs,tabPanels,SelectTab
end

-- ======================================================
-- UNIT PANEL
-- ======================================================
local function BuildUnitPanel(parent, unit)
    local sc,sf=MakeScrollContent(parent)
    local P2=12; local y=-P2

    local function getBar()  return _G["rCastbar_"..(unit:sub(1,1):upper()..unit:sub(2))] end
    local function getCont() return _G["rCastbar_"..(unit:sub(1,1):upper()..unit:sub(2)).."_Container"] end

    -- Layout
    SectionHeader(sc, L["Layout"], sc, P2, y); y=y-26

    ModernToggle(sc, L["Enabled"], sc, P2, y,
        function() return rCastbarDB[unit].enabled end,
        function(v) rCastbarDB[unit].enabled=v end,
        L["Enabled_Desc"])
    y=y-56

    -- Width
    -- NOTE: when dynamicWidth is ON, the slider value is the minimum width only;
    --       we do NOT directly resize the bar (it resizes on next cast start).
    ModernSlider(sc, L["Width"], L["Width_Desc"], 80, 500, 1, sc, y,
        function() return rCastbarDB[unit].width end,
        function(v)
            rCastbarDB[unit].width = v
            if not rCastbarDB[unit].dynamicWidth then
                local bar=getBar(); local cont=getCont()
                if bar then bar:SetWidth(v); if bar.UpdateArtwork then bar:UpdateArtwork(v) end end
                if cont then cont:SetWidth(v+20) end
            end
        end,
        function(v) return string.format(L["Width_Fmt"], v) end)
    y=y-72

    -- Dynamic Width
    ModernToggle(sc, L["DynamicWidth"], sc, P2, y,
        function() return rCastbarDB[unit].dynamicWidth end,
        function(v) rCastbarDB[unit].dynamicWidth=v end,
        L["DynamicWidth_Desc"])
    y=y-56

    -- Font Size
    ModernSlider(sc, L["FontSize"], L["FontSize_Desc"], 7, 20, 1, sc, y,
        function() return rCastbarDB[unit].fontSize end,
        function(v)
            rCastbarDB[unit].fontSize=v
            local bar=getBar()
            if bar then
                if bar.Text then bar.Text:SetFont(MEDIA.."Expressway.ttf",v,"THINOUTLINE") end
                if bar.Time then bar.Time:SetFont(MEDIA.."Expressway.ttf",v,"THINOUTLINE") end
            end
        end,
        function(v) return string.format(L["FontSize_Fmt"], v) end)
    y=y-72

    -- Fail display duration
    ModernSlider(sc, L["TimeToHold"], L["TimeToHold_Desc"], 0, 5, 0.1, sc, y,
        function() return rCastbarDB[unit].timeToHold end,
        function(v) rCastbarDB[unit].timeToHold=v end,
        function(v) return string.format(L["TimeToHold_Fmt"], v) end)
    y=y-72

    -- Show Icon
    ModernToggle(sc, L["ShowIcon"], sc, P2, y,
        function() return rCastbarDB[unit].showIcon end,
        function(v)
            rCastbarDB[unit].showIcon=v
            local bar=getBar()
            if bar and bar.Icon then
                if v then bar.Icon:Show() else bar.Icon:Hide() end
            end
        end,
        L["ShowIcon_Desc"])
    y=y-56

    -- Show Spell Name
    ModernToggle(sc, L["ShowSpellName"], sc, P2, y,
        function() return rCastbarDB[unit].showSpellName end,
        function(v)
            rCastbarDB[unit].showSpellName = v
            local bar=getBar()
            if bar and bar.Text then
                if v then bar.Text:Show() else bar.Text:Hide() end
            end
        end,
        L["ShowSpellName_Desc"])
    y=y-56

    -- Show Cast Time
    ModernToggle(sc, L["ShowTime"], sc, P2, y,
        function() return rCastbarDB[unit].showTime end,
        function(v)
            rCastbarDB[unit].showTime = v
            local bar=getBar()
            if bar then
                if bar.Time then
                    if v then bar.Time:Show() else bar.Time:Hide() end
                end
                -- Adjust Text RIGHT anchor to use freed space
                updateBarTextAnchor(bar, v)
            end
        end,
        L["ShowTime_Desc"])
    y=y-56

    -- Latency (player only)
    if unit == "player" then
        ModernToggle(sc, L["ShowLatency"], sc, P2, y,
            function() return rCastbarDB.player.latency end,
            function(v) rCastbarDB.player.latency=v end,
            L["ShowLatency_Desc"])
        y=y-56
    end

    -- Fade
    y=y-8; SectionHeader(sc, L["Fade"], sc, P2, y); y=y-26

    ModernToggle(sc, L["FadeEnable"], sc, P2, y,
        function() return rCastbarDB[unit].fadeEnable end,
        function(v) rCastbarDB[unit].fadeEnable=v end,
        L["FadeEnable_Desc"])
    y=y-56

    ModernSlider(sc, L["FadeIn"], L["FadeIn_Desc"], 0, 1, 0.05, sc, y,
        function() return rCastbarDB[unit].fadeInTime end,
        function(v) rCastbarDB[unit].fadeInTime=v end,
        function(v) return string.format(L["FadeTime_Fmt"], v) end)
    y=y-72

    ModernSlider(sc, L["FadeOut"], L["FadeOut_Desc"], 0, 2, 0.05, sc, y,
        function() return rCastbarDB[unit].fadeOutTime end,
        function(v) rCastbarDB[unit].fadeOutTime=v end,
        function(v) return string.format(L["FadeTime_Fmt"], v) end)
    y=y-72

    -- Successful Cast
    y=y-8; SectionHeader(sc, L["SuccessSection"], sc, P2, y); y=y-26

    ModernToggle(sc, L["CastSuccess"], sc, P2, y,
        function() return rCastbarDB[unit].castSuccess end,
        function(v) rCastbarDB[unit].castSuccess=v end,
        L["CastSuccess_Desc"])
    y=y-56

    -- Colors
    y=y-8; SectionHeader(sc, L["Colors"], sc, P2, y); y=y-26

    local colorNote=FS(sc,"GameFontHighlightSmall"); colorNote:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    colorNote:SetTextColor(C.primaryDim[1]+0.2,C.primaryDim[2]+0.1,C.primaryDim[3]+0.3,0.8)
    colorNote:SetText(L["Colors_Note"]); y=y-20

    -- Class color toggle
    ModernToggle(sc, L["ClassColor"], sc, P2, y,
        function() return rCastbarDB[unit].classColor end,
        function(v)
            rCastbarDB[unit].classColor=v
            local bar=_G["rCastbar_"..(unit:sub(1,1):upper()..unit:sub(2))]
            if bar then
                if v then
                    local _,classFile=UnitClass(unit)
                    local cc=classFile and RAID_CLASS_COLORS[classFile]
                    if cc then bar:SetStatusBarColor(cc.r,cc.g,cc.b,rCastbarDB[unit].color.a) end
                else
                    local col=rCastbarDB[unit].color
                    bar:SetStatusBarColor(col.r,col.g,col.b,col.a)
                end
            end
        end,
        L["ClassColor_Desc"])
    y=y-56

    local colorRows={}

    local r1=ColorPickerRow(sc, L["ColorCast"],
        function() return rCastbarDB[unit].color end,
        function(t) rCastbarDB[unit].color=t; LiveApplyColor(unit,"color") end,
        sc,y); colorRows[#colorRows+1]=r1; y=y-32

    local r2=ColorPickerRow(sc, L["ColorBg"],
        function() return rCastbarDB[unit].bgColor end,
        function(t) rCastbarDB[unit].bgColor=t; LiveApplyColor(unit,"bgColor") end,
        sc,y); colorRows[#colorRows+1]=r2; y=y-32

    local r3=ColorPickerRow(sc, L["ColorFail"],
        function() return rCastbarDB[unit].failColor end,
        function(t) rCastbarDB[unit].failColor=t end,
        sc,y); colorRows[#colorRows+1]=r3; y=y-32

    local r4=ColorPickerRow(sc, L["ColorSuccess"],
        function() return rCastbarDB[unit].successColor end,
        function(t) rCastbarDB[unit].successColor=t end,
        sc,y); colorRows[#colorRows+1]=r4; y=y-32

    if unit == "target" then
        local r5=ColorPickerRow(sc, L["ColorShield"],
            function() return rCastbarDB.target.shieldColor end,
            function(t) rCastbarDB.target.shieldColor=t end,
            sc,y); colorRows[#colorRows+1]=r5; y=y-32
        local r6=ColorPickerRow(sc, L["ColorShieldBg"],
            function() return rCastbarDB.target.shieldBgColor end,
            function(t) rCastbarDB.target.shieldBgColor=t end,
            sc,y); colorRows[#colorRows+1]=r6; y=y-32
    end

    local resetColorBtn=StyledButton(sc, L["ResetColors"], nil, 24, function()
        local def=DEFAULT_COLORS[unit]; if not def then return end
        rCastbarDB[unit].color        = copyColor(def.color)
        rCastbarDB[unit].bgColor      = copyColor(def.bgColor)
        rCastbarDB[unit].failColor    = copyColor(def.failColor)
        rCastbarDB[unit].successColor = copyColor(def.successColor)
        LiveApplyColor(unit,"color"); LiveApplyColor(unit,"bgColor")
        if unit=="target" and def.shieldColor then
            rCastbarDB.target.shieldColor   = copyColor(def.shieldColor)
            rCastbarDB.target.shieldBgColor = copyColor(def.shieldBgColor)
        end
        for _,row in ipairs(colorRows) do if row.UpdatePreview then row:UpdatePreview() end end
    end)
    resetColorBtn:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y); y=y-32

    -- Position
    y=y-8; SectionHeader(sc, L["Position"], sc, P2, y); y=y-26

    local posNote=FS(sc,"GameFontHighlightSmall"); posNote:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    posNote:SetTextColor(unpack(C.textDim)); posNote:SetText(L["Position_Note"]); y=y-20

    ModernSlider(sc, L["XOffset"], nil, -1000, 1000, 1, sc, y,
        function() return rCastbarDB[unit].x end,
        function(v)
            rCastbarDB[unit].x=v; local cont=getCont()
            if cont then cont:ClearAllPoints(); cont:SetPoint(rCastbarDB[unit].point,
                _G[rCastbarDB[unit].relFrame] or UIParent,rCastbarDB[unit].relPoint,v,rCastbarDB[unit].y) end
        end,
        function(v) return string.format("%d",v) end)
    y=y-72

    ModernSlider(sc, L["YOffset"], nil, -800, 800, 1, sc, y,
        function() return rCastbarDB[unit].y end,
        function(v)
            rCastbarDB[unit].y=v; local cont=getCont()
            if cont then cont:ClearAllPoints(); cont:SetPoint(rCastbarDB[unit].point,
                _G[rCastbarDB[unit].relFrame] or UIParent,rCastbarDB[unit].relPoint,rCastbarDB[unit].x,v) end
        end,
        function(v) return string.format("%d",v) end)
    y=y-72

    y=y-8
    local sepLine=Tex(sc,"BACKGROUND"); sepLine:SetHeight(1)
    sepLine:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y); sepLine:SetPoint("TOPRIGHT",sc,"TOPRIGHT",-P2,y)
    sepLine:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.18); y=y-14
    local applyBtn=StyledButton(sc, L["ReloadUI"], nil, 28, function() ReloadUI() end)
    applyBtn:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y); y=y-38

    sc:SetHeight(-y+P2); return sc,sf
end

-- ======================================================
-- GENERAL TAB
-- ======================================================
local function BuildGeneralTab(parent)
    local sc,sf=MakeScrollContent(parent)
    local P2=12; local y=-P2

    local LOGO_W=math.min(CONTENT_W-40,450)
    local LOGO_H=math.floor(LOGO_W*LOGO_RATIO)
    local logo=Tex(sc,"ARTWORK"); logo:SetTexture(MEDIA.."logo_big"); logo:SetSize(LOGO_W,LOGO_H)
    logo:SetPoint("TOP",sc,"TOP",0,y); y=y-LOGO_H-20

    SectionHeader(sc, L["EditMode"], sc, P2, y); y=y-26
    local editDesc=FS(sc,"GameFontHighlightSmall"); editDesc:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    editDesc:SetTextColor(unpack(C.textDim)); editDesc:SetText(L["EditMode_Note"]); y=y-20

    local editBtn,editBtnLbl=StyledButton(sc, L["EditMode_Enable"], nil, 30, nil)
    editBtn.editBtnLbl = editBtnLbl
    _G.rCastbarEditBtn = editBtn
    editBtn:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    editBtn:SetScript("OnClick",function()
        if InCombatLockdown() then return end
        local pC=_G["rCastbar_Player_Container"]; local tC=_G["rCastbar_Target_Container"]
        if editBtn._editOn then
            editBtn._editOn=false; editBtnLbl:SetText(L["EditMode_Enable"])
            editBtn:SetBackdropColor(C.primary[1]*0.18,C.primary[2]*0.08,C.primary[3]*0.18,1)
            for _,cont in ipairs({pC,tC}) do if cont and cont._editFrame then cont._editFrame:Hide() end end
            HideEditGrid(); if editModeTopBar then editModeTopBar:Hide() end
            if editModeKeyButton then ClearOverrideBindings(editModeKeyButton) end
            if rCastbar_ShowPreview then rCastbar_ShowPreview(false) end
            if configFrame then configFrame:Show() end
        else
            editBtn._editOn=true; editBtnLbl:SetText(L["EditMode_Disable"])
            editBtn:SetBackdropColor(C.primary[1]*0.35,C.primary[2]*0.12,C.primary[3]*0.35,1)
            local function MkOv(cont)
                if not cont then return end
                if not cont._editFrame then
                    local ef=CreateFrame("Frame",nil,cont)
                    ef:SetAllPoints(cont)
                    ef:SetFrameStrata("HIGH")
                    ef:SetFrameLevel(cont:GetFrameLevel()+5)
                    ef:EnableMouse(false)
                    local ov=ef:CreateTexture(nil,"OVERLAY",nil,7); ov:SetAllPoints()
                    ov:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.18)
                    local ovLbl=ef:CreateFontString(nil,"OVERLAY"); ovLbl:SetFont(FONT,10,"OUTLINE"); ovLbl:SetPoint("CENTER",0,8)
                    ovLbl:SetTextColor(1,1,1,0.9); ovLbl:SetText("SHIFT + DRAG")
                    local xText=ef:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
                    xText:SetPoint("TOP",cont,"BOTTOM",0,-2); xText:SetTextColor(0.2,0.9,1)
                    local yText=ef:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
                    yText:SetPoint("TOP",xText,"BOTTOM",0,-2); yText:SetTextColor(0.2,0.9,1)
                    ef.xText, ef.yText = xText, yText
                    cont._editFrame=ef
                    cont:HookScript("OnDragStop",function() UpdateAnchorPositionDisplay(cont) end)
                    cont:HookScript("OnMouseDown",function() rCastbar_SelectedContainer=cont end)
                end
                cont._editFrame:Show()
                UpdateAnchorPositionDisplay(cont)
            end
            MkOv(pC); MkOv(tC)
            if rCastbar_ShowPreview then rCastbar_ShowPreview(true) end
            ShowEditGrid()
            CreateEditModeTopBar(); editModeTopBar:Show()
            if configFrame then configFrame:Hide() end
            if not editModeKeyButton then
                editModeKeyButton=CreateFrame("Button","rCastbarEditKeyHandler",UIParent)
                editModeKeyButton:SetScript("OnClick",function(_,btn)
                    if not rCastbar_NudgeSelected then return end
                    local step=IsShiftKeyDown() and 5 or 1
                    local dx,dy=0,0
                    if btn=="UP" then dy=step elseif btn=="DOWN" then dy=-step
                    elseif btn=="LEFT" then dx=-step elseif btn=="RIGHT" then dx=step
                    else return end
                    rCastbar_NudgeSelected(dx,dy)
                end)
            end
            ClearOverrideBindings(editModeKeyButton)
            SetOverrideBindingClick(editModeKeyButton,false,"UP","rCastbarEditKeyHandler","UP")
            SetOverrideBindingClick(editModeKeyButton,false,"DOWN","rCastbarEditKeyHandler","DOWN")
            SetOverrideBindingClick(editModeKeyButton,false,"LEFT","rCastbarEditKeyHandler","LEFT")
            SetOverrideBindingClick(editModeKeyButton,false,"RIGHT","rCastbarEditKeyHandler","RIGHT")
        end
        C_Timer.After(0,function() if editBtnLbl then local sw=editBtnLbl:GetStringWidth()
            if sw and sw>0 then editBtn:SetWidth(sw+BTN_PAD_X) end end end)
    end)
    y=y-38

    -- Minimap Icon Toggle
    y=y-8; SectionHeader(sc, L["ShowMinimapIcon"], sc, P2, y); y=y-26

    local mmToggle = ModernToggle(sc, L["ShowMinimapIcon"], sc, P2, y,
        function()
            if rCastbarDB and rCastbarDB.minimap then return not rCastbarDB.minimap.hide end
            return true
        end,
        function(v) end,  -- setter: handled by the modal dialog below
        L["ShowMinimapIcon_Desc"])
    y=y-56

    -- Modal reload dialog (built once, shown on toggle click)
    local pendingHide = false
    local mmDlg = CreateFrame("Frame", "rCastbarMinimapReloadDialog", UIParent, "BackdropTemplate")
    mmDlg:SetSize(420, 150); mmDlg:SetPoint("CENTER")
    mmDlg:SetFrameStrata("FULLSCREEN_DIALOG"); mmDlg:SetFrameLevel(300)
    mmDlg:Hide(); mmDlg:EnableMouse(true)
    mmDlg:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8",
        tile=false, edgeSize=2, insets={left=0,right=0,top=0,bottom=0}})
    mmDlg:SetBackdropColor(0.04, 0.04, 0.06, 0.97)
    mmDlg:SetBackdropBorderColor(C.primary[1]*0.7, C.primary[2]*0.6, C.primary[3]*0.9, 1)

    local mmDlgHdr = Tex(mmDlg, "BORDER")
    mmDlgHdr:SetPoint("TOPLEFT"); mmDlgHdr:SetPoint("TOPRIGHT"); mmDlgHdr:SetHeight(32)
    mmDlgHdr:SetColorTexture(C.primary[1]*0.15, C.primary[2]*0.12, C.primary[3]*0.24, 1)

    local mmDlgTitle = FS(mmDlg, "GameFontNormalLarge")
    mmDlgTitle:SetPoint("TOPLEFT", 14, -8)
    mmDlgTitle:SetText(L["Minimap_ReloadTitle"])
    mmDlgTitle:SetTextColor(C.primary[1], C.primary[2], C.primary[3])

    local mmDlgBody = FS(mmDlg, "GameFontNormal")
    mmDlgBody:SetPoint("TOPLEFT", 14, -44); mmDlgBody:SetWidth(392)
    mmDlgBody:SetJustifyH("LEFT"); mmDlgBody:SetText(L["Minimap_ReloadBody"])
    mmDlgBody:SetTextColor(unpack(C.text))

    StyledButton(mmDlg, L["Minimap_BtnCancel"], nil, 28, function()
        -- Revert toggle visual back to current saved state
        if mmToggle and mmToggle.Refresh then mmToggle.Refresh() end
        mmDlg:Hide()
    end):SetPoint("BOTTOMLEFT", 14, 12)

    StyledButton(mmDlg, L["Minimap_BtnReload"], nil, 28, function()
        if rCastbarDB and rCastbarDB.minimap then
            rCastbarDB.minimap.hide = pendingHide
        end
        mmDlg:Hide()
        ReloadUI()
    end):SetPoint("BOTTOMRIGHT", -14, 12)

    -- Intercept the toggle track click to show the dialog instead
    local mmTrack = select(1, mmToggle:GetChildren())
    if mmTrack then
        mmTrack:SetScript("OnClick", function()
            if not rCastbarDB or not rCastbarDB.minimap then return end
            pendingHide = not rCastbarDB.minimap.hide
            mmDlg:Show()
        end)
    end

    y=y-8; SectionHeader(sc, L["Reset"], sc, P2, y); y=y-26
    local resetNote=FS(sc,"GameFontHighlightSmall"); resetNote:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    resetNote:SetTextColor(unpack(C.textDim)); resetNote:SetText(L["Reset_Note"]); y=y-20
    local resetBtn=DangerButton(sc, L["ResetBtn"], nil, 28, function()
        if rCastbarDB then rCastbarDB.player=nil; rCastbarDB.target=nil; rCastbarDB.guiScale=nil end; ReloadUI()
    end)
    resetBtn:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y); y=y-38

    y=y-8
    local sep=Tex(sc,"BACKGROUND"); sep:SetHeight(1)
    sep:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y); sep:SetPoint("TOPRIGHT",sc,"TOPRIGHT",-P2,y)
    sep:SetColorTexture(C.primary[1],C.primary[2],C.primary[3],0.15); y=y-12
    local infoLbl=FS(sc,"GameFontHighlightSmall"); infoLbl:SetPoint("TOPLEFT",sc,"TOPLEFT",P2,y)
    infoLbl:SetTextColor(unpack(C.textDim)); infoLbl:SetText(L["Commands"]); y=y-20

    sc:SetHeight(-y+P2)
end

-- ======================================================
-- BLIZZARD PANEL
-- ======================================================
local function CreateBlizzardPanel()
    local panel=CreateFrame("Frame","rCastbarInterfacePanel"); panel.name="rCastbar"
    local BW=300; local BH=math.floor(BW*LOGO_RATIO)
    local logo=panel:CreateTexture(nil,"ARTWORK"); logo:SetSize(BW,BH); logo:SetPoint("TOP",panel,"TOP",0,-28); logo:SetTexture(MEDIA.."logo_big")
    local titleLbl=panel:CreateFontString(nil,"ARTWORK","GameFontNormalHuge"); titleLbl:SetPoint("TOP",logo,"BOTTOM",0,-10)
    titleLbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1); titleLbl:SetText("|cff7327d9r|rCastbar")
    local subLbl=panel:CreateFontString(nil,"ARTWORK","GameFontNormalSmall"); subLbl:SetPoint("TOP",titleLbl,"BOTTOM",0,-4)
    subLbl:SetTextColor(0.50,0.40,0.65,1); subLbl:SetText(L["BlizzSubtitle"])
    local descLbl=panel:CreateFontString(nil,"ARTWORK","GameFontHighlightSmall"); descLbl:SetPoint("TOP",subLbl,"BOTTOM",0,-8)
    descLbl:SetWidth(420); descLbl:SetJustifyH("CENTER"); descLbl:SetTextColor(0.55,0.45,0.70,1); descLbl:SetText(L["BlizzDesc"])
    local openBtn=CreateFrame("Button",nil,panel,"BackdropTemplate"); openBtn:SetSize(220,38); openBtn:SetPoint("TOP",descLbl,"BOTTOM",0,-18)
    openBtn:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",tile=false,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
    openBtn:SetBackdropColor(C.primary[1]*0.20,C.primary[2]*0.08,C.primary[3]*0.20,1)
    openBtn:SetBackdropBorderColor(C.primary[1]*0.70,C.primary[2]*0.30,C.primary[3]*0.70,1)
    local openLbl=openBtn:CreateFontString(nil,"OVERLAY","GameFontNormal"); openLbl:SetPoint("CENTER")
    openLbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1); openLbl:SetText(L["OpenSettings"])
    openBtn:SetScript("OnEnter",function(s) s:SetBackdropColor(C.primary[1]*0.40,C.primary[2]*0.15,C.primary[3]*0.40,1)
        s:SetBackdropBorderColor(C.primaryHi[1],C.primaryHi[2],C.primaryHi[3],1); openLbl:SetTextColor(1,1,1,1) end)
    openBtn:SetScript("OnLeave",function(s) s:SetBackdropColor(C.primary[1]*0.20,C.primary[2]*0.08,C.primary[3]*0.20,1)
        s:SetBackdropBorderColor(C.primary[1]*0.70,C.primary[2]*0.30,C.primary[3]*0.70,1); openLbl:SetTextColor(C.primary[1],C.primary[2],C.primary[3],1) end)
    openBtn:SetScript("OnClick",function()
        C_Timer.After(0,function()
            if SettingsPanel and SettingsPanel:IsShown() then HideUIPanel(SettingsPanel) end
            if configFrame then configFrame:FadeIn() end
        end)
    end)
    if Settings and Settings.RegisterCanvasLayoutCategory then
        local cat=Settings.RegisterCanvasLayoutCategory(panel,"rCastbar"); cat.ID="rCastbar"; Settings.RegisterAddOnCategory(cat)
    elseif InterfaceOptions_AddCategory then panel.parent="AddOns"; InterfaceOptions_AddCategory(panel) end
end

-- ======================================================
-- INIT
-- ======================================================
local initFrame=CreateFrame("Frame"); initFrame:RegisterEvent("PLAYER_LOGIN")
initFrame:SetScript("OnEvent",function(self,event)
    if event~="PLAYER_LOGIN" then return end; self:UnregisterEvent("PLAYER_LOGIN")
    -- Re-bind L in case locale was loaded after this file's top-level assignment
    L = rCastbar_L or L or {}
    if rCastbarDB and not rCastbarDB.guiScale then rCastbarDB.guiScale=1.0 end
    local frame,tabs,tabPanels,SelectTab=BuildConfigFrame()
    BuildGeneralTab(tabPanels[1]); BuildUnitPanel(tabPanels[2],"player"); BuildUnitPanel(tabPanels[3],"target")
    CreateBlizzardPanel()
    frame:HookScript("OnShow",function(f) f:SetScale((rCastbarDB and rCastbarDB.guiScale)or 1.0) end)
    SelectTab(1)
end)

SLASH_RCASTBAR1="/rcastbar"; SLASH_RCASTBAR2="/rcb"
SlashCmdList["RCASTBAR"]=function(msg)
    msg=(msg or ""):lower():trim()
    if msg=="edit" or msg=="move" or msg=="toggle" then
        if InCombatLockdown() then return end
        local editBtn=_G.rCastbarEditBtn
        if editBtn and editBtn:GetScript("OnClick") then editBtn:Click() end
    else
        if configFrame then if configFrame:IsShown() then configFrame:FadeOut() else configFrame:FadeIn() end end
    end
end

-- ======================================================
-- MINIMAP ICON (LibDBIcon-1.0)
-- ======================================================
do
    local LDB     = LibStub and LibStub("LibDataBroker-1.1", true)
    local LDBIcon = LibStub and LibStub("LibDBIcon-1.0",      true)

    if LDB and LDBIcon then
        local broker = LDB:NewDataObject("rCastbar", {
            type  = "launcher",
            icon  = 237017,
            label = "rCastbar",
            OnClick = function(_, btn)
                if not configFrame then return end
                if btn == "RightButton" then return end
                if configFrame:IsShown() then configFrame:FadeOut() else configFrame:FadeIn() end
            end,
            OnTooltipShow = function(tt)
                tt:AddLine("|cff7327d9r|rCastbar")
                tt:AddLine(" ")
                tt:AddLine(L["Tooltip_Click"], 0.9, 0.9, 0.9)
                tt:AddLine(L["Tooltip_Drag"],  0.6, 0.6, 0.6)
            end,
        })

        local minimapInitFrame = CreateFrame("Frame")
        minimapInitFrame:RegisterEvent("PLAYER_LOGIN")
        minimapInitFrame:SetScript("OnEvent", function(self)
            self:UnregisterEvent("PLAYER_LOGIN")
            if not rCastbarDB then rCastbarDB = {} end
            if not rCastbarDB.minimap then
                rCastbarDB.minimap = { minimapPos = 225, hide = false }
            end
            LDBIcon:Register("rCastbar", broker, rCastbarDB.minimap)
        end)
    end
end
