local addonName, NS = ...
local GR = _G["GRS_MainFrame"]

-- Import Constants & Helpers
local INSPECT_BG_PATH = GR.RR.CONST.INSPECT_BG_PATH
local ParseDate = GR.RR.ParseDate
local GetFullPlayerName = GR.RR.GetFullPlayerName
local GetCanonicalName = GR.RR.GetCanonicalName
local GetRegionName = GR.RR.GetRegionName
local GetFactionID = GR.RR.GetFactionID

-- Helpers from Shared
local CreateBg = NS.CreateBg
local SkinButton = NS.SkinButton

-- =========================================================================
-- B.1 FIX: OVERLAY BORDER METHOD
-- Replaces NS.CreateBorder locally to prevent background overlap
-- =========================================================================
local function CreateBorder(parent, r, g, b, a)
    if not parent then return end
    
    -- Check if a border ghost already exists to avoid duplication
    if parent.borderOverlay then 
        parent.borderOverlay:SetBackdropBorderColor(r, g, b, a)
        return parent.borderOverlay 
    end

    -- 1. Create the Ghost Frame (Overlay)
    local border = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    
    -- 2. Anchor Perfectly to the Parent
    border:SetAllPoints(parent)
    
    -- 3. The "Hollow" Definition (bgFile = nil is crucial)
    border:SetBackdrop({
        edgeFile = "Interface\\Buttons\\WHITE8x8", 
        edgeSize = 1, 
        bgFile = nil -- This ensures the center is transparent
    })
    
    -- 4. Apply Color
    border:SetBackdropBorderColor(r or 1, g or 1, b or 1, a or 1)
    
    -- Save reference
    parent.borderOverlay = border
    return border
end

-- Local Custom Helper for Scrollbars
local function CreateCustomScrollbar(parent, targetScrollFrame)
    local slider = CreateFrame("Slider", nil, parent)
    slider:SetWidth(12)
    slider:SetOrientation("VERTICAL")
    local track = slider:CreateTexture(nil, "BACKGROUND")
    track:SetAllPoints()
    track:SetColorTexture(0, 0, 0, 0.5)
    local thumb = slider:CreateTexture(nil, "ARTWORK")
    thumb:SetSize(12, 24)
    thumb:SetColorTexture(0.4, 0.4, 0.4, 1) 
    slider:SetThumbTexture(thumb)
    slider:SetScript("OnValueChanged", function(s, v)
        targetScrollFrame:SetVerticalScroll(v)
    end)
    return slider
end

function GR:CreateDialog(name, title, w, h)
    local f = _G[name]
    if f then f:Show(); f.title:SetText(title); return f end
    
    f = CreateFrame("Frame", name, UIParent, "BackdropTemplate")
    f:SetSize(w, h); f:SetPoint("CENTER")
    f:SetFrameStrata("FULLSCREEN_DIALOG") 
    f:SetToplevel(true)
    f:EnableMouse(true); f:SetMovable(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    
    CreateBg(f, 0, 0, 0, 0.95)
    CreateBorder(f, 0.5, 0.5, 0.5, 1)
    
    f.title = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
    f.title:SetPoint("TOP", 0, -12); f.title:SetText(title)
    
    local c = CreateFrame("Button", nil, f)
    c:SetSize(24, 24); c:SetPoint("TOPRIGHT", -5, -5)
    SkinButton(c)
    local cfs = c:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    cfs:SetPoint("CENTER"); cfs:SetText("X")
    c:SetFontString(cfs)
    c:SetScript("OnClick", function() f:Hide() end)
    
    return f
end

-- =========================================================================
-- LOG DIALOG
-- =========================================================================
function GR:RR_OpenLogDialog()
    local f = GR:CreateDialog("GRS_RR_LogFrame", "Roster Audit Log", 400, 500)
    f:Show()
    
    if not f.content then
        local sf = CreateFrame("ScrollFrame", "GRS_RR_LogScroll", f, "UIPanelScrollFrameTemplate")
        sf:SetPoint("TOPLEFT", 15, -50); sf:SetPoint("BOTTOMRIGHT", -30, 15)
        
        local content = CreateFrame("Frame", nil, sf)
        content:SetSize(355, 400)
        sf:SetScrollChild(content)
        f.scrollContent = content
        f.lines = {}
        
        f.UpdateLog = function()
            local h = GRS_RaidRosterDB.logs or {}
            local y = 0
            for _, line in ipairs(f.lines) do line:Hide() end
            
            for i, entry in ipairs(h) do
                local frame = f.lines[i]
                if not frame then
                    frame = CreateFrame("Frame", nil, f.scrollContent)
                    frame:SetSize(350, 45)
                    frame.ts = frame:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
                    frame.ts:SetPoint("TOPLEFT", 0, 0)
                    frame.txt = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                    frame.txt:SetPoint("TOPLEFT", 0, -12)
                    frame.txt:SetWidth(350); frame.txt:SetJustifyH("LEFT")
                    local div = frame:CreateTexture(nil, "ARTWORK")
                    div:SetColorTexture(0.2, 0.2, 0.2, 1); div:SetHeight(1)
                    div:SetPoint("BOTTOMLEFT"); div:SetPoint("BOTTOMRIGHT")
                    f.lines[i] = frame
                end
                frame:SetPoint("TOPLEFT", 0, y)
                frame:Show()
                local dateStr = date("%H:%M:%S", entry.ts)
                local displayAuthor = strsplit("-", Ambiguate(entry.author or "Unknown", "none"))
                frame.ts:SetText(dateStr .. " - " .. displayAuthor)
                frame.txt:SetText(entry.desc)
                y = y - 48
            end
            f.scrollContent:SetHeight(math.abs(y))
        end
    end
    
    f.UpdateLog()
end

-- =========================================================================
-- DIALOGS (CREATE / EDIT / DELETE)
-- =========================================================================

function GR:RR_OpenEventDialog(mode, existingData)
    if C_GuildInfo and C_GuildInfo.GuildRoster then C_GuildInfo.GuildRoster() end

    local f = GR:CreateDialog("GRS_RR_EventDlg", mode == "EDIT" and "Edit Event" or "Create Event", 500, 450)
    f:Show()
    
    if not f.inputs then
        f.inputs = {}
        local xL, y = 20, -40
        
        local function MkInp(lbl, w, def, parent, pAnchor, px, py)
            local l = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            if parent then l:SetPoint("TOPLEFT", parent, pAnchor, px, py) 
            else l:SetPoint("TOPLEFT", xL, y) end
            l:SetText(lbl)
            
            local eb = CreateFrame("EditBox", nil, f, "InputBoxTemplate"); eb:SetSize(w, 20); eb:SetPoint("LEFT", l, "RIGHT", 10, 0); eb:SetAutoFocus(false)
            if def then eb:SetText(def) end
            return eb
        end
        
        f.inputs.name = MkInp("Name:", 150); y = y - 40
        f.inputs.date = MkInp("Date:", 80); y = y - 40
        f.inputs.startT = MkInp("Start:", 50)
        f.inputs.endT = MkInp("End:", 50, nil, f.inputs.startT, "TOPRIGHT", 20, 0)
        
        local dl = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); dl:SetPoint("TOPLEFT", 20, y-40); dl:SetText("Desc:")
        f.inputs.def = CreateFrame("EditBox", nil, f, "InputBoxTemplate"); f.inputs.def:SetSize(200, 20); f.inputs.def:SetPoint("LEFT", dl, "RIGHT", 10, 0); f.inputs.def:SetAutoFocus(false)
        
        local xR, yR = 260, -40
        local function MkDrop(lbl, offY)
            local l = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); l:SetPoint("TOPLEFT", xR, yR + (offY or 0)); l:SetText(lbl)
            local d = CreateFrame("Frame", nil, f, "UIDropDownMenuTemplate"); d:SetPoint("TOPLEFT", xR-15, yR + (offY or 0) - 20)
            UIDropDownMenu_SetWidth(d, 120)
            return d
        end
        
        f.inputs.typeDrop = MkDrop("Event Type:")
        f.inputs.rankDrop = MkDrop("Min Rank:", -60)
        
        local limLbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); limLbl:SetPoint("TOPLEFT", xR, -160); limLbl:SetText("Limit:")
        f.inputs.limit = CreateFrame("EditBox", nil, f, "InputBoxTemplate"); f.inputs.limit:SetSize(40, 20); f.inputs.limit:SetPoint("LEFT", limLbl, "RIGHT", 10, 0); f.inputs.limit:SetAutoFocus(false)
        
        f.inputs.repeatChk = CreateFrame("CheckButton", nil, f, "UICheckButtonTemplate")
        f.inputs.repeatChk:SetPoint("TOPLEFT", xR, -200)
        f.inputs.repeatChk.text:SetText("Weekly Repeat")
        
        f.saveBtn = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); f.saveBtn:SetSize(100,25); f.saveBtn:SetPoint("BOTTOMLEFT", 20, 20); f.saveBtn:SetText("Save")
        f.cancelBtn = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); f.cancelBtn:SetSize(100,25); f.cancelBtn:SetPoint("BOTTOMRIGHT", -20, 20); f.cancelBtn:SetText("Cancel")
        f.cancelBtn:SetScript("OnClick", function() f:Hide() end)
    end
    
    local raidList = {"The Voidspire", "The Dreamrift", "March on Quel'Danas", "Mythic+"}
        
    UIDropDownMenu_Initialize(f.inputs.typeDrop, function(self, level)
        for _, r in ipairs(raidList) do
            local info = UIDropDownMenu_CreateInfo()
            info.text = r
            info.func = function(self) 
                UIDropDownMenu_SetSelectedID(f.inputs.typeDrop, self:GetID()) 
                f.selectedType = r
                UIDropDownMenu_SetText(f.inputs.typeDrop, r)
            end
            UIDropDownMenu_AddButton(info, level)
        end
    end)
    
    UIDropDownMenu_Initialize(f.inputs.rankDrop, function(self, level)
        local numRanks = GuildControlGetNumRanks()
        for i=1, numRanks do
            local rName = GuildControlGetRankName(i)
            local info = UIDropDownMenu_CreateInfo()
            info.text = rName
            info.func = function(self)
                UIDropDownMenu_SetSelectedID(f.inputs.rankDrop, i)
                f.selectedRank = i-1
                UIDropDownMenu_SetText(f.inputs.rankDrop, rName)
            end
            UIDropDownMenu_AddButton(info, level)
        end
    end)
    
    if mode == "EDIT" and existingData then
        f.inputs.name:SetText(existingData.name)
        f.inputs.date:SetText(existingData.date)
        f.inputs.startT:SetText(existingData.startTime or "")
        f.inputs.endT:SetText(existingData.endTime or "")
        f.inputs.def:SetText(existingData.def or "")
        f.inputs.limit:SetText(existingData.limit or 99)
        f.inputs.repeatChk:SetChecked(false); f.inputs.repeatChk:Disable()
        f.selectedType = existingData.type; UIDropDownMenu_SetText(f.inputs.typeDrop, existingData.type)
        f.selectedRank = existingData.rankLimit; 
        local storedRankName = GuildControlGetRankName((existingData.rankLimit or 0) + 1)
        UIDropDownMenu_SetText(f.inputs.rankDrop, storedRankName)
    else
        f.inputs.name:SetText(""); f.inputs.date:SetText(date("%d-%m-%Y")); f.inputs.limit:SetText("20")
        f.inputs.repeatChk:Enable(); f.inputs.repeatChk:SetChecked(false)
        f.selectedType = raidList[1]; UIDropDownMenu_SetText(f.inputs.typeDrop, raidList[1])
        f.selectedRank = 99; UIDropDownMenu_SetText(f.inputs.rankDrop, "Any")
    end
    
    f.saveBtn:SetScript("OnClick", function()
        local name = f.inputs.name:GetText()
        local dateStr = f.inputs.date:GetText()
        if name == "" or not ParseDate(dateStr) then return end
        
        local sTime, eTime = f.inputs.startT:GetText(), f.inputs.endT:GetText()
        local defTxt, typeVal = f.inputs.def:GetText(), f.selectedType
        local limVal, rankVal = tonumber(f.inputs.limit:GetText()), f.selectedRank
        local doRepeat = (mode == "CREATE" and f.inputs.repeatChk:GetChecked())
        
        local function SaveOne(dStr)
            local ts = ParseDate(dStr) or time()
            local id = (mode == "EDIT" and existingData.id) or ("EVT-" .. ts .. "-" .. math.random(100, 999))
            local rosterStruct = {TANK={},HEALER={},DPS={},BENCH={},TENTATIVE={},ABSENT={}}
            if existingData and existingData.roster then
                 rosterStruct = existingData.roster
                 if not rosterStruct.TENTATIVE then rosterStruct.TENTATIVE = {} end
                 if not rosterStruct.BENCH then rosterStruct.BENCH = {} end
                 if not rosterStruct.ABSENT then rosterStruct.ABSENT = {} end
            end

            local evt = {
                id = id, name = name, date = dStr,
                startTime = sTime, endTime = eTime,
                def = defTxt, type = typeVal,
                limit = limVal, rankLimit = rankVal,
                roster = rosterStruct,
                counts = { TANK=0, HEALER=0, DPS=0 }
            }
            GRS_RaidRosterDB.events[id] = evt
            GR:RR_BroadcastEvent(evt)
        end
        
        SaveOne(dateStr)
        if doRepeat then
            local t = ParseDate(dateStr)
            local repeatCount = 0
            C_Timer.NewTicker(0.5, function()
                repeatCount = repeatCount + 1
                t = t + (7 * 86400)
                SaveOne(date("%d-%m-%Y", t))
                GR:RR_ScanDB()
            end, 4)
        end
        GR:RR_ScanDB(); f:Hide()
    end)
end

function GR:RR_SetupDeleteButton(btn)
    btn:SetScript("OnClick", function()
        local count = 0
        local ids = {}
        for id, _ in pairs(GR.RR.multiSelect) do count = count + 1; table.insert(ids, id) end
        if GR.RR.selectedEvent and count == 0 then table.insert(ids, GR.RR.selectedEvent.id); count = 1 end
        
        if count > 0 then
            local dlg = GR:CreateDialog("GRS_RR_DelDlg", "Delete Events", 300, 150)
            if not dlg.lbl then dlg.lbl = dlg:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); dlg.lbl:SetPoint("CENTER", 0, 20) end
            dlg.lbl:SetText("Delete " .. count .. " events?")
            
            if not dlg.ok then 
                dlg.ok = CreateFrame("Button", nil, dlg, "UIPanelButtonTemplate"); dlg.ok:SetSize(80, 25); dlg.ok:SetPoint("BOTTOMLEFT", 60, 20)
                dlg.cancel = CreateFrame("Button", nil, dlg, "UIPanelButtonTemplate"); dlg.cancel:SetSize(80, 25); dlg.cancel:SetPoint("BOTTOMRIGHT", -60, 20); dlg.cancel:SetText("Cancel")
                dlg.cancel:SetScript("OnClick", function() dlg:Hide() end)
            end
            
            dlg:Show(); dlg.ok:Disable(); dlg.timeLeft = 5
            dlg:SetScript("OnUpdate", function(s, elapsed)
                s.timeLeft = s.timeLeft - elapsed
                if s.timeLeft <= 0 then 
                    s.ok:Enable(); s.ok:SetText("Delete")
                    s:SetScript("OnUpdate", nil)
                else 
                    s.ok:SetText(math.ceil(s.timeLeft)) 
                end
            end)
            
            dlg.ok:SetScript("OnClick", function()
                for _, id in ipairs(ids) do GRS_RaidRosterDB.events[id] = nil end
                GR:RR_BroadcastDelete(ids)
                GR.RR.multiSelect = {}
                GR:RR_ScanDB(); GR:RR_SelectEvent(nil)
                dlg:Hide()
            end)
        end
    end)
end

-- =========================================================================
-- INSPECT DIALOG
-- =========================================================================

function GR:RR_OpenInspectDialog()
    local evt = GR.RR.selectedEvent
    if not evt then return end
    
    GR.RR.inspectSelection = nil
    GR.RR.inviteSelection = {}
    local f = GR:CreateDialog("GRS_RR_InspectDlg", "Inspect: " .. evt.name, 900, 450)
    
    local bg = f:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints(); bg:SetTexture(INSPECT_BG_PATH); bg:SetVertexColor(1, 1, 1, 1)
    bg:SetDrawLayer("BACKGROUND", 1)

    local function GetTargets()
        local t = {}
        local hasChecks = false
        for name, data in pairs(GR.RR.inviteSelection) do
            if data then 
                table.insert(t, data)
                hasChecks = true 
            end
        end
        if not hasChecks and GR.RR.inspectSelection then
            table.insert(t, GR.RR.inspectSelection)
        end
        return t
    end

    if not f.content then
        f.scroll = CreateFrame("ScrollFrame", "GRS_RR_InspectScroll", f)
        f.scroll:SetPoint("TOPLEFT", 10, -30); f.scroll:SetPoint("BOTTOMRIGHT", -30, 60)
        
        local slider = CreateCustomScrollbar(f, f.scroll)
        slider:SetPoint("TOPRIGHT", f, "TOPRIGHT", -10, -30)
        slider:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -10, 60)
        f.scroll.slider = slider
        f.scroll:SetScript("OnMouseWheel", function(s, d) 
            local cur = slider:GetValue()
            slider:SetValue(cur - (d * 20)) 
        end)
        
        f.content = CreateFrame("Frame", nil, f.scroll); f.content:SetSize(860, 360)
        f.scroll:SetScrollChild(f.content)
        f.cols = {}
        
        f.actionBar = CreateFrame("Frame", nil, f)
        f.actionBar:SetPoint("BOTTOMLEFT", 10, 10)
        f.actionBar:SetPoint("BOTTOMRIGHT", -10, 10)
        f.actionBar:SetHeight(40)
        
        local function MkActBtn(txt, w, x, func)
            local b = CreateFrame("Button", nil, f.actionBar, "UIPanelButtonTemplate")
            b:SetSize(w, 25); b:SetPoint("LEFT", x, 0); b:SetText(txt)
            b:SetScript("OnClick", func); b:Disable() 
            return b
        end
        
        f.btnPresent = MkActBtn("Set Present", 90, 0, function()
            local targets = GetTargets()
            for _, s in ipairs(targets) do
                GR:RR_BroadcastModStatus(evt.id, s.name, s.role or "DPS", "Present")
                GR:RR_UpdateMemberStatus(evt.id, s.name, s.role or "DPS", "Present", GetServerTime())
            end
        end)
        
        f.btnBench = MkActBtn("Set Bench", 90, 95, function()
            local targets = GetTargets()
            for _, s in ipairs(targets) do
                GR:RR_BroadcastModStatus(evt.id, s.name, s.role or "DPS", "BENCH")
                GR:RR_UpdateMemberStatus(evt.id, s.name, s.role or "DPS", "BENCH", GetServerTime())
            end
        end)
        
        f.btnTentative = MkActBtn("Set Tentative", 90, 190, function()
            local targets = GetTargets()
            for _, s in ipairs(targets) do
                GR:RR_BroadcastModStatus(evt.id, s.name, s.role or "DPS", "Tentative")
                GR:RR_UpdateMemberStatus(evt.id, s.name, s.role or "DPS", "Tentative", GetServerTime())
            end
        end)
        
        f.btnAbsent = MkActBtn("Set Absent", 90, 285, function()
            local targets = GetTargets()
            for _, s in ipairs(targets) do
                GR:RR_BroadcastModStatus(evt.id, s.name, s.role or "DPS", "ABSENT")
                GR:RR_UpdateMemberStatus(evt.id, s.name, s.role or "DPS", "ABSENT", GetServerTime())
            end
        end)
        
        f.btnRemove = MkActBtn("Remove", 90, 380, function()
            local targets = GetTargets()
            for _, s in ipairs(targets) do
                GR:RR_BroadcastRemove(evt.id, s.name)
                GR:RR_RemoveMember(evt.id, s.name)
            end
            GR.RR.inspectSelection = nil
            GR.RR.inviteSelection = {}
            f.UpdateListDisplay()
        end)

        f.btnInvite = MkActBtn("Mass Invite", 120, 500, function()
             local count = 0
             local targets = GetTargets()
             for _, s in ipairs(targets) do
                 C_PartyInfo.InviteUnit(s.name)
                 count = count + 1
             end
             print("Inviting " .. count .. " players...")
        end)
        f.btnInvite:Enable() 
        if GR.RR.viewMode ~= "OFFICER" then f.actionBar:Hide() end
    end
    f:Show()
    
    f.UpdateListDisplay = function()
        local colCounter = 1
        local x = 0
        
        if f.btnPresent then
            local targets = GetTargets()
            if #targets > 0 then
                f.btnPresent:Enable(); f.btnBench:Enable(); f.btnTentative:Enable()
                f.btnAbsent:Enable(); f.btnRemove:Enable()
            else
                f.btnPresent:Disable(); f.btnBench:Disable(); f.btnTentative:Disable()
                f.btnAbsent:Disable(); f.btnRemove:Disable()
            end
        end

        local function AddCol(header, list, color)
            local count = list and #list or 0
            local numCols = math.ceil(count / 10)
            if numCols == 0 then numCols = 1 end
            
            for c=1, numCols do
                local frame = f.cols[colCounter]
                if not frame then
                    frame = CreateFrame("Frame", nil, f.content, "BackdropTemplate")
                    frame:SetBackdrop({bgFile="Interface\\DialogFrame\\UI-DialogBox-Background", tile=true, tileSize=16})
                    frame:SetBackdropColor(0,0,0,0.5)
                    frame:SetSize(135, 250)
                    frame.head = frame:CreateFontString(nil,"OVERLAY","GameFontNormalSmall")
                    frame.head:SetPoint("TOP",0,-5); frame.rows = {}
                    f.cols[colCounter] = frame 
                end
                
                frame:ClearAllPoints(); frame:SetPoint("TOPLEFT", x, 0); frame:Show()
                local hTxt = header
                if c > 1 then hTxt = hTxt .. " (Ext" .. (c-1) .. ")" end
                frame.head:SetText(hTxt); frame.head:SetTextColor(unpack(color))
                for _, btn in pairs(frame.rows) do btn:Hide() end
                
                for r=1, 10 do
                    local btn = frame.rows[r]
                    if not btn then
                        btn = CreateFrame("Button", nil, frame)
                        btn:SetSize(130, 18)
                        btn:SetPoint("TOPLEFT", 2, -25-((r-1)*20))
                        btn.hl = btn:CreateTexture(nil, "BACKGROUND")
                        btn.hl:SetAllPoints(); btn.hl:SetColorTexture(1, 1, 0, 0.3); btn.hl:Hide()
                        local cb = CreateFrame("CheckButton", nil, btn, "UICheckButtonTemplate")
                        cb:SetSize(16, 16); cb:SetPoint("LEFT", 2, 0); btn.cb = cb
                        local fs = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
                        fs:SetPoint("LEFT", cb, "RIGHT", 2, 0); fs:SetWidth(105); fs:SetJustifyH("LEFT")
                        btn:SetFontString(fs)
                        btn:SetScript("OnEnter", function(s) s.fs:SetTextColor(1, 1, 1) end)
                        btn:SetScript("OnLeave", function(s) s.fs:SetTextColor(0.8, 0.8, 0.8) end)
                        btn.fs = fs; frame.rows[r] = btn
                    end
                    
                    local idx = (c-1)*10 + r
                    if list and list[idx] then 
                        local d = list[idx]
                        local displayName = strsplit("-", Ambiguate(d.name, "none"))
                        btn:Show()
                        btn:SetText(displayName)
                        btn.cb:SetChecked(GR.RR.inviteSelection[d.name] ~= nil)
                        btn.cb:SetScript("OnClick", function(s)
                            if s:GetChecked() then GR.RR.inviteSelection[d.name] = d 
                            else GR.RR.inviteSelection[d.name] = nil end
                            f.UpdateListDisplay()
                        end)
                        if GR.RR.inspectSelection and GR.RR.inspectSelection.name == d.name then
                            btn.hl:Show()
                        else btn.hl:Hide() end
                        if GR.RR.viewMode == "OFFICER" then
                            btn:SetScript("OnClick", function() GR.RR.inspectSelection = d; f.UpdateListDisplay() end)
                        else btn:SetScript("OnClick", nil); btn.cb:Hide() end
                    else btn:Hide() end
                end
                x = x + 140; colCounter = colCounter + 1
            end
        end
        AddCol("Tank", evt.roster["TANK"], {0.2,0.6,1})
        AddCol("Healer", evt.roster["HEALER"], {0.2,1,0.4})
        AddCol("DPS", evt.roster["DPS"], {1,0.3,0.3})
        AddCol("Bench", evt.roster["BENCH"], {0.4, 0.8, 1})
        AddCol("Tentative", evt.roster["TENTATIVE"] or {}, {1, 0.8, 0})
        AddCol("Absent", evt.roster["ABSENT"] or {}, {0.6, 0.6, 0.6})
        for i = colCounter, #f.cols do f.cols[i]:Hide() end
        f.content:SetWidth(x + 20)
        local maxScroll = math.max(0, f.content:GetHeight() - f.scroll:GetHeight())
        f.scroll.slider:SetMinMaxValues(0, maxScroll)
    end
    f.UpdateListDisplay()
end

-- =========================================================================
-- SIGN-UP DIALOG
-- =========================================================================
function GR:RR_OpenSignUpDialog()
    local evt = GR.RR.selectedEvent
    if not evt then return end
    if not evt.counts then evt.counts = { TANK=0, HEALER=0, DPS=0 } end

    local playerGUID = UnitGUID("player")
    local myRankIdx = 99
    if playerGUID then myRankIdx = C_GuildInfo.GetGuildRankOrder(playerGUID) end
    if evt.rankLimit and myRankIdx > evt.rankLimit then
        print("You do not meet the rank requirements for this event.")
        return
    end
    
    local f = GR:CreateDialog("GRS_RR_SignUpDlg", "Sign Up", 300, 350)
    f:Show()
    
    if not f.inited then
        f.inited = true
        f.info = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); f.info:SetPoint("TOP", 0, -30)
        f.slots = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); f.slots:SetPoint("TOP", 0, -50); f.slots:SetTextColor(1, 0.82, 0)
        f.roles = {}
        local rx = 40
        for _, r in ipairs({"TANK", "HEALER", "DPS"}) do
            local cb = CreateFrame("CheckButton", nil, f, "UICheckButtonTemplate")
            cb:SetPoint("TOPLEFT", rx, -90); cb.text:SetText(r)
            cb:SetScript("OnClick", function(s) 
                for _, o in pairs(f.roles) do o:SetChecked(false) end
                s:SetChecked(true); f.selRole = r; f:CheckSave()
            end)
            f.roles[r] = cb; rx = rx + 80
        end
        f.att = {}
        local y = -140
        for _, a in ipairs({"Present", "Tentative", "BENCH", "ABSENT"}) do
            local cb = CreateFrame("CheckButton", nil, f, "UICheckButtonTemplate")
            cb:SetPoint("TOPLEFT", 80, y); cb.text:SetText(a)
            cb:SetScript("OnClick", function(s) 
                for _, o in pairs(f.att) do o:SetChecked(false) end
                s:SetChecked(true); f.selStatus = a; f:CheckSave()
            end)
            f.att[a] = cb; y = y - 30
        end
        f.save = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); f.save:SetSize(100,25); f.save:SetPoint("BOTTOMLEFT", 30, 20); f.save:SetText("Save")
        f.cancel = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); f.cancel:SetSize(100,25); f.cancel:SetPoint("BOTTOMRIGHT", -30, 20); f.cancel:SetText("Cancel")
        f.cancel:SetScript("OnClick", function() f:Hide() end)
        f.CheckSave = function(s) if s.selRole and s.selStatus then s.save:Enable() else s.save:Disable() end end
    end
    
    f.info:SetText(evt.name .. " - " .. (evt.startTime or "") .. " to " .. (evt.endTime or ""))
    local total = (evt.counts.TANK + evt.counts.HEALER + evt.counts.DPS)
    local remain = (evt.limit or 99) - total
    f.slots:SetText("Slots: " .. math.max(0, remain))
    local isFull = remain <= 0
    if isFull then
        f.att["Present"]:Disable(); f.att["Present"]:SetChecked(false)
        f.att["Tentative"]:Disable(); f.att["Tentative"]:SetChecked(false)
        f.att["BENCH"]:SetChecked(true); f.selStatus = "BENCH"
    else f.att["Present"]:Enable(); f.att["Tentative"]:Enable() end
    
    f.selRole = nil; f.selStatus = nil; f.save:Disable()
    for _, c in pairs(f.roles) do c:SetChecked(false) end
    for _, c in pairs(f.att) do c:SetChecked(false) end
    if isFull then f.att["BENCH"]:SetChecked(true); f.selStatus = "BENCH" end
    
    f.save:SetScript("OnClick", function()
        local fullName = GetFullPlayerName()
        GR:RR_BroadcastSignUp(evt.id, f.selRole, f.selStatus)
        GR:RR_UpdateMemberStatus(evt.id, fullName, f.selRole, f.selStatus, GetServerTime())
        f:Hide()
    end)
end

-- =========================================================================
-- SETTINGS POPUP (COGWHEEL)
-- =========================================================================

function GR:RR_OpenPanelSettings(panelKey)
    local f = GR:CreateDialog("GRS_RR_SettingsDlg", "Configure " .. panelKey, 300, 480)
    f:Show()
    local settings = GRS_RaidRosterDB.settings[panelKey]
    
    if not f.content then
        f.content = CreateFrame("Frame", nil, f); f.content:SetPoint("TOPLEFT", 10, -30); f.content:SetPoint("BOTTOMRIGHT", -10, 40)
        local l1 = f.content:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); l1:SetPoint("TOPLEFT", 10, -10); l1:SetText("Panel Title:")
        f.titleEb = CreateFrame("EditBox", nil, f.content, "InputBoxTemplate")
        f.titleEb:SetSize(180, 20); f.titleEb:SetPoint("TOPLEFT", 10, -25); f.titleEb:SetAutoFocus(false)
        local l2 = f.content:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); l2:SetPoint("TOPLEFT", 10, -60); l2:SetText("History Length:")
        f.histSl = CreateFrame("Slider", nil, f.content, "OptionsSliderTemplate")
        f.histSl:SetPoint("TOPLEFT", 10, -80); f.histSl:SetWidth(180)
        f.histSl:SetMinMaxValues(1, 20); f.histSl:SetValueStep(1); f.histSl:SetObeyStepOnDrag(true)
        f.histSl.valText = f.histSl:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); f.histSl.valText:SetPoint("RIGHT", f.histSl, "LEFT", -5, 0)
        f.histSl:SetScript("OnValueChanged", function(s, v) s.valText:SetText(math.floor(v)) end)
        local l3 = f.content:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); l3:SetPoint("TOPLEFT", 10, -110); l3:SetText("Include Ranks:")
        f.rankScroll = CreateFrame("ScrollFrame", nil, f.content, "UIPanelScrollFrameTemplate")
        f.rankScroll:SetPoint("TOPLEFT", 10, -130); f.rankScroll:SetPoint("BOTTOMRIGHT", -25, 140); CreateBg(f.rankScroll, 0, 0, 0, 0.3)
        f.rankContent = CreateFrame("Frame", nil, f.rankScroll); f.rankContent:SetSize(240, 300); f.rankScroll:SetScrollChild(f.rankContent)
        f.rankChecks = {}
        
        local function CreateCustomDropdown(label, parent, relPoint, x, y, saveKey)
            local lbl = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); lbl:SetPoint("TOPLEFT", relPoint, "BOTTOMLEFT", x, y); lbl:SetText(label)
            local btn = CreateFrame("Button", nil, parent); CreateBg(btn, 0.2, 0.2, 0.2, 1); CreateBorder(btn, 0.5, 0.5, 0.5, 1); btn:SetSize(200, 24); btn:SetPoint("TOPLEFT", lbl, "BOTTOMLEFT", 0, -5)
            local txt = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); txt:SetPoint("LEFT", 5, 0); txt:SetText("Select..."); btn.txt = txt
            local arrow = btn:CreateTexture(nil, "ARTWORK"); arrow:SetTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Up"); arrow:SetSize(20, 20); arrow:SetPoint("RIGHT", -2, 0)
            local list = CreateFrame("Frame", nil, btn); CreateBg(list, 0.1, 0.1, 0.1, 1); CreateBorder(list, 0.5, 0.5, 0.5, 1); list:SetSize(200, 150); list:SetPoint("TOPLEFT", btn, "BOTTOMLEFT", 0, -2); list:SetFrameStrata("FULLSCREEN_DIALOG"); list:Hide()
            local scroll = CreateFrame("ScrollFrame", nil, list, "UIPanelScrollFrameTemplate"); scroll:SetPoint("TOPLEFT", 5, -5); scroll:SetPoint("BOTTOMRIGHT", -25, 5)
            local content = CreateFrame("Frame", nil, scroll); content:SetSize(170, 200); scroll:SetScrollChild(content)
            btn:SetScript("OnClick", function()
                if list:IsShown() then list:Hide(); return end
                local candidates = {}; local numGuild = GetNumGuildMembers(); C_GuildInfo.GuildRoster()
                for i=1, numGuild do
                    local name, _, rankIndex = GetGuildRosterInfo(i)
                    if name and rankIndex then
                        local cb = f.rankChecks[rankIndex + 1]
                        if cb and cb:GetChecked() then table.insert(candidates, Ambiguate(name, "none")) end
                    end
                end
                table.sort(candidates); table.insert(candidates, 1, "- None -")
                local kids = {content:GetChildren()}; for _, k in ipairs(kids) do k:Hide() end
                local dy = 0
                for _, fullName in ipairs(candidates) do
                     local displayName = strsplit("-", fullName)
                     local b = CreateFrame("Button", nil, content); b:SetSize(165, 20); b:SetPoint("TOPLEFT", 2, dy)
                     local t = b:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall"); t:SetPoint("LEFT", 5, 0); t:SetText(displayName); b:SetFontString(t)
                     b:SetScript("OnEnter", function(s) s:GetFontString():SetTextColor(1,1,1) end)
                     b:SetScript("OnLeave", function(s) s:GetFontString():SetTextColor(0.8,0.8,0.8) end)
                     b:SetScript("OnClick", function()
                         if fullName == "- None -" then f[saveKey] = nil; txt:SetText("Select...")
                         else f[saveKey] = fullName; txt:SetText(displayName) end; list:Hide()
                     end)
                     b:Show(); dy = dy - 20
                end
                content:SetHeight(math.abs(dy)+10); list:Show()
            end)
            return btn, lbl
        end
        f.mainLeaderBtn, f.mainLeaderLbl = CreateCustomDropdown("Main Raid Leader:", f.content, f.rankScroll, 0, -10, "selMain")
        f.backupLeaderBtn, f.backupLeaderLbl = CreateCustomDropdown("Backup Raid Leader:", f.content, f.mainLeaderBtn, 0, -15, "selBackup")
        f.save = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); f.save:SetSize(80, 22); f.save:SetPoint("BOTTOMLEFT", 20, 10); f.save:SetText("Save")
        f.cancel = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); f.cancel:SetSize(80, 22); f.cancel:SetPoint("BOTTOMRIGHT", -20, 10); f.cancel:SetText("Cancel")
        f.cancel:SetScript("OnClick", function() f:Hide() end)
    end
    
    f.titleEb:SetText(settings.title or ""); local curLimit = settings.limit or 10
    if curLimit < 1 then curLimit = 1 end; f.histSl:SetValue(curLimit)
    if panelKey == "panelB" then f.mainLeaderBtn:Hide(); f.mainLeaderLbl:Hide(); f.backupLeaderBtn:Hide(); f.backupLeaderLbl:Hide()
    else
        f.mainLeaderBtn:Show(); f.mainLeaderLbl:Show(); f.backupLeaderBtn:Show(); f.backupLeaderLbl:Show()
        f.selMain = settings.mainLeader; f.selBackup = settings.backupLeader
        f.mainLeaderBtn.txt:SetText(strsplit("-", settings.mainLeader or "Select...")); f.backupLeaderBtn.txt:SetText(strsplit("-", settings.backupLeader or "Select..."))
    end
    
    local numRanks = GuildControlGetNumRanks(); local y = 0
    for i=1, numRanks do
        local rIndex = i-1; local rName = GuildControlGetRankName(i); local cb = f.rankChecks[i]
        if not cb then
            cb = CreateFrame("CheckButton", nil, f.rankContent, "UICheckButtonTemplate"); cb:SetPoint("TOPLEFT", 5, -y); cb.text:SetText(rName); f.rankChecks[i] = cb
        else cb.text:SetText(rName); cb:Show() end
        cb:SetChecked(settings.ranks[rIndex] == true); cb.rIndex = rIndex; y = y + 25
    end
    for i=numRanks+1, #f.rankChecks do f.rankChecks[i]:Hide() end
    f.rankContent:SetHeight(y)
    
    f.save:SetScript("OnClick", function()
        settings.title = f.titleEb:GetText(); settings.limit = math.floor(f.histSl:GetValue()); settings.ranks = {}
        for i=1, numRanks do if f.rankChecks[i]:GetChecked() then settings.ranks[f.rankChecks[i].rIndex] = true end end
        if panelKey ~= "panelB" then settings.mainLeader = f.selMain; settings.backupLeader = f.selBackup end
        local panel = GR.RR.trackingPanels[panelKey]; if panel then panel.title:SetText(settings.title); panel.Update() end; f:Hide()
    end)
end

-- =========================================================================
-- LITE ROSTER MODULE
-- =========================================================================

GR.RR.Lite = {}; local Lite = GR.RR.Lite
Lite.RosterData = {}; Lite.Rows = {}; Lite.ViewList = {}; Lite.SortKey = "rankIndex"; Lite.SortAsc = true; Lite.CurrentFilter = nil; Lite.ShowOffline = false 

local LITE_ROW_HEIGHT = 24
local LITE_COL_DEF = {
    { name = "Name", width = 130, key = "name", sort = "name" },
    { name = "Rank", width = 80, key = "rank", sort = "rankIndex" }, 
    { name = "Lvl", width = 35, key = "level", sort = "level" },
    { name = "ilvl", width = 40, key = "ilvl", sort = "ilvl" },
    { name = "Key", width = 50, key = "maxKey", sort = "maxKey" },
    { name = "Score", width = 60, key = "rio", sort = "rio" },
    { name = "N", width = 55, key = "raidN", sort = "raidN" },
    { name = "H", width = 55, key = "raidH", sort = "raidH" },
    { name = "M", width = 55, key = "raidM", sort = "raidM" },
}

function Lite:Init()
    if not self.Frame then
        local f = CreateFrame("Frame", "GRS_RR_PopupFrame", UIParent); f:SetSize(650, 500); f:SetPoint("CENTER"); f:Hide()
        f:SetFrameStrata("FULLSCREEN_DIALOG"); f:EnableMouse(true); f:SetMovable(true); f:RegisterForDrag("LeftButton")
        f:SetScript("OnDragStart", f.StartMoving); f:SetScript("OnDragStop", f.StopMovingOrSizing)
        CreateBg(f, 0, 0, 0, 0.95); CreateBorder(f, 0.5, 0.5, 0.5, 1)
        local t = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); t:SetPoint("TOP", 0, -15); t:SetText("Available Members"); f.title = t
        local c = CreateFrame("Button", nil, f); c:SetSize(24, 24); c:SetPoint("TOPRIGHT", -5, -5); SkinButton(c)
        local cfs = c:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); cfs:SetPoint("CENTER"); cfs:SetText("X"); c:SetFontString(cfs); c:SetScript("OnClick", function() f:Hide() end)
        local sf = CreateFrame("ScrollFrame", "GRS_RR_LiteScroll", f); sf:SetPoint("TOPLEFT", 10, -50); sf:SetPoint("BOTTOMRIGHT", -30, 40)
        local slider = CreateCustomScrollbar(sf, sf); slider:SetPoint("TOPRIGHT", f, "TOPRIGHT", -10, -50); slider:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -10, 40); sf.slider = slider
        local content = CreateFrame("Frame", nil, sf); content:SetSize(600, 500); sf:SetScrollChild(content)
        sf:EnableMouseWheel(true); sf:SetScript("OnMouseWheel", function(s, d) local cur = slider:GetValue(); slider:SetValue(cur - (d * 20)) end)
        self.Frame = f; self.Content = content
        local h = CreateFrame("Frame", nil, f); h:SetHeight(20); h:SetPoint("TOPLEFT", 10, -30); h:SetPoint("RIGHT", -10, 0)
        local x = 0
        for _, col in ipairs(LITE_COL_DEF) do
            local b = CreateFrame("Button", nil, h); b:SetSize(col.width, 20); b:SetPoint("LEFT", x, 0)
            local txt = b:CreateFontString(nil,"OVERLAY","GameFontNormalSmall"); txt:SetPoint("LEFT",5,0); txt:SetText(col.name)
            if col.sort then b:SetScript("OnClick", function() Lite:Sort(col.sort) end) end; x = x + col.width
        end
        local chk = CreateFrame("CheckButton", nil, f, "UICheckButtonTemplate"); chk:SetPoint("BOTTOMLEFT", 10, 10); chk.text:SetText("Show Offline Members")
        chk:SetScript("OnClick", function(s) Lite.ShowOffline = s:GetChecked(); Lite:FetchData() end); chk:SetChecked(Lite.ShowOffline); self.OfflineChk = chk
    end
end

function Lite:ShowPopup(classFilter) if not self.Frame then self:Init() end; self.CurrentFilter = classFilter; self.Frame.title:SetText(classFilter .. " - Available"); self.Frame:Show(); self:FetchData() end

function Lite:FetchData()
    if not IsInGuild() then return {} end
    C_GuildInfo.GuildRoster(); local num = GetNumGuildMembers(); local temp = {}
    for i=1, num do
        local fullName, rank, rankIndex, level, class, zone, note, onote, online, status, classFile = GetGuildRosterInfo(i)
        if fullName and (online or self.ShowOffline) then
            local rioScore, ilvl, rN, rH, rM, maxKey = 0, 0, "-", "-", "-", "-"
            if _G.RaiderIO and _G.RaiderIO.GetProfile then
                local name, realm = strsplit("-", fullName)
                if not realm or realm == "" then realm = GetRealmName() end
                local region = GetRegionName(); local factionID = GetFactionID()
                local p = _G.RaiderIO.GetProfile(name, realm, factionID)
                if not p then local otherFaction = (factionID == 1) and 2 or 1; p = _G.RaiderIO.GetProfile(name, realm, otherFaction) end
                if not p then p = _G.RaiderIO.GetProfile(name, realm, region) end
                if p then
                    if p.mythicKeystoneProfile then rioScore = p.mythicKeystoneProfile.currentScore or 0; if p.mythicKeystoneProfile.sortedDungeons then local top = p.mythicKeystoneProfile.sortedDungeons[1]; if top then maxKey = "+"..top.level end end end
                    if p.gear then ilvl = p.gear.item_level_equipped or 0 end
                    if p.raidProfile and p.raidProfile.progress then
                        local currentTierPriority = 0
                        for _, prog in ipairs(p.raidProfile.progress) do
                            local rName = string.lower(prog.raid and prog.raid.name or "")
                            local tier = 0
                            if string.find(rName, "manaforge") or string.find(rName, "omega") then tier = 3
                            elseif string.find(rName, "liberation") or string.find(rName, "undermine") then tier = 2
                            elseif string.find(rName, "nerub") or string.find(rName, "palace") then tier = 1 end
                            if tier > 0 and tier >= currentTierPriority then
                                if tier > currentTierPriority then rN, rH, rM = "-", "-", "-"; currentTierPriority = tier end
                                local kills = prog.progressCount or 0; local total = 8; if prog.raid and prog.raid.bossCount then total = prog.raid.bossCount end
                                local str = kills .. "/" .. total
                                if prog.difficulty == 1 then rN = str end; if prog.difficulty == 2 then rH = str end; if prog.difficulty == 3 then rM = str end
                            end
                        end
                    end
                end
            end
            table.insert(temp, {
                name = Ambiguate(fullName, "none"), rank = rank, rankIndex = rankIndex, level = level,
                class = classFile, online = online, ilvl = ilvl,
                rio = rioScore, maxKey = maxKey, raidN = rN, raidH = rH, raidM = rM
            })
        end
    end
    self.RosterData = temp; self:BuildView(); return temp
end

function Lite:Sort(key) if self.SortKey == key then self.SortAsc = not self.SortAsc else self.SortKey = key; self.SortAsc = true end; self:BuildView() end

function Lite:BuildView()
    if not self.Content then return end
    local data = {}; for _, d in ipairs(self.RosterData) do if not self.CurrentFilter or d.class == self.CurrentFilter then table.insert(data, d) end end
    table.sort(data, function(a, b) local valA, valB = a[self.SortKey], b[self.SortKey]; if self.SortAsc then return (valA or 0) < (valB or 0) else return (valA or 0) > (valB or 0) end end)
    for _, r in ipairs(self.Rows) do r:Hide() end; local y = 0
    for i, d in ipairs(data) do
        local r = self.Rows[i]
        if not r then
            r = CreateFrame("Button", nil, self.Content); r:SetSize(600, LITE_ROW_HEIGHT); local bg = r:CreateTexture(nil, "BACKGROUND"); bg:SetAllPoints(); r.bg = bg
            local hl = r:CreateTexture(nil,"HIGHLIGHT"); hl:SetAllPoints(); hl:SetColorTexture(1,1,1,0.1); r.cols = {}
            local x = 0; for _, def in ipairs(LITE_COL_DEF) do local t = r:CreateFontString(nil,"OVERLAY","GameFontHighlightSmall"); t:SetPoint("LEFT", x+5, 0); t:SetWidth(def.width-10); t:SetJustifyH("LEFT"); r.cols[def.key] = t; x = x + def.width end; self.Rows[i] = r
        end
        r:SetPoint("TOPLEFT", 0, -y); r:Show()
        if (i % 2 == 0) then r.bg:SetColorTexture(0.1, 0.1, 0.1, 0.6) else r.bg:SetColorTexture(0.15, 0.15, 0.15, 0.6) end
        local cColor = C_ClassColor.GetClassColor(d.class) or C_ClassColor.GetClassColor("PRIEST")
        local displayName = strsplit("-", d.name)
        local nameText = cColor:WrapTextInColorCode(displayName)
        if not d.online then nameText = "|cff808080" .. displayName .. "|r" end
        r.cols["name"]:SetText(nameText); r.cols["rank"]:SetText(d.rank); r.cols["level"]:SetText(d.level); r.cols["ilvl"]:SetText(d.ilvl > 0 and math.floor(d.ilvl) or "-")
        r.cols["maxKey"]:SetText(d.maxKey); local scoreTxt = (d.rio > 0) and (GR.RR.ScoreColor(d.rio) .. d.rio .. "|r") or ""; r.cols["rio"]:SetText(scoreTxt)
        r.cols["raidN"]:SetText(GR.RR.RaidProgressColor(d.raidN) .. d.raidN .. "|r"); r.cols["raidH"]:SetText(GR.RR.RaidProgressColor(d.raidH) .. d.raidH .. "|r"); r.cols["raidM"]:SetText(GR.RR.RaidProgressColor(d.raidM) .. d.raidM .. "|r")
        y = y + LITE_ROW_HEIGHT
    end
    self.Content:SetHeight(math.max(y, 10)); if self.Frame and self.Frame.title then local scrollFrame = _G["GRS_RR_LiteScroll"]; if scrollFrame and scrollFrame.slider then local maxScroll = math.max(0, self.Content:GetHeight() - scrollFrame:GetHeight()); scrollFrame.slider:SetMinMaxValues(0, maxScroll) end end
end

-- =========================================================================
-- RESET HISTORY DIALOG
-- =========================================================================

function GR:RR_OpenResetHistoryDialog()
    local f = GR:CreateDialog("GRS_RR_ResetHistDlg", "WARNING: Clear Attendance Data", 400, 200)
    f:Show()
    if not f.lbl then
        f.lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        f.lbl:SetPoint("TOP", 0, -35)
        f.lbl:SetWidth(360)
        f.lbl:SetJustifyH("CENTER")
        f.lbl:SetText("This will permanently clear ALL sign-ups, rosters, and attendance logs from the database. Events themselves will be retained.\n\nIf you only want to change specific players, please use manual controls. Continue?")
        f.lbl:SetTextColor(1, 0.2, 0.2)
        
        f.ok = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
        f.ok:SetSize(100, 25)
        f.ok:SetPoint("BOTTOMLEFT", 80, 20)
        
        f.cancel = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
        f.cancel:SetSize(100, 25)
        f.cancel:SetPoint("BOTTOMRIGHT", -80, 20)
        f.cancel:SetText("Cancel")
        f.cancel:SetScript("OnClick", function() f:Hide() end)
    end
    f.ok:Disable(); f.timeLeft = 5; f.ok:SetText("Wait (5)")
    f:SetScript("OnUpdate", function(s, elapsed)
        s.timeLeft = s.timeLeft - elapsed
        if s.timeLeft <= 0 then s.ok:Enable(); s.ok:SetText("CONFIRM"); s:SetScript("OnUpdate", nil)
        else s.ok:SetText(string.format("Wait (%.0f)", math.ceil(s.timeLeft))) end
    end)
    f.ok:SetScript("OnClick", function() GR:RR_ResetAllAttendanceData(); f:Hide() end)
end

-- =========================================================================
-- ATTENDANCE BLOCK EDITOR (UPDATED WITH NO-SHOW)
-- =========================================================================
function GR:RR_OpenBlockEditDialog(data)
    local displayName = strsplit("-", data.cleanName or "Unknown")
    local f = GR:CreateDialog("GRS_RR_BlockEditor", "Edit: " .. displayName, 450, 180); f:Show()
    local options = { 
        {key = "Present", label = "Present", color = "|cff00ff00"}, 
        {key = "BENCH",   label = "Benched", color = "|cff0099ff"}, 
        {key = "ABSENT",  label = "Absent",  color = "|cff808080"},
        {key = "NOSHOW",  label = "No-Show", color = "|cffff0000"} 
    }
    if not f.inited then
        f.inited = true; f.lbl = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); f.lbl:SetPoint("TOP", 0, -40); f.checks = {}
        local xOff = 20
        for i, opt in ipairs(options) do
            local header = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); header:SetPoint("TOPLEFT", xOff, -70); header:SetText(opt.color .. opt.label .. "|r")
            local cb = CreateFrame("CheckButton", nil, f, "UICheckButtonTemplate"); cb:SetPoint("TOP", header, "BOTTOM", 0, -5); cb.key = opt.key
            cb:SetScript("OnClick", function(self) for _, other in pairs(f.checks) do if other ~= self then other:SetChecked(false) end end; if self:GetChecked() then f.selectedStatus = self.key else f.selectedStatus = nil end end); f.checks[opt.key] = cb; xOff = xOff + 100
        end
        f.save = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); f.save:SetSize(80, 25); f.save:SetPoint("BOTTOMLEFT", 60, 20); f.save:SetText("Save")
        f.cancel = CreateFrame("Button", nil, f, "UIPanelButtonTemplate"); f.cancel:SetSize(80, 25); f.cancel:SetPoint("BOTTOMRIGHT", -60, 20); f.cancel:SetText("Cancel"); f.cancel:SetScript("OnClick", function() f:Hide() end)
    end
    f.title:SetText("Edit: " .. displayName); f.lbl:SetText("Editing status for: " .. (data.eventName or "Unknown Event")); f.data = data; f.selectedStatus = data.status
    for key, cb in pairs(f.checks) do local isMatch = (key == data.status); if key == "Present" and (data.status == "TANK" or data.status == "HEALER" or data.status == "DPS" or data.status == "Present") then isMatch = true end; cb:SetChecked(isMatch) end
    f.save:SetScript("OnClick", function() if not f.selectedStatus then return end; local me = GetFullPlayerName(); GR:RR_BroadcastModStatus(data.eventID, data.playerName, "DPS", f.selectedStatus); GR:RR_UpdateMemberStatus(data.eventID, data.playerName, "DPS", f.selectedStatus, GetServerTime()); GR:RR_AddLogEntry(me, string.format("Live update: %s set to %s for event %s", Ambiguate(data.playerName, "none"), f.selectedStatus, data.eventID)); if GR.RR.trackingPanels then for _, p in pairs(GR.RR.trackingPanels) do p.Update() end end; f:Hide() end)
end