local _, ns = ...
local HeliHeal = ns.addon
local L = ns.L or function(value, ...) return select("#", ...) > 0 and value:format(...) or value end

local TALENTS = {
    echoOfTheElements = { 333919 },
    elementalReverb = { 443418 },
    downpour = { 462488 },
    doubleDip = { 1252882 },
    mysticKnowledge = { 1270453 },
    surgingTotem = { 444995 },
    unleashLife = { 73685 },
    druidGermination = { 155675 },
    druidPowerArchdruid = { 392302 },
    druidLifetreading = { 1217941 },
    druidKeeper = { 433831, 428731 },
    druidWildstalker = { 439926, 439530 },
}

local SNAPSHOT_FLAGS = {
    "echoOfTheElements", "elementalReverb", "downpour", "doubleDip",
    "mysticKnowledge", "surgingTotem", "unleashLife", "restorationTier2", "restorationTier4",
    "druidGermination", "druidPowerArchdruid", "druidLifetreading", "druidKeeper", "druidWildstalker",
}

local function isKnownSpell(spellID)
    if C_SpellBook and type(C_SpellBook.IsSpellKnown) == "function" then
        local ok, known = pcall(C_SpellBook.IsSpellKnown, spellID)
        if ok and known then return true end
    end
    if type(IsPlayerSpell) == "function" then
        local ok, known = pcall(IsPlayerSpell, spellID)
        if ok and known then return true end
    end
    return false
end

local function containsTalent(spells, spellIDs)
    for _, spellID in ipairs(spellIDs) do
        if spells[spellID] then return true end
    end
    return false
end

local function snapshotsEqual(a, b)
    if not a or not b then return false end
    if a.available ~= b.available or a.configID ~= b.configID then return false end
    for _, flag in ipairs(SNAPSHOT_FLAGS) do
        if a[flag] ~= b[flag] then return false end
    end
    return true
end

function HeliHeal:ReadActiveTalentSpells()
    if not C_ClassTalents or type(C_ClassTalents.GetActiveConfigID) ~= "function"
        or not C_Traits or type(C_Traits.GetConfigInfo) ~= "function" then
        return nil, nil
    end

    local configID = C_ClassTalents.GetActiveConfigID()
    if not configID then return nil, nil end
    local configInfo = C_Traits.GetConfigInfo(configID)
    if not configInfo or type(configInfo.treeIDs) ~= "table" then return nil, configID end

    local spells = {}
    local committedEntryCount = 0
    for _, treeID in ipairs(configInfo.treeIDs) do
        local nodeIDs = C_Traits.GetTreeNodes(treeID) or {}
        for _, nodeID in ipairs(nodeIDs) do
            local nodeInfo = C_Traits.GetNodeInfo(configID, nodeID)
            -- This committed list represents the selected live loadout, not
            -- uncommitted talent-preview changes in the talent window.
            local committedRanks = nodeInfo and (not nodeInfo.subTreeID or nodeInfo.subTreeActive)
                and nodeInfo.entryIDsWithCommittedRanks or {}
            for _, rankInfo in ipairs(committedRanks) do
                local entryID = type(rankInfo) == "table" and rankInfo.entryID or rankInfo
                if entryID then committedEntryCount = committedEntryCount + 1 end
                local entryInfo = entryID and C_Traits.GetEntryInfo(configID, entryID)
                local definitionInfo = entryInfo and entryInfo.definitionID
                    and C_Traits.GetDefinitionInfo(entryInfo.definitionID)
                if definitionInfo and definitionInfo.spellID then
                    spells[definitionInfo.spellID] = true
                end
            end
        end
    end
    -- A max-level active combat config cannot legitimately have zero
    -- committed entries. Treat that as an API-not-ready state and retain the
    -- static preset assumptions instead of hiding valid abilities.
    if committedEntryCount == 0 then return nil, configID end
    return spells, configID
end

function HeliHeal:RefreshTalentSnapshot(silent)
    if InCombatLockdown and InCombatLockdown() then
        self.talentSnapshotPending = true
        return false
    end

    local ok, spells, configID = pcall(self.ReadActiveTalentSpells, self)
    if not ok then
        spells, configID = nil, nil
    end
    local snapshot = {
        available = spells ~= nil,
        configID = configID,
        spells = spells or {},
    }
    for talentKey, spellIDs in pairs(TALENTS) do
        snapshot[talentKey] = spells and containsTalent(spells, spellIDs) or false
    end
    snapshot.restorationTier2 = self.classToken ~= "DRUID" and isKnownSpell(1264866)
    snapshot.restorationTier4 = self.classToken ~= "DRUID" and isKnownSpell(1264867)

    local changed = not snapshotsEqual(self.talentSnapshot, snapshot)
    self.talentSnapshot = snapshot
    self.talentSnapshotPending = false
    if changed then
        local currentPreset = self.db and self.db.profile and self.db.profile.rotationPreset
        local _, content
        if currentPreset then
            _, content = currentPreset:match("^[a-z]+_([a-z]+)_([a-z]+)$")
            if content ~= "mythicplus" and content ~= "raid" then content = nil end
        end
        local detectedHero
        if self.classToken == "DRUID" then
            detectedHero = snapshot.druidKeeper and "keeper" or (snapshot.druidWildstalker and "wildstalker")
        else
            detectedHero = (snapshot.elementalReverb or snapshot.mysticKnowledge) and "farseer"
                or (snapshot.surgingTotem and "totemic")
        end
        local classPrefix = self.classToken == "DRUID" and "druid" or "shaman"
        local detectedPreset = detectedHero and content and (classPrefix .. "_" .. detectedHero .. "_" .. content)
        if detectedPreset and ns.AbilityLibrary:GetPreset(detectedPreset) and detectedPreset ~= currentPreset then
            self.db.profile.rotationPreset = detectedPreset
            self.db.profile.slots = ns.AbilityLibrary:BuildPresetSlots(detectedPreset, self.db.profile.bindings)
        end
        self:ResetRuntimeState()
        if self.frame then self:RefreshDisplay() end
        if self.RefreshOptionsUI then self:RefreshOptionsUI() end
    end

    if not silent then self:PrintTalentSnapshot() end
    return snapshot.available
end

function HeliHeal:IsTalentActive(talentKey)
    return self.talentSnapshot and self.talentSnapshot.available
        and self.talentSnapshot[talentKey] == true
end

function HeliHeal:GetRiptideMaxCharges(fallback)
    if not self.talentSnapshot or not self.talentSnapshot.available then
        return fallback or 2
    end
    return 1
        + (self.talentSnapshot.echoOfTheElements and 1 or 0)
        + (self.talentSnapshot.elementalReverb and 1 or 0)
end

function HeliHeal:PrintTalentSnapshot()
    local snapshot = self.talentSnapshot
    if not snapshot or not snapshot.available then
        self:Print(L("Talent-Snapshot noch nicht verfügbar; bestehende Preset-Annahmen bleiben aktiv."))
        return
    end
    local function yesNo(value) return L(value and "JA" or "nein") end
    if self.classToken == "DRUID" then
        local details = ("Germination %s | Power of the Archdruid %s | Lifetreading %s | Keeper %s | Wildstalker %s")
            :format(yesNo(snapshot.druidGermination), yesNo(snapshot.druidPowerArchdruid),
                yesNo(snapshot.druidLifetreading), yesNo(snapshot.druidKeeper), yesNo(snapshot.druidWildstalker))
        self:Print(L("Talente (Config %s): %s", tostring(snapshot.configID or "?"), details))
        return
    end
    local details = ("Echo %s | Elemental Reverb %s | Surging Totem %s | Unleash Life %s | Downpour %s | Double Dip %s | Mystic Knowledge %s | Set 2p %s | Set 4p %s")
        :format(yesNo(snapshot.echoOfTheElements),
            yesNo(snapshot.elementalReverb), yesNo(snapshot.surgingTotem), yesNo(snapshot.unleashLife),
            yesNo(snapshot.downpour), yesNo(snapshot.doubleDip), yesNo(snapshot.mysticKnowledge),
            yesNo(snapshot.restorationTier2), yesNo(snapshot.restorationTier4))
    self:Print(L("Talente (Config %s): %s", tostring(snapshot.configID or "?"), details))
end

function HeliHeal:CreateTalentListener()
    if self.talentListener then
        local listener = self.talentListener
        listener:RegisterEvent("PLAYER_ENTERING_WORLD")
        listener:RegisterEvent("TRAIT_CONFIG_LIST_UPDATED")
        listener:RegisterEvent("TRAIT_CONFIG_UPDATED")
        listener:RegisterEvent("ACTIVE_COMBAT_CONFIG_CHANGED")
        listener:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
        listener:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
        listener:RegisterEvent("SPELLS_CHANGED")
        listener:RegisterEvent("PLAYER_REGEN_ENABLED")
        self:RefreshTalentSnapshot(true)
        return
    end
    local listener = CreateFrame("Frame")
    listener:RegisterEvent("PLAYER_ENTERING_WORLD")
    listener:RegisterEvent("TRAIT_CONFIG_LIST_UPDATED")
    listener:RegisterEvent("TRAIT_CONFIG_UPDATED")
    listener:RegisterEvent("ACTIVE_COMBAT_CONFIG_CHANGED")
    listener:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
    listener:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
    listener:RegisterEvent("SPELLS_CHANGED")
    listener:RegisterEvent("PLAYER_REGEN_ENABLED")
    listener:SetScript("OnEvent", function(_, event, argument)
        if event == "PLAYER_SPECIALIZATION_CHANGED" and argument and argument ~= "player" then return end
        if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_SPECIALIZATION_CHANGED" then
            HeliHeal:RefreshPlayerSupport(true)
        end
        if event == "TRAIT_CONFIG_UPDATED" then
            local activeConfigID = C_ClassTalents and C_ClassTalents.GetActiveConfigID
                and C_ClassTalents.GetActiveConfigID()
            if activeConfigID and argument and argument ~= activeConfigID then return end
        end
        HeliHeal:RefreshTalentSnapshot(true)
        if event == "PLAYER_REGEN_ENABLED" then
            HeliHeal:ReconcileOutOfCombatState(true)
        end
    end)
    self.talentListener = listener
    self:RefreshTalentSnapshot(true)
end
