local addonName, NS = ...
local GR = NS.GR -- Reference the Core Frame

-- =========================================================================
-- ENGINE: HELPERS
-- =========================================================================

local function GetRegionName()
    local regionID = GetCurrentRegion()
    if regionID == 1 then return "us"
    elseif regionID == 2 then return "kr"
    elseif regionID == 3 then return "eu"
    elseif regionID == 4 then return "tw"
    elseif regionID == 5 then return "cn"
    else return "us" end 
end

local function GetFactionID()
    local factionGroup = UnitFactionGroup("player")
    if factionGroup == "Alliance" then return 1
    elseif factionGroup == "Horde" then return 2
    else return 2 end 
end

local function GetTableSize(t)
    local count = 0
    if t then for _ in pairs(t) do count = count + 1 end end
    return count
end

NS.Engine = {}
NS.Engine.GetTableSize = GetTableSize 

-- Initialize Engine Properties on GR
GR.foundPlayers = {}    
GR.currentTemplate = "M+ Focused"
GR.rows = {}            
GR.chatFrames = {}      
GR.activeChats = {} 
GR.recruitedPlayers = {} -- Registry for strict recruitment tracking
GR.currentPage = 1 
GR.classIndex = 1
GR.selectedFilterData = nil 
GR.isScanning = false 
GR.lastScanTime = 0 
GR.sortKey = "name"
GR.sortOrder = "ASC" 

-- =========================================================================
-- ENGINE: STRICT BYTE-RATE LIMITER (12 BPS)
-- =========================================================================

NS.MsgQueue = {}
NS.ByteBudget = 255 -- Start with enough for 1 message immediately
NS.MaxBudget = 512  -- Cap stored budget (don't let it grow infinitely)
NS.RefillRate = 12  -- Bytes per second

-- The Heartbeat: Processes the queue based on Byte Budget
local function ProcessMsgQueue()
    -- 1. Refill Budget
    if NS.ByteBudget < NS.MaxBudget then
        NS.ByteBudget = NS.ByteBudget + NS.RefillRate
        if NS.ByteBudget > NS.MaxBudget then NS.ByteBudget = NS.MaxBudget end
    end

    -- 2. Process Queue
    if #NS.MsgQueue == 0 then return end
    
    local task = NS.MsgQueue[1] -- Peek at first item
    
    -- Calculate Cost (Prefix + Text Length)
    local cost = string.len(task.prefix or "") + string.len(task.text or "") + 1
    
    -- Check Affordability
    if NS.ByteBudget >= cost then
        -- We can afford it. Send it.
        table.remove(NS.MsgQueue, 1) -- Pop
        C_ChatInfo.SendAddonMessage(task.prefix, task.text, task.channel)
        
        -- Deduct cost
        NS.ByteBudget = NS.ByteBudget - cost
        
        -- Optional Debug
        if GR.db and GR.db.debugMode then
            print(string.format("GRS SafeSend: Sent %d bytes. Remaining Budget: %d", cost, NS.ByteBudget))
        end
    else
        -- Too expensive. Wait for next tick.
        -- Do nothing.
    end
end

-- Ticker runs every 1.0 second to add 12 bytes
if not NS.QueueTicker then
    NS.QueueTicker = C_Timer.NewTicker(1.0, ProcessMsgQueue)
end

function GR:SafeSendAddonMessage(prefix, text, channel)
    if not prefix or not text or not channel then return end
    
    table.insert(NS.MsgQueue, {
        prefix = prefix,
        text = text,
        channel = channel
    })
    
    -- Trigger an immediate check in case we have budget now (don't wait 1s if we don't have to)
    ProcessMsgQueue()
end

-- =========================================================================
-- ENGINE: SEARCH & DATA PROCESSING
-- =========================================================================

function GR:InjectGlobalMenus()
    if Menu and Menu.ModifyMenu then
        local function AddGuildInvite(rootDescription, contextData)
            local name = contextData.name
            if not name and contextData.unit then name = UnitName(contextData.unit) end
            if name then
                name = Ambiguate(name, "none")
                rootDescription:CreateDivider()
                rootDescription:CreateTitle("GRS Actions")
                rootDescription:CreateButton("Invite to Guild", function() GuildInvite(name) end)
            end
        end
        Menu.ModifyMenu("MENU_UNIT_PLAYER", function(owner, root, context) AddGuildInvite(root, context) end)
        Menu.ModifyMenu("MENU_UNIT_FRIEND", function(owner, root, context) AddGuildInvite(root, context) end)
        Menu.ModifyMenu("MENU_CHAT_PLAYER", function(owner, root, context) AddGuildInvite(root, context) end)
        Menu.ModifyMenu("MENU_UNIT_PARTY", function(owner, root, context) AddGuildInvite(root, context) end)
        Menu.ModifyMenu("MENU_UNIT_RAID", function(owner, root, context) AddGuildInvite(root, context) end)
    end
end

function GR:StartSearch()
    local now = GetTime()
    if (now - self.lastScanTime) < 5 then
        return
    end

    self:UpdateRaiderIOStatus() 
    self.lastScanTime = now
    self.isScanning = true 

    -- [[ BUTTON COOLDOWN MANAGEMENT ]]
    -- Apply cooldown visual to BOTH Standard and Compact buttons
    local buttonsToUpdate = {self.SearchBtn, self.CompactSearchBtn}
    
    for _, btn in pairs(buttonsToUpdate) do
        if btn then
            btn:Disable()
            -- Set initial text
            if btn == self.SearchBtn then
                btn:SetText("|cffff0000Cooldown for search (5)|r") -- Standard Text
            else
                btn:SetText("|cffff0000Cooldown (5)|r") -- Compact Text (Shorter)
            end
        end
    end

    -- Timer for countdown
    local cd = 5
    C_Timer.NewTicker(1, function(timer)
        cd = cd - 1
        if cd > 0 then
            for _, btn in pairs(buttonsToUpdate) do
                if btn then
                    if btn == self.SearchBtn then
                        btn:SetText("|cffff0000Cooldown for search ("..cd..")|r")
                    else
                        btn:SetText("|cffff0000Cooldown ("..cd..")|r")
                    end
                end
            end
        else
            timer:Cancel()
            for _, btn in pairs(buttonsToUpdate) do
                if btn then
                    btn:Enable()
                    btn:SetText("Search Players")
                end
            end
        end
    end, 5)

    C_Timer.After(60, function()
        if GR.isScanning then
            GR.isScanning = false
            if GR.StatusText then GR.StatusText:SetText("Scan Timed Out - Ready") end
        end
    end)

    local query = ""
    local filterName = "Custom"
    local modeLabel = ""
    if self.db.remixMode == "OFF" then modeLabel = "World Only"
    elseif self.db.remixMode == "ALLOW" then modeLabel = "All Zones" end

    local filterType = self.filterType or "Default"
    local activeClass = nil
    local activeRace = nil
    
    local CLASS_LIST = NS.Constants.CLASS_LIST

    if filterType == "Default" then
        activeClass = CLASS_LIST[self.classIndex]
        self.classIndex = self.classIndex + 1
        if self.classIndex > #CLASS_LIST then self.classIndex = 1 end
    else
        if self.selectedFilterData then
            if self.selectedFilterData.class == "Any" then
                activeClass = CLASS_LIST[self.classIndex]
                self.classIndex = self.classIndex + 1
                if self.classIndex > #CLASS_LIST then self.classIndex = 1 end
            else
                activeClass = self.selectedFilterData.class
            end
            if self.selectedFilterData.race ~= "Any" then activeRace = self.selectedFilterData.race end
            filterName = self.selectedFilterData.name
        end
    end

    local parts = {}
    if activeClass then table.insert(parts, 'c-"'..activeClass..'"') end
    if activeRace then table.insert(parts, 'r-"'..activeRace..'"') end
    table.insert(parts, string.format("%d-%d", self.db.minLevel, self.db.maxLevel))
    query = table.concat(parts, " ")

    if self.ResultsHeader then
        self.ResultsHeader:SetText(string.format("Unguilded Players - Current Filter: %s + %s", (filterName == "Custom" and activeClass or filterName), modeLabel))
    end
    if self.StatusText then
        self.StatusText:SetText("Scanning: " .. query .. "...")
    end

    if self.db.debugMode then
        self:GenerateDebugData()
        self.isScanning = false 
        return
    end

    if FriendsFrame then FriendsFrame:UnregisterEvent("WHO_LIST_UPDATE") end
    C_FriendList.SetWhoToUi(1) 
    C_FriendList.SendWho(query)
end

function GR:ProcessWhoResults()
    if not self.isScanning then return end
    if FriendsFrame and FriendsFrame:IsShown() then FriendsFrame:Hide() end
    if self.db.debugMode then self.isScanning = false; return end
    
    local numWhos = C_FriendList.GetNumWhoResults()
    local newCount = 0
    local myRealm = GetRealmName()
    
    for i = 1, numWhos do
        local info = C_FriendList.GetWhoInfo(i)
        if info and (not info.fullGuildName or info.fullGuildName == "") then
            local zone = info.area or "Unknown"
            
            -- [[ LOGIC: World Only vs All ]]
            local isBlocklisted = NS.Data and NS.Data.BLOCKLISTED_ZONES and NS.Data.BLOCKLISTED_ZONES[zone]
            local mode = self.db.remixMode -- "OFF" = World Only, "ALLOW" = All
            
            local keep = false
            if mode == "ALLOW" then
                keep = true -- "All" mode: Ignore Blocklist (Risky)
            elseif mode == "OFF" then
                -- "World Only" mode: Strict Blocklist enforcement
                if not isBlocklisted then keep = true end
            end
            
            if keep then
                local isBlacklisted = self.db.blacklist[info.fullName]
                if not isBlacklisted then
                    local normName = info.fullName
                    if not string.find(normName, "-") then
                         normName = normName .. "-" .. myRealm
                    end
                    if self.db.blacklist[normName] then isBlacklisted = true end
                end
                
                if not isBlacklisted then
                    if not self:IsDuplicate(info.fullName) then
                        self:AddPlayerToTable(info, zone, isBlocklisted)
                        newCount = newCount + 1
                    end
                end
            end
        end
    end
    if self.StatusText then
        self.StatusText:SetText("Scan Complete. Added " .. newCount .. " new players.")
    end
    self:UpdateScrollFrame()
    self.isScanning = false
end

-- [[ FIXED DUPLICATE CHECK ]]
function GR:IsDuplicate(name)
    local target = Ambiguate(name, "none") -- Strip realm for comparison
    for _, p in ipairs(self.foundPlayers) do
        if Ambiguate(p.name, "none") == target then return true end
    end
    return false
end

function GR:AddPlayerToTable(info, zone, isRiskyZone)
    -- CLEANED FOR TBC: No RIO, No Raid Progress Logic
    table.insert(self.foundPlayers, {
        name = info.fullName,
        race = info.raceStr or "Unknown", 
        fullClass = info.filename,
        classFile = info.filename,
        level = info.level,
        zone = zone,
        status = "Online",
        version = isRiskyZone and "Instance" or "World",
        selected = false 
    })
    
    self:SortPlayers(self.sortKey, true)
end

function GR:SortPlayers(key, keepOrder)
    if not keepOrder then
        if self.sortKey == key then
            self.sortOrder = (self.sortOrder == "ASC") and "DESC" or "ASC"
        else
            self.sortKey = key
            self.sortOrder = "ASC"
        end
    end

    table.sort(self.foundPlayers, function(a, b)
        local valA, valB = a[key], b[key]
        
        if key == "level" then
            valA = tonumber(valA) or 0
            valB = tonumber(valB) or 0
        else
            valA = tostring(valA):lower()
            valB = tostring(valB):lower()
        end

        if self.sortOrder == "ASC" then
            return valA < valB
        else
            return valA > valB
        end
    end)
    
    self:UpdateScrollFrame()
end

function GR:GenerateDebugData()
    local name = UnitName("player")
    local _, classFile = UnitClass("player")
    local className = UnitClass("player") 
    local level = UnitLevel("player")
    local zone = GetZoneText()
    local race = UnitRace("player") 
    
    if self.db.blacklist[name] then
        print("Debug Info: " .. name .. " is in blacklist. Skipping table add.")
        return
    end

    local p = {
        name = name,
        race = race, 
        fullClass = className,
        classFile = classFile,
        level = level,
        zone = zone,
        status = "Online",
        version = "Retail",
        selected = true
    }
    
    if not self:IsDuplicate(p.name) then
        table.insert(self.foundPlayers, p)
        self:SortPlayers(self.sortKey, true)
        self:UpdateScrollFrame()
        self.StatusText:SetText("Added SELF (Debug).")
    else
        print("Debug: You are already in the list.")
    end
end

function GR:RemovePlayer(name)
    if not name then return end
    for i = #self.foundPlayers, 1, -1 do
        if self.foundPlayers[i].name == name then table.remove(self.foundPlayers, i) end
    end
    self:UpdateScrollFrame()
end

function GR:UpdateRecentList() end

function GR:AddToRecent(name, status)
    local hist = self.db.recentHistory
    for i, v in ipairs(hist) do if v.name == name then table.remove(hist, i); break end end
    table.insert(hist, 1, {name=name, status=status})
    if #hist > 10 then table.remove(hist) end 
    self:UpdateRecentList()
end

-- =========================================================================
-- ENGINE: CHAT / MESSAGING LOGIC
-- =========================================================================

function GR:SendMessageSequence()
    local rawMsg = self.InputBody:GetText()
    if rawMsg == "" then print("Message empty."); return end
    local queue = {}
    for _, p in ipairs(self.foundPlayers) do if p.selected then table.insert(queue, p) end end
    if #queue == 0 then print("No players selected."); return end
    
    self.StatusText:SetText("Initializing Batch Protocol...")
    local batchSize = 5; local batchCooldown = 5; local currentIndex = 1
    
    local function ProcessNext()
        if currentIndex > #queue then GR.StatusText:SetText("All messages sent."); return end
        local p = queue[currentIndex]
        
        local msg = rawMsg
        local safeName = Ambiguate(p.name, "none")
        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 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)
        
        local isBatchPause = (currentIndex > 1) and ((currentIndex - 1) % batchSize == 0)
        local delay = isBatchPause and batchCooldown or GR.db.scanDelay
        
        C_Timer.After(delay, function()
            local checkName = p.name
            if not string.find(checkName, "-") then checkName = checkName .. "-" .. GetRealmName() end
            
            if not GR.db.blacklist[checkName] and not GR.db.blacklist[p.name] then
                if GR.db.debugMode then
                    print("|cff00ff00[DEBUG]:|r Would whisper: " .. p.name .. " -> " .. msg)
                    GR:AddToRecent(p.name, "Sent (Debug)")
                    if p.name == UnitName("player") then SendChatMessage(msg, "WHISPER", nil, p.name) end
                    GR:AddToBlacklist(p.name)
                    -- Debug Whitelist
                    GR.recruitedPlayers[Ambiguate(p.name, "none")] = true
                else
                    SendChatMessage(msg, "WHISPER", nil, p.name)
                    GR:AddToBlacklist(p.name) 
                    GR:AddToRecent(p.name, "Sent")
                    -- [[ STRICT MODE: Register this player as valid for Whisper Grid ]]
                    GR.recruitedPlayers[Ambiguate(p.name, "none")] = true
                end
            end
            currentIndex = currentIndex + 1
            ProcessNext() 
        end)
    end
    ProcessNext() 
end

function GR:HandleChatEvent(event, msg, sender, ...)
    if not sender then return end
    sender = Ambiguate(sender, "none") 
    if not self:IsShown() then return end
    
    -- [[ STRICT FILTER: Only show chats initiated by the addon ]]
    -- Use the recruitedPlayers registry.
    if not GR.recruitedPlayers[sender] then return end
    
    self:UpdateWhisperGrid(sender, msg, event == "CHAT_MSG_WHISPER_INFORM")
end

function GR:UpdateWhisperGrid(name, msg, isOutgoing)
    local session = nil
    for _, s in ipairs(self.activeChats) do if s.name == name then session = s; break end end
    
    local pageSize = self.chatPageSize or 4
    
    if not session then
        if #self.activeChats >= 100 then table.remove(self.activeChats, 1) end
        session = {name = name, log = {}, hasReplied = false}
        table.insert(self.activeChats, session)
        -- Reset pagination logic might change with filtering, handle in Render
    end
    
    session.lastActivity = GetTime()
    
    -- [[ TRACK REPLIES ]]
    if not isOutgoing then
        session.hasReplied = true
    end
    
    local color = isOutgoing and "|cff00ffff[You]: |r" or "|cffff8000["..name.."]: |r"
    table.insert(session.log, color .. msg)
    if #session.log > 10 then table.remove(session.log, 1) end 
    
    self:RenderWhisperGrid()
end

function GR:PruneInactiveChats()
    local limit = 120; local now = GetTime(); local count = 0
    for i = #self.activeChats, 1, -1 do
        local s = self.activeChats[i]
        if s.lastActivity and (now - s.lastActivity > limit) then
            table.remove(self.activeChats, i); count = count + 1
        end
    end
    self:RenderWhisperGrid()
    print("GRS: Pruned " .. count .. " inactive chats.")
end

function GR:RenderWhisperGrid()
    local pageSize = self.chatPageSize or 4
    
    -- [[ FILTERING LOGIC FOR REPLIES ONLY ]]
    local displayList = {}
    if GR.db and GR.db.showRepliesOnly then
        for _, session in ipairs(self.activeChats) do
            if session.hasReplied then
                table.insert(displayList, session)
            end
        end
    else
        displayList = self.activeChats
    end
    
    local totalChats = #displayList
    local totalPages = math.ceil(totalChats / pageSize)
    if totalPages == 0 then totalPages = 1 end
    
    if self.currentPage > totalPages then self.currentPage = totalPages end
    if self.currentPage < 1 then self.currentPage = 1 end
    
    if self.pageText then
        self.pageText:SetText(string.format("Page %d / %d", self.currentPage, totalPages))
        if self.currentPage == 1 then self.prevBtn:Disable() else self.prevBtn:Enable() end
        if self.currentPage == totalPages then self.nextBtn:Disable() else self.nextBtn:Enable() end
    end
    
    local startIndex = (self.currentPage - 1) * pageSize + 1
    
    for _, f in pairs(self.chatFrames) do f:Hide() end

    for i = 1, pageSize do
        local frame = self.chatFrames[i]
        if frame then
            local dataIndex = startIndex + (i - 1)
            local session = displayList[dataIndex]
            
            if session then
                frame:Show()
                frame.headerBtn:Show()
                frame.headerBtn.text:SetText(session.name)
                frame.headerBtn.playerName = session.name 
                frame.dataIndex = dataIndex 
                local fullText = ""
                for _, line in ipairs(session.log) do fullText = fullText .. line .. "\n" end
                frame.text:SetText(fullText)
                frame.scroll:SetVerticalScroll(frame.scroll:GetVerticalScrollRange())
            end
        end
    end
end

-- =========================================================================
-- ENGINE: FILTERS & LOGIC
-- =========================================================================

function GR:BuildFilterUI()
    local f = CreateFrame("Frame", "GRS_FilterFrame", UIParent)
    f:SetSize(300, 250); f:SetPoint("CENTER"); f:SetFrameStrata("FULLSCREEN_DIALOG"); f:Hide()
    NS.CreateBg(f, 0, 0, 0, 0.95); NS.CreateBorder(f, 0.5, 0.5, 0.5, 1)
    GR.FilterFrame = f
    
    local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOP", 0, -15); title:SetText("Create Custom Filter")
    f.Title = title
    
    local lblC = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); lblC:SetPoint("TOPLEFT", 20, -50); lblC:SetText("Class:")
    local btnC = CreateFrame("Button", nil, f); NS.CreateBg(btnC, 0.2, 0.2, 0.2, 1); btnC:SetSize(150, 24); btnC:SetPoint("LEFT", lblC, "RIGHT", 10, 0)
    local txtC = btnC:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); txtC:SetPoint("LEFT", 5, 0); txtC:SetText("Any")
    
    local lblR = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); lblR:SetPoint("TOPLEFT", 20, -85); lblR:SetText("Race:")
    local btnR = CreateFrame("Button", nil, f); NS.CreateBg(btnR, 0.2, 0.2, 0.2, 1); btnR:SetSize(150, 24); btnR:SetPoint("LEFT", lblR, "RIGHT", 10, 0)
    local txtR = btnR:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); txtR:SetPoint("LEFT", 5, 0); txtR:SetText("Any")
    
    local lblN = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); lblN:SetPoint("TOPLEFT", 20, -120); lblN:SetText("Name:")
    local editN = CreateFrame("EditBox", nil, f, "InputBoxTemplate"); editN:SetSize(160, 24); editN:SetPoint("LEFT", lblN, "RIGHT", 10, 0)
    editN:SetAutoFocus(false)
    editN:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    editN:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
    
    local bSave = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); bSave:SetSize(100, 24); bSave:SetPoint("BOTTOMLEFT", 20, 20); bSave:SetText("Save")
    NS.SkinButton(bSave) 

    local bCancel = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); bCancel:SetSize(100, 24); bCancel:SetPoint("BOTTOMRIGHT", -20, 20); bCancel:SetText("Cancel")
    NS.SkinButton(bCancel) 

    local listC = CreateFrame("Frame", nil, f); NS.CreateBg(listC, 0.1, 0.1, 0.1, 1); listC:SetSize(150, 300); listC:SetPoint("TOPLEFT", btnC, "BOTTOMLEFT", 0, -2); listC:SetFrameStrata("Tooltip"); listC:Hide()
    local listR = CreateFrame("Frame", nil, f); NS.CreateBg(listR, 0.1, 0.1, 0.1, 1); listR:SetSize(150, 300); listR:SetPoint("TOPLEFT", btnR, "BOTTOMLEFT", 0, -2); listR:SetFrameStrata("Tooltip"); listR:Hide()
    
    local function MkBtn(parent, text, func, y)
        local b = CreateFrame("Button", nil, parent); b:SetSize(140, 20); b:SetPoint("TOPLEFT", 5, y)
        local t = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t:SetPoint("LEFT"); t:SetText(text)
        b:SetScript("OnClick", function() func(text); parent:Hide() end)
    end
    
    MkBtn(listC, "Any", function(v) f.selClass=v; txtC:SetText(v) end, -5)
    for i, c in ipairs(NS.Constants.CLASS_LIST) do MkBtn(listC, c, function(v) f.selClass=v; txtC:SetText(v) end, -5 - (i*20)) end
    
    MkBtn(listR, "Any", function(v) f.selRace=v; txtR:SetText(v) end, -5)
    for i, r in ipairs(NS.Constants.RACES[UnitFactionGroup("player")] or {}) do MkBtn(listR, r, function(v) f.selRace=v; txtR:SetText(v) end, -5 - (i*20)) end
    
    btnC:SetScript("OnClick", function() listC:SetShown(not listC:IsShown()); listR:Hide() end)
    btnR:SetScript("OnClick", function() listR:SetShown(not listR:IsShown()); listC:Hide() end)
    bCancel:SetScript("OnClick", function() f:Hide() end)
    
    bSave:SetScript("OnClick", function()
        local newName = editN:GetText()
        if not newName or newName == "" then return end
        
        if f.mode == "EDIT" and f.origName and f.origName ~= newName then
            GR.db.customFilters[f.origName] = nil
        end
        
        GR.db.customFilters[newName] = { class = f.selClass, race = f.selRace, name = newName }
        GR:RefreshMainFilterList()
        
        if GR.filterType == "Custom" and GR.selectedFilterData and (GR.selectedFilterData.name == f.origName or GR.selectedFilterData.name == newName) then
             GR.selectedFilterData = GR.db.customFilters[newName]
             GR.dFiltTxt:SetText("Filter: " .. newName)
        end
        
        f:Hide()
        print("Filter Saved: " .. newName)
    end)
    
    f.OpenCreate = function()
        f.mode = "CREATE"; f.origName = nil
        f.Title:SetText("Create Custom Filter")
        f.selClass = "Any"; txtC:SetText("Any")
        f.selRace = "Any"; txtR:SetText("Any")
        editN:SetText("")
        f:Show()
    end
    
    f.OpenEdit = function(data)
        f.mode = "EDIT"; f.origName = data.name
        f.Title:SetText("Edit Filter: " .. data.name)
        f.selClass = data.class or "Any"; txtC:SetText(f.selClass)
        f.selRace = data.race or "Any"; txtR:SetText(f.selRace)
        editN:SetText(data.name)
        f:Show()
    end
end

function GR:RefreshMainFilterList()
    local list = self.dFiltList
    local kids = {list:GetChildren()}; for _, k in ipairs(kids) do k:Hide() end
    
    local y = -5
    local function Mk(text, func)
        local b = CreateFrame("Button", nil, list); b:SetSize(170, 20); b:SetPoint("TOPLEFT", 5, y)
        local t = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); t:SetPoint("LEFT"); t:SetText(text)
        b:SetScript("OnClick", func); y = y - 20
    end
    
    Mk("Default (All)", function()
        self.filterType = "Default"; self.dFiltTxt:SetText("Filter: Default (All)")
        self.selectedFilterData = nil; list:Hide()
    end)
    
    for name, data in pairs(self.db.customFilters) do
        Mk(name, function()
            self.filterType = "Custom"; self.dFiltTxt:SetText("Filter: " .. name)
            self.selectedFilterData = data; list:Hide()
        end)
    end
    list:SetHeight(math.abs(y) + 5)
end

function GR:UpdateRaiderIOStatus()
    if not self.ioStatus then return end

    local isOfficer = false
    if IsInGuild() then
        isOfficer = C_GuildInfo.CanEditOfficerNote()
    end

    if _G.RaiderIO and _G.RaiderIO.GetProfile then
        self.ioStatus:SetText("Raider.IO addon active. Data retrieved")
        self.ioStatus:SetTextColor(0, 1, 0)
    else
        if not isOfficer then
            local name = UnitName("player")
            self.ioStatus:SetText("Greetings " .. name)
            self.ioStatus:SetTextColor(1, 1, 1) 
        else
            self.ioStatus:SetText("Raider.IO missing or not loaded. Check addons load list.")
            self.ioStatus:SetTextColor(1, 0, 0) 
        end
    end
end

function GR:UpdateOfficerStatus()
    if not self.officerStatus then return end

    local hasAccess = false
    local hasGuild = IsInGuild()
    
    if hasGuild then
        local canEditNote = C_GuildInfo.CanEditOfficerNote()
        local canInvite = (CanGuildInvite and CanGuildInvite()) or false
        if canEditNote or canInvite then
            hasAccess = true
        end
    end

    if not hasGuild then
        self.officerStatus:SetText("No Guild detected.")
        self.officerStatus:SetTextColor(0.5, 0.5, 0.5) 
    elseif hasAccess then
        self.officerStatus:SetText("Recruiter mode Active ")
        self.officerStatus:SetTextColor(0, 1, 0) 
    else
        self.officerStatus:SetText("")
        self.officerStatus:SetTextColor(1, 0, 0) 
    end
    
    local restrictedTabs = {[1] = true, [2] = true, [3] = true}
    
    if self.Tabs then
        local currentY = -10
        for i, tab in ipairs(self.Tabs) do
            if restrictedTabs[i] then
                if hasAccess then
                    tab:Show()
                else
                    tab:Hide()
                end
            end
            if tab:IsShown() then
                tab:ClearAllPoints()
                tab:SetPoint("TOP", self.SideBar, "TOP", 0, currentY)
                currentY = currentY - 40
            end
        end
    end
    
    if not hasAccess and restrictedTabs[self.activeTab] then
        print("|cffff0000GRS: Access restricted. Switching to Guild Roster.|r")
        self:SwitchView(5)
    end
end