-- UI_Core.lua
local addonName, addonTable = ...

local BACKDROP_PATH = "Interface\\AddOns\\RNTools\\Textures\\Backdrop"
local function ApplyWindowBackdrop(aceFrame, flip)
    aceFrame.frame:SetBackdropColor(0, 0, 0, 0)
    local t = aceFrame.frame:CreateTexture(nil, "BACKGROUND")
    t:SetTexture(BACKDROP_PATH)
    t:SetBlendMode("DISABLE")
    t:SetAllPoints(aceFrame.frame)
    if flip then t:SetTexCoord(0, 1, 1, 0) end
end

-- ============================================================
-- RNTS: RNTools visual style (matches RealNibba.Pack dark theme)
-- Inlined here so it is ALWAYS defined before any function runs.
-- ============================================================
RNTS = {}

-- Matches AceGUI PaneBackdrop exactly (ChatFrameBackground + UI-Tooltip-Border)
RNTS.BACKDROP = {
    bgFile   = "Interface/ChatFrame/ChatFrameBackground",
    edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
    tile=true, tileSize=16, edgeSize=16,
    insets={left=3, right=3, top=5, bottom=3},
}
RNTS.BACKDROP_BTN = {
    bgFile   = "Interface/ChatFrame/ChatFrameBackground",
    edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
    tile=true, tileSize=16, edgeSize=16,
    insets={left=3, right=3, top=5, bottom=3},
}
RNTS.C = {
    bg          = {0.10,  0.10,  0.10,  0.50},  -- AceGUI InlineGroup bg
    bgBtn       = {0.14,  0.14,  0.14,  1.00},
    bgBtnHover  = {0.22,  0.22,  0.22,  1.00},
    bgBtnDown   = {0.08,  0.08,  0.08,  1.00},
    border      = {0.40,  0.40,  0.40,  1.00},  -- AceGUI InlineGroup border
    borderBtn   = {0.40,  0.40,  0.40,  1.00},
    borderGold  = {1.00,  0.82,  0.00,  1.00},
    borderGreen = {0.10,  0.80,  0.30,  1.00},
    borderRed   = {0.80,  0.20,  0.10,  1.00},
    textMain    = {0.95,  0.95,  0.95,  1.00},
    textGold    = {1.00,  0.82,  0.00,  1.00},
    textGrey    = {0.55,  0.55,  0.55,  1.00},
    textGreen   = {0.20,  1.00,  0.40,  1.00},
    textRed     = {1.00,  0.40,  0.40,  1.00},
    divider     = {0.40,  0.40,  0.40,  0.70},
}

function RNTS.Panel(f, bgR,bgG,bgB,bgA, brR,brG,brB,brA)
    local C = RNTS.C
    f:SetBackdrop(RNTS.BACKDROP)
    f:SetBackdropColor(bgR or C.bg[1], bgG or C.bg[2], bgB or C.bg[3], bgA or C.bg[4])
    f:SetBackdropBorderColor(brR or C.border[1], brG or C.border[2],
                             brB or C.border[3], brA or C.border[4])
end
function RNTS.DarkPanel(f) RNTS.Panel(f) end

function RNTS.TabBtn(parent, label, w, h)
    local C   = RNTS.C
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(w, h or 26)
    btn:SetBackdrop(RNTS.BACKDROP_BTN)
    btn:SetBackdropColor(0.05,0.05,0.07,1)
    btn:SetBackdropBorderColor(C.border[1],C.border[2],C.border[3],C.border[4])
    local fs = btn:CreateFontString(nil,"OVERLAY","GameFontNormalSmall")
    fs:SetAllPoints(btn); fs:SetJustifyH("CENTER"); fs:SetText(label)
    fs:SetTextColor(C.textGrey[1],C.textGrey[2],C.textGrey[3],1)
    btn._fs   = fs
    btn._active = false
    function btn:SetActive(on)
        btn._active = on
        if on then
            btn:SetBackdropColor(0.08,0.06,0.04,1)
            btn:SetBackdropBorderColor(C.borderGold[1],C.borderGold[2],C.borderGold[3],1)
            btn._fs:SetTextColor(C.textGold[1],C.textGold[2],C.textGold[3],1)
        else
            btn:SetBackdropColor(0.05,0.05,0.07,1)
            btn:SetBackdropBorderColor(C.border[1],C.border[2],C.border[3],C.border[4])
            btn._fs:SetTextColor(C.textGrey[1],C.textGrey[2],C.textGrey[3],1)
        end
    end
    btn:SetScript("OnEnter",function()
        if not btn._active then btn:SetBackdropColor(0.09,0.09,0.11,1) end
    end)
    btn:SetScript("OnLeave",function()
        if not btn._active then btn:SetBackdropColor(0.05,0.05,0.07,1) end
    end)
    return btn
end  -- RNTS.TabBtn

-- ── QA helpers (hoisted here - must be before CreateMainFrameIfNeeded) ────────
local function QASetBG(f,r,g,b,a)
    if not f._rnts_bd then f:SetBackdrop(RNTS.BACKDROP); f._rnts_bd=true end
    f:SetBackdropColor(r,g,b,a or 1)
end
local function QABorder(f,r,g,b,a)
    if not f._rnts_bd then f:SetBackdrop(RNTS.BACKDROP); f._rnts_bd=true end
    f:SetBackdropBorderColor(r,g,b,a or 1)
end

local function QABtn(parent, label, w, h, r, g, b)
    local C   = RNTS.C
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(w, h or 24)
    btn:SetBackdrop(RNTS.BACKDROP_BTN)
    btn:SetBackdropColor(r or C.bgBtn[1], g or C.bgBtn[2], b or C.bgBtn[3], 1)
    btn:SetBackdropBorderColor(C.borderBtn[1],C.borderBtn[2],C.borderBtn[3],C.borderBtn[4])
    local fs = btn:CreateFontString(nil,"OVERLAY","GameFontNormalSmall")
    fs:SetAllPoints(btn); fs:SetJustifyH("CENTER"); fs:SetText(label)
    fs:SetTextColor(C.textMain[1],C.textMain[2],C.textMain[3],1)
    btn._fs = fs
    btn:SetScript("OnEnter",    function() btn:SetBackdropColor(C.bgBtnHover[1],C.bgBtnHover[2],C.bgBtnHover[3],1) end)
    btn:SetScript("OnLeave",    function() btn:SetBackdropColor(r or C.bgBtn[1],g or C.bgBtn[2],b or C.bgBtn[3],1) end)
    btn:SetScript("OnMouseDown",function() btn:SetBackdropColor(C.bgBtnDown[1],C.bgBtnDown[2],C.bgBtnDown[3],1) end)
    btn:SetScript("OnMouseUp",  function() btn:SetBackdropColor(C.bgBtnHover[1],C.bgBtnHover[2],C.bgBtnHover[3],1) end)
    return btn
end

local function QAHdr(parent, text, y, w)
    local C = RNTS.C
    local f = CreateFrame("Frame", nil, parent)
    f:SetPoint("TOPLEFT",  parent, "TOPLEFT",  4, y)
    f:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -4, y)
    f:SetHeight(18)
    f:SetBackdrop(RNTS.BACKDROP)
    f:SetBackdropColor(0.08,0.06,0.04,1)
    f:SetBackdropBorderColor(C.divider[1],C.divider[2],C.divider[3],C.divider[4])
    local fs = f:CreateFontString(nil,"OVERLAY","GameFontNormal")
    fs:SetPoint("CENTER",f,"CENTER",0,0)
    fs:SetTextColor(C.textGold[1],C.textGold[2],C.textGold[3],1)
    fs:SetText(text)
    return f
end




local MAIN_FRAME_NAME   = "RNToolsMainFrame"
local MAIN_FRAME_WIDTH  = 1100
local MAIN_FRAME_HEIGHT = 660

-- Track whether buffs have been assigned this raid — popup only shows once
local RNT_buffsAssignedThisRaid = false
local RNT_wasInRaid = false

local RNT_buffRaidTracker = CreateFrame("Frame")
RNT_buffRaidTracker:RegisterEvent("RAID_ROSTER_UPDATE")
RNT_buffRaidTracker:RegisterEvent("PARTY_MEMBERS_CHANGED")
RNT_buffRaidTracker:RegisterEvent("PLAYER_ENTERING_WORLD")
RNT_buffRaidTracker:SetScript("OnEvent", function()
    local inRaid = GetNumRaidMembers() > 0
    if inRaid and not RNT_wasInRaid then
        RNT_buffsAssignedThisRaid = false
    end
    if not inRaid then
        RNT_buffsAssignedThisRaid = false
    end
    RNT_wasInRaid = inRaid
end)

-- Check which paladins are out of inspect range
local function RNT_GetOutOfRangePaladins()
    local outOfRange = {}
    local myName = UnitName("player")
    local n = GetNumRaidMembers()
    if n > 0 then
        for i = 1, n do
            local name, _, _, _, _, clsFile, _, online = GetRaidRosterInfo(i)
            if name and clsFile == "PALADIN" and online and name ~= myName then
                local unit = "raid" .. i
                if not CheckInteractDistance(unit, 1) then
                    outOfRange[#outOfRange + 1] = name
                end
            end
        end
    else
        for i = 1, GetNumPartyMembers() do
            local _, cls = UnitClass("party" .. i)
            if cls == "PALADIN" then
                local name = UnitName("party" .. i)
                if name and name ~= myName and not CheckInteractDistance("party" .. i, 1) then
                    outOfRange[#outOfRange + 1] = name
                end
            end
        end
    end
    return outOfRange
end

StaticPopupDialogs["RNTOOLS_BUFF_ASSIGN"] = {
    text         = "%s",
    button1      = "Auto-Assign",
    button2      = "Manual",
    OnShow = function(self)
        local oor = RNT_GetOutOfRangePaladins()
        if #oor > 0 then
            local btn = self.button1 or _G[self:GetName() .. "Button1"]
            if btn then btn:Disable() end
        end
    end,
    OnAccept = function()
        RNT_buffsAssignedThisRaid = true
        if RNTools_AutoAssignBuffs then RNTools_AutoAssignBuffs() end
        if RNTools_AnnounceBuffOrder then RNTools_AnnounceBuffOrder("rw") end
    end,
    OnCancel = function()
        RNT_buffsAssignedThisRaid = true
        if RNTools_OpenModuleTab then RNTools_OpenModuleTab(6) end
    end,
    timeout       = 0,
    whileDead     = true,
    hideOnEscape  = true,
    preferredIndex = 3,
}

local STATUS_OVERLAY_NAME   = "RNToolsStatusOverlayFrame"
local STATUS_OVERLAY_WIDTH  = 410
local STATUS_OVERLAY_HEIGHT = 22

-- ── Shared visual helpers (same style as BiS Tracker) ─────────────────────
-- ── Visual helpers — delegates to RNTS (RNTools_Styles.lua) ─────────────────
-- UISetBG / UIAddBorder kept for backward compat with any call sites,
-- but now use proper SetBackdrop instead of texture hacks.
local function UISetBG(f, r, g, b, a)
    if not f.SetBackdrop then return end
    if not f._rnts_bd then
        f:SetBackdrop(RNTS.BACKDROP); f._rnts_bd = true
    end
    f:SetBackdropColor(r, g, b, a or 1)
end

local function UIAddBorder(f, r, g, b, a)
    if not f.SetBackdropBorderColor then return end
    if not f._rnts_bd then
        f:SetBackdrop(RNTS.BACKDROP); f._rnts_bd = true
    end
    f:SetBackdropBorderColor(r, g, b, a or 1)
end

local STATUS_ON_TEXT  = "|cff00ff00ON|r"
local STATUS_OFF_TEXT = "|cffff3b30OFF|r"
local DEFAULT_POINT   = "CENTER"

local AceGUI = LibStub("AceGUI-3.0")

-- Hides the AceGUI Frame widget's built-in "Close" text button (bottom-right).
-- The button is a constructor local (not on widget table), so find by text.
local function HideAceCloseBtn(aceFrame)
    local f = aceFrame.frame
    local n = f.GetNumChildren and f:GetNumChildren() or 0
    for i = 1, n do
        local c = select(i, f:GetChildren())
        if c and c.GetText and c:GetText() == (CLOSE or "Close") then
            c:Hide(); break
        end
    end
end

local mainFrame        = nil
local mainTabGroup     = nil
local statusOverlayFrame = nil
local tabFrames        = {}
local activeTabIndex   = 1

-- TAB_DEFINITIONS: BiS Tracker first, then Spammer / Auto Reply / Master Looter
local TAB_DEFINITIONS = {
    { name = "BiS Tracker",   creator = CreateBisTabContent,          moduleKey = "bis"  },
    { name = "Spammer",       creator = CreateSpammerTabContent,      moduleKey = "spam" },
    { name = "Auto Reply",    creator = CreateAutoResponseTabContent, moduleKey = "ar"   },
    { name = "Master Looter", creator = CreateMasterLooterTabContent, moduleKey = "ml"   },
    { name = "AutoInvite",    creator = CreateAutoInviteTabContent,   moduleKey = "ai"   },
    { name = "MS+Buffs",      creator = CreateMSChangesTabContent,    moduleKey = "ms"   },
    { name = "Raid Browser",  creator = CreateRaidBrowserTabContent,  moduleKey = "rb"   },
    { name = "Help/FAQ",      creator = CreateFAQTabContent,          moduleKey = nil    },
}

local function GetActiveTabDefs()
    local p = RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile
    local active = {}
    for _, def in ipairs(TAB_DEFINITIONS) do
        if def.moduleKey == nil or not p or p["module_" .. def.moduleKey] ~= false then
            active[#active + 1] = def
        end
    end
    return active
end

local activeTabDefs = nil  -- set during CreateMainFrameIfNeeded

function EnsureStatusOverlaySavedVariables()
    if type(RNToolsOverlaySave) ~= "table" then
        RNToolsOverlaySave = {}
    end
    if RNToolsOverlaySave.point        == nil then RNToolsOverlaySave.point        = DEFAULT_POINT end
    if RNToolsOverlaySave.relativePoint == nil then RNToolsOverlaySave.relativePoint = DEFAULT_POINT end
    if RNToolsOverlaySave.x            == nil then RNToolsOverlaySave.x            = 0 end
    if RNToolsOverlaySave.y            == nil then RNToolsOverlaySave.y            = 0 end
    if RNToolsOverlaySave.enabled      == nil then RNToolsOverlaySave.enabled      = true end
end

local function GetPlayerRaidRank()
    if GetNumRaidMembers() == 0 then return nil end
    local playerName = UnitName("player")
    for i = 1, GetNumRaidMembers() do
        local raidMemberName, raidRank = GetRaidRosterInfo(i)
        if raidMemberName == playerName then
            return raidRank
        end
    end
    return nil
end

function IsPlayerLeaderOrAssist()
    local raidRank = GetPlayerRaidRank()
    if raidRank == nil then return false end
    return raidRank > 0
end

function GetRaidChatType()
    if IsPlayerLeaderOrAssist() then return "RAID_WARNING" end
    return "RAID"
end

local function SetFrameShown(frame, shouldShow)
    if not frame then return end
    if shouldShow then frame:Show() else frame:Hide() end
end

local function ShowTab(index)
    activeTabIndex = index
    for i = 1, #tabFrames do
        SetFrameShown(tabFrames[i], i == index)
    end
end

local function CreateTabFrames(contentFrame, onClose)
    local defs = activeTabDefs or GetActiveTabDefs()
    for i = 1, #defs do
        local tabDefinition = defs[i]
        local frame = nil

        if type(tabDefinition.creator) == "function" then
            frame = tabDefinition.creator(contentFrame, onClose)
        else
            frame = CreateFrame("Frame", nil, contentFrame)
            frame:SetAllPoints(contentFrame)
        end

        frame:Hide()
        tabFrames[i] = frame
    end
end

local allScalableFrames = {}  -- every floating window frame; updated on creation, scaled together

-- Global scale applicator — called by Config.lua's slider on MouseUp.
-- Scales the main frame directly (it is NOT in allScalableFrames), every
-- other registered floating window, and saves to BiSRaidConfig + AceDB.
function RNTools_ApplyAddonScale(scaleVal)
    scaleVal = math.max(0.50, math.min(2.0, scaleVal))
    local mf = _G["RNToolsMainFrame"]
    if mf then mf:SetScale(scaleVal) end
    for _, sf in ipairs(allScalableFrames) do
        sf:SetScale(scaleVal)
    end
    if BiSRaidConfig then BiSRaidConfig.addonScale = scaleVal end
    if RNToolsAddon and RNToolsAddon.db then
        RNToolsAddon.db.profile.addonScale = scaleVal
    end
end

-- Allow other files (Config.lua) to register their frames for global scaling.
function RNTools_RegisterScalableFrame(rawFrame)
    for _, sf in ipairs(allScalableFrames) do
        if sf == rawFrame then return end
    end
    allScalableFrames[#allScalableFrames+1] = rawFrame
end

-- Apply all Status Bar settings from the AceDB profile to the live overlay.
-- Called from Config.lua whenever a bar_ setting changes.
function RNTools_ApplyStatusBarSettings()
    if not statusOverlayFrame then return end
    local p = (RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile) or {}
    -- Scale
    statusOverlayFrame:SetScale(p.bar_scale or 1.0)
    -- Lock / unlock drag
    statusOverlayFrame:SetMovable(not p.bar_lock)
    if p.bar_lock then
        statusOverlayFrame:RegisterForDrag()
    else
        statusOverlayFrame:RegisterForDrag("LeftButton")
    end
    -- Pill visibility — re-layout the visible pills left-to-right
    if statusOverlayFrame._pills then
        local prevFrame = nil
        for _, pill in ipairs(statusOverlayFrame._pills) do
            local visible = (p["bar_pill_" .. pill._key] ~= false)
            if visible then
                pill.frame:ClearAllPoints()
                if prevFrame then
                    pill.frame:SetPoint("LEFT", prevFrame, "RIGHT", 4, 0)
                else
                    pill.frame:SetPoint("LEFT", statusOverlayFrame, "LEFT", 6, 0)
                end
                pill.frame:Show()
                prevFrame = pill.frame
            else
                pill.frame:Hide()
            end
        end
    end
end

function CreateMainFrameIfNeeded()
    if mainFrame then return end

    -- AceGUI Frame container (stock Ace3/Blizzard dialog skin)
    local frame = AceGUI:Create("Frame")
    frame:SetTitle("|cffCC2222RealNibba's|r |cff44aaffRaid Tools|r  |cff888888v2.0|r")
    frame:SetWidth(MAIN_FRAME_WIDTH)
    frame:SetHeight(MAIN_FRAME_HEIGHT)
    frame:SetLayout("Fill")
    frame:EnableResize(false)
    frame:SetCallback("OnClose", function(widget) widget:Hide() end)
    frame.frame:SetClampedToScreen(true)
    frame.frame:ClearAllPoints()
    frame.frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)

    mainFrame = frame
    _G[MAIN_FRAME_NAME] = frame.frame
    tinsert(UISpecialFrames, MAIN_FRAME_NAME)

    -- Apply saved scale on load — prefer AceDB (single source of truth) over
    -- BiSRaidConfig so that a Config-window scale change (which writes AceDB)
    -- is always the value that sticks after /reload.
    if not BiSRaidConfig then BiSRaidConfig = {} end
    local savedScale = (RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile.addonScale)
                    or BiSRaidConfig.addonScale
                    or 1.0
    if type(savedScale) ~= "number" or savedScale <= 0 then savedScale = 1.0 end
    BiSRaidConfig.addonScale = savedScale   -- keep them in sync
    frame.frame:SetScale(savedScale)

    ApplyWindowBackdrop(frame)

    -- ── Tab navigation (AceGUI TabGroup, stock Ace3 tab skin) ────────
    activeTabDefs = GetActiveTabDefs()
    local tabList = {}
    for i, tabDefinition in ipairs(activeTabDefs) do
        tabList[i] = { value = i, text = tabDefinition.name }
    end

    local tabGroup = AceGUI:Create("TabGroup")
    tabGroup:SetLayout("Fill")
    tabGroup.noAutoHeight = true

    -- Push the content box down a bit so it doesn't overlap the bottom
    -- edge of the top-row module tab buttons, and stretch it to fill the
    -- rest of the main frame exactly (no leftover gaps on the other sides).
    local origBuildTabs = tabGroup.BuildTabs
    tabGroup.BuildTabs = function(self)
        origBuildTabs(self)
        self.borderoffset = self.borderoffset + 8
        self.border:ClearAllPoints()
        self.border:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 0, -self.borderoffset)
        self.border:SetPoint("BOTTOMRIGHT", self.frame, "BOTTOMRIGHT", 0, 0)
    end

    tabGroup:SetTabs(tabList)
    tabGroup:SetCallback("OnGroupSelected", function(_, _, value) ShowTab(value) end)
    mainTabGroup = tabGroup

    frame:AddChild(tabGroup)

    CreateTabFrames(tabGroup.content, function() frame:Hide() end)

    tabGroup:SelectTab(activeTabIndex)

    frame:Hide()
end

function ToggleMainUI()
    CreateMainFrameIfNeeded()
    if mainFrame.frame:IsShown() then
        mainFrame:Hide()
    else
        mainFrame:Show()
    end
end

-- Open the main window (if needed) and jump straight to a given module tab.
-- Used by status-overlay pills that have no dedicated toggle function.
local function OpenModuleTab(index)
    CreateMainFrameIfNeeded()
    if mainTabGroup then mainTabGroup:SelectTab(index) end
    if not mainFrame.frame:IsShown() then mainFrame:Show() end
end
RNTools_OpenModuleTab = OpenModuleTab

local function SaveStatusOverlayPosition()
    if not statusOverlayFrame then return end
    EnsureStatusOverlaySavedVariables()
    local point, _, relativePoint, xOfs, yOfs = statusOverlayFrame:GetPoint(1)
    RNToolsOverlaySave.point         = point or DEFAULT_POINT
    RNToolsOverlaySave.relativePoint = relativePoint or DEFAULT_POINT
    RNToolsOverlaySave.x             = xOfs or 0
    RNToolsOverlaySave.y             = yOfs or 0
end

local function GetModuleStatusLabel(label, isEnabled)
    local statusText = isEnabled and STATUS_ON_TEXT or STATUS_OFF_TEXT
    return "|cffffffff" .. label .. ": |r" .. statusText
end

local function IsSpammerEnabled()
    if not RT_IsSpammerEnabled then return false end
    return RT_IsSpammerEnabled() and true or false
end

local function IsMasterLooterEnabled()
    if not TML_IsMasterLooterEnabled then return false end
    return TML_IsMasterLooterEnabled() and true or false
end

local function IsAutoReplyEnabled()
    if not RT_IsAutoResponseEnabled then return false end
    return RT_IsAutoResponseEnabled() and true or false
end

function RefreshStatusOverlay()
    if not statusOverlayFrame or not statusOverlayFrame._pills then return end
    for _, pill in ipairs(statusOverlayFrame._pills) do
        local on = pill._checkFn and pill._checkFn()
        if on then
            pill:SetText("|cff20ff60" .. pill._key .. "|r")
        else
            pill:SetText("|cffff4444" .. pill._key .. "|r")
        end
    end
end

local function CreateStatusText(parent, anchorTarget, offsetY, defaultText)
    local text = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    text:SetPoint("TOPLEFT", anchorTarget, "TOPLEFT", 0, offsetY)
    text:SetJustifyH("LEFT")
    text:SetWidth(STATUS_OVERLAY_WIDTH)
    text:SetText(defaultText)
    return text
end

function CreateStatusOverlayIfNeeded()
    if statusOverlayFrame then return end
    EnsureStatusOverlaySavedVariables()

    statusOverlayFrame = CreateFrame("Button", STATUS_OVERLAY_NAME, UIParent)
    statusOverlayFrame:SetSize(STATUS_OVERLAY_WIDTH, STATUS_OVERLAY_HEIGHT)
    statusOverlayFrame:SetFrameStrata("HIGH")
    statusOverlayFrame:SetMovable(true)
    statusOverlayFrame:EnableMouse(true)
    statusOverlayFrame:RegisterForClicks("RightButtonUp")
    statusOverlayFrame:RegisterForDrag("LeftButton")
    statusOverlayFrame:SetClampedToScreen(true)
    statusOverlayFrame:SetPoint(
        RNToolsOverlaySave.point, UIParent, RNToolsOverlaySave.relativePoint,
        RNToolsOverlaySave.x, RNToolsOverlaySave.y)

    -- Status overlay: flat 1px border matching ElvUI skinned frames
    statusOverlayFrame:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false, tileSize = 1, edgeSize = 1,
        insets = {left=1, right=1, top=1, bottom=1},
    })
    do
        local bgR, bgG, bgB, bgA   = RNTS.C.bg[1], RNTS.C.bg[2], RNTS.C.bg[3], RNTS.C.bg[4]
        local brR, brG, brB        = RNTS.C.border[1], RNTS.C.border[2], RNTS.C.border[3]
        if IsAddOnLoaded and IsAddOnLoaded("ElvUI") then
            local E = unpack(ElvUI)
            if E and E.media then
                local bg = E.media.backdropfadecolor
                local br = E.media.bordercolor
                if bg then bgR,bgG,bgB,bgA = bg.r,bg.g,bg.b,bg.a end
                if br then brR,brG,brB     = br.r,br.g,br.b      end
            end
        end
        statusOverlayFrame:SetBackdropColor(bgR, bgG, bgB, bgA)
        statusOverlayFrame:SetBackdropBorderColor(brR, brG, brB)
    end

    local function ShowOverlayTooltip(anchor, extraLine)
        GameTooltip:SetOwner(anchor, "ANCHOR_BOTTOM")
        GameTooltip:ClearLines()
        GameTooltip:AddLine("|cffFFD100RealNibba's Raid Tools|r")
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cff44aaffBT|r  =  BiS Tracker", 1, 1, 1)
        GameTooltip:AddLine("|cff44aaffSPAM|r  =  Spammer", 1, 1, 1)
        GameTooltip:AddLine("|cff44aaffAR|r  =  Auto Reply", 1, 1, 1)
        GameTooltip:AddLine("|cff44aaffML|r  =  Master Looter", 1, 1, 1)
        GameTooltip:AddLine("|cff44aaffAI|r  =  Auto Invite", 1, 1, 1)
        GameTooltip:AddLine("|cff44aaffMS|r  =  MS Changes (open)", 1, 1, 1)
        GameTooltip:AddLine("|cff44aaffRB|r  =  Raid Browser", 1, 1, 1)
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cff20ff60Green|r = Enabled  |cffff4444Red|r = Disabled")
        GameTooltip:AddLine("|cff888888Left-drag to move  |r|cffFFD100Right-click to open addon|r")
        if extraLine then
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine(extraLine, 1, 0.82, 0)
        end
        GameTooltip:Show()
    end

    statusOverlayFrame:SetScript("OnDragStart", function(self) self:StartMoving() end)
    statusOverlayFrame:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing(); SaveStatusOverlayPosition()
    end)
    statusOverlayFrame:SetScript("OnClick", function(_, button)
        if button == "RightButton" then ToggleMainUI() end
    end)

    statusOverlayFrame:SetScript("OnEnter", function(self) ShowOverlayTooltip(self) end)
    statusOverlayFrame:SetScript("OnLeave", function() GameTooltip:Hide() end)

    -- Status pills, in a single row: BT  SPAM  AR  ML  AI  MS  RB
    -- Each pill is clickable: toggles its module directly when a toggle
    -- function exists, otherwise (AR/RB) jumps to that module's tab.
    local labels = {
        { key="BT",   moduleKey="bis",  width=34, checkFn=function() return BiSRaidConfig and BiSRaidConfig.enabled == true end,
          toggleFn=function() if RNTools_ToggleBiSTracker  then RNTools_ToggleBiSTracker()  end end },
        { key="SPAM", moduleKey="spam", width=56, checkFn=IsSpammerEnabled,
          toggleFn=function() if RNTools_ToggleSpammer     then RNTools_ToggleSpammer()     end end },
        { key="AR",   moduleKey="ar",   width=34, checkFn=IsAutoReplyEnabled,
          toggleFn=function() if RNTools_ToggleAutoResponse then RNTools_ToggleAutoResponse() end end, tabName="Auto Reply" },
        { key="ML",   moduleKey="ml",   width=34, checkFn=IsMasterLooterEnabled,
          toggleFn=function() if RNTools_ToggleMasterLooter then RNTools_ToggleMasterLooter() end end },
        { key="AI",   moduleKey="ai",   width=34, checkFn=function() return RNTools_IsAutoInviteEnabled and RNTools_IsAutoInviteEnabled() end,
          toggleFn=function() if RNTools_ToggleAutoInvite   then RNTools_ToggleAutoInvite()   end end },
        { key="MS",   moduleKey="ms",   width=34, checkFn=function() return RNTools_IsMSChangesOpen and RNTools_IsMSChangesOpen() end,
          toggleFn=function() if RNTools_ToggleMSChanges    then RNTools_ToggleMSChanges()    end end },
        { key="RB",   moduleKey="rb",   width=34, checkFn=function() return RNTools_IsRaidBrowserEnabled and RNTools_IsRaidBrowserEnabled() end,
          toggleFn=function() if RNTools_SetRBEnabled then RNTools_SetRBEnabled(not (RNTools_IsRaidBrowserEnabled and RNTools_IsRaidBrowserEnabled())) end end, tabName="Raid Browser" },
    }

    statusOverlayFrame._pills = {}
    local prevPillFrame = nil
    local pp = (RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile) or {}
    for i, def in ipairs(labels) do
        if def.moduleKey and pp["module_" .. def.moduleKey] == false then
            -- Module disabled — skip pill entirely
        else
        local pill = AceGUI:Create("InteractiveLabel")
        pill:SetFontObject(GameFontNormalLarge)
        pill:SetJustifyH("CENTER")
        pill:SetWidth(def.width)
        pill.frame:SetParent(statusOverlayFrame)
        pill.frame:SetFrameStrata("HIGH")
        pill.frame:ClearAllPoints()
        if prevPillFrame then
            pill.frame:SetPoint("LEFT", prevPillFrame, "RIGHT", 4, 0)
        else
            pill.frame:SetPoint("LEFT", statusOverlayFrame, "LEFT", 6, 0)
        end
        pill.frame:Show()

        pill._key     = def.key
        pill._checkFn = def.checkFn
        pill:SetText("|cffff4444" .. def.key .. "|r")

        local extraLine
        if def.toggleFn then
            extraLine = "Click to toggle " .. def.key
        elseif def.tabIndex then
            extraLine = "Click to open " .. def.key .. " tab"
        end
        pill:SetCallback("OnEnter", function() ShowOverlayTooltip(pill.frame, extraLine) end)
        pill:SetCallback("OnLeave", function() GameTooltip:Hide() end)

        if def.toggleFn or def.tabName then
            pill:SetCallback("OnClick", function()
                local ppp = (RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile) or {}
                if def.toggleFn and ppp.bar_click_toggle ~= false then
                    def.toggleFn()
                    RefreshStatusOverlay()
                elseif def.tabName and activeTabDefs then
                    for ti, td in ipairs(activeTabDefs) do
                        if td.name == def.tabName then OpenModuleTab(ti); break end
                    end
                end
            end)
        end

        statusOverlayFrame._pills[#statusOverlayFrame._pills + 1] = pill
        prevPillFrame = pill.frame
        end -- close the else block for module check
    end

    -- resize overlay to fit content
    statusOverlayFrame:SetSize(STATUS_OVERLAY_WIDTH, STATUS_OVERLAY_HEIGHT)

    RefreshStatusOverlay()

    -- Always show the overlay on load, even if it was left hidden in a
    -- prior session. The runtime toggle (/rnt overlay, minimap right-click)
    -- still works for the rest of this session.
    RNToolsOverlaySave.enabled = true
    statusOverlayFrame:Show()

    -- Quick Access button on the right end of the status bar
    local qaBtn = AceGUI:Create("Button")
    qaBtn:SetText("Quick Access")
    qaBtn:SetWidth(108)
    qaBtn.frame:SetParent(statusOverlayFrame)
    qaBtn.frame:SetFrameStrata("HIGH")
    qaBtn.frame:ClearAllPoints()
    qaBtn.frame:SetPoint("RIGHT", statusOverlayFrame, "RIGHT", -2, 0)
    qaBtn.frame:SetHeight(STATUS_OVERLAY_HEIGHT)
    qaBtn.frame:Show()
    qaBtn:SetCallback("OnClick", function() RNTools_ToggleQuickAccess() end)
    qaBtn:SetCallback("OnEnter", function()
        GameTooltip:SetOwner(qaBtn.frame, "ANCHOR_BOTTOM")
        GameTooltip:ClearLines()
        GameTooltip:AddLine("|cffFFD100Quick Access|r")
        GameTooltip:AddLine("Click to open / close", 1,1,1)
        GameTooltip:Show()
    end)
    qaBtn:SetCallback("OnLeave", function() GameTooltip:Hide() end)

    -- Auto-refresh the overlay every 0.5s so BT and other toggles
    -- always reflect the current state without needing manual hook calls.
    local refreshTimer = 0
    statusOverlayFrame:SetScript("OnUpdate", function(self, elapsed)
        refreshTimer = refreshTimer + elapsed
        if refreshTimer >= 0.5 then
            refreshTimer = 0
            RefreshStatusOverlay()
        end
    end)
end

-------------------------------------------------------------------------------
-- Quick Access Window
-------------------------------------------------------------------------------
local QA_frame    = nil
local QA_miniRM   = nil   -- mini roll manager frame
-- QASetBG/QABorder hoisted to top (see RNTS inline block)

-- QABtn hoisted to top (see RNTS inline block)

-- QAHdr hoisted to top (see RNTS inline block)

-- Small AceGUI helpers for the Quick Access panel's content list
local function QAAceHeading(text)
    local h = AceGUI:Create("Heading")
    h:SetText(text)
    return h
end

local function QAAceButton(label, relWidth, onClick)
    local btn = AceGUI:Create("Button")
    btn:SetText(label)
    if relWidth then btn:SetRelativeWidth(relWidth) else btn:SetFullWidth(true) end
    btn:SetCallback("OnClick", onClick)
    return btn
end

-- Spec tracking toggle — true by default; toggled via checkbox in Mini RM
RNTools_MiniTrackSpec = true

-- ── Mini Roll Manager window ──────────────────────────────────────────
function BuildMiniRollManager()
    if QA_miniRM then
        QA_miniRM:Show()
        return
    end

    local W = 400
    local frame = AceGUI:Create("Frame")
    frame:SetTitle("Mini Roll Manager")
    frame:SetWidth(W)
    frame:SetHeight(530)
    frame:EnableResize(false)
    frame.frame:SetClampedToScreen(true)
    frame.frame:SetFrameStrata("HIGH")
    frame.frame:SetScale(BiSRaidConfig and BiSRaidConfig.addonScale or 1.0)
    allScalableFrames[#allScalableFrames+1] = frame.frame
    ApplyWindowBackdrop(frame)

    -- Restore saved position
    if type(RNToolsMinRMSave) == "table" and RNToolsMinRMSave.x then
        frame.frame:ClearAllPoints()
        frame.frame:SetPoint(RNToolsMinRMSave.point or "CENTER", UIParent,
            RNToolsMinRMSave.rPoint or "CENTER",
            RNToolsMinRMSave.x, RNToolsMinRMSave.y)
    else
        frame.frame:SetPoint("CENTER", UIParent, "CENTER", 200, 0)
    end

    -- Save position on drag stop and close
    frame.frame:HookScript("OnDragStop", function(self)
        if type(RNToolsMinRMSave) ~= "table" then RNToolsMinRMSave = {} end
        local p, _, rp, x, y = self:GetPoint(1)
        RNToolsMinRMSave.point=p; RNToolsMinRMSave.rPoint=rp
        RNToolsMinRMSave.x=x; RNToolsMinRMSave.y=y
    end)
    frame:SetCallback("OnClose", function(widget)
        if type(RNToolsMinRMSave) ~= "table" then RNToolsMinRMSave = {} end
        local p, _, rp, x, y = widget.frame:GetPoint(1)
        RNToolsMinRMSave.point=p; RNToolsMinRMSave.rPoint=rp
        RNToolsMinRMSave.x=x; RNToolsMinRMSave.y=y
        widget:Hide()
    end)

    -- Hide AceGUI's bottom "Close" button and status bar; use WoW X button instead
    HideAceCloseBtn(frame)
    frame.statustext:GetParent():Hide()
    -- AceGUI anchors content BOTTOMRIGHT at y=40 for status bar; reclaim that space
    frame.content:SetPoint("BOTTOMRIGHT", frame.frame, "BOTTOMRIGHT", -17, 12)
    local miniRM_xBtn = CreateFrame("Button", nil, frame.frame, "UIPanelCloseButton")
    miniRM_xBtn:SetPoint("TOPRIGHT", frame.frame, "TOPRIGHT", -5, -5)
    miniRM_xBtn:SetScript("OnClick", function() frame:Hide() end)

    QA_miniRM = frame
    local c  = frame.content   -- AceGUI Frame content area
    local CW = W - 34          -- content width = 406
    local P  = 8

    -- OnShow
    frame.frame:HookScript("OnShow", function()
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
    end)

    local y = -12  -- start below X button (X extends to content y=-10, leave 2px gap)

    -- ── ML/RL status ────────────────────────────────────────
    local miniPermFS = c:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    miniPermFS:SetPoint("TOPLEFT", c, "TOPLEFT", P+3, y-2)
    if RM_HasPermission and RM_HasPermission() then
        miniPermFS:SetText("|cff20ff60ML/RL|r")
    else
        miniPermFS:SetText("|cffff4444Not RL/ML|r")
    end
    y = y - 28   -- extra gap so CD button / permFS don't crowd the loot header

    -- ── Item List ───────────────────────────────────────────
    local lootHdrFS = c:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    lootHdrFS:SetPoint("TOPLEFT",  c, "TOPLEFT",  P+3, y)
    lootHdrFS:SetPoint("TOPRIGHT", c, "TOPRIGHT", -P-3, y)
    lootHdrFS:SetText("Loot  — click to select for roll")
    lootHdrFS:SetTextColor(1, 0.82, 0, 1)
    y = y - 18

    -- Selected item display
    local selFS = c:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    selFS:SetPoint("TOPLEFT",  c, "TOPLEFT",  P+3, y)
    selFS:SetPoint("TOPRIGHT", c, "TOPRIGHT", -P-3, y)
    selFS:SetText("|cff666666No item selected|r")
    selFS:SetNonSpaceWrap(false)
    y = y - 18

    -- Item scroll frame (5 rows visible: 5 * 26px = 130)
    local ITEM_H = 130
    local iSF = CreateFrame("ScrollFrame", "RNMiniItemSF", c, "UIPanelScrollFrameTemplate")
    iSF:SetPoint("TOPLEFT",  c, "TOPLEFT",   P,     y)
    iSF:SetPoint("TOPRIGHT", c, "TOPRIGHT",  -P-18, y)
    iSF:SetHeight(ITEM_H)
    local iSC = CreateFrame("Frame", nil, iSF)
    iSC:SetHeight(800)
    iSC:SetWidth(CW - P*2 - 20)
    iSF:SetScrollChild(iSC)
    iSF:SetScript("OnSizeChanged", function(s, w)
        if w and w > 10 then iSC:SetWidth(w) end
    end)
    iSF:SetScript("OnScrollRangeChanged", function(self, xr, yr)
        local bar  = _G["RNMiniItemSFScrollBar"]
        if not bar then return end
        yr = math.floor(yr+0.5)
        bar:SetMinMaxValues(0,yr); bar:SetValue(math.min(bar:GetValue(),yr))
        local up   = _G["RNMiniItemSFScrollBarScrollUpButton"]
        local down = _G["RNMiniItemSFScrollBarScrollDownButton"]
        local v    = bar:GetValue()
        if up   then if v>0  then up:Enable()   else up:Disable()   end end
        if down then if v<yr then down:Enable() else down:Disable() end end
    end)

    -- Mini item buttons pool (separate from full RM)
    local miniItemBtns = {}
    local miniSelRef   = nil   -- selected item reference (not index, survives re-sort)

    -- Global setter so parser can sync mini selection from outside
    RNTools_SetMiniSelection = function(itemRef)
        miniSelRef = itemRef
        if itemRef and selFS then
            selFS:SetText("Selected: " .. (itemRef.link or ""))
        end
        -- MiniRefreshItems will sync on next OnUpdate tick (0.5s)
    end

    local function MiniRefreshItems()
        for i = 1, #miniItemBtns do miniItemBtns[i]:Hide() end
        if not RM_items then return end
        local ROW_H = 24
        local count = RM_itemCount or 0
        -- Sorted display: 1) currently rolling item, 2) pending newest→oldest, 3) done
        local displayOrder = {}
        for i = count, 1, -1 do
            local it = RM_items[i]
            if it and (miniSelRef == it) and RM_recording then
                displayOrder[#displayOrder+1] = i
            end
        end
        for i = count, 1, -1 do
            local it = RM_items[i]
            if it and not it.awarded and not ((miniSelRef == it) and RM_recording) then
                displayOrder[#displayOrder+1] = i
            end
        end
        for i = count, 1, -1 do
            local it = RM_items[i]
            if it and it.awarded then displayOrder[#displayOrder+1] = i end
        end
        for displayRow, itemIdx in ipairs(displayOrder) do
            local i = itemIdx
            local item = RM_items[i]
            local btn = miniItemBtns[i]
            if not btn then
                btn = CreateFrame("Button", nil, iSC)
                btn:SetHeight(ROW_H)
                QASetBG(btn, 0.10,0.10,0.10,0.95); QABorder(btn,0.40,0.40,0.40,0.8)
                local icon = btn:CreateTexture(nil,"ARTWORK")
                icon:SetSize(20,20); icon:SetPoint("LEFT",btn,"LEFT",3,0); btn._icon=icon
                local nFS = btn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
                nFS:SetPoint("LEFT",btn,"LEFT",27,0); nFS:SetPoint("RIGHT",btn,"RIGHT",-60,0)
                nFS:SetJustifyH("LEFT"); btn._nFS = nFS
                local tFS = btn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
                tFS:SetPoint("RIGHT",btn,"RIGHT",-4,0); tFS:SetJustifyH("RIGHT"); btn._tFS=tFS
                btn:SetScript("OnClick", function(self)
                    local ref = self._itemRef
                    if not ref then return end
                    -- Block ALL selection changes while roll in progress
                    if RM_recording then
                        DEFAULT_CHAT_FRAME:AddMessage("|cffFF8C00[Roll Manager]:|r Roll in progress - stop the roll first.")
                        return
                    end
                    miniSelRef = ref
                    if selFS then selFS:SetText("Selected: "..ref.link) end
                    -- Always clear rolls first; repopulate only for done items with history
                    RM_rolls = {}; RM_rollCount = 0; RM_winner = nil
                    if ref.awarded and ref.link and RM_GetRollHistory then
                        local hist = RM_GetRollHistory(ref.link)
                        if hist and #hist > 0 then
                            local last = hist[1]
                            for _, r in ipairs(last.rolls or {}) do
                                RM_rollCount = RM_rollCount + 1
                                RM_rolls[RM_rollCount] = {name=r.name, roll=r.roll}
                            end
                            if RM_rollCount > 0 then
                                RM_winner = ref.winner or RM_rolls[1].name
                            end
                        end
                    end
                    if RM_RefreshRollList then RM_RefreshRollList() end
                    if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
                    MiniRefreshItems()
                end)
                btn:SetScript("OnEnter", function(self)
                    QASetBG(self,0.22,0.22,0.22,1)
                    local ref = self._itemRef
                    if ref and ref.link then
                        GameTooltip:SetOwner(self,"ANCHOR_RIGHT")
                        GameTooltip:SetHyperlink(ref.link)
                        GameTooltip:Show()
                    end
                end)
                btn:SetScript("OnLeave", function(self)
                    GameTooltip:Hide()
                    local ref = self._itemRef
                    if not ref then return end
                    if miniSelRef == ref then
                        QASetBG(self, 0.04, 0.20, 0.08, 1)
                    elseif ref.awarded then
                        QASetBG(self, 0.03, 0.03, 0.03, 0.7)
                    else
                        local dr = self._displayRow or 1
                        QASetBG(self, dr%2==0 and 0.10 or 0.13,
                                      dr%2==0 and 0.10 or 0.13,
                                      dr%2==0 and 0.10 or 0.13, 0.95)
                    end
                end)
                miniItemBtns[i] = btn
            end
            btn._displayRow = displayRow
            btn:ClearAllPoints()
            btn:SetPoint("TOPLEFT",  iSC,"TOPLEFT",  0,-(displayRow-1)*(ROW_H+2))
            btn:SetPoint("TOPRIGHT", iSC,"TOPRIGHT", 0,-(displayRow-1)*(ROW_H+2))
            btn._itemRef = item
            local isSel = (miniSelRef == item)  -- ref-based, survives re-sort
            if isSel then
                QASetBG(btn,0.04,0.20,0.08,1); QABorder(btn,0.10,0.80,0.30,1)
            elseif item.awarded then
                QASetBG(btn,0.03,0.03,0.03,0.7); QABorder(btn,0.15,0.15,0.15,0.5)
            else
                QASetBG(btn,displayRow%2==0 and 0.10 or 0.13,
                             displayRow%2==0 and 0.10 or 0.13,
                             displayRow%2==0 and 0.10 or 0.13,0.95)
                QABorder(btn,0.40,0.40,0.40,0.8)
            end
            local qc = ({[0]="ff9d9d9d",[1]="ffffffff",[2]="ff1eff00",
                         [3]="ff0070dd",[4]="ffa335ee",[5]="ffff8000"})[item.quality] or "ffffffff"
            if item.awarded then
                local winText  = item.winner or "?"
                local winColor = winText=="Timeout" and "ff4444" or winText=="No Winner" and "888888" or "20ff60"
                btn._nFS:SetText("|cff555555[Done] "..item.name.." - |cff"..winColor..winText.."|r")
                btn._tFS:SetText("")
            elseif isSel and RM_recording then
                btn._nFS:SetText("|c"..qc..item.name.."|r |cffFFD100- Rolling...|r")
                btn._tFS:SetText("")
            else
                btn._nFS:SetText("|c"..qc..item.name.."|r")
                btn._tFS:SetText(item.tradeTime or "|cff444444--|r")
            end
            btn._icon:SetTexture(item.texture)
            btn:Show()
        end
    end

    y = y - ITEM_H - P

    -- ── Roll Buttons (2x2 grid, AceGUI SimpleGroup rows) ────────────────
    local rollHdrFS = c:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    rollHdrFS:SetPoint("TOPLEFT", c, "TOPLEFT", P+3, y)
    rollHdrFS:SetText("Start Roll")
    rollHdrFS:SetTextColor(1, 0.82, 0, 1)
    y = y - 22

    local rowW = CW - P*2

    -- Row 1: MS | OS
    local msOsRow = AceGUI:Create("SimpleGroup")
    msOsRow:SetLayout("Flow"); msOsRow.noAutoHeight = true; msOsRow:SetWidth(rowW)
    msOsRow.frame:SetParent(c); msOsRow.frame:ClearAllPoints()
    msOsRow.frame:SetPoint("TOPLEFT", c, "TOPLEFT", P, y)
    msOsRow.frame:SetHeight(26); msOsRow.frame:Show()
    local msBtn = AceGUI:Create("Button"); msBtn:SetText("MS"); msBtn:SetRelativeWidth(0.5)
    msBtn:SetCallback("OnClick", function()
        if miniSelRef then
            for idx=1,(RM_itemCount or 0) do
                if RM_items[idx]==miniSelRef then RM_selectedIdx=idx; break end
            end
        end
        if RM_AnnounceRoll then RM_AnnounceRoll("MS") end
    end)
    local osBtn = AceGUI:Create("Button"); osBtn:SetText("OS"); osBtn:SetRelativeWidth(0.5)
    osBtn:SetCallback("OnClick", function()
        if miniSelRef then
            for idx=1,(RM_itemCount or 0) do
                if RM_items[idx]==miniSelRef then RM_selectedIdx=idx; break end
            end
        end
        if RM_AnnounceRoll then RM_AnnounceRoll("OS") end
    end)
    msOsRow:AddChild(msBtn); msOsRow:AddChild(osBtn)
    y = y - 30

    -- Row 2: Free | Stop
    local frStopRow = AceGUI:Create("SimpleGroup")
    frStopRow:SetLayout("Flow"); frStopRow.noAutoHeight = true; frStopRow:SetWidth(rowW)
    frStopRow.frame:SetParent(c); frStopRow.frame:ClearAllPoints()
    frStopRow.frame:SetPoint("TOPLEFT", c, "TOPLEFT", P, y)
    frStopRow.frame:SetHeight(26); frStopRow.frame:Show()
    local frBtn = AceGUI:Create("Button"); frBtn:SetText("Free"); frBtn:SetRelativeWidth(0.5)
    frBtn:SetCallback("OnClick", function()
        if miniSelRef then
            for idx=1,(RM_itemCount or 0) do
                if RM_items[idx]==miniSelRef then RM_selectedIdx=idx; break end
            end
        end
        if RM_AnnounceRoll then RM_AnnounceRoll("Free") end
    end)
    local stopBtn = AceGUI:Create("Button"); stopBtn:SetText("Stop Roll"); stopBtn:SetRelativeWidth(0.5)
    stopBtn:SetCallback("OnClick", function()
        if miniSelRef then
            for idx=1,(RM_itemCount or 0) do
                if RM_items and RM_items[idx]==miniSelRef then
                    RM_selectedIdx=idx; break
                end
            end
        end
        if RM_StopRoll then RM_StopRoll() end
    end)
    frStopRow:AddChild(frBtn); frStopRow:AddChild(stopBtn)
    y = y - 30

    -- Track Spec toggle (between roll buttons and status line)
    local trackSpecChk = AceGUI:Create("CheckBox")
    trackSpecChk:SetLabel("|cffaaaaaa Track Spec in rolls|r")
    trackSpecChk:SetValue(RNTools_MiniTrackSpec ~= false)
    trackSpecChk.frame:SetParent(c); trackSpecChk.frame:ClearAllPoints()
    trackSpecChk.frame:SetPoint("TOPLEFT", c, "TOPLEFT", P, y)
    trackSpecChk.frame:SetHeight(20); trackSpecChk.frame:Show()
    trackSpecChk:SetCallback("OnValueChanged", function(_, _, val)
        RNTools_MiniTrackSpec = val
        if RNTools_MiniRefreshRolls then RNTools_MiniRefreshRolls() end
    end)
    RNTools_MiniTrackSpecCB = trackSpecChk  -- exposed so Config can sync it live
    y = y - 22

    -- CD button — top-right corner, same Y as ML/RL label (AceGUI Button)
    local MiniCDDDMenu = CreateFrame("Frame","RNMiniCDDDMenu",UIParent,"UIDropDownMenuTemplate")
    local miniCDOpen   = nil
    local cdBtn = AceGUI:Create("Button")
    local _cd0 = RM_cdDuration or 20
    cdBtn:SetText(_cd0 == 0 and "CD: --" or ("CD: ".._cd0.."s"))
    cdBtn:SetWidth(110)
    cdBtn.frame:SetParent(c); cdBtn.frame:ClearAllPoints()
    cdBtn.frame:SetPoint("TOPRIGHT", c, "TOPRIGHT", -3, -14)
    cdBtn.frame:SetHeight(24); cdBtn.frame:Show()
    cdBtn:SetCallback("OnClick", function(widget)
        if miniCDOpen == widget then
            CloseDropDownMenus(); miniCDOpen = nil; return
        end
        miniCDOpen = widget
        local menu = {}
        for _, secs in ipairs({0,10,15,20,25,30}) do
            local capSecs = secs
            menu[#menu+1] = {
                text    = capSecs == 0 and "(none)" or capSecs.." sec",
                checked = (RM_cdDuration == capSecs),
                func    = function()
                    RM_cdDuration = capSecs
                    miniCDOpen = nil
                    if type(RTRollManagerSave) ~= "table" then RTRollManagerSave = {} end
                    RTRollManagerSave.cdDuration = capSecs
                    if RNTools_SyncCDLabel then RNTools_SyncCDLabel()
                    else cdBtn:SetText(capSecs == 0 and "CD: --" or "CD: "..capSecs.."s") end
                end,
            }
        end
        EasyMenu(menu, MiniCDDDMenu, widget.frame, 0, 0, "MENU")
    end)

    -- Status
    local miniStatusFS = c:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    miniStatusFS:SetPoint("TOPLEFT", c, "TOPLEFT", P+3, y)
    miniStatusFS:SetText("|cff666666Idle|r")
    y = y - 18

    -- ── Roll List ────────────────────────────────────────────
    local rollListHdrFS = c:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    rollListHdrFS:SetPoint("TOPLEFT", c, "TOPLEFT", P+3, y)
    rollListHdrFS:SetText("Rolls  (click to select winner)")
    rollListHdrFS:SetTextColor(1, 0.82, 0, 1)
    y = y - 22

    local rSF = CreateFrame("ScrollFrame", "RNMiniRollSF", c, "UIPanelScrollFrameTemplate")
    rSF:SetPoint("TOPLEFT",     c, "TOPLEFT",     P,     y)
    rSF:SetPoint("BOTTOMRIGHT", c, "BOTTOMRIGHT", -P-18, 42)
    local rSC = CreateFrame("Frame", nil, rSF)
    rSC:SetHeight(600)
    rSC:SetWidth(CW - P*2 - 20)
    rSF:SetScrollChild(rSC)
    rSF:SetScript("OnSizeChanged", function(s, w)
        if w and w > 10 then rSC:SetWidth(w) end
    end)
    rSF:SetScript("OnScrollRangeChanged", function(self, xr, yr)
        local bar = _G["RNMiniRollSFScrollBar"]
        if not bar then return end
        yr = math.floor(yr+0.5)
        bar:SetMinMaxValues(0,yr); bar:SetValue(math.min(bar:GetValue(),yr))
        local up   = _G["RNMiniRollSFScrollBarScrollUpButton"]
        local down = _G["RNMiniRollSFScrollBarScrollDownButton"]
        local v    = bar:GetValue()
        if up   then if v>0  then up:Enable()   else up:Disable()   end end
        if down then if v<yr then down:Enable() else down:Disable() end end
    end)

    RNTools_MiniRefreshItems = function() MiniRefreshItems(); iSF:SetVerticalScroll(0) end

    -- Mini roll row buttons pool (separate display, reads shared RM_rolls)
    local miniRollBtns = {}
    local function MiniRefreshRolls()
        cdBtn:SetText("CD: "..(RM_cdDuration or 20).."s")  -- keep in sync with main RM
        for i = 1, #miniRollBtns do miniRollBtns[i]:Hide() end
        if not RM_rolls then return end
        for i = 1, (RM_rollCount or 0) do
            local entry = RM_rolls[i]; if not entry then break end
            local btn = miniRollBtns[i]
            if not btn then
                btn = CreateFrame("Button", nil, rSC)
                btn:SetHeight(22)
                QASetBG(btn,0.10,0.10,0.10,0.95); QABorder(btn,0.40,0.40,0.40,0.8)
                local nFS = btn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
                nFS:SetPoint("LEFT",btn,"LEFT",6,0); btn._nFS=nFS
                local rFS = btn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
                rFS:SetPoint("RIGHT",btn,"RIGHT",-6,0); btn._rFS=rFS
                local sFS = btn:CreateFontString(nil,"OVERLAY","GameFontNormal")
                sFS:SetPoint("LEFT",nFS,"RIGHT",4,0)
                sFS:SetTextColor(1,0.82,0,1); btn._sFS=sFS
                local captI = i
                btn:SetScript("OnClick", function()
                    if RM_rolls and RM_rolls[captI] then
                        if RM_winner ~= nil then RM_winner = RM_rolls[captI].name end
                        MiniRefreshRolls()
                        if RM_RefreshRollList then RM_RefreshRollList() end
                    end
                end)
                btn:SetScript("OnEnter",function() QASetBG(btn,0.22,0.22,0.22,1) end)
                btn:SetScript("OnLeave",function()
                    local isW = RM_winner and RM_rolls[captI] and RM_rolls[captI].name==RM_winner
                    if isW then QASetBG(btn,0.04,0.20,0.08,1)
                    else QASetBG(btn,i%2==0 and 0.10 or 0.13,
                                      i%2==0 and 0.10 or 0.13,
                                      i%2==0 and 0.10 or 0.13,0.95) end
                end)
                miniRollBtns[i] = btn
            end
            btn:ClearAllPoints()
            btn:SetPoint("TOPLEFT",  rSC,"TOPLEFT",  0,-(i-1)*24)
            btn:SetPoint("TOPRIGHT", rSC,"TOPRIGHT", 0,-(i-1)*24)
            local isW = (RM_winner and entry.name==RM_winner)
            if isW then QASetBG(btn,0.04,0.20,0.08,1); QABorder(btn,0.10,0.80,0.30,1)
            else
                QASetBG(btn,i%2==0 and 0.10 or 0.13,
                             i%2==0 and 0.10 or 0.13,
                             i%2==0 and 0.10 or 0.13,0.95)
                QABorder(btn,0.40,0.40,0.40,0.8)
            end
            local _,cls = UnitClass(entry.name)
            local cc = ({DEATHKNIGHT="ffc41f3b",DRUID="ffff7d0a",HUNTER="ffabd473",
                         MAGE="ff69ccf0",PALADIN="fff58cba",PRIEST="ffffffff",
                         ROGUE="fffff569",SHAMAN="ff0070de",WARLOCK="ff9482c9",
                         WARRIOR="ffc79c6e"})[cls] or "ffffffff"
            -- Spec: MS change takes priority, then spec DB (skipped if tracking off)
            local spec = nil
            if RNTools_MiniTrackSpec ~= false then
                if msChanges and msChanges[entry.name] then
                    spec = msChanges[entry.name]
                elseif RNTools_GetPlayerSpec then
                    local s = RNTools_GetPlayerSpec(entry.name)
                    if s and s ~= "" then spec = s end
                end
            end
            if entry.ineligible then
                btn._nFS:SetText("|c"..cc..entry.name.."|r |cffff4444["..(entry.ineligSpec or "?").."]|r")
                btn._rFS:SetText("|cff888888"..entry.roll.."|r")
                btn._sFS:SetText("|cffff4444x|r")
                QASetBG(btn,0.15,0.05,0.05,0.95); QABorder(btn,0.50,0.15,0.15,1)
            else
                local nameStr = "|c"..cc..entry.name.."|r"
                if spec then nameStr = nameStr.." |cffaaaaaa["..spec.."]|r" end
                btn._nFS:SetText(nameStr)
                btn._rFS:SetText("|cffFFD100"..entry.roll.."|r")
                btn._sFS:SetText(isW and "(W)" or "")
            end
            btn:Show()
        end
        -- Status
        if miniStatusFS then
            if RM_winner and (RM_rollCount or 0)>0 then
                local wRoll=0; for _,e in ipairs(RM_rolls) do if e.name==RM_winner then wRoll=e.roll;break end end
                miniStatusFS:SetText("Winner: |cff20ff60"..RM_winner.."|r ("..wRoll..")")
            elseif RM_recording then
                miniStatusFS:SetText("|cffFFD100["..(RM_rollLabel or "Roll").."] Open — /roll 1-100|r")
            else
                miniStatusFS:SetText("|cff666666Idle|r")
            end
        end
    end

    -- ── Bottom action buttons (equal-width AceGUI, SimpleGroup row) ──────
    local bottomRow = AceGUI:Create("SimpleGroup")
    bottomRow:SetLayout("Flow"); bottomRow.noAutoHeight = true; bottomRow:SetWidth(CW - P*2)
    bottomRow.frame:SetParent(c); bottomRow.frame:ClearAllPoints()
    bottomRow.frame:SetPoint("BOTTOMLEFT", c, "BOTTOMLEFT", P, P)
    bottomRow.frame:SetHeight(26); bottomRow.frame:Show()

    local awardBtn = AceGUI:Create("Button")
    awardBtn:SetText("Award Winner"); awardBtn:SetRelativeWidth(0.33)
    awardBtn:SetCallback("OnClick", function()
        if not (RM_HasPermission and RM_HasPermission()) then return end
        if miniSelRef then
            for idx=1,(RM_itemCount or 0) do
                if RM_items[idx]==miniSelRef then RM_selectedIdx=idx; break end
            end
        end
        if RM_Award then RM_Award() end
        miniSelRef = nil
        if selFS then selFS:SetText("|cff666666No item selected|r") end
        MiniRefreshItems(); MiniRefreshRolls()
    end)

    local miniHistBtn = AceGUI:Create("Button")
    miniHistBtn:SetText("History"); miniHistBtn:SetRelativeWidth(0.33)
    miniHistBtn:SetCallback("OnClick", function()
        if RNTools_ToggleHistory then RNTools_ToggleHistory() end
    end)

    local clearItemsMiniBtn = AceGUI:Create("Button")
    clearItemsMiniBtn:SetText("Clear Items"); clearItemsMiniBtn:SetRelativeWidth(0.33)
    clearItemsMiniBtn:SetCallback("OnClick", function()
        if RM_recording then
            DEFAULT_CHAT_FRAME:AddMessage("|cffFF8C00[Roll Manager]:|r Stop the roll first.")
            return
        end
        RM_items = {}; RM_itemCount = 0; RM_selectedIdx = nil
        miniSelRef = nil
        if selFS then selFS:SetText("|cff666666No item selected|r") end
        RM_rolls = {}; RM_rollCount = 0; RM_winner = nil
        if RM_selectedFS then RM_selectedFS:SetText("|cff666666No item selected|r") end
        if RM_SaveItemList then RM_SaveItemList() end
        if RM_RefreshItemList then RM_RefreshItemList() end
        if RNTools_MiniRefreshItems then RNTools_MiniRefreshItems() end
        MiniRefreshRolls()
    end)

    bottomRow:AddChild(awardBtn); bottomRow:AddChild(miniHistBtn); bottomRow:AddChild(clearItemsMiniBtn)

    RNTools_MiniRefreshRolls = function() MiniRefreshRolls() end

    -- OnUpdate: sync every 0.5s
    local syncTick = 0; local lastRollCount = 0
    frame.frame:SetScript("OnUpdate", function(self, elapsed)
        syncTick = syncTick + elapsed
        if syncTick < 0.5 then return end
        syncTick = 0
        -- Refresh ML/RL permission text
        if miniPermFS then
            if RM_HasPermission and RM_HasPermission() then
                miniPermFS:SetText("|cff20ff60ML/RL|r")
            else
                miniPermFS:SetText("|cffff4444Not RL/ML|r")
            end
        end
        local currentRollCount = RM_rollCount or 0
        if currentRollCount ~= lastRollCount then
            lastRollCount = currentRollCount; MiniRefreshRolls()
        end
        cdBtn:SetText("CD: "..(RM_cdDuration or 20).."s")
    end)

    MiniRefreshItems()
    MiniRefreshRolls()
    frame:Show()
end

-- ── Quick Access main window ──────────────────────────────────────────
local function BuildQuickAccess()
    if QA_frame then return end

    local p = (RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile) or {}
    local W = 330

    local frame = AceGUI:Create("Frame")
    frame:SetTitle("|cffFFD100Quick Access|r")
    frame:SetWidth(W)
    frame:EnableResize(false)
    frame.frame:SetClampedToScreen(true)
    frame.frame:SetScale(BiSRaidConfig and BiSRaidConfig.addonScale or 1.0)
    allScalableFrames[#allScalableFrames+1] = frame.frame

    frame:SetCallback("OnClose", function(widget)
        if type(RNToolsOverlaySave) == "table" then
            local p, _, rp, x, y = widget.frame:GetPoint(1)
            RNToolsOverlaySave.qa_point  = p
            RNToolsOverlaySave.qa_rPoint = rp
            RNToolsOverlaySave.qa_x      = x
            RNToolsOverlaySave.qa_y      = y
        end
        widget:Hide()
    end)

    -- Restore saved position (or default near the left of center)
    frame.frame:ClearAllPoints()
    if type(RNToolsOverlaySave) == "table" and RNToolsOverlaySave.qa_x then
        frame.frame:SetPoint(RNToolsOverlaySave.qa_point or "TOPLEFT", UIParent,
            RNToolsOverlaySave.qa_rPoint or "BOTTOMLEFT",
            RNToolsOverlaySave.qa_x, RNToolsOverlaySave.qa_y)
    else
        frame.frame:SetPoint("CENTER", UIParent, "CENTER", -300, 0)
    end

    -- Hide AceGUI's bottom "Close" button and status bar; use WoW X button instead
    HideAceCloseBtn(frame)
    frame.statustext:GetParent():Hide()
    local qa_xBtn = CreateFrame("Button", nil, frame.frame, "UIPanelCloseButton")
    qa_xBtn:SetPoint("TOPRIGHT", frame.frame, "TOPRIGHT", -5, -5)
    qa_xBtn:SetScript("OnClick", function() frame:Hide() end)

    QA_frame = frame
    ApplyWindowBackdrop(frame, true)

    -- Lock: unregister drag events rather than SetMovable(false).
    -- SetMovable(false) on an AceGUI Frame breaks its own OnDragStart script
    -- (which calls StartMoving unconditionally), causing a Lua error on drag.
    if p.qa_lock then
        frame.frame:RegisterForDrag()        -- no events → drag never fires
    else
        frame.frame:RegisterForDrag("LeftButton")
    end

    -- Manual yOffset anchoring inside the AceGUI Frame's content area (same
    -- "formula" as Spammer.lua/AutoInvite.lua) - AceGUIContainer-Frame
    -- anchors frame.content TOPLEFT 17,-27 / BOTTOMRIGHT -17,40 and its
    -- OnWidthSet sets content.width = W - 34 synchronously when
    -- frame:SetWidth(W) runs above, so the full content width is known up
    -- front (unlike a manually-reparented container, whose width is 0 until
    -- first render).
    local FULL_CONTENT_W = W - 34
    -- UIPanelScrollFrameTemplate scrollbar extends ~25px to the RIGHT of scrollFrame.
    -- X button sits at TOPRIGHT -5,-5 on frame.frame; frame.content is inset 17px
    -- from frame.frame on each side. To put the scrollbar right edge at frame.frame
    -- right - 5 (same gap as X button): SCROLL_INSET = 25 - (17 - 5) = 13.
    local SCROLL_INSET = 13
    local CONTENT_W = FULL_CONTENT_W - SCROLL_INSET  -- 283

    local scrollFrame = CreateFrame("ScrollFrame", "RNToolsQAScrollFrame", frame.content, "UIPanelScrollFrameTemplate")
    scrollFrame:SetPoint("TOPLEFT",    frame.content, "TOPLEFT",    0,            -12)
    scrollFrame:SetPoint("BOTTOMLEFT", frame.content, "BOTTOMLEFT", 0,              0)
    scrollFrame:SetPoint("TOPRIGHT",   frame.content, "TOPRIGHT",   -SCROLL_INSET, -12)
    scrollFrame:SetPoint("BOTTOMRIGHT",frame.content, "BOTTOMRIGHT",-SCROLL_INSET,  0)
    if RNToolsQAScrollFrameScrollBar then
        RNToolsQAScrollFrameScrollBar:Hide()
    end

    local content = CreateFrame("Frame", nil, scrollFrame)
    content:SetWidth(CONTENT_W)
    content:SetHeight(1)
    scrollFrame:SetScrollChild(content)

    local yOffset = 0
    local function PlaceWidget(widget, height, spacing)
        widget.frame:SetParent(content)
        widget.frame:ClearAllPoints()
        widget.frame:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -yOffset)
        widget.frame:SetPoint("TOPRIGHT", content, "TOPRIGHT", 0, -yOffset)
        widget.frame:SetHeight(height)
        widget.frame:Show()
        yOffset = yOffset + height + (spacing or 0)
        return widget
    end

    local toggles = {}
    local function AddToggle(group, label, checkFn, toggleFn, relWidth)
        local cb = AceGUI:Create("CheckBox")
        cb:SetLabel(label)
        if relWidth then cb:SetRelativeWidth(relWidth) else cb:SetFullWidth(true) end
        cb:SetValue(checkFn and checkFn() or false)
        cb:SetCallback("OnValueChanged", function(widget)
            if toggleFn then toggleFn() end
            widget:SetValue(checkFn and checkFn() or false)
            if RefreshStatusOverlay then RefreshStatusOverlay() end
        end)
        group:AddChild(cb)
        toggles[#toggles+1] = { widget = cb, checkFn = checkFn }
    end

    local specStatusLabel  -- forward-declared so the spec section and the ticker both see it
    local specProgressLabel

    -- ── Module Toggles ─────────────────────────────────────────────
    local function ModEnabled(key) return p["module_" .. key] ~= false end

    if p.qa_sec_toggles ~= false then
        PlaceWidget(QAAceHeading("Module Toggles"), 18, 4)

        local row1 = AceGUI:Create("SimpleGroup")
        row1:SetLayout("Flow")
        row1.noAutoHeight = true
        row1:SetWidth(CONTENT_W)
        if ModEnabled("bis") then
            AddToggle(row1, "BiS Tracker",
                function() return BiSRaidConfig and BiSRaidConfig.enabled == true end,
                function() if RNTools_ToggleBiSTracker then RNTools_ToggleBiSTracker() end end, 0.5)
        end
        if ModEnabled("spam") then
            AddToggle(row1, "Spammer", IsSpammerEnabled,
                function() if RNTools_ToggleSpammer then RNTools_ToggleSpammer() end end, 0.5)
        end
        PlaceWidget(row1, 24, 2)

        local row2 = AceGUI:Create("SimpleGroup")
        row2:SetLayout("Flow")
        row2.noAutoHeight = true
        row2:SetWidth(CONTENT_W)
        if ModEnabled("ai") then
            AddToggle(row2, "Auto Invite",
                function() return RNTools_IsAutoInviteEnabled and RNTools_IsAutoInviteEnabled() end,
                function() if RNTools_ToggleAutoInvite then RNTools_ToggleAutoInvite() end end, 0.5)
        end
        if ModEnabled("ml") then
            AddToggle(row2, "Master Looter", IsMasterLooterEnabled,
                function() if RNTools_ToggleMasterLooter then RNTools_ToggleMasterLooter() end end, 0.5)
        end
        PlaceWidget(row2, 24, 2)

        local row3 = AceGUI:Create("SimpleGroup")
        row3:SetLayout("Flow")
        row3.noAutoHeight = true
        row3:SetWidth(CONTENT_W)
        if ModEnabled("ms") then
            AddToggle(row3, "MS Changes",
                function() return RNTools_IsMSChangesOpen and RNTools_IsMSChangesOpen() end,
                function() if RNTools_ToggleMSChanges then RNTools_ToggleMSChanges() end end, 0.5)
        end
        if ModEnabled("ar") then
            AddToggle(row3, "Auto Reply", RT_IsAutoResponseEnabled,
                function() if RNTools_ToggleAutoResponse then RNTools_ToggleAutoResponse() end end, 0.5)
        end
        PlaceWidget(row3, 24, 8)
    end

    -- ── Quick Announce ─────────────────────────────────────────────
    if p.qa_sec_announce ~= false and ModEnabled("ms") then
        PlaceWidget(QAAceHeading("Quick Announce"), 18, 4)

        local annRow = AceGUI:Create("SimpleGroup")
        annRow:SetLayout("Flow")
        annRow.noAutoHeight = true
        annRow:SetWidth(CONTENT_W)
        annRow:AddChild(QAAceButton("Buffs - /rw", 0.5, function()
            if RNT_buffsAssignedThisRaid then
                if RNTools_AnnounceBuffOrder then RNTools_AnnounceBuffOrder("rw") end
            else
                local oor = RNT_GetOutOfRangePaladins()
                local msg = "How do you want to assign paladin buffs?"
                if #oor > 0 then
                    msg = msg .. "\n\n|cffff4444WARNING: Out of inspect range:|r\n" .. table.concat(oor, ", ")
                    msg = msg .. "\n|cffff4444Auto-Assign disabled — use Manual.|r"
                end
                StaticPopup_Show("RNTOOLS_BUFF_ASSIGN", msg)
            end
        end))
        annRow:AddChild(QAAceButton("MS Change - /rw", 0.5, function()
            if RNTools_AnnounceAllMSChanges then RNTools_AnnounceAllMSChanges() end
        end))
        PlaceWidget(annRow, 24, 8)

        -- Share MS data with officers
        local shareRow = AceGUI:Create("SimpleGroup")
        shareRow:SetLayout("Flow")
        shareRow.noAutoHeight = true
        shareRow:SetWidth(CONTENT_W)
        shareRow:AddChild(QAAceButton("Share MS + Buffs", 0.5, function()
            if RNTools_ShareMSData then RNTools_ShareMSData() end
        end))
        local autoShareChk = AceGUI:Create("CheckBox")
        autoShareChk:SetLabel("Auto-share on close")
        autoShareChk:SetRelativeWidth(0.5)
        autoShareChk:SetValue(p.ms_autoshare or false)
        autoShareChk:SetCallback("OnValueChanged", function(_, _, val)
            if RNToolsAddon and RNToolsAddon.db then
                RNToolsAddon.db.profile.ms_autoshare = val
            end
        end)
        shareRow:AddChild(autoShareChk)
        PlaceWidget(shareRow, 24, 8)
    end

    -- ── Roll Manager ───────────────────────────────────────────────
    if p.qa_sec_rolls ~= false and (ModEnabled("ml") or ModEnabled("rb")) then
        PlaceWidget(QAAceHeading("Roll Manager"), 18, 4)

        local rollRow = AceGUI:Create("SimpleGroup")
        rollRow:SetLayout("Flow")
        rollRow.noAutoHeight = true
        rollRow:SetWidth(CONTENT_W)
        if ModEnabled("ml") then
            rollRow:AddChild(QAAceButton("Mini Roll Manager", 0.5, function()
                BuildMiniRollManager()
            end))
        end
        if ModEnabled("rb") then
            rollRow:AddChild(QAAceButton("Raid Browser", 0.5, function()
                if RNTools_ToggleMiniRaidBrowser then RNTools_ToggleMiniRaidBrowser() end
            end))
        end
        PlaceWidget(rollRow, 24, 8)
    end

    -- ── Spec Database ──────────────────────────────────────────────
    if p.qa_sec_spec ~= false and ModEnabled("ms") then
        PlaceWidget(QAAceHeading("Spec Database"), 18, 4)

        specStatusLabel = AceGUI:Create("Label")
        specStatusLabel:SetText("|cff888888" .. (RNTools_GetSpecDBCount and (RNTools_GetSpecDBCount() .. " players stored") or "0 players stored") .. "|r")
        PlaceWidget(specStatusLabel, 16, 2)

        specProgressLabel = AceGUI:Create("Label")
        specProgressLabel:SetText("")
        PlaceWidget(specProgressLabel, 16, 4)

        -- Register UI refs with spec module (same :SetText() surface as the old FontStrings)
        if RNTools_SetSpecStatusFS then
            RNTools_SetSpecStatusFS(specStatusLabel, specProgressLabel)
        end

        PlaceWidget(QAAceButton("Gather Spec Data", nil, function()
            if RNTools_GatherSpecData then RNTools_GatherSpecData(false) end
            C_Timer_fake(1, function()
                if RNTools_GetSpecDBCount and specStatusLabel then
                    specStatusLabel:SetText("|cff888888" .. RNTools_GetSpecDBCount() .. " players stored|r")
                end
            end)
        end), 24, 2)

        local specRow = AceGUI:Create("SimpleGroup")
        specRow:SetLayout("Flow")
        specRow.noAutoHeight = true
        specRow:SetWidth(CONTENT_W)
        specRow:AddChild(QAAceButton("Force Re-Gather", 0.5, function()
            if RNTools_GatherSpecData then RNTools_GatherSpecData(true) end
        end))
        specRow:AddChild(QAAceButton("Clear Spec DB", 0.5, function()
            if RNToolsSpecDB then
                RNToolsSpecDB = {}
                if specStatusLabel then
                    specStatusLabel:SetText("|cff888888Cleared. 0 players stored.|r")
                end
                DEFAULT_CHAT_FRAME:AddMessage("|cffFFD100[RNTools Spec]:|r Spec database cleared.")
            end
        end))
        PlaceWidget(specRow, 24, 8)
    end

    -- ── Settings button ────────────────────────────────────────────
    local settingsBtn = AceGUI:Create("Button")
    settingsBtn:SetText("Settings")
    settingsBtn:SetFullWidth(true)
    settingsBtn:SetCallback("OnClick", function()
        if RNTools_ToggleConfig then RNTools_ToggleConfig() end
    end)
    PlaceWidget(settingsBtn, 24, 8)

    -- ── Footer ─────────────────────────────────────────────────────
    local footer = AceGUI:Create("Label")
    footer:SetJustifyH("CENTER")
    footer:SetColor(0.50, 0.50, 0.50)
    footer:SetText("RealNibba's Raid Tools  -  drag title to move")
    footer:SetWidth(CONTENT_W)  -- must call AceGUI method so FontString width matches column
    PlaceWidget(footer, footer.frame.height or 16, 0)

    content:SetHeight(yOffset)
    -- scrollFrame starts at y=-12 below content top, so its visible height =
    -- (frame.content height) - 12 = (frame.frame height - 67) - 12 = frame.frame height - 79.
    -- For scroll child (yOffset tall) to fit with no overflow: frame.frame height = yOffset + 79.
    frame:SetHeight(yOffset + 79)

    -- Shared 0.5s ticker: re-sync toggle checkboxes and the spec DB count
    local tick = 0
    frame.frame:SetScript("OnUpdate", function(self, elapsed)
        tick = tick + elapsed
        if tick < 0.5 then return end
        tick = 0
        for _, t in ipairs(toggles) do
            t.widget:SetValue(t.checkFn and t.checkFn() or false)
        end
        if RNTools_GetSpecDBCount and specStatusLabel then
            specStatusLabel:SetText("|cff888888" .. RNTools_GetSpecDBCount() .. " players stored|r")
        end
    end)

    frame:Hide()  -- hidden by default, QA button shows it
end

function RNTools_IsQAShown()
    return QA_frame and QA_frame:IsShown()
end

function RNTools_ToggleQuickAccess()
    BuildQuickAccess()
    if QA_frame:IsShown() then
        QA_frame:Hide()
    else
        QA_frame:Show()
    end
end

-- Called from Config when any Quick Access setting changes.
-- Applies lock live; rebuilds content so section visibility is reflected.
function RNTools_ApplyQASettings()
    local p = (RNToolsAddon and RNToolsAddon.db and RNToolsAddon.db.profile) or {}
    if QA_frame then
        local wasShown = QA_frame:IsShown()
        -- Don't Release (pool reuse causes duplicate-child bugs); just hide
        -- and orphan so BuildQuickAccess creates a fresh frame.
        QA_frame.frame:Hide()
        QA_frame = nil
        if wasShown then
            BuildQuickAccess()
            if QA_frame then QA_frame:Show() end
        end
    end
end

-------------------------------------------------------------------------------
-- Loot History Window
-- Columns: # | Time | Item | Type | Winner | Rolled | Pos | [Copy]
-- Each date group starts its own numbering 1. 2. 3. ...
-- Newest date on top, newest entry on top within date.
-------------------------------------------------------------------------------
local HIST_frame = nil
HIST_textFS      = nil

-- Class colour helper (shared with Master_Looter via RM_GetWinnerClassColor)
local function HIST_ClassColor(name)
    if not name or name == "No Winner" or name == "Skipped" then return nil end
    if RNToolsSpecDB and RNToolsSpecDB[name] then
        local cls = RNToolsSpecDB[name].class
        if cls and RAID_CLASS_COLORS and RAID_CLASS_COLORS[cls] then
            local c = RAID_CLASS_COLORS[cls]
            return c.r, c.g, c.b
        end
    end
    for i = 1, GetNumRaidMembers() do
        local rname, _, _, _, _, fn = GetRaidRosterInfo(i)
        if rname == name and fn then
            local cls = string.upper(fn)
            if RAID_CLASS_COLORS and RAID_CLASS_COLORS[cls] then
                local c = RAID_CLASS_COLORS[cls]
                return c.r, c.g, c.b
            end
        end
    end
    return 0.20, 1.00, 0.40  -- default green
end

-- Column definitions (x positions, widths)
local HIST_COLS = {
    {label="#",      x=4,   w=22,  just="LEFT"},
    {label="Time",   x=28,  w=40,  just="LEFT"},
    {label="Item",   x=72,  w=180, just="LEFT"},
    {label="Type",   x=256, w=34,  just="LEFT"},
    {label="Winner", x=294, w=148, just="LEFT"},
    {label="Rolled", x=446, w=44,  just="LEFT"},
    {label="Pos.",   x=494, w=30,  just="LEFT"},
    -- Copy button at x=528
}
local HIST_W       = 620
local HIST_ROW_H   = 18
local HIST_COPY_X  = 530

-- Parse one history entry string into fields
local function HIST_ParseEntry(entry)
    local timeStr  = entry:match("%((%d%d:%d%d)%)$") or "--"
    local rollType = entry:match("%((%a+)%)") or "--"
    local clean    = entry:gsub("|c%x%x%x%x%x%x%x%x",""):gsub("|r","")
    local itemName = clean:match("%[(.-)%]") or "?"
    local winner, rolled, pos
    if clean:find("Item Skipped") then
        winner = "Skipped"; rolled = "--"; pos = "--"
    else
        -- "Winner -> Name rolled N (#P)"
        winner = clean:match("Winner %-> ([^ ].-)%s+rolled") or
                 clean:match("Winner %-> (.+) %(#") or "--"
        if winner then winner = winner:match("^%s*(.-)%s*$") or winner end
        rolled = clean:match("rolled (%d+)") or "--"
        pos    = clean:match("#(%d+)") or "--"
    end
    return timeStr, rollType, itemName, winner, rolled, pos
end

-- Build a single plain-text line from an entry (for copy)
local function HIST_PlainLine(num, entry)
    local t, ty, item, winner, rolled, pos = HIST_ParseEntry(entry)
    if winner == "Skipped" then
        return num..". ["..t.."] ["..item.."] ("..ty..") - Skipped"
    else
        return num..". ["..t.."] ["..item.."] ("..ty..") Winner: "..winner.." rolled "..rolled.." (#"..pos..")"
    end
end

-- Copy text to chat editbox (player can Ctrl+A, Ctrl+C)
local function HIST_OpenCopyPopup(text)
    -- Open chat and pre-fill with the text so player can copy it
    if ChatFrame1EditBox then
        ChatFrame1EditBox:Show()
        ChatFrame1EditBox:SetFocus()
        ChatFrame1EditBox:SetText(text)
        ChatFrame1EditBox:HighlightText()
    else
        -- Fallback: print to chat
        DEFAULT_CHAT_FRAME:AddMessage(text)
    end
end

-- History window: AceGUI Frame shell + raw ScrollFrame rows (no per-entry AceGUI
-- widgets — raw FontStrings + raw buttons for zero allocation overhead at 50+ entries).
local HIST_rowPool = {}  -- { frame, numFS, timeFS, itemFS, typeFS, winFS, rollFS, posFS, copyBtn }

local function BuildHistoryWindow()
    if HIST_frame then return end

    local frame = AceGUI:Create("Frame")
    frame:SetTitle("Loot History")
    frame:SetWidth(HIST_W + 34)
    frame:SetHeight(540)
    frame:EnableResize(false)
    frame.frame:SetClampedToScreen(true)
    frame.frame:SetFrameStrata("HIGH")
    frame.frame:SetScale(BiSRaidConfig and BiSRaidConfig.addonScale or 1.0)
    allScalableFrames[#allScalableFrames+1] = frame.frame
    frame.frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    ApplyWindowBackdrop(frame, true)
    frame:SetCallback("OnClose", function(widget) widget:Hide() end)
    HideAceCloseBtn(frame)
    frame.statustext:GetParent():Hide()
    local hist_xBtn = CreateFrame("Button", nil, frame.frame, "UIPanelCloseButton")
    hist_xBtn:SetPoint("TOPRIGHT", frame.frame, "TOPRIGHT", -5, -5)
    hist_xBtn:SetScript("OnClick", function() frame:Hide() end)

    local c = frame.content   -- 620px wide, ~483px tall (540 - 57)

    -- Clear All — AceGUI Button, safely below X button zone (X extends to content y≈-10)
    local clearBtn = AceGUI:Create("Button")
    clearBtn:SetText("Clear All")
    clearBtn:SetWidth(88)
    clearBtn.frame:SetParent(c); clearBtn.frame:ClearAllPoints()
    clearBtn.frame:SetPoint("TOPRIGHT", c, "TOPRIGHT", 0, -14)
    clearBtn.frame:SetHeight(22); clearBtn.frame:Show()
    clearBtn:SetCallback("OnClick", function()
        RNToolsLootHistory = {}; RNTools_RefreshHistory()
    end)

    -- Column header FontStrings (raw, cheap)
    local HDR_Y = -40
    for _, col in ipairs(HIST_COLS) do
        local fs = c:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
        fs:SetPoint("TOPLEFT", c, "TOPLEFT", col.x, HDR_Y)
        fs:SetWidth(col.w); fs:SetJustifyH(col.just or "LEFT")
        fs:SetTextColor(1, 0.82, 0, 1); fs:SetText(col.label)
    end
    local copyHdrFS = c:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    copyHdrFS:SetPoint("TOPLEFT", c, "TOPLEFT", HIST_COPY_X, HDR_Y)
    copyHdrFS:SetTextColor(1, 0.82, 0, 1); copyHdrFS:SetText("Copy")

    -- Horizontal divider under headers
    local hDiv = c:CreateTexture(nil,"ARTWORK")
    hDiv:SetHeight(1)
    hDiv:SetPoint("TOPLEFT",  c, "TOPLEFT",  0,          HDR_Y - 14)
    hDiv:SetPoint("TOPRIGHT", c, "TOPRIGHT", 0,          HDR_Y - 14)
    hDiv:SetTexture("Interface\\Buttons\\WHITE8X8")
    hDiv:SetVertexColor(0.40, 0.40, 0.40, 0.5)

    -- Raw ScrollFrame (no UIPanelScrollFrameTemplate overhead for pool, just a plain SF)
    local sf = CreateFrame("ScrollFrame", "RNHistSF", c, "UIPanelScrollFrameTemplate")
    sf:SetPoint("TOPLEFT",     c, "TOPLEFT",     0,    HDR_Y - 16)
    sf:SetPoint("BOTTOMRIGHT", c, "BOTTOMRIGHT", -20,  0)
    local sc = CreateFrame("Frame", nil, sf)
    sc:SetWidth(HIST_W - 20)
    sc:SetHeight(10)
    sf:SetScrollChild(sc)

    sf:SetScript("OnScrollRangeChanged", function(self, xr, yr)
        local bar = _G["RNHistSFScrollBar"]; if not bar then return end
        yr = math.floor(yr + 0.5)
        bar:SetMinMaxValues(0, yr); bar:SetValue(math.min(bar:GetValue(), yr))
        local up = _G["RNHistSFScrollBarScrollUpButton"]
        local dn = _G["RNHistSFScrollBarScrollDownButton"]
        local v  = bar:GetValue()
        if up then if v > 0  then up:Enable()  else up:Disable()  end end
        if dn then if v < yr then dn:Enable()  else dn:Disable()  end end
    end)

    HIST_frame = frame
    HIST_frame._sc = sc
    HIST_frame._sf = sf
    frame.frame:HookScript("OnShow", function() RNTools_RefreshHistory() end)
    frame:Hide()
end

-- Raw row factory — creates one row frame + FontStrings + raw Copy button.
-- Pool is reused across refreshes; only created on demand.
local function HIST_GetRow(i, sc)
    if HIST_rowPool[i] then return HIST_rowPool[i] end
    local row = CreateFrame("Frame", nil, sc)
    row:SetHeight(HIST_ROW_H)

    local function FS(x, w, just)
        local fs = row:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
        fs:SetPoint("LEFT", row, "LEFT", x, 0)
        if w then fs:SetWidth(w) end
        fs:SetJustifyH(just or "LEFT")
        return fs
    end

    row._numFS  = FS(HIST_COLS[1].x, HIST_COLS[1].w)
    row._timeFS = FS(HIST_COLS[2].x, HIST_COLS[2].w)
    row._itemFS = FS(HIST_COLS[3].x, HIST_COLS[3].w)
    row._typeFS = FS(HIST_COLS[4].x, HIST_COLS[4].w)
    row._winFS  = FS(HIST_COLS[5].x, HIST_COLS[5].w)
    row._rollFS = FS(HIST_COLS[6].x, HIST_COLS[6].w)
    row._posFS  = FS(HIST_COLS[7].x, HIST_COLS[7].w)

    -- Raw copy button — lightweight stock button template
    local copyBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
    copyBtn:SetSize(HIST_W - HIST_COPY_X - 20, HIST_ROW_H - 2)
    copyBtn:SetPoint("LEFT", row, "LEFT", HIST_COPY_X, 0)
    row._copyBtn = copyBtn

    HIST_rowPool[i] = row
    return row
end

function RNTools_RefreshHistory()
    if not HIST_frame or not HIST_frame._sc then return end
    local sc = HIST_frame._sc

    -- Hide all pooled rows first
    for _, row in ipairs(HIST_rowPool) do row:Hide() end

    if type(RNToolsLootHistory) ~= "table" then return end

    local dates = {}
    for d in pairs(RNToolsLootHistory) do dates[#dates+1] = d end
    table.sort(dates, function(a,b) return a > b end)

    local rowIndex = 0
    local totalY   = 0
    local SW = HIST_W - 20

    for _, d in ipairs(dates) do
        -- Date separator (cheap texture + FontString on sc directly)
        local dateHdrH = HIST_ROW_H + 4
        local dateHdr = sc:CreateFontString(nil,"OVERLAY","GameFontNormalSmall")
        dateHdr:SetPoint("TOPLEFT", sc, "TOPLEFT", 4, -totalY - 2)
        dateHdr:SetTextColor(1, 0.82, 0, 1)
        dateHdr:SetText("[  " .. d .. "  ]")
        totalY = totalY + dateHdrH

        local entries = RNToolsLootHistory[d]
        if type(entries) == "table" then
            for n, entry in ipairs(entries) do
                local timeStr, rollType, itemName, winner, rolled, pos = HIST_ParseEntry(entry)
                rowIndex = rowIndex + 1
                local row = HIST_GetRow(rowIndex, sc)

                -- Alternating row bg
                if rowIndex % 2 == 0 then
                    row:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",tile=false,tileSize=1,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
                    row:SetBackdropColor(0.13,0.13,0.13,0.9)
                    row:SetBackdropBorderColor(0,0,0,0)
                else
                    row:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",tile=false,tileSize=1,edgeSize=1,insets={left=0,right=0,top=0,bottom=0}})
                    row:SetBackdropColor(0.10,0.10,0.10,0.9)
                    row:SetBackdropBorderColor(0,0,0,0)
                end
                row:ClearAllPoints()
                row:SetPoint("TOPLEFT",  sc, "TOPLEFT",  0,  -totalY)
                row:SetPoint("TOPRIGHT", sc, "TOPRIGHT", 0,  -totalY)
                row:Show()

                -- Columns
                row._numFS:SetText("|cff666666"..n..".|r")
                row._timeFS:SetText("|cff888888"..timeStr.."|r")
                local itemDisplay = itemName
                if string.len(itemName) > 22 then itemDisplay = string.sub(itemName,1,20)..".." end
                row._itemFS:SetText("|cffdddddd["..itemDisplay.."]|r")
                row._typeFS:SetText("|cffFFD100"..rollType.."|r")

                if winner == "Skipped" then
                    row._winFS:SetTextColor(0.6,0.6,0.6,1); row._winFS:SetText("Skipped")
                else
                    local r,g,b = HIST_ClassColor(winner)
                    row._winFS:SetTextColor(r or 0.20, g or 1.00, b or 0.40, 1)
                    row._winFS:SetText(winner or "--")
                end
                row._rollFS:SetText(tostring(rolled))
                row._posFS:SetText(pos == "--" and "--" or "#"..pos)

                -- Copy button
                local capEntry = entry; local capN = n
                local btn = row._copyBtn
                btn:SetText("Copy")
                btn:SetScript("OnClick", function()
                    HIST_OpenCopyPopup(HIST_PlainLine(capN, capEntry))
                end)

                totalY = totalY + HIST_ROW_H + 1
            end
        end
        totalY = totalY + 4
    end

    sc:SetHeight(math.max(totalY + 10, 10))
end

function RNTools_ToggleHistory()
    BuildHistoryWindow()
    if HIST_frame:IsShown() then
        HIST_frame:Hide()
    else
        RNTools_RefreshHistory()
        HIST_frame:Show()
    end
end

function ToggleStatusOverlayVisibility()
    EnsureStatusOverlaySavedVariables()
    CreateStatusOverlayIfNeeded()
    RNToolsOverlaySave.enabled = not RNToolsOverlaySave.enabled
    SetFrameShown(statusOverlayFrame, RNToolsOverlaySave.enabled)
end


-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- FAQ tab — plain text, fixed-width FontString, exactly like working BiSRaid UI
-------------------------------------------------------------------------------
-- CreateFAQTabContent is defined in BiS_Tab.lua (needs C/SetBG helpers)
local function OnPlayerLogin()
    -- Init BiS DB
    if RN_LoadBisDB then RN_LoadBisDB() end

    EnsureStatusOverlaySavedVariables()
    CreateMainFrameIfNeeded()
    CreateStatusOverlayIfNeeded()

    if CreateMinimapButtonIfNeeded then
        CreateMinimapButtonIfNeeded()
    end

    -- Slash commands and load message are now handled by
    -- RNTools_Core.lua (AceConsole:RegisterChatCommand in AceAddon:OnInitialize)
    -- and the OnEnable print. Nothing to do here.
end

-- NOTE: PLAYER_LOGIN is now handled by RNTools_Core.lua (AceAddon:OnEnable).
-- The loader frame below has been removed to avoid double-initialization.
-- OnPlayerLogin() is called from RNToolsAddon:OnEnable() in RNTools_Core.lua.
-- All functions it called (CreateMainFrameIfNeeded, etc.) are still defined here.
-- IMPORTANT: OnPlayerLogin is exposed as a global so Core.lua can call its parts.

------------------------------------------------------------
-- MINI RAID BROWSER
------------------------------------------------------------

local QA_miniRB   = nil
local miniRBRows  = {}
local miniRBEnBtn = nil

-- Column layout (x = offset from left edge inside scroll child, NO roles column)
local MRB_P   = 6   -- inner padding
local MRB_ROW = 22  -- row height px
-- Col positions (absolute x from left of row)
local MRB_C = {
    raid   = {x=2,   w=100},  -- Raid name
    gs     = {x=104, w=42},   -- GS
    sender = {x=148, w=88},   -- Sender name
    ress   = {x=238, w=34},   -- Ress YES/NO
    wbtn   = {x=274, w=28},   -- /W
    jbtn   = {x=304, w=34},   -- Join
}
-- Total row width = 304+34 = 338, window = 338 + MRB_P*2 = 350
local MRB_W = 350
local MRB_H = 440

local function MiniRB_UpdateEnableBtn()
    if not miniRBEnBtn then return end
    local en = RNTools_IsRaidBrowserEnabled and RNTools_IsRaidBrowserEnabled()
    if en then
        miniRBEnBtn:SetText("|cff20ff60[ON] Stop Raid Browser|r")
    else
        miniRBEnBtn:SetText("|cffff4444[OFF] Start Raid Browser|r")
    end
end

local function BuildMiniRaidBrowser()
    if QA_miniRB then QA_miniRB:Show(); MiniRB_UpdateEnableBtn(); return end

    local frame = AceGUI:Create("Frame")
    frame:SetTitle("|cff44aaffMini Raid Browser|r")
    frame:SetWidth(MRB_W + 34)   -- content width = MRB_W = 350
    frame:SetHeight(460)
    frame:EnableResize(false)
    frame.frame:SetClampedToScreen(true)
    frame.frame:SetFrameStrata("HIGH")
    frame.frame:SetScale(BiSRaidConfig and BiSRaidConfig.addonScale or 1.0)
    allScalableFrames[#allScalableFrames+1] = frame.frame
    frame.frame:SetPoint("CENTER", UIParent, "CENTER", 300, 0)
    ApplyWindowBackdrop(frame)

    if type(RNToolsMinRMSave)=="table" and RNToolsMinRMSave.rb_x then
        frame.frame:ClearAllPoints()
        frame.frame:SetPoint(RNToolsMinRMSave.rb_point or "CENTER", UIParent,
            RNToolsMinRMSave.rb_rPoint or "CENTER",
            RNToolsMinRMSave.rb_x, RNToolsMinRMSave.rb_y)
    end

    frame.frame:HookScript("OnDragStop", function(self)
        if type(RNToolsMinRMSave)~="table" then RNToolsMinRMSave={} end
        local p,_,rp,x,y = self:GetPoint(1)
        RNToolsMinRMSave.rb_point=p; RNToolsMinRMSave.rb_rPoint=rp
        RNToolsMinRMSave.rb_x=x;    RNToolsMinRMSave.rb_y=y
    end)
    frame:SetCallback("OnClose", function(widget)
        if type(RNToolsMinRMSave)~="table" then RNToolsMinRMSave={} end
        local p,_,rp,x,y = widget.frame:GetPoint(1)
        RNToolsMinRMSave.rb_point=p; RNToolsMinRMSave.rb_rPoint=rp
        RNToolsMinRMSave.rb_x=x;    RNToolsMinRMSave.rb_y=y
        widget:Hide()
    end)

    -- Hide AceGUI's bottom "Close" button and status bar; use WoW X button instead
    HideAceCloseBtn(frame)
    frame.statustext:GetParent():Hide()
    -- AceGUI anchors content BOTTOMRIGHT at y=40 for status bar; reclaim that space
    frame.content:SetPoint("BOTTOMRIGHT", frame.frame, "BOTTOMRIGHT", -17, 12)
    local miniRB_xBtn = CreateFrame("Button", nil, frame.frame, "UIPanelCloseButton")
    miniRB_xBtn:SetPoint("TOPRIGHT", frame.frame, "TOPRIGHT", -5, -5)
    miniRB_xBtn:SetScript("OnClick", function() frame:Hide() end)

    QA_miniRB = frame
    local c = frame.content   -- content width = MRB_W = 350

    -- Cloned from main RB: RB_BACKDROP style + raw button helpers (defined early so
    -- both the header loop and the row factory below can use them)
    local MRB_BD = {bgFile="Interface\\Buttons\\WHITE8X8",edgeFile="Interface\\Buttons\\WHITE8X8",
                    tile=false,tileSize=1,edgeSize=1,insets={left=1,right=1,top=1,bottom=1}}
    local MRB_C2 = {
        btn_n={0.14,0.14,0.14,1}, btn_h={0.22,0.22,0.22,1}, btn_p={0.08,0.08,0.08,1},
        border={0.40,0.40,0.40,0.8},
        row_odd={0.10,0.10,0.10,0.90}, row_even={0.13,0.13,0.13,0.90},
    }
    local function MRBSetBG(f,r,g,b,a) f:SetBackdrop(MRB_BD); f:SetBackdropColor(r,g,b,a or 1) end
    local function MRBBorder(f,r,g,b,a) f:SetBackdropBorderColor(r,g,b,a or 1) end
    local function MRBBtn(par,lbl,w,h)
        local btn=CreateFrame("Button",nil,par); btn:SetSize(w,h or MRB_ROW)
        MRBSetBG(btn,unpack(MRB_C2.btn_n)); MRBBorder(btn,unpack(MRB_C2.border))
        local fs=btn:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
        fs:SetAllPoints(btn); fs:SetText(lbl); fs:SetTextColor(0.90,0.90,0.90,1)
        btn:SetScript("OnEnter",   function() MRBSetBG(btn,unpack(MRB_C2.btn_h)); MRBBorder(btn,unpack(MRB_C2.border)) end)
        btn:SetScript("OnLeave",   function() MRBSetBG(btn,unpack(MRB_C2.btn_n)); MRBBorder(btn,unpack(MRB_C2.border)) end)
        btn:SetScript("OnMouseDown",function() MRBSetBG(btn,unpack(MRB_C2.btn_p)); MRBBorder(btn,unpack(MRB_C2.border)) end)
        btn:SetScript("OnMouseUp",  function() MRBSetBG(btn,unpack(MRB_C2.btn_h)); MRBBorder(btn,unpack(MRB_C2.border)) end)
        btn._label=fs; return btn
    end

    -- Count label at top of content
    local countFS = c:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
    countFS:SetPoint("TOPLEFT",c,"TOPLEFT",MRB_P+2,-2)
    countFS:SetTextColor(0.6,0.6,0.6)

    -- Sort state, click-to-sort column headers (mirrors RaidBrowser.lua's RB_Sort).
    -- key: "raid"|"gs"|"sender"|"time"  dir: "asc"|"desc"
    local MRB_Sort = { key = "time", dir = "desc" }
    local MRB_ColHeaders = {}
    local RefreshMiniRB -- forward declaration, assigned below

    local function MRB_RefreshHeaderLabels()
        for key, h in pairs(MRB_ColHeaders) do
            local arrow = ""
            if MRB_Sort.key == key then
                arrow = (MRB_Sort.dir == "asc") and "  ^" or "  v"
            end
            h.fs:SetText(h.baseText .. arrow)
        end
    end

    local function MRB_SortValue(info, key)
        if key == "raid" then
            if info.weekly then return "weekly" end
            if info.raid and type(info.raid) == "table" then
                local name = (RNTools_RB_DisplayName and RNTools_RB_DisplayName(info.raid.name)) or (info.raid.name or "")
                return name:lower()
            end
            return tostring(info.raid or ""):lower()
        elseif key == "gs" then
            return tonumber(info.gs) or -1
        elseif key == "sender" then
            return (info.sender or ""):lower()
        end
        return info.time or 0   -- "time"
    end

    local function MRB_SortList(list)
        local key, dir = MRB_Sort.key, MRB_Sort.dir
        table.sort(list, function(a,b)
            local va, vb = MRB_SortValue(a,key), MRB_SortValue(b,key)
            if va == vb then return (a.time or 0) > (b.time or 0) end
            if dir == "asc" then return va < vb else return va > vb end
        end)
    end

    -- Column header row (8px below count label)
    local HDR_Y = -22
    local HDR_H = 16
    local cols = {
        {label="Raid",   c=MRB_C.raid,   key="raid"},
        {label="GS",     c=MRB_C.gs,     key="gs"},
        {label="Sender", c=MRB_C.sender, key="sender"},
        {label="Ress",   c=MRB_C.ress},
        {label="/W",     c=MRB_C.wbtn},
        {label="Join",   c=MRB_C.jbtn},
    }
    for _,col in ipairs(cols) do
        local hBG = CreateFrame("Frame",nil,c)
        hBG:SetSize(col.c.w, HDR_H)
        hBG:SetPoint("TOPLEFT",c,"TOPLEFT",MRB_P+col.c.x, HDR_Y)
        MRBSetBG(hBG,0.18,0.18,0.18,1); MRBBorder(hBG,0.40,0.40,0.40,0.7)
        local hFS = hBG:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
        hFS:SetAllPoints(hBG); hFS:SetJustifyH("CENTER")
        hFS:SetText(col.label); hFS:SetTextColor(0.90,0.90,0.90,1)
        if col.key then
            MRB_ColHeaders[col.key] = { fs = hFS, baseText = col.label }
            local key = col.key
            local hBtn = CreateFrame("Button",nil,hBG)
            hBtn:SetAllPoints(hBG)
            hBtn:SetScript("OnEnter", function() hFS:SetTextColor(1,1,1,1) end)
            hBtn:SetScript("OnLeave", function()
                if MRB_Sort.key ~= key then hFS:SetTextColor(0.90,0.90,0.90,1) end
            end)
            hBtn:SetScript("OnClick", function()
                if MRB_Sort.key == key then
                    MRB_Sort.dir = (MRB_Sort.dir == "asc") and "desc" or "asc"
                else
                    MRB_Sort.key = key
                    MRB_Sort.dir = (key == "gs") and "desc" or "asc"
                end
                MRB_RefreshHeaderLabels()
                if RefreshMiniRB then RefreshMiniRB() end
            end)
        end
    end
    MRB_RefreshHeaderLabels()

    -- Scroll frame (starts 2px below headers)
    local sf = CreateFrame("ScrollFrame",nil,c)
    sf:SetPoint("TOPLEFT",  c,"TOPLEFT",  MRB_P, HDR_Y-HDR_H-2)
    sf:SetPoint("BOTTOMRIGHT",c,"BOTTOMRIGHT",-MRB_P, 38)
    sf:EnableMouseWheel(true)
    local sc = CreateFrame("Frame",nil,sf)
    sc:SetWidth(MRB_W - MRB_P*2)
    sc:SetHeight(10)
    sf:SetScrollChild(sc)
    sf:SetScript("OnMouseWheel",function(self,delta)
        local cur = self:GetVerticalScroll()
        local mx  = self:GetVerticalScrollRange()
        self:SetVerticalScroll(math.max(0,math.min(mx,cur-delta*MRB_ROW*3)))
    end)

    -- Row factory
    local ROW_W = MRB_W - MRB_P*2
    local function GetRow(i)
        if miniRBRows[i] then return miniRBRows[i] end
        local r   = {}
        local alt = i%2==0
        local rc  = alt and MRB_C2.row_even or MRB_C2.row_odd
        local bg  = CreateFrame("Frame",nil,sc)
        bg:SetSize(ROW_W, MRB_ROW)
        bg:SetPoint("TOPLEFT",sc,"TOPLEFT",0,-(i-1)*(MRB_ROW+1))
        MRBSetBG(bg, rc[1],rc[2],rc[3],rc[4])
        MRBBorder(bg, 0,0,0,0)

        -- Vertical dividers at column boundaries
        for _,dx in ipairs({MRB_C.gs.x, MRB_C.sender.x, MRB_C.ress.x, MRB_C.wbtn.x, MRB_C.jbtn.x}) do
            local dl = bg:CreateTexture(nil,"ARTWORK")
            dl:SetWidth(1)
            dl:SetPoint("TOPLEFT",bg,"TOPLEFT",dx,0)
            dl:SetPoint("BOTTOMLEFT",bg,"BOTTOMLEFT",dx,0)
            dl:SetTexture("Interface\\Buttons\\WHITE8X8")
            dl:SetVertexColor(0.40,0.40,0.40,0.3)
        end

        -- Column FontStrings
        local function Col(c, align)
            local fs = bg:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
            fs:SetPoint("LEFT",bg,"LEFT",c.x+3,0)
            fs:SetWidth(c.w-6)
            fs:SetJustifyH(align or "LEFT")
            fs:SetTextColor(0.90,0.90,0.90,1)
            return fs
        end

        r.raidFS   = Col(MRB_C.raid,   "LEFT")
        r.gsFS     = Col(MRB_C.gs,     "CENTER")
        r.senderFS = Col(MRB_C.sender, "LEFT")

        -- Action buttons — raw (cloned from main RB RBBtn pattern)
        local BTN_H = MRB_ROW - 4
        local BTN_OY = -2  -- offset from TOPLEFT of bg

        local ressBtn = MRBBtn(bg,"NO",MRB_C.ress.w-2,BTN_H)
        ressBtn:SetPoint("TOPLEFT",bg,"TOPLEFT",MRB_C.ress.x+1,BTN_OY)
        r.ressBtn = ressBtn

        local wBtn = MRBBtn(bg,"/W",MRB_C.wbtn.w-2,BTN_H)
        wBtn:SetPoint("TOPLEFT",bg,"TOPLEFT",MRB_C.wbtn.x+1,BTN_OY)
        wBtn._label:SetTextColor(0.85,0.75,0.55,1)
        r.wBtn = wBtn

        local jBtn = MRBBtn(bg,"Join",MRB_C.jbtn.w-2,BTN_H)
        jBtn:SetPoint("TOPLEFT",bg,"TOPLEFT",MRB_C.jbtn.x+1,BTN_OY)
        jBtn._label:SetTextColor(0.20,1.00,0.40,1)
        r.jBtn = jBtn

        -- Hover highlight + exact RB tooltip
        bg:EnableMouse(true)
        bg:SetScript("OnEnter",function(self)
            MRBSetBG(self, 0.22,0.22,0.22,1); MRBBorder(self, 0,0,0,0)
            if not self._info then return end
            local info = self._info
            GameTooltip:SetOwner(self,"ANCHOR_RIGHT")
            GameTooltip:ClearLines()
            GameTooltip:AddLine(info.message, 1, 1, 1, true)
            local age = time() - (info.time or time())
            GameTooltip:AddLine("Last seen: "..age.." seconds ago", 0.7, 0.7, 0.7)
            GameTooltip:AddLine(" ")
            local raidName = (info.raid and type(info.raid)=="table" and info.raid.instance_name) or "this raid"
            if self._is_locked then
                GameTooltip:AddLine("You are |cffff4444saved|r for "..raidName, 1, 0.82, 0)
                if self._reset_str then
                    GameTooltip:AddLine("Lockout resets at "..self._reset_str, 0.7, 0.7, 0.7)
                end
            else
                GameTooltip:AddLine("You are |cff00ffffnot saved|r for "..raidName, 1, 0.82, 0)
            end
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine("Click |cff44aaff/w|r to whisper the raid leader", 0.60,0.60,0.60)
            GameTooltip:Show()
        end)
        bg:SetScript("OnLeave",function(self)
            local ii = self._idx or 1
            local rc = (ii%2==0) and MRB_C2.row_even or MRB_C2.row_odd
            MRBSetBG(self, rc[1],rc[2],rc[3],rc[4]); MRBBorder(self, 0,0,0,0)
            GameTooltip:Hide()
        end)

        r.bg = bg
        miniRBRows[i] = r
        return r
    end

    -- Refresh function
    RefreshMiniRB = function()
        if not (RNTools_IsRaidBrowserEnabled and RNTools_IsRaidBrowserEnabled()) then
            countFS:SetText("|cffff4444Raid Browser OFF — click Start below|r")
            for _,r in ipairs(miniRBRows) do if r.bg then r.bg:Hide() end end
            sc:SetHeight(10); MiniRB_UpdateEnableBtn(); return
        end
        local msgs = RNTools_GetRBMessages and RNTools_GetRBMessages()
        if not msgs then
            countFS:SetText("|cff888888No messages yet|r")
            for _,r in ipairs(miniRBRows) do if r.bg then r.bg:Hide() end end
            sc:SetHeight(10); MiniRB_UpdateEnableBtn(); return
        end
        local now  = time()
        local list = {}
        for _, info in pairs(msgs) do
            if now - (info.time or 0) <= 60 then list[#list+1] = info end
        end
        MRB_SortList(list)

        if #list==0 then
            countFS:SetText("|cff888888Listening... 0 raids found|r")
            for _,r in ipairs(miniRBRows) do if r.bg then r.bg:Hide() end end
            sc:SetHeight(10); MiniRB_UpdateEnableBtn(); return
        end
        countFS:SetText("|cff44aaff"..#list.."|r listing(s) — hover = saved status + full msg")

        for i, info in ipairs(list) do
            local r = GetRow(i)

            -- Raid name (colour from RB, truncate)
            local rname, rcolor = "", {0.90,0.90,0.90}
            if info.weekly then
                rname = "Weekly"; rcolor = {1.0,0.82,0.0}
            elseif info.raid and type(info.raid)=="table" then
                local is_locked, reset_time = RNTools_RB_GetLockout and
                    RNTools_RB_GetLockout(info.raid.instance_name, info.raid.size) or false, nil
                r.bg._is_locked = is_locked
                r.bg._reset_str = reset_time and date("%H:%M",reset_time) or nil
                if RNTools_RB_RaidColor then
                    local rc = {RNTools_RB_RaidColor(info.raid.name, is_locked)}
                    rcolor = rc
                end
                rname = RNTools_RB_DisplayName and RNTools_RB_DisplayName(info.raid.name) or
                        (info.raid.name or "?"):upper()
            elseif info.raid and type(info.raid)=="string" then
                rname = info.raid:upper()
            end
            if string.len(rname) > 11 then rname = string.sub(rname,1,10)..".." end
            r.raidFS:SetText(rname)
            r.raidFS:SetTextColor(rcolor[1] or 0.88, rcolor[2] or 0.92, rcolor[3] or 0.98, 1)

            -- GS
            r.gsFS:SetText(type(info.gs)=="string" and info.gs~="" and
                ("|cffFFD100"..info.gs.."k|r") or "")

            local sn = info.sender or "?"
            if string.len(sn) > 11 then sn = string.sub(sn,1,10)..".." end
            r.senderFS:SetText(sn)

            -- Ress button (raw, cloned from main RB)
            if info.has_ress then
                r.ressBtn._label:SetText("|cff20ff60YES|r")
                MRBSetBG(r.ressBtn,0.04,0.20,0.08,1); MRBBorder(r.ressBtn,0.10,0.80,0.30,1)
            else
                r.ressBtn._label:SetText("|cffff4444NO|r")
                MRBSetBG(r.ressBtn,0.22,0.04,0.04,1); MRBBorder(r.ressBtn,0.80,0.20,0.10,1)
            end
            r.ressBtn:SetScript("OnEnter",function(self)
                GameTooltip:SetOwner(self,"ANCHOR_RIGHT"); GameTooltip:ClearLines()
                if info.has_ress and info.ress_text~="" then
                    GameTooltip:AddLine("|cff20ff60Reserved items:|r",1,0.82,0)
                    GameTooltip:AddLine(info.ress_text,1,1,1,true)
                else
                    GameTooltip:AddLine("|cffff4444No reserved items|r",1,0.82,0)
                end
                GameTooltip:Show()
            end)
            r.ressBtn:SetScript("OnLeave",function() GameTooltip:Hide() end)

            -- /W and Join using RB's BuildWhisper (raw SetScript — raw buttons)
            local captInfo = info
            r.wBtn:SetScript("OnClick",function()
                if RNTools_RB_BuildWhisper then
                    local fullMsg = RNTools_RB_BuildWhisper(captInfo.sender, captInfo.raid)
                    if ChatFrame1EditBox then
                        ChatFrame1EditBox:Show()
                        ChatFrame1EditBox:SetText(fullMsg)
                        ChatFrame1EditBox:SetFocus()
                    end
                end
            end)
            r.jBtn:SetScript("OnClick",function()
                if RNTools_RB_BuildWhisper then
                    local _,appMsg = RNTools_RB_BuildWhisper(captInfo.sender, captInfo.raid)
                    SendChatMessage(appMsg,"WHISPER",nil,captInfo.sender)
                    DEFAULT_CHAT_FRAME:AddMessage("|cff44aaff[RaidBrowser]:|r Applied to |cffFFD100"..captInfo.sender.."|r")
                end
            end)

            r.bg._info = info; r.bg._idx = i
            r.bg:SetPoint("TOPLEFT",sc,"TOPLEFT",0,-(i-1)*(MRB_ROW+1))
            r.bg:Show()
        end
        for i=#list+1,#miniRBRows do if miniRBRows[i].bg then miniRBRows[i].bg:Hide() end end
        sc:SetHeight(math.max(#list*(MRB_ROW+1), 10))
        MiniRB_UpdateEnableBtn()
    end

    -- Enable/Disable button (AceGUI)
    local enBtn = AceGUI:Create("Button")
    enBtn:SetWidth(MRB_W - MRB_P*2 - 4)
    enBtn.frame:SetParent(c); enBtn.frame:ClearAllPoints()
    enBtn.frame:SetPoint("BOTTOMLEFT", c, "BOTTOMLEFT", MRB_P+2, 10)
    enBtn.frame:SetHeight(26); enBtn.frame:Show()
    miniRBEnBtn = enBtn
    enBtn:SetCallback("OnClick", function()
        if RNTools_SetRBEnabled then
            RNTools_SetRBEnabled(not (RNTools_IsRaidBrowserEnabled and RNTools_IsRaidBrowserEnabled()))
        end
        RefreshMiniRB()
    end)
    MiniRB_UpdateEnableBtn()

    local rbTick = 0
    frame.frame:SetScript("OnUpdate",function(self,elapsed)
        rbTick=rbTick+elapsed; if rbTick<3 then return end
        rbTick=0; RefreshMiniRB()
    end)
    frame.frame:HookScript("OnShow",function() RefreshMiniRB() end)
    RefreshMiniRB()
    frame:Show()
end


-- Register global so QA button can open it
RNTools_ToggleMiniRaidBrowser = function()
    if QA_miniRB and QA_miniRB:IsShown() then
        QA_miniRB:Hide()
    else
        BuildMiniRaidBrowser()
    end
end
