-- [ META TALENTS ]--------------------------------------------------------------------------
-- QoL feature: overlays WCL Top 100 talent pick-rates on the talent tree.
-- Heatmap ring coloring + percentage text below each node.
-- Data sourced from a Load-On-Demand module auto-generated by CI pipeline.
-- Toggle via Quality of Life > Meta Talents in the Orbit settings panel.

local _, Orbit = ...

-- [ CONSTANTS ]-----------------------------------------------------------------------------
local LOD_ADDON = "OrbitData"
local FONT_TEMPLATE = "GameFontHighlightSmallOutline"
local TEXT_OFFSET_Y = 13
local MIN_PICK_RATE = 1 -- Ignore sub-1% noise from WCL data

-- [ WCL PARSE COLORS ]----------------------------------------------------------------------
-- Matches Warcraft Logs / Raider.IO percentile tiers
local COLOR_PINK   = { 0.886, 0.408, 0.659 } -- #E268A8 (95-100%)
local COLOR_PURPLE = { 0.639, 0.208, 0.933 } -- #A335EE (75-94%)
local COLOR_BLUE   = { 0.000, 0.439, 0.867 } -- #0070DD (50-74%)
local COLOR_GREEN  = { 0.118, 1.000, 0.000 } -- #1EFF00 (25-49%)
local COLOR_GRAY   = { 0.400, 0.400, 0.400 } -- #666666 (0-24%)

-- [ MODULE ]--------------------------------------------------------------------------------
Orbit.MetaTalents = {}
local MT = Orbit.MetaTalents
MT._active = false
MT._hooked = false

local TALENT_DATA -- Deferred until LoD module loads

-- [ HEATMAP COLOR ]-------------------------------------------------------------------------
local function HeatmapColor(pct)
    if pct >= 95 then return COLOR_PINK[1], COLOR_PINK[2], COLOR_PINK[3] end
    if pct >= 75 then return COLOR_PURPLE[1], COLOR_PURPLE[2], COLOR_PURPLE[3] end
    if pct >= 50 then return COLOR_BLUE[1], COLOR_BLUE[2], COLOR_BLUE[3] end
    if pct >= 25 then return COLOR_GREEN[1], COLOR_GREEN[2], COLOR_GREEN[3] end
    return COLOR_GRAY[1], COLOR_GRAY[2], COLOR_GRAY[3]
end

-- [ HELPERS ]-------------------------------------------------------------------------------
local cachedClassKey, cachedSpecKey
local spellToPickRate = {}
local spellCacheDirty = true

local function RefreshPlayerKeys()
    local _, classFile = UnitClass("player")
    local specIndex = GetSpecialization()
    if not specIndex or not classFile then cachedClassKey, cachedSpecKey = nil, nil; return end
    local _, specName = GetSpecializationInfo(specIndex)
    if not specName then cachedClassKey, cachedSpecKey = nil, nil; return end
    cachedClassKey = string.lower(classFile)
    cachedSpecKey = string.lower(specName):gsub(" ", "")
end

function MT:EnsureMetaLoadout()
    -- No-op: ImportLoadout creates the config atomically, no pre-allocation needed
end

local specWatcher = CreateFrame("Frame")
specWatcher:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
specWatcher:RegisterEvent("PLAYER_ENTERING_WORLD")
specWatcher:SetScript("OnEvent", function(_, _, unit)
    if unit == "player" or unit == nil then 
        RefreshPlayerKeys()
        spellCacheDirty = true 
        if MT._active then
            MT:EnsureMetaLoadout()
        end
    end
end)

local activeSpecData = nil
local activeSpecDataKey = nil

local function UpdateActiveSpecData()
    if not TALENT_DATA or not cachedClassKey or not cachedSpecKey then
        if activeSpecData then
            activeSpecData = nil
            activeSpecDataKey = nil
            spellCacheDirty = true
        end
        return nil
    end

    local key = cachedClassKey .. "_" .. cachedSpecKey .. "_" .. (MT.SelectedContent or "Mythic+") .. "_" .. (MT.SelectedDifficulty or "Key 10-15")
    if activeSpecDataKey == key then return activeSpecData end
    
    local contentData = TALENT_DATA[MT.SelectedContent or "Mythic+"]
    local diffData = contentData and contentData[MT.SelectedDifficulty or "Key 10-15"]
    local classData = diffData and diffData[cachedClassKey]
    activeSpecData = classData and classData[cachedSpecKey]
    activeSpecDataKey = key
    spellCacheDirty = true
    
    return activeSpecData
end

local function LookupPickRate(entryID)
    if not cachedClassKey then RefreshPlayerKeys() end
    local specData = UpdateActiveSpecData()
    if not specData then return nil end
    return specData[entryID] or 0
end

-- [ SPELL ID REVERSE LOOKUP ]---------------------------------------------------------------

local function RebuildSpellCache()
    wipe(spellToPickRate)
    spellCacheDirty = false
    
    local specData = UpdateActiveSpecData()
    if not specData then return end
    local configID = C_ClassTalents.GetActiveConfigID()
    if not configID then return end
    local configInfo = C_Traits.GetConfigInfo(configID)
    if not configInfo or not configInfo.treeIDs or not configInfo.treeIDs[1] then return end
    local nodes = C_Traits.GetTreeNodes(configInfo.treeIDs[1])
    if not nodes then return end
    for _, nodeID in ipairs(nodes) do
        local nodeInfo = C_Traits.GetNodeInfo(configID, nodeID)
        if nodeInfo and nodeInfo.entryIDs then
            for _, entryID in ipairs(nodeInfo.entryIDs) do
                local pct = specData[entryID]
                if pct then
                    local entryInfo = C_Traits.GetEntryInfo(configID, entryID)
                    if entryInfo and entryInfo.definitionID then
                        local defInfo = C_Traits.GetDefinitionInfo(entryInfo.definitionID)
                        if defInfo and defInfo.spellID then
                            spellToPickRate[defInfo.spellID] = pct
                        end
                    end
                end
            end
        end
    end
end

-- [ TOOLTIP HOOK ]--------------------------------------------------------------------------
local function HookTooltips()
    TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Spell, function(tooltip, data)
        local db = Orbit.db and Orbit.db.AccountSettings
        if not db or not db.MetaTalentsTooltip then return end
        if not data or not data.id then return end
        if spellCacheDirty then RebuildSpellCache() end
        local pickRate = spellToPickRate[data.id]
        if not pickRate then return end
        local r, g, b = HeatmapColor(pickRate)
        tooltip:AddLine(" ")
        tooltip:AddDoubleLine("WCL Top 100 Pick Rate", string.format("%.1f%%", pickRate), 0.6, 0.6, 0.6, r, g, b)
    end)
end

-- [ TALENT TREE HOOKS ]---------------------------------------------------------------------
local BADGE_ATLAS = "UI-Character-Info-ItemLevel-Bounce"
local BADGE_WIDTH = 64
local BADGE_HEIGHT = 22
local UpdateShapeGlow

local function CreateBadge(button)
    local badge = CreateFrame("Frame", nil, button)
    badge:SetFrameLevel(math.max(1, button:GetFrameLevel() + 2))
    badge:SetSize(42, 16)
    badge:SetPoint("TOP", button, "BOTTOM", 0, 11)

    -- Organic backing shadow scaled up by 30%
    local bg = badge:CreateTexture(nil, "BACKGROUND")
    bg:SetPoint("CENTER")
    bg:SetSize(73, 42)
    bg:SetAtlas("PetJournal-BattleSlot-Shadow")
    bg:SetVertexColor(0, 0, 0, 0.95)

    local fs = badge:CreateFontString(nil, "OVERLAY", FONT_TEMPLATE)
    fs:SetPoint("CENTER", badge, "CENTER", 1, 0)
    fs:SetTextColor(1, 1, 1)
    badge.text = fs
    button._orbitMetaBadge = badge
    return badge
end

local metaSetCache = nil
local metaEntriesCache = nil
local lastMetaSetKey = nil

-- [ BUDGET-AWARE META BUILD ] -------------------------------------------------------
-- Scores every visible node by WCL pick rate, sorts descending, then greedily
-- fills within the point budget using C_Traits currency tracking.
-- Free nodes (cost=0) are always included. Hero branch is auto-detected.
-- Returns: importEntries[], metaSet{entryID=true}
local function BuildOptimalEntries(configID, treeID, specData)
    local treeNodes = C_Traits.GetTreeNodes(treeID)
    if not treeNodes then return {}, {} end
    -- Pre-pass: determine hero branch selection from SubTreeSelection nodes
    local activeSubTreeID = nil
    for _, nodeID in ipairs(treeNodes) do
        local info = C_Traits.GetNodeInfo(configID, nodeID)
        if info and info.type == Enum.TraitNodeType.SubTreeSelection and info.entryIDs then
            local bestEntry, bestRate = nil, 0
            for _, eID in ipairs(info.entryIDs) do
                local rate = specData[eID] or 0
                if rate > bestRate then bestEntry, bestRate = eID, rate end
            end
            -- Fallback: if no WCL data, use the player's current selection
            if not bestEntry and info.activeEntry then bestEntry = info.activeEntry.entryID end
            if bestEntry then
                local eInfo = C_Traits.GetEntryInfo(configID, bestEntry)
                if eInfo and eInfo.subTreeID then activeSubTreeID = eInfo.subTreeID end
            end
        end
    end
    -- Phase 1: Score all eligible nodes
    local candidates = {}
    for _, nodeID in ipairs(treeNodes) do
        local info = C_Traits.GetNodeInfo(configID, nodeID)
        if not info or not info.entryIDs or #info.entryIDs == 0 or not info.isVisible then
            -- skip
        elseif info.subTreeID and activeSubTreeID and info.subTreeID ~= activeSubTreeID then
            -- skip inactive hero branch nodes
        else
            local bestEntry, bestRate = nil, 0
            for _, eID in ipairs(info.entryIDs) do
                local rate = specData[eID] or 0
                if rate > bestRate then bestEntry, bestRate = eID, rate end
            end
            -- Fallback entry for nodes with no WCL data (hero nodes, free roots)
            if not bestEntry then bestEntry = info.entryIDs[1] end
            -- Detect free nodes: zero cost OR condition-granted in the live config
            local isFree, costPerRank, currencyID = false, 0, nil
            local nodeCosts = C_Traits.GetNodeCost(configID, nodeID)
            if not nodeCosts or #nodeCosts == 0 or nodeCosts[1].amount == 0 then
                isFree = true
            elseif info.ranksPurchased == 0 and info.activeRank > 0 and (info.ranksIncreased or 0) == 0 then
                -- Condition-granted node (e.g. class tree root, hero tree root)
                isFree = true
            else
                costPerRank = nodeCosts[1].amount
                currencyID = nodeCosts[1].ID
            end
            -- Is this a hero branch node?
            local isHeroBranch = info.subTreeID and info.subTreeID == activeSubTreeID
            candidates[#candidates + 1] = {
                nodeID = nodeID, info = info,
                bestEntry = bestEntry, bestRate = bestRate,
                isFree = isFree, costPerRank = costPerRank, currencyID = currencyID,
                isHeroBranch = isHeroBranch,
            }
        end
    end
    -- Phase 2: Sort by pick rate descending
    table.sort(candidates, function(a, b) return a.bestRate > b.bestRate end)
    -- Phase 3: Get point budgets per currency
    local currencyInfo = C_Traits.GetTreeCurrencyInfo(configID, treeID, false)
    local budgets = {}
    for _, ci in ipairs(currencyInfo) do
        budgets[ci.traitCurrencyID] = ci.maxQuantity or (ci.quantity + ci.spent)
    end
    -- Helper: add a single candidate to the import entries
    local importEntries, metaSet = {}, {}
    local function AddEntry(cand)
        local info = cand.info
        local isChoice = info.type == Enum.TraitNodeType.Selection or info.type == Enum.TraitNodeType.SubTreeSelection
        local isTiered = info.type == Enum.TraitNodeType.Tiered
        local maxRanks = info.maxRanks or 1
        metaSet[cand.bestEntry] = true
        if isTiered then
            local remaining = maxRanks
            for _, eID in ipairs(info.entryIDs) do
                if remaining <= 0 then break end
                local eInfo = C_Traits.GetEntryInfo(configID, eID)
                local r = math.min(remaining, eInfo and eInfo.maxRanks or 1)
                importEntries[#importEntries + 1] = { nodeID = cand.nodeID, ranksGranted = 0, ranksPurchased = r, selectionEntryID = eID }
                metaSet[eID] = true
                remaining = remaining - r
            end
        elseif isChoice then
            importEntries[#importEntries + 1] = { nodeID = cand.nodeID, ranksGranted = 0, ranksPurchased = 1, selectionEntryID = cand.bestEntry }
        else
            importEntries[#importEntries + 1] = { nodeID = cand.nodeID, ranksGranted = 0, ranksPurchased = maxRanks, selectionEntryID = cand.bestEntry }
        end
    end
    -- Phase 4: Iterate all candidates with priority logic
    -- Free nodes → always include. Hero branch → always include. Paid → budget-gated.
    for _, cand in ipairs(candidates) do
        if not cand.bestEntry then
            -- No entry available, skip
        elseif cand.isFree then
            AddEntry(cand)
        elseif cand.isHeroBranch then
            local purchaseRanks = (cand.info.type == Enum.TraitNodeType.Selection) and 1 or (cand.info.maxRanks or 1)
            local totalCost = cand.costPerRank * purchaseRanks
            local cID = cand.currencyID
            if cID and budgets[cID] then budgets[cID] = budgets[cID] - totalCost end
            AddEntry(cand)
        elseif cand.bestRate >= MIN_PICK_RATE then
            local isChoice = cand.info.type == Enum.TraitNodeType.Selection or cand.info.type == Enum.TraitNodeType.SubTreeSelection
            local purchaseRanks = isChoice and 1 or (cand.info.maxRanks or 1)
            local totalCost = cand.costPerRank * purchaseRanks
            local cID = cand.currencyID
            if cID and budgets[cID] and budgets[cID] >= totalCost then
                budgets[cID] = budgets[cID] - totalCost
                AddEntry(cand)
            end
        end
    end
    return importEntries, metaSet
end

-- Cached wrapper: computes once per spec/content/difficulty change
local function ComputeMetaBuild()
    local specData = UpdateActiveSpecData()
    if not specData then return nil, nil end
    if metaSetCache and lastMetaSetKey == activeSpecDataKey then return metaEntriesCache, metaSetCache end
    local configID = C_ClassTalents.GetActiveConfigID()
    if not configID then return nil, nil end
    local configInfo = C_Traits.GetConfigInfo(configID)
    if not configInfo or not configInfo.treeIDs or not configInfo.treeIDs[1] then return nil, nil end
    metaEntriesCache, metaSetCache = BuildOptimalEntries(configID, configInfo.treeIDs[1], specData)
    lastMetaSetKey = activeSpecDataKey
    return metaEntriesCache, metaSetCache
end

local function GetMetaLoadoutSet()
    local _, metaSet = ComputeMetaBuild()
    return metaSet
end

local function ApplyHeatmap(button)
    -- Suppress native rigid Tier separators piercing through the Orbit circular geometry
    if button.Divider then
        button.Divider:SetAlpha(0)
    end

    local db = Orbit.db and Orbit.db.AccountSettings
    local enabled = db and db.MetaTalentsTree
    if not enabled then

        if button._orbitMetaBadge then 
            button._orbitMetaBadge:Hide()
        end
        return
    end
    local metaConfigID = C_ClassTalents.GetActiveConfigID()
    local nID = button.GetNodeID and button:GetNodeID()
    local info = nID and metaConfigID and C_Traits.GetNodeInfo(metaConfigID, nID)
    
    local activeRank = info and info.activeRank or 0
    if button.nodeInfo and button.nodeInfo.ranksPurchased then
        activeRank = button.nodeInfo.ranksPurchased
    end
    local isSelected = activeRank > 0
    local isChoiceNode = info and info.type == Enum.TraitNodeType.Selection
    local pickRate = nil

    if isSelected and isChoiceNode then
        local pickedEntryID = (info and info.activeEntry and info.activeEntry.entryID) or (button.GetEntryID and button:GetEntryID())
        if pickedEntryID then
            pickRate = LookupPickRate(pickedEntryID)
        end
    else
        local bestPickRate = nil
        if info and info.entryIDs then
            for _, eID in ipairs(info.entryIDs) do
                local rate = LookupPickRate(eID)
                if rate and (not bestPickRate or rate > bestPickRate) then
                    bestPickRate = rate
                end
            end
        end
        local entryID = button.GetEntryID and button:GetEntryID()
        pickRate = bestPickRate or (entryID and LookupPickRate(entryID))
    end

    if pickRate then
        local r, g, b = HeatmapColor(pickRate)

        -- Badge
        local badge = button._orbitMetaBadge or CreateBadge(button)
        
        local gs = Orbit.db and Orbit.db.GlobalSettings
        local fontPath = gs and gs.Font and LibStub("LibSharedMedia-3.0"):Fetch("font", gs.Font) or "Fonts\\FRIZQT__.TTF"
        badge.text:SetFont(fontPath, 13, Orbit.Skin:GetFontOutline())
        Orbit.Skin:ApplyFontShadow(badge.text)
        
        badge.text:SetText(math.floor(pickRate) .. "%")
        badge.text:SetTextColor(r, g, b, 1)
        badge:Show()

        -- Clean up legacy flat bounds
        if Orbit.Skin and Orbit.Skin.ClearHighlightBorder then
            Orbit.Skin:ClearHighlightBorder(button, "_orbitMetaRed")
            Orbit.Skin:ClearHighlightBorder(button, "_orbitMetaGreen")
        end

        if not UpdateShapeGlow then
        UpdateShapeGlow = function(button, glowType)
            if not glowType then
                if button._orbitShapeGlow then button._orbitShapeGlow:Hide() end
                return
            end
            local glow = button._orbitShapeGlow
            if not glow then
                glow = button:CreateTexture(nil, "BACKGROUND", nil, -3)
                glow:SetBlendMode("ADD")
                glow:SetPoint("CENTER", button, "CENTER")
                button._orbitShapeGlow = glow
            end
            local atlas = "talents-node-circle-greenglow"
            local refAtlas = (button.StateBorder and button.StateBorder:GetAtlas()) or (button.Border and button.Border:GetAtlas())
            if refAtlas then
                if string.find(refAtlas, "choice", 1, true) then
                    atlas = "talents-node-choice-greenglow"
                elseif string.find(refAtlas, "square", 1, true) then
                    atlas = "talents-node-square-greenglow"
                end
            end
            glow:SetAtlas(atlas)
            glow:ClearAllPoints()
            local anchor = button.StateBorder or button
            glow:SetPoint("TOPLEFT", anchor, "TOPLEFT", -14, 14)
            glow:SetPoint("BOTTOMRIGHT", anchor, "BOTTOMRIGHT", 14, -14)
            glow:SetDesaturation(1)
            if glowType == "RED" then
                glow:SetVertexColor(1, 0, 0, 1)
            else
                glow:SetVertexColor(0, 1, 0, 1)
            end
            glow:Show()
        end
        end

        local metaSet = GetMetaLoadoutSet()
        local glowReq = nil

        if metaSet and info then
            local pickedEntryID = (info.activeEntry and info.activeEntry.entryID) or (button.GetEntryID and button:GetEntryID())
            
            local function IsLegallyPurchasable(cfg, nid, eid, itype, avail)
                if itype == 3 then return avail end
                return C_Traits.CanPurchaseRank(cfg, nid, eid)
            end

            if isSelected then
                if isChoiceNode then
                    if pickedEntryID and not metaSet[pickedEntryID] then
                        glowReq = "RED"
                    end
                else
                    local isMetaPath = false
                    if pickedEntryID and metaSet[pickedEntryID] then
                        isMetaPath = true
                    elseif info.entryIDs then
                        for _, eID in ipairs(info.entryIDs) do
                            if metaSet[eID] then
                                isMetaPath = true
                                break
                            end
                        end
                    end
                    
                    if not isMetaPath then
                        glowReq = "RED"
                    elseif activeRank < (info.maxRanks or 1) then
                        local canBuy = false
                        if info.entryIDs then
                            for _, eID in ipairs(info.entryIDs) do
                                if IsLegallyPurchasable(metaConfigID, nID, eID, info.type, info.isAvailable) then
                                    canBuy = true
                                    break
                                end
                            end
                        end
                        if canBuy then
                            glowReq = "GREEN"
                        end
                    end
                end
            else
                if info.entryIDs then
                    for _, eID in ipairs(info.entryIDs) do
                        if metaSet[eID] and IsLegallyPurchasable(metaConfigID, nID, eID, info.type, info.isAvailable) then
                            glowReq = "GREEN"
                            break
                        end
                    end
                end
            end
        end

        UpdateShapeGlow(button, glowReq)
    else

        if button._orbitMetaBadge then 
            button._orbitMetaBadge:Hide()
        end
        if button._orbitShapeGlow then button._orbitShapeGlow:Hide() end
    end
end

local function HookTalentTree()
    if MT._hooked then return end
    EventUtil.ContinueOnAddOnLoaded("Blizzard_SharedTalentUI", function()
        if MT._hooked then return end
        MT._hooked = true
        hooksecurefunc(TalentButtonArtMixin, "UpdateStateBorder", ApplyHeatmap)
        
        -- In WoW 11.0, PlayerSpellsFrame replaced ClassTalentFrame
        EventUtil.ContinueOnAddOnLoaded("Blizzard_PlayerSpells", function()
            if PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame then
                hooksecurefunc(PlayerSpellsFrame.TalentsFrame, "UpdateTreeCurrencyInfo", function()
                    for button in PlayerSpellsFrame.TalentsFrame:EnumerateAllTalentButtons() do
                        ApplyHeatmap(button)
                    end
                end)
            end
            
            -- Reskin Blizzard's Native Overlay Cast Bar (Used for "Applying Talents")
            if OverlayPlayerCastingBarFrame and not OverlayPlayerCastingBarFrame._orbitSkinReskinned then
                OverlayPlayerCastingBarFrame._orbitSkinReskinned = true
                
                local bar = OverlayPlayerCastingBarFrame
                bar:SetStatusBarTexture("") -- Hide the fill texture
                
                -- Hide all native regions that make up the frame borders and glows
                if bar.Border then bar.Border:SetAlpha(0) end
                if bar.Background then bar.Background:SetAlpha(0) end
                if bar.TextBorder then bar.TextBorder:SetAlpha(0) end
                if bar.Flash then bar.Flash:SetAlpha(0) end
                if bar.Spark then bar.Spark:SetAlpha(0) end
                if bar.EnergyGlow then bar.EnergyGlow:SetAlpha(0) end
                if bar.ChargeGlow then bar.ChargeGlow:SetAlpha(0) end
                if bar.BorderShield then bar.BorderShield:SetAlpha(0) end
                if bar.DropShadow then bar.DropShadow:SetAlpha(0) end
                if bar.InterruptGlow then bar.InterruptGlow:SetAlpha(0) end
                
                -- Aggressively kill native animations attempting to flash or shake the bar (e.g. on interrupt)
                local nativeAnims = { "InterruptGlowAnim", "InterruptShakeAnim", "InterruptSparkAnim", "FlashLoopingAnim", "FlashAnim" }
                for _, animName in ipairs(nativeAnims) do
                    if bar[animName] then
                        hooksecurefunc(bar[animName], "Play", function(self) self:Stop() end)
                        bar[animName]:Stop()
                    end
                end
                
                local SAFE_TYPE_INFO = { filling = "", full = "", glow = "" }
                bar.GetTypeInfo = function() return SAFE_TYPE_INFO end
                bar.ShowSpark = function() end
                bar.HideSpark = function() end
                bar.PlayFinishAnim = function() end
                bar.PlayFadeAnim = function(self)
                    if self.FadeOutAnim and self:GetAlpha() > 0 and self:IsVisible() and not self.isInEditMode then
                        self.FadeOutAnim:Play()
                    end
                end
                bar.PlayInterruptAnims = function() end
                bar.StopFinishAnims = function(self)
                    if self.FlashAnim then self.FlashAnim:Stop() end
                    if self.FadeOutAnim then self.FadeOutAnim:Stop() end
                    if self.StandardFinish then self.StandardFinish:Stop() end
                    if self.ChannelFinish then self.ChannelFinish:Stop() end
                    if self.StageFinish then self.StageFinish:Stop() end
                    if self.CraftingFinish then self.CraftingFinish:Stop() end
                end
                bar.StopAnims = function(self)
                    self:StopInterruptAnims()
                    self:StopFinishAnims()
                end
                local host = PlayerSpellsFrame.TalentsFrame
                
                -- Move the native BottomBar up to BORDER so it can overhang our spark
                if host.BottomBar then
                    host.BottomBar:SetDrawLayer("BORDER", 7)
                end
                
                -- 2px Gold Status Fill - BORDER sublevel 5, under BottomBar (BORDER,7)
                local fill = host:CreateTexture(nil, "BORDER", nil, 5)
                fill:SetColorTexture(1, 0.82, 0, 1)
                fill:SetPoint("LEFT", host.BottomBar, "TOPLEFT", 0, 1)
                fill:SetHeight(2)
                fill:Hide()
                bar._orbitFill = fill
                
                -- Rolling Star Spark - BORDER sublevel 6, under BottomBar (BORDER,7)
                local spark = host:CreateTexture(nil, "BORDER", nil, 6)
                spark:SetTexture("Interface\\Cooldown\\star4")
                spark:SetBlendMode("ADD")
                spark:SetSize(35, 35)
                spark:SetVertexColor(1, 0.85, 0.2, 1)
                spark:Hide()
                
                -- Animate the star to roll continuously
                local animGroup = spark:CreateAnimationGroup()
                local rot = animGroup:CreateAnimation("Rotation")
                rot:SetDegrees(-360)
                rot:SetDuration(1.5)
                animGroup:SetLooping("REPEAT")
                
                bar._orbitSparkGlow = spark
                bar._orbitAnimGroup = animGroup
                
                -- Engine visibility binding
                bar:HookScript("OnShow", function(self)
                    if self._orbitFill then self._orbitFill:Show() end
                    if self._orbitSparkGlow then self._orbitSparkGlow:Show() end
                    if self._orbitAnimGroup then self._orbitAnimGroup:Play() end
                end)
                
                bar:HookScript("OnHide", function(self)
                    if self._orbitFill then self._orbitFill:Hide() end
                    if self._orbitSparkGlow then self._orbitSparkGlow:Hide() end
                end)
                
                -- Keep text centered relative to the traveling spark but lowered by ~50px
                if bar.Text then
                    local fontName = Orbit.db and Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.Font or "Fonts\\FRIZQT__.TTF"
                    bar.Text:SetFont(fontName, 12, "OUTLINE")
                    bar.Text:ClearAllPoints()
                    bar.Text:SetPoint("BOTTOM", spark, "TOP", 0, -46)
                end
                
                hooksecurefunc(bar, "SetValue", function(self, value)
                    local minV, maxV = self:GetMinMaxValues()
                    if not minV or not maxV or maxV == 0 then return end
                    
                    -- Blizzard aggressively resets textures depending on the Cast Look. 
                    -- Forcing alpha to 0 organically every frame obliterates any remnants.
                    local tex = self:GetStatusBarTexture()
                    if tex then tex:SetAlpha(0) end
                    if self.Spark then self.Spark:SetAlpha(0) end
                    if self.Flash then self.Flash:SetAlpha(0) end
                    if self.Border then self.Border:SetAlpha(0) end
                    
                    local progress = (value - minV) / (maxV - minV)
                    local width = host.BottomBar:GetWidth()
                    local offset = progress * width
                    
                    self._orbitFill:SetWidth(math.max(0.001, offset))
                    
                    self._orbitSparkGlow:ClearAllPoints()
                    -- Reanchor explicitly along the host BottomBar for accurate tracking since it belongs to TalentsFrame now
                    self._orbitSparkGlow:SetPoint("CENTER", host.BottomBar, "TOPLEFT", offset, 1)
                end)
                
                hooksecurefunc(PlayerSpellsFrame.TalentsFrame, "SetCommitCastBarActive", function(self, active)
                    -- We just let the invisible native Cast Bar do its thing in DIALOG space
                end)
                
                -- Suppress native spinning commit visuals on staged nodes
                hooksecurefunc(PlayerSpellsFrame.TalentsFrame, "SetCommitVisualsActive", function(self, active)
                    if self.FxModelScene then self.FxModelScene:ClearEffects() end
                end)
            end
        end)
        
        -- Custom SpendText formatting logic
        hooksecurefunc(TalentButtonUtil, "SetSpendText", function(button, spendText)
            if not button.SpendText then return end
            
            -- CASE 1: Capstone Pip (Child nodes) -> Completely suppress native text and visual artifacts
            if button.CalculateSpendStateForPip then
                button.SpendText:SetText("")
                if button.spendTextShadows then
                    for _, shadow in ipairs(button.spendTextShadows) do
                        shadow:SetAlpha(0)
                    end
                end
                
                button:SetAlpha(0)
                button:EnableMouse(false)
                if button._orbitMetaBadge then button._orbitMetaBadge:Hide() end
                if button._orbitShapeGlow then button._orbitShapeGlow:Hide() end
                
                return
            end

            -- CASE 2: Capstone Master Node -> Aggregate tracker to the right
            if button.trackPipArray and button.nodeInfo then
                local current = button.nodeInfo.ranksPurchased or 0
                local maxRanks = button.nodeInfo.totalMaxRanks or 4
                local text = current .. "/" .. maxRanks
                
                button.SpendText:SetText(text)
                button.SpendText:Show()
                
                button.SpendText:ClearAllPoints()
                button.SpendText:SetPoint("LEFT", button, "RIGHT", 8, 0)
                
                if not button._orbitCapstoneTextAdjusted then
                    button._orbitCapstoneTextAdjusted = true
                    local _, size = button.SpendText:GetFont()
                    if size then
                        local fontName = Orbit.db and Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.Font
                        Orbit.Skin:SkinText(button.SpendText, { font = fontName, textSize = size + 2 })
                        
                        -- Hide native text shadow layers, Orbit provides hardware strings
                        if button.spendTextShadows then
                            for _, shadow in ipairs(button.spendTextShadows) do
                                shadow:SetAlpha(0)
                            end
                        end
                    end
                end
                return
            end

            -- CASE 3: Standard Talent Node -> Scale down by 2, move to TOPRIGHT, and skin
            if not button._orbitSpendTextAdjusted then
                button._orbitSpendTextAdjusted = true
                local _, size = button.SpendText:GetFont()
                if size then
                    local newSize = math.max(6, size - 2)
                    local fontName = Orbit.db and Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.Font
                    Orbit.Skin:SkinText(button.SpendText, { font = fontName, textSize = newSize })
                    
                    if button.spendTextShadows then
                        for _, shadow in ipairs(button.spendTextShadows) do
                            shadow:SetAlpha(0)
                        end
                    end
                end
            end
            
            button.SpendText:ClearAllPoints()
            button.SpendText:SetPoint("TOPRIGHT", button, "TOPRIGHT", -2, -2)
        end)

        -- Force UI refresh when dropdowns change
        local function RefreshMetaUI()
            spellCacheDirty = true
            metaSetCache = nil
            metaEntriesCache = nil
            lastMetaSetKey = nil
            if PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame then
                for talentButton in PlayerSpellsFrame.TalentsFrame:EnumerateAllTalentButtons() do
                    ApplyHeatmap(talentButton)
                    if talentButton.trackPipArray then
                        for _, pipFrame in ipairs(talentButton.trackPipArray) do
                            ApplyHeatmap(pipFrame)
                        end
                    end
                end
            end
            if MT.UpdateApplyButtonState then MT.UpdateApplyButtonState() end
        end

        -- [ IMPORT ENTRY BUILDER ] ----------------------------------------------------------
        -- Uses the shared BuildOptimalEntries via ComputeMetaBuild cache.

        function MT:ApplyMetaBuild()
            if not TALENT_DATA then return end
            if not cachedClassKey then RefreshPlayerKeys() end
            if not cachedClassKey or not cachedSpecKey then return end
            local specData = UpdateActiveSpecData()
            if not specData then
                print("|cffff0000[Orbit]|r MetaTalents: No data available for this spec/content combination.")
                return
            end

            local talentsFrame = PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame
            if not talentsFrame then return end

            local configID = talentsFrame:GetConfigID()
            local treeInfo = talentsFrame:GetTreeInfo()
            if not configID or not treeInfo then return end

            local entries = ComputeMetaBuild()
            if #entries == 0 then
                print("|cffff0000[Orbit]|r MetaTalents: No matching nodes found for this build.")
                return
            end

            -- Collect any existing "Orbit Loadout" config IDs for cleanup AFTER import
            local specID = PlayerUtil.GetCurrentSpecID()
            local oldMetaIDs = {}
            local oldMetaSet = {}
            local existingIDs = C_ClassTalents.GetConfigIDsBySpecID(specID)
            if existingIDs then
                for _, id in ipairs(existingIDs) do
                    local cInfo = C_Traits.GetConfigInfo(id)
                    if cInfo and cInfo.name == "Orbit Loadout" then
                        oldMetaIDs[#oldMetaIDs + 1] = id
                        oldMetaSet[id] = true
                    end
                end
            end

            -- Import first while the active config is still valid
            local success, errorString = C_ClassTalents.ImportLoadout(configID, entries, "Orbit Loadout")
            if success then
                -- Notify TalentsFrame so it auto-loads the new config on TRAIT_CONFIG_CREATED
                talentsFrame:OnTraitConfigCreateStarted(true)
                -- Enable shared action bars on the newly created config
                local newConfigIDs = C_ClassTalents.GetConfigIDsBySpecID(specID)
                if newConfigIDs then
                    for _, id in ipairs(newConfigIDs) do
                        local cInfo = C_Traits.GetConfigInfo(id)
                        if cInfo and cInfo.name == "Orbit Loadout" and not oldMetaSet[id] then
                            C_ClassTalents.SetUsesSharedActionBars(id, true)
                            -- Route through the UI framework rather than the raw C_API so the organic Cast Bar visually triggers!
                            talentsFrame:SetSelectedSavedConfigID(id, true)
                            break
                        end
                    end
                end
                -- Clean up old duplicates after new config exists
                for _, oldID in ipairs(oldMetaIDs) do
                    C_ClassTalents.DeleteConfig(oldID)
                end
                print("|cff00ff00[Orbit]|r Orbit Loadout applied.")
            else
                print("|cffff0000[Orbit]|r MetaTalents: Import failed: " .. tostring(errorString))
            end
        end

        -- Deferred UI Setup for SearchBox and Dropdowns
        local function SetupOrbitDropdowns()
            if MT._dropdownsSetup then return end
            if not (PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame and PlayerSpellsFrame.TalentsFrame.SearchBox) then return end
            
            MT._dropdownsSetup = true

            local searchBox = PlayerSpellsFrame.TalentsFrame.SearchBox
            hooksecurefunc(searchBox, "SetPoint", function(self)
                if self._orbitOverriding then return end
                self._orbitOverriding = true
                self:ClearAllPoints()
                self:SetPoint("LEFT", PlayerSpellsFrame.TalentsFrame.ApplyButton, "RIGHT", 150, 0)
                self._orbitOverriding = false
            end)
            searchBox:ClearAllPoints()
            searchBox:SetPoint("LEFT", PlayerSpellsFrame.TalentsFrame.ApplyButton, "RIGHT", 150, 0)

            -- Standardize LoadSystem (Native Profile Dropdown) position and width (including inner native child)
            local loadSys = PlayerSpellsFrame.TalentsFrame.LoadSystem
            hooksecurefunc(loadSys, "SetPoint", function(self)
                if self._orbitOverriding then return end
                self._orbitOverriding = true
                self:ClearAllPoints()
                self:SetPoint("LEFT", PlayerSpellsFrame.TalentsFrame.BottomBar, "LEFT", 33, 0)
                self:SetWidth(150)
                if self.Dropdown then self.Dropdown:SetWidth(150) end
                self._orbitOverriding = false
            end)
            loadSys._orbitOverriding = true
            loadSys:ClearAllPoints()
            loadSys:SetPoint("LEFT", PlayerSpellsFrame.TalentsFrame.BottomBar, "LEFT", 33, 0)
            loadSys:SetWidth(150)
            if loadSys.Dropdown then loadSys.Dropdown:SetWidth(150) end
            loadSys._orbitOverriding = false

            -- M+ Dungeon list (alphabetical)
            local MPLUS_DUNGEONS = {
                "Algeth'ar Academy",
                "Magisters' Terrace",
                "Maisara Caverns",
                "Nexus-Point Xenas",
                "Pit of Saron",
                "Seat of the Triumvirate",
                "Skyreach",
                "Windrunner Spire",
            }
            local MPLUS_DUNGEON_SET = {}
            for _, name in ipairs(MPLUS_DUNGEONS) do MPLUS_DUNGEON_SET[name] = true end

            MT.SelectedContent = MT.SelectedContent or MPLUS_DUNGEONS[1]
            MT.SelectedDifficulty = MT.SelectedDifficulty or "Mythic+"

            -- 1. Content Type Dropdown
            local contentDropdown = CreateFrame("DropdownButton", "OrbitMetaTalentContentDropdown", PlayerSpellsFrame.TalentsFrame, "WowStyle1DropdownTemplate")
            contentDropdown:SetPoint("LEFT", PlayerSpellsFrame.TalentsFrame.LoadSystem, "RIGHT", 15, 0)
            contentDropdown:SetWidth(150)
            contentDropdown:SetMenuAnchor(AnchorUtil.CreateAnchor("BOTTOMLEFT", contentDropdown, "TOPLEFT", 0, 0))
            
            -- We need diffDropdown declared before we use it in the contentDropdown callback
            local diffDropdown = CreateFrame("DropdownButton", "OrbitMetaTalentDifficultyDropdown", PlayerSpellsFrame.TalentsFrame, "WowStyle1DropdownTemplate")
            diffDropdown:SetMenuAnchor(AnchorUtil.CreateAnchor("BOTTOMLEFT", diffDropdown, "TOPLEFT", 0, 0))

            contentDropdown:SetupMenu(function(dropdown, rootDescription)
                -- M+ Dungeons section
                rootDescription:CreateTitle("Mythic+ Dungeons")
                for _, dungeon in ipairs(MPLUS_DUNGEONS) do
                    rootDescription:CreateRadio(dungeon,
                        function() return MT.SelectedContent == dungeon end,
                        function()
                            MT.SelectedContent = dungeon
                            MT.SelectedDifficulty = "Mythic+"
                            if diffDropdown.GenerateMenu then diffDropdown:GenerateMenu() end
                            RefreshMetaUI()
                        end
                    )
                end

                -- Raid Bosses section
                rootDescription:CreateDivider()
                rootDescription:CreateTitle("Raid Bosses")
                local raidBosses = {
                    "Imperator Averzian",
                    "Vorasius",
                    "Fallen-King Salhadaar",
                    "Vaelgor & Ezzorak",
                    "Lightblinded Vanguard",
                    "Crown of the Cosmos",
                    "Chimaerus, the Undreamt God",
                    "Belo'ren, Child of Al'ar",
                    "Midnight Falls"
                }
                for _, boss in ipairs(raidBosses) do
                    rootDescription:CreateRadio(boss,
                        function() return MT.SelectedContent == boss end,
                        function()
                            MT.SelectedContent = boss
                            MT.SelectedDifficulty = "Heroic"
                            if diffDropdown.GenerateMenu then diffDropdown:GenerateMenu() end
                            RefreshMetaUI()
                        end
                    )
                end
            end)

            -- 2. Difficulty Dropdown
            diffDropdown:SetPoint("LEFT", contentDropdown, "RIGHT", 15, 0)
            diffDropdown:SetWidth(150)
            diffDropdown:SetupMenu(function(dropdown, rootDescription)
                local options
                if MPLUS_DUNGEON_SET[MT.SelectedContent] then
                    options = { "Mythic+" }
                else
                    options = { "Normal", "Heroic", "Mythic" }
                end
                for _, opt in ipairs(options) do
                    rootDescription:CreateRadio(opt,
                        function() return MT.SelectedDifficulty == opt end,
                        function()
                            MT.SelectedDifficulty = opt
                            RefreshMetaUI()
                        end
                    )
                end
            end)

            contentDropdown:Show()
            diffDropdown:Show()

            if not MT._toggleBtn then
                local toggleBtn = CreateFrame("Button", nil, PlayerSpellsFrame.TalentsFrame)
                toggleBtn:SetSize(28, 28)
                -- Push it cleanly above the ButtonsParent wall so it catches clicks!
                toggleBtn:SetFrameLevel(9900)
                -- Align flush with the left-edge of the loadout selector, but float it above the footer
                toggleBtn:SetPoint("BOTTOMLEFT", PlayerSpellsFrame.TalentsFrame.BottomBar, "TOPLEFT", 33, 20)
                
                local function UpdateToggleIcon()
                    local enabled = Orbit.db and Orbit.db.AccountSettings and Orbit.db.AccountSettings.MetaTalentsTree
                    if enabled then
                        toggleBtn:SetNormalAtlas("common-icon-visual")
                    else
                        toggleBtn:SetNormalAtlas("common-icon-visual-disabled")
                    end
                end
                UpdateToggleIcon()

                toggleBtn:SetPushedAtlas("common-icon-visual-pressed")
                
                local thl = toggleBtn:CreateTexture(nil, "HIGHLIGHT")
                thl:SetAllPoints()
                thl:SetAtlas("common-icon-visual")
                thl:SetBlendMode("ADD")
                thl:SetAlpha(0.2)
                
                toggleBtn:SetScript("OnEnter", function(self)
                    GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                    GameTooltip:SetText("Toggle Meta Heatmap", 1, 1, 1)
                    GameTooltip:Show()
                end)
                toggleBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
                
                toggleBtn:SetScript("OnClick", function()
                    if Orbit.db and Orbit.db.AccountSettings then
                        Orbit.db.AccountSettings.MetaTalentsTree = not Orbit.db.AccountSettings.MetaTalentsTree
                        UpdateToggleIcon()
                        RefreshMetaUI()
                    end
                end)
                
                MT._toggleBtn = toggleBtn
            end

            if not MT._applyBtn then
                local applyMetaBtn = CreateFrame("Button", nil, PlayerSpellsFrame.TalentsFrame)
                applyMetaBtn:SetSize(28, 28)
                -- Conform to the exact 15px UI spacing interval
                applyMetaBtn:SetPoint("LEFT", diffDropdown, "RIGHT", 15, 0)
                
                applyMetaBtn:SetNormalAtlas("128-RedButton-Plus")
                applyMetaBtn:SetPushedAtlas("128-RedButton-Plus-Pressed")
                applyMetaBtn:SetDisabledAtlas("128-RedButton-Plus-Disabled")
                
                local hl = applyMetaBtn:CreateTexture(nil, "HIGHLIGHT")
                hl:SetAllPoints()
                hl:SetAtlas("128-RedButton-Plus")
                hl:SetBlendMode("ADD")
                hl:SetAlpha(0.2)
                
                applyMetaBtn:SetScript("OnEnter", function(self)
                    GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                    if self._isApplied then
                        GameTooltip:SetText("Orbit Loadout Already Active", 0.5, 0.5, 0.5)
                    else
                        GameTooltip:SetText("Apply Orbit Loadout", 1, 1, 1)
                    end
                    GameTooltip:Show()
                end)
                applyMetaBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
                
                applyMetaBtn:SetScript("OnClick", function(self)
                    if self._isApplied then return end
                    MT:ApplyMetaBuild()
                end)
                MT._applyBtn = applyMetaBtn
            end

            -- Update apply button based on whether current talents already match meta build
            local lastMatchState = nil
            local function UpdateApplyButtonState()
                local btn = MT._applyBtn
                if not btn then return end
                if not TALENT_DATA then return end
                if not cachedClassKey then RefreshPlayerKeys() end
                local talentsFrame = PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame
                if not talentsFrame then return end
                local configID = talentsFrame:GetConfigID()
                local treeInfo = talentsFrame:GetTreeInfo()
                if not configID or not treeInfo then return end
                local metaSet = GetMetaLoadoutSet()
                if not metaSet then return end
                -- Check if every meta entry is currently active (early-exit on first mismatch)
                local allMatch = true
                local treeNodes = C_Traits.GetTreeNodes(treeInfo.ID)
                if not treeNodes then return end
                for _, nodeID in ipairs(treeNodes) do
                    local nodeInfo = C_Traits.GetNodeInfo(configID, nodeID)
                    if nodeInfo and nodeInfo.entryIDs then
                        for _, eID in ipairs(nodeInfo.entryIDs) do
                            if metaSet[eID] then
                                local activeEntry = nodeInfo.activeEntry
                                if not activeEntry or activeEntry.entryID ~= eID or nodeInfo.activeRank < 1 then
                                    allMatch = false
                                    break
                                end
                            end
                        end
                        if not allMatch then break end
                    end
                end
                -- Skip redundant UI updates if state hasn't changed
                if lastMatchState == allMatch then return end
                lastMatchState = allMatch
                btn._isApplied = allMatch
                local tex = btn:GetNormalTexture()
                if tex then tex:SetDesaturated(allMatch) end
                btn:SetAlpha(allMatch and 0.4 or 1.0)
            end
            MT.UpdateApplyButtonState = UpdateApplyButtonState

            -- Hook config changes with debounce to avoid stacking timers
            local pendingUpdate = false
            local stateWatcher = CreateFrame("Frame")
            stateWatcher:RegisterEvent("TRAIT_CONFIG_CREATED")
            stateWatcher:RegisterEvent("TRAIT_CONFIG_DELETED")
            stateWatcher:RegisterEvent("TRAIT_CONFIG_UPDATED")
            stateWatcher:RegisterEvent("SELECTED_LOADOUT_CHANGED")
            stateWatcher:RegisterEvent("TRAIT_CONFIG_LIST_UPDATED")
            stateWatcher:SetScript("OnEvent", function()
                if pendingUpdate then return end
                pendingUpdate = true
                C_Timer.After(0.1, function()
                    pendingUpdate = false
                    UpdateApplyButtonState()
                end)
            end)
            UpdateApplyButtonState()
        end

        -- Try applying immediately if already loaded
        SetupOrbitDropdowns()

        -- Experimental: Relocate Apex (Capstone) nodes centered underneath the Hero Talent tree
        hooksecurefunc(TalentButtonUtil, "ApplyPosition", function(button, talentFrame, posX, posY)
            SetupOrbitDropdowns()
            
            -- Check if this is an apex/capstone button (has the progress track mixin elements)
            if button.nodeInfo and button.Track and button.Track.ProgressBar then
                local heroContainer = PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame and PlayerSpellsFrame.TalentsFrame.HeroTalentsContainer
                if heroContainer and heroContainer:IsShown() then
                    local targetBox = heroContainer.ExpandedContainer or heroContainer
                    C_Timer.After(0, function() -- wait 1 frame for layout to settle
                        -- Reparent to escape the scroll container's clipping boundaries
                        button:SetParent(heroContainer)
                        button:SetFrameLevel(heroContainer:GetFrameLevel() + 5)
                        button:ClearAllPoints()
                        button:SetPoint("TOP", targetBox, "BOTTOM", 0, -10)
                    end)
                end
            end
        end)

        -- Flatten the Apex capstone pips horizontally so the percentage badges don't overlap
        if TalentButtonCapstoneWithTrackMixin then
            hooksecurefunc(TalentButtonCapstoneWithTrackMixin, "UpdateTrack", function(button)
                if not button.trackPipArray then return end
                local spacing = 55
                for index, pipFrame in ipairs(button.trackPipArray) do
                    pipFrame:ClearAllPoints()
                    if index == 1 then
                        pipFrame:SetPoint("CENTER", button, "BOTTOM", -spacing, -30)
                    elseif index == 2 then
                        pipFrame:SetPoint("CENTER", button, "BOTTOM", 0, -30)
                    elseif index == 3 then
                        pipFrame:SetPoint("CENTER", button, "BOTTOM", spacing, -30)
                    end
                end
                
                -- Hide the curved native backing bar so it doesn't conflict with our straight layout
                if button.Track and button.Track.ProgressBar then
                    button.Track.ProgressBar:SetAlpha(0)
                end
            end)
        end
    end)
end

-- [ LIFECYCLE ]-----------------------------------------------------------------------------
function MT:Enable()
    if self._active then return end
    C_AddOns.EnableAddOn(LOD_ADDON)
    local loaded, reason = C_AddOns.LoadAddOn(LOD_ADDON)
    if not loaded then
        print("|cffff0000[Orbit]|r MetaTalents disabled: Data module missing or failed to load. Reason: " .. tostring(reason))
        return
    end
    TALENT_DATA = Orbit.Data and Orbit.Data.TalentMeta
    if not TALENT_DATA then 
        print("|cffff0000[Orbit]|r MetaTalents disabled: Module loaded but Orbit.Data.TalentMeta is empty.")
        return 
    end
    self._active = true
    HookTooltips()
    HookTalentTree()
    MT:EnsureMetaLoadout()
end

function MT:Disable()
    self._active = false
    C_AddOns.DisableAddOn(LOD_ADDON)
end

-- [ AUTO-ENABLE ON LOGIN ]------------------------------------------------------------------
local loader = CreateFrame("Frame")
loader:RegisterEvent("PLAYER_LOGIN")
loader:SetScript("OnEvent", function()
    C_Timer.After(0.5, function()
        local db = Orbit.db and Orbit.db.AccountSettings
        if db and (db.MetaTalentsTooltip or db.MetaTalentsTree) then
            MT:Enable()
        else
            C_AddOns.DisableAddOn(LOD_ADDON)
        end
    end)
    loader:UnregisterAllEvents()
end)
