local Cowmera = _G.Cowmera or {}
_G.Cowmera = Cowmera

local TELEPORT_SUBTABS = {
    { key = "season", title = "Current Season" },
    { key = "classic", title = "Classic" },
    { key = "bc", title = "Burning Crusade" },
    { key = "wrath", title = "Wrath of the Lich King" },
    { key = "cata", title = "Cataclysm" },
    { key = "mop", title = "Mists of Pandaria" },
    { key = "wod", title = "Warlords of Draenor" },
    { key = "legion", title = "Legion" },
    { key = "bfa", title = "Battle for Azeroth" },
    { key = "shadowlands", title = "Shadowlands" },
    { key = "dragonflight", title = "Dragonflight" },
    { key = "tww", title = "The War Within" },
    { key = "midnight", title = "Midnight" },
}

local function GetFactionSensitiveBfaSpell(spellHorde, spellAlliance)
    local factionGroup = UnitFactionGroup("player")
    if factionGroup == "Horde" then
        return spellHorde
    elseif factionGroup == "Alliance" then
        return spellAlliance
    end
    return spellHorde or spellAlliance
end

local TELEPORT_DATA = {
    season = {
        { icon = "Interface\\Icons\\achievement_dungeon_dragonacademy", spellID = 393273, name = "Algeth'ar Academy", fullname = "Algeth'ar Academy" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_maisarahills", spellID = 1254559, name = "Mai Caves", fullname = "Mai Caves" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_magistersterrace", spellID = 1254572, name = "Magisters' Terrace", fullname = "Magisters' Terrace" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_nexuspointxenas", spellID = 1254563, name = "Nexus Point", fullname = "Nexus Point" },
        { icon = "Interface\\Icons\\achievement_dungeon_icecrown_pitofsaron", spellID = 1254555, name = "Pit of Saron", fullname = "Pit of Saron" },
        { icon = "Interface\\Icons\\achievement_dungeon_argusdungeon", spellID = 1254551, name = "Seat of the Triumvirate", fullname = "Seat of the Triumvirate" },
        { icon = "Interface\\Icons\\achievement_dungeon_arakkoaspires", spellID = 159898, name = "Spires of Arak", fullname = "Spires of Arak" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_windrunnerspire", spellID = 1254400, name = "Windrunner Spire", fullname = "Windrunner Spire" },
    },
    midnight = {
        { icon = "Interface\\Icons\\inv_achievement_dungeon_magistersterrace", spellID = 1254572, name = "Magisters' Terrace", fullname = "Magisters' Terrace" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_maisarahills", spellID = 1254559, name = "Mai Caves", fullname = "Mai Caves" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_nexuspointxenas", spellID = 1254563, name = "Nexus Point", fullname = "Nexus Point" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_windrunnerspire", spellID = 1254400, name = "Windrunner Spire", fullname = "Windrunner Spire" },
    },
    tww = {
        { icon = "Interface\\Icons\\inv_achievement_dungeon_arak-ara", spellID = 445417, name = "Ara-Kara", fullname = "Ara-Kara" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_cinderbrewmeadery", spellID = 445440, name = "Cinderbrew Meadery", fullname = "Cinderbrew Meadery" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_cityofthreads", spellID = 445416, name = "City of Threads", fullname = "City of Threads" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_darkflamecleft", spellID = 445441, name = "Darkflame Cleft", fullname = "Darkflame Cleft" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_dawnbreaker", spellID = 445414, name = "The Dawnbreaker", fullname = "The Dawnbreaker" },
        { icon = "Interface\\Icons\\inv_112_achievement_dungeon_ecodome", spellID = 1237215, name = "Eco-Dome Al'dani", fullname = "Eco-Dome Al'dani" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_waterworks", spellID = 1216786, name = "Operation: Floodgate", fullname = "Operation: Floodgate" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_prioryofthesacredflame", spellID = 445444, name = "Priory of the Sacred Flame", fullname = "Priory of the Sacred Flame" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_rookery", spellID = 445443, name = "The Rookery", fullname = "The Rookery" },
        { icon = "Interface\\Icons\\inv_achievement_dungeon_stonevault", spellID = 445269, name = "The Stonevault", fullname = "The Stonevault" },
    },
    dragonflight = {
        { icon = "Interface\\Icons\\achievement_dungeon_dragonacademy", spellID = 393273, name = "Algeth'ar Academy", fullname = "Algeth'ar Academy" },
        { icon = "Interface\\Icons\\achievement_dungeon_arcanevaults", spellID = 393279, name = "The Azure Vault", fullname = "The Azure Vault" },
        { icon = "Interface\\Icons\\achievement_dungeon_brackenhidehollow", spellID = 393267, name = "Brackenhide Hollow", fullname = "Brackenhide Hollow" },
        { icon = "Interface\\Icons\\achievement_dungeon_dawnoftheinfinite", spellID = 424197, name = "Dawn of the Infinite", fullname = "Dawn of the Infinite" },
        { icon = "Interface\\Icons\\achievement_dungeon_hallsofinfusion", spellID = 393283, name = "Halls of Infusion", fullname = "Halls of Infusion" },
        { icon = "Interface\\Icons\\achievement_dungeon_neltharus", spellID = 393276, name = "Neltharus", fullname = "Neltharus" },
        { icon = "Interface\\Icons\\achievement_dungeon_centaurplains", spellID = 393262, name = "The Nokhud Offensive", fullname = "The Nokhud Offensive" },
        { icon = "Interface\\Icons\\achievement_dungeon_lifepools", spellID = 393256, name = "Ruby Life Pools", fullname = "Ruby Life Pools" },
        { icon = "Interface\\Icons\\achievement_dungeon_uldaman", spellID = 393222, name = "Uldaman: Legacy of Tyr", fullname = "Uldaman: Legacy of Tyr" },
    },
    shadowlands = {
        { icon = "Interface\\Icons\\achievement_dungeon_theotherside", spellID = 354468, name = "De Other Side", fullname = "De Other Side" },
        { icon = "Interface\\Icons\\achievement_dungeon_hallsofattonement", spellID = 354465, name = "Halls of Atonement", fullname = "Halls of Atonement" },
        { icon = "Interface\\Icons\\achievement_dungeon_mistsoftirnascithe", spellID = 354464, name = "Mists of Tirna Scithe", fullname = "Mists of Tirna Scithe" },
        { icon = "Interface\\Icons\\achievement_dungeon_theneroticwake", spellID = 354462, name = "The Necrotic Wake", fullname = "The Necrotic Wake" },
        { icon = "Interface\\Icons\\achievement_dungeon_plaguefall", spellID = 354463, name = "Plaguefall", fullname = "Plaguefall" },
        { icon = "Interface\\Icons\\achievement_dungeon_sanguinedepths", spellID = 354469, name = "Sanguine Depths", fullname = "Sanguine Depths" },
        { icon = "Interface\\Icons\\achievement_dungeon_spireofascension", spellID = 354466, name = "Spires of Ascension", fullname = "Spires of Ascension" },
        { icon = "Interface\\Icons\\achievement_dungeon_brokerdungeon", spellID = 367416, name = "Tazavesh", fullname = "Tazavesh" },
        { icon = "Interface\\Icons\\achievement_dungeon_theatreofpain", spellID = 354467, name = "Theatre of Pain", fullname = "Theatre of Pain" },
    },
    bfa = {
        { icon = "Interface\\Icons\\achievement_dungeon_ataldazar", spellID = 424187, name = "Atal'Dazar", fullname = "Atal'Dazar" },
        { icon = "Interface\\Icons\\achievement_dungeon_freehold", spellID = 410071, name = "Freehold", fullname = "Freehold" },
        { icon = "Interface\\Icons\\achievement_boss_mechagon", spellID = 373274, name = "Operation: Mechagon", fullname = "Operation: Mechagon" },
        { icon = "Interface\\Icons\\achievement_dungeon_kezan", spellID = GetFactionSensitiveBfaSpell(467555, 467553), name = "The MOTHERLODE!!", fullname = "The MOTHERLODE!!" },
        { icon = "Interface\\Icons\\achievement_dungeon_siegeofboralus", spellID = GetFactionSensitiveBfaSpell(464256, 445418), name = "Siege of Boralus", fullname = "Siege of Boralus" },
        { icon = "Interface\\Icons\\achievement_dungeon_underrot", spellID = 410074, name = "The Underrot", fullname = "The Underrot" },
        { icon = "Interface\\Icons\\achievement_dungeon_waycrestmannor", spellID = 424167, name = "Waycrest Manor", fullname = "Waycrest Manor" },
    },
    legion = {
        { icon = "Interface\\Icons\\achievement_dungeon_blackrookhold", spellID = 424153, name = "Black Rook Hold", fullname = "Black Rook Hold" },
        { icon = "Interface\\Icons\\achievement_dungeon_courtofstars", spellID = 393766, name = "Court of Stars", fullname = "Court of Stars" },
        { icon = "Interface\\Icons\\achievement_dungeon_darkheartthicket", spellID = 424163, name = "Darkheart Thicket", fullname = "Darkheart Thicket" },
        { icon = "Interface\\Icons\\achievement_dungeon_hallsofvalor", spellID = 393764, name = "Halls of Valor", fullname = "Halls of Valor" },
        { icon = "Interface\\Icons\\achievement_raid_karazhan", spellID = 373262, name = "Karazhan", fullname = "Karazhan" },
        { icon = "Interface\\Icons\\achievement_dungeon_neltharionslair", spellID = 410078, name = "Neltharion's Lair", fullname = "Neltharion's Lair" },
        { icon = "Interface\\Icons\\achievement_dungeon_argusdungeon", spellID = 1254551, name = "Seat of the Triumvirate", fullname = "Seat of the Triumvirate" },
    },
    wod = {
        { icon = "Interface\\Icons\\achievement_dungeon_auchindoun", spellID = 159897, name = "Auchindoun", fullname = "Auchindoun" },
        { icon = "Interface\\Icons\\achievement_dungeon_ogreslagmines", spellID = 159895, name = "Bloodmaul Slag Mines", fullname = "Bloodmaul Slag Mines" },
        { icon = "Interface\\Icons\\achievement_dungeon_everbloom", spellID = 159901, name = "The Everbloom", fullname = "The Everbloom" },
        { icon = "Interface\\Icons\\achievement_dungeon_blackrockdepot", spellID = 159900, name = "Grimrail Depot", fullname = "Grimrail Depot" },
        { icon = "Interface\\Icons\\achievement_dungeon_blackrockdocks", spellID = 159896, name = "Iron Docks", fullname = "Iron Docks" },
        { icon = "Interface\\Icons\\achievement_dungeon_shadowmoonhideout", spellID = 159899, name = "Shadowmoon Burial Grounds", fullname = "Shadowmoon Burial Grounds" },
        { icon = "Interface\\Icons\\achievement_dungeon_arakkoaspires", spellID = 159898, name = "Spires of Arak", fullname = "Spires of Arak" },
        { icon = "Interface\\Icons\\achievement_dungeon_upperblackrockspire", spellID = 159902, name = "Upper Blackrock Spire", fullname = "Upper Blackrock Spire" },
    },
    mop = {
        { icon = "Interface\\Icons\\achievement_greatwall", spellID = 131225, name = "Gate of the Setting Sun", fullname = "Gate of the Setting Sun" },
        { icon = "Interface\\Icons\\achievement_dungeon_mogupalace", spellID = 131222, name = "Mogu'shan Palace", fullname = "Mogu'shan Palace" },
        { icon = "Interface\\Icons\\spell_holy_senseundead", spellID = 131232, name = "Scholomance", fullname = "Scholomance" },
        { icon = "Interface\\Icons\\inv_helmet_52", spellID = 131231, name = "Scarlet Halls", fullname = "Scarlet Halls" },
        { icon = "Interface\\Icons\\spell_holy_resurrection", spellID = 131229, name = "Scarlet Monastery", fullname = "Scarlet Monastery" },
        { icon = "Interface\\Icons\\achievement_dungeon_siegeofniuzaotemple", spellID = 131228, name = "Siege of Niuzao Temple", fullname = "Siege of Niuzao Temple" },
        { icon = "Interface\\Icons\\achievement_shadowpan_hideout", spellID = 131206, name = "Shado-Pan Monastery", fullname = "Shado-Pan Monastery" },
        { icon = "Interface\\Icons\\achievement_brewery", spellID = 131205, name = "Stormstout Brewery", fullname = "Stormstout Brewery" },
        { icon = "Interface\\Icons\\achievement_jadeserpent", spellID = 131204, name = "Temple of the Jade Serpent", fullname = "Temple of the Jade Serpent" },
    },
    cata = {
        { icon = "Interface\\Icons\\achievement_dungeon_grimbatol", spellID = 445424, name = "Grim Batol", fullname = "Grim Batol" },
        { icon = "Interface\\Icons\\achievement_dungeon_throne of the tides", spellID = 424142, name = "Throne of the Tides", fullname = "Throne of the Tides" },
        { icon = "Interface\\Icons\\achievement_dungeon_skywall", spellID = 410080, name = "The Vortex Pinnacle", fullname = "The Vortex Pinnacle" },
    },
    wrath = {
        { icon = "Interface\\Icons\\achievement_dungeon_icecrown_pitofsaron", spellID = 1254555, name = "Pit of Saron", fullname = "Pit of Saron" },
    },
    classic = {},
    bc = {},
}

local function ClearChildren(frame)
    if not frame then return end
    local children = { frame:GetChildren() }
    for _, child in ipairs(children) do
        child:Hide()
        child:SetParent(nil)
    end
    local regions = { frame:GetRegions() }
    for _, region in ipairs(regions) do
        if region and region.GetObjectType and region:GetObjectType() == "FontString" then
            region:Hide()
        end
    end
end

local function FormatCooldownTime(seconds)
    if seconds >= 3600 then
        return string.format("%dh", math.floor(seconds / 3600))
    elseif seconds >= 60 then
        return string.format("%dm", math.floor(seconds / 60))
    else
        return string.format("%ds", math.floor(seconds))
    end
end

local function SpellKnown(spellID)
    if not spellID then return false end
    if C_SpellBook and C_SpellBook.IsSpellInSpellBook then
        return C_SpellBook.IsSpellInSpellBook(spellID)
    end
    if IsPlayerSpell then
        return IsPlayerSpell(spellID)
    end
    return false
end

local function UpdateCooldownText(button, spellID)
    if not button or not button.cooldownText or not button.cooldownOverlay then return end
    if InCombatLockdown() then return end
    if not spellID or not SpellKnown(spellID) or not C_Spell or not C_Spell.GetSpellCooldown then
        button.cooldownText:SetText("")
        button.cooldownOverlay:Hide()
        return
    end

    local info = C_Spell.GetSpellCooldown(spellID)
    if not info then
        button.cooldownText:SetText("")
        button.cooldownOverlay:Hide()
        return
    end

    local startTime = info.startTime
    local duration = info.duration
    local isEnabled = info.isEnabled

    if type(startTime) == "number" and type(duration) == "number" and startTime > 0 and duration > 0 and isEnabled then
        local remaining = math.max(0, (startTime + duration) - GetTime())
        if remaining > 5 then
            button.cooldownText:SetText(FormatCooldownTime(remaining))
            button.cooldownOverlay:Show()
            return
        end
    end

    button.cooldownText:SetText("")
    button.cooldownOverlay:Hide()
end


local function PlayLockedTeleportSound()
    if PlaySoundFile then
        PlaySoundFile(1537084, "SFX")
    end
end

local function NormalizeDungeonName(value)
    local text = tostring(value or "")
    text = text:lower()
    text = text:gsub("|c%x%x%x%x%x%x%x%x", "")
    text = text:gsub("|r", "")
    text = text:gsub("[^%w%s]", " ")
    text = text:gsub("%s+", " ")
    text = text:gsub("^%s+", "")
    text = text:gsub("%s+$", "")
    return text
end

local DUNGEON_NAME_ALIASES = {
    ["maisara caverns"] = "mai caves",
    ["maisara caverns mythic keystone"] = "mai caves",
    ["mai caverns"] = "mai caves",
    ["mai caves"] = "mai caves",
    ["nexus point xenas"] = "nexus point",
    ["nexus point xena"] = "nexus point",
    ["nexus point xenas mythic"] = "nexus point",
    ["nexus point xenas mythic keystone"] = "nexus point",
    ["nexus-point xenas"] = "nexus point",
    ["nexus-point xena"] = "nexus point",
    ["magisters terrace"] = "magisters terrace",
    ["operation floodgate"] = "operation floodgate",
    ["operation mechagon workshop"] = "operation mechagon",
    ["operation mechagon junkyard"] = "operation mechagon",
    ["tazavesh streets of wonder"] = "tazavesh",
    ["tazavesh so leahs gambit"] = "tazavesh",
}

local function StripDungeonDecorators(value)
    local text = tostring(value or "")
    text = text:gsub("%s*%b()", "")
    text = text:gsub("%s+[Ii]nvited$", "")
    text = text:gsub("%s+[Aa]ccepted$", "")
    text = text:gsub("%s+[Jj]oined$", "")
    text = text:gsub("%s+$", "")
    return text
end

local function GetAllTeleportEntries()
    local entries = {}
    local seen = {}
    for _, groupEntries in pairs(TELEPORT_DATA) do
        for _, entry in ipairs(groupEntries or {}) do
            local key = tostring(entry.spellID or "") .. "|" .. tostring(entry.fullname or entry.name or "")
            if not seen[key] then
                seen[key] = true
                table.insert(entries, entry)
            end
        end
    end
    return entries
end

local function FindTeleportEntryByDungeonName(name)
    local normalized = NormalizeDungeonName(StripDungeonDecorators(name))
    normalized = DUNGEON_NAME_ALIASES[normalized] or normalized
    if normalized == "" then
        return nil
    end

    for _, entry in ipairs(GetAllTeleportEntries()) do
        local fullName = NormalizeDungeonName(entry.fullname or entry.name)
        local shortName = NormalizeDungeonName(entry.name or entry.fullname)
        if normalized == fullName or normalized == shortName then
            return entry
        end
    end

    for _, entry in ipairs(GetAllTeleportEntries()) do
        local fullName = NormalizeDungeonName(entry.fullname or entry.name)
        local shortName = NormalizeDungeonName(entry.name or entry.fullname)
        if fullName ~= "" and (normalized:find(fullName, 1, true) or fullName:find(normalized, 1, true)) then
            return entry
        end
        if shortName ~= "" and (normalized:find(shortName, 1, true) or shortName:find(normalized, 1, true)) then
            return entry
        end
    end

    return nil
end

local function GetGroupFinderLastJoinedRecord()
    local db = CowmeraDB and CowmeraDB.groupFinder
    if type(db) ~= "table" then
        return nil
    end
    if type(db.currentJoined) == "table" then
        return db.currentJoined
    end
    if type(db.lastJoined) == "table" then
        return db.lastJoined
    end
    return nil
end


local function GetOwnedKeystoneInfoForTeleport()
    local level = 0
    local mapID = 0
    local mapName = ""

    if C_MythicPlus and C_MythicPlus.GetOwnedKeystoneLevel then
        local ok, value = pcall(C_MythicPlus.GetOwnedKeystoneLevel)
        if ok then
            level = tonumber(value) or 0
        end
    end

    if C_MythicPlus and C_MythicPlus.GetOwnedKeystoneChallengeMapID then
        local ok, value = pcall(C_MythicPlus.GetOwnedKeystoneChallengeMapID)
        if ok then
            mapID = tonumber(value) or 0
        end
    end

    if mapID ~= 0 and C_ChallengeMode and C_ChallengeMode.GetMapUIInfo then
        local ok, name = pcall(C_ChallengeMode.GetMapUIInfo, mapID)
        if ok and type(name) == "string" and name ~= "" then
            mapName = name
        end
    end

    return tostring(level or 0), tostring(mapName or "")
end


local function WrapDungeonLabel(text)
    local words = {}
    for word in tostring(text or ""):gmatch("%S+") do
        table.insert(words, word)
    end
    if #words <= 1 then
        return tostring(text or "")
    end

    local lines = {}
    local current = ""

    for _, word in ipairs(words) do
        local candidate = (current == "") and word or (current .. " " .. word)
        if #candidate <= 14 then
            current = candidate
        else
            if current ~= "" then
                table.insert(lines, current)
            end
            current = word
        end
    end

    if current ~= "" then
        table.insert(lines, current)
    end

    return table.concat(lines, "\n")
end

local function CreateSpellButton(parent, entry)
    local buttonSize = 84
    local buttonBorderThickness = 2

    local button = CreateFrame("Button", nil, parent, "SecureActionButtonTemplate")
    button:SetSize(buttonSize, buttonSize)
    button:RegisterForClicks("AnyUp", "AnyDown")
    button:SetAttribute("type", "spell")
    button:SetAttribute("spell", entry.spellID)
    button.entry = entry

    local buttonTexture = button:CreateTexture(nil, "BACKGROUND")
    buttonTexture:SetAllPoints(button)
    buttonTexture:SetTexture(entry.icon)
    buttonTexture:SetTexCoord(0.07, 0.93, 0.07, 0.93)
    button.buttonTexture = buttonTexture

    local cooldownOverlay = button:CreateTexture(nil, "ARTWORK")
    cooldownOverlay:SetAllPoints(button)
    cooldownOverlay:SetTexture("Interface\\Tooltips\\UI-Tooltip-Background")
    cooldownOverlay:SetVertexColor(0, 0, 0, 0.6)
    cooldownOverlay:Hide()
    button.cooldownOverlay = cooldownOverlay

    local hoverOverlay = button:CreateTexture(nil, "ARTWORK")
    hoverOverlay:SetAllPoints(button)
    hoverOverlay:SetColorTexture(0, 0, 0, 0.6)
    hoverOverlay:SetDrawLayer("ARTWORK", 1)
    hoverOverlay:Hide()
    button.hoverOverlay = hoverOverlay

    local borderTop = button:CreateTexture(nil, "OVERLAY")
    borderTop:SetColorTexture(0, 0, 0, 1)
    borderTop:SetPoint("TOPLEFT", button, "TOPLEFT", buttonBorderThickness, 0)
    borderTop:SetPoint("TOPRIGHT", button, "TOPRIGHT", -buttonBorderThickness, 0)
    borderTop:SetHeight(buttonBorderThickness)

    local borderBottom = button:CreateTexture(nil, "OVERLAY")
    borderBottom:SetColorTexture(0, 0, 0, 1)
    borderBottom:SetPoint("BOTTOMLEFT", button, "BOTTOMLEFT", buttonBorderThickness, 0)
    borderBottom:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -buttonBorderThickness, 0)
    borderBottom:SetHeight(buttonBorderThickness)

    local borderLeft = button:CreateTexture(nil, "OVERLAY")
    borderLeft:SetColorTexture(0, 0, 0, 1)
    borderLeft:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0)
    borderLeft:SetPoint("BOTTOMLEFT", button, "BOTTOMLEFT", 0, 0)
    borderLeft:SetWidth(buttonBorderThickness)

    local borderRight = button:CreateTexture(nil, "OVERLAY")
    borderRight:SetColorTexture(0, 0, 0, 1)
    borderRight:SetPoint("TOPRIGHT", button, "TOPRIGHT", 0, 0)
    borderRight:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 0, 0)
    borderRight:SetWidth(buttonBorderThickness)

    local buttonText = button:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    buttonText:SetPoint("TOP", button, "BOTTOM", 1, 8)
    buttonText:SetWidth(112)
    buttonText:SetJustifyH("CENTER")
    buttonText:SetJustifyV("TOP")
    if buttonText.SetWordWrap then
        buttonText:SetWordWrap(true)
    end
    buttonText:SetFont(GameFontNormal:GetFont(), 12, "OUTLINE")
    buttonText:SetTextColor(1, 1, 1)
    buttonText:SetText(WrapDungeonLabel(entry.name or ""))
    button.buttonText = buttonText

    local cooldownText = button:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    cooldownText:SetPoint("CENTER", button, "CENTER", 1, 0)
    cooldownText:SetTextColor(1, 1, 1)
    cooldownText:SetFont(GameFontNormal:GetFont(), 22, "OUTLINE")
    button.cooldownText = cooldownText

    function button:UpdateButtonTexture()
        self.buttonTexture:SetTexture(entry.icon)
        self.buttonTexture:SetTexCoord(0.07, 0.93, 0.07, 0.93)
        self.buttonTexture:SetDesaturated(not SpellKnown(entry.spellID))
    end

    button:SetScript("OnEnter", function(self)
        if GameTooltip and entry.spellID then
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetSpellByID(entry.spellID)
            GameTooltip:Show()
        end
        self.hoverOverlay:Show()
    end)

    button:SetScript("OnLeave", function(self)
        if GameTooltip then GameTooltip:Hide() end
        self.hoverOverlay:Hide()
    end)

    button:HookScript("OnMouseDown", function(self, mouseButton)
        if mouseButton == "LeftButton" and not SpellKnown(entry.spellID) then
            PlayLockedTeleportSound()
        end
    end)

    button:RegisterEvent("CHALLENGE_MODE_COMPLETED")
    button:RegisterEvent("PLAYER_ENTERING_WORLD")
    button:RegisterEvent("SPELLS_CHANGED")
    button:SetScript("OnEvent", function(self, event)
        if event == "CHALLENGE_MODE_COMPLETED" then
            C_Timer.After(2, function()
                if self and self.UpdateButtonTexture then
                    self:UpdateButtonTexture()
                    UpdateCooldownText(self, entry.spellID)
                end
            end)
        else
            self:UpdateButtonTexture()
            UpdateCooldownText(self, entry.spellID)
        end
    end)

    button:UpdateButtonTexture()
    UpdateCooldownText(button, entry.spellID)
    button.cooldownTicker = C_Timer.NewTicker(1, function()
        if button and button:IsShown() then
            UpdateCooldownText(button, entry.spellID)
        end
    end)

    return button
end

function Cowmera.BuildTeleportPanel(host, rootFrame)
    if not host then return end
    if host.__cowmeraTeleportBuilt then return end
    host.__cowmeraTeleportBuilt = true

    ClearChildren(host)

    local title = host:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 18, -18)


    local subtitle = host:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10)
    subtitle:SetWidth(620)
    subtitle:SetJustifyH("LEFT")


    local tabHolder = CreateFrame("Frame", nil, host)
    tabHolder:SetPoint("TOPLEFT", 18, -18)
    tabHolder:SetPoint("TOPRIGHT", -18, -18)
    tabHolder:SetHeight(170)

    local detailFrame = CreateFrame("Frame", nil, host, "BackdropTemplate")
    detailFrame:SetPoint("TOPLEFT", tabHolder, "BOTTOMLEFT", 0, -12)
    detailFrame:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", -18, 18)
    if detailFrame.SetBackdrop then
        detailFrame:SetBackdrop({
            bgFile = "Interface/ChatFrame/ChatFrameBackground",
            edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
            tile = true,
            tileSize = 16,
            edgeSize = 12,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
        detailFrame:SetBackdropColor(0, 0, 0, 0.35)
        detailFrame:SetBackdropBorderColor(0.65, 0.4, 0.08, 0.9)
    end

    local detailHeader = detailFrame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    detailHeader:SetPoint("TOPLEFT", 18, -18)
    detailHeader:SetText("Classic")

    local detailBody = detailFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    detailBody:SetPoint("TOPLEFT", detailHeader, "BOTTOMLEFT", 0, -6)
    detailBody:SetWidth(560)
    detailBody:SetJustifyH("LEFT")
    detailBody:SetText("No teleports added for this tab yet.")

    local contentFrame = CreateFrame("Frame", nil, detailFrame)
    contentFrame:SetPoint("TOPLEFT", 18, -62)
    contentFrame:SetPoint("BOTTOMRIGHT", -18, 18)

    local emptyState = contentFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlightLarge")
    emptyState:SetPoint("CENTER", contentFrame, "CENTER", 0, 10)
    emptyState:SetText("Empty for now")

    local emptyStateSub = contentFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    emptyStateSub:SetPoint("TOP", emptyState, "BOTTOM", 0, -10)
    emptyStateSub:SetWidth(560)
    emptyStateSub:SetJustifyH("CENTER")
    emptyStateSub:SetText("Teleport buttons for this tab have not been added yet.")

    -- Current keystone text beside the BOTTOM "Current Season" header.
    -- The visible text is a plain FontString so it cannot disappear if the secure click button fails.
    local currentKeyText = detailFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    currentKeyText:SetPoint("TOPLEFT", detailFrame, "TOPLEFT", 160, -18)
    currentKeyText:SetWidth(420)
    currentKeyText:SetJustifyH("CENTER")
    currentKeyText:SetText("MY KEY: Loading...")
    currentKeyText:Show()

    local currentKeyButton = nil
    local SafeRefreshCurrentKeyButton = function() end

    local function SetCurrentKeyVisibleText(text)
        if currentKeyText then
            currentKeyText:SetText(text or "")
            currentKeyText:Show()
        end
    end

    local function ClearCurrentKeyButtonSpell()
        if not currentKeyButton then return end
        currentKeyButton.entry = nil
        currentKeyButton.hasKnownTeleport = false
        if not InCombatLockdown() then
            currentKeyButton:SetAttribute("type", nil)
            currentKeyButton:SetAttribute("spell", nil)
            currentKeyButton:SetAttribute("type1", nil)
            currentKeyButton:SetAttribute("spell1", nil)
        end
    end

    local function RefreshCurrentKeyButton()
        if rootFrame and rootFrame.selectedTeleportGroup and rootFrame.selectedTeleportGroup ~= "season" then
            SetCurrentKeyVisibleText("")
            if currentKeyText then currentKeyText:Hide() end
            if currentKeyButton then
                if InCombatLockdown and InCombatLockdown() then
                    currentKeyButton._cowmeraPendingEnableMouse = false
                    currentKeyButton._cowmeraPendingMouseRefresh = true
                else
                    currentKeyButton:EnableMouse(false)
                end
            end
            ClearCurrentKeyButtonSpell()
            return
        end

        if currentKeyButton then
            if InCombatLockdown and InCombatLockdown() then
                currentKeyButton._cowmeraPendingEnableMouse = true
                currentKeyButton._cowmeraPendingMouseRefresh = true
            else
                currentKeyButton:EnableMouse(true)
            end
        end
        if currentKeyText then
            currentKeyText:Show()
        end

        local levelText, mapName = GetOwnedKeystoneInfoForTeleport()
        local lookupName = StripDungeonDecorators(mapName or "")
        if levelText == "" or levelText == "0" or lookupName == "" or lookupName == "No Keystone" then
            SetCurrentKeyVisibleText("MY KEY: No Keystone")
            ClearCurrentKeyButtonSpell()
            return
        end

        local entry = FindTeleportEntryByDungeonName(lookupName)
        local displayName = entry and (entry.fullname or entry.name) or lookupName
        local displayText = "MY KEY: +" .. tostring(levelText) .. " " .. tostring(displayName)

        if currentKeyButton then
            currentKeyButton.entry = entry
            currentKeyButton.hasKnownTeleport = entry and entry.spellID and SpellKnown(entry.spellID) or false
        end

        if entry and entry.spellID and SpellKnown(entry.spellID) then
            SetCurrentKeyVisibleText(displayText)
            if currentKeyButton and not InCombatLockdown() then
                currentKeyButton:SetAttribute("type", "spell")
                currentKeyButton:SetAttribute("spell", entry.spellID)
                currentKeyButton:SetAttribute("type1", "spell")
                currentKeyButton:SetAttribute("spell1", entry.spellID)
            end
        else
            SetCurrentKeyVisibleText(displayText)
            ClearCurrentKeyButtonSpell()
        end
    end

    SafeRefreshCurrentKeyButton = function()
        local ok = pcall(RefreshCurrentKeyButton)
        if not ok then
            SetCurrentKeyVisibleText("MY KEY: No Keystone")
            ClearCurrentKeyButtonSpell()
        end
    end

    -- Secure teleport buttons cannot have mouse state changed in combat.
    -- If a refresh happens while fighting, apply the pending EnableMouse state after combat ends.
    local currentKeyCombatFrame = CreateFrame("Frame")
    currentKeyCombatFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
    currentKeyCombatFrame:SetScript("OnEvent", function()
        if currentKeyButton and currentKeyButton._cowmeraPendingMouseRefresh then
            currentKeyButton._cowmeraPendingMouseRefresh = nil
            local enabled = currentKeyButton._cowmeraPendingEnableMouse
            currentKeyButton._cowmeraPendingEnableMouse = nil
            currentKeyButton:EnableMouse(enabled and true or false)
        end
        SafeRefreshCurrentKeyButton()
    end)

    -- Secure invisible click area over the visible keystone text.
    -- If the teleport is known, it casts it. If not, it only plays the locked/moo sound.
    pcall(function()
        currentKeyButton = CreateFrame("Button", nil, detailFrame, "SecureActionButtonTemplate,UIPanelButtonTemplate")
        currentKeyButton:SetSize(340, 24)
        currentKeyButton:SetPoint("TOPLEFT", detailFrame, "TOPLEFT", 155, -16)
        currentKeyButton:RegisterForClicks("AnyUp", "AnyDown")
        -- Use the same standard WoW button styling as the Teleports expansion buttons.
        -- Keep the clickable secure action button, but let UIPanelButtonTemplate draw the art.
        if currentKeyButton.SetText then
            currentKeyButton:SetText("")
        end

        if currentKeyText then
            currentKeyText:ClearAllPoints()
            currentKeyText:SetParent(currentKeyButton)
            currentKeyText:SetPoint("LEFT", currentKeyButton, "LEFT", 12, 0)
            currentKeyText:SetPoint("RIGHT", currentKeyButton, "RIGHT", -12, 0)
            currentKeyText:SetPoint("CENTER", currentKeyButton, "CENTER", 0, 0)
            currentKeyText:SetJustifyH("CENTER")
            currentKeyText:SetJustifyV("MIDDLE")
            currentKeyText:SetDrawLayer("OVERLAY", 7)
            currentKeyText:SetFontObject(GameFontNormal)
            if currentKeyText.SetShadowOffset then
                currentKeyText:SetShadowOffset(1, -1)
            end
            if currentKeyText.SetShadowColor then
                currentKeyText:SetShadowColor(0, 0, 0, 1)
            end
        end
        currentKeyButton.entry = nil
        currentKeyButton.hasKnownTeleport = false
        if currentKeyButton.SetFrameLevel and detailFrame.GetFrameLevel then
            currentKeyButton:SetFrameLevel(detailFrame:GetFrameLevel() + 10)
        end

        currentKeyButton:HookScript("OnMouseDown", function(self, mouseButton)
            if mouseButton == "LeftButton" and not self.hasKnownTeleport then
                PlayLockedTeleportSound()
            end
        end)

        currentKeyButton:SetScript("OnEnter", function(self)
            if not GameTooltip then return end
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            local entry = self.entry
            if entry and entry.spellID then
                GameTooltip:SetSpellByID(entry.spellID)
                if not SpellKnown(entry.spellID) then
                    GameTooltip:AddLine("Teleport not learned yet.", 1, 0.25, 0.25, true)
                end
            else
                GameTooltip:SetText("Current Keystone")
                GameTooltip:AddLine("No matching learned teleport for your current keystone.", 1, 0.25, 0.25, true)
            end
            GameTooltip:Show()
        end)

        currentKeyButton:SetScript("OnLeave", function()
            if GameTooltip then GameTooltip:Hide() end
        end)

        currentKeyButton:RegisterEvent("PLAYER_ENTERING_WORLD")
        currentKeyButton:RegisterEvent("BAG_UPDATE_DELAYED")
        currentKeyButton:RegisterEvent("CHALLENGE_MODE_COMPLETED")
        currentKeyButton:RegisterEvent("SPELLS_CHANGED")
        currentKeyButton:SetScript("OnEvent", SafeRefreshCurrentKeyButton)
    end)

    local teleportButtons = {}
    local activeSpellButtons = {}

    local function SetTeleportTabActive(activeKey)
        for key, btn in pairs(teleportButtons) do
            if key == activeKey then
                btn:LockHighlight()
            else
                btn:UnlockHighlight()
            end
        end
    end

    local function ReleaseActiveSpellButtons()
        for _, btn in ipairs(activeSpellButtons) do
            if btn.cooldownTicker then
                btn.cooldownTicker:Cancel()
                btn.cooldownTicker = nil
            end
            btn:Hide()
            btn:SetParent(nil)
        end
        wipe(activeSpellButtons)
    end

    local function LayoutTeleportButtons(group)
        ReleaseActiveSpellButtons()

        local entries = TELEPORT_DATA[group.key] or {}
        local count = #entries
        local columns = (group.key == "season") and 4 or 5
        local xSpacing = 114
        local ySpacing = 150
        local startX = 8
        local startY = -8

        if count == 0 then
            emptyState:Show()
            emptyStateSub:Show()
            return
        end

        emptyState:Hide()
        emptyStateSub:Hide()

        for index, entry in ipairs(entries) do
            local button = CreateSpellButton(contentFrame, entry)
            local column = (index - 1) % columns
            local row = math.floor((index - 1) / columns)
            button:SetPoint("TOPLEFT", contentFrame, "TOPLEFT", startX + (column * xSpacing), startY - (row * ySpacing))
            table.insert(activeSpellButtons, button)
        end
    end

    local function ShowTeleportGroup(group)
        if not group then return end
        detailHeader:SetText(group.title or "Teleports")
        local entries = TELEPORT_DATA[group.key] or {}
        if #entries > 0 then
            detailBody:SetText("")
        else
            detailBody:SetText("No teleports added to " .. tostring(group.title or "this tab") .. " yet.")
        end
        if rootFrame then
            rootFrame.selectedTeleportGroup = group.key
        end
        SetTeleportTabActive(group.key)
        LayoutTeleportButtons(group)
        SafeRefreshCurrentKeyButton()
    end

    local normalIndex = 0
    for _, group in ipairs(TELEPORT_SUBTABS) do
        local btn = CreateFrame("Button", nil, tabHolder, "UIPanelButtonTemplate")

        if group.key == "season" then
            btn:SetSize(678, 26)
            btn:SetPoint("TOPLEFT", 0, 0)
        else
            local column = normalIndex % 3
            local row = math.floor(normalIndex / 3) + 1
            btn:SetSize(220, 26)
            btn:SetPoint("TOPLEFT", 0 + (column * 226), 0 - (row * 32))
            normalIndex = normalIndex + 1
        end

        btn:SetText(group.title)
        btn:SetScript("OnClick", function()
            ShowTeleportGroup(group)
        end)
        teleportButtons[group.key] = btn
    end

    host:SetScript("OnShow", function()
        for _, group in ipairs(TELEPORT_SUBTABS) do
            if group.key == "season" then
                ShowTeleportGroup(group)
                return
            end
        end
        ShowTeleportGroup(TELEPORT_SUBTABS[1])
    end)

    host:SetScript("OnHide", function()
        ReleaseActiveSpellButtons()
    end)

    for _, group in ipairs(TELEPORT_SUBTABS) do
        if group.key == "season" then
            ShowTeleportGroup(group)
            return
        end
    end
    ShowTeleportGroup(TELEPORT_SUBTABS[1])
end
