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

-- Import Constants
local COMM_PREFIX = GR.RR.CONST.COMM_PREFIX
local COMM_DELIM = GR.RR.CONST.COMM_DELIM
local MAX_COMM_CHUNK = GR.RR.CONST.MAX_COMM_CHUNK
local debugMode = false -- Set to true to enable GRS DEBUG/NET/QUEUE prints

-- =========================================================================
-- SOPHISTICATED SYNCHRONIZATION PROTOCOL (SMART DIFF)
-- =========================================================================
-- Flow:
-- 1. Client Login -> Sends S_ASK ("I am online, sync me")
-- 2. Peers -> Calculate Rank-Based Delay -> Authority replies first with S_OFFER (Manifest)
-- 3. Client -> Compares Manifest [ID:Revision] vs Local DB -> Sends S_FETCH (List of IDs needed)
-- 4. Authority -> Receives S_FETCH -> Sends full data via DIRECTED WHISPER (Bypassing Auth Checks)

local SYNC_OPS = {
    ASK = "S_ASK",      -- Request Manifest
    OFFER = "S_OFFER",  -- Broadcast Manifest [ID:Rev;ID:Rev]
    FETCH = "S_FETCH"   -- Request Specific Data [ID,ID,ID]
}

-- Local State
local activeSyncTimer = nil
local outgoingQueue = {}
local outgoingTicker = nil

-- =========================================================================
-- HELPER: REVISION CALCULATOR
-- =========================================================================

local function GetEventRevision(evt)
    if not evt then return 0 end
    local maxTS = 0
    if evt.roster then
        for _, roleList in pairs(evt.roster) do
            for _, member in ipairs(roleList) do
                if member.ts and member.ts > maxTS then
                    maxTS = member.ts
                end
            end
        end
    end
    return maxTS
end

local function GetMyGuildRankFactor()
    if not IsInGuild() then return 5.0 end
    -- Classic-safe: scan guild roster for our rank index
    local myName = UnitName("player")
    local rankIndex = 10
    local numMembers = GetNumGuildMembers()
    for i = 1, numMembers do
        local name, _, ri = GetGuildRosterInfo(i)
        if name and Ambiguate(name, "none") == myName then rankIndex = ri; break end
    end
    if rankIndex == 0 then return 0.1 end -- GM (Highest Priority)
    if CanEditOfficerNote() then return 1.0 end -- Officer (High Priority)
    return 3.0 -- Member (Low Priority)
end

-- =========================================================================
-- HANDLER: INITIATE SYNC (CLIENT SIDE)
-- =========================================================================

function GR:RR_RequestSync()
    if not IsInGuild() then return end
    if debugMode then print("|cff00ccffGRS DEBUG:|r Sending Sync Request (S_ASK)") end
    if C_ChatInfo and C_ChatInfo.SendAddonMessage then
        C_ChatInfo.SendAddonMessage(COMM_PREFIX, SYNC_OPS.ASK, "GUILD")
    elseif SendAddonMessage then
        SendAddonMessage(COMM_PREFIX, SYNC_OPS.ASK, "GUILD")
    end
end

-- =========================================================================
-- HANDLER: TRAFFIC CONTROLLER
-- =========================================================================

function GR:RR_HandleSyncComm(op, payload, sender)
    local senderName = Ambiguate(sender, "none")
    local myName = GR.RR.GetFullPlayerName()
    if senderName == myName then return end
    
    if op == SYNC_OPS.ASK then
        if debugMode then print(string.format("|cff00ccffGRS DEBUG:|r Received S_ASK from %s. Calculating offer delay...", senderName)) end
        GR:RR_AttemptAuthorityResponse(senderName)
    elseif op == SYNC_OPS.OFFER then
        if debugMode then print(string.format("|cff00ccffGRS DEBUG:|r Received S_OFFER from %s.", senderName)) end
        GR:RR_ProcessOffer(senderName, payload)
    elseif op == SYNC_OPS.FETCH then
        if debugMode then print(string.format("|cff00ccffGRS DEBUG:|r Received S_FETCH from %s.", senderName)) end
        GR:RR_FulfilRequest(senderName, payload)
    end
end

-- =========================================================================
-- LOGIC: AUTHORITY RESPONSE (SERVER SIDE)
-- =========================================================================

function GR:RR_AttemptAuthorityResponse(requestor)
    if activeSyncTimer then activeSyncTimer:Cancel() end
    local baseDelay = GetMyGuildRankFactor()
    local randomJitter = math.random() * 0.5
    local totalDelay = baseDelay + randomJitter
    
    if C_Timer and C_Timer.NewTimer then
        activeSyncTimer = C_Timer.NewTimer(totalDelay, function()
            GR:RR_SendOffer()
            activeSyncTimer = nil
        end)
    elseif NS and NS.NewTimer then
        activeSyncTimer = NS.NewTimer(totalDelay, function()
            GR:RR_SendOffer()
            activeSyncTimer = nil
        end)
    else
        local tf = CreateFrame("Frame"); local te = 0
        activeSyncTimer = { Cancel = function() tf:SetScript("OnUpdate", nil) end }
        tf:SetScript("OnUpdate", function(s, dt) te = te + dt; if te >= totalDelay then s:SetScript("OnUpdate", nil); GR:RR_SendOffer(); activeSyncTimer = nil end end)
    end
end

function GR:RR_SendOffer()
    if not GRS_RaidRosterDB or not GRS_RaidRosterDB.events then return end
    local manifest = {}
    for id, evt in pairs(GRS_RaidRosterDB.events) do
        if not (GR.RR.IsTombstoned and GR.RR.IsTombstoned(id)) then
            local rev = GetEventRevision(evt)
            table.insert(manifest, id .. ":" .. rev)
        end
    end
    local payload = table.concat(manifest, ";")
    if payload == "" then 
        if debugMode then print("|cff00ccffGRS DEBUG:|r Offer empty, nothing to send.") end
        return 
    end
    if debugMode then print(string.format("|cff00ccffGRS DEBUG:|r Sending S_OFFER with %d items.", #manifest)) end
    if C_ChatInfo and C_ChatInfo.SendAddonMessage then
        C_ChatInfo.SendAddonMessage(COMM_PREFIX, SYNC_OPS.OFFER .. COMM_DELIM .. payload, "GUILD")
    elseif SendAddonMessage then
        SendAddonMessage(COMM_PREFIX, SYNC_OPS.OFFER .. COMM_DELIM .. payload, "GUILD")
    end
end

-- =========================================================================
-- LOGIC: PROCESSING OFFER (CLIENT SIDE)
-- =========================================================================

function GR:RR_ProcessOffer(sender, payload)
    if not payload then return end
    local neededIDs = {}
    local myDB = GRS_RaidRosterDB.events or {}
    local senderIsOutdated = false
    
    for entry in string.gmatch(payload, "([^;]+)") do
        local id, remoteRevStr = strsplit(":", entry)
        local remoteRev = tonumber(remoteRevStr) or 0
        local localEvt = myDB[id]
        
        local needUpdate = false
        if not localEvt then
            if GR.RR.IsTombstoned and GR.RR.IsTombstoned(id) then
                senderIsOutdated = true
            else
                needUpdate = true
            end
        else
            local localRev = GetEventRevision(localEvt)
            if remoteRev > localRev then
                needUpdate = true
            elseif localRev > remoteRev then
                senderIsOutdated = true
            end
        end
        if needUpdate then 
            if debugMode then print(string.format("|cff00ccffGRS DEBUG:|r Need update for %s (Remote: %d, Local: %d)", id, remoteRev, GetEventRevision(localEvt))) end
            table.insert(neededIDs, id) 
        end
    end
    
    if activeSyncTimer and not senderIsOutdated then 
        activeSyncTimer:Cancel(); activeSyncTimer = nil 
    end
    
    if senderIsOutdated and not activeSyncTimer then
        if debugMode then print(string.format("|cff00ccffGRS DEBUG:|r Sender %s is outdated. Attempting response.", sender)) end
        GR:RR_AttemptAuthorityResponse(sender)
    end
    
    if #neededIDs > 0 then
        if debugMode then print(string.format("|cff00ccffGRS DEBUG:|r Sending S_FETCH for %d events.", #neededIDs)) end
        local reqPayload = table.concat(neededIDs, ",")
        if C_ChatInfo and C_ChatInfo.SendAddonMessage then
            C_ChatInfo.SendAddonMessage(COMM_PREFIX, SYNC_OPS.FETCH .. COMM_DELIM .. reqPayload, "GUILD")
        elseif SendAddonMessage then
            SendAddonMessage(COMM_PREFIX, SYNC_OPS.FETCH .. COMM_DELIM .. reqPayload, "GUILD")
        end
    else
        if debugMode then print("|cff00ccffGRS DEBUG:|r Offer processed. No updates needed.") end
    end
end

-- =========================================================================
-- LOGIC: FULFILL REQUEST (DIRECTED WHISPER) - THROTTLED
-- =========================================================================

local function ProcessOutgoingQueue()
    if #outgoingQueue == 0 then
        if outgoingTicker then
            outgoingTicker:Cancel()
            outgoingTicker = nil
        end
        return
    end

    -- Process one item per tick
    local job = table.remove(outgoingQueue, 1)
    if job then
        local evt = GRS_RaidRosterDB.events[job.id]
        if evt then
            if debugMode then print(string.format("|cff00ccffGRS NET:|r Sending Event %s to %s (Queue: %d)", job.id, job.target, #outgoingQueue)) end
            GR:RR_BroadcastEvent(evt, job.target)
        end
    end
end

function GR:RR_FulfilRequest(senderName, payload)
    if not payload then return end
    if debugMode then print(string.format("|cff00ccffGRS DEBUG:|r Fulfilling S_FETCH from %s via WHISPER (Queued).", senderName)) end
    
    local addedCount = 0
    -- Iterate requested IDs and queue them
    for id in string.gmatch(payload, "([^,]+)") do
        table.insert(outgoingQueue, { id = id, target = senderName })
        addedCount = addedCount + 1
    end
    
    if addedCount > 0 then
        if debugMode then print(string.format("|cff00ccffGRS QUEUE:|r Added %d items. Total Queue: %d", addedCount, #outgoingQueue)) end
        if not outgoingTicker then
            -- 0.5 seconds = 2 messages per second
            if C_Timer and C_Timer.NewTicker then
                outgoingTicker = C_Timer.NewTicker(0.5, ProcessOutgoingQueue)
            else
                local oqFrame = CreateFrame("Frame"); local oqEl = 0
                outgoingTicker = { Cancel = function() oqFrame:SetScript("OnUpdate", nil) end }
                oqFrame:SetScript("OnUpdate", function(s, dt) oqEl = oqEl + dt; if oqEl >= 0.5 then oqEl = 0; ProcessOutgoingQueue() end end)
            end
        end
    end
end