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

local COMM_PREFIX = "GRS_MAP"
local HS_PREFIX = "GRS_MAP_HS"
local HEARTBEAT_INTERVAL = 2.0
local HS_RETRY_INTERVAL = 3.0

function GR:CanEditMap()
    if not IsInGroup() then return true end
    if UnitIsGroupLeader("player") or UnitIsGroupAssistant("player") then return true end
    local name = UnitName("player"); if DB_Rmap_Tools.settings.strategists and DB_Rmap_Tools.settings.strategists[name] then return true end
    return false
end

local LibDeflate = LibStub:GetLibrary("LibDeflate")

function GR:CompressMapData(data)
    local str = ""
    for _, d in ipairs(data) do
        if d.isLabel then str = str .. string.format("T^%s^%.3f^%.3f^%.2f^%.2f^%.2f^%.2f^%d^%s^%s^%s;", d.text, d.normX, d.normY, d.scale, d.r, d.g, d.b, d.fontSize, tostring(d.isBold), tostring(d.isItalic), tostring(d.isUnderline)) else str = str .. string.format("I^%s^%s^%.3f^%.3f^%.2f^%.2f^%.2f;", d.type, d.path, d.normX, d.normY, d.scaleX, d.scaleY, d.rotation or 0) end
    end
    if LibDeflate then local compressed = LibDeflate:CompressDeflate(str); return LibDeflate:EncodeForWoWAddonChannel(compressed) end
    return str
end

function GR:DecompressMapData(str)
    if LibDeflate then local decoded = LibDeflate:DecodeForWoWAddonChannel(str); if decoded then local decompressed = LibDeflate:DecompressDeflate(decoded); if decompressed then str = decompressed end end end
    local t = {}
    for entry in string.gmatch(str, "([^;]+)") do
        local parts = { strsplit("^", entry) }
        if parts[1] == "T" then table.insert(t, { type="text", isLabel=true, text=parts[2], normX=tonumber(parts[3]), normY=tonumber(parts[4]), scale=tonumber(parts[5]), r=tonumber(parts[6]), g=tonumber(parts[7]), b=tonumber(parts[8]), fontSize=tonumber(parts[9]), isBold=(parts[10]=="true"), isItalic=(parts[11]=="true"), isUnderline=(parts[12]=="true"), isValid=true }) elseif parts[1] == "I" then table.insert(t, { type=parts[2], path=parts[3], normX=tonumber(parts[4]), normY=tonumber(parts[5]), scaleX=tonumber(parts[6]), scaleY=tonumber(parts[7]), rotation=tonumber(parts[8]), isValid=true }) end
    end
    return t
end

function GR:InitMapComms()
    if not C_ChatInfo.IsAddonMessagePrefixRegistered(COMM_PREFIX) then C_ChatInfo.RegisterAddonMessagePrefix(COMM_PREFIX) end
    if not C_ChatInfo.IsAddonMessagePrefixRegistered(HS_PREFIX) then C_ChatInfo.RegisterAddonMessagePrefix(HS_PREFIX) end
    local f = CreateFrame("Frame"); f:RegisterEvent("CHAT_MSG_ADDON"); f:SetScript("OnEvent", function(_, _, prefix, text, channel, sender)
        if sender == UnitName("player") then return end
        if prefix == HS_PREFIX then if text == "PING" then C_ChatInfo.SendAddonMessage(HS_PREFIX, "PONG", "WHISPER", sender) elseif text == "PONG" then if GR.HandshakeData and GR.HandshakeData.active then GR:RegisterHandshakeSuccess(sender) end end return end
        if prefix == COMM_PREFIX then
            local parts = { strsplit("~", text, 4) }; local cmd = parts[1]
            if cmd == "ENFORCE" then local raid, boss = parts[2], parts[3]; GR:RR_OpenMapWindow(); DB_Rmap_Tools.lastRaid = raid; DB_Rmap_Tools.lastBoss = boss; if GR.MapWindow:IsShown() then GR.MapWindow:GetScript("OnSizeChanged")(GR.MapWindow) end; GR:ShowMapToast("Strategist Enforced Map View", true); PlaySound(12867)
            elseif cmd == "LIVE" or cmd == "PUSH" then local raid, boss, payload = parts[2], parts[3], parts[4]; if not GR.RR then GR.RR = {} end; GR.RR.LiveBuffer = { raid = raid, boss = boss, data = GR:DecompressMapData(payload) }; if GR.MapWindow and GR.MapWindow.liveSlot then if GR.selectedSaveSlot == 11 then GR:RenderLiveBuffer() else if GR.MapWindow.liveSlot.anim then GR.MapWindow.liveSlot.anim:Play() end; GR:ShowMapToast("New Strategy Received", false) end end end
        end
    end)
end

function GR:BroadcastMap(mode)
    if not GR:CanEditMap() then return end
    local raid, boss = DB_Rmap_Tools.lastRaid or "Unknown", DB_Rmap_Tools.lastBoss or "Unknown"; local payload = GR:CompressMapData(DB_Rmap_Tools.canvasData); local msg = mode .. "~" .. raid .. "~" .. boss .. "~" .. payload
    if IsInGroup() then C_ChatInfo.SendAddonMessage(COMM_PREFIX, msg, IsInRaid() and "RAID" or "PARTY") end
end

function GR:StartHandshakeProcess()
    GR.HandshakeData = { active = true, roster = {}, count = 0, total = 0 }; local num = GetNumGroupMembers()
    if num > 0 then for i = 1, num do local unit = IsInRaid() and ("raid"..i) or ("party"..i); if not IsInRaid() and i == num then unit = "player" end; local name = GetUnitName(unit, true); if name and name ~= UnitName("player") then GR.HandshakeData.roster[name] = false end end end
    local total = 0; for _ in pairs(GR.HandshakeData.roster) do total = total + 1 end; GR.HandshakeData.total = total
    C_ChatInfo.SendAddonMessage(HS_PREFIX, "PING", IsInRaid() and "RAID" or "PARTY"); GR:ShowMapToast(string.format("Live Stream Handshake: 0/%d", total), false)
    if GR.HandshakeTicker then GR.HandshakeTicker:Cancel() end; GR.HandshakeTicker = C_Timer.NewTicker(HS_RETRY_INTERVAL, function()
        if not GR.HandshakeData.active then GR.HandshakeTicker:Cancel(); return end
        if GR.HandshakeData.count >= GR.HandshakeData.total then GR:ShowMapToast("Handshake Complete: All Players Synced", false); GR.HandshakeData.active = false; GR.HandshakeTicker:Cancel(); return end
        local missing = 0; for name, synced in pairs(GR.HandshakeData.roster) do if not synced then missing = missing + 1 end end
        if missing > 0 then C_ChatInfo.SendAddonMessage(HS_PREFIX, "PING", IsInRaid() and "RAID" or "PARTY"); GR:ShowMapToast(string.format("Handshaking... %d/%d", GR.HandshakeData.count, GR.HandshakeData.total), true) end
    end)
end

function GR:RegisterHandshakeSuccess(senderName)
    if not GR.HandshakeData.roster[senderName] then GR.HandshakeData.roster[senderName] = true; GR.HandshakeData.count = GR.HandshakeData.count + 1; local color = (GR.HandshakeData.count == GR.HandshakeData.total) and "|cff00ff00" or "|cffffd100"; GR:ShowMapToast(string.format("Live Stream Handshake: %s%d/%d|r", color, GR.HandshakeData.count, GR.HandshakeData.total), false) end
end

local function ApplyLiveButtonVisuals(btn)
    if not btn.goldenMask then local mask = btn:CreateTexture(nil, "BACKGROUND"); mask:SetDrawLayer("BACKGROUND", -1); mask:SetPoint("TOPLEFT", -2, 2); mask:SetPoint("BOTTOMRIGHT", 2, -2); mask:SetColorTexture(1, 0.82, 0, 1); btn.goldenMask = mask end
    btn.liveState = "OFF"; local function LerpColor(r1, g1, b1, r2, g2, b2, t) return r1 + (r2 - r1)*t, g1 + (g2 - g1)*t, b1 + (b2 - b1)*t end
    btn:SetScript("OnUpdate", function(self, elapsed)
        local time = GetTime(); local r, g, b
        if self.liveState == "ON" then local duration = 3.0; local progress = (math.sin(time * (2 * math.pi) / duration) + 1) / 2; r, g, b = LerpColor(0, 0.4, 0, 0, 1, 0, progress) else local duration = 5.0; local progress = (math.sin(time * (2 * math.pi) / duration) + 1) / 2; r, g, b = LerpColor(0.4, 0, 0, 0, 0, 0, progress) end
        self:SetBackdropColor(r, g, b, 1)
    end)
end

local hookInstalled = false
function GR:ApplyMapSessionLogic()
    if hookInstalled then return end; hookInstalled = true; GR:InitMapComms()
    hooksecurefunc(GR, "RR_OpenMapWindow", function()
        local f = GR.MapWindow; if not f then return end
        if not f.liveSlot then
            local b = CreateFrame("Button", nil, f.slotContainer, "BackdropTemplate"); b:SetSize(60, 24); b:SetPoint("RIGHT", f.saveSlots[1], "LEFT", -15, 0); b:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 1 }); b:SetBackdropBorderColor(1, 0.82, 0, 1)
            local fs = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); fs:SetPoint("CENTER"); fs:SetText("LIVE"); b:SetFontString(fs)
            local ag = b:CreateAnimationGroup(); local a1 = ag:CreateAnimation("Alpha"); a1:SetFromAlpha(1); a1:SetToAlpha(0.2); a1:SetDuration(0.5); a1:SetOrder(1); local a2 = ag:CreateAnimation("Alpha"); a2:SetFromAlpha(0.2); a2:SetToAlpha(1); a2:SetDuration(0.5); a2:SetOrder(2); ag:SetLooping("REPEAT"); b.anim = ag
            b:SetScript("OnClick", function() GR.selectedSaveSlot = 11; for i=1, 10 do f.saveSlots[i]:SetBackdropBorderColor(0.5,0.5,0.5,1) end; b:SetBackdropBorderColor(0, 1, 0, 1); b.anim:Stop(); b:SetAlpha(1); GR:RenderLiveBuffer(); f.btnSave:UpdateState() end)
            f.liveSlot = b; ApplyLiveButtonVisuals(f.liveSlot)
        end
        local btns = {f:GetChildren()}; for _, obj in ipairs(btns) do if obj and obj.GetText then local txt = obj:GetText()
                if txt == "Awareness Enforcer" then obj:SetScript("OnClick", function() if not GR:CanEditMap() then return end; local raid, boss = DB_Rmap_Tools.lastRaid or "Unknown", DB_Rmap_Tools.lastBoss or "Unknown"; C_ChatInfo.SendAddonMessage(COMM_PREFIX, "ENFORCE~" .. raid .. "~" .. boss, IsInRaid() and "RAID" or "PARTY"); GR:ShowMapToast("Forcing Map Open on Group...", false) end)
                elseif txt == "Push" then obj:SetScript("OnClick", function() GR:BroadcastMap("PUSH"); GR:ShowMapToast("Strategy Pushed", false) end)
                elseif txt == "Live" then obj:SetScript("OnClick", function() if GR.LiveActive then GR.LiveActive = false; obj:SetText("Live"); if f.liveSlot then f.liveSlot.liveState = "OFF" end; GR:ShowMapToast("Live Stream Stopped", true); if GR.LiveTicker then GR.LiveTicker:Cancel() end; if GR.HandshakeTicker then GR.HandshakeTicker:Cancel() end; GR.HandshakeData = nil else GR.LiveActive = true; obj:SetText("STOP"); if f.liveSlot then f.liveSlot.liveState = "ON" end; GR:StartHandshakeProcess(); GR.LiveTicker = C_Timer.NewTicker(HEARTBEAT_INTERVAL, function() GR:BroadcastMap("LIVE") end) end end)
                end
            end
        end; GR:ApplyMapPermissions()
    end)
    local originalSpawn = GR.SpawnIndependentIcon
    GR.SpawnIndependentIcon = function(self, data, x, y, isLoad)
        if isLoad then if GR.selectedSaveSlot == 11 or not GR:CanEditMap() then originalSpawn(self, data, x, y, true); local icon = GR.CanvasIcons[#GR.CanvasIcons]; if icon then icon:EnableMouse(false) end else originalSpawn(self, data, x, y, true) end return end
        if not GR:CanEditMap() then GR:ShowMapToast("Restricted: Strategist Access Required", true); PlaySound(846); return end; originalSpawn(self, data, x, y, false)
    end
end

function GR:RenderLiveBuffer()
    GR:WipeCanvasData()
    if GR.RR.LiveBuffer and GR.RR.LiveBuffer.data then
        DB_Rmap_Tools.canvasData = CopyTable(GR.RR.LiveBuffer.data)
        for _, toolData in ipairs(DB_Rmap_Tools.canvasData) do GR:SpawnIndependentIcon(toolData, nil, nil, true) end
        GR:ShowMapToast("Viewing Live Feed", false); if GR.MapWindow.liveSlot then GR.MapWindow.liveSlot.liveState = "ON" end
    else GR:ShowMapToast("No Live Data Received Yet", true); if GR.MapWindow.liveSlot then GR.MapWindow.liveSlot.liveState = "OFF" end end
end

function GR:ApplyMapPermissions()
    local f = GR.MapWindow; if not f then return end; local canEdit = GR:CanEditMap(); local btns = {f:GetChildren()}
    for _, obj in ipairs(btns) do if obj and obj.GetText then local txt = obj:GetText()
            if txt == "Awareness Enforcer" or txt == "Strategists" or txt == "Push" or txt == "Live" or txt == "Reset Save" or txt == "STOP" then if canEdit then obj:Show() else obj:Hide() end end
            if txt == "Save" then if GR.selectedSaveSlot == 11 then obj:SetScript("OnClick", function() GR:ShowMapToast("Select a slot 1-10 to save this stream.", false); GR.SavingLiveStream = true end) end end
        end
    end
end

GR:ApplyMapSessionLogic()