local addonName, ns = ...
if type(rCastbarBlizzEdit) ~= "table" then return end
local Castbar = rCastbarBlizzEdit
local optionsDialog
local L = rCastbar_L or {}

local function editModeNotify(msg)
    if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
        pcall(function()
            DEFAULT_CHAT_FRAME:AddMessage("|cffffcc00rCastbar:|r " .. tostring(msg))
        end)
    end
end

local UIParent = UIParent
local GameTooltip = GameTooltip
local math = math
local pairs = pairs
local ipairs = ipairs
local type = type
local pcall = pcall
local format = string.format

local DBKEY_MASTER = "showCastbarsInHUDEditMode"

local function profile()
    return rCastbarDB
end

local function getMasterEnabled()
    local p = profile()
    if not p then return true end
    if p[DBKEY_MASTER] == false then return false end
    return true
end

local function setMasterEnabled(v)
    local p = profile()
    if not p then return end
    p[DBKEY_MASTER] = v and true or false
end

local function isBlizzardEditModeActive()
    local f = EditModeManagerFrame
    return f and f.IsEditModeActive and f:IsEditModeActive()
end

local function getMouseFocusCompat()
    if GetMouseFoci then
        local list = GetMouseFoci()
        if list and list[1] then return list[1] end
    end
    if GetMouseFocus then return GetMouseFocus() end
end

local function isMouseFocusOnAnchorEditSubtree(anchor)
    if not anchor or not Castbar then return false end
    local f = getMouseFocusCompat()
    local dlg = optionsDialog
    while f do
        if f == anchor then return true end
        if anchor.Selection and f == anchor.Selection then return true end
        local cb = Castbar.frames and Castbar.frames[anchor.unit]
        if cb and f == cb then return true end
        if dlg and dlg:IsShown() and dlg.anchorRef == anchor then
            if f == dlg then return true end
            if f.isRCastbarSettingsUIPart then
                local p = f
                while p do
                    if p == dlg then return true end
                    p = p:GetParent()
                end
            end
        end
        f = f:GetParent()
    end
    return false
end

local function markRcUi(f)
    if f then f.isRCastbarSettingsUIPart = true end
end

local liveApplyTimer
local function requestLiveApplyFromDialog()
    local unit = optionsDialog and optionsDialog._rcEditUnit
    if not unit or not Castbar then return end
    if liveApplyTimer then liveApplyTimer:Cancel() end
    liveApplyTimer = C_Timer.NewTimer(0, function()
        liveApplyTimer = nil
        pcall(function()
            Castbar:Create(unit)
            if isBlizzardEditModeActive() and getMasterEnabled() and Castbar.ShowEditModePreviewForUnit then
                Castbar:ShowEditModePreviewForUnit(unit)
            end
        end)
    end)
end

local function defaultPositionForUnit(unit)
    if rCastbar_GetDefaultPosition then
        local ok, t = pcall(rCastbar_GetDefaultPosition, unit)
        if ok and type(t) == "table" and t[1] and t[2] and t[3] then
            return { t[1], t[2], t[3], t[4] or 0, t[5] or 0 }
        end
    end
    return { "CENTER", "UIParent", "CENTER", 0, 0 }
end

local function unitLabel(unit)
    if unit == "player" then return L["Player"] or "Player" end
    if unit == "target" then return L["Target"] or "Target" end
    return unit
end

local function tooltipActiveCastbars()
    local lines
    for _, u in ipairs({ "player", "target" }) do
        local cfg = profile() and profile()[u]
        if cfg and cfg.enabled then
            local name = "- " .. unitLabel(u)
            if lines then lines = lines .. "\n" .. name else lines = name end
        end
    end
    return lines
end

-- ---------------------------------------------------------------------------
-- Selection overlay (same approach as HUD edit overlays: nine-slice BLP)
-- ---------------------------------------------------------------------------
local TEX_EDITMODE_HIGHLIGHT = "Interface/AddOns/rCastbar/media/EditModeHighlighted"
local TEX_EDITMODE_SELECTED = "Interface/AddOns/rCastbar/media/EditModeSelected"

local EditModeSelectionMixin = {}

function EditModeSelectionMixin:OnDragStart()
    local p = self.parent
    local fn = p and p.GetScript and p:GetScript("OnDragStart")
    if fn then fn(p) end
end

function EditModeSelectionMixin:OnDragStop()
    local p = self.parent
    local fn = p and p.GetScript and p:GetScript("OnDragStop")
    if fn then fn(p) end
end

function EditModeSelectionMixin:ShowHighlighted()
    if not self.parent or not self.parent:IsShown() then return end
    self.isSelected = false
    if self.Background then
        self.Background:SetTexture(TEX_EDITMODE_HIGHLIGHT)
        self.Background:SetVertexColor(1, 1, 1, 1)
    end
    self:Show()
    if self.Label then self.Label:Hide() end
end

function EditModeSelectionMixin:ShowSelected()
    if not self.parent or not self.parent:IsShown() then return end
    self.isSelected = true
    if self.Background then
        self.Background:SetTexture(TEX_EDITMODE_SELECTED)
        self.Background:SetVertexColor(1, 1, 1, 1)
    end
    self:Show()
    GameTooltip:Hide()
    if self.Label then self.Label:Show() end
end

function EditModeSelectionMixin:OnShow()
    local offset = 8
    if self.Background then
        self.Background:SetPoint("TOPLEFT", self, "TOPLEFT", -offset, offset)
        self.Background:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", offset, -offset)
    end
    self:RegisterEvent("GLOBAL_MOUSE_DOWN")
end

function EditModeSelectionMixin:OnHide()
    self:UnregisterEvent("GLOBAL_MOUSE_DOWN")
end

-- Plumber pattern (SharedEditMode.lua): GLOBAL_MOUSE_DOWN; if not focused, close options + blue highlight.
-- Synchronous (no C_Timer): hit test uses GetMouseFoci at click time, same as Plumber GetMouseFocus.
function EditModeSelectionMixin:OnEvent(event, ...)
    if event ~= "GLOBAL_MOUSE_DOWN" then return end
    if not isBlizzardEditModeActive() or not getMasterEnabled() then return end
    if not self:IsShown() or not self.parent then return end
    local ch = _G.nCastbarEditModeCheckboxHost
    if ch and ch:IsShown() and ch:IsMouseOver() then
        return
    end
    local rch = _G.rCastbarEditModeCheckboxHost
    if rch and rch:IsShown() and rch:IsMouseOver() then
        return
    end
    local ro = _G.rOrbsEditModeCheckboxHost
    if ro and ro:IsShown() and ro:IsMouseOver() then
        return
    end
    local rt = _G.rThreatEditModeCheckboxHost
    if rt and rt:IsShown() and rt:IsMouseOver() then
        return
    end
    local om = _G.ObeliskMinimapEditModeCheckboxHost
    if om and om:IsShown() and om:IsMouseOver() then
        return
    end
    local nmm = _G.nMinimapEditModeCheckboxHost
    if nmm and nmm:IsShown() and nmm:IsMouseOver() then
        return
    end
    local gqt = _G.GQuesttrackerEditModeCheckboxHost
    if gqt and gqt:IsShown() and gqt:IsMouseOver() then
        return
    end
    local cs = _G.CombatStatusEditModeCheckboxHost
    if cs and cs:IsShown() and cs:IsMouseOver() then
        return
    end
    local nbh = _G.nBuffEditModeCheckboxHost
    if nbh and nbh:IsShown() and nbh:IsMouseOver() then
        return
    end
    if self.parent:IsFocused() then
        return
    end
    self:ShowHighlighted()
    if self.parent.ShowAnchorOptions then
        self.parent:ShowAnchorOptions(false)
    end
    if Castbar and Castbar.DeselectAnchorForEditMode then
        Castbar:DeselectAnchorForEditMode(self.parent)
    end
end

function EditModeSelectionMixin:OnEnter()
    GameTooltip:SetOwner(self, "ANCHOR_CURSOR")
    if not self.isSelected then
        local hint = L["EDITMODE_CLICK_TO_EDIT"] or L["EditMode Click To Edit"] or "Click to edit"
        GameTooltip:SetText(hint, 1, 1, 1, 1, true)
        if self.uiName then
            GameTooltip:AddLine(self.uiName, 1, 0.82, 0, true)
        end
    elseif self.uiName then
        GameTooltip:SetText(self.uiName, 1, 0.82, 0, 1, true)
    end
    GameTooltip:Show()
end

function EditModeSelectionMixin:OnLeave()
    GameTooltip:Hide()
end

function EditModeSelectionMixin:OnMouseDown(_, button)
    local md = self.parent.GetScript and self.parent:GetScript("OnMouseDown")
    if md then md(self.parent, button) end
    self:ShowSelected()
    if Castbar then
        Castbar.selectedAnchor = self.parent
    end
    if self.parent.ShowAnchorOptions then
        self.parent:ShowAnchorOptions(true)
    end
    if EditModeManagerFrame and EditModeManagerFrame.ClearSelectedSystem then
        EditModeManagerFrame:ClearSelectedSystem()
    end
    if button == "RightButton" and self.parent.OnAnchorRightClick then
        self.parent:OnAnchorRightClick()
    end
end

local function createSelection(parent, uiName)
    local f = CreateFrame("Frame", nil, parent)
    f:Hide()
    f:SetAllPoints(true)
    f:SetFrameStrata(parent:GetFrameStrata())
    f:SetToplevel(true)
    f:SetFrameLevel(999)
    f:EnableMouse(true)
    f:EnableMouseMotion(true)
    f:RegisterForDrag("LeftButton")
    f:SetIgnoreParentAlpha(true)

    f.Background = f:CreateTexture(nil, "BACKGROUND")
    f.Background:SetTexture(TEX_EDITMODE_HIGHLIGHT)
    pcall(function()
        f.Background:SetTextureSliceMargins(16, 16, 16, 16)
        f.Background:SetTextureSliceMode(0)
    end)
    f.Background:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
    f.Background:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
    f.Background:SetVertexColor(1, 1, 1, 1)

    f.Label = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightMedium")
    f.Label:SetText(uiName)
    f.Label:SetJustifyH("CENTER")
    f.Label:SetPoint("CENTER", f, "CENTER", 0, 0)
    f.Label:Hide()

    Mixin(f, EditModeSelectionMixin)
    f:SetScript("OnShow", f.OnShow)
    f:SetScript("OnHide", f.OnHide)
    f:SetScript("OnEnter", f.OnEnter)
    f:SetScript("OnLeave", f.OnLeave)
    f:SetScript("OnEvent", f.OnEvent)
    f:SetScript("OnMouseDown", f.OnMouseDown)
    f:SetScript("OnDragStart", f.OnDragStart)
    f:SetScript("OnDragStop", f.OnDragStop)

    f.parent = parent
    f.uiName = uiName
    f._rcBlizzEditHit = true
    return f
end

-- ---------------------------------------------------------------------------
-- Options side panel (Blizzard DialogBorderTranslucentTemplate)
-- ---------------------------------------------------------------------------
local optionsWidgets = {}
-- Forward: CloseButton calls clearAllEditFocus before its definition at EOF (Lua scope).
local clearAllEditFocus

-- DialogBorderTranslucentTemplate may reset border child FrameLevel on Show; keep scroll/close above.
local function applyEditOptionsZOrder(dlg)
    if not dlg or not dlg:IsShown() or not dlg._rcDialogBorder then return end
    local base = dlg:GetFrameLevel() or 0
    dlg._rcDialogBorder:SetFrameLevel(math.max(0, base - 100))
    if dlg.scrollFrame then dlg.scrollFrame:SetFrameLevel(base + 20) end
    if dlg.CloseButton then dlg.CloseButton:SetFrameLevel(base + 40) end
end

local function snapEditSliderValue(v, minV, maxV, step)
    v = math.max(minV, math.min(maxV, v))
    if step and step >= 1 then
        v = math.floor(v + 0.5)
    elseif step and step < 1 then
        local m = math.floor(1 / step + 0.5)
        v = math.floor(v * m + 0.5) / m
    end
    return v
end

local function clearOptionsWidgets()
    for i = #optionsWidgets, 1, -1 do
        local w = optionsWidgets[i]
        if w then
            w:Hide()
            w:SetParent(nil)
        end
        optionsWidgets[i] = nil
    end
end

local function layoutOptionsContent()
    if not optionsDialog or not optionsDialog.content then return end
    local c = optionsDialog.content
    local y = -4
    local gap = 10
    for i = 1, #optionsWidgets do
        local w = optionsWidgets[i]
        if w then
            w:ClearAllPoints()
            w:SetPoint("TOPLEFT", c, "TOPLEFT", 8, y)
            local h = w.estimatedHeight or w:GetHeight() or 24
            y = y - h - gap
        end
    end
    local contentDepth = math.abs(y) + 44
    c:SetHeight(math.max(1, contentDepth))
    local wScroll = optionsDialog.scrollFrame and optionsDialog.scrollFrame:GetWidth() or 0
    if wScroll < 24 then
        wScroll = (optionsDialog:GetWidth() or 432) - 48
    end
    c:SetWidth(math.max(200, wScroll - 8))
    local screenH = (GetScreenHeight and GetScreenHeight()) or (UIParent and UIParent:GetHeight()) or 800
    local chromeScroll = 64
    local maxDialog = math.floor(screenH * 0.92)
    local maxViewport = math.max(160, maxDialog - chromeScroll)
    local viewport = math.min(math.max(1, contentDepth), maxViewport)
    optionsDialog:SetHeight(math.max(220, math.min(maxDialog, viewport + chromeScroll)))
    if optionsDialog.scrollFrame then
        optionsDialog.scrollFrame:SetVerticalScroll(0)
        optionsDialog.scrollFrame:UpdateScrollChildRect()
    end
    applyEditOptionsZOrder(optionsDialog)
end

local function addFontHeader(text)
    if not optionsDialog or not optionsDialog.content then return end
    local fs = optionsDialog.content:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    fs:SetWidth(math.max(200, (optionsDialog:GetWidth() or 420) - 40))
    fs:SetJustifyH("LEFT")
    fs:SetTextColor(1, 1, 1)
    fs:SetText(text)
    markRcUi(fs)
    fs.estimatedHeight = 22
    table.insert(optionsWidgets, fs)
end

local function addCheckbox(label, isChecked, onToggle, tooltip)
    if not optionsDialog or not optionsDialog.content then return end
    local cb = CreateFrame("CheckButton", nil, optionsDialog.content, "InterfaceOptionsCheckButtonTemplate")
    cb:SetSize(26, 26)
    cb.Text:SetText(label)
    cb.Text:SetFontObject("GameFontHighlightMedium")
    cb.Text:SetTextColor(1, 1, 1)
    cb:SetHitRectInsets(0, -280, 0, 0)
    cb:SetChecked(isChecked and true or false)
    cb:SetScript("OnClick", function(self)
        onToggle(self:GetChecked() and true or false)
        requestLiveApplyFromDialog()
    end)
    if tooltip then
        cb:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText(tooltip, nil, nil, nil, nil, true)
            GameTooltip:Show()
        end)
        cb:SetScript("OnLeave", function() GameTooltip:Hide() end)
    end
    markRcUi(cb)
    cb.estimatedHeight = 32
    table.insert(optionsWidgets, cb)
end

local optionsSliderSeq = 0
local function addSlider(label, minV, maxV, step, current, onCommit, valueFmt)
    if not optionsDialog or not optionsDialog.content then return end
    optionsSliderSeq = optionsSliderSeq + 1
    local holder = CreateFrame("Frame", nil, optionsDialog.content)
    holder:SetSize(360, 48)
    local lbl = holder:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    lbl:SetPoint("TOPLEFT", 0, -2)
    lbl:SetText(label)
    lbl:SetTextColor(1, 1, 1)
    markRcUi(lbl)
    local valStr = holder:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    valStr:SetTextColor(1, 0.82, 0)
    valStr:SetPoint("TOPRIGHT", holder, "TOPRIGHT", -6, -2)
    markRcUi(valStr)
    local sliderName = "rCastbarEditOptMS" .. optionsSliderSeq
    local s = CreateFrame("Slider", sliderName, holder, "MinimalSliderTemplate")
    s:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, -24)
    s:SetPoint("TOPRIGHT", holder, "TOPRIGHT", -8, -24)
    s:SetHeight(20)
    s:SetMinMaxValues(minV, maxV)
    s:SetValueStep(step or 1)
    s:SetObeyStepOnDrag(true)
    local back = CreateFrame("Button", nil, holder)
    back:SetSize(11, 19)
    back:SetPoint("RIGHT", s, "LEFT", -4, 0)
    back:SetNormalAtlas("Minimal_SliderBar_Button_Left")
    back:SetHighlightAtlas("Minimal_SliderBar_Button_Left", "ADD")
    local fwd = CreateFrame("Button", nil, holder)
    fwd:SetSize(11, 19)
    fwd:SetPoint("LEFT", s, "RIGHT", 4, 0)
    fwd:SetNormalAtlas("Minimal_SliderBar_Button_Right")
    fwd:SetHighlightAtlas("Minimal_SliderBar_Button_Right", "ADD")
    local function syncValStr(v)
        if valueFmt then valStr:SetText(valueFmt(v)) else valStr:SetText(tostring(v)) end
    end
    local lock = false
    local function publishFromSlider(v)
        if lock then return end
        lock = true
        v = snapEditSliderValue(v, minV, maxV, step)
        s:SetValue(v)
        syncValStr(v)
        onCommit(v)
        requestLiveApplyFromDialog()
        lock = false
    end
    local function nudgeBy(delta)
        lock = true
        local v = snapEditSliderValue(s:GetValue() + delta, minV, maxV, step)
        s:SetValue(v)
        syncValStr(v)
        onCommit(v)
        requestLiveApplyFromDialog()
        lock = false
        if PlaySound then pcall(PlaySound, SOUNDKIT and SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON or 856) end
    end
    s:SetValue(snapEditSliderValue(current, minV, maxV, step))
    syncValStr(s:GetValue())
    s:SetScript("OnValueChanged", function(_, v)
        publishFromSlider(v)
    end)
    back:SetScript("OnClick", function()
        nudgeBy(-(s:GetValueStep() or 1))
    end)
    fwd:SetScript("OnClick", function()
        nudgeBy(s:GetValueStep() or 1)
    end)
    markRcUi(holder)
    markRcUi(s)
    markRcUi(back)
    markRcUi(fwd)
    holder.estimatedHeight = 52
    table.insert(optionsWidgets, holder)
end

local function addResetButton(unit)
    if not optionsDialog or not optionsDialog.content then return end
    local b
    do
        local ok, btn = pcall(CreateFrame, "Button", nil, optionsDialog.content, "MagicButtonTemplate")
        if ok and btn then
            b = btn
        else
            b = CreateFrame("Button", nil, optionsDialog.content, "UIPanelButtonTemplate")
        end
    end
    b:SetSize(320, 26)
    b:SetText(L["Reset To Default Position"] or RESET_TO_DEFAULT or "Reset position")
    b:SetScript("OnClick", function()
        local pos = defaultPositionForUnit(unit)
        local cfg = profile() and profile()[unit]
        if cfg then
            cfg.point = pos[1]
            cfg.relFrame = pos[2]
            cfg.relPoint = pos[3]
            cfg.x = pos[4] or 0
            cfg.y = pos[5] or 0
        end
        local an = Castbar and Castbar.anchors and Castbar.anchors[unit]
        if an then
            an:ClearAllPoints()
            an:SetPoint(pos[1], _G[pos[2]] or UIParent, pos[3], pos[4] or 0, pos[5] or 0)
        end
        requestLiveApplyFromDialog()
    end)
    markRcUi(b)
    b.estimatedHeight = 34
    table.insert(optionsWidgets, b)
end

local function ensureOptionsDialog()
    if optionsDialog then return optionsDialog end
    local f = CreateFrame("Frame", "rCastbarEditModeOptions", UIParent)
    f:SetSize(432, 320)
    f:SetPoint("CENTER", UIParent, "CENTER", 260, 0)
    f:SetFrameStrata("DIALOG")
    f:SetFrameLevel(8000)
    f:Hide()
    f:EnableMouse(true)
    f:SetMovable(true)
    f:SetClampedToScreen(true)
    f:RegisterForDrag("LeftButton")
    f:SetDontSavePosition(true)
    markRcUi(f)

    local border = CreateFrame("Frame", nil, f, "DialogBorderTranslucentTemplate")
    border:SetAllPoints(true)
    markRcUi(border)
    border:SetFrameLevel(5)
    f._rcDialogBorder = border

    f.Title = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
    f.Title:SetPoint("TOP", f, "TOP", 0, -20)
    markRcUi(f.Title)

    f.scrollFrame = CreateFrame("ScrollFrame", nil, f)
    f.scrollFrame:SetPoint("TOPLEFT", f, "TOPLEFT", 14, -48)
    f.scrollFrame:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -14, 14)
    f.scrollFrame:EnableMouseWheel(true)
    f.scrollFrame:SetFrameLevel(100)
    f.scrollFrame:SetScript("OnMouseWheel", function(self, delta)
        local step = 40
        local v = self:GetVerticalScroll() - (delta > 0 and step or -step)
        local max = math.max(0, self:GetVerticalScrollRange())
        if v < 0 then v = 0 elseif v > max then v = max end
        self:SetVerticalScroll(v)
    end)
    markRcUi(f.scrollFrame)

    f.content = CreateFrame("Frame", nil, f.scrollFrame)
    f.scrollFrame:SetScrollChild(f.content)
    f.content:SetPoint("TOPLEFT", f.scrollFrame, "TOPLEFT", 0, 0)
    markRcUi(f.content)

    f.CloseButton = CreateFrame("Button", nil, f, "UIPanelCloseButtonNoScripts")
    f.CloseButton:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0)
    markRcUi(f.CloseButton)
    f.CloseButton:SetFrameLevel(200)
    f.CloseButton:SetScript("OnClick", function()
        clearAllEditFocus()
    end)

    f:SetScript("OnDragStart", function(self) self:StartMoving() end)
    f:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        local left = self:GetLeft()
        local top = self:GetTop()
        self:ClearAllPoints()
        self:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", left, top)
    end)

    optionsDialog = f
    return f
end

local function buildOptionsForUnit(unit)
    ensureOptionsDialog()
    clearOptionsWidgets()
    local cfg = profile() and profile()[unit]
    if not cfg then return end

    optionsDialog._rcEditUnit = unit

    optionsDialog.Title:SetText(format(L["EDITMODE_PANEL_TITLE"] or "rCastbar: %s", unitLabel(unit)))

    addFontHeader(L["Layout"] or "Layout")
    addSlider(L["Width"] or "Width", 80, 600, 1, cfg.width or 265, function(v)
        cfg.width = v
    end, function(v) return format("%d px", v) end)
    addSlider(L["Height"] or "Height", 6, 80, 1, cfg.height or 15, function(v)
        cfg.height = v
    end, function(v) return format("%d px", v) end)

    addFontHeader(L["Text"] or "Text")
    addCheckbox(L["ShowTime"] or "Show Time", cfg.showTime ~= false, function(v) cfg.showTime = v end, L["ShowTime_Desc"])
    addCheckbox(L["ShowSpellName"] or "Spell name", cfg.showSpellName ~= false, function(v) cfg.showSpellName = v end, L["ShowSpellName_Desc"])
    addCheckbox(L["DynamicWidth"] or "Dynamic width", cfg.dynamicWidth and true or false, function(v) cfg.dynamicWidth = v end, L["DynamicWidth_Desc"])
    addCheckbox(L["ClassColor"] or "Class color", cfg.classColor and true or false, function(v) cfg.classColor = v end, L["ClassColor_Desc"])
    addSlider(L["FontSize"] or "Font size", 8, 24, 1, cfg.fontSize or 11, function(v)
        cfg.fontSize = v
    end, function(v) return format("%d pt", v) end)

    addFontHeader(L["Icon"] or "Icon")
    addCheckbox(L["ShowIcon"] or "Show icon", cfg.showIcon ~= false, function(v) cfg.showIcon = v end, L["ShowIcon_Desc"])

    addFontHeader(L["Fade"] or "Fade")
    addCheckbox(L["FadeEnable"] or "Fade", cfg.fadeEnable ~= false, function(v) cfg.fadeEnable = v end, L["FadeEnable_Desc"])
    addSlider(L["FadeIn"] or "Fade in", 0.05, 1.0, 0.05, cfg.fadeInTime or 0.15, function(v)
        cfg.fadeInTime = v
    end, function(v) return format("%.2f s", v) end)
    addSlider(L["FadeOut"] or "Fade out", 0.05, 2.0, 0.05, cfg.fadeOutTime or 0.3, function(v)
        cfg.fadeOutTime = v
    end, function(v) return format("%.2f s", v) end)

    addFontHeader(L["SuccessSection"] or "Success")
    addCheckbox(L["CastSuccess"] or "Success flash", cfg.castSuccess ~= false, function(v) cfg.castSuccess = v end, L["CastSuccess_Desc"])

    addSlider(L["TimeToHold"] or "Hold time", 0.1, 3.0, 0.1, cfg.timeToHold or 0.5, function(v)
        cfg.timeToHold = v
    end, function(v) return format("%.1f s", v) end)

    if unit == "player" then
        addFontHeader(L["Player"] or "Player")
        addCheckbox(L["ShowLatency"] or "Latency", cfg.latency ~= false, function(v) cfg.latency = v end, L["ShowLatency_Desc"])
    end

    addResetButton(unit)

    layoutOptionsContent()
end

local function positionEditDlgNearAnchor(dlg, anchor)
    if not dlg or not anchor then return end
    dlg:ClearAllPoints()
    local w = (UIParent.GetWidth and UIParent:GetWidth()) or 1920
    local left = anchor:GetLeft()
    local right = anchor:GetRight()
    local mid
    if left and right then
        mid = (left + right) * 0.5
    else
        mid = w * 0.5
    end
    local gap = 16
    if mid > w * 0.5 then
        dlg:SetPoint("TOPRIGHT", anchor, "TOPLEFT", -gap, 0)
    else
        dlg:SetPoint("TOPLEFT", anchor, "TOPRIGHT", gap, 0)
    end
end

local function showOptionsForAnchor(anchor, showState)
    local dlg = ensureOptionsDialog()
    if showState then
        buildOptionsForUnit(anchor.unit)
        dlg.anchorRef = anchor
        positionEditDlgNearAnchor(dlg, anchor)
        dlg:Show()
        dlg:SetFrameLevel(8000)
        dlg:Raise()
        if dlg.CloseButton then
            dlg.CloseButton:SetFrameStrata("DIALOG")
        end
        applyEditOptionsZOrder(dlg)
        C_Timer.After(0, function()
            if dlg and dlg:IsShown() and anchor and anchor:IsVisible() then
                positionEditDlgNearAnchor(dlg, anchor)
                applyEditOptionsZOrder(dlg)
            end
        end)
    else
        if dlg.anchorRef == anchor then
            dlg:Hide()
            dlg.anchorRef = nil
        end
    end
end

-- Wire anchors
local function wireAnchor(anchor)
    if anchor._rcBlizzEditWired then return end
    anchor._rcBlizzEditWired = true

    anchor.ShowAnchorOptions = function(self, state)
        showOptionsForAnchor(self, state)
    end

    function anchor:IsFocused()
        return isMouseFocusOnAnchorEditSubtree(self)
    end

    function anchor:OnAnchorRightClick()
        if rCastbar_OpenConfigUI then
            pcall(rCastbar_OpenConfigUI)
        end
    end

    if not anchor.Selection then
        local title = format(L["EDITMODE_PANEL_TITLE"] or "rCastbar: %s", unitLabel(anchor.unit))
        anchor.Selection = createSelection(anchor, title)
    end
end

rCastbarBlizzEditAfterAnchorsBuilt = function()
    for _, anchor in pairs(rCastbarBlizzEdit.anchors or {}) do
        wireAnchor(anchor)
    end
end

local function showAnchorPresentation(anchor)
    wireAnchor(anchor)
    if Castbar.SetAnchorBlizzardPresentation then
        Castbar:SetAnchorBlizzardPresentation(anchor, true)
    end
    if Castbar.ShowEditModePreviewForUnit then
        Castbar:ShowEditModePreviewForUnit(anchor.unit)
    end
    anchor._rcLayoutMode = nil
    anchor:Show()
    anchor:EnableMouse(true)
    if Castbar.selectedAnchor == anchor then
        anchor.Selection:ShowSelected()
    else
        anchor.Selection:ShowHighlighted()
    end
    anchor._rcBepActive = true
end

-- Tear down edit-mode visuals. Castbars are parented to anchors: the anchor must
-- stay shown (invisible holder) during normal play, or bars disappear globally.
local function hideAnchorPresentation(anchor, opts)
    if anchor then
        anchor._rcBepActive = nil
    end
    opts = opts or {}
    local forceHide = opts.forceHide
    if anchor.ShowAnchorOptions then anchor:ShowAnchorOptions(false) end
    if Castbar.HideEditModePreviewForUnit then
        Castbar:HideEditModePreviewForUnit(anchor.unit)
    end
    if anchor.Selection then
        anchor.Selection:Hide()
    end
    local cfg = profile() and profile()[anchor.unit]
    if forceHide or not (cfg and cfg.enabled) then
        if Castbar.SetAnchorBlizzardPresentation then
            Castbar:SetAnchorBlizzardPresentation(anchor, false)
        end
        anchor:Hide()
    else
        anchor._rcLayoutMode = nil
        if Castbar.SetAnchorBlizzardPresentation then
            Castbar:SetAnchorBlizzardPresentation(anchor, true)
        end
        anchor:Show()
        anchor:EnableMouse(false)
    end
end

-- ---------------------------------------------------------------------------
-- Master checkbox (pattern from Plumber BlizzardEditMode.lua)
-- ---------------------------------------------------------------------------
local checkboxFrame

local function updateCastbarsVisibility()
    if not Castbar or not Castbar.anchors then return false end
    local any = false
    if isBlizzardEditModeActive() and getMasterEnabled() then
        for unit, anchor in pairs(Castbar.anchors) do
            local cfg = profile() and profile()[unit]
            if cfg and cfg.enabled then
                any = true
                showAnchorPresentation(anchor)
            else
                hideAnchorPresentation(anchor, {})
            end
        end
    else
        local forceHide = isBlizzardEditModeActive() and not getMasterEnabled()
        for _, anchor in pairs(Castbar.anchors) do
            hideAnchorPresentation(anchor, { forceHide = forceHide })
        end
    end
    return any
end

local arrowKeyBtn
local function clearArrowKeys()
    if arrowKeyBtn then
        ClearOverrideBindings(arrowKeyBtn)
    end
end

local function setupArrowKeys()
    if not isBlizzardEditModeActive() or not getMasterEnabled() then
        clearArrowKeys()
        return
    end
    if InCombatLockdown() then return end
    if not arrowKeyBtn then
        arrowKeyBtn = CreateFrame("Button", "rCastbarBlizzEditKeyHandler", UIParent)
        arrowKeyBtn:Hide()
        arrowKeyBtn:SetScript("OnClick", function(_, key)
            if not Castbar or not Castbar.NudgeSelected then return end
            local step = IsShiftKeyDown() and 5 or 1
            local dx, dy = 0, 0
            if key == "UP" then dy = step elseif key == "DOWN" then dy = -step
            elseif key == "LEFT" then dx = -step elseif key == "RIGHT" then dx = step
            else return end
            Castbar:NudgeSelected(dx, dy)
        end)
    end
    ClearOverrideBindings(arrowKeyBtn)
    SetOverrideBindingClick(arrowKeyBtn, false, "UP", "rCastbarBlizzEditKeyHandler", "UP")
    SetOverrideBindingClick(arrowKeyBtn, false, "DOWN", "rCastbarBlizzEditKeyHandler", "DOWN")
    SetOverrideBindingClick(arrowKeyBtn, false, "LEFT", "rCastbarBlizzEditKeyHandler", "LEFT")
    SetOverrideBindingClick(arrowKeyBtn, false, "RIGHT", "rCastbarBlizzEditKeyHandler", "RIGHT")
end

function clearAllEditFocus()
    if Castbar and Castbar.ClearAnchorSelection then Castbar:ClearAnchorSelection() end
    if optionsDialog then
        optionsDialog:Hide()
        optionsDialog.anchorRef = nil
    end
    if Castbar and Castbar.anchors then
        for _, a in pairs(Castbar.anchors) do
            if a.Selection and a.Selection:IsShown() then
                a.Selection:ShowHighlighted()
            end
        end
    end
end

local function initCheckboxFrame()
    if checkboxFrame or not EditModeManagerFrame then return end
    local MainFrame = CreateFrame("Frame", "rCastbarEditModeCheckboxHost", UIParent)
    MainFrame:Hide()
    MainFrame.owner = EditModeManagerFrame
    MainFrame:SetFrameStrata("DIALOG")
    MainFrame:SetFrameLevel(EditModeManagerFrame:GetFrameLevel() + 2)

    MainFrame.Border = CreateFrame("Frame", nil, MainFrame, "DialogBorderTranslucentTemplate")
    MainFrame.Border:SetAllPoints(true)

    local cb = CreateFrame("CheckButton", nil, MainFrame, "UICheckButtonTemplate")
    MainFrame.Checkbox = cb
    cb.Text:SetText(L["EDITMODE_MASTER_LABEL"] or "rCastbar")
    cb.Text:SetFontObject("GameFontHighlightMedium")
    cb:SetHitRectInsets(0, -120, 0, 0)
    cb:SetChecked(getMasterEnabled())
    cb:SetScript("OnClick", function(self)
        setMasterEnabled(self:GetChecked())
        updateCastbarsVisibility()
        setupArrowKeys()
    end)
    cb:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(L["EDITMODE_MASTER_HEADER"] or "rCastbar", 1, 0.82, 0)
        local list = tooltipActiveCastbars()
        local body = list and format(L["EDITMODE_MASTER_DESC_FMT"] or "%s", list) or (L["EDITMODE_MASTER_DESC_EMPTY"] or "")
        if body ~= "" then
            GameTooltip:AddLine(body, 1, 1, 1, true)
        end
        GameTooltip:Show()
    end)
    cb:SetScript("OnLeave", function() GameTooltip:Hide() end)

    markRcUi(MainFrame)
    markRcUi(cb)

    local tw = math.max(cb.Text:GetStringWidth() or 70, 48)
    local chk = 28
    local gap = 8
    local total = chk + gap + tw
    local padH = 18
    MainFrame:SetSize(total + padH * 2, 46)
    cb:ClearAllPoints()
    cb:SetPoint("LEFT", MainFrame, "CENTER", -total * 0.5, 0)

    local function checkboxHostOnUpdate(self, elapsed)
        self.t = (self.t or 0) + elapsed
        if self.t < 0.25 then return end
        self.t = 0
        local owner = self.owner
        if not owner then return end
        if owner:IsShown() then
            self:ClearAllPoints()
            local anchorRef = (self.secondaryOwner and self.secondaryOwner:IsShown()) and self.secondaryOwner or owner
            local wf = EditModeExpandedWarningFrame
            local gapY = 6
            local nc = _G["nCastbarEditModeCheckboxHost"]
            if nc and nc:IsShown() then
                self:SetPoint("TOPRIGHT", nc, "BOTTOMRIGHT", 0, -gapY)
            elseif wf and wf:IsShown() then
                self:SetPoint("TOPRIGHT", wf, "BOTTOMLEFT", -4, -4)
            else
                self:SetPoint("TOPRIGHT", anchorRef, "TOPLEFT", -8, -4)
            end
        else
            self:ClearAllPoints()
            self:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", 0, -8)
        end
        if owner:IsShown() then
            updateCastbarsVisibility()
            if isBlizzardEditModeActive() then
                self:Show()
            else
                self:Hide()
            end
        else
            self:Hide()
        end
    end

    MainFrame.t = 0
    MainFrame:SetScript("OnUpdate", checkboxHostOnUpdate)

    function MainFrame:EnterHost()
        if EditModeManagerExpandedFrame then
            self.secondaryOwner = EditModeManagerExpandedFrame
        end
        self.Checkbox:SetChecked(getMasterEnabled())
        self.t = 1
        self:SetScript("OnUpdate", checkboxHostOnUpdate)
        updateCastbarsVisibility()
        setupArrowKeys()
        self:Show()
    end

    function MainFrame:ExitHost()
        self:Hide()
        self:SetScript("OnUpdate", nil)
        for _, anchor in pairs(Castbar and Castbar.anchors or {}) do
            hideAnchorPresentation(anchor)
        end
        if Castbar and Castbar.ClearAnchorSelection then Castbar:ClearAnchorSelection() end
        clearArrowKeys()
        if optionsDialog then
            optionsDialog:Hide()
            optionsDialog.anchorRef = nil
        end
    end

    checkboxFrame = MainFrame
end

local function onEditModeEnter()
    if not checkboxFrame then initCheckboxFrame() end
    if checkboxFrame then checkboxFrame:EnterHost() end
end

local function onEditModeExit()
    if checkboxFrame then checkboxFrame:ExitHost() end
end

function ns.OpenBlizzardHudEditMode()
    if InCombatLockdown() then
        editModeNotify(L["combat_editmode_blocked"] or "Cannot toggle Edit Mode in combat.")
        return false
    end
    local em = EditModeManagerFrame
    if not em then return false end
    setMasterEnabled(true)
    if em.IsEditModeActive and em:IsEditModeActive() then
        onEditModeEnter()
        return true
    end
    -- Do not call EnterEditMode() from addon execution: Blizzard's handler uses
    -- TargetUnit() and triggers ADDON_ACTION_FORBIDDEN when invoked from our UI.
    if ShowUIPanel then
        pcall(ShowUIPanel, em)
    end
    if em.IsShown and not em:IsShown() and em.Show then
        pcall(em.Show, em)
    end
    return true
end

function ns.AreRCastbarEditAnchorsVisible()
    if not Castbar or not Castbar.anchors then return false end
    for _, a in pairs(Castbar.anchors) do
        if a:IsShown() then return true end
    end
    return false
end

if EventRegistry and EventRegistry.RegisterCallback then
    EventRegistry:RegisterCallback("EditMode.Enter", onEditModeEnter, ns)
    EventRegistry:RegisterCallback("EditMode.Exit", onEditModeExit, ns)
end
