-- =========================================================================
-- GRS RECRUITMENT HISTORY v12.7 (SMART DEPARTURE TRACKING)
-- =========================================================================

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. DATA PERSISTENCE ENGINE
-- =========================================================================

local RosterCache = {}

local function GetGuildKey()
    local gName = GetGuildInfo("player")
    if not gName then 
        -- Fallback: Try to get it from API if cache is cold
        gName = GetGuildInfo("player") 
    end
    if not gName then return nil end
    
    local realm = GetRealmName()
    return gName .. "-" .. (realm or "")
end

local function UpdateRosterCache()
    table.wipe(RosterCache)
    if not IsInGuild() then return end
    
    C_GuildInfo.GuildRoster() 
    
    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 ResolveClass(name, dbClass)
    if dbClass then return dbClass end
    if not name then return nil end
    if RosterCache[name] then return RosterCache[name] end
    local short = string.match(name, "^([^%-]+)")
    if short and RosterCache[short] then return RosterCache[short] end
    return nil
end

local function GenerateDNA(ts, type, p1, p2)
    return string.format("%d|%s|%s|%s", ts or 0, type, p1 or "nil", p2 or "nil")
end

-- =========================================================================
-- 3. THE VULTURE (UI SCRAPER HOOK)
-- =========================================================================

local function ScrapeLogIndex(index)
    if not index then return false end 
    
    -- [VULTURE] Attempt to grab data
    local eventType, player1, player2, rank, year, month, day, weekday, hour, minute = GetGuildEventInfo(index)
    
    if not eventType then return false end -- End of list check
    
    -- [RAW CAPTURE]
    if not year then return true end 
    
    local gKey = GetGuildKey()
    if not gKey then return true end
    
    if not GRS_HistoryDB then GRS_HistoryDB = {} end
    if not GRS_HistoryDB[gKey] then GRS_HistoryDB[gKey] = {} end
    local activeDB = GRS_HistoryDB[gKey]
    
    local safeY = tonumber(year)
    local safeM = month
    local safeD = day
    local safeH = hour or 0
    local safeMin = minute or 0
    
    -- [RAW TIMESTAMP]
    if not safeY then safeY = 0 end 
    
    local ts = time({year = safeY, month = safeM, day = safeD, hour = safeH, min = safeMin, sec = 0})
    
    local dna = GenerateDNA(ts, 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
        local c1 = ResolveClass(player1)
        local c2 = ResolveClass(player2)
        
        table.insert(activeDB, {
            dna = dna, type = eventType, p1 = player1, p2 = player2, 
            cP1 = c1, cP2 = c2, rank = rank,
            ts = ts, 
            source = "VULTURE"
        })
    end
    return true 
end

local vultureHooked = false
local function ActivateVulture()
    if vultureHooked then return end
    
    if not C_AddOns.IsAddOnLoaded("Blizzard_Communities") then
        C_AddOns.LoadAddOn("Blizzard_Communities")
    end

    if CommunitiesGuildLogFrame and CommunitiesGuildLogFrame.Container and CommunitiesGuildLogFrame.Container.ScrollBox then
        
        CommunitiesGuildLogFrame.Container.ScrollBox:RegisterCallback("OnScroll", function(scrollBox)
            if not IsInGuild() then return end
            
            local dataProvider = scrollBox:GetDataProvider()
            if not dataProvider then return end
            
            local count = dataProvider:GetSize()
            local limit = math.min(count, 250) 
            
            for i = 1, limit do ScrapeLogIndex(i) end
        end)
        
        CommunitiesGuildLogFrame:HookScript("OnShow", function()
             QueryGuildEventLog() 
             for i = 1, 50 do ScrapeLogIndex(i) end
        end)
        
        vultureHooked = true
        print("|cff00ff00GRS:|r Vulture Protocol Active. Monitoring Default Guild Log.")
    else
        -- [DEBUG] Warn user if Vulture cannot attach (Frame not created yet)
        -- print("|cffFF0000GRS Warning:|r Vulture Protocol paused. Blizzard Log Frame not ready.")
    end
end

-- =========================================================================
-- 4. 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, -10); lbl:SetText(title)
    
    -- [HEADERS] Created once
    local hName = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    hName:SetText(NS.L["PLAYER"] or "Player")
    hName:SetPoint("TOPLEFT", 10, -35)
    hName:SetJustifyH("LEFT")
    f.headName = hName

    -- [REMOVED] Age Header

    local hAction = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    hAction:SetText(NS.L["CONTEXT"] or "Context")
    hAction:SetJustifyH("RIGHT")
    f.headAction = hAction
    
    local sf = CreateFrame("ScrollFrame", nil, f, "UIPanelScrollFrameTemplate")
    -- Pushed down slightly to accommodate headers
    sf:SetPoint("TOPLEFT", 10, -55); 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, 40) -- [STACKING] Increased height to allow 2 lines
    
    -- [COLUMN 1: ICON + NAME]
    -- Anchor: Top-Left
    r.icon = r:CreateTexture(nil, "ARTWORK")
    r.icon:SetSize(20, 20)
    r.icon:SetPoint("TOPLEFT", 4, -4) -- Strict TOP alignment
    r.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark") 
    
    r.name = r:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    r.name:SetPoint("TOPLEFT", r.icon, "TOPRIGHT", 4, 0) -- Align with Icon Top
    -- Right point is dynamic, handled in FillLogFrame
    r.name:SetJustifyH("LEFT")
    r.name:SetJustifyV("TOP") -- [ALIGNMENT FIX] Force top alignment
    r.name:SetWordWrap(false)

    -- [REMOVED] Time/Age Column
    
    -- [COLUMN 3: ACTION]
    r.details = r:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    -- Points handled dynamically
    r.details:SetJustifyH("RIGHT")
    r.details:SetJustifyV("TOP") -- [ALIGNMENT FIX] Force top alignment
    r.details:SetWordWrap(true) -- [STACKING] Enabled word wrap
    r.details:SetSpacing(2)
    
    r.line = r:CreateTexture(nil, "ARTWORK")
    r.line:SetHeight(1)
    r.line:SetColorTexture(0.2, 0.2, 0.2, 0.3)
    r.line:SetPoint("BOTTOMLEFT", 0, 0)
    r.line:SetPoint("BOTTOMRIGHT", 0, 0)
    
    return r
end

-- =========================================================================
-- 5. MAIN LOGIC
-- =========================================================================

function GR_Funcs.PopulateHistory(self)
    if not IsInGuild() then return end
    
    ActivateVulture()
    UpdateRosterCache()
    
    local gKey = GetGuildKey()
    if not gKey then 
        -- print("|cffFF0000GRS:|r Guild Key missing. Cannot load history.")
        return 
    end 
    
    if not GRS_HistoryDB then GRS_HistoryDB = {} end
    if not GRS_HistoryDB[gKey] then GRS_HistoryDB[gKey] = {} end
    local activeDB = GRS_HistoryDB[gKey]
    
    -- [12.7 LOGIC] Build Historical Class Cache
    local HistoryClassCache = {}
    for _, entry in ipairs(activeDB) do
        if entry.p1 and entry.cP1 then HistoryClassCache[entry.p1] = entry.cP1 end
        if entry.p2 and entry.cP2 then HistoryClassCache[entry.p2] = entry.cP2 end
    end

    local viewList = { joins = {}, ranks = {}, leaves = {} }
    
    -- [BLIND SCRAPE PROTOCOL]
    local reportedEvents = GetNumGuildEvents()
    local scrapeLimit = 250
    if reportedEvents > 0 and reportedEvents < 250 then
        scrapeLimit = reportedEvents
    end
    
    for i = 1, scrapeLimit do 
        local success = ScrapeLogIndex(i) 
        if not success and reportedEvents == 0 then
            break 
        end
    end
    
    for _, entry in ipairs(activeDB) do
        -- [NO FILTERS]
        -- [NO TIME FORMATTING]
        local sortVal = entry.ts or 0
        
        -- Identify the "Subject"
        local subjectName = nil
        if entry.type == "invite" or entry.type == "promote" or entry.type == "demote" or entry.type == "remove" then
            subjectName = entry.p2
        else
            subjectName = entry.p1
        end

        -- Smart Resolve
        local finalClass = nil
        if subjectName == entry.p1 then finalClass = entry.cP1 
        elseif subjectName == entry.p2 then finalClass = entry.cP2 end
        if not finalClass and subjectName then finalClass = ResolveClass(subjectName) end
        if not finalClass and subjectName and HistoryClassCache[subjectName] then finalClass = HistoryClassCache[subjectName] end

        local rowData = { 
            type = entry.type, p1 = entry.p1, p2 = entry.p2, 
            resolvedClass = finalClass, 
            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
    
    local function SortNewest(a, b) 
        local valA = a.val or 0
        local valB = b.val or 0
        
        -- [TACTICAL SORT FIX]
        -- Detect Raw 2-Digit Year timestamps (Low Value) vs Normalized 4-Digit Year timestamps (High Value).
        -- Raw Years (e.g. 25) generate timestamps around ~negative or low epoch (1925).
        -- Normalized Years (e.g. 2024) generate timestamps around ~1.7 Billion.
        -- We want Raw(New) > Normalized(Old). 
        -- Since 1925 < 2024, we must virtually boost the Raw values above 2 Billion for sorting.
        
        local cmpA = valA
        local cmpB = valB
        
        -- Threshold: 1.2 Billion (approx year 2008). 
        if cmpA < 1200000000 then cmpA = cmpA + 3000000000 end 
        if cmpB < 1200000000 then cmpB = cmpB + 3000000000 end
        
        return cmpA > cmpB
    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
    
    -- [DYNAMIC LAYOUT CALCULATION]
    local totalWidth = frame.content:GetWidth()
    local colNameWidth = totalWidth * 0.55 -- Increased Name Width (55%)
    -- No Age Column
    
    -- [HEADER ALIGNMENT]
    if frame.headName then frame.headName:SetWidth(colNameWidth); end
    
    if frame.headAction then
        frame.headAction:ClearAllPoints()
        frame.headAction:SetPoint("TOPLEFT", frame.headName, "TOPRIGHT", 5, 0)
        frame.headAction:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -30, -35)
    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(totalWidth)
        r:SetPoint("TOPLEFT", 0, y)
        r:Show()
        
        -- [DYNAMIC ROW ANCHORING]
        r.name:SetWidth(colNameWidth - 25)
        
        -- Removed date setup
        
        r.details:ClearAllPoints()
        r.details:SetPoint("TOPLEFT", r.name, "TOPRIGHT", 20, 0) -- Anchored to Name now
        r.details:SetPoint("TOPRIGHT", r, "TOPRIGHT", -5, -4) 
        
        local sName, details, rC, gC, bC = "Unknown", "", 1, 1, 1
        
        if entry.type == "invite" then 
            sName = entry.p2; -- Subject: Invitee
            details = "by "..(entry.p1 or "?"); rC,gC,bC = 0,1,0
        elseif entry.type == "join" then 
            sName = entry.p1; -- Subject: Joiner
            details = "Joined"; rC,gC,bC = 0,1,0
        elseif entry.type == "promote" then 
            sName = entry.p2; -- Subject: Promoted
            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; -- Subject: Demoted
            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; -- Subject: Kicked
            details = "Kicked by "..(entry.p1 or "?"); rC,gC,bC = 1,0,0
        elseif entry.type == "quit" then 
            sName = entry.p1; -- Subject: Leaver
            details = "Left Guild"; rC,gC,bC = 1,0.5,0
        end
        
        if not sName then sName = "Unknown" end
        
        local classFile = entry.resolvedClass

        if classFile and CLASS_ICON_TCOORDS[classFile] then
            r.icon:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes")
            r.icon:SetTexCoord(unpack(CLASS_ICON_TCOORDS[classFile]))
            local cObj = C_ClassColor.GetClassColor(classFile)
            if cObj then 
                r.icon:SetVertexColor(1, 1, 1)
                r.name:SetText(ColorText(sName, cObj.r, cObj.g, cObj.b))
            else
                r.name:SetText(ColorText(sName, 0.6, 0.6, 0.6))
            end
        else
            r.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
            r.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
            r.icon:SetVertexColor(1, 1, 1)
            r.name:SetText(ColorText(sName, 0.6, 0.6, 0.6))
        end
        
        r.details:SetText(details)
        r.details:SetTextColor(rC, gC, bC)
        
        y = y - 40 -- [STACKING] Adjusted Y-step for taller rows
    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, NS.L["INVITES"] or "Invites")
    self.histJoinFrame = fJoin
    
    local fRank = self.histRankFrame or CreateLogFrame(parent, NS.L["PROMOTIONS"] or "Promotions")
    self.histRankFrame = fRank
    
    local fLeave = self.histLeaveFrame or CreateLogFrame(parent, NS.L["DEPARTURES"] or "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
            ActivateVulture()
            QueryGuildEventLog() 
            C_Timer.After(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(NS.L["FORCE_REFRESH"] or "Force Refresh")
        btn:SetScript("OnClick", function(thisBtn) 
            local gKey = GetGuildKey()
            if gKey and GRS_HistoryDB and GRS_HistoryDB[gKey] then
                table.wipe(GRS_HistoryDB[gKey])
                print("|cff00ff00GRS:|r Recruitment History DB Cleared.")
            end
            
            if self.histJoinFrame then self:FillLogFrame(self.histJoinFrame, {}) end
            if self.histRankFrame then self:FillLogFrame(self.histRankFrame, {}) end
            if self.histLeaveFrame then self:FillLogFrame(self.histLeaveFrame, {}) end

            QueryGuildEventLog() 
            
            thisBtn:Disable()
            local countdown = 5
            thisBtn:SetText("Please wait ("..countdown..")")
            
            C_Timer.NewTicker(1, function(ticker)
                countdown = countdown - 1
                if countdown > 0 then
                    thisBtn:SetText("Please wait ("..countdown..")")
                else
                    thisBtn:SetText(NS.L["FORCE_REFRESH"] or "Force Refresh")
                    thisBtn:Enable()
                    ticker:Cancel()
                    self:PopulateHistory()
                end
            end, 5)
        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
    
    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 Vulture Protocol Active. Monitoring Communities Log.")
end