-- =========================================================================
-- GRS RECRUITMENT HISTORY v10.0 (STRICT ISOLATION)
-- =========================================================================

local addonName, NS = ...
local GR_Funcs = {}

-- =========================================================================
-- 1. CONFIG & ASSETS
-- =========================================================================

local CLASS_ICON_TCOORDS = {
    ["WARRIOR"] = {0, 0.25, 0, 0.25}, ["MAGE"] = {0.25, 0.5, 0, 0.25},
    ["ROGUE"] = {0.5, 0.75, 0, 0.25}, ["DRUID"] = {0.75, 1, 0, 0.25},
    ["HUNTER"] = {0, 0.25, 0.25, 0.5}, ["SHAMAN"] = {0.25, 0.5, 0.25, 0.5},
    ["PRIEST"] = {0.5, 0.75, 0.25, 0.5}, ["WARLOCK"] = {0.75, 1, 0.25, 0.5},
    ["PALADIN"] = {0, 0.25, 0.5, 0.75}, ["DEATHKNIGHT"] = {0.25, 0.5, 0.5, 0.75},
    ["MONK"] = {0.5, 0.75, 0.5, 0.75}, ["DEMONHUNTER"] = {0.75, 1, 0.5, 0.75},
    ["EVOKER"] = {0, 0.25, 0.75, 1},
}

local function ColorText(text, r, g, b)
    if not text then return "" end
    if not r then r, g, b = 1, 1, 1 end
    return string.format("|cff%02x%02x%02x%s|r", r*255, g*255, b*255, text)
end

-- =========================================================================
-- 2. UI BUILDERS
-- =========================================================================

local function CreateLogFrame(parent, title)
    local f = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    local t = f:CreateTexture(nil, "BACKGROUND"); t:SetAllPoints(); t:SetColorTexture(0,0,0,0.5)
    f.bg = t
    
    local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    lbl:SetPoint("TOP", 0, -15); lbl:SetText(title)
    
    local sf = CreateFrame("ScrollFrame", nil, f, "UIPanelScrollFrameTemplate")
    sf:SetPoint("TOPLEFT", 10, -45); sf:SetPoint("BOTTOMRIGHT", -30, 10)
    
    local content = CreateFrame("Frame", nil, sf)
    content:SetSize(sf:GetWidth(), 500); sf:SetScrollChild(content)
    
    f.scroll = sf; f.content = content; f.rows = {}
    return f
end

local function CreateRow(parent)
    local r = CreateFrame("Frame", nil, parent)
    r:SetSize(300, 45)
    
    r.icon = r:CreateTexture(nil, "ARTWORK"); r.icon:SetSize(24, 24); r.icon:SetPoint("LEFT", 5, 0)
    r.icon:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes")
    r.icon:SetTexCoord(0.5, 0.75, 0.25, 0.5) 
    
    r.date = r:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    r.date:SetPoint("TOPLEFT", r.icon, "TOPRIGHT", 5, 2)
    
    r.name = r:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    r.name:SetPoint("BOTTOMLEFT", r.icon, "BOTTOMRIGHT", 5, -2)
    
    r.details = r:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    r.details:SetPoint("RIGHT", -5, 0); r.details:SetJustifyH("RIGHT")
    
    r.line = r:CreateTexture(nil, "ARTWORK"); r.line:SetHeight(1)
    r.line:SetColorTexture(0.2, 0.2, 0.2, 0.5)
    r.line:SetPoint("BOTTOMLEFT", 0, 0); r.line:SetPoint("BOTTOMRIGHT", 0, 0)
    
    return r
end

-- =========================================================================
-- 3. DATA ENGINE (STRICT ISOLATION)
-- =========================================================================

local RosterCache = {}
local function UpdateRosterCache()
    table.wipe(RosterCache)
    if not IsInGuild() then return end
    for i = 1, GetNumGuildMembers() do
        local name, _, _, _, _, _, _, _, _, _, classFileName = GetGuildRosterInfo(i)
        if name and classFileName then
            RosterCache[name] = classFileName
            local short = string.match(name, "^([^%-]+)")
            if short then RosterCache[short] = classFileName end
        end
    end
end

local function GetGuildKey()
    local gName = GetGuildInfo("player")
    if not gName then return nil end
    local realm = GetRealmName()
    return gName .. "-" .. (realm or "")
end

local function GenerateDNA(y, m, d, h, min, type, p1, p2)
    return string.format("%d:%d:%d:%d:%d|%s|%s|%s", y, m, d, h, min, type, p1 or "nil", p2 or "nil")
end

-- ** MANDATORY MIGRATION ** -- Checks if the DB is in the old flat format and converts it.
local function CheckDBStructure()
    if not GRS_HistoryDB then GRS_HistoryDB = {} end
    
    -- If the DB has numeric keys at the top level, it is the old format.
    local isOldFormat = false
    if #GRS_HistoryDB > 0 then isOldFormat = true end
    
    if isOldFormat then
        print("|cff00ff00GRS:|r Migrating Legacy History Data...")
        local oldData = CopyTable(GRS_HistoryDB)
        table.wipe(GRS_HistoryDB)
        GRS_HistoryDB["Legacy_Archive"] = oldData
        print("|cff00ff00GRS:|r Migration Complete. Old data moved to 'Legacy_Archive'.")
    end
end

function GR_Funcs.PopulateHistory(self)
    if not IsInGuild() then return end
    
    -- 1. Ensure DB Structure
    CheckDBStructure()
    UpdateRosterCache()
    
    -- 2. Get Strict Key
    local gKey = GetGuildKey()
    if not gKey then return end -- No guild info yet
    
    -- 3. Initialize Guild Table
    if not GRS_HistoryDB[gKey] then GRS_HistoryDB[gKey] = {} end
    local activeDB = GRS_HistoryDB[gKey]
    
    local viewList = { joins = {}, ranks = {}, leaves = {} }
    local numEvents = GetNumGuildEvents()
    
    -- 4. READ Blizzard Log -> Write to Active DB (Deduplicated)
    for i = 1, numEvents do
        local eventType, player1, player2, rank, year, month, day, weekday, hour, minute = GetGuildEventInfo(i)
        if eventType then
            local y, m, d, h, min = year or 0, month or 0, day or 0, hour or 0, minute or 0
            
            -- Only store "Historical" events (non-zero date) in the DB
            if not (m == 0 and d == 0) then
                local dna = GenerateDNA(y, m, d, h, min, eventType, player1, player2)
                local isDuplicate = false
                
                for _, stored in ipairs(activeDB) do
                    if stored.dna == dna then isDuplicate = true; break end
                end
                
                if not isDuplicate then
                    table.insert(activeDB, {
                        dna = dna, type = eventType, p1 = player1, p2 = player2, rank = rank,
                        y = y, m = m, d = d, h = h, min = min
                    })
                end
            end
        end
    end
    
    -- 5. READ FROM Active DB (Persistent) -> View List
    for _, entry in ipairs(activeDB) do
        local sortVal = (entry.m * 1000000) + (entry.d * 10000) + (entry.h * 100) + entry.min
        if entry.y > 0 then sortVal = sortVal + (entry.y * 100000000) end
        
        local tsStr = string.format("%02d/%02d %02d:%02d", entry.m, entry.d, entry.h, entry.min)
        
        local rowData = { ts = tsStr, type = entry.type, p1 = entry.p1, p2 = entry.p2, rank = entry.rank, val = sortVal }
        
        if entry.type == "invite" or entry.type == "join" then table.insert(viewList.joins, rowData)
        elseif entry.type == "promote" or entry.type == "demote" then table.insert(viewList.ranks, rowData)
        elseif entry.type == "remove" or entry.type == "quit" then table.insert(viewList.leaves, rowData)
        end
    end
    
    -- 6. READ "Recent" (Live/Transient) events directly from API
    -- These often have 0/0 dates and are not stored in DB to avoid clutter/duplication issues with 0 dates
    for i = 1, numEvents do
        local eventType, player1, player2, rank, year, month, day, weekday, hour, minute = GetGuildEventInfo(i)
        if eventType and (month == 0 and day == 0) then
             local rowData = {
                ts = "|cff00ff00Recent|r", type = eventType, p1 = player1, p2 = player2, rank = rank,
                val = 99999999999 -- Force top
             }
             if eventType == "invite" or eventType == "join" then table.insert(viewList.joins, rowData)
             elseif eventType == "promote" or eventType == "demote" then table.insert(viewList.ranks, rowData)
             elseif eventType == "remove" or eventType == "quit" then table.insert(viewList.leaves, rowData)
             end
        end
    end
    
    local function SortNewest(a, b) return a.val > b.val end
    table.sort(viewList.joins, SortNewest)
    table.sort(viewList.ranks, SortNewest)
    table.sort(viewList.leaves, SortNewest)
    
    self:FillLogFrame(self.histJoinFrame, viewList.joins)
    self:FillLogFrame(self.histRankFrame, viewList.ranks)
    self:FillLogFrame(self.histLeaveFrame, viewList.leaves)
end

function GR_Funcs.FillLogFrame(self, frame, data)
    if not frame then return end
    for _, r in ipairs(frame.rows) do r:Hide() end
    
    local y = 0
    for i, entry in ipairs(data) do
        local r = frame.rows[i]
        if not r then r = CreateRow(frame.content); frame.rows[i] = r end
        
        r:SetWidth(frame.content:GetWidth())
        r:SetPoint("TOPLEFT", 0, y)
        r:Show()
        
        r.date:SetText(entry.ts)
        
        local sName, details, rC, gC, bC = "Unknown", "", 1, 1, 1
        
        if entry.type == "invite" then sName = entry.p2; details = "Invited by\n"..(entry.p1 or "?"); rC,gC,bC = 0,1,0
        elseif entry.type == "join" then sName = entry.p1; details = "Joined"; rC,gC,bC = 0,1,0
        elseif entry.type == "promote" then sName = entry.p2; details = "Promoted to "..(entry.rank or "?").."\nby "..(entry.p1 or "?"); rC,gC,bC = 0.2,1,0.2
        elseif entry.type == "demote" then sName = entry.p2; details = "Demoted to "..(entry.rank or "?").."\nby "..(entry.p1 or "?"); rC,gC,bC = 1,0.5,0
        elseif entry.type == "remove" then sName = entry.p2; details = "Kicked by\n"..(entry.p1 or "?"); rC,gC,bC = 1,0,0
        elseif entry.type == "quit" then sName = entry.p1; details = "Left Guild"; rC,gC,bC = 1,0.5,0
        end
        
        if not sName then sName = "Unknown" end
        
        -- Class Icon Lookup
        r.icon:SetTexCoord(0.5, 0.75, 0.25, 0.5) -- Default Unknown
        local cR, cG, cB = 0.6, 0.6, 0.6
        
        if sName ~= "Unknown" then
            local cFile = RosterCache[sName]
            if cFile and CLASS_ICON_TCOORDS[cFile] then
                r.icon:SetTexCoord(unpack(CLASS_ICON_TCOORDS[cFile]))
                local cObj = C_ClassColor.GetClassColor(cFile)
                if cObj then cR, cG, cB = cObj.r, cObj.g, cObj.b end
            end
        end
        
        r.name:SetText(ColorText(sName, cR, cG, cB))
        r.details:SetText(details)
        r.details:SetTextColor(rC, gC, bC)
        
        y = y - 45
    end
    frame.content:SetHeight(math.abs(y) + 20)
end

function GR_Funcs.BuildHistoryView(self, parent)
    if not parent then return end
    
    for _, region in ipairs({parent:GetRegions()}) do
        if region:GetObjectType() == "FontString" then region:Hide() end
    end
    
    local fJoin = self.histJoinFrame or CreateLogFrame(parent, "Invites")
    self.histJoinFrame = fJoin
    
    local fRank = self.histRankFrame or CreateLogFrame(parent, "Promotions")
    self.histRankFrame = fRank
    
    local fLeave = self.histLeaveFrame or CreateLogFrame(parent, "Departures")
    self.histLeaveFrame = fLeave
    
    local function UpdateLayout()
        local w = parent:GetWidth(); if not w or w < 10 then w = 800 end
        local colWidth = (w - 40) / 3
        
        fJoin:SetSize(colWidth, parent:GetHeight()-80)
        fJoin:ClearAllPoints(); fJoin:SetPoint("TOPLEFT", 10, -50)
        
        fRank:SetSize(colWidth, parent:GetHeight()-80)
        fRank:ClearAllPoints(); fRank:SetPoint("TOPLEFT", fJoin, "TOPRIGHT", 10, 0)
        
        fLeave:SetSize(colWidth, parent:GetHeight()-80)
        fLeave:ClearAllPoints(); fLeave:SetPoint("TOPLEFT", fRank, "TOPRIGHT", 10, 0)
        
        fJoin.content:SetWidth(colWidth-40)
        fRank.content:SetWidth(colWidth-40)
        fLeave.content:SetWidth(colWidth-40)
    end
    
    parent:SetScript("OnSizeChanged", UpdateLayout)
    parent:SetScript("OnShow", function()
        UpdateLayout()
        if IsInGuild() then
            QueryGuildEventLog()
            UpdateRosterCache()
            C_Timer.After(0.5, function() self:PopulateHistory() end)
        end
    end)
    
    if not parent.refreshBtn then
        local btn = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
        btn:SetSize(100, 25); btn:SetPoint("TOPRIGHT", -10, -10); btn:SetText("Reload Logs")
        btn:SetScript("OnClick", function() QueryGuildEventLog(); self:PopulateHistory() end)
        parent.refreshBtn = btn
    end
    
    if not self.histEventFrame then
        self.histEventFrame = CreateFrame("Frame")
        self.histEventFrame:RegisterEvent("GUILD_EVENT_LOG_UPDATE")
        self.histEventFrame:SetScript("OnEvent", function() if parent:IsVisible() then self:PopulateHistory() end end)
    end
    
    UpdateLayout()
end

if _G["GRS_MainFrame"] then
    local GR = _G["GRS_MainFrame"]
    GR.PopulateHistory = GR_Funcs.PopulateHistory
    GR.FillLogFrame = GR_Funcs.FillLogFrame
    GR.BuildHistoryView = GR_Funcs.BuildHistoryView
    
    -- Initialization Hook
    hooksecurefunc(GR, "Initialize", function(self)
        if self.views and self.views[4] then self:BuildHistoryView(self.views[4]) end
    end)
    
    if GR.isInitialized and GR.views and GR.views[4] then
        GR:BuildHistoryView(GR.views[4])
    end
    
    print("|cff00ff00GRS:|r Database Engine v10.0 (Strict) Active.")
end-- =========================================================================
-- GRS RECRUITMENT HISTORY v10.0 (STRICT ISOLATION)
-- =========================================================================

local addonName, NS = ...
local GR_Funcs = {}

-- =========================================================================
-- 1. CONFIG & ASSETS
-- =========================================================================

local CLASS_ICON_TCOORDS = {
    ["WARRIOR"] = {0, 0.25, 0, 0.25}, ["MAGE"] = {0.25, 0.5, 0, 0.25},
    ["ROGUE"] = {0.5, 0.75, 0, 0.25}, ["DRUID"] = {0.75, 1, 0, 0.25},
    ["HUNTER"] = {0, 0.25, 0.25, 0.5}, ["SHAMAN"] = {0.25, 0.5, 0.25, 0.5},
    ["PRIEST"] = {0.5, 0.75, 0.25, 0.5}, ["WARLOCK"] = {0.75, 1, 0.25, 0.5},
    ["PALADIN"] = {0, 0.25, 0.5, 0.75}, ["DEATHKNIGHT"] = {0.25, 0.5, 0.5, 0.75},
    ["MONK"] = {0.5, 0.75, 0.5, 0.75}, ["DEMONHUNTER"] = {0.75, 1, 0.5, 0.75},
    ["EVOKER"] = {0, 0.25, 0.75, 1},
}

local function ColorText(text, r, g, b)
    if not text then return "" end
    if not r then r, g, b = 1, 1, 1 end
    return string.format("|cff%02x%02x%02x%s|r", r*255, g*255, b*255, text)
end

-- =========================================================================
-- 2. UI BUILDERS
-- =========================================================================

local function CreateLogFrame(parent, title)
    local f = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    local t = f:CreateTexture(nil, "BACKGROUND"); t:SetAllPoints(); t:SetColorTexture(0,0,0,0.5)
    f.bg = t
    
    local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    lbl:SetPoint("TOP", 0, -15); lbl:SetText(title)
    
    local sf = CreateFrame("ScrollFrame", nil, f, "UIPanelScrollFrameTemplate")
    sf:SetPoint("TOPLEFT", 10, -45); sf:SetPoint("BOTTOMRIGHT", -30, 10)
    
    local content = CreateFrame("Frame", nil, sf)
    content:SetSize(sf:GetWidth(), 500); sf:SetScrollChild(content)
    
    f.scroll = sf; f.content = content; f.rows = {}
    return f
end

local function CreateRow(parent)
    local r = CreateFrame("Frame", nil, parent)
    r:SetSize(300, 45)
    
    r.icon = r:CreateTexture(nil, "ARTWORK"); r.icon:SetSize(24, 24); r.icon:SetPoint("LEFT", 5, 0)
    r.icon:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes")
    r.icon:SetTexCoord(0.5, 0.75, 0.25, 0.5) 
    
    r.date = r:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    r.date:SetPoint("TOPLEFT", r.icon, "TOPRIGHT", 5, 2)
    
    r.name = r:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    r.name:SetPoint("BOTTOMLEFT", r.icon, "BOTTOMRIGHT", 5, -2)
    
    r.details = r:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    r.details:SetPoint("RIGHT", -5, 0); r.details:SetJustifyH("RIGHT")
    
    r.line = r:CreateTexture(nil, "ARTWORK"); r.line:SetHeight(1)
    r.line:SetColorTexture(0.2, 0.2, 0.2, 0.5)
    r.line:SetPoint("BOTTOMLEFT", 0, 0); r.line:SetPoint("BOTTOMRIGHT", 0, 0)
    
    return r
end

-- =========================================================================
-- 3. DATA ENGINE (STRICT ISOLATION)
-- =========================================================================

local RosterCache = {}
local function UpdateRosterCache()
    table.wipe(RosterCache)
    if not IsInGuild() then return end
    for i = 1, GetNumGuildMembers() do
        local name, _, _, _, _, _, _, _, _, _, classFileName = GetGuildRosterInfo(i)
        if name and classFileName then
            RosterCache[name] = classFileName
            local short = string.match(name, "^([^%-]+)")
            if short then RosterCache[short] = classFileName end
        end
    end
end

local function GetGuildKey()
    local gName = GetGuildInfo("player")
    if not gName then return nil end
    local realm = GetRealmName()
    return gName .. "-" .. (realm or "")
end

local function GenerateDNA(y, m, d, h, min, type, p1, p2)
    return string.format("%d:%d:%d:%d:%d|%s|%s|%s", y, m, d, h, min, type, p1 or "nil", p2 or "nil")
end

-- ** MANDATORY MIGRATION ** -- Checks if the DB is in the old flat format and converts it.
local function CheckDBStructure()
    if not GRS_HistoryDB then GRS_HistoryDB = {} end
    
    -- If the DB has numeric keys at the top level, it is the old format.
    local isOldFormat = false
    if #GRS_HistoryDB > 0 then isOldFormat = true end
    
    if isOldFormat then
        print("|cff00ff00GRS:|r Migrating Legacy History Data...")
        local oldData = CopyTable(GRS_HistoryDB)
        table.wipe(GRS_HistoryDB)
        GRS_HistoryDB["Legacy_Archive"] = oldData
        print("|cff00ff00GRS:|r Migration Complete. Old data moved to 'Legacy_Archive'.")
    end
end

function GR_Funcs.PopulateHistory(self)
    if not IsInGuild() then return end
    
    -- 1. Ensure DB Structure
    CheckDBStructure()
    UpdateRosterCache()
    
    -- 2. Get Strict Key
    local gKey = GetGuildKey()
    if not gKey then return end -- No guild info yet
    
    -- 3. Initialize Guild Table
    if not GRS_HistoryDB[gKey] then GRS_HistoryDB[gKey] = {} end
    local activeDB = GRS_HistoryDB[gKey]
    
    local viewList = { joins = {}, ranks = {}, leaves = {} }
    local numEvents = GetNumGuildEvents()
    
    -- 4. READ Blizzard Log -> Write to Active DB (Deduplicated)
    for i = 1, numEvents do
        local eventType, player1, player2, rank, year, month, day, weekday, hour, minute = GetGuildEventInfo(i)
        if eventType then
            local y, m, d, h, min = year or 0, month or 0, day or 0, hour or 0, minute or 0
            
            -- Only store "Historical" events (non-zero date) in the DB
            if not (m == 0 and d == 0) then
                local dna = GenerateDNA(y, m, d, h, min, eventType, player1, player2)
                local isDuplicate = false
                
                for _, stored in ipairs(activeDB) do
                    if stored.dna == dna then isDuplicate = true; break end
                end
                
                if not isDuplicate then
                    table.insert(activeDB, {
                        dna = dna, type = eventType, p1 = player1, p2 = player2, rank = rank,
                        y = y, m = m, d = d, h = h, min = min
                    })
                end
            end
        end
    end
    
    -- 5. READ FROM Active DB (Persistent) -> View List
    for _, entry in ipairs(activeDB) do
        local sortVal = (entry.m * 1000000) + (entry.d * 10000) + (entry.h * 100) + entry.min
        if entry.y > 0 then sortVal = sortVal + (entry.y * 100000000) end
        
        local tsStr = string.format("%02d/%02d %02d:%02d", entry.m, entry.d, entry.h, entry.min)
        
        local rowData = { ts = tsStr, type = entry.type, p1 = entry.p1, p2 = entry.p2, rank = entry.rank, val = sortVal }
        
        if entry.type == "invite" or entry.type == "join" then table.insert(viewList.joins, rowData)
        elseif entry.type == "promote" or entry.type == "demote" then table.insert(viewList.ranks, rowData)
        elseif entry.type == "remove" or entry.type == "quit" then table.insert(viewList.leaves, rowData)
        end
    end
    
    -- 6. READ "Recent" (Live/Transient) events directly from API
    -- These often have 0/0 dates and are not stored in DB to avoid clutter/duplication issues with 0 dates
    for i = 1, numEvents do
        local eventType, player1, player2, rank, year, month, day, weekday, hour, minute = GetGuildEventInfo(i)
        if eventType and (month == 0 and day == 0) then
             local rowData = {
                ts = "|cff00ff00Recent|r", type = eventType, p1 = player1, p2 = player2, rank = rank,
                val = 99999999999 -- Force top
             }
             if eventType == "invite" or eventType == "join" then table.insert(viewList.joins, rowData)
             elseif eventType == "promote" or eventType == "demote" then table.insert(viewList.ranks, rowData)
             elseif eventType == "remove" or eventType == "quit" then table.insert(viewList.leaves, rowData)
             end
        end
    end
    
    local function SortNewest(a, b) return a.val > b.val end
    table.sort(viewList.joins, SortNewest)
    table.sort(viewList.ranks, SortNewest)
    table.sort(viewList.leaves, SortNewest)
    
    self:FillLogFrame(self.histJoinFrame, viewList.joins)
    self:FillLogFrame(self.histRankFrame, viewList.ranks)
    self:FillLogFrame(self.histLeaveFrame, viewList.leaves)
end

function GR_Funcs.FillLogFrame(self, frame, data)
    if not frame then return end
    for _, r in ipairs(frame.rows) do r:Hide() end
    
    local y = 0
    for i, entry in ipairs(data) do
        local r = frame.rows[i]
        if not r then r = CreateRow(frame.content); frame.rows[i] = r end
        
        r:SetWidth(frame.content:GetWidth())
        r:SetPoint("TOPLEFT", 0, y)
        r:Show()
        
        r.date:SetText(entry.ts)
        
        local sName, details, rC, gC, bC = "Unknown", "", 1, 1, 1
        
        if entry.type == "invite" then sName = entry.p2; details = "Invited by\n"..(entry.p1 or "?"); rC,gC,bC = 0,1,0
        elseif entry.type == "join" then sName = entry.p1; details = "Joined"; rC,gC,bC = 0,1,0
        elseif entry.type == "promote" then sName = entry.p2; details = "Promoted to "..(entry.rank or "?").."\nby "..(entry.p1 or "?"); rC,gC,bC = 0.2,1,0.2
        elseif entry.type == "demote" then sName = entry.p2; details = "Demoted to "..(entry.rank or "?").."\nby "..(entry.p1 or "?"); rC,gC,bC = 1,0.5,0
        elseif entry.type == "remove" then sName = entry.p2; details = "Kicked by\n"..(entry.p1 or "?"); rC,gC,bC = 1,0,0
        elseif entry.type == "quit" then sName = entry.p1; details = "Left Guild"; rC,gC,bC = 1,0.5,0
        end
        
        if not sName then sName = "Unknown" end
        
        -- Class Icon Lookup
        r.icon:SetTexCoord(0.5, 0.75, 0.25, 0.5) -- Default Unknown
        local cR, cG, cB = 0.6, 0.6, 0.6
        
        if sName ~= "Unknown" then
            local cFile = RosterCache[sName]
            if cFile and CLASS_ICON_TCOORDS[cFile] then
                r.icon:SetTexCoord(unpack(CLASS_ICON_TCOORDS[cFile]))
                local cObj = C_ClassColor.GetClassColor(cFile)
                if cObj then cR, cG, cB = cObj.r, cObj.g, cObj.b end
            end
        end
        
        r.name:SetText(ColorText(sName, cR, cG, cB))
        r.details:SetText(details)
        r.details:SetTextColor(rC, gC, bC)
        
        y = y - 45
    end
    frame.content:SetHeight(math.abs(y) + 20)
end

function GR_Funcs.BuildHistoryView(self, parent)
    if not parent then return end
    
    for _, region in ipairs({parent:GetRegions()}) do
        if region:GetObjectType() == "FontString" then region:Hide() end
    end
    
    local fJoin = self.histJoinFrame or CreateLogFrame(parent, "Invites")
    self.histJoinFrame = fJoin
    
    local fRank = self.histRankFrame or CreateLogFrame(parent, "Promotions")
    self.histRankFrame = fRank
    
    local fLeave = self.histLeaveFrame or CreateLogFrame(parent, "Departures")
    self.histLeaveFrame = fLeave
    
    local function UpdateLayout()
        local w = parent:GetWidth(); if not w or w < 10 then w = 800 end
        local colWidth = (w - 40) / 3
        
        fJoin:SetSize(colWidth, parent:GetHeight()-80)
        fJoin:ClearAllPoints(); fJoin:SetPoint("TOPLEFT", 10, -50)
        
        fRank:SetSize(colWidth, parent:GetHeight()-80)
        fRank:ClearAllPoints(); fRank:SetPoint("TOPLEFT", fJoin, "TOPRIGHT", 10, 0)
        
        fLeave:SetSize(colWidth, parent:GetHeight()-80)
        fLeave:ClearAllPoints(); fLeave:SetPoint("TOPLEFT", fRank, "TOPRIGHT", 10, 0)
        
        fJoin.content:SetWidth(colWidth-40)
        fRank.content:SetWidth(colWidth-40)
        fLeave.content:SetWidth(colWidth-40)
    end
    
    parent:SetScript("OnSizeChanged", UpdateLayout)
    parent:SetScript("OnShow", function()
        UpdateLayout()
        if IsInGuild() then
            QueryGuildEventLog()
            UpdateRosterCache()
            C_Timer.After(0.5, function() self:PopulateHistory() end)
        end
    end)
    
    if not parent.refreshBtn then
        local btn = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
        btn:SetSize(100, 25); btn:SetPoint("TOPRIGHT", -10, -10); btn:SetText("Reload Logs")
        btn:SetScript("OnClick", function() QueryGuildEventLog(); self:PopulateHistory() end)
        parent.refreshBtn = btn
    end
    
    if not self.histEventFrame then
        self.histEventFrame = CreateFrame("Frame")
        self.histEventFrame:RegisterEvent("GUILD_EVENT_LOG_UPDATE")
        self.histEventFrame:SetScript("OnEvent", function() if parent:IsVisible() then self:PopulateHistory() end end)
    end
    
    UpdateLayout()
end

if _G["GRS_MainFrame"] then
    local GR = _G["GRS_MainFrame"]
    GR.PopulateHistory = GR_Funcs.PopulateHistory
    GR.FillLogFrame = GR_Funcs.FillLogFrame
    GR.BuildHistoryView = GR_Funcs.BuildHistoryView
    
    -- Initialization Hook
    hooksecurefunc(GR, "Initialize", function(self)
        if self.views and self.views[4] then self:BuildHistoryView(self.views[4]) end
    end)
    
    if GR.isInitialized and GR.views and GR.views[4] then
        GR:BuildHistoryView(GR.views[4])
    end
    
    print("|cff00ff00GRS:|r Database Engine v10.0 (Strict) Active.")
end