local addonName, ns = ...
local L = ns.L

local function GetAccent()
    if ns.Theme then return ns.Theme.GetAccent() end
    return 0, 207/255, 1.0
end

local function CC()
    if ns.GetClassColor then return ns.GetClassColor() end
    return 0.7, 0.7, 0.7
end

---------------------------------------------------------------------------
-- Tab registry and active state
---------------------------------------------------------------------------
local activeTabKey   = nil
local activeGroupDef = nil
local activeGroupBtn = nil
local activeSideBtn  = nil

---------------------------------------------------------------------------
-- Group definitions
-- dbKey = string key in BrightQoL (global) whose .enabled field drives the dot.
--         nil = no dot (non-module tabs like General, About, etc.)
---------------------------------------------------------------------------
local GROUPS = {
    {
        name  = "combat",
        label = "Combat",
        tabs  = {
            { key = "combat_timer",     label = "Combat Timer",      dbKey = "combatTimer",    init = function() if ns.InitCombatTimer     then ns:InitCombatTimer()     end end },
            { key = "combat_alert",     label = "Combat Alert",      dbKey = "combatAlert",    init = function() if ns.InitCombatAlerts    then ns:InitCombatAlerts()    end end },
            { key = "focus_cast_bar",   label = "Focus Cast Bar",    dbKey = "focusCastBar",   init = function() if ns.InitFocusCastBar    then ns:InitFocusCastBar()    end end },
            { key = "combat_logger",    label = "Combat Logger",     dbKey = "combatLogger",   init = function() if ns.InitCombatLogger    then ns:InitCombatLogger()    end end },
            { key = "gcd_tracker",      label = "GCD Tracker",       dbKey = "gcdTracker",     init = function() if ns.InitGcdTracker      then ns:InitGcdTracker()      end end },
            { key = "movement_alert",   label = "Movement Alert",    dbKey = "movementAlert",  init = function() if ns.InitMovementAlert   then ns:InitMovementAlert()   end end },
            { key = "bloodlust_detect", label = "Bloodlust Detect",  dbKey = "bloodlust",      init = function() if ns.InitBloodlustDetect then ns:InitBloodlustDetect() end end },
            { key = "markers",          label = "Markers",           dbKey = "markers",        init = function() if ns.InitMarker          then ns:InitMarker()          end end },
        },
    },
    {
        name  = "hud",
        label = "HUD",
        tabs  = {
            { key = "mouse_ring",   label = "Mouse Ring",     dbKey = "mouseRing",    init = function() if ns.InitMouseOptions   then ns:InitMouseOptions()   end end },
            { key = "crosshair",    label = "Crosshair",      dbKey = "crosshair",    init = function() if ns.InitCrosshair      then ns:InitCrosshair()      end end },
            { key = "game_bar",     label = "Game Bar",       dbKey = "gameBar",      init = function() if ns.InitGameBar        then ns:InitGameBar()        end end },
            { key = "dragonriding", label = "Dragonriding",   dbKey = "dragonriding", init = function() if ns.InitDragonridingUI then ns:InitDragonridingUI() end end },
            { key = "minimap",      label = "Minimap",        dbKey = "minimap",      init = function() if ns.InitMinimap        then ns:InitMinimap()        end end },
            { key = "datatexts",    label = "DataText & Panels", dbKey = "dataPanels", init = function() if ns.InitDataTexts      then ns:InitDataTexts()      end end },
            { key = "chat",         label = "Chat",              dbKey = "brightChat",  init = function() if ns.InitChat           then ns:InitChat()           end end },
        },
    },
    {
        name  = "tracking",
        label = "Tracking",
        tabs  = {
            { key = "buff_watcher", label = "Buff Watcher",        dbKey = "buffWatcherV2",   init = function() if ns.InitBuffWatcher     then ns:InitBuffWatcher()     end end },
            { key = "stealth",      label = "Stealth/Stance Check", dbKey = "stealthReminder", init = function() if ns.InitStealthReminder then ns:InitStealthReminder() end end },
            { key = "range_check",  label = "Range Check",         dbKey = "rangeCheck",      init = function() if ns.InitRangeCheck      then ns:InitRangeCheck()      end end },
            { key = "pet_tracker",  label = "Pet Tracker",         dbKey = "petTracker",      init = function() if ns.InitPetTracker      then ns:InitPetTracker()      end end },
            { key = "co_tank",      label = "Co-Tank Frame",       dbKey = "coTank",          init = function() if ns.InitCoTank          then ns:InitCoTank()          end end },
        },
    },
    {
        name  = "mythicplus",
        label = "Mythic +",
        tabs  = {
            { key = "healer_mana", label = "Healer Mana", dbKey = "healerMana",  init = function() if ns.InitHealerMana   then ns:InitHealerMana()   end end },
            { key = "swift_stats", label = "Stats",       dbKey = "swiftStats",  init = function() if ns.InitStats         then ns:InitStats()         end end },
            { key = "mplus_timer", label = "M+ Timer",    dbKey = "mythicTimer", isNew = true, init = function() if ns.InitMythicTimer then ns:InitMythicTimer() end end },
        },
    },
    {
        name  = "reminders",
        label = "Reminders",
        tabs  = {
            { key = "personal_reminders", label = "Personal Reminders", dbKey = "personalReminders", init = function() if ns.InitPersonalReminders then ns:InitPersonalReminders() end end },
            { key = "talent_reminder",    label = "Talent Reminder",    dbKey = "talentReminder",    init = function() if ns.InitTalentReminder    then ns:InitTalentReminder()    end end },
            { key = "equipment_reminder", label = "Equipment Reminder", dbKey = "equipmentReminder", init = function() if ns.InitEquipmentReminder then ns:InitEquipmentReminder() end end },
            { key = "crez",               label = "Combat Rez",         dbKey = "cRez",              init = function() if ns.InitCRez             then ns:InitCRez()             end end },
            { key = "misc",               label = "Misc",               dbKey = nil,                 init = function() if ns.InitModuleOptions    then ns:InitModuleOptions()    end end },
        },
    },
    {
        name  = "system",
        label = "System",
        tabs  = {
            { key = "general",        label = "General",        dbKey = nil, init = function() if ns.InitGeneral       then ns:InitGeneral()       end end },
            { key = "optimizations",  label = "Optimizations",  dbKey = nil, init = function() if ns.InitOptOptions    then ns:InitOptOptions()   end end },
            { key = "profiles",       label = "Profiles",       dbKey = nil, init = function() if ns.InitImportExport  then ns:InitImportExport() end end },
            { key = "slash_commands", label = "Slash Commands", dbKey = nil, init = function() if ns.InitSlashCommands then ns:InitSlashCommands() end end },
            { key = "about",          label = "About",          dbKey = nil, init = function() if ns.InitAbout         then ns:InitAbout()         end end },
        },
    },
}

---------------------------------------------------------------------------
-- Dimensions
---------------------------------------------------------------------------
local GROUP_ROW_H = 34
local SIDEBAR_W   = 152
local ITEM_H      = 26
local DOT_SIZE    = 6

---------------------------------------------------------------------------
-- Group row (main tabs — top bar, unchanged from old layout)
---------------------------------------------------------------------------
local GroupRow = CreateFrame("Frame", nil, ns.MainFrame, "BackdropTemplate")
GroupRow:SetHeight(GROUP_ROW_H)
GroupRow:SetPoint("TOPLEFT",  ns.MainFrame, "TOPLEFT",  1, -36)
GroupRow:SetPoint("TOPRIGHT", ns.MainFrame, "TOPRIGHT", -1, -36)
GroupRow:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
GroupRow:SetBackdropColor(0.03, 0.03, 0.06, 1)

local GroupRowSep = GroupRow:CreateTexture(nil, "OVERLAY")
GroupRowSep:SetHeight(1)
GroupRowSep:SetPoint("BOTTOMLEFT",  GroupRow, "BOTTOMLEFT",  0, 0)
GroupRowSep:SetPoint("BOTTOMRIGHT", GroupRow, "BOTTOMRIGHT", 0, 0)
do local ar,ag,ab = GetAccent() GroupRowSep:SetColorTexture(ar, ag, ab, 0.35) end

---------------------------------------------------------------------------
-- Sidebar (vertical panel, left of content area)
---------------------------------------------------------------------------
local Sidebar = CreateFrame("Frame", nil, ns.MainFrame, "BackdropTemplate")
Sidebar:SetWidth(SIDEBAR_W)
Sidebar:SetPoint("TOPLEFT",    GroupRow,     "BOTTOMLEFT",  0, -1)
Sidebar:SetPoint("BOTTOMLEFT", ns.MainFrame, "BOTTOMLEFT",  1,  2)
Sidebar:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8X8",
    edgeFile = "Interface\\Buttons\\WHITE8X8",
    edgeSize = 1,
    insets   = { left = 1, right = 1, top = 1, bottom = 1 },
})
Sidebar:SetBackdropColor(0.04, 0.04, 0.07, 1)
do local ar,ag,ab = GetAccent() Sidebar:SetBackdropBorderColor(ar, ag, ab, 0.25) end

-- Right separator
local SidebarSep = Sidebar:CreateTexture(nil, "OVERLAY")
SidebarSep:SetWidth(1)
SidebarSep:SetPoint("TOPRIGHT",    Sidebar, "TOPRIGHT",    0, 0)
SidebarSep:SetPoint("BOTTOMRIGHT", Sidebar, "BOTTOMRIGHT", 0, 0)
do local ar,ag,ab = GetAccent() SidebarSep:SetColorTexture(ar, ag, ab, 0.4) end

-- ScrollFrame inside sidebar (handles long module lists gracefully)
local SideScroll = CreateFrame("ScrollFrame", "BrightQoL_SidebarScroll",
    Sidebar, "UIPanelScrollFrameTemplate")
SideScroll:SetPoint("TOPLEFT",     Sidebar, "TOPLEFT",     2,  -2)
SideScroll:SetPoint("BOTTOMRIGHT", Sidebar, "BOTTOMRIGHT", -18, 2)

local SideChild = CreateFrame("Frame", nil, SideScroll)
SideChild:SetWidth(SIDEBAR_W - 22)
SideScroll:SetScrollChild(SideChild)

---------------------------------------------------------------------------
-- Content area — right of sidebar, below group row
---------------------------------------------------------------------------
local ContentArea = CreateFrame("Frame", "BrightQoL_Content", ns.MainFrame, "BackdropTemplate")
ContentArea:SetPoint("TOPLEFT",     Sidebar,      "TOPRIGHT",     1,  0)
ContentArea:SetPoint("BOTTOMRIGHT", ns.MainFrame, "BOTTOMRIGHT", -2,  2)
ContentArea:SetBackdrop({
    bgFile   = "Interface\\Buttons\\WHITE8X8",
    edgeFile = "Interface\\Buttons\\WHITE8X8",
    edgeSize = 1,
    insets   = { left = 2, right = 2, top = 2, bottom = 2 },
})
ContentArea:SetBackdropColor(0, 0, 0, 0.3)
do local ar,ag,ab = GetAccent() ContentArea:SetBackdropBorderColor(ar, ag, ab, 0.35) end

ns.MainFrame.Content = ContentArea

function ns.MainFrame:ResetContent()
    for _, child in ipairs({ ContentArea:GetChildren() }) do
        if child then child:Hide() end
    end
    for _, region in ipairs({ ContentArea:GetRegions() }) do
        if region and region.Hide then region:Hide() end
    end
end

---------------------------------------------------------------------------
-- Dot colour helper
---------------------------------------------------------------------------
local function GetDotColor(dbKey)
    if not dbKey then return nil end
    local t = BrightQoL and BrightQoL[dbKey]
    if t and t.enabled then
        return 0, 1, 0, 0.9           -- green = enabled
    else
        return 0.35, 0.35, 0.35, 0.8  -- grey  = disabled / unknown
    end
end

---------------------------------------------------------------------------
-- RefreshSidebarDots — public, called by Config files after toggling enabled
---------------------------------------------------------------------------
local function RefreshSidebarDots()
    if not SideChild then return end
    for _, child in ipairs({ SideChild:GetChildren() }) do
        if child._dot and child._dbKey ~= nil then
            local r, g, b, a = GetDotColor(child._dbKey)
            child._dot:SetColorTexture(r, g, b, a)
        end
    end
end
ns.RefreshSidebarDots = RefreshSidebarDots

---------------------------------------------------------------------------
-- Build one sidebar item frame
---------------------------------------------------------------------------
local function MakeSideItem(parent, tabDef, yOff)
    local item = CreateFrame("Button", nil, parent, "BackdropTemplate")
    item:SetHeight(ITEM_H)
    item:SetPoint("TOPLEFT",  parent, "TOPLEFT",  0, yOff)
    item:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, yOff)
    item:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
    })
    item:SetBackdropColor(0, 0, 0, 0)
    item:SetBackdropBorderColor(0, 0, 0, 0)

    -- Status dot
    local dot = item:CreateTexture(nil, "OVERLAY")
    dot:SetSize(DOT_SIZE, DOT_SIZE)
    dot:SetPoint("LEFT", item, "LEFT", 7, 0)
    if tabDef.dbKey then
        local r, g, b, a = GetDotColor(tabDef.dbKey)
        dot:SetColorTexture(r, g, b, a)
    else
        dot:SetColorTexture(0, 0, 0, 0)
    end
    item._dot   = dot
    item._dbKey = tabDef.dbKey

    -- Label
    local lbl = item:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    lbl:SetPoint("LEFT",  item, "LEFT",  DOT_SIZE + 13, 0)
    lbl:SetPoint("RIGHT", item, "RIGHT", -6, 0)
    lbl:SetJustifyH("LEFT")
    lbl:SetText(tabDef.label)
    lbl:SetTextColor(0.7, 0.7, 0.7, 1)
    item._label = lbl

    -- NEW badge (top-right corner of item)
    local seen = BrightQoL and BrightQoL.seenTabs and BrightQoL.seenTabs[tabDef.key]
    if tabDef.isNew and not seen then
        local badge = item:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        badge:SetPoint("RIGHT", item, "RIGHT", -6, 0)
        badge:SetText("NEW")
        do local ar,ag,ab = GetAccent() badge:SetTextColor(ar, ag, ab, 0.9) end
        badge:SetFont(badge:GetFont(), 8)
        item._newBadge = badge
    end

    item:SetScript("OnEnter", function(self)
        if self ~= activeSideBtn then
            local r, g, b = CC()
            self:SetBackdropColor(r, g, b, 0.06)
            self:SetBackdropBorderColor(r, g, b, 0.2)
            if self._label then self._label:SetTextColor(1, 1, 1, 1) end
        end
    end)
    item:SetScript("OnLeave", function(self)
        if self ~= activeSideBtn then
            self:SetBackdropColor(0, 0, 0, 0)
            self:SetBackdropBorderColor(0, 0, 0, 0)
            if self._label then self._label:SetTextColor(0.7, 0.7, 0.7, 1) end
        end
    end)

    return item
end

---------------------------------------------------------------------------
-- Switch to a sidebar tab (highlight item, load content)
---------------------------------------------------------------------------
local function SwitchTab(key, initFunc, sideBtn)
    if not ns.MainFrame or not ns.MainFrame.Content then return end

    -- Reset all sidebar item colours
    for _, child in ipairs({ SideChild:GetChildren() }) do
        if child._label then
            child:SetBackdropColor(0, 0, 0, 0)
            child:SetBackdropBorderColor(0, 0, 0, 0)
            child._label:SetTextColor(0.7, 0.7, 0.7, 1)
        end
    end

    -- Highlight active item
    if sideBtn then
        local r, g, b = CC()
        sideBtn:SetBackdropColor(r, g, b, 0.12)
        sideBtn:SetBackdropBorderColor(r, g, b, 0.4)
        if sideBtn._label then sideBtn._label:SetTextColor(r, g, b, 1) end
    end

    ns.MainFrame:ResetContent()
    if initFunc then initFunc() end

    activeTabKey  = key
    activeSideBtn = sideBtn

    if BrightQoL and BrightQoL.config then
        BrightQoL.config.lastTab = key
    end

    -- Mark as seen — remove NEW badge permanently
    if sideBtn and sideBtn._newBadge then
        sideBtn._newBadge:Hide()
        sideBtn._newBadge = nil
        BrightQoL.seenTabs = BrightQoL.seenTabs or {}
        BrightQoL.seenTabs[key] = true
    end
end

---------------------------------------------------------------------------
-- Populate sidebar for a group
---------------------------------------------------------------------------
local function PopulateSidebar(groupDef, activateFirst)
    -- Clear existing children
    for _, child in ipairs({ SideChild:GetChildren() }) do
        child:Hide()
        child:SetParent(nil)
    end
    activeSideBtn = nil

    local yOff = -3
    for _, tabDef in ipairs(groupDef.tabs) do
        local item = MakeSideItem(SideChild, tabDef, yOff)

        local captureKey  = tabDef.key
        local captureInit = tabDef.init
        local captureItem = item
        item:SetScript("OnClick", function()
            SwitchTab(captureKey, captureInit, captureItem)
        end)

        tabDef._btn = item
        yOff = yOff - (ITEM_H + 2)
    end

    SideChild:SetHeight(math.abs(yOff) + 4)

    if activateFirst and #groupDef.tabs > 0 then
        local first = groupDef.tabs[1]
        SwitchTab(first.key, first.init, first._btn)
    end
end

---------------------------------------------------------------------------
-- Search box (right side of GroupRow)
---------------------------------------------------------------------------
local SEARCH_W      = 160
local searchDebounce
local searchMode    = false

local SearchBox = CreateFrame("EditBox", nil, GroupRow, "BackdropTemplate")
SearchBox:SetSize(SEARCH_W, 22)
SearchBox:SetPoint("RIGHT", GroupRow, "RIGHT", -8, 0)
SearchBox:SetBackdrop({
    bgFile   = [[Interface\Buttons\WHITE8x8]],
    edgeFile = [[Interface\Buttons\WHITE8x8]],
    edgeSize = 1,
})
SearchBox:SetBackdropColor(0.04, 0.04, 0.08, 0.9)
SearchBox:SetBackdropBorderColor(0.15, 0.15, 0.25, 1)
SearchBox:SetFont(ns.Theme and ns.Theme.GetFont and ns.Theme.GetFont() or "Fonts\\FRIZQT__.TTF", 11, "")
SearchBox:SetTextColor(0.85, 0.85, 0.85, 1)
SearchBox:SetTextInsets(6, 24, 0, 0)
SearchBox:SetAutoFocus(false)
SearchBox:SetMaxLetters(30)

local SearchPlaceholder = SearchBox:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
SearchPlaceholder:SetPoint("LEFT", SearchBox, "LEFT", 8, 0)
SearchPlaceholder:SetText("Search...")

local SearchClear = CreateFrame("Button", nil, SearchBox)
SearchClear:SetSize(16, 16)
SearchClear:SetPoint("RIGHT", SearchBox, "RIGHT", -4, 0)
SearchClear:Hide()
local SearchClearTex = SearchClear:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
SearchClearTex:SetPoint("CENTER", 0, 0)
SearchClearTex:SetText("|cffaaaaaa x|r")
SearchClear:SetScript("OnClick", function()
    SearchBox:SetText("")
    SearchBox:ClearFocus()
end)

-- Flat list of ALL tabs (built once at load time)
local ALL_TABS = {}
for _, g in ipairs(GROUPS) do
    for _, t in ipairs(g.tabs) do
        ALL_TABS[#ALL_TABS + 1] = {
            key   = t.key,
            label = t.label,
            lower = t.label:lower(),
            dbKey = t.dbKey,
            isNew = t.isNew,
            init  = t.init,
        }
    end
end

local function ApplySearch(query)
    query = query:lower():gsub("^%s+", ""):gsub("%s+$", "")

    if query == "" then
        searchMode = false
        SearchPlaceholder:Show()
        SearchClear:Hide()
        SearchBox:SetBackdropBorderColor(0.15, 0.15, 0.25, 1)
        if activeGroupDef then PopulateSidebar(activeGroupDef, false) end
        return
    end

    searchMode = true
    SearchPlaceholder:Hide()
    SearchClear:Show()
    SearchBox:SetBackdropBorderColor(0, 207/255, 1.0, 0.6)

    -- Clear sidebar
    for _, child in ipairs({ SideChild:GetChildren() }) do
        child:Hide(); child:SetParent(nil)
    end
    activeSideBtn = nil

    local yOff = -3
    for _, t in ipairs(ALL_TABS) do
        if t.lower:find(query, 1, true) then
            local item = MakeSideItem(SideChild, t, yOff)

            local captureKey  = t.key
            local captureInit = t.init
            local captureItem = item
            item:SetScript("OnClick", function()
                SwitchTab(captureKey, captureInit, captureItem)
            end)

            yOff = yOff - (ITEM_H + 2)
        end
    end

    SideChild:SetHeight(math.abs(yOff) + 4)
end

SearchBox:SetScript("OnTextChanged", function(self)
    local text = self:GetText()
    SearchPlaceholder:SetShown(text == "")
    SearchClear:SetShown(text ~= "")
    if searchDebounce then searchDebounce:Cancel(); searchDebounce = nil end
    searchDebounce = C_Timer.NewTimer(0.15, function()
        searchDebounce = nil
        ApplySearch(text)
    end)
end)

SearchBox:SetScript("OnEscapePressed", function(self)
    self:SetText("")
    self:ClearFocus()
end)

SearchBox:SetScript("OnEditFocusGained", function(self)
    self:SetBackdropBorderColor(0, 207/255, 1.0, 0.6)
end)

SearchBox:SetScript("OnEditFocusLost", function(self)
    if self:GetText() == "" then
        self:SetBackdropBorderColor(0.15, 0.15, 0.25, 1)
    end
end)

---------------------------------------------------------------------------
-- Group buttons (top row)
---------------------------------------------------------------------------
local groupBtns    = {}
local allGroupBtns = {}

local function SetActiveGroupBtn(btn)
    if activeGroupBtn then
        activeGroupBtn:SetBackdropColor(0, 0, 0, 0)
        activeGroupBtn:SetBackdropBorderColor(0, 0, 0, 0)
        if activeGroupBtn._label then
            activeGroupBtn._label:SetTextColor(0.65, 0.65, 0.65, 1)
        end
        if activeGroupBtn._line then activeGroupBtn._line:Hide() end
    end
    activeGroupBtn = btn
    if btn then
        local r, g, b = CC()
        btn:SetBackdropColor(r, g, b, 0.15)
        btn:SetBackdropBorderColor(r, g, b, 0.3)
        if btn._label then btn._label:SetTextColor(r, g, b, 1) end
        if btn._line then
            btn._line:SetColorTexture(r, g, b, 1)
            btn._line:Show()
        end
    end
end

local GROUP_BTN_W = math.floor(950 / #GROUPS)

local function UpdateGroupBtnLayout()
    local rowW = GroupRow:GetWidth()
    if not rowW or rowW < 10 then return end
    local available = rowW - 176   -- 160px search + 16px margin
    local btnW = math.floor(available / #GROUPS)
    for i, btn in ipairs(allGroupBtns) do
        btn:SetWidth(btnW)
        btn:ClearAllPoints()
        btn:SetPoint("LEFT", GroupRow, "LEFT", (i - 1) * btnW, 0)
    end
end

GroupRow:SetScript("OnSizeChanged", function() UpdateGroupBtnLayout() end)

local xOff = 0
for _, groupDef in ipairs(GROUPS) do
    local btn = CreateFrame("Button", nil, GroupRow, "BackdropTemplate")
    btn:SetSize(GROUP_BTN_W, GROUP_ROW_H)
    btn:SetPoint("LEFT", GroupRow, "LEFT", xOff, 0)
    btn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
    })
    btn:SetBackdropColor(0, 0, 0, 0)
    btn:SetBackdropBorderColor(0, 0, 0, 0)

    local lbl = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    lbl:SetPoint("LEFT",  btn, "LEFT",  4, 0)
    lbl:SetPoint("RIGHT", btn, "RIGHT", -4, 0)
    lbl:SetJustifyH("CENTER")
    lbl:SetText(groupDef.label)
    lbl:SetTextColor(0.65, 0.65, 0.65, 1)
    btn._label = lbl

    local line = btn:CreateTexture(nil, "OVERLAY")
    line:SetHeight(3)
    line:SetPoint("BOTTOMLEFT",  btn, "BOTTOMLEFT",  0, 0)
    line:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 0, 0)
    line:Hide()
    btn._line = line

    btn:SetScript("OnEnter", function(self)
        if self ~= activeGroupBtn then
            self:SetBackdropColor(0, 0, 0, 0.2)
            if self._label then self._label:SetTextColor(0.9, 0.9, 0.9, 1) end
        end
    end)
    btn:SetScript("OnLeave", function(self)
        if self ~= activeGroupBtn then
            self:SetBackdropColor(0, 0, 0, 0)
            if self._label then self._label:SetTextColor(0.65, 0.65, 0.65, 1) end
        end
    end)

    local captureGroup = groupDef
    local captureBtn   = btn
    btn:SetScript("OnClick", function()
        SearchBox:SetText("")
        searchMode = false
        activeGroupDef = captureGroup
        activeSideBtn  = nil
        SetActiveGroupBtn(captureBtn)
        PopulateSidebar(captureGroup, true)
    end)

    groupBtns[groupDef.name]        = btn
    allGroupBtns[#allGroupBtns + 1] = btn
    xOff = xOff + GROUP_BTN_W
end

---------------------------------------------------------------------------
-- Public: open a specific tab by key (slash command / last-tab restore)
---------------------------------------------------------------------------
function ns:OpenTab(tabKey)
    for _, groupDef in ipairs(GROUPS) do
        for _, tabDef in ipairs(groupDef.tabs) do
            if tabDef.key == tabKey then
                activeGroupDef = groupDef
                activeSideBtn  = nil
                SetActiveGroupBtn(groupBtns[groupDef.name])
                PopulateSidebar(groupDef, false)
                SwitchTab(tabDef.key, tabDef.init, tabDef._btn)
                return
            end
        end
    end
    -- Fallback: system / optimizations
    local sysGroup = nil
    for _, g in ipairs(GROUPS) do if g.name == "system" then sysGroup = g; break end end
    if sysGroup then
        activeGroupDef = sysGroup
        activeSideBtn  = nil
        SetActiveGroupBtn(groupBtns["system"])
        PopulateSidebar(sysGroup, true)
    end
end

---------------------------------------------------------------------------
-- Public refresh — repaint accents and highlights after theme/class change
---------------------------------------------------------------------------
function ns.SidebarRefresh()
    local ar, ag, ab = GetAccent()
    GroupRowSep:SetColorTexture(ar, ag, ab, 0.35)
    SidebarSep:SetColorTexture(ar, ag, ab, 0.4)
    Sidebar:SetBackdropBorderColor(ar, ag, ab, 0.25)
    ContentArea:SetBackdropBorderColor(ar, ag, ab, 0.35)

    local r, g, b = CC()

    if ns.COLORS then
        ns.COLORS.ORANGE = string.format("%02x%02x%02x", r*255, g*255, b*255)
    end

    -- Group buttons
    for _, btn in ipairs(allGroupBtns) do
        if btn == activeGroupBtn then
            btn:SetBackdropColor(r, g, b, 0.15)
            btn:SetBackdropBorderColor(r, g, b, 0.3)
            if btn._label then btn._label:SetTextColor(r, g, b, 1) end
            if btn._line then btn._line:SetColorTexture(r, g, b, 1); btn._line:Show() end
        else
            btn:SetBackdropColor(0, 0, 0, 0)
            btn:SetBackdropBorderColor(0, 0, 0, 0)
            if btn._label then btn._label:SetTextColor(0.65, 0.65, 0.65, 1) end
            if btn._line then btn._line:Hide() end
        end
    end

    -- Sidebar items
    for _, child in ipairs({ SideChild:GetChildren() }) do
        if child._label then
            if child == activeSideBtn then
                child:SetBackdropColor(r, g, b, 0.12)
                child:SetBackdropBorderColor(r, g, b, 0.4)
                child._label:SetTextColor(r, g, b, 1)
            else
                child:SetBackdropColor(0, 0, 0, 0)
                child:SetBackdropBorderColor(0, 0, 0, 0)
                child._label:SetTextColor(0.7, 0.7, 0.7, 1)
            end
        end
    end

    RefreshSidebarDots()
end

---------------------------------------------------------------------------
-- Initialise on PLAYER_LOGIN
---------------------------------------------------------------------------
local loader = CreateFrame("Frame")
loader:RegisterEvent("PLAYER_LOGIN")
loader:SetScript("OnEvent", function()
    UpdateGroupBtnLayout()

    local firstGroup = GROUPS[1]
    activeGroupDef = firstGroup
    SetActiveGroupBtn(groupBtns[firstGroup.name])
    PopulateSidebar(firstGroup, false)

    if ns.InitHomePage then ns:InitHomePage() end
end)
