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

-- Import Constants & Helpers
local ROSTER_BG_PATH = GR.RR.CONST.ROSTER_BG_PATH
local CLASS_ICON_TCOORDS = GR.RR.CONST.CLASS_ICON_TCOORDS
local MAX_ROWS_PER_COLUMN = 50
local GetCanonicalName = GR.RR.GetCanonicalName

-- Helpers from Shared
local CreateBg = NS.CreateBg or function(f, r, g, b, a) local t = f:CreateTexture(nil, "BACKGROUND"); t:SetAllPoints(); t:SetColorTexture(r, g, b, a); f.bg = t end
local SkinButton = NS.SkinButton or function(btn)
    if not btn then return end
    btn:SetNormalTexture(""); btn:SetHighlightTexture(""); btn:SetPushedTexture(""); btn:SetDisabledTexture("")
    local t = btn:CreateTexture(nil, "BACKGROUND"); t:SetAllPoints(); t:SetColorTexture(0.2, 0.2, 0.2, 1); btn.bg = t
    local fs = btn:GetFontString(); if fs then fs:SetTextColor(1, 1, 1) end
end

-- TBC Safe Class Color Helper
local function GetClassColorSafe(classFile)
    if C_ClassColor and C_ClassColor.GetClassColor then
        return C_ClassColor.GetClassColor(classFile or "PRIEST")
    elseif RAID_CLASS_COLORS and RAID_CLASS_COLORS[classFile] then
        local c = RAID_CLASS_COLORS[classFile]
        if c and not c.WrapTextInColorCode then
            c.WrapTextInColorCode = function(self, text)
                return string.format("|cff%02x%02x%02x%s|r", self.r*255, self.g*255, self.b*255, text)
            end
        end
        return c
    end
    return { r = 1, g = 1, b = 1, WrapTextInColorCode = function(self, t) return "|cffffffff"..t.."|r" end }
end

-- =========================================================================
-- B.1 FIX: OVERLAY BORDER METHOD
-- =========================================================================
local function CreateBorder(parent, r, g, b, a)
    if not parent then return end
    if parent.borderOverlay then 
        parent.borderOverlay:SetBackdropBorderColor(r, g, b, a)
        return parent.borderOverlay 
    end
    local border = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    border:SetAllPoints(parent)
    border:SetBackdrop({
        edgeFile = "Interface\\Buttons\\WHITE8x8", 
        edgeSize = 1, 
        bgFile = nil
    })
    border:SetBackdropBorderColor(r or 1, g or 1, b or 1, a or 1)
    parent.borderOverlay = border
    return border
end

-- =========================================================================
-- STANDALONE DATA RESET FUNCTION
-- =========================================================================

function GR:RR_ResetAllAttendanceData()
    if GRS_RaidRosterDB and GRS_RaidRosterDB.events then
        for _, event in pairs(GRS_RaidRosterDB.events) do
            event.roster = { TANK={}, HEALER={}, DPS={}, BENCH={}, TENTATIVE={}, ABSENT={}, NOSHOW={} }
            if event.counts then event.counts = { TANK=0, HEALER=0, DPS=0 } end
        end
    end
    
    GRS_RaidRosterDB.attendanceHistory = {}
    GRS_RaidRosterDB.logs = {}
    
    local me = GR.RR.GetFullPlayerName()
    GR:RR_AddLogEntry(me, "SYSTEM: Database purge complete.")

    if GR.RR.trackingPanels then 
        for _, p in pairs(GR.RR.trackingPanels) do if p.Update then p.Update() end end 
    end
    if GR.RR_UpdateRaster then GR:RR_UpdateRaster() end
    GR:RR_ScanDB()
end

-- =========================================================================
-- SCROLLBAR FACTORY
-- =========================================================================
local function CreateRobustVerticalScrollbar(parent, targetScrollFrame)
    local container = CreateFrame("Frame", nil, parent, "BackdropTemplate"); container:SetWidth(18); container:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = false, tileSize = 16, edgeSize = 12, insets = { left = 3, right = 3, top = 3, bottom = 3 } }); container:SetBackdropColor(0, 0, 0, 0.8); container:SetBackdropBorderColor(0.6, 0.6, 0.6, 1); container:SetFrameLevel(parent:GetFrameLevel() + 10)
    local upBtn = CreateFrame("Button", nil, container); upBtn:SetSize(16, 16); upBtn:SetPoint("TOP", 0, -2); upBtn:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Up"); upBtn:SetPushedTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Down"); upBtn:SetDisabledTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Disabled"); upBtn:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight")
    local downBtn = CreateFrame("Button", nil, container); downBtn:SetSize(16, 16); downBtn:SetPoint("BOTTOM", 0, 2); downBtn:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Up"); downBtn:SetPushedTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Down"); downBtn:SetDisabledTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Disabled"); downBtn:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight")
    local slider = CreateFrame("Slider", nil, container); slider:SetOrientation("VERTICAL"); slider:SetPoint("TOP", upBtn, "BOTTOM", 0, 0); slider:SetPoint("BOTTOM", downBtn, "TOP", 0, 0); slider:SetWidth(18); local track = slider:CreateTexture(nil, "BACKGROUND"); track:SetAllPoints(); track:SetColorTexture(0.1, 0.1, 0.1, 1); local thumb = slider:CreateTexture(nil, "ARTWORK"); thumb:SetSize(14, 24); thumb:SetColorTexture(0.5, 0.5, 0.5, 1); slider:SetThumbTexture(thumb)
    local step = 40; upBtn:SetScript("OnClick", function() slider:SetValue(slider:GetValue() - step) end); downBtn:SetScript("OnClick", function() slider:SetValue(slider:GetValue() + step) end); slider:SetScript("OnValueChanged", function(self, value) targetScrollFrame:SetVerticalScroll(value); thumb:SetColorTexture(0.8, 0.6, 0, 1) end); slider:SetScript("OnMouseUp", function() thumb:SetColorTexture(0.5, 0.5, 0.5, 1) end)
    container.SetMinMaxValues = function(self, min, max) slider:SetMinMaxValues(min, max); if max <= min then upBtn:Disable(); downBtn:Disable(); thumb:Hide() else upBtn:Enable(); downBtn:Enable(); thumb:Show() end end; container.GetValue = function(self) return slider:GetValue() end; container.SetValue = function(self, v) slider:SetValue(v) end; container.slider = slider; return container
end

-- =========================================================================
-- MAIN UI STRUCTURE
-- =========================================================================

function GR:RR_BuildUI()
    local p = GR.RR.Frame
    if GR.RR.Left then GR.RR.Left:Hide(); GR.RR.Left:SetParent(nil) end
    if GR.RR.Center then GR.RR.Center:Hide(); GR.RR.Center:SetParent(nil) end
    if GR.RR.Tracking then GR.RR.Tracking:Hide(); GR.RR.Tracking:SetParent(nil) end
    local regions = {p:GetRegions()}; for _, region in ipairs(regions) do if region:GetObjectType() == "Texture" then region:Hide() end end
    
    -- UI WRAPPING: Create outer scroll container with fixed-width content
    local outerScroll = CreateFrame("ScrollFrame", "GRS_RR_OuterScroll", p, "UIPanelScrollFrameTemplate")
    outerScroll:SetPoint("TOPLEFT", 0, -55)
    outerScroll:SetPoint("BOTTOMRIGHT", -28, 10)
    if outerScroll.ScrollBar then outerScroll.ScrollBar:Hide(); outerScroll.ScrollBar:SetWidth(0) end
    
    local content = CreateFrame("Frame", "GRS_RR_ScrollChild", outerScroll)
    outerScroll:SetScrollChild(content)
    GR.RR.Content = content
    
    -- Fixed content width: ensures tracking panel stays min 600px wide
    -- Calculation: Left(180+10) + Center(945+20) + Tracking(600+10+margin) = ~1800px
    local FIXED_CONTENT_WIDTH = 1800
    
    p:SetScript("OnSizeChanged", function(self, w, h)
        local scrollW = w - 28
        local scrollH = h - 65
        local finalContentW = math.max(scrollW, FIXED_CONTENT_WIDTH)
        content:SetWidth(finalContentW)
        content:SetHeight(math.max(scrollH, 1))
    end)
    
    -- Background textures on content frame
    local bgSolid = content:CreateTexture(nil, "BACKGROUND", nil, -1); bgSolid:SetAllPoints(); bgSolid:SetColorTexture(0, 0, 0, 0.35)
    local bg = content:CreateTexture(nil, "BACKGROUND"); bg:SetAllPoints(); bg:SetTexture(ROSTER_BG_PATH); bg:SetVertexColor(1, 1, 1, 1)
    
    -- Build panes inside content (not directly on p)
    local left = CreateFrame("Frame", "GRS_RR_Left", content); left:SetWidth(180); left:SetPoint("TOPLEFT", 10, -5); left:SetPoint("BOTTOMLEFT", 10, 10); CreateBg(left, 0, 0, 0, 0.5); GR.RR.Left = left; GR:RR_BuildLeftPane()
    -- FIXED WIDTH: Center panel = 7 columns × 130px + 35px = 945px
    local center = CreateFrame("Frame", "GRS_RR_Center", content); center:SetPoint("TOPLEFT", left, "TOPRIGHT", 10, 0); center:SetPoint("BOTTOMLEFT", left, "BOTTOMRIGHT", 10, 0); center:SetWidth(945); GR.RR.Center = center
    -- Tracking panel: 20px gap from center, widening to right edge
    local tracking = CreateFrame("Frame", "GRS_RR_Tracking", content); tracking:SetPoint("TOPLEFT", center, "TOPRIGHT", 20, 0); tracking:SetPoint("BOTTOMLEFT", center, "BOTTOMRIGHT", 20, 0); tracking:SetPoint("TOPRIGHT", -10, -5); tracking:SetPoint("BOTTOMRIGHT", -10, 10); GR.RR.Tracking = tracking
    GR:RR_BuildOfficerCenterPane(); GR:RR_BuildTrackingPanels(); GR:RR_UpdateLeftPane(); GR:RR_UpdateRaster(); GR:RR_UpdateOfficerRightPane()
end

function GR:RR_BuildLeftPane()
    local p = GR.RR.Left; if not p then return end
    local t = p:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); t:SetPoint("TOP", 0, -10); t:SetText(NS.L and NS.L["SIGN_UPS"] or "Sign-Ups")
    
    local scroll = CreateFrame("ScrollFrame", "GRS_RR_Scroll", p)
    scroll:SetPoint("TOPLEFT", 5, -40); 
    scroll:SetPoint("BOTTOMRIGHT", -25, 150)
    
    local slider = CreateRobustVerticalScrollbar(p, scroll)
    slider:SetPoint("TOPRIGHT", -5, -40)
    slider:SetPoint("BOTTOMRIGHT", -5, 150)
    scroll.slider = slider
    
    scroll:EnableMouseWheel(true)
    scroll:SetScript("OnMouseWheel", function(s, d) 
        local cur = slider:GetValue()
        slider:SetValue(cur - (d * 20)) 
    end)
    
    local content = CreateFrame("Frame", nil, scroll); content:SetSize(150, 500); scroll:SetScrollChild(content); GR.RR.EventListContent = content; GR.RR.EventCards = {}
    local btnArea = CreateFrame("Frame", nil, p); btnArea:SetPoint("TOPLEFT", scroll, "BOTTOMLEFT", 0, -10); btnArea:SetPoint("BOTTOMRIGHT", p, "BOTTOMRIGHT", -5, 5)
    local function MkBtn(txt, parent, w, h) local b = CreateFrame("Button", nil, parent); b:SetSize(w, h); SkinButton(b); local fs = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); fs:SetPoint("CENTER"); fs:SetText(txt); b:SetFontString(fs); return b end
    if GR.RR.viewMode == "OFFICER" then
        local btnCreate = MkBtn(NS.L and NS.L["CREATE"] or "Create", btnArea, 80, 22); btnCreate:SetPoint("TOPLEFT", 0, 0); btnCreate:SetScript("OnClick", function() GR:RR_OpenEventDialog("CREATE") end)
        local btnEdit = MkBtn(NS.L and NS.L["EDIT"] or "Edit", btnArea, 80, 22); btnEdit:SetPoint("LEFT", btnCreate, "RIGHT", 5, 0); btnEdit:SetScript("OnClick", function() if GR.RR.selectedEvent then GR:RR_OpenEventDialog("EDIT", GR.RR.selectedEvent) end end)
        local btnDel = MkBtn(NS.L and NS.L["DELETE"] or "Delete", btnArea, 80, 22); btnDel:SetPoint("TOPLEFT", btnCreate, "BOTTOMLEFT", 0, -5); GR:RR_SetupDeleteButton(btnDel)
        local btnRefresh = MkBtn(NS.L and NS.L["REFRESH"] or "Refresh", btnArea, 80, 22); btnRefresh:SetPoint("LEFT", btnDel, "RIGHT", 5, 0); btnRefresh:SetScript("OnClick", function() GR:RR_RequestSync() end)
        local btnInspect = MkBtn(NS.L and NS.L["INSPECT"] or "Inspect", btnArea, 165, 22); btnInspect:SetPoint("TOPLEFT", btnDel, "BOTTOMLEFT", 0, -5); btnInspect:SetScript("OnClick", function() GR:RR_OpenInspectDialog() end)
        local btnSignUp = MkBtn(NS.L and NS.L["SIGN_UP"] or "Sign-Up", btnArea, 165, 22); btnSignUp:SetPoint("TOPLEFT", btnInspect, "BOTTOMLEFT", 0, -5); btnSignUp:SetScript("OnClick", function() GR:RR_OpenSignUpDialog() end)
    else local btnSignUp = MkBtn(NS.L and NS.L["SIGN_UP"] or "Sign-Up", btnArea, 165, 30); btnSignUp:SetPoint("TOP", 0, -10); btnSignUp:SetScript("OnClick", function() GR:RR_OpenSignUpDialog() end) end
end

function GR:RR_UpdateLeftPane()
    local content = GR.RR.EventListContent; if not content then return end
    local list = GR.RR.cache or {}; for _, card in pairs(GR.RR.EventCards) do card:Hide() end; local y = 0
    for i, event in ipairs(list) do
        local card = GR.RR.EventCards[i]
        if not card then
            card = CreateFrame("Button", nil, content, "BackdropTemplate"); card:SetSize(150, 95); CreateBg(card, 0, 0, 0, 0.5)
            
            -- GOLD SELECTION BORDER
            local sel = CreateFrame("Frame", nil, card, "BackdropTemplate")
            sel:SetAllPoints()
            sel:SetBackdrop({
                edgeFile = "Interface\\Buttons\\WHITE8x8",
                edgeSize = 2,
            })
            sel:SetBackdropBorderColor(1, 0.84, 0, 1)
            sel:Hide()
            card.sel = sel

            card.name = card:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); card.name:SetPoint("TOPLEFT", 5, -5); card.name:SetJustifyH("LEFT"); card.name:SetWidth(120)
            card.dt = card:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); card.dt:SetPoint("TOPLEFT", 5, -25); card.dt:SetJustifyH("LEFT")
            card.tm = card:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); card.tm:SetPoint("TOPLEFT", 5, -45); card.tm:SetJustifyH("LEFT")
            card.ds = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); card.ds:SetPoint("TOPLEFT", 5, -65); card.ds:SetJustifyH("LEFT"); card.ds:SetWidth(120)
            local chk = CreateFrame("CheckButton", nil, card, "UICheckButtonTemplate"); chk:SetSize(20, 20); chk:SetPoint("RIGHT", -2, 0); chk:SetScript("OnClick", function(s) if s:GetChecked() then GR.RR.multiSelect[card.data.id] = true else GR.RR.multiSelect[card.data.id] = nil end end); card.chk = chk; card:SetScript("OnClick", function(s) GR:RR_SelectEvent(s.data) end); GR.RR.EventCards[i] = card
        end
        card.data = event; card:SetPoint("TOP", 0, -y); card:Show()
        card.name:SetText(event.name)
        card.dt:SetText(event.date or "")
        card.tm:SetText((event.startTime or "??:??") .. " - " .. (event.endTime or "??:??"))
        local truncatedDesc = (event.def or ""):sub(1, 10)
        if #(event.def or "") > 10 then truncatedDesc = truncatedDesc .. "..." end
        card.ds:SetText(truncatedDesc)
        
        if card.chk then card.chk:SetShown(GR.RR.viewMode == "OFFICER"); card.chk:SetChecked(GR.RR.multiSelect[event.id]) end
        
        if GR.RR.selectedEvent and GR.RR.selectedEvent.id == event.id then 
            card.bg:SetColorTexture(0.3, 0.3, 0.5, 0.8) 
            card.sel:Show()
        else 
            card.bg:SetColorTexture(0, 0, 0, 0.5) 
            card.sel:Hide()
        end; y = y + 100
    end
    content:SetHeight(math.max(y, 10))
    
    local scroll = content:GetParent()
    if scroll and scroll.slider then
        local maxScroll = math.max(0, content:GetHeight() - scroll:GetHeight())
        scroll.slider:SetMinMaxValues(0, maxScroll)
    end
end

function GR:RR_UpdateLeftPaneCounters() GR:RR_UpdateLeftPane() end

function GR:RR_BuildOfficerCenterPane()
    local p = GR.RR.Center; if not p then return end
    local availFrame = CreateFrame("Frame", nil, p); availFrame:SetPoint("TOPLEFT", 0, 0); availFrame:SetPoint("RIGHT", 0, 0); availFrame:SetHeight(75); CreateBg(availFrame, 0, 0, 0, 0.4)
    
    local offBtn = CreateFrame("Button", nil, availFrame); offBtn:SetSize(100, 18); 
    SkinButton(offBtn); local offFs = offBtn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); offFs:SetPoint("CENTER"); offFs:SetText(NS.L and NS.L["SHOW_OFFLINE"] or "Show Offline"); offBtn:SetFontString(offFs)
    
    local function UpdateOffBtn() if GRS_RaidRosterDB.settings.showOffline then offFs:SetTextColor(0, 1, 0) else offFs:SetTextColor(1, 1, 1) end end; UpdateOffBtn()
    offBtn:SetScript("OnClick", function() GRS_RaidRosterDB.settings.showOffline = not GRS_RaidRosterDB.settings.showOffline; UpdateOffBtn(); GR:RR_UpdateOfficerRightPane() end)
    
    -- TBC Classes Only (no DK, Monk, DH, Evoker) - 9 classes fit on one row (9 × 55 = 495px)
    local classes = { "WARRIOR", "PALADIN", "HUNTER", "ROGUE", "PRIEST", "SHAMAN", "MAGE", "WARLOCK", "DRUID" }
    GR.RR.ClassButtons = {}; local x, y = 10, -25
    for i, cls in ipairs(classes) do
        local btn = CreateFrame("Button", nil, availFrame); btn:SetSize(24, 24); btn:SetPoint("TOPLEFT", x, y); local icon = btn:CreateTexture(nil, "ARTWORK"); icon:SetAllPoints(); local coords = CLASS_ICON_TCOORDS[cls]; icon:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes"); if coords then icon:SetTexCoord(unpack(coords)) else icon:SetTexCoord(0, 1, 0, 1) end; btn:SetScript("OnClick", function() if GR.RR.Lite then GR.RR.Lite.ShowOffline = GRS_RaidRosterDB.settings.showOffline; GR.RR.Lite:ShowPopup(cls) end end); local cnt = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); cnt:SetPoint("LEFT", btn, "RIGHT", 2, 0); cnt:SetText("0"); btn.cnt = cnt; GR.RR.ClassButtons[cls] = btn; x = x + 55
    end
    
    -- TBC: Anchor Show Offline to Druid (last class) like Retail anchors to Shaman
    local druid = GR.RR.ClassButtons["DRUID"]
    if druid then
        offBtn:SetPoint("LEFT", druid.cnt, "RIGHT", 25, 0)
    else
        offBtn:SetPoint("TOPRIGHT", availFrame, "TOPRIGHT", -200, -25)
    end
    
    -- (Raid Map removed from TBC)

    GR:RR_BuildRaster(p); GR.RR.RasterFrame:SetPoint("TOPLEFT", availFrame, "BOTTOMLEFT", 0, -10); GR.RR.RasterFrame:SetPoint("BOTTOMRIGHT", 0, 0)
end

function GR:RR_UpdateOfficerRightPane() 
    -- DevOfficerMode bypass for testing without guild
    if not IsInGuild() and not GR.devOfficerMode then return end
    if GR.RR.Lite then GR.RR.Lite.ShowOffline = GRS_RaidRosterDB.settings.showOffline end
    local roster = GR:RR_FetchClassCounts(GRS_RaidRosterDB.settings.showOffline)
    if GR.RR.ClassButtons then 
        for cls, btn in pairs(GR.RR.ClassButtons) do 
            btn.cnt:SetText(roster[cls] or 0) 
        end 
    end
    GR:RR_UpdateRaster() 
end

function GR:RR_BuildMemberRightPane() GR:RR_BuildOfficerCenterPane() end
function GR:RR_UpdateMemberRightPane() GR:RR_UpdateRaster() end

function GR:RR_BuildRaster(parent)
    local f = CreateFrame("ScrollFrame", "GRS_RR_RasterScroll", parent); CreateBg(f, 0, 0, 0, 0.4); local slider = CreateRobustVerticalScrollbar(f, f); 
    slider:SetPoint("TOPRIGHT", -2, 0); 
    slider:SetPoint("BOTTOMRIGHT", -2, 0); 
    f.slider = slider; local content = CreateFrame("Frame", nil, f); content:SetSize(400, 400); f:SetScrollChild(content); f:EnableMouseWheel(true); f:SetScript("OnMouseWheel", function(s, d) local cur = slider:GetValue(); slider:SetValue(cur - (d * 30)) end); GR.RR.RasterFrame = f; GR.RR.RasterContent = content; GR.RR.EventContainers = {}
end

function GR:RR_UpdateRaster()
    local content = GR.RR.RasterContent; if not content then return end; if GR.RR.EventContainers then for _, container in pairs(GR.RR.EventContainers) do container:Hide() end end; local events = {}; if GRS_RaidRosterDB and GRS_RaidRosterDB.events then for id, data in pairs(GRS_RaidRosterDB.events) do table.insert(events, data) end end; table.sort(events, function(a, b) local tA = GR.RR.ParseDate(a.date) or 0; local tB = GR.RR.ParseDate(b.date) or 0; return tA < tB end); local yOff = 10; local containerIdx = 1
    
    local colHeight = (MAX_ROWS_PER_COLUMN * 12) + 40
    local containerHeight = colHeight + 40

    for _, evt in ipairs(events) do
        local frame = GR.RR.EventContainers[containerIdx]
        if not frame then
            frame = CreateFrame("Frame", nil, content); frame:SetWidth(800); 
            frame.header = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge"); 
            frame.header:SetPoint("TOPLEFT", 10, 0); 
            frame.cols = {}
            for i = 1, 7 do
                local c = CreateFrame("Frame", nil, frame, "BackdropTemplate"); 
                c:SetSize(125, colHeight); 
                c:SetBackdrop({bgFile="Interface\\DialogFrame\\UI-DialogBox-Background", edgeFile="", tile=true, tileSize=16}); c:SetBackdropColor(0,0,0,0.5); c.head = c:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); c.head:SetPoint("TOP", 0, -5); c.rows = {}
                for r = 1, MAX_ROWS_PER_COLUMN do local t = c:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall"); t:SetPoint("TOPLEFT", 5, -25-((r-1)*12)); t:SetWidth(115); t:SetJustifyH("LEFT"); c.rows[r] = t end; frame.cols[i] = c
            end
            frame.sep = frame:CreateTexture(nil, "ARTWORK"); frame.sep:SetHeight(2); frame.sep:SetTexture("Interface\\ChatFrame\\ChatFrameBackground"); frame.sep:SetVertexColor(1, 0.82, 0, 0.6); GR.RR.EventContainers[containerIdx] = frame
        end
        frame:ClearAllPoints(); frame:SetPoint("TOPLEFT", 0, -yOff); frame:Show()
        
        local timeRange = (evt.startTime or "??:??") .. " - " .. (evt.endTime or "??:??")
        local headerText = string.format("%s - %s - %s |cffC0C0C0 Description:|r %s", (evt.name or "Unknown"), (evt.date or ""), timeRange, (evt.def or ""))
        frame.header:SetText(headerText)

        local xCol = 10; 
        local roles = { 
            {name = "TANKS", key = "TANK", color = {0.2,0.6,1}}, 
            {name = "HEALERS", key = "HEALER", color = {0.2,1,0.4}}, 
            {name = "DPS", key = "DPS", color = {1,0.3,0.3}}, 
            {name = "BENCH", key = "BENCH", color = {0.4, 0.8, 1}}, 
            {name = "TENTATIVE", key = "TENTATIVE", color = {1, 0.8, 0}}, 
            {name = "ABSENT", key = "ABSENT", color = {0.6, 0.6, 0.6}},
            {name = "NO SHOW", key = "NOSHOW", color = {0.8, 0, 0}} 
        }
        for i, role in ipairs(roles) do
            local col = frame.cols[i]; col:SetPoint("TOPLEFT", xCol, -25); col.head:SetText(role.name); col.head:SetTextColor(unpack(role.color)); local list = evt.roster and evt.roster[role.key] or {}
            for r = 1, MAX_ROWS_PER_COLUMN do 
                if list[r] then 
                    local displayName = strsplit("-", list[r].name)
                    local classColor = GetClassColorSafe(list[r].class or "PRIEST")
                    col.rows[r]:SetText(classColor:WrapTextInColorCode(displayName)); col.rows[r]:Show() 
                else 
                    col.rows[r]:Hide() 
                end 
            end; xCol = xCol + 130
        end
        frame:SetHeight(containerHeight); frame.sep:SetPoint("BOTTOMLEFT", 10, 5); frame.sep:SetPoint("BOTTOMRIGHT", xCol - 130, 5); yOff = yOff + (containerHeight + 10); containerIdx = containerIdx + 1
    end
    content:SetHeight(yOff); content:SetWidth(800); local maxScroll = math.max(0, yOff - GR.RR.RasterFrame:GetHeight()); GR.RR.RasterFrame.slider:SetMinMaxValues(0, maxScroll)
end

function GR:RR_BuildTrackingPanels()
    local container = GR.RR.Tracking; if not container then return end; local children = {container:GetChildren()}; for _, child in ipairs(children) do child:Hide(); child:SetParent(nil) end; local pA = CreateFrame("Frame", "GRS_RR_PanelA", container); pA:SetPoint("TOPLEFT", 0, 0); pA:SetPoint("TOPRIGHT", 0, 0); pA:SetHeight(container:GetHeight() / 2 - 5); GR:RR_BuildSingleTracker(pA, "panelA"); GR.RR.trackingPanels["panelA"] = pA; local pB = CreateFrame("Frame", "GRS_RR_PanelB", container); pB:SetPoint("BOTTOMLEFT", 0, 0); pB:SetPoint("BOTTOMRIGHT", 0, 0); pB:SetPoint("TOP", pA, "BOTTOM", 0, -10); GR:RR_BuildSingleTracker(pB, "panelB"); GR.RR.trackingPanels["panelB"] = pB; container:SetScript("OnSizeChanged", function(s, w, h) pA:SetHeight(h/2 - 5); if pA.Update then pA.Update() end; if pB.Update then pB.Update() end end)
end

function GR:RR_BuildSingleTracker(frame, panelKey)
    local bg = frame:CreateTexture(nil, "BACKGROUND"); bg:SetAllPoints(); bg:SetColorTexture(0, 0, 0, 0.4); local settings = GRS_RaidRosterDB.settings[panelKey]; local header = CreateFrame("Frame", nil, frame); header:SetHeight(25); header:SetPoint("TOPLEFT"); header:SetPoint("TOPRIGHT"); CreateBg(header, 0.1, 0.1, 0.1, 0.8); local title = header:CreateFontString(nil, "OVERLAY", "GameFontNormal"); title:SetPoint("LEFT", 5, 0); title:SetText(settings.title or "Panel"); frame.title = title; local btnCfg = CreateFrame("Button", nil, header); btnCfg:SetSize(16, 16); btnCfg:SetPoint("RIGHT", -5, 0); btnCfg:SetNormalTexture("Interface\\WorldMap\\Gear_64"); btnCfg:GetNormalTexture():SetTexCoord(0, 0.5, 0, 0.5); btnCfg:SetHighlightTexture("Interface\\WorldMap\\Gear_64"); btnCfg:GetHighlightTexture():SetTexCoord(0.5, 1, 0, 0.5); btnCfg:SetPushedTexture("Interface\\WorldMap\\Gear_64"); btnCfg:GetNormalTexture():SetTexCoord(0, 0.5, 0.5, 1)
    if GR.RR.viewMode == "OFFICER" then btnCfg:Show(); btnCfg:SetScript("OnClick", function() GR:RR_OpenPanelSettings(panelKey) end); btnCfg:SetScript("OnEnter", function(self) GameTooltip:SetOwner(self, "ANCHOR_RIGHT"); GameTooltip:SetText("Configure Panel Settings"); GameTooltip:Show() end); btnCfg:SetScript("OnLeave", function() GameTooltip:Hide() end) else btnCfg:Hide() end
    if GR.RR.viewMode == "OFFICER" and panelKey == "panelA" then
        local btnSnap = CreateFrame("Button", nil, header); btnSnap:SetSize(16, 16); btnSnap:SetPoint("RIGHT", btnCfg, "LEFT", -5, 0); btnSnap:SetNormalTexture("Interface\\Buttons\\UI-GuildButton-PublicNote-Up"); btnSnap:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight"); btnSnap:SetScript("OnClick", function() GR:RR_TakeSnapshot(panelKey) end); btnSnap:SetScript("OnEnter", function(self) GameTooltip:SetOwner(self, "ANCHOR_RIGHT"); GameTooltip:SetText("Snapshot Status"); GameTooltip:AddLine("Verifies current roster data.", 1, 1, 1); GameTooltip:Show() end); btnSnap:SetScript("OnLeave", function() GameTooltip:Hide() end)
        local btnReset = CreateFrame("Button", nil, header); btnReset:SetSize(16, 16); btnReset:SetPoint("RIGHT", btnSnap, "LEFT", -5, 0); btnReset:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up"); btnReset:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight"); btnReset:SetScript("OnClick", function() GR:RR_OpenResetHistoryDialog() end); btnReset:SetScript("OnEnter", function(self) GameTooltip:SetOwner(self, "ANCHOR_RIGHT"); GameTooltip:SetText("Reset History"); GameTooltip:AddLine("Permanently clear all saved attendance data.", 1, 0.2, 0.2); GameTooltip:Show() end); btnReset:SetScript("OnLeave", function() GameTooltip:Hide() end)
        local btnLog = CreateFrame("Button", nil, header); btnLog:SetSize(40, 18); btnLog:SetPoint("RIGHT", btnReset, "LEFT", -8, 0); local logFs = btnLog:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); logFs:SetPoint("CENTER"); logFs:SetText("LOG"); btnLog:SetFontString(logFs); SkinButton(btnLog); btnLog:SetScript("OnClick", function() GR:RR_OpenLogDialog() end); btnLog:SetScript("OnEnter", function(self) GameTooltip:SetOwner(self, "ANCHOR_RIGHT"); GameTooltip:SetText("Open Audit Log"); GameTooltip:Show() end); btnLog:SetScript("OnLeave", function() GameTooltip:Hide() end)
        local btnImport = CreateFrame("Button", nil, header); btnImport:SetSize(80, 18); btnImport:SetPoint("RIGHT", btnLog, "LEFT", -8, 0); local impFs = btnImport:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); impFs:SetPoint("CENTER"); impFs:SetText("IMPORT WCL"); btnImport:SetFontString(impFs); SkinButton(btnImport); btnImport:SetScript("OnClick", function() if GR.RR_OpenImportDialog then GR:RR_OpenImportDialog() end end); btnImport:SetScript("OnEnter", function(self) GameTooltip:SetOwner(self, "ANCHOR_RIGHT"); GameTooltip:SetText("Import Warcraft Logs"); GameTooltip:AddLine("Paste CSV data to populate raid attendance.", 1, 1, 1); GameTooltip:Show() end); btnImport:SetScript("OnLeave", function() GameTooltip:Hide() end)
    end
    local scroll = CreateFrame("ScrollFrame", nil, frame); scroll:SetPoint("TOPLEFT", 5, -30); scroll:SetPoint("BOTTOMRIGHT", -25, 25); local content = CreateFrame("Frame", nil, scroll); content:SetSize(frame:GetWidth()-30, 500); scroll:SetScrollChild(content); local vSlider = CreateRobustVerticalScrollbar(frame, scroll); vSlider:ClearAllPoints(); vSlider:SetPoint("TOPRIGHT", -5, -30); vSlider:SetPoint("BOTTOMRIGHT", -5, 25); scroll:EnableMouseWheel(true); scroll:SetScript("OnMouseWheel", function(s, d) local cur = vSlider:GetValue(); vSlider:SetValue(cur - (d * 20)) end); frame.scroll = scroll; frame.content = content; frame.vSlider = vSlider; frame.rows = {}; frame.Update = function() GR:RR_UpdateTrackerList(frame, panelKey) end; frame.Update()
end

function GR:RR_GetOrCreateTrackingRow(parent, index)
    if not parent.rows then parent.rows = {} end; local r = parent.rows[index]
    if not r then
        r = CreateFrame("Frame", nil, parent); r:SetSize(parent:GetWidth(), 32); r.n = r:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); r.n:SetPoint("TOPLEFT", 5, -2); r.n:SetPoint("BOTTOMLEFT", 5, 2); r.n:SetJustifyH("LEFT"); r.n:SetJustifyV("MIDDLE"); r.n:SetWordWrap(true); r.buttons = {}
        for i = 1, 20 do
            local b = CreateFrame("Button", nil, r)
            b:SetSize(20, 20)
            local t = b:CreateTexture(nil, "BACKGROUND")
            t:SetAllPoints()
            b.bg = t
            local lbl = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            lbl:SetPoint("CENTER")
            lbl:SetShadowColor(0,0,0,1)
            lbl:SetShadowOffset(1,-1)
            b.lbl = lbl
            b:SetScript("OnEnter", function(s) if not s.info then return end; GameTooltip:SetOwner(s, "ANCHOR_RIGHT"); GameTooltip:SetText(s.info.date); GameTooltip:AddLine(s.info.eventName, 1, 1, 1); local displayP = strsplit("-", s.info.playerName or "Unknown"); GameTooltip:AddLine("Player: " .. displayP, 1, 1, 1); GameTooltip:AddLine("Status: " .. (s.info.status or "None"), 1, 0.82, 0); GameTooltip:Show() end)
            b:SetScript("OnLeave", function() GameTooltip:Hide() end)
            b:SetScript("OnClick", function(s) if s.info and GR.RR.viewMode == "OFFICER" then GR:RR_OpenBlockEditDialog(s.info) end end)
            r.buttons[i] = b
        end
        parent.rows[index] = r
    end
    return r
end

function GR:RR_UpdateTrackingRow(row, playerData, settings, eventIDs, btnWidth)
    local limit = settings.limit or 10
    local classColor = GetClassColorSafe(playerData.class or "PRIEST")
    local displayName = strsplit("-", playerData.name or "Unknown")
    row.n:SetText(classColor:WrapTextInColorCode(displayName)); local me = GR.RR.GetFullPlayerName()
    for i = 1, #row.buttons do
        local btn = row.buttons[i]
        if i <= limit then
            btn:Show(); btn:SetWidth(btnWidth); local xOffset = -((limit - i) * (btnWidth + 2)) - 2; btn:ClearAllPoints(); btn:SetPoint("RIGHT", row, "RIGHT", xOffset, 0); local evtID = eventIDs[i]
            if evtID then
                local status, isSnapshot = GR:RR_GetSmartStatus(evtID, playerData.name)
                local evtDetails = GR:RR_GetEventDetails(evtID)
                
                if GR.RR.viewMode == "OFFICER" or playerData.name == me then 
                    if status then 
                        local upperStatus = string.upper(status)
                        local color = GR.RR.CONST.STATUS_COLORS[upperStatus] or GR.RR.CONST.STATUS_COLORS.NULL
                        btn.bg:SetColorTexture(unpack(color)) 
                    else 
                        btn.bg:SetColorTexture(0.1, 0.1, 0.1, 0.3) 
                    end 
                else 
                    btn.bg:SetColorTexture(unpack(GR.RR.CONST.STATUS_COLORS.NULL)) 
                end
                
                btn.info = { eventID = evtID, playerName = playerData.name, cleanName = playerData.name, name = playerData.name, date = evtDetails.date, eventName = evtDetails.name, status = status, isSnapshot = isSnapshot }
                if evtDetails.date then local d, m = strsplit("-", evtDetails.date); btn.lbl:SetText(d or "") end
            else 
                btn.bg:SetColorTexture(0, 0, 0, 0); btn.info = nil; btn.lbl:SetText("") 
            end
        else 
            btn:Hide() 
        end
    end
end

function GR:RR_UpdateTrackerList(frame, panelKey)
    local settings = GRS_RaidRosterDB.settings[panelKey]; if not settings then return end
    local limit = settings.limit or 10
    -- TBC: Use GuildRoster() instead of C_GuildInfo.GuildRoster()
    if GuildRoster then GuildRoster() elseif C_GuildInfo and C_GuildInfo.GuildRoster then C_GuildInfo.GuildRoster() end
    local candidates = {}; local num = GetNumGuildMembers()
    for i=1, num do local name, rank, rankIndex, _, _, _, _, _, online, _, class = GetGuildRosterInfo(i); if name and settings.ranks[rankIndex] then table.insert(candidates, { name = Ambiguate(name, "none"), class = class, online = online }) end end
    table.sort(candidates, function(a,b) return a.name < b.name end); local eventIDs = GR:RR_GetSortedEventIDs(limit); local viewWidth = frame.scroll:GetWidth(); local nameReserved = 130; local totalContentWidth = viewWidth; local btnWidth = ((viewWidth - nameReserved) / limit) - 2; frame.content:SetWidth(totalContentWidth); local y = 0; for _, r in pairs(frame.rows) do r:Hide() end
    for i, data in ipairs(candidates) do local r = GR:RR_GetOrCreateTrackingRow(frame.content, i); r:SetWidth(totalContentWidth); r:SetPoint("TOPLEFT", 0, -y); r:Show(); GR:RR_UpdateTrackingRow(r, data, settings, eventIDs, btnWidth); y = y + 32 end; frame.content:SetHeight(math.max(y, 10)); local maxScroll = math.max(0, frame.content:GetHeight() - frame.scroll:GetHeight()); frame.vSlider:SetMinMaxValues(0, maxScroll)
end
