local addonName, NS = ...
local GR = NS.GR

-- =========================================================================
-- UI: CENTER PANE (RESULTS)
-- =========================================================================

function GR:BuildCenterPane(parent)
    self.ResultsHeader = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    self.ResultsHeader:SetPoint("TOPLEFT", 15, -15); self.ResultsHeader:SetText(NS.L["UNGUILDED_PLAYERS_HEADER"] or "Unguilded Players - Current Filter: Default (All)")
    
    local sBtn = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    sBtn:SetSize(140, 30); sBtn:SetPoint("TOPRIGHT", -15, -40); sBtn:SetText(NS.L["SEARCH_PLAYERS"] or "Search Players")
    self.SearchBtn = sBtn 
    sBtn:SetScript("OnClick", function() self:StartSearch() end)
    NS.SkinButton(sBtn) 
    
    local rListBtn = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    rListBtn:SetSize(100, 30)
    rListBtn:SetPoint("RIGHT", sBtn, "LEFT", -5, 0)
    rListBtn:SetText(NS.L["RESET_LIST"] or "Reset List")
    rListBtn:SetScript("OnClick", function()
        GR.foundPlayers = {}
        GR:UpdateScrollFrame()
        if GR.StatusText then GR.StatusText:SetText(NS.L["LIST_DISCARDED"] or "List Discarded.") end
    end)
    NS.SkinButton(rListBtn)

    -- Status text positioned below buttons
    self.StatusText = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    self.StatusText:SetPoint("TOPRIGHT", sBtn, "BOTTOMRIGHT", 0, -5)
    self.StatusText:SetText(NS.L["READY"] or "Ready.")

    local headF = CreateFrame("Frame", nil, parent); headF:SetHeight(20); headF:SetPoint("TOPLEFT", 21, -90) 

    local selectAll = CreateFrame("CheckButton", nil, headF, "UICheckButtonTemplate")
    selectAll:SetSize(20, 20)
    selectAll:SetPoint("LEFT", 0, 0)
    selectAll:SetScript("OnClick", function(self)
        local state = self:GetChecked()
        if GR.foundPlayers then
            for _, p in ipairs(GR.foundPlayers) do
                p.selected = state
            end
            GR:UpdateScrollFrame()
        end
    end)
    
    -- Column visibility config (each mode uses its own GRS config)
    local ccBlock = self.db.customColumns or {}
    local guildMode = self.db.guildMode
    local cc
    if guildMode == "social" then cc = ccBlock.socialGrs
    elseif guildMode == "raid" then cc = ccBlock.raidGrs
    elseif guildMode == "mplus" then cc = ccBlock.mplusGrs
    else cc = ccBlock.grs end
    cc = cc or {}
    local function IsGrsColVisible(key) return cc[key] ~= false end

    -- Build headers: fixed columns always shown, customizable columns filtered
    local headers = {
        {text=NS.L["NAME"] or "Name", key="name", width=110}, 
        {text=NS.L["RACE"] or "Race", key="race", width=90},  
        {text=NS.L["CLASS"] or "Class", key="fullClass", width=100},
        {text=NS.L["LEVEL"] or "Level", key="level", width=40}, 
    }
    -- Customizable columns in display order
    local customHeaders = {
        {text=NS.L["SCORE"] or "Score", key="rio", width=70},
        {text=NS.L["MAX_KEY"] or "Max Key", key="maxKey", width=70},
        {text=NS.L["WARBAND_SCORE"] or "Warband Score", key="warbandScore", width=100},
        {text=NS.L["ZONE"] or "Zone", key="zone", width=110}, 
        {text=NS.L["NORMAL"] or "Normal", key="raidN", width=70},
        {text=NS.L["HEROIC"] or "Heroic", key="heroic", width=70}, 
        {text=NS.L["MYTHIC"] or "Mythic", key="mythic", width=70},
    }
    for _, h in ipairs(customHeaders) do
        if IsGrsColVisible(h.key) then headers[#headers + 1] = h end
    end
    -- Store visible custom keys for row creation
    self.grsVisibleCols = {}
    for _, h in ipairs(customHeaders) do
        if IsGrsColVisible(h.key) then self.grsVisibleCols[h.key] = true end
    end
    
    local lastH = nil
    for i, data in ipairs(headers) do
        local btn = CreateFrame("Button", nil, headF)
        btn:SetSize(data.width, 20)
        
        if i==1 then 
            btn:SetPoint("LEFT", 30, 0) 
        else 
            btn:SetPoint("LEFT", lastH, "RIGHT", 0, 0) 
        end
        
        local txt = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        txt:SetPoint("LEFT", 5, 0)
        txt:SetText(data.text)
        btn:SetFontString(txt)
        
        -- Add border to header button
        local border = btn:CreateTexture(nil, "BORDER")
        border:SetAllPoints()
        border:SetColorTexture(0.3, 0.3, 0.3, 0.8)
        local highlight = btn:CreateTexture(nil, "ARTWORK")
        highlight:SetPoint("TOPLEFT", 1, -1)
        highlight:SetPoint("BOTTOMRIGHT", -1, 1)
        highlight:SetColorTexture(0.15, 0.15, 0.15, 0.9)
        
        btn:SetScript("OnEnter", function(s) 
            s:GetFontString():SetTextColor(1, 1, 1) 
        end)
        btn:SetScript("OnLeave", function(s) 
            s:GetFontString():SetTextColor(1, 0.82, 0) 
        end)
        
        btn:SetScript("OnClick", function() 
            GR:SortPlayers(data.key) 
        end)
        
        lastH = btn
    end

    -- Compute total accumulated header width (30 for selectAll checkbox + sum of all header widths)
    local totalHeaderW = 30
    for _, hd in ipairs(headers) do totalHeaderW = totalHeaderW + hd.width end
    headF:SetWidth(totalHeaderW)
    
    local bot = CreateFrame("Frame", nil, parent); bot:SetHeight(40)
    bot:SetPoint("BOTTOMLEFT", 15, 5)
    bot:SetPoint("BOTTOMRIGHT", -15, 5)
    
    local slL = bot:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall"); slL:SetPoint("LEFT", 0, 0); slL:SetText(NS.L["MSG_DELAY"] or "Msg Delay: 0.8s")
    self.MsgDelayLabel = slL
    local sl = CreateFrame("Slider", nil, bot, "OptionsSliderTemplate")
    sl:SetPoint("LEFT", slL, "RIGHT", 10, 0); sl:SetWidth(150); sl:SetMinMaxValues(0.5, 1.0); sl:SetValue(0.8); sl:SetValueStep(0.1); sl:SetObeyStepOnDrag(true)
    sl:SetScript("OnValueChanged", function(s,v) v=math.floor(v*10)/10; self.db.scanDelay=v; slL:SetText((NS.L["MSG_DELAY_FORMAT"] or "Msg Delay: %ss"):format(v)) end)
    self.MsgDelaySlider = sl
    
    local snd = CreateFrame("Button", nil, bot, "UIPanelButtonTemplate"); snd:SetSize(120, 30); snd:SetPoint("RIGHT", -10, 0); snd:SetText(NS.L["SEND_BATCH"] or "Send Batch")
    snd:SetScript("OnClick", function()
        local mode = self.db.sendMode or "MESSAGE"
        if mode == "MESSAGE" then
            self:SendMessageSequence()
        else
            -- INVITE or BOTH mode: invite first player in list
            if #self.foundPlayers == 0 then
                print("|cffff0000[A.X.I.O.M.]|r No players in list."); return
            end
            local p = self.foundPlayers[1]
            local justName = Ambiguate(p.name, "none")
            C_GuildInfo.Invite(justName)
            self:AddToBlacklist(p.name)
            self:AddToRecent(p.name, mode == "BOTH" and "Invited + Msg" or "Invited")
            
            -- If BOTH mode, also send message
            if mode == "BOTH" and self.InputBody then
                local msg = self.InputBody:GetText()
                if msg and msg ~= "" then
                    local myName = UnitName("player")
                    local gName = GetGuildInfo("player") or "Guild"
                    local pClass = p.fullClass or "Hero"
                    
                    msg = msg:gsub("%b[]", function(captured)
                        local inner = captured:sub(2, -2)
                        local clean = inner:lower():gsub("%s+", "")
                        if clean == "player" then return justName end
                        if clean == "ownname" then return myName end
                        if clean == "guild" then return gName end
                        if clean == "playerclass" or clean == "class" then return pClass end
                        return captured
                    end)
                    
                    self.sessionWhitelist[justName] = true
                    SendChatMessage(msg, "WHISPER", nil, p.name)
                end
            end
            
            self:UpdateScrollFrame()
        end
    end)
    NS.SkinButton(snd)
    self.SendBatchBtn = snd 

    local btnBL = CreateFrame("Button", nil, bot, "UIPanelButtonTemplate")
    btnBL:SetSize(130, 30)
    btnBL:SetPoint("RIGHT", snd, "LEFT", -10, 0)
    btnBL:SetText(NS.L["BLACKLIST_SELECTED"] or "Blacklist Selected")
    btnBL:SetScript("OnClick", function() self:ManualBlacklist() end)
    NS.SkinButton(btnBL)

    -- =========================================================================
    -- SEND MODE DROPDOWN
    -- =========================================================================
    local SEND_MODES = {
        { key = "MESSAGE", label = NS.L["SEND_MODE_MESSAGE"] or "Message Only" },
        { key = "INVITE",  label = NS.L["SEND_MODE_INVITE"] or "Guild Invite Only" },
        { key = "BOTH",    label = NS.L["SEND_MODE_BOTH"] or "Message + G-Invite" },
    }
    
    -- Initialize default if not set
    if not self.db.sendMode then self.db.sendMode = "MESSAGE" end
    
    -- Get label for current mode
    local function GetModeLabel(modeKey)
        for _, m in ipairs(SEND_MODES) do
            if m.key == modeKey then return m.label end
        end
        return SEND_MODES[1].label
    end
    
    local dMode = CreateFrame("Button", nil, bot)
    NS.CreateBg(dMode, 0.2, 0.2, 0.2, 1)
    dMode:SetSize(145, 30)
    dMode:SetPoint("RIGHT", btnBL, "LEFT", -15, 0)
    
    local dModeTxt = dMode:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    dModeTxt:SetPoint("LEFT", 8, 0)
    dModeTxt:SetText(GetModeLabel(self.db.sendMode))
    
    local dModeArrow = dMode:CreateTexture(nil, "ARTWORK")
    dModeArrow:SetTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Up")
    dModeArrow:SetSize(20, 20)
    dModeArrow:SetPoint("RIGHT", -2, 0)
    
    -- Dropdown list (opens UPWARD)
    local dModeList = CreateFrame("Frame", nil, dMode)
    NS.CreateBg(dModeList, 0.1, 0.1, 0.1, 1)
    NS.CreateBorder(dModeList, 0.5, 0.5, 0.5, 1)
    dModeList:SetSize(150, 70)
    dModeList:SetPoint("BOTTOMLEFT", dMode, "TOPLEFT", 0, 2)
    dModeList:SetFrameStrata("TOOLTIP") -- Above FULLSCREEN_DIALOG to clear compact buttons
    dModeList:SetFrameLevel(200)
    dModeList:Hide()
    
    local ly = -5
    for _, mode in ipairs(SEND_MODES) do
        local b = CreateFrame("Button", nil, dModeList)
        b:SetSize(140, 20)
        b:SetPoint("TOPLEFT", 5, ly)
        
        -- Hover glow background
        local glow = b:CreateTexture(nil, "BACKGROUND")
        glow:SetAllPoints()
        glow:SetColorTexture(0.2, 0.8, 0.3, 0.15)
        glow:Hide()
        
        local t = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        t:SetPoint("LEFT")
        t:SetText(mode.label)
        b:SetScript("OnClick", function()
            self.db.sendMode = mode.key
            dModeTxt:SetText(mode.label)
            dModeList:Hide()
            self:UpdateSendModeUI()
        end)
        b:SetScript("OnEnter", function(s) t:SetTextColor(1, 1, 1); glow:Show() end)
        b:SetScript("OnLeave", function(s) t:SetTextColor(1, 0.82, 0); glow:Hide() end)
        ly = ly - 20
    end
    dModeList:SetHeight(math.abs(ly) + 5)
    
    dMode:SetScript("OnClick", function()
        dModeList:SetShown(not dModeList:IsShown())
    end)
    
    -- Expose for Compact Mode repositioning
    self.SendModeBtn = dMode
    self.SendModeList = dModeList
    self.SendModeOriginalParent = bot

    -- StatusText is now created above with the search buttons
    
    -- =========================================================================
    -- LAYERING FIX: Separate Backdrop from Scroll Logic
    -- =========================================================================
    
    -- 1. Create the Backdrop "Slab" first
    -- This frame sits at the bottom and holds the black background texture
    local listBackdrop = CreateFrame("Frame", nil, parent)
    listBackdrop:SetPoint("TOPLEFT", headF, "BOTTOMLEFT", 0, -5)
    listBackdrop:SetPoint("BOTTOMRIGHT", bot, "TOPRIGHT", -5, 5)
    listBackdrop:SetFrameLevel(parent:GetFrameLevel() + 1) -- Just above parent
    listBackdrop:SetClipsChildren(true) -- CRITICAL: Prevent children from bleeding outside
    
    -- Apply background to THIS frame, not the scroll frame
    NS.CreateBg(listBackdrop, 0, 0, 0, 0.8) 
    self.listBackdrop = listBackdrop

    -- 2. Create the Logical ScrollFrame
    self.scroll = CreateFrame("ScrollFrame", "GRS_S", parent, "FauxScrollFrameTemplate")
    parent.scrollFrame = self.scroll
    parent.scroll = self.scroll 
    local scrollFrame = self.scroll 
    
    -- Anchor scrollframe to the backdrop
    scrollFrame:SetAllPoints(listBackdrop)
    scrollFrame:SetClipsChildren(true) -- CRITICAL: Prevent rows from bleeding outside
    
    -- Ensure scroll logic sits ABOVE the backdrop
    scrollFrame:SetFrameLevel(listBackdrop:GetFrameLevel() + 5)
    
    -- ScrollBar Styling (matches Raid Roster design)
    local scrollBar = _G["GRS_SScrollBar"]
    if scrollBar then
        NS.StyleScrollBar(scrollBar)
        -- Position scrollbar within list backdrop
        scrollBar:ClearAllPoints()
        scrollBar:SetPoint("TOPRIGHT", listBackdrop, "TOPRIGHT", 0, -2)
        scrollBar:SetPoint("BOTTOMRIGHT", listBackdrop, "BOTTOMRIGHT", 0, 2)
        scrollBar:Show()
    end

    scrollFrame:SetScript("OnVerticalScroll", function(s, offset)
        FauxScrollFrame_OnVerticalScroll(s, offset, 20, function() self:UpdateScrollFrame() end)
    end)
    
    scrollFrame:EnableMouseWheel(true)
    
    parent:SetScript("OnSizeChanged", function(frame, w, h)
        local sFrame = scrollFrame or frame.scrollFrame or frame.scroll
        if not sFrame then return end

        -- Dynamic width: use header total
        
        bot:ClearAllPoints()
        bot:SetPoint("BOTTOMLEFT", 15, 5)
        bot:SetPoint("BOTTOMRIGHT", -15, 5)
        
        -- Update Backdrop with fixed width
        if self.listBackdrop then
            self.listBackdrop:ClearAllPoints()
            self.listBackdrop:SetPoint("TOPLEFT", headF, "BOTTOMLEFT", 0, -5)
            self.listBackdrop:SetPoint("BOTTOMLEFT", bot, "TOPLEFT", 0, 5)
            self.listBackdrop:SetWidth(headF:GetWidth())
        end
        
        -- Snap Scroll to Backdrop
        sFrame:ClearAllPoints()
        sFrame:SetAllPoints(self.listBackdrop)
    end)
    
    self.rows = {}
    
    function GR:UpdateResultsLayout(height)
        if not height then height = self.scroll:GetHeight() end
        local numRows = math.floor(height / 20) - 2 -- Subtract 2 to prevent bottom row bleed
        if numRows < 1 then numRows = 1 end -- Ensure at least 1 row
        self.visibleRows = numRows 
        
        for i = 1, numRows do
            if not self.rows[i] then
                local r = CreateFrame("Button", nil, parent)
                -- Force rows to sit above the backdrop (ScrollFrame level + 1)
                r:SetFrameLevel(self.scroll:GetFrameLevel() + 1)
                
                r:SetHeight(20); 
                r:SetPoint("LEFT", 41, 0); 
                r:SetPoint("RIGHT", -25, 0)
                if i==1 then r:SetPoint("TOP", self.scroll, "TOP") else r:SetPoint("TOP", self.rows[i-1], "BOTTOM") end
                
                r.c = CreateFrame("CheckButton", nil, r, "UICheckButtonTemplate"); r.c:SetSize(20,20); r.c:SetPoint("LEFT", -20, 0)
                r.c:SetScript("OnClick", function(b) if r.d then r.d.selected=b:GetChecked() end end)
                
                -- Fixed columns (always visible)
                r.n = r:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall"); r.n:SetPoint("LEFT",15,0); r.n:SetWidth(110); r.n:SetJustifyH("LEFT")
                r.ra = r:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall"); r.ra:SetPoint("LEFT",r.n,"RIGHT"); r.ra:SetWidth(90); r.ra:SetJustifyH("LEFT")
                r.cl = r:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall"); r.cl:SetPoint("LEFT",r.ra,"RIGHT"); r.cl:SetWidth(100); r.cl:SetJustifyH("LEFT")
                r.lvl = r:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall"); r.lvl:SetPoint("LEFT",r.cl,"RIGHT"); r.lvl:SetWidth(40); r.lvl:SetJustifyH("LEFT")
                
                -- Customizable columns: created dynamically, chained in order
                local lastAnchor = r.lvl
                local colDefs = {
                    {key="rio",    ref="s",  width=70,  gap=5},
                    {key="maxKey", ref="mk", width=70,  gap=0},
                    {key="warbandScore", ref="ws", width=100, gap=0},
                    {key="zone",   ref="z",  width=110, gap=0},
                    {key="raidN",  ref="rn", width=70,  gap=0},
                    {key="heroic", ref="h",  width=70,  gap=0},
                    {key="mythic", ref="m",  width=70,  gap=0},
                }
                local vc = self.grsVisibleCols or {}
                for _, cd in ipairs(colDefs) do
                    if vc[cd.key] then
                        local fs = r:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall")
                        fs:SetPoint("LEFT", lastAnchor, "RIGHT", cd.gap, 0)
                        fs:SetWidth(cd.width); fs:SetJustifyH("LEFT")
                        r[cd.ref] = fs
                        lastAnchor = fs
                    end
                end
                
                -- ROW CLICK HANDLER: In invite modes, clicking row triggers GuildInvite (1:1 TBC Parity)
                r:SetScript("OnClick", function(self)
                    if not self.d then return end
                    local mode = GR.db.sendMode or "MESSAGE"
                    if mode == "INVITE" or mode == "BOTH" then
                        local justName = Ambiguate(self.d.name, "none")
                        C_GuildInfo.Invite(justName)
                        GR:AddToBlacklist(self.d.name)
                        GR:AddToRecent(self.d.name, mode == "BOTH" and "Invited + Msg" or "Invited")
                        
                        -- If BOTH mode, also send message
                        if mode == "BOTH" and GR.InputBody then
                            local msg = GR.InputBody:GetText()
                            if msg and msg ~= "" then
                                local safeName = justName
                                local myName = UnitName("player")
                                local gName = GetGuildInfo("player") or "Guild"
                                local pClass = self.d.fullClass or "Hero"
                                
                                msg = msg:gsub("%b[]", function(captured)
                                    local inner = captured:sub(2, -2)
                                    local clean = inner:lower():gsub("%s+", "")
                                    if clean == "player" then return safeName end
                                    if clean == "ownname" then return myName end
                                    if clean == "guild" then return gName end
                                    if clean == "playerclass" or clean == "class" then return pClass end
                                    return captured
                                end)
                                
                                -- Add to sessionWhitelist BEFORE sending so whisper pane tracks it
                                GR.sessionWhitelist[justName] = true
                                SendChatMessage(msg, "WHISPER", nil, self.d.name)
                            end
                        end
                        
                        -- Remove from list after invite
                        for k, v in ipairs(GR.foundPlayers) do
                            if v.name == self.d.name then
                                table.remove(GR.foundPlayers, k)
                                break
                            end
                        end
                        GR:UpdateScrollFrame()
                    end
                end)
                
                -- Highlight on hover for invite mode (1:1 TBC Parity)
                r:SetScript("OnEnter", function(self)
                    local mode = GR.db.sendMode or "MESSAGE"
                    if mode == "INVITE" or mode == "BOTH" then
                        self.n:SetTextColor(0, 1, 0.5)
                    end
                end)
                r:SetScript("OnLeave", function(self)
                    self.n:SetTextColor(1, 1, 1)
                end)
                
                self.rows[i] = r
            end
        end
        self:UpdateScrollFrame() 
    end
end

function GR:UpdateScrollFrame()
    local d = self.foundPlayers or {}
    local rowCount = self.visibleRows or 15
    if rowCount > #self.rows then rowCount = #self.rows end
    
    FauxScrollFrame_Update(self.scroll, #d, rowCount, 20)
    
    local scrollBar = _G["GRS_SScrollBar"]
    if scrollBar then
        scrollBar:Show() 
        
        local _, maxVal = scrollBar:GetMinMaxValues()
        local thumb = scrollBar:GetThumbTexture()
        
        if not maxVal or maxVal == 0 then
             if thumb then thumb:Hide() end
             
             local up = _G["GRS_SScrollBarScrollUpButton"]
             local down = _G["GRS_SScrollBarScrollDownButton"]
             if up then up:Disable() end
             if down then down:Disable() end
        else
             if thumb then thumb:Show() end
             
             local up = _G["GRS_SScrollBarScrollUpButton"]
             local down = _G["GRS_SScrollBarScrollUpButton"]
             if up then up:Enable() end
             if down then down:Enable() end
        end
    end
    
    local offset = FauxScrollFrame_GetOffset(self.scroll)
    
    for i=1, #self.rows do
        local r = self.rows[i]
        if i > rowCount then
            r:Hide()
        else
            local idx = offset + i
            if idx <= #d then
                local p = d[idx]
                r.d = p; r:Show()
                r.n:SetText(p.name); 
                r.lvl:SetText(p.level);
                r.ra:SetText(p.race or "Unknown")
                local classColor = C_ClassColor.GetClassColor(p.classFile or "PRIEST")
                r.cl:SetText(classColor and classColor:WrapTextInColorCode(p.fullClass) or p.fullClass)
                
                -- Customizable columns: only set if font string exists
                if r.mk then r.mk:SetText(p.maxKey) end
                if r.z then r.z:SetText(p.zone) end
                
                if r.s then
                    local sc = p.rio or 0; local col = "|cffffffff"
                    if sc>=2500 then col="|cffff8000" 
                    elseif sc>=2000 then col="|cffa335ee" 
                    elseif sc>=1000 then col="|cff0070dd" 
                    end
                    r.s:SetText(col..sc.."|r")
                end
                
                local function Col(str)
                    if not str or str == "-" then return "|cff808080"..(str or "-").."|r" end
                    local k = tonumber(str:match("^(%d+)")) or 0
                    if k >= 8 then return "|cffff8000"..str.."|r"
                    elseif k >= 4 then return "|cffa335ee"..str.."|r"
                    else return "|cff00ff00"..str.."|r" end
                end
                
                if r.rn then r.rn:SetText(Col(p.raidN)) end
                if r.h then r.h:SetText(Col(p.heroic)) end
                if r.m then r.m:SetText(Col(p.mythic)) end
                if r.ws then
                    local sc = p.warbandScore or 0; local col = "|cffffffff"
                    if sc>=2500 then col="|cffff8000"
                    elseif sc>=2000 then col="|cffa335ee"
                    elseif sc>=1000 then col="|cff0070dd"
                    end
                    r.ws:SetText(col..sc.."|r")
                end
                r.c:SetChecked(p.selected)
                
                -- Hide/show checkbox based on mode
                local mode = self.db.sendMode or "MESSAGE"
                if mode == "INVITE" or mode == "BOTH" then
                    r.c:Hide()
                else
                    r.c:Show()
                end
            else 
                r:Hide() 
            end
        end
    end
end

-- MISSING FUNCTION IMPLEMENTATION: SORT PLAYERS
function GR:SortPlayers(key)
    if not self.foundPlayers or #self.foundPlayers == 0 then return end
    
    -- Toggle sort direction state if needed, or default to ASC
    if not self.sortState then self.sortState = {} end
    if self.sortState.key == key then
        self.sortState.asc = not self.sortState.asc
    else
        self.sortState.key = key
        self.sortState.asc = true
    end
    
    table.sort(self.foundPlayers, function(a, b)
        local v1, v2 = a[key], b[key]
        -- Handle nils
        if v1 == v2 then return false end
        if v1 == nil then return false end
        if v2 == nil then return true end
        
        -- Compare
        if self.sortState.asc then
            return v1 < v2
        else
            return v1 > v2
        end
    end)
    
    self:UpdateScrollFrame()
end

-- MISSING FUNCTION IMPLEMENTATION: MANUAL BLACKLIST (Selected Players)
function GR:ManualBlacklist()
    if not self.foundPlayers or #self.foundPlayers == 0 then return end
    local count = 0
    for i = #self.foundPlayers, 1, -1 do
        local p = self.foundPlayers[i]
        if p.selected then
            self:AddToBlacklist(p.name)
            table.remove(self.foundPlayers, i)
            count = count + 1
        end
    end
    if count > 0 then
        print("|cff00ccff[A.X.I.O.M.]|r Blacklisted " .. count .. " player(s).")
    end
    self:UpdateScrollFrame()
end

-- =========================================================================
-- UI: UPDATE SEND MODE (Button text, slider visibility, checkbox refresh)
-- =========================================================================

function GR:UpdateSendModeUI()
    local mode = self.db.sendMode or "MESSAGE"
    
    -- Update button text
    if self.SendBatchBtn then
        if mode == "MESSAGE" then
            self.SendBatchBtn:SetText("Send Batch")
        else
            self.SendBatchBtn:SetText("Invite to Guild")
        end
        self.SendBatchBtn:Enable()
    end
    
    -- Update compact mode button text (1:1 parity)
    if self.CompactBatchBtn then
        if mode == "MESSAGE" then
            self.CompactBatchBtn:SetText("Send Batch")
        else
            self.CompactBatchBtn:SetText("Invite to Guild")
        end
    end
    
    -- Show/hide message delay slider based on mode
    if self.MsgDelaySlider then
        if mode == "MESSAGE" then
            self.MsgDelaySlider:Show()
            if self.MsgDelayLabel then self.MsgDelayLabel:Show() end
        else
            self.MsgDelaySlider:Hide()
            if self.MsgDelayLabel then self.MsgDelayLabel:Hide() end
        end
    end
    
    -- Refresh scroll frame to update checkbox visibility
    self:UpdateScrollFrame()
end