--## Author: DieLifeTW ## Version: 2.3.0
local KGL = {}

-- ==========================================
-- 🌍 核心變數與設定
-- ==========================================
KGL.Prefix = "WA-KeyStGrList"
KGL.PartyData = {} -- 隊伍鑰匙
KGL.GuildData = {} -- 公會/外部鑰匙
KGL.IsChallengeActive = false 
KGL.NeedUpdate = false 
KGL.NeedVisibilityUpdate = false 

if not C_ChatInfo.IsAddonMessagePrefixRegistered(KGL.Prefix) then
    C_ChatInfo.RegisterAddonMessagePrefix(KGL.Prefix)
end

-- ==========================================
-- 🚪 全版本 M+ 傳送門法術資料庫
-- ==========================================
local DungeonPorts = {
    [438] = 410080, [456] = 424142, [507] = 445424, 
    [2]   = 131204, 
    [165] = 159899, [166] = 159900, [168] = 159901, [169] = 159896, 
    [198] = 424163, [199] = 424153, [200] = 393764, [206] = 410078, [210] = 393766, [227] = 393768, [234] = 393768, [239] = 1254551,
    [244] = 424187, [245] = 410071, [247] = {467553, 467555}, [248] = 424167, [251] = 410074, [353] = {445418, 464256}, [369] = 373274, [370] = 373274, 
    [375] = 354464, [376] = 354462, [377] = 354463, [378] = 354465, [379] = 354468, [380] = 354466, [381] = 354469, [382] = 354467, [391] = 367416, [392] = 367416, 
    [399] = 393256, [400] = 393262, [401] = 393279, [402] = 393273, [403] = 393222, [404] = 393276, [405] = 393267, [406] = 393283, [463] = 424197, [464] = 424197, 
    [499] = 445444, [500] = 445443, [501] = 445269, [502] = 445416, [503] = 445417, [504] = 445441, [505] = 445414, [506] = 445440, [525] = 1216786, [542] = 1237215, 
    [548] = 445424, [476] = 464122, 
    [9001] = 1283720, [9002] = 1283721, [9003] = 1283722, [9004] = 1283723, [9005] = 464120, 
    [557] = 1254400, [558] = 1254572, [559] = 1254563, [560] = 1254559,
    [161] = 159898, [556] = 1254555, 
}

-- 建立法術 ID 到 Map ID 的反向查詢表，供傳送通報使用
local SpellToMapID = {}
for mapID, port in pairs(DungeonPorts) do
    if type(port) == "table" then
        SpellToMapID[port[1]] = mapID
        SpellToMapID[port[2]] = mapID
    else
        SpellToMapID[port] = mapID
    end
end

local function GetFactionPortID(mapID)
    local port = DungeonPorts[mapID]
    if type(port) == "table" then return UnitFactionGroup("player") == "Horde" and port[2] or port[1] end
    return port
end

local function GetPlayerFullName(unit)
    local name, realm = UnitName(unit)
    if not name then return nil end
    if not realm or realm == "" then realm = GetRealmName() end
    return name .. "-" .. realm
end

-- ==========================================
-- 🎨 方案 A: 隊伍極簡 HUD 介面 (自動顯示/隱藏)
-- ==========================================
local MainFrame = CreateFrame("Frame", "KGL_MainFrame", PVEFrame, "BackdropTemplate")
MainFrame:SetSize(260, 20)
MainFrame:SetPoint("BOTTOMLEFT", PVEFrame, "BOTTOMRIGHT", 12, -12)
--[[MainFrame:SetMovable(true); MainFrame:RegisterForDrag("LeftButton")
MainFrame:SetScript("OnDragStart", MainFrame.StartMoving)
MainFrame:SetScript("OnDragStop", function(self)
    self:StopMovingOrSizing()
    local point, _, relativePoint, x, y = self:GetPoint()
    KGL_SavedVars.point = {point, relativePoint, x, y}
end)

MainFrame.bg = MainFrame:CreateTexture(nil, "BACKGROUND")
MainFrame.bg:SetAllPoints()
MainFrame.bg:SetColorTexture(0, 0.4, 0.8, 0.7) 

MainFrame.title = MainFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
MainFrame.title:SetPoint("CENTER", MainFrame, "CENTER", 0, 0)
MainFrame.title:SetText(L.TITLE or "隊伍鑰匙 (拖曳此處)")]]

local Rows = {}
for i = 1, 5 do
    local row = CreateFrame("Button", "KGL_Row"..i, MainFrame, "SecureActionButtonTemplate, BackdropTemplate")
    row:RegisterForClicks("LeftButtonDown"); row:SetAttribute("type", "spell") 
    
    local bg = row:CreateTexture(nil, "BACKGROUND"); bg:SetAllPoints(); bg:SetColorTexture(0, 0, 0, 0) 
    local hl = row:CreateTexture(nil, "HIGHLIGHT"); hl:SetAllPoints(); hl:SetColorTexture(1, 1, 1, 0.1)

    row.icon = row:CreateTexture(nil, "ARTWORK")
    row.icon:SetPoint("LEFT", row, "LEFT", 0, 0)

    row.level = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
    row.level:SetPoint("LEFT", row.icon, "RIGHT", 3, 0)

    row.name = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.name:SetPoint("LEFT", row.icon, "RIGHT", 30, 0)
    row.name:SetJustifyH("LEFT")
    row.name:SetWordWrap(false)

    row.noPort = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    row.noPort:SetPoint("TOP", row.level, "BOTTOM", 0, -1) 
    row.noPort:SetJustifyH("CENTER")
    row.noPort:SetTextColor(0, 1, 0) 
    row.noPort:SetText("未开门") 

    row.mapName = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    row.mapName:SetPoint("LEFT", row.name, "RIGHT", 3, 0) 
    row.mapName:SetJustifyH("LEFT")
    row.mapName:SetWordWrap(false) 

    row:Hide(); Rows[i] = row
end

local function UpdateVisibilityCondition()
    if InCombatLockdown() then
        KGL.NeedVisibilityUpdate = true
        return
    end
    KGL.NeedVisibilityUpdate = false 

    local condition = "[combat] hide; [group:raid] hide; "
    if KGL.IsChallengeActive then condition = condition .. "hide;" else condition = condition .. "[group:party] show; hide;" end
    RegisterStateDriver(MainFrame, "visibility", condition)
end

-- ==========================================
-- 🔄 靜態同步外部插件資料 (Details! / Astral Keys)
-- ==========================================
local function SyncExternalKeystones()
    if not IsInGroup() then return end
    
    for i = 1, GetNumSubgroupMembers() do
        local unit = "party" .. i
        local name, realm = UnitName(unit)
        if name then
            local fullName = name .. "-" .. (realm and realm ~= "" and realm or GetRealmName())
            
            -- 1. LibOpenRaid (Details!)
            local openRaidLib = _G.LibStub and _G.LibStub("LibOpenRaid-1.0", true)
            if openRaidLib and openRaidLib.GetKeystoneInfo then
                local mapID, level = openRaidLib.GetKeystoneInfo(unit)
                if mapID and level and mapID > 0 then
                    -- 🌟 統一使用純名字作為唯一 Key，杜絕分身
                    KGL.PartyData[name] = { unit = unit, name = name, mapID = mapID, level = level }
                end
            end
            
            -- 2. Astral Keys
            if _G.AstralCharacters and _G.AstralCharacters[fullName] then
                local data = _G.AstralCharacters[fullName]
                local mapID = data.mapId or data.map
                local level = data.key_level or data.level
                if mapID and level and mapID > 0 and level > 0 then
                    KGL.PartyData[name] = { unit = unit, name = name, mapID = mapID, level = level }
                end
            end
        end
    end
end

local function RenderPartyUI()
    if InCombatLockdown() then
        KGL.NeedUpdate = true
        return
    end
    KGL.NeedUpdate = false

    if not IsInGroup() or IsInRaid() then return end
    
    -- 主動從隊友的本機暫存抓資料 (純讀取，不發送網路請求)
    SyncExternalKeystones()

    local list = {}
    for keyName, data in pairs(KGL.PartyData) do
        -- 🌟 更安全的判斷法，確保跨伺服器也不會被誤刪
        local inParty = false
        if UnitIsUnit("player", data.name) then
            inParty = true
        else
            for i = 1, GetNumSubgroupMembers() do
                if UnitName("party"..i) == data.name then
                    inParty = true; break
                end
            end
        end

        if inParty then 
            table.insert(list, data) 
        else 
            KGL.PartyData[keyName] = nil 
        end
    end
    table.sort(list, function(a, b) return a.level > b.level end)

    for i = 1, 5 do
        local row = Rows[i]
        local data = list[i]
        if data and data.mapID and data.level > 0 then
            local mapName, _, _, mapTexture = C_ChallengeMode.GetMapUIInfo(data.mapID)
            local portID = GetFactionPortID(data.mapID)
            row.icon:SetTexture(mapTexture or "Interface\\Icons\\inv_relics_hourglass_02")
            --row.level:SetText("[" .. data.level .. "]")
            row.level:SetText(data.level)
            local _, class = UnitClass(data.unit)
            local classColor = class and RAID_CLASS_COLORS[class] or NORMAL_FONT_COLOR
            row.name:SetText(data.name); row.name:SetTextColor(classColor.r, classColor.g, classColor.b)
            row.mapName:SetText(mapName or "")

            if portID and IsPlayerSpell(portID) then
                row.noPort:Hide(); row:SetAttribute("spell", portID) 
            else
                row.noPort:Show(); row:SetAttribute("spell", nil) 
            end
            row:Show()
        else
            row:Hide()
        end
    end
end

-- ==========================================
-- ⚙️ 應用外觀與鎖定設定
-- ==========================================
KGL.ApplySettings = function()
    local KGL_iconSize = 22  --KGL_iconSize or 
    local KGL_fontSize = 16  --KGL_fontSize or 
    --[[if KGL_SavedVars.locked == nil then KGL_SavedVars.locked = false end

    if KGL_SavedVars.locked then
        MainFrame:EnableMouse(false)
        MainFrame.bg:Hide()
        MainFrame.title:Hide()
    else
        MainFrame:EnableMouse(true)
        MainFrame.bg:Show()
        MainFrame.title:Show()
    end]]

    local rowHeight = KGL_iconSize + 6
    local frameWidth = math.max(260, KGL_iconSize + (KGL_fontSize * 16))
    MainFrame:SetSize(frameWidth, 20)

    for i = 1, 5 do
        local row = Rows[i]
        row:SetSize(frameWidth, rowHeight)
        row:ClearAllPoints()
        row:SetPoint("BOTTOMLEFT", MainFrame, "TOPLEFT", 0, (i-1) * rowHeight)
        
        row.icon:SetSize(KGL_iconSize, KGL_iconSize)
        
        local font, _, flags = row.name:GetFont()
        if not font then font = STANDARD_TEXT_FONT or "Fonts\\bKAI00M.ttf" end
        
        row.name:SetFont(font, KGL_fontSize, flags)
        row.mapName:SetFont(font, KGL_fontSize - 1, flags)
        row.level:SetFont(font, KGL_fontSize + 2, "OUTLINE")
        row.noPort:SetFont(font, 12, "OUTLINE") 
    end
end

-- ==========================================
-- 🖥️ 獨立公會與好友查詢面板 (/kgl)
-- ==========================================
local GuildFrame = CreateFrame("Frame", "KGL_GuildFrame", UIParent, "BasicFrameTemplateWithInset")
GuildFrame:SetSize(350, 450); GuildFrame:SetPoint("CENTER"); GuildFrame:Hide()
GuildFrame:SetMovable(true); GuildFrame:EnableMouse(true); GuildFrame:RegisterForDrag("LeftButton")
GuildFrame:SetScript("OnDragStart", GuildFrame.StartMoving); GuildFrame:SetScript("OnDragStop", GuildFrame.StopMovingOrSizing)
GuildFrame.title = GuildFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
GuildFrame.title:SetPoint("CENTER", GuildFrame.TitleBg, "CENTER", 0, 0)
GuildFrame.title:SetText("队伍钥匙" .. " - 工会和好友")

local scrollFrame = CreateFrame("ScrollFrame", nil, GuildFrame, "UIPanelScrollFrameTemplate")
scrollFrame:SetPoint("TOPLEFT", 10, -35); scrollFrame:SetPoint("BOTTOMRIGHT", -30, 10)
local scrollChild = CreateFrame("Frame"); scrollChild:SetSize(300, 10); scrollFrame:SetScrollChild(scrollChild)

GuildFrame.rows = {}

local function RenderGuildUI()
    local list = {}
    for _, data in pairs(KGL.PartyData) do table.insert(list, data) end
    for _, data in pairs(KGL.GuildData) do
        local isDuplicate = false
        for _, p in ipairs(list) do if p.name == data.name then isDuplicate = true; break end end
        if not isDuplicate then table.insert(list, data) end
    end
    if _G.AstralCharacters then
        for fullName, data in pairs(_G.AstralCharacters) do
            local mapID = data.mapId or data.map
            local level = data.key_level or data.level
            if mapID and level and mapID > 0 and level > 0 then
                local charName = fullName:match("(.+)-") or fullName
                local isDuplicate = false
                for _, p in ipairs(list) do if p.name == charName then isDuplicate = true; break end end
                if not isDuplicate then
                    table.insert(list, { name = charName, mapID = mapID, level = level, source = "Astral" })
                end
            end
        end
    end

    table.sort(list, function(a, b) return a.level > b.level end)

    local yOff = 0
    for i, data in ipairs(list) do
        local row = GuildFrame.rows[i]
        if not row then
            row = CreateFrame("Frame", nil, scrollChild)
            row:SetSize(300, 30)
            row.bg = row:CreateTexture(nil, "BACKGROUND"); row.bg:SetAllPoints(); row.bg:SetColorTexture(1, 1, 1, i%2==0 and 0.05 or 0)
            row.icon = row:CreateTexture(nil, "ARTWORK"); row.icon:SetSize(24, 24); row.icon:SetPoint("LEFT", 5, 0)
            row.level = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge"); row.level:SetPoint("CENTER", row.icon, "CENTER", 0, 0)
            row.name = row:CreateFontString(nil, "OVERLAY", "GameFontNormal"); row.name:SetPoint("LEFT", row.icon, "RIGHT", 10, 0); row.name:SetWidth(100); row.name:SetJustifyH("LEFT")
            row.mapName = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); row.mapName:SetPoint("LEFT", row.name, "RIGHT", 5, 0); row.mapName:SetWidth(140); row.mapName:SetJustifyH("LEFT")
            GuildFrame.rows[i] = row
        end

        local mapName, _, _, mapTexture = C_ChallengeMode.GetMapUIInfo(data.mapID)
        row.icon:SetTexture(mapTexture or "Interface\\Icons\\inv_relics_hourglass_02")
        row.level:SetText(data.level)
        row.name:SetText(data.name)
        row.mapName:SetText(mapName or "未知地城")
        
        row:SetPoint("TOPLEFT", 0, yOff)
        row:Show(); yOff = yOff - 30
    end
    for i = #list + 1, #GuildFrame.rows do GuildFrame.rows[i]:Hide() end
end

GuildFrame:SetScript("OnShow", function()
    KGL.GuildData = {} 
    if IsInGuild() then C_ChatInfo.SendAddonMessage(KGL.Prefix, "KSGL:Request:0", "GUILD") end
    RenderGuildUI()
end)

SLASH_KGL1 = "/kgl"
SlashCmdList["KGL"] = function()
    if GuildFrame:IsShown() then GuildFrame:Hide() else GuildFrame:Show() end
end

-- ==========================================
-- 🔄 核心鑰匙更新與廣播
-- ==========================================
local function UpdateMyKeystone()
    local mapID = C_MythicPlus.GetOwnedKeystoneChallengeMapID()
    local level = C_MythicPlus.GetOwnedKeystoneLevel()
    
    if mapID and level then
        local myName = UnitName("player")
        -- 🌟 統一使用純名字，杜絕重複
        KGL.PartyData[myName] = { unit = "player", name = myName, mapID = mapID, level = level }
        local msg = "KSGL:Send:" .. mapID .. ":" .. level .. ":0:0"
        if IsInGroup() then C_ChatInfo.SendAddonMessage(KGL.Prefix, msg, "PARTY") end
        if IsInGuild() then C_ChatInfo.SendAddonMessage(KGL.Prefix, msg, "GUILD") end
    end
    RenderPartyUI()
    if GuildFrame:IsShown() then RenderGuildUI() end
end

-- ==========================================
-- 🔗 跨插件通訊橋樑
-- ==========================================
local openRaidLib = _G.LibStub and _G.LibStub("LibOpenRaid-1.0", true)
if openRaidLib then
    local function OnKeystoneUpdate(...)
        local arg1, arg2, arg3 = ...
        local unitName, keystoneInfo
        
        if type(arg1) == "string" and arg1 == "KeystoneUpdate" then
            unitName, keystoneInfo = arg2, arg3
        else
            unitName, keystoneInfo = arg1, arg2
        end

        if type(unitName) ~= "string" or type(keystoneInfo) ~= "table" then return end
        
        local charName = unitName:match("(.+)-") or unitName
        local mapID = keystoneInfo.challengeMapID
        local level = keystoneInfo.level
        
        if mapID and level and mapID > 0 and level > 0 then
            KGL.PartyData[charName] = { unit = charName, name = charName, mapID = mapID, level = level }
            RenderPartyUI()
            if GuildFrame:IsShown() then RenderGuildUI() end
        end
    end
    openRaidLib.RegisterCallback(KGL, "KeystoneUpdate", OnKeystoneUpdate)
end

local libKeystone = _G.LibStub and _G.LibStub("LibKeystone", true)
if libKeystone then
    local function OnLibKeystoneUpdate(keyLevel, keyMap, playerRating, playerName, channel)
        if channel == "PARTY" then
            local charName = playerName:match("(.+)-") or playerName
            if keyMap and keyLevel and keyMap > 0 and keyLevel > 0 then
                KGL.PartyData[charName] = { unit = charName, name = charName, mapID = keyMap, level = keyLevel }
                KGL.NeedUpdate = true
                if not InCombatLockdown() then RenderPartyUI() end
                if GuildFrame:IsShown() then RenderGuildUI() end
            end
        end
    end
    libKeystone.Register(KGL, OnLibKeystoneUpdate)
end

-- ==========================================
-- 📡 遊戲事件監聽器
-- ==========================================
local EventFrame = CreateFrame("Frame")
EventFrame:RegisterEvent("PLAYER_LOGIN")
EventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")

EventFrame:RegisterEvent("CHAT_MSG_ADDON")
EventFrame:RegisterEvent("CHALLENGE_MODE_START")
EventFrame:RegisterEvent("CHALLENGE_MODE_COMPLETED")
EventFrame:RegisterEvent("CHALLENGE_MODE_RESET")
EventFrame:RegisterEvent("PLAYER_REGEN_ENABLED") 
EventFrame:RegisterEvent("BAG_UPDATE_DELAYED") 
-- 🌟 [新增] 監聽法術施放成功事件 (用於傳送門通報)
EventFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")

EventFrame:SetScript("OnEvent", function(self, event, ...)
    if event == "PLAYER_LOGIN" then
        --KGL_SavedVars = KGL_SavedVars or {}
        --if KGL_SavedVars.point then 
            --MainFrame:ClearAllPoints()
            --MainFrame:SetPoint(KGL_SavedVars.point[1], UIParent, KGL_SavedVars.point[2], KGL_SavedVars.point[3], KGL_SavedVars.point[4]) 
        --end
        --if KGL_SavedVars.autoAnnounceReroll == nil then KGL_SavedVars.autoAnnounceReroll = true end -- 新增的預設值
        --if KGL_SavedVars.autoAnnouncePortal == nil then KGL_SavedVars.autoAnnouncePortal = true end -- 🌟 新增的傳送通報預設值
        KGL.ApplySettings()
        
        -- 🌟 讓系統允許 KGL 接收 AstralKeys 的廣播
        if not C_ChatInfo.IsAddonMessagePrefixRegistered("AstralKeys") then
            C_ChatInfo.RegisterAddonMessagePrefix("AstralKeys")
        end

        
        --[[local OptionsPanel = CreateFrame("Frame", "KGL_OptionsPanel")
        OptionsPanel.name = "KeystoneGroupList" 
        local title = OptionsPanel:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
        title:SetPoint("TOPLEFT", 16, -16)
        title:SetText("KeystoneGroupList") 

        local lockCheck = CreateFrame("CheckButton", nil, OptionsPanel, "InterfaceOptionsCheckButtonTemplate")
        lockCheck:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -20)
        lockCheck.Text:SetText(L.LOCK_FRAME or "鎖定主面板位置")
        lockCheck:SetChecked(KGL_SavedVars.locked)
        lockCheck:SetScript("OnClick", function(self)
            KGL_SavedVars.locked = self:GetChecked()
            KGL.ApplySettings()
        end)

        local iconSlider = CreateFrame("Slider", nil, OptionsPanel, "OptionsSliderTemplate")
        iconSlider:SetPoint("TOPLEFT", lockCheck, "BOTTOMLEFT", 0, -30)
        iconSlider:SetMinMaxValues(16, 48)
        iconSlider:SetValueStep(1)
        iconSlider:SetObeyStepOnDrag(true)
        iconSlider:SetValue(KGL_iconSize or 28)
        iconSlider.Text:SetText((L.ICON_SIZE or "圖示大小") .. " (" .. iconSlider:GetValue() .. ")")
        iconSlider.Low:SetText("16"); iconSlider.High:SetText("48")
        iconSlider:SetScript("OnValueChanged", function(self, value)
            KGL_iconSize = value
            self.Text:SetText((L.ICON_SIZE or "圖示大小") .. " (" .. value .. ")")
            KGL.ApplySettings()
        end)

        local fontSlider = CreateFrame("Slider", nil, OptionsPanel, "OptionsSliderTemplate")
        fontSlider:SetPoint("TOPLEFT", iconSlider, "BOTTOMLEFT", 0, -40)
        fontSlider:SetMinMaxValues(8, 24)
        fontSlider:SetValueStep(1)
        fontSlider:SetObeyStepOnDrag(true)
        fontSlider:SetValue(KGL_fontSize or 12)
        fontSlider.Text:SetText((L.FONT_SIZE or "字體大小") .. " (" .. fontSlider:GetValue() .. ")")
        fontSlider.Low:SetText("8"); fontSlider.High:SetText("24")
        fontSlider:SetScript("OnValueChanged", function(self, value)
            KGL_fontSize = value
            self.Text:SetText((L.FONT_SIZE or "字體大小") .. " (" .. value .. ")")
            KGL.ApplySettings()
        end)

        -- 新增的換 Key 選項面板
        local rerollCheck = CreateFrame("CheckButton", nil, OptionsPanel, "InterfaceOptionsCheckButtonTemplate")
        rerollCheck:SetPoint("TOPLEFT", fontSlider, "BOTTOMLEFT", 0, -30)
        rerollCheck.Text:SetText(L.OPT_AUTO_ANNOUNCE or "限時通關後自動提醒換 Key")
        rerollCheck:SetChecked(KGL_SavedVars.autoAnnounceReroll)
        rerollCheck:SetScript("OnClick", function(self)
            KGL_SavedVars.autoAnnounceReroll = self:GetChecked()
        end)

        -- 🌟 新增的傳送通報選項面板
        local portalCheck = CreateFrame("CheckButton", nil, OptionsPanel, "InterfaceOptionsCheckButtonTemplate")
        portalCheck:SetPoint("TOPLEFT", rerollCheck, "BOTTOMLEFT", 0, -10)
        portalCheck.Text:SetText(L.OPT_AUTO_PORTAL or "啟用傳送法術通報")
        portalCheck:SetChecked(KGL_SavedVars.autoAnnouncePortal)
        portalCheck:SetScript("OnClick", function(self)
            KGL_SavedVars.autoAnnouncePortal = self:GetChecked()
        end)

        if Settings and Settings.RegisterCanvasLayoutCategory then
            local category = Settings.RegisterCanvasLayoutCategory(OptionsPanel, OptionsPanel.name)
            Settings.RegisterAddOnCategory(category)
        else
            InterfaceOptions_AddCategory(OptionsPanel)
        end
        UpdateVisibilityCondition()]]

    elseif event == "BAG_UPDATE_DELAYED" then
        if IsInGroup() and not IsInRaid() then UpdateMyKeystone() end

    elseif event == "GROUP_ROSTER_UPDATE" then
        UpdateVisibilityCondition()
        if IsInGroup() and not IsInRaid() then
            C_Timer.After(1, function()
                UpdateMyKeystone()
                C_ChatInfo.SendAddonMessage(KGL.Prefix, "KSGL:Request:0", "PARTY")
                if openRaidLib and openRaidLib.RequestKeystoneDataFromParty then openRaidLib.RequestKeystoneDataFromParty() end
                -- 🌟 安全的觸發廣播要求 (不會造成迴圈)
                if libKeystone then libKeystone.Request("PARTY") end
            end)
        else
            KGL.PartyData = {} 
            RenderPartyUI()
        end

    elseif event == "CHAT_MSG_ADDON" then
        local prefix, text, channel, sender = ...
        
        -- [KGL 原生通訊攔截]
        if prefix == KGL.Prefix and sender ~= GetPlayerFullName("player") then
            local msgType = text:match("KSGL:(%a+):")
            if msgType == "Send" then
                local mapID = tonumber(text:match("KSGL:.-:(%d+):")); local level = tonumber(text:match("KSGL:.-:%d+:(%d+):"))
                local senderName = sender:match("(.+)-") or sender
                if mapID and level then
                    local data = { unit = senderName, name = senderName, mapID = mapID, level = level }
                    if channel == "PARTY" then
                        KGL.PartyData[senderName] = data
                        RenderPartyUI()
                    elseif channel == "GUILD" then
                        KGL.GuildData[senderName] = data
                        if GuildFrame:IsShown() then RenderGuildUI() end
                    end
                end
            elseif msgType == "Request" then
                UpdateMyKeystone()
            end
            
        -- 🌟 [新增] 跨界通訊：Astral Keys 封包攔截與解析
        elseif prefix == "AstralKeys" and sender ~= GetPlayerFullName("player") then
            local msgType, content = text:match("^(%w+)%s+(.+)")
            if msgType and (string.sub(msgType, 1, 7) == "updateV" or string.sub(msgType, 1, 4) == "sync") then
                -- 用底線 "_" 切割每個玩家的資料
                for chunk in content:gmatch("[^_]+") do
                    -- 抓取：名字、職業(略過)、副本ID、層數
                    local fullName, mapID, level = chunk:match("^([^:]+):[^:]+:(%d+):(%d+)")
                    if fullName and mapID and level then
                        local charName = fullName:match("(.+)-") or fullName
                        mapID = tonumber(mapID)
                        level = tonumber(level)
                        
                        if mapID and level and mapID > 0 and level > 0 then
                            local data = { unit = charName, name = charName, mapID = mapID, level = level, source = "Astral" }
                            if channel == "PARTY" or channel == "RAID" then
                                KGL.PartyData[charName] = data
                                KGL.NeedUpdate = true
                                if not InCombatLockdown() then RenderPartyUI() end
                            elseif channel == "GUILD" then
                                KGL.GuildData[charName] = data
                                if GuildFrame:IsShown() then RenderGuildUI() end
                            end
                        end
                    end
                end
            end
        end

    --[[ 🌟 [新增] 傳送門施放通報邏輯
    elseif event == "UNIT_SPELLCAST_SUCCEEDED" then
        local unit, _, spellID = ...
        if unit == "player" and KGL_SavedVars.autoAnnouncePortal then
            local mapID = SpellToMapID[spellID]
            if mapID and IsInGroup() then
                local mapName = C_ChallengeMode.GetMapUIInfo(mapID) or "未知地城"
                
                -- 取得法術超連結 (兼容新舊 API)
                local spellLink
                if C_Spell and C_Spell.GetSpellLink then
                    spellLink = C_Spell.GetSpellLink(spellID)
                elseif GetSpellLink then
                    spellLink = GetSpellLink(spellID)
                end
                
                -- 若抓不到連結，給個純文字備案
                if not spellLink then
                    local spellInfo = C_Spell and C_Spell.GetSpellInfo and C_Spell.GetSpellInfo(spellID)
                    local name = spellInfo and spellInfo.name or (GetSpellInfo and GetSpellInfo(spellID)) or "傳送門"
                    spellLink = "[" .. name .. "]"
                end

                -- 組合並發送通報
                local msg = string.format(L.PORTAL_ANNOUNCE or "正在詠唱{rt1}%s{rt1}，穿越時空前往{rt8}%s{rt8}", spellLink, mapName)
                SendChatMessage(msg, "PARTY")
            end
        end]]

    elseif event == "CHALLENGE_MODE_START" then
        KGL.IsChallengeActive = true; UpdateVisibilityCondition()
    elseif event == "CHALLENGE_MODE_COMPLETED" or event == "CHALLENGE_MODE_RESET" then
        KGL.IsChallengeActive = false; UpdateVisibilityCondition()
        C_Timer.After(2, UpdateMyKeystone)
        
    elseif event == "PLAYER_REGEN_ENABLED" then
        if KGL.NeedVisibilityUpdate then UpdateVisibilityCondition() end
        if KGL.NeedUpdate then RenderPartyUI() end
    end
end)

-- ==========================================
-- 🚪 附加模組：M+ 介面地城傳送門按鈕 (BigWigs 排版風格)
--[[ ==========================================
local PortalEventFrame = CreateFrame("Frame")
PortalEventFrame:RegisterEvent("ADDON_LOADED")

local function InitKGLPortals()
    if not _G.ChallengesFrame then return end
    hooksecurefunc(_G.ChallengesFrame, "Update", function(self)
        if not self.DungeonIcons then return end
        for _, icon in ipairs(self.DungeonIcons) do
            if not icon.kglHooked then
                icon:HookScript("OnEnter", function(self)
                    local challengeModeID = self.mapID
                    if not challengeModeID then return end

                    local portID = GetFactionPortID(challengeModeID)
                    GameTooltip:AddLine("|cffffff00KGL" .. (L.PORTAL_PREFIX or "：傳送") .. "|r")

                    if portID then
                        local spellInfo = C_Spell and C_Spell.GetSpellInfo and C_Spell.GetSpellInfo(portID)
                        local name = spellInfo and spellInfo.name or (GetSpellInfo and GetSpellInfo(portID))
                        local tex = spellInfo and spellInfo.iconID or (GetSpellTexture and GetSpellTexture(portID))

                        if name then
                            local iconStr = tex and ("|T" .. tex .. ":16|t ") or ""
                            GameTooltip:AddLine(iconStr .. "|cff20ff20" .. name .. "|r")

                            if IsPlayerSpell(portID) then
                                local start, duration = 0, 0
                                if C_Spell and C_Spell.GetSpellCooldown then
                                    local cdInfo = C_Spell.GetSpellCooldown(portID)
                                    if cdInfo then start = cdInfo.startTime; duration = cdInfo.duration end
                                elseif GetSpellCooldown then
                                    start, duration = GetSpellCooldown(portID)
                                end

                                if start and start > 0 and duration > 0 then
                                    local cdLeft = (start + duration) - GetTime()
                                    local hours = math.floor(cdLeft / 3600)
                                    local minutes = math.floor((cdLeft % 3600) / 60)
                                    local timeStr = string.format("%02d:%02d", hours, minutes)
                                    GameTooltip:AddLine("|cffff0000" .. string.format(L.CD_REMAINING or "冷卻中 (%s)", timeStr) .. "|r")
                                else
                                    GameTooltip:AddLine("|cff20ff20" .. (L.CLICK_TO_PORT or "點擊傳送至此") .. "|r")
                                end
                            else
                                GameTooltip:AddLine("|cff999999" .. (L.NOT_LEARNED or "無法傳送：法術尚未學會。") .. "|r")
                            end
                        else
                            GameTooltip:AddLine("|cff999999無效的法術 ID: " .. tostring(portID) .. "|r")
                        end
                    else
                        GameTooltip:AddLine("|cff999999未知地城 MapID: " .. tostring(challengeModeID) .. "|r")
                    end
                    
                    GameTooltip:Show()
                    if GameTooltip.Layout then GameTooltip:Layout() end
                end)
                icon.kglHooked = true
            end

            if not icon.kglPortBtn then
                local btn = CreateFrame("Button", nil, icon, "SecureActionButtonTemplate")
                btn:SetAllPoints()
                btn:RegisterForClicks("LeftButtonDown")
                icon.kglPortBtn = btn

                btn:SetScript("OnEnter", function()
                    local onEnter = icon:GetScript("OnEnter")
                    if onEnter then onEnter(icon) end
                end)

                btn:SetScript("OnLeave", function()
                    local onLeave = icon:GetScript("OnLeave")
                    if onLeave then onLeave(icon) end
                end)
                    hooksecurefunc(icon, "SetUp", function()
                    if InCombatLockdown() then return end -- 🛑 戰鬥中不更新安全屬性
                    
                    local challengeModeID = icon.mapID
                    local portID = challengeModeID and GetFactionPortID(challengeModeID) or nil
                    if portID and IsPlayerSpell(portID) then
                        btn:SetAttribute("type", "spell")
                        btn:SetAttribute("spell", portID)
                    else
                        btn:SetAttribute("type", nil)
                        btn:SetAttribute("spell", nil)
                    end
                end) 
                
            end
                if not InCombatLockdown() then -- 🛑 戰鬥中不更新安全屬性
                local portID = icon.mapID and GetFactionPortID(icon.mapID) or nil
                if portID and IsPlayerSpell(portID) then
                    icon.kglPortBtn:SetAttribute("type", "spell")
                    icon.kglPortBtn:SetAttribute("spell", portID)
                else
                    icon.kglPortBtn:SetAttribute("type", nil)
                    icon.kglPortBtn:SetAttribute("spell", nil)
                end
            end

        end
    end)
    if _G.ChallengesFrame and _G.ChallengesFrame:IsShown() then _G.ChallengesFrame:Update() end
end

PortalEventFrame:SetScript("OnEvent", function(self, event, arg1)
    if event == "ADDON_LOADED" and arg1 == "Blizzard_ChallengesUI" then
        InitKGLPortals()
        self:UnregisterEvent("ADDON_LOADED")
    end
end)

local checkAddonLoaded = (C_AddOns and C_AddOns.IsAddOnLoaded) or IsAddOnLoaded
if checkAddonLoaded and checkAddonLoaded("Blizzard_ChallengesUI") then
    InitKGLPortals()
end]]

-- ==========================================
-- 🎲 附加模組：換鑰匙提醒 (Reroll Module)
-- ==========================================
local RerollFrame = CreateFrame("Frame", "KGL_RerollFrame", UIParent, "BackdropTemplate")
RerollFrame:SetSize(460, 110)
RerollFrame:SetPoint("TOP", 0, -120) 
RerollFrame:Hide()

RerollFrame:SetBackdrop({
    bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
    edgeFile = "Interface\\Buttons\\WHITE8X8",
    tile = true, tileSize = 32, edgeSize = 2,
    insets = { left = 0, right = 0, top = 0, bottom = 0 }
})
RerollFrame:SetBackdropColor(0, 0, 0, 0.85)
RerollFrame:SetBackdropBorderColor(1, 0.84, 0, 1)

RerollFrame.icon = RerollFrame:CreateTexture(nil, "ARTWORK")
RerollFrame.icon:SetSize(64, 64)
RerollFrame.icon:SetPoint("TOPLEFT", 20, -15) 
RerollFrame.icon:SetTexture(4352494)

RerollFrame.text = RerollFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
RerollFrame.text:SetPoint("TOPLEFT", RerollFrame.icon, "TOPRIGHT", 15, -4)

RerollFrame.timerText = RerollFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
local font, _, flags = RerollFrame.timerText:GetFont()
RerollFrame.timerText:SetFont(font or STANDARD_TEXT_FONT, 16, flags)
RerollFrame.timerText:SetSpacing(8) 
RerollFrame.timerText:SetPoint("TOPLEFT", RerollFrame.text, "BOTTOMLEFT", 0, -12)
RerollFrame.timerText:SetJustifyH("LEFT") 

-- 新增：通報按鈕
RerollFrame.sendBtn = CreateFrame("Button", nil, RerollFrame, "UIPanelButtonTemplate")
RerollFrame.sendBtn:SetSize(120, 25)
RerollFrame.sendBtn:SetPoint("BOTTOMRIGHT", -15, 12)
RerollFrame.sendBtn:SetText("通报至队伍")

RerollFrame.sendBtn:SetScript("OnClick", function()
    if not RerollFrame.currentAnnouncement then return end
    
    SendChatMessage(RerollFrame.currentAnnouncement.header, "PARTY")
    SendChatMessage("{rt1}以下队友记得可以更换你们的钥匙喔{rt1}", "PARTY")
    for _, line in ipairs(RerollFrame.currentAnnouncement.list) do
        SendChatMessage(line, "PARTY")
    end
    RerollFrame.sendBtn:Hide()
end)

local function GetKeystoneLinkByData(mapID, level)
    if not mapID or not level then return "---" end
    local mapName = C_ChallengeMode.GetMapUIInfo(mapID) or "Unknown"
    local itemName = C_Item.GetItemInfo(180653) or "Keystone"
    return string.format("|cffa335ee|Hkeystone:180653:%d:%d:0:0:0:0|h[%s: %s (%d)]|h|r", mapID, level, itemName, mapName, level)
end

-- 🔄 核心邏輯更新：加入 dungeonName 參數傳遞
local function ShowRerollAlert(completedLevel, dungeonName)
    --if not KGL_SavedVars.autoAnnounceReroll then return end

    local myMapID = C_MythicPlus.GetOwnedKeystoneChallengeMapID()
    local myLevel = C_MythicPlus.GetOwnedKeystoneLevel()
    if myMapID and myLevel then
        local myName = UnitName("player")
        KGL.PartyData[myName] = { unit = "player", name = myName, mapID = myMapID, level = myLevel }
    end

    local eligibleList = {}
    local rawAnnouncementList = {} 

    for name, data in pairs(KGL.PartyData) do
        if data.level and data.level <= completedLevel then
            -- UI 顯示用的彩色超連結
            local link = GetKeystoneLinkByData(data.mapID, data.level)
            local classColor = "|cffffffff"
            if data.unit and UnitExists(data.unit) then
                local _, class = UnitClass(data.unit)
                if class and RAID_CLASS_COLORS[class] then
                    classColor = "|c" .. RAID_CLASS_COLORS[class].colorStr
                end
            end
            table.insert(eligibleList, classColor .. name .. "|r: " .. link)
            
            -- 🛡️ 頻道通報用：改為「純文字」避免被伺服器阻擋吃掉訊息
            local safeMapName = C_ChallengeMode.GetMapUIInfo(data.mapID) or "未知地城"
            table.insert(rawAnnouncementList, name .. " -- " .. safeMapName .. " [+" .. data.level .. "]")
        end
    end

    if #eligibleList > 0 then
        -- 確保有名字可用
        dungeonName = dungeonName or "未知地城"
        
        RerollFrame.currentAnnouncement = {
            header = string.format("各位队友，你们表现真棒，我们限时通关了『%s』！", dungeonName),
            list = rawAnnouncementList
        }

        RerollFrame.text:SetText("|cffffd700" .. ("记得换 钥石 喔!") .. "|r")
        RerollFrame.timerText:SetText(table.concat(eligibleList, "\n"))
        
        local newHeight = math.max(110, 85 + (#eligibleList * 24))
        RerollFrame:SetHeight(newHeight)
        RerollFrame.sendBtn:Show()
        RerollFrame:Show()
        
        if RerollFrame.hideTimer then RerollFrame.hideTimer:Cancel() end
        RerollFrame.hideTimer = C_Timer.NewTimer(60, function() RerollFrame:Hide() end)
    end
end

-- 📡 監聽完成事件
RerollFrame:RegisterEvent("CHALLENGE_MODE_COMPLETED")
RerollFrame:SetScript("OnEvent", function(self, event)
    local completedLevel = 0
    local isTimed = false
    
    -- 🌟 終極解法：不問 M+ 系統了，直接讀取玩家角色「當前物理上所在的副本名稱」
    local currentDungeonName = GetInstanceInfo()
    if not currentDungeonName or currentDungeonName == "" then
        currentDungeonName = "未知地城"
    end
    
    -- 取得結算資訊
    if C_ChallengeMode and C_ChallengeMode.GetChallengeCompletionInfo then
        local info = C_ChallengeMode.GetChallengeCompletionInfo()
        if type(info) == "table" then
            completedLevel = info.level or 0
            isTimed = (info.onTime == true)
        end
    end
    
    if isTimed and completedLevel > 0 then
        -- 把抓好的地城名稱，傳遞給 2 秒後的函式
        C_Timer.After(2, function()
            ShowRerollAlert(completedLevel, currentDungeonName)
        end)
    end
end)

-- 🖱️ 滑鼠互動：左鍵拖曳、右鍵關閉
RerollFrame:EnableMouse(true)
RerollFrame:SetMovable(true)
RerollFrame:RegisterForDrag("LeftButton")
RerollFrame:SetScript("OnDragStart", RerollFrame.StartMoving)
RerollFrame:SetScript("OnDragStop", RerollFrame.StopMovingOrSizing)
RerollFrame:SetScript("OnMouseUp", function(self, button)
    if button == "RightButton" then
        self:Hide()
    end
end)