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

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

local GuildFrame -- 🌟 提前宣告，避免下方函式找不到變數而產生 nil 報錯

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, 
}

local SpellToMapID = {}
for mapID, port in pairs(DungeonPorts) do
    if type(port) == "table" then
        for _, id in ipairs(port) do SpellToMapID[id] = mapID end
    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

local function GetPartyUnitToken(name)
    if UnitIsUnit("player", name) then return "player" end
    for i = 1, GetNumSubgroupMembers() do
        if UnitName("party"..i) == name then return "party"..i end
    end
    return name
end

-- ==========================================
-- 📡 KGL Mesh Network (網路廣播/中繼核心)
-- ==========================================
local function SetPartyKeystone(name, mapID, level, source)
    if not name or not mapID or not level then return false end
    local existing = KGL.PartyData[name]
    
    -- 若資料有更新，才寫入並進行後續處理
    if not existing or existing.mapID ~= mapID or existing.level ~= level then
        local inParty = false
        if UnitIsUnit("player", name) then
            inParty = true
        else
            for i = 1, GetNumSubgroupMembers() do
                if UnitName("party"..i) == name then
                    inParty = true; break
                end
            end
        end
        
        if inParty then
            KGL.PartyData[name] = { unit = GetPartyUnitToken(name), name = name, mapID = mapID, level = level }
            
            -- 🌟 [廣播核心]：如果是透過外部 UI (Details/Astral) 讀取到的，代為廣播給其他只有裝 KGL 的隊友！
            if source == "External" and IsInGroup() then
                local msg = string.format("KSGL:Sync:%s:%d:%d", name, mapID, level)
                C_ChatInfo.SendAddonMessage(KGL.Prefix, msg, "PARTY")
            end
            return true
        end
    end
    return false
end

-- ==========================================
-- 🎨 方案 A: 隊伍極簡 HUD 介面 (自動顯示/隱藏)
-- ==========================================
local MainFrame = CreateFrame("Frame", "KGL_MainFrame", PVEFrame, "BackdropTemplate")
MainFrame:SetSize(260, 20)
MainFrame:SetPoint("BOTTOMLEFT", PVEFrame, "BOTTOMRIGHT", 8, -8)
--[[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("隊伍鑰匙 (拖曳此處)")]]

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

-- ==========================================
-- 🔄 核心鑰匙更新與廣播
-- ==========================================
local function UpdateMyKeystone()
    local mapID = C_MythicPlus.GetOwnedKeystoneChallengeMapID()
    local level = C_MythicPlus.GetOwnedKeystoneLevel()
    
    if mapID and level then
        local myName = UnitName("player")
        SetPartyKeystone(myName, mapID, level, "KGL")
        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

    if not InCombatLockdown() then
        if KGL.RenderPartyUI then KGL.RenderPartyUI() end
        if KGL.RenderGuildUI and GuildFrame and GuildFrame:IsShown() then KGL.RenderGuildUI() end
    end
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())
            
            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
                    SetPartyKeystone(name, mapID, level, "External")
                end
            end
            
            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
                    SetPartyKeystone(name, mapID, level, "External")
                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

    if KGL.EvaluateReroll then KGL.EvaluateReroll() end
end
KGL.RenderPartyUI = RenderPartyUI

-- ==========================================
-- ⚙️ 應用外觀與鎖定設定
-- ==========================================
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 * 0.8)
        
        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)
-- ==========================================
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
KGL.RenderGuildUI = RenderGuildUI

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 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
            if SetPartyKeystone(charName, mapID, level, "External") then
                RenderPartyUI()
                if GuildFrame and GuildFrame:IsShown() then RenderGuildUI() end
            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
                if SetPartyKeystone(charName, keyMap, keyLevel, "External") then
                    KGL.NeedUpdate = true
                    if not InCombatLockdown() then RenderPartyUI() end
                    if GuildFrame and GuildFrame:IsShown() then RenderGuildUI() end
                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_START")
EventFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
EventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")

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.enableTeleportAnnounce == nil then KGL_SavedVars.enableTeleportAnnounce = true end 
        --if KGL_SavedVars.announceOnStart == nil then KGL_SavedVars.announceOnStart = false end
        --if KGL_SavedVars.announceOnSuccess == nil then KGL_SavedVars.announceOnSuccess = true end
        --if KGL_SavedVars.portalMessageStart == nil then KGL_SavedVars.portalMessageStart = "" end
        --if KGL_SavedVars.portalMessageSuccess == nil then KGL_SavedVars.portalMessageSuccess = "" end
        
        --if not KGL_SavedVars.announceOnStart and not KGL_SavedVars.announceOnSuccess then
            --KGL_SavedVars.announceOnSuccess = true
        --end

        KGL.ApplySettings()
        
        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("锁定主面板位置")
        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("图标大小") .. " (" .. iconSlider:GetValue() .. ")"
        iconSlider.Low:SetText("16"); iconSlider.High:SetText("48")
        iconSlider:SetScript("OnValueChanged", function(self, value)
            KGL_iconSize = value
            self.Text:SetText("图标大小") .. " (" .. 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("字体大小") .. " (" .. fontSlider:GetValue() .. ")"
        fontSlider.Low:SetText("8"); fontSlider.High:SetText("24")
        fontSlider:SetScript("OnValueChanged", function(self, value)
            KGL_fontSize = value
            self.Text:SetText("字体大小") .. " (" .. value .. ")"
            KGL.ApplySettings()
        end)

        local rerollCheck = CreateFrame("CheckButton", nil, OptionsPanel, "InterfaceOptionsCheckButtonTemplate")
        rerollCheck:SetPoint("TOPLEFT", fontSlider, "BOTTOMLEFT", 0, -30)
        rerollCheck.Text:SetText("限时通关后自动提醒换 Key")
        rerollCheck:SetChecked(KGL_SavedVars.autoAnnounceReroll)
        rerollCheck:SetScript("OnClick", function(self)
            KGL_SavedVars.autoAnnounceReroll = self:GetChecked()
        end)

        local teleportCheck = CreateFrame("CheckButton", nil, OptionsPanel, "InterfaceOptionsCheckButtonTemplate")
        teleportCheck:SetPoint("TOPLEFT", rerollCheck, "BOTTOMLEFT", 0, -10)
        teleportCheck.Text:SetText("启用传送法术通报")
        teleportCheck:SetChecked(KGL_SavedVars.enableTeleportAnnounce)
        teleportCheck:SetScript("OnClick", function(self)
            KGL_SavedVars.enableTeleportAnnounce = self:GetChecked()
        end)

        local announceStartCheck = CreateFrame("CheckButton", nil, OptionsPanel, "InterfaceOptionsCheckButtonTemplate")
        local announceSuccessCheck = CreateFrame("CheckButton", nil, OptionsPanel, "InterfaceOptionsCheckButtonTemplate")
        local msgEditBoxStart = CreateFrame("EditBox", nil, OptionsPanel, "InputBoxTemplate")
        local msgEditBoxSuccess = CreateFrame("EditBox", nil, OptionsPanel, "InputBoxTemplate")

        local function ConvertRTtoIcon(text)
            if not text then return "" end
            for i = 1, 8 do
                text = string.gsub(text, "{rt" .. i .. "}", "|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_" .. i .. ":14|t")
            end
            return text
        end

        local function UpdatePreview()
            local spellLink
            if C_Spell and C_Spell.GetSpellLink then
                spellLink = C_Spell.GetSpellLink(424153)
            elseif GetSpellLink then
                spellLink = GetSpellLink(424153)
            end
            if not spellLink then spellLink = "|cff71d5ff|Hspell:424153:0|h[傳送：風行者塔]|h|r" end
            
            local mapName = C_ChallengeMode.GetMapUIInfo(199) or "風行者塔"
            local previewStr = ""
            local isBothChecked = KGL_SavedVars.announceOnStart and KGL_SavedVars.announceOnSuccess
            
            local txtDefault = ("正在咏唱{rt1}[spell]{rt1}，穿越时空前往{rt8}[dungeon]{rt8}")
            local txtStart   = ("正在咏唱{rt1}[spell]{rt1}")
            local txtSuccess = ("穿越时空前往{rt8}[dungeon]{rt8}")

            if KGL_SavedVars.announceOnStart then
                local msgStart = msgEditBoxStart:GetText()
                if msgStart == "" then
                    msgStart = isBothChecked and txtStart or txtDefault
                end
                msgStart = string.gsub(msgStart, "%[spell%]", function() return spellLink end)
                msgStart = string.gsub(msgStart, "%[dungeon%]", function() return mapName end)
                previewStr = previewStr .. "[开始] " .. ConvertRTtoIcon(msgStart) .. "\n"
            end
            
            if KGL_SavedVars.announceOnSuccess then
                local msgSuccess = msgEditBoxSuccess:GetText()
                if msgSuccess == "" then
                    msgSuccess = isBothChecked and txtSuccess or txtDefault
                end
                msgSuccess = string.gsub(msgSuccess, "%[spell%]", function() return spellLink end)
                msgSuccess = string.gsub(msgSuccess, "%[dungeon%]", function() return mapName end)
                previewStr = previewStr .. "[完成] " .. ConvertRTtoIcon(msgSuccess)
            end
            
            if OptionsPanel.previewText then
                OptionsPanel.previewText:SetText("预览") .. ":\n" .. previewStr)
            end
        end

        announceStartCheck:SetPoint("TOPLEFT", teleportCheck, "BOTTOMLEFT", 20, -5)
        announceStartCheck.Text:SetText("开始施法时通报")
        announceStartCheck:SetChecked(KGL_SavedVars.announceOnStart)
        announceStartCheck:SetScript("OnClick", function(self)
            if not self:GetChecked() and not announceSuccessCheck:GetChecked() then
                announceSuccessCheck:SetChecked(true)
                KGL_SavedVars.announceOnSuccess = true
            end
            KGL_SavedVars.announceOnStart = self:GetChecked()
            UpdatePreview()
        end)

        announceSuccessCheck:SetPoint("TOPLEFT", announceStartCheck, "BOTTOMLEFT", 0, -5)
        announceSuccessCheck.Text:SetText("施法完成时通报"))
        announceSuccessCheck:SetChecked(KGL_SavedVars.announceOnSuccess)
        announceSuccessCheck:SetScript("OnClick", function(self)
            if not self:GetChecked() and not announceStartCheck:GetChecked() then
                self:SetChecked(true)
            end
            KGL_SavedVars.announceOnSuccess = self:GetChecked()
            UpdatePreview()
        end)

        local msgLabelStart = OptionsPanel:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        msgLabelStart:SetPoint("TOPLEFT", announceSuccessCheck, "BOTTOMLEFT", -20, -10)
        msgLabelStart:SetText("自订开始施法内容 (标签: [spell], [dungeon]，留空为默认):")

        msgEditBoxStart:SetSize(350, 20)
        msgEditBoxStart:SetPoint("TOPLEFT", msgLabelStart, "BOTTOMLEFT", 5, -5)
        msgEditBoxStart:SetAutoFocus(false)
        msgEditBoxStart:SetText(KGL_SavedVars.portalMessageStart)
        msgEditBoxStart:SetScript("OnTextChanged", function(self)
            KGL_SavedVars.portalMessageStart = self:GetText()
            UpdatePreview()
        end)

        local msgLabelSuccess = OptionsPanel:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        msgLabelSuccess:SetPoint("TOPLEFT", msgEditBoxStart, "BOTTOMLEFT", -5, -10)
        msgLabelSuccess:SetText("自订完成施法内容 (标签: [spell], [dungeon]，留空为默认):")

        msgEditBoxSuccess:SetSize(350, 20)
        msgEditBoxSuccess:SetPoint("TOPLEFT", msgLabelSuccess, "BOTTOMLEFT", 5, -5)
        msgEditBoxSuccess:SetAutoFocus(false)
        msgEditBoxSuccess:SetText(KGL_SavedVars.portalMessageSuccess)
        msgEditBoxSuccess:SetScript("OnTextChanged", function(self)
            KGL_SavedVars.portalMessageSuccess = self:GetText()
            UpdatePreview()
        end)

        OptionsPanel.previewText = OptionsPanel:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
        OptionsPanel.previewText:SetPoint("TOPLEFT", msgEditBoxSuccess, "BOTTOMLEFT", -5, -10)
        OptionsPanel.previewText:SetWidth(400)
        OptionsPanel.previewText:SetJustifyH("LEFT")
        
        UpdatePreview()

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

    elseif event == "PLAYER_ENTERING_WORLD" then
        local isActive = C_ChallengeMode and C_ChallengeMode.IsChallengeModeActive() or false
        if KGL.IsChallengeActive and not isActive then
            KGL.IsChallengeActive = false
            UpdateVisibilityCondition()
            C_Timer.After(2, UpdateMyKeystone)
        elseif not KGL.IsChallengeActive and isActive then
            KGL.IsChallengeActive = true
            UpdateVisibilityCondition()
        end

    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 = ...
        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
                    if channel == "PARTY" then
                        if SetPartyKeystone(senderName, mapID, level, "KGL") then RenderPartyUI() end
                    elseif channel == "GUILD" then
                        KGL.GuildData[senderName] = { unit = senderName, name = senderName, mapID = mapID, level = level }
                        if GuildFrame:IsShown() then RenderGuildUI() end
                    end
                end
            -- 🌟 [Mesh Network]：接收別人代發的外部鑰匙資訊！
            elseif msgType == "Sync" then
                local targetName, mapStr, levelStr = text:match("KSGL:Sync:([^:]+):(%d+):(%d+)")
                local mapID = tonumber(mapStr)
                local level = tonumber(levelStr)
                if targetName and mapID and level then
                    if channel == "PARTY" then
                        if SetPartyKeystone(targetName, mapID, level, "KGL") then RenderPartyUI() end
                    end
                end
            elseif msgType == "Request" then
                UpdateMyKeystone()
                -- 🌟 收到請求時，順便廣播自己代抓的隊友資訊給剛加入隊伍的人
                if channel == "PARTY" then
                    for name, data in pairs(KGL.PartyData) do
                        if name ~= UnitName("player") then
                            local syncMsg = string.format("KSGL:Sync:%s:%d:%d", name, data.mapID, data.level)
                            C_ChatInfo.SendAddonMessage(KGL.Prefix, syncMsg, "PARTY")
                        end
                    end
                end
            end
        end

    --[[elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_SUCCEEDED" then
        local unit, _, spellID = ...
        if unit == "player" and KGL_SavedVars.enableTeleportAnnounce then
            
            local isStart = (event == "UNIT_SPELLCAST_START")
            local isSuccess = (event == "UNIT_SPELLCAST_SUCCEEDED")
            local isBothChecked = KGL_SavedVars.announceOnStart and KGL_SavedVars.announceOnSuccess
            
            local shouldAnnounce = false
            if isStart and KGL_SavedVars.announceOnStart then shouldAnnounce = true end
            if isSuccess and KGL_SavedVars.announceOnSuccess then shouldAnnounce = true end
            
            if shouldAnnounce then
                local mapID = SpellToMapID[spellID]
                if mapID then
                    local spellLink
                    if C_Spell and C_Spell.GetSpellLink then
                        spellLink = C_Spell.GetSpellLink(spellID)
                    elseif GetSpellLink then
                        spellLink = GetSpellLink(spellID)
                    end
                    
                    local mapName = C_ChallengeMode.GetMapUIInfo(mapID)
                    if spellLink and mapName and IsInGroup() then
                        local msg = ""
                        local txtDefault = "正在咏唱{rt1}[spell]{rt1}，穿越时空前往{rt8}[dungeon]{rt8}"
                        local txtStart   = "正在咏唱{rt1}[spell]{rt1}"
                        local txtSuccess = "穿越时空前往{rt8}[dungeon]{rt8}"
                        
                        if isStart then
                            msg = KGL_SavedVars.portalMessageStart or ""
                            if msg == "" then 
                                msg = isBothChecked and txtStart or txtDefault 
                            end
                        elseif isSuccess then
                            msg = KGL_SavedVars.portalMessageSuccess or ""
                            if msg == "" then 
                                msg = isBothChecked and txtSuccess or txtDefault 
                            end
                        end
                        
                        msg = string.gsub(msg, "%[spell%]", function() return spellLink end)
                        msg = string.gsub(msg, "%[dungeon%]", function() return mapName end)
                        SendChatMessage(msg, "PARTY")
                    end
                end
            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(3, UpdateMyKeystone)
        
    elseif event == "PLAYER_REGEN_ENABLED" then
        if KGL.NeedVisibilityUpdate then UpdateVisibilityCondition() end
        if KGL.NeedUpdate then RenderPartyUI() end
    end
end)

-- ==========================================
-- 🚪 附加模組：M+ 介面地城傳送門按鈕
--[[ ==========================================
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)
                                    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
                        end
                    end
                    GameTooltip:Show()
                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() icon:GetScript("OnEnter")(icon) end)
                btn:SetScript("OnLeave", function() icon:GetScript("OnLeave")(icon) end)
                hooksecurefunc(icon, "SetUp", function()
                    if InCombatLockdown() then return end
                    local portID = icon.mapID and GetFactionPortID(icon.mapID) 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
        end
    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 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: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)

KGL.EvaluateReroll = function()
    --if not KGL_SavedVars.autoAnnounceReroll then return end
    if not KGL.RecentCompletion then return end
    
    if not KGL.RecentCompletion.ready then return end 

    if GetTime() > KGL.RecentCompletion.expires then
        RerollFrame:Hide()
        KGL.RecentCompletion = nil
        return
    end

    KGL.RecentCompletion.initialKeys = KGL.RecentCompletion.initialKeys or {}

    local eligibleList = {}
    local rawAnnouncementList = {} 

    for name, data in pairs(KGL.PartyData) do
        if data.level and data.level <= KGL.RecentCompletion.level then
            local hasRerolled = false
            
            if not KGL.RecentCompletion.initialKeys[name] then
                KGL.RecentCompletion.initialKeys[name] = data.mapID
            elseif KGL.RecentCompletion.initialKeys[name] ~= data.mapID then
                hasRerolled = true
            end

            if not hasRerolled then
                local mapName = C_ChallengeMode.GetMapUIInfo(data.mapID) or "Unknown"
                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: " .. mapName .. " (+" .. data.level .. ")")
                table.insert(rawAnnouncementList, name .. " -- " .. mapName .. " (+" .. data.level .. ")")
            end
        end
    end

    if #eligibleList > 0 then
        local headerMsg = "各位队友，你们表现真棒，我们限时通关了『%s』！"
        RerollFrame.currentAnnouncement = {
            header = string.format(headerMsg, KGL.RecentCompletion.name),
            list = rawAnnouncementList
        }
        RerollFrame.text:SetText("|cffffd700" .. "- 记得换 钥石 喔 -" .. "|r")
        RerollFrame.timerText:SetText(table.concat(eligibleList, "\n"))
        RerollFrame:SetHeight(math.max(110, 85 + (#eligibleList * 24)))
        RerollFrame.sendBtn:Show()
        
        if not RerollFrame:IsShown() then RerollFrame:Show() end
    else
        RerollFrame:Hide()
    end
end

RerollFrame:RegisterEvent("CHALLENGE_MODE_COMPLETED")
RerollFrame:SetScript("OnEvent", function(self, event)
    local completedLevel = 0
    local isTimed = false
    local currentDungeonName = GetInstanceInfo()
    
    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
        KGL.RecentCompletion = {
            level = completedLevel,
            name = currentDungeonName or "未知地城",
            expires = GetTime() + 60,
            ready = false,
            initialKeys = {}
        }
        
        C_Timer.After(3, function()
            if KGL.RecentCompletion then
                KGL.RecentCompletion.ready = true
                UpdateMyKeystone()
            end
        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)