local ADDON_NAME, ns = ...

-- unify ns across files
ns = _G[ADDON_NAME] or ns
_G[ADDON_NAME] = ns

ns.config = ns.config or {}
local Cfg = ns.config

-- =============================================================
-- pom_config.lua (DARK APP LOOK) - Backdrop SAFE
-- Tabs:
--  1) General & Options
--  2) Heights & Base Colors
--  3) Threat & Aggro
--  4) PvP
--  5) Specials              (opens /pomspecial)
--  6) Who Is Targeting Me   (opens /castmark)
--  7) Scripts               (SSR inside POM, NO slash)
--
-- EXTRA:
--  - Castbar settings launcher (/pomca) lives in General tab (button).
--
-- NOTES:
--  - "Who Is Targeting Me" is CastMark, and it stores settings in PomTDB.castmark
--    (NO separate SavedVariables needed for this feature).
--  - POM now separates style resolution from base color resolution internally.
--  - This config uses the new bucket names, while pom.lua keeps legacy DB aliases alive.
--
-- FIXES:
--  - Host frame may not support SetBackdrop -> safe ApplyBackdrop support
--  - Hide host CloseButton to prevent double X
--  - General tab: improved layout (right column spacing + slider label/value placement)
--  - Restores UI toggle for role-based DPS/Healer base plate colors
-- =============================================================

local CreateFrame = CreateFrame
local UIParent = UIParent
local StaticPopup_Show = StaticPopup_Show
local StaticPopupDialogs = StaticPopupDialogs
local InCombatLockdown = InCombatLockdown

local ipairs = ipairs
local pairs = pairs
local tostring = tostring
local tonumber = tonumber
local math_min = math.min
local string_format = string.format
local math_max = math.max

-- =============================================================
-- DB
-- =============================================================
local function EnsureDB()
    _G.PlateOMaticDB = _G.PlateOMaticDB or {}
    return _G.PlateOMaticDB
end

local function ApplyAll(applyAll)
    if applyAll then applyAll() end
end

local function DBBool(key, default)
    local db = EnsureDB()
    if db[key] == nil then return default and true or false end
    return db[key] and true or false
end

local function SetDBBool(key, v)
    local db = EnsureDB()
    db[key] = v and true or false
end

local function DBBoolWithLegacy(modernKey, legacyKey, default)
    local db = EnsureDB()
    if db[modernKey] ~= nil then
        return db[modernKey] and true or false
    end
    if legacyKey and db[legacyKey] ~= nil then
        return db[legacyKey] and true or false
    end
    return default and true or false
end

local function SetDBBoolWithLegacy(modernKey, legacyKey, v)
    local db = EnsureDB()
    local b = v and true or false
    db[modernKey] = b
    if legacyKey then
        db[legacyKey] = b
    end
end

local function DBNum(key, default)
    local db = EnsureDB()
    local v = db[key]
    if v == nil then return default end
    return tonumber(v) or default
end

local function SetDBNum(key, v)
    local db = EnsureDB()
    db[key] = tonumber(v) or db[key]
end

local function ThreatDB()
    local db = EnsureDB()
    db.threat = db.threat or {}
    return db.threat
end

-- =============================================================
-- Theme
-- =============================================================
local THEME = {
    accent = { 1.00, 0.82, 0.00, 1.00 },
    bg     = { 0.06, 0.06, 0.06, 0.98 },
    topbar = { 0.10, 0.10, 0.10, 1.00 },

    sidebarTexAlpha = 0.22,
    contentTexAlpha = 0.18,

    btn      = { 0.12, 0.13, 0.14, 1.00 },
    btnHover = { 0.18, 0.19, 0.20, 1.00 },
    btnSel   = { 0.20, 0.21, 0.22, 1.00 },

    inputBg  = { 0.00, 0.00, 0.00, 0.50 },
    border   = { 1.00, 1.00, 1.00, 0.14 },
}

-- =============================================================
-- Backdrop SAFE (supports SetBackdrop and ApplyBackdrop)
-- =============================================================
local function ApplyBackdropSafe(frame, backdrop, bgRGBA, borderRGBA)
    if not frame then return end

    if frame.SetBackdrop then
        frame:SetBackdrop(backdrop)
        if bgRGBA and frame.SetBackdropColor then
            frame:SetBackdropColor(bgRGBA[1], bgRGBA[2], bgRGBA[3], bgRGBA[4] or 1)
        end
        if borderRGBA and frame.SetBackdropBorderColor then
            frame:SetBackdropBorderColor(borderRGBA[1], borderRGBA[2], borderRGBA[3], borderRGBA[4] or 1)
        end
        return
    end

    if frame.ApplyBackdrop then
        frame.backdropInfo = {
            bgFile   = backdrop.bgFile,
            edgeFile = backdrop.edgeFile,
            tile     = backdrop.tile,
            tileSize = backdrop.tileSize,
            edgeSize = backdrop.edgeSize,
            insets   = backdrop.insets,
        }
        frame:ApplyBackdrop()
        if bgRGBA and frame.SetBackdropColor then
            frame:SetBackdropColor(bgRGBA[1], bgRGBA[2], bgRGBA[3], bgRGBA[4] or 1)
        end
        if borderRGBA and frame.SetBackdropBorderColor then
            frame:SetBackdropBorderColor(borderRGBA[1], borderRGBA[2], borderRGBA[3], borderRGBA[4] or 1)
        end
        return
    end
end

local function SetSolidBackdrop(frame, bgRGBA, edgeSize, borderRGBA)
    ApplyBackdropSafe(frame, {
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        tile     = false,
        tileSize = 0,
        edgeSize = edgeSize or 1,
        insets   = { left=0, right=0, top=0, bottom=0 },
    }, bgRGBA or {0,0,0,1}, borderRGBA or {0,0,0,1})
end

local function AddSoftTexture(parent, texturePath, alpha)
    if not parent then return nil end
    local tex = parent:CreateTexture(nil, "BACKGROUND")
    tex:SetAllPoints(parent)
    tex:SetTexture(texturePath)
    tex:SetAlpha(alpha or 0.2)
    return tex
end

local function HideBlizzardRegions(frame)
    if not frame then return end

    local hide = {
        "Bg", "TitleBg", "DialogBG", "DialogBorder", "TopTileStreaks",
        "TopLeftCorner", "TopRightCorner", "BottomLeftCorner", "BottomRightCorner",
        "LeftBorder", "RightBorder", "TopBorder", "BottomBorder",
        "InsetBg", "Inset", "NineSlice",
    }
    for _, k in ipairs(hide) do
        local r = frame[k]
        if r and r.Hide then r:Hide() end
    end

    if frame.GetRegions then
        local regions = { frame:GetRegions() }
        for i = 1, #regions do
            local r = regions[i]
            if r and r.GetObjectType and r:GetObjectType() == "Texture" then
                r:SetAlpha(0)
            end
        end
    end
end

local function HideHostCloseButtons(frame)
    if not frame then return end

    local candidates = {
        frame.CloseButton,
        frame.closeButton,
        frame.Close,
        frame.close,
        frame.closeBtn,
        frame.CloseBtn,
    }

    if frame.TitleContainer then
        candidates[#candidates+1] = frame.TitleContainer.CloseButton
        candidates[#candidates+1] = frame.TitleContainer.closeButton
    end

    for _, b in ipairs(candidates) do
        if b and b.Hide then
            b:Hide()
            if b.SetShown then b:SetShown(false) end
            if b.EnableMouse then b:EnableMouse(false) end
        end
    end
end

local function SkinHostFrame(frame)
    HideBlizzardRegions(frame)
    HideHostCloseButtons(frame)
    if frame.SetBackdrop then frame:SetBackdrop(nil) end
    SetSolidBackdrop(frame, THEME.bg, 1, THEME.border)
end

-- =============================================================
-- Fonts
-- =============================================================
local AVAILABLE_FONTS = {
    { name = "Default (Friz Quadrata)", path = _G.STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF" },
    { name = "Friz Quadrata",           path = "Fonts\\FRIZQT__.TTF" },
    { name = "Arial Narrow",            path = "Fonts\\ARIALN.TTF" },
    { name = "Skurri",                  path = "Fonts\\skurri.ttf" },
    { name = "Morpheus",                path = "Fonts\\MORPHEUS.ttf" },
    { name = "2002",                    path = "Fonts\\2002.ttf" },
}

-- =========================================================
-- Popup: Skins
-- =========================================================
local function ShowSkinList(anchor, skins, currentID, onPick)
    if Cfg._skinList and Cfg._skinList:IsShown() then
        Cfg._skinList:Hide()
    end

    if not Cfg._skinList then
        local f = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
        f:SetFrameStrata("DIALOG")
        SetSolidBackdrop(f, {0,0,0,0.95}, 1, {0,0,0,1})
        f.buttons = {}
        Cfg._skinList = f
    end

    local f = Cfg._skinList
    f:ClearAllPoints()
    f:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -2)

    for i = 1, #f.buttons do
        f.buttons[i]:Hide()
    end

    local btnH = 18
    local w = 260
    local maxShown = 30
    local count = math_min(#skins, maxShown)
    f:SetSize(w, count * btnH + 8)

    for i = 1, count do
        local skin = skins[i]
        local b = f.buttons[i]
        if not b then
            b = CreateFrame("Button", nil, f)
            b:SetSize(w - 8, btnH)
            b:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -4 - (i-1) * btnH)

            local t = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            t:SetPoint("LEFT", b, "LEFT", 4, 0)
            t:SetJustifyH("LEFT")
            b.text = t

            b:SetScript("OnEnter", function(self) self.text:SetTextColor(1, 0.82, 0) end)
            b:SetScript("OnLeave", function(self) self.text:SetTextColor(1, 1, 1) end)

            f.buttons[i] = b
        end

        b.text:SetText(((i == currentID) and "|cff00ff00•|r " or "  ") .. (skin.name or ("Skin " .. i)))
        b:SetScript("OnClick", function()
            f:Hide()
            if onPick then onPick(i) end
        end)

        b:Show()
    end

    f:Show()
end

-- =========================================================
-- Popup: Fonts
-- =========================================================
local function ShowFontList(anchor, fonts, currentPath, onPick)
    if Cfg._fontList and Cfg._fontList:IsShown() then
        Cfg._fontList:Hide()
    end

    if not Cfg._fontList then
        local f = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
        f:SetFrameStrata("DIALOG")
        SetSolidBackdrop(f, {0,0,0,0.95}, 1, {0,0,0,1})
        f.buttons = {}
        Cfg._fontList = f
    end

    local f = Cfg._fontList
    f:ClearAllPoints()
    f:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -2)

    for i = 1, #f.buttons do
        f.buttons[i]:Hide()
    end

    local btnH = 18
    local w = 260
    local maxShown = 18
    local count = math_min(#fonts, maxShown)
    f:SetSize(w, count * btnH + 8)

    for i = 1, count do
        local font = fonts[i]
        local b = f.buttons[i]
        if not b then
            b = CreateFrame("Button", nil, f)
            b:SetSize(w - 8, btnH)
            b:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -4 - (i-1) * btnH)

            local t = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            t:SetPoint("LEFT", b, "LEFT", 4, 0)
            t:SetJustifyH("LEFT")
            b.text = t

            b:SetScript("OnEnter", function(self) self.text:SetTextColor(1, 0.82, 0) end)
            b:SetScript("OnLeave", function(self) self.text:SetTextColor(1, 1, 1) end)

            f.buttons[i] = b
        end

        local isCurrent = (font.path == currentPath)
        b.text:SetText((isCurrent and "|cff00ff00•|r " or "  ") .. (font.name or ("Font " .. i)))
        b:SetScript("OnClick", function()
            f:Hide()
            if onPick then onPick(font) end
        end)

        b:Show()
    end

    f:Show()
end

-- =============================================================
-- Slash open helpers
-- =============================================================
local function RunSlash(cmd)
    if _G.ChatEdit_ExecuteCommand then
        ChatEdit_ExecuteCommand(cmd)
        return
    end
    local eb = _G.ChatFrame1EditBox or (_G.ChatFrame1 and _G.ChatFrame1.editBox)
    if eb and eb.SetText and _G.ChatEdit_SendText then
        eb:SetText(cmd)
        ChatEdit_SendText(eb, 0)
    end
end

local function ToggleSpecials()
    if _G.SlashCmdList and _G.SlashCmdList.POMSPECIAL then
        _G.SlashCmdList.POMSPECIAL("")
        return
    end
    RunSlash("/pomspecial")
end

local function ToggleCastMark()
    if _G.SlashCmdList and _G.SlashCmdList.ROBUICASTMARK then
        _G.SlashCmdList.ROBUICASTMARK("")
        return
    end
    RunSlash("/castmark")
end

local function ToggleCastBarSettings()
    if _G.SlashCmdList and _G.SlashCmdList.POMCA then
        _G.SlashCmdList.POMCA("")
        return
    end
    RunSlash("/pomca")
end

local function TogglePvP()
    RunSlash("/pvppom")
end

-- =============================================================
-- UI helpers
-- =============================================================
local function MakeHeaderText(parent, text, x, y)
    local t = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
    t:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    t:SetText(text or "")
    return t
end

local function MakeSectionText(parent, text, x, y)
    local t = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    t:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    t:SetText(text or "")
    t:SetTextColor(THEME.accent[1], THEME.accent[2], THEME.accent[3], 1)
    return t
end

local function MakeInfoText(parent, text, x, y, width)
    local t = parent:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    t:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    t:SetWidth(width or 640)
    t:SetJustifyH("LEFT")
    t:SetText(text or "")
    return t
end

local function MakeCheckbox(parent, label, x, y, getFunc, setFunc, applyAll)
    local cb = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    cb:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    cb:SetSize(24, 24)

    local txt = cb:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    txt:SetPoint("LEFT", cb, "RIGHT", 8, 0)
    txt:SetText(label or "")

    local function Refresh()
        local v = false
        if getFunc then v = getFunc() and true or false end
        cb:SetChecked(v)
    end

    cb:SetScript("OnClick", function(self)
        if setFunc then setFunc(self:GetChecked() and true or false) end
        ApplyAll(applyAll)
        Refresh()
    end)

    Refresh()
    cb._refresh = Refresh
    return cb
end

local function MakeButton(parent, label, x, y, w, h, onClick)
    local b = CreateFrame("Button", nil, parent, "BackdropTemplate")
    b:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    b:SetSize(w or 260, h or 24)
    SetSolidBackdrop(b, THEME.btn, 1, THEME.border)

    local t = b:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    t:SetPoint("CENTER", b, "CENTER", 0, 0)
    t:SetText(label or "Button")
    t:SetTextColor(1, 1, 1, 1)

    b:SetScript("OnEnter", function(self)
        if self._selected then return end
        if self.SetBackdropColor then
            self:SetBackdropColor(THEME.btnHover[1], THEME.btnHover[2], THEME.btnHover[3], THEME.btnHover[4])
        end
    end)
    b:SetScript("OnLeave", function(self)
        if self._selected then return end
        if self.SetBackdropColor then
            self:SetBackdropColor(THEME.btn[1], THEME.btn[2], THEME.btn[3], THEME.btn[4])
        end
    end)
    b:SetScript("OnClick", function()
        if onClick then onClick() end
    end)

    return b
end

local _sliderId = 0
local function NextSliderName()
    _sliderId = _sliderId + 1
    return "POM_DarkUI_Slider_" .. tostring(_sliderId)
end

local function MakeSlider(parent, label, x, y, minVal, maxVal, step, getFunc, setFunc, applyAll, decimals)
    local name = NextSliderName()
    local s = CreateFrame("Slider", name, parent, "OptionsSliderTemplate")
    s:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    s:SetMinMaxValues(minVal, maxVal)
    s:SetValueStep(step or 0.1)
    s:SetObeyStepOnDrag(true)
    s:SetWidth(300)

    local low  = _G[name .. "Low"]
    local high = _G[name .. "High"]
    local text = _G[name .. "Text"]

    if low then low:SetText(tostring(minVal)) end
    if high then high:SetText(tostring(maxVal)) end
    if text then
        text:SetText(label or "")
        text:ClearAllPoints()
        text:SetPoint("TOP", s, "TOP", 0, 18)
    end

    local val = s:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    val:SetPoint("LEFT", s, "RIGHT", 10, 0)
    val:SetText("")
    s._val = val

    local function fmt(v)
        if decimals and decimals > 0 then
            return string_format("%." .. tostring(decimals) .. "f", v)
        end
        return string_format("%.0f", v)
    end

    local function Refresh()
        if not getFunc then return end
        local v = tonumber(getFunc())
        if v then
            s:SetValue(v)
            if s._val then s._val:SetText(fmt(v)) end
        end
    end

    s:SetScript("OnValueChanged", function(self, v)
        if setFunc then setFunc(v) end
        if self._val then self._val:SetText(fmt(v)) end
        ApplyAll(applyAll)
    end)

    Refresh()
    s._refresh = Refresh
    return s
end

local function MakeFontButton(parent, fonts, x, y, applyAll)
    local btn = MakeButton(parent, "Font: ...", x, y, 260, 20, nil)

    local function FontNameFromPath(p)
        for _, fnt in ipairs(fonts) do
            if fnt.path == p then return fnt.name end
        end
        return "Custom"
    end

    local function refresh()
        local db = EnsureDB()
        local label = "Font: " .. FontNameFromPath(db.fontPath)

        local regions = { btn:GetRegions() }
        for i=1,#regions do
            local r = regions[i]
            if r and r.GetObjectType and r:GetObjectType() == "FontString" then
                r:SetText(label)
                break
            end
        end
    end

    btn:SetScript("OnClick", function()
        local db = EnsureDB()
        ShowFontList(btn, fonts, db.fontPath, function(pick)
            EnsureDB().fontPath = pick.path
            refresh()
            ApplyAll(applyAll)
        end)
    end)

    refresh()
    btn._refresh = refresh
    return btn
end

-- =============================================================
-- SSR (Scripts) inside POM
-- =============================================================
local function SSR_Ensure(db)
    db.ssr = type(db.ssr) == "table" and db.ssr or {}
    db.ssr.scripts = type(db.ssr.scripts) == "table" and db.ssr.scripts or {}
    db.ssr.autorun = type(db.ssr.autorun) == "table" and db.ssr.autorun or {}
end

local function SSR_Run(name, code)
    local func, err = loadstring(code or "")
    if func then
        local ok, runErr = pcall(func)
        if not ok then
            print("|cffff0000[POM Scripts] Runtime Error in " .. (name or "Unsaved") .. ":|r", runErr)
        else
            print("|cff00ff00[POM Scripts]|r Ran:", name or "Unsaved")
        end
    else
        print("|cffff0000[POM Scripts] Syntax Error in " .. (name or "Unsaved") .. ":|r", err)
    end
end

-- =============================================================
-- Build
-- =============================================================
function Cfg:Build(frame, skins, applyAll)
    EnsureDB()

    if frame._pomDarkBuilt then
        if frame._pomDarkRefreshers then
            for _, fn in ipairs(frame._pomDarkRefreshers) do
                if fn then fn() end
            end
        end
        return
    end

    frame._pomDarkBuilt = true
    frame._pomDarkRefreshers = {}

    SkinHostFrame(frame)

    frame:SetSize(1090, 640)

    local titleBar = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    titleBar:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
    titleBar:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
    titleBar:SetHeight(34)
    SetSolidBackdrop(titleBar, THEME.topbar, 1, THEME.border)

    local title = titleBar:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    title:SetPoint("LEFT", titleBar, "LEFT", 14, 0)
    title:SetText("Plate-o-Matic - Configuration")

    local close = CreateFrame("Button", nil, titleBar, "UIPanelCloseButton")
    close:SetPoint("RIGHT", titleBar, "RIGHT", -2, 0)
    close:SetScript("OnClick", function() frame:Hide() end)

    local sidebar = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    sidebar:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, -34)
    sidebar:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 0)
    sidebar:SetWidth(240)
    SetSolidBackdrop(sidebar, {0.05, 0.05, 0.05, 1}, 1, THEME.border)
    AddSoftTexture(sidebar, "Interface\\FrameGeneral\\UI-Background-Rock", THEME.sidebarTexAlpha)

    local content = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    content:SetPoint("TOPLEFT", sidebar, "TOPRIGHT", 0, 0)
    content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
    SetSolidBackdrop(content, {0.04, 0.04, 0.04, 1}, 1, THEME.border)
    AddSoftTexture(content, "Interface\\FrameGeneral\\UI-Background-Marble", THEME.contentTexAlpha)

    local contentPad = CreateFrame("Frame", nil, content)
    contentPad:SetPoint("TOPLEFT", content, "TOPLEFT", 18, -18)
    contentPad:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -18, 18)

    local panels = {}
    local navButtons = {}

    local function ShowPanel(idx)
        for i, p in ipairs(panels) do
            if i == idx then p:Show() else p:Hide() end
        end
        for i, b in ipairs(navButtons) do
            if i == idx then
                b._selected = true
                b:SetBackdropColor(THEME.btnSel[1], THEME.btnSel[2], THEME.btnSel[3], THEME.btnSel[4])
                if b._bar then b._bar:Show() end
            else
                b._selected = false
                b:SetBackdropColor(THEME.btn[1], THEME.btn[2], THEME.btn[3], THEME.btn[4])
                if b._bar then b._bar:Hide() end
            end
        end
    end

    local function NewPanel()
        local p = CreateFrame("Frame", nil, contentPad)
        p:SetAllPoints(contentPad)
        p:Hide()
        panels[#panels + 1] = p
        return p, #panels
    end

    local function MakeNavButton(text, idx)
        local b = CreateFrame("Button", nil, sidebar, "BackdropTemplate")
        b:SetSize(220, 34)
        b:SetPoint("TOPLEFT", sidebar, "TOPLEFT", 10, -(12 + (idx - 1) * 40))
        SetSolidBackdrop(b, THEME.btn, 1, THEME.border)

        local bar = b:CreateTexture(nil, "OVERLAY")
        bar:SetPoint("TOPLEFT", b, "TOPLEFT", 0, 0)
        bar:SetPoint("BOTTOMLEFT", b, "BOTTOMLEFT", 0, 0)
        bar:SetWidth(3)
        bar:SetColorTexture(THEME.accent[1], THEME.accent[2], THEME.accent[3], THEME.accent[4])
        bar:Hide()
        b._bar = bar

        local t = b:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        t:SetPoint("LEFT", b, "LEFT", 12, 0)
        t:SetText(text)
        t:SetTextColor(THEME.accent[1], THEME.accent[2], THEME.accent[3], 1)

        b:SetScript("OnEnter", function(self)
            if self._selected then return end
            self:SetBackdropColor(THEME.btnHover[1], THEME.btnHover[2], THEME.btnHover[3], THEME.btnHover[4])
        end)
        b:SetScript("OnLeave", function(self)
            if self._selected then return end
            self:SetBackdropColor(THEME.btn[1], THEME.btn[2], THEME.btn[3], THEME.btn[4])
        end)
        b:SetScript("OnClick", function() ShowPanel(idx) end)

        navButtons[idx] = b
        return b
    end

    -- =========================================================
    -- Panel 1: General & Options
    -- =========================================================
    local p1, i1 = NewPanel()
    MakeHeaderText(p1, "General & Options", 0, 0)

    local Lx = 0
    local Rx = 500
    local RowW = 440

    MakeSectionText(p1, "Skins (Style Buckets)", Lx, -34)
    MakeInfoText(
        p1,
        "Skins now follow style buckets internally. These use the new bucket names while legacy aliases are still preserved in the database.",
        Lx,
        -58,
        430
    )

    local function MakeSkinPicker(parent, label, typeKey, x, y)
        local row = CreateFrame("Frame", nil, parent)
        row:SetSize(RowW, 22)
        row:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)

        local txt = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        txt:SetPoint("LEFT", row, "LEFT", 0, 0)
        txt:SetText(label)

        local btn = MakeButton(row, "Skin", 0, 0, 300, 20, nil)
        btn:ClearAllPoints()
        btn:SetPoint("RIGHT", row, "RIGHT", 0, 0)

        local function refresh()
            local db = EnsureDB()
            db.skins = db.skins or {}
            local id = db.skins[typeKey] or 1
            local s = skins and skins[id]
            local labelText = s and s.name or ("Skin " .. tostring(id))
            local regions = { btn:GetRegions() }
            for i=1,#regions do
                local r = regions[i]
                if r and r.GetObjectType and r:GetObjectType() == "FontString" then
                    r:SetText(labelText)
                    break
                end
            end
        end

        btn:SetScript("OnClick", function()
            local db = EnsureDB()
            db.skins = db.skins or {}
            local currentID = db.skins[typeKey] or 1
            ShowSkinList(btn, skins or {}, currentID, function(id)
                db.skins[typeKey] = id
                refresh()
                ApplyAll(applyAll)
            end)
        end)

        refresh()
        row._refresh = refresh
        frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = refresh
        return row
    end

    MakeSkinPicker(p1, "Normal",             "normal",      Lx, -92)
    MakeSkinPicker(p1, "Boss / Highlevel",   "boss",        Lx, -120)
    MakeSkinPicker(p1, "Mini boss",          "plusOne",     Lx, -148)
    MakeSkinPicker(p1, "Elite Caster Style", "eliteCaster", Lx, -176)
    MakeSkinPicker(p1, "Elite Melee Style",  "eliteMelee",  Lx, -204)

    MakeSectionText(p1, "Launchers", Lx, -250)
    MakeButton(p1, "Open Castbar Settings (/pomca)", Lx, -278, 400, 24, function()
        ToggleCastBarSettings()
    end)

    MakeSectionText(p1, "Text & UI", Rx, -34)

    local fontBtn = MakeFontButton(p1, AVAILABLE_FONTS, Rx, -62, applyAll)
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = fontBtn._refresh

    local outlineBtn = MakeButton(p1, "Outline: OUTLINE", Rx, -92, 260, 20, nil)

    local function refreshOutline()
        local db = EnsureDB()
        local v = db.fontOutline or "OUTLINE"
        local regions = { outlineBtn:GetRegions() }
        for i=1,#regions do
            local r = regions[i]
            if r and r.GetObjectType and r:GetObjectType() == "FontString" then
                r:SetText("Outline: " .. v)
                break
            end
        end
    end

    outlineBtn:SetScript("OnClick", function()
        local db = EnsureDB()
        local v = db.fontOutline or "OUTLINE"
        local nxt = (v == "NONE") and "OUTLINE" or (v == "OUTLINE") and "THICKOUTLINE" or "NONE"
        db.fontOutline = nxt
        refreshOutline()
        ApplyAll(applyAll)
    end)
    refreshOutline()
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = refreshOutline

    local fontSizeSlider = MakeSlider(p1, "Font Size", Rx, -132, 6, 20, 1,
        function() return DBNum("fontSize", 10) end,
        function(v) SetDBNum("fontSize", v) end,
        applyAll,
        0
    )
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = fontSizeSlider._refresh

    MakeSectionText(p1, "Options", Rx, -210)

    local g1 = MakeCheckbox(p1, "Hide Elite Icon", Rx, -238,
        function() return DBBool("hideClassIcon", true) end,
        function(v) SetDBBool("hideClassIcon", v) end,
        applyAll
    )
    local g2 = MakeCheckbox(p1, "Show Target Arrows", Rx, -268,
        function() return DBBool("showTargetArrow", true) end,
        function(v) SetDBBool("showTargetArrow", v) end,
        applyAll
    )
    local g3 = MakeCheckbox(p1, "Invert Arrows", Rx, -298,
        function() return DBBool("arrowPointingOut", false) end,
        function(v) SetDBBool("arrowPointingOut", v) end,
        applyAll
    )
    local g4 = MakeCheckbox(p1, "Show Quest Icon", Rx, -328,
        function() return DBBool("showQuestIcon", true) end,
        function(v) SetDBBool("showQuestIcon", v) end,
        applyAll
    )
    local g5 = MakeCheckbox(p1, "Show Quest Progress", Rx, -358,
        function() return DBBool("showQuestProg", true) end,
        function(v) SetDBBool("showQuestProg", v) end,
        applyAll
    )
    local g6 = MakeCheckbox(p1, "Disable DPS/Healer Base Color Fallback", Rx, -388,
        function() return not DBBoolWithLegacy("useRoleBaseColors", "alwaysShowRoleBaseColors", true) end,
        function(v) SetDBBoolWithLegacy("useRoleBaseColors", "alwaysShowRoleBaseColors", not v) end,
        applyAll
    )

    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = g1._refresh
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = g2._refresh
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = g3._refresh
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = g4._refresh
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = g5._refresh
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = g6._refresh

    MakeSectionText(p1, "Scaling", Rx, -444)

    local gs = MakeSlider(p1, "Global Scale", Rx, -472, 0.5, 2.0, 0.05,
        function() return DBNum("globalScale", 1.0) end,
        function(v) SetDBNum("globalScale", v) end,
        applyAll,
        2
    )
    local ts = MakeSlider(p1, "Target Scale", Rx, -542, 0.5, 2.0, 0.05,
        function() return DBNum("targetScale", 1.15) end,
        function(v) SetDBNum("targetScale", v) end,
        applyAll,
        2
    )
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = gs._refresh
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = ts._refresh

    -- =========================================================
    -- Panel 2: Heights & Base Colors
    -- =========================================================
    local p2, i2 = NewPanel()
    MakeHeaderText(p2, "Heights & Base Colors", 0, 0)

    MakeSectionText(p2, "Heights (Style-based)", 0, -34)
    MakeInfoText(
        p2,
        "Heights now follow style resolution. Use this to control bar height for the style groups: normal, +1, boss/highlevel, elite caster and elite melee.",
        0,
        -60,
        700
    )
    MakeButton(p2, "Open Heights UI", 0, -98, 320, 24, function()
        if ns.heights and ns.heights.ToggleFrame then
            ns.heights:ToggleFrame(function() ApplyAll(applyAll) end)
        end
    end)

    MakeSectionText(p2, "Base Colors (Non-threat)", 0, -158)
    MakeInfoText(
        p2,
        "Base colors are edited separately from threat colors. Internally POM now separates style from base color, and this UI uses the new bucket names while keeping old database aliases alive.",
        0,
        -184,
        720
    )
    MakeButton(p2, "Open Base Colors UI", 0, -222, 320, 24, function()
        if ns.BaseColorsUI and ns.BaseColorsUI.ToggleFrame then
            ns.BaseColorsUI:ToggleFrame(function() ApplyAll(applyAll) end)
        elseif ns.basecolors_ui and ns.basecolors_ui.Toggle then
            ns.basecolors_ui:Toggle(function() ApplyAll(applyAll) end)
        end
    end)

    MakeSectionText(p2, "Role Color Fallback", 0, -282)
    MakeInfoText(
        p2,
        "By default, DPS and Healer characters keep their selected base plate colors as the fallback state. Gray/tapped plates and threat colors still override this. Tanks are controlled by the threat system, not by this option.",
        0,
        -308,
        740
    )

    local h1 = MakeCheckbox(p2, "Disable DPS/Healer Base Color Fallback", 0, -346,
        function() return not DBBoolWithLegacy("useRoleBaseColors", "alwaysShowRoleBaseColors", true) end,
        function(v) SetDBBoolWithLegacy("useRoleBaseColors", "alwaysShowRoleBaseColors", not v) end,
        applyAll
    )

    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = h1._refresh

    -- =========================================================
    -- Panel 3: Threat & Aggro
    -- =========================================================
    local p3, i3 = NewPanel()
    MakeHeaderText(p3, "Threat & Aggro", 0, 0)

    MakeSectionText(p3, "Threat (quick toggles)", 0, -34)
    local t1 = MakeCheckbox(p3, "Enable Threat System", 0, -60,
        function()
            local tdb = ThreatDB()
            if tdb.enabled == nil then return true end
            return tdb.enabled and true or false
        end,
        function(v) ThreatDB().enabled = v and true or false end,
        applyAll
    )

    local t2 = MakeCheckbox(p3, "Color Bar By Threat", 0, -90,
        function()
            local tdb = ThreatDB()
            if tdb.colorBarByThreat == nil then return true end
            return tdb.colorBarByThreat and true or false
        end,
        function(v) ThreatDB().colorBarByThreat = v and true or false end,
        applyAll
    )

    local t3 = MakeCheckbox(p3, "Tank Mode Enabled", 0, -120,
        function()
            local tdb = ThreatDB()
            if tdb.tankModeEnabled == nil then return true end
            return tdb.tankModeEnabled and true or false
        end,
        function(v) ThreatDB().tankModeEnabled = v and true or false end,
        applyAll
    )

    MakeButton(p3, "Open Threat UI", 0, -170, 320, 24, function()
        if ns.ThreatUI and ns.ThreatUI.ToggleFrame then
            ns.ThreatUI:ToggleFrame(function() ApplyAll(applyAll) end)
        end
    end)

    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = t1._refresh
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = t2._refresh
    frame._pomDarkRefreshers[#frame._pomDarkRefreshers+1] = t3._refresh

    -- =========================================================
    -- Panel 4: PvP
    -- =========================================================
    local p4, i4 = NewPanel()
    MakeHeaderText(p4, "PvP", 0, 0)

    MakeSectionText(p4, "PvP Module", 0, -34)
    MakeButton(p4, "Open PvP POM (/pvppom)", 0, -60, 320, 24, function()
        TogglePvP()
    end)

    -- =========================================================
    -- Panel 5: Specials
    -- =========================================================
    local p5, i5 = NewPanel()
    MakeHeaderText(p5, "Specials", 0, 0)

    MakeSectionText(p5, "Special Warnings", 0, -34)
    MakeButton(p5, "Open Specials (/pomspecial)", 0, -60, 340, 24, function()
        ToggleSpecials()
    end)

    -- =========================================================
    -- Panel 6: Who Is Targeting Me (CastMark)
    -- =========================================================
    local p6, i6 = NewPanel()
    MakeHeaderText(p6, "Who Is Targeting Me", 0, 0)

    MakeSectionText(p6, "CastMark (Casting on YOU marker)", 0, -34)
    MakeButton(p6, "Open Who Is Targeting Me (/castmark)", 0, -60, 380, 24, function()
        ToggleCastMark()
    end)

    MakeSectionText(p6, "Info", 0, -110)
    local info = p6:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    info:SetPoint("TOPLEFT", p6, "TOPLEFT", 0, -136)
    info:SetWidth(640)
    info:SetJustifyH("LEFT")
    info:SetText("This feature stores settings in PomTDB.castmark.\nIt does not need its own SavedVariables entry.")

    -- =========================================================
    -- Panel 7: Scripts (SSR)
    -- =========================================================
    local p7, i7 = NewPanel()
    MakeHeaderText(p7, "Scripts", 0, 0)

    local db = EnsureDB()
    SSR_Ensure(db)
    db.ssr.last = db.ssr.last or nil

    MakeSectionText(p7, "Saved Scripts", 0, -34)

    local listBox = CreateFrame("Frame", nil, p7, "BackdropTemplate")
    listBox:SetPoint("TOPLEFT", p7, "TOPLEFT", 0, -62)
    listBox:SetSize(220, 470)
    SetSolidBackdrop(listBox, {0,0,0,0.25}, 1, THEME.border)

    local listScroll = CreateFrame("ScrollFrame", nil, listBox, "UIPanelScrollFrameTemplate")
    listScroll:SetPoint("TOPLEFT", listBox, "TOPLEFT", 6, -6)
    listScroll:SetPoint("BOTTOMRIGHT", listBox, "BOTTOMRIGHT", -28, 6)

    local listContent = CreateFrame("Frame", nil, listScroll)
    listContent:SetSize(1, 1)
    listScroll:SetScrollChild(listContent)

    local editBoxFrame = CreateFrame("Frame", nil, p7, "BackdropTemplate")
    editBoxFrame:SetPoint("TOPLEFT", listBox, "TOPRIGHT", 18, 0)
    editBoxFrame:SetPoint("BOTTOMRIGHT", p7, "BOTTOMRIGHT", 0, 0)
    SetSolidBackdrop(editBoxFrame, {0,0,0,0.25}, 1, THEME.border)

    local editScroll = CreateFrame("ScrollFrame", nil, editBoxFrame, "UIPanelScrollFrameTemplate")
    editScroll:SetPoint("TOPLEFT", editBoxFrame, "TOPLEFT", 10, -36)
    editScroll:SetPoint("BOTTOMRIGHT", editBoxFrame, "BOTTOMRIGHT", -30, 54)

    local editBox = CreateFrame("EditBox", nil, editScroll)
    editBox:SetMultiLine(true)
    editBox:SetMaxLetters(999999)
    editBox:SetFontObject("ChatFontNormal")
    editBox:SetAutoFocus(false)
    editBox:SetWidth(650)
    editBox:SetText("")
    editScroll:SetScrollChild(editBox)

    local hint = editBoxFrame:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    hint:SetPoint("TOPLEFT", editBoxFrame, "TOPLEFT", 10, -12)
    hint:SetText("Write Lua code here. Use at your own risk. This runs in the UI thread.")

    local currentName = db.ssr.last
    local listButtons = {}

    local autoRunCheck = CreateFrame("CheckButton", nil, editBoxFrame, "UICheckButtonTemplate")
    autoRunCheck:SetSize(26, 26)
    autoRunCheck:SetPoint("BOTTOMLEFT", editBoxFrame, "BOTTOMLEFT", 10, 18)
    local autoTxt = autoRunCheck:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    autoTxt:SetPoint("LEFT", autoRunCheck, "RIGHT", 6, 0)
    autoTxt:SetText("Auto-Run on Login")

    local function RefreshAuto()
        if not currentName then
            autoRunCheck:SetChecked(false)
            return
        end
        autoRunCheck:SetChecked(db.ssr.autorun[currentName] and true or false)
    end

    autoRunCheck:SetScript("OnClick", function(selfBtn)
        if not currentName then
            selfBtn:SetChecked(false)
            return
        end
        db.ssr.autorun[currentName] = selfBtn:GetChecked() and true or false
    end)

    local function SelectScript(name)
        currentName = name
        db.ssr.last = name
        editBox:SetText(db.ssr.scripts[name] or "")
        RefreshAuto()
    end

    local function UpdateList()
        for i=1,#listButtons do
            listButtons[i]:Hide()
        end

        local idx = 1
        local y = 0

        for name,_ in pairs(db.ssr.scripts) do
            local b = listButtons[idx]
            if not b then
                b = CreateFrame("Button", nil, listContent, "BackdropTemplate")
                b:SetSize(180, 22)
                SetSolidBackdrop(b, THEME.btn, 1, THEME.border)

                local t = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
                t:SetPoint("LEFT", b, "LEFT", 8, 0)
                t:SetJustifyH("LEFT")
                b._text = t

                b:SetScript("OnEnter", function(self)
                    if self._selected then return end
                    self:SetBackdropColor(THEME.btnHover[1], THEME.btnHover[2], THEME.btnHover[3], THEME.btnHover[4])
                end)
                b:SetScript("OnLeave", function(self)
                    if self._selected then return end
                    self:SetBackdropColor(THEME.btn[1], THEME.btn[2], THEME.btn[3], THEME.btn[4])
                end)

                listButtons[idx] = b
            end

            b:ClearAllPoints()
            b:SetPoint("TOPLEFT", listContent, "TOPLEFT", 0, -y)
            b._text:SetText(name)

            b._selected = (name == currentName)
            if b._selected then
                b:SetBackdropColor(THEME.btnSel[1], THEME.btnSel[2], THEME.btnSel[3], THEME.btnSel[4])
            else
                b:SetBackdropColor(THEME.btn[1], THEME.btn[2], THEME.btn[3], THEME.btn[4])
            end

            b:SetScript("OnClick", function()
                SelectScript(name)
                UpdateList()
            end)

            b:Show()
            y = y + 26
            idx = idx + 1
        end

        listContent:SetHeight(math_max(y, 1))

        if currentName and db.ssr.scripts[currentName] then
            RefreshAuto()
        else
            autoRunCheck:SetChecked(false)
        end
    end

    StaticPopupDialogs["POM_SSR_SAVE"] = {
        text = "Enter name for the new script:",
        button1 = "Save",
        button2 = "Cancel",
        hasEditBox = true,
        OnAccept = function(selfPopup)
            local name = (selfPopup.EditBox and selfPopup.EditBox:GetText()) or ""
            if name ~= "" then
                db.ssr.scripts[name] = editBox:GetText() or ""
                if db.ssr.autorun[name] == nil then db.ssr.autorun[name] = false end
                SelectScript(name)
                UpdateList()
            end
        end,
        timeout = 0,
        whileDead = true,
        hideOnEscape = true,
        preferredIndex = 3,
    }

    local btnNew = MakeButton(editBoxFrame, "New", 10, -0, 90, 24, function()
        currentName = nil
        db.ssr.last = nil
        editBox:SetText("")
        autoRunCheck:SetChecked(false)
        UpdateList()
    end)
    btnNew:ClearAllPoints()
    btnNew:SetPoint("BOTTOMLEFT", editBoxFrame, "BOTTOMLEFT", 10, 10)

    local btnSave = MakeButton(editBoxFrame, "Save", 0, 0, 90, 24, function()
        if currentName then
            db.ssr.scripts[currentName] = editBox:GetText() or ""
        else
            StaticPopup_Show("POM_SSR_SAVE")
        end
        UpdateList()
    end)
    btnSave:ClearAllPoints()
    btnSave:SetPoint("LEFT", btnNew, "RIGHT", 8, 0)

    local btnDelete = MakeButton(editBoxFrame, "Delete", 0, 0, 90, 24, function()
        if currentName and db.ssr.scripts[currentName] then
            db.ssr.scripts[currentName] = nil
            db.ssr.autorun[currentName] = nil
            if db.ssr.last == currentName then db.ssr.last = nil end
            currentName = nil
            editBox:SetText("")
            autoRunCheck:SetChecked(false)
            UpdateList()
        end
    end)
    btnDelete:ClearAllPoints()
    btnDelete:SetPoint("LEFT", btnSave, "RIGHT", 8, 0)

    local btnRun = MakeButton(editBoxFrame, "Run Code", 0, 0, 120, 24, function()
        SSR_Run(currentName or "Unsaved", editBox:GetText() or "")
    end)
    btnRun:ClearAllPoints()
    btnRun:SetPoint("BOTTOMRIGHT", editBoxFrame, "BOTTOMRIGHT", -10, 10)

    if currentName and db.ssr.scripts[currentName] then
        editBox:SetText(db.ssr.scripts[currentName] or "")
    else
        currentName = nil
        autoRunCheck:SetChecked(false)
    end
    UpdateList()

    MakeNavButton("General & Options", i1)
    MakeNavButton("Heights & Base Colors", i2)
    MakeNavButton("Threat & Aggro", i3)
    MakeNavButton("PvP", i4)
    MakeNavButton("Specials", i5)
    MakeNavButton("Who Is Targeting Me", i6)
    MakeNavButton("Scripts", i7)

    ShowPanel(1)

    for _, fn in ipairs(frame._pomDarkRefreshers) do
        if fn then fn() end
    end
end