-- Cadence: Data.lua -- healer cooldown library and spell lookups.
--
-- Grouped by spec, not by class: Discipline and Holy do not share a cooldown
-- list, and a plan you import may be written for any of them. Every spec is
-- always loaded, so a Paladin can build a Shaman's plan and names still resolve.
--
-- Spell IDs are validated at runtime. Anything Blizzard has renumbered still
-- appears, labelled with its raw ID, so a broken entry is visible rather than
-- silently missing.

local ADDON, ns = ...

ns.SPECS = {
    {
        spec = "Restoration druid", class = "DRUID", specID = 105,
        spells = {
            740,    -- Tranquility
            197721, -- Flourish
            33891,  -- Incarnation: Tree of Life
            391528, -- Convoke the Spirits
            102342, -- Ironbark
            77761,  -- Stampeding Roar
        },
    },
    {
        spec = "Discipline priest", class = "PRIEST", specID = 256,
        spells = {
            62618,  -- Power Word: Barrier
            47536,  -- Rapture
            246287, -- Evangelism
            33206,  -- Pain Suppression
            15286,  -- Vampiric Embrace
            73325,  -- Leap of Faith
        },
    },
    {
        spec = "Holy priest", class = "PRIEST", specID = 257,
        spells = {
            64843,  -- Divine Hymn
            200183, -- Apotheosis
            265202, -- Holy Word: Salvation
            64901,  -- Symbol of Hope
            47788,  -- Guardian Spirit
            73325,  -- Leap of Faith
        },
    },
    {
        spec = "Restoration shaman", class = "SHAMAN", specID = 264,
        spells = {
            108280, -- Healing Tide Totem
            98008,  -- Spirit Link Totem
            114052, -- Ascendance
            108281, -- Ancestral Guidance
            157153, -- Cloudburst Totem
            207399, -- Ancestral Protection Totem
            198838, -- Earthen Wall Totem
        },
    },
    {
        spec = "Mistweaver monk", class = "MONK", specID = 270,
        spells = {
            115310, -- Revival
            116849, -- Life Cocoon
            322118, -- Invoke Yu'lon, the Jade Serpent
            325197, -- Invoke Chi-Ji, the Red Crane
            115176, -- Zen Meditation
            116844, -- Ring of Peace
        },
    },
    {
        spec = "Holy paladin", class = "PALADIN", specID = 65,
        spells = {
            31821,  -- Aura Mastery
            31884,  -- Avenging Wrath
            375576, -- Divine Toll
            6940,   -- Blessing of Sacrifice
            1022,   -- Blessing of Protection
            633,    -- Lay on Hands
            642,    -- Divine Shield
        },
    },
    {
        spec = "Preservation evoker", class = "EVOKER", specID = 1468,
        spells = {
            363534, -- Rewind
            370960, -- Emerald Communion
            359816, -- Dream Flight
            370537, -- Stasis
            370564, -- Stasis release (ID unconfirmed -- see README)
            357170, -- Time Dilation
            374227, -- Zephyr
        },
    },
    {
        -- Shared raid duties that show up in plans regardless of who is healing.
        spec = "Raid utility", class = nil, specID = nil,
        spells = {
            2825,   -- Bloodlust
            32182,  -- Heroism
            80353,  -- Time Warp
            390386, -- Fury of the Aspects
            20484,  -- Rebirth
            61999,  -- Raise Ally
            20707,  -- Soulstone
            361227, -- Return
        },
    },
}

--------------------------------------------------------------------------------
-- Label overrides
--------------------------------------------------------------------------------
--
-- These win over whatever the client returns. Use them when the game's own name
-- is missing, or when it is ambiguous -- Stasis and its release would otherwise
-- both read "Stasis" in a plan, which is unreadable at a glance mid-pull.

ns.SPELL_LABELS = {
    [370564] = "Release Stasis",
}

--------------------------------------------------------------------------------
-- Spell lookups
--------------------------------------------------------------------------------

local nameToID

function ns.GetSpellName(spellID)
    if not spellID then return nil end
    local info = C_Spell and C_Spell.GetSpellInfo and C_Spell.GetSpellInfo(spellID)
    return info and info.name or nil
end

-- The name to show and speak: override first, then the client, then the raw ID.
function ns.SpellLabel(spellID)
    if not spellID then return nil end
    return ns.SPELL_LABELS[spellID] or ns.GetSpellName(spellID) or ("Spell " .. spellID)
end

-- True when we have a real name rather than a bare number.
function ns.HasSpellName(spellID)
    if not spellID then return false end
    return (ns.SPELL_LABELS[spellID] or ns.GetSpellName(spellID)) ~= nil
end

function ns.GetSpellIcon(spellID)
    if not spellID then return nil end
    if C_Spell and C_Spell.GetSpellTexture then
        return C_Spell.GetSpellTexture(spellID)
    end
    local info = C_Spell and C_Spell.GetSpellInfo and C_Spell.GetSpellInfo(spellID)
    return info and info.iconID or nil
end

-- The index spans every spec, so a plan written for another healer still
-- resolves to icons and clean spoken names on your character.
local function BuildNameIndex()
    nameToID = {}
    for _, group in ipairs(ns.SPECS) do
        for _, id in ipairs(group.spells) do
            local name = ns.GetSpellName(id)
            if name then nameToID[name:lower()] = id end
        end
    end
    -- Overrides last so they beat a client name that maps elsewhere.
    for id, label in pairs(ns.SPELL_LABELS) do
        nameToID[label:lower()] = id
    end
    if CadenceDB then
        for _, enc in pairs(CadenceDB.encounters) do
            for _, plan in ipairs(enc.plans or {}) do
                for _, r in ipairs(plan.reminders or {}) do
                    if r.spellID and r.label and r.label ~= "" then
                        nameToID[r.label:lower()] = r.spellID
                    end
                end
            end
        end
    end
end

function ns.FindSpellID(name)
    if type(name) ~= "string" or name == "" then return nil end
    local asNumber = tonumber(name)
    if asNumber and ns.GetSpellName(asNumber) then return asNumber end
    if not nameToID then BuildNameIndex() end
    return nameToID[name:lower():gsub("^%s+", ""):gsub("%s+$", "")]
end

function ns.RebuildSpellIndex() nameToID = nil end

-- Turns whatever was typed into the editor into a spellID and a label.
-- A bare number is kept as the ID even when this client cannot name it, so an
-- unrecognised or renumbered spell still drives the icon and survives export.
function ns.ParseSpellInput(text)
    text = tostring(text or ""):gsub("^%s+", ""):gsub("%s+$", "")
    if text == "" then return nil, "" end

    local num = tonumber(text)
    if num and num == math.floor(num) and num > 0 then
        return num, ns.SpellLabel(num)
    end

    local id = ns.FindSpellID(text)
    if id then return id, ns.SpellLabel(id) end
    return nil, text
end

--------------------------------------------------------------------------------
-- Grouped lists for the picker
--------------------------------------------------------------------------------

local function Resolve(group)
    local seen, out = {}, {}
    for _, id in ipairs(group.spells) do
        if not seen[id] then
            seen[id] = true
            out[#out + 1] = {
                spellID = id,
                name = ns.SpellLabel(id),
                icon = ns.GetSpellIcon(id),
                known = ns.HasSpellName(id),
            }
        end
    end
    table.sort(out, function(a, b) return a.name < b.name end)
    return out
end

-- Blizzard has been moving these into C_SpecializationInfo. Prefer the namespaced
-- pair where it exists and keep the globals as the fallback, so this keeps working
-- on either side of the change rather than quietly dropping to class detection --
-- which cannot tell Discipline from Holy.
function ns.GetCurrentSpecID()
    local C = C_SpecializationInfo
    local getIndex = (C and C.GetSpecialization) or GetSpecialization
    local getInfo  = (C and C.GetSpecializationInfo) or GetSpecializationInfo
    if not getIndex or not getInfo then return nil end

    local ok, index = pcall(getIndex)
    if not ok or not index then return nil end
    local ok2, specID = pcall(getInfo, index)
    if not ok2 then return nil end
    return specID
end

-- The player's own cooldowns: by spec where the client tells us, by class
-- otherwise, and empty on a non-healer rather than guessing.
function ns.GetSpecCooldowns()
    local specID = ns.GetCurrentSpecID()
    local _, class = UnitClass("player")

    if specID then
        for _, group in ipairs(ns.SPECS) do
            if group.specID == specID then return Resolve(group), group.spec end
        end
    end

    for _, group in ipairs(ns.SPECS) do
        if group.class and group.class == class then return Resolve(group), group.spec end
    end

    return {}, nil
end

-- Every spec, for building or checking a plan written for someone else.
function ns.GetAllCooldownGroups()
    local out = {}
    for _, group in ipairs(ns.SPECS) do
        out[#out + 1] = { spec = group.spec, spells = Resolve(group) }
    end
    return out
end

-- Kept so older calls keep working.
function ns.GetClassCooldowns()
    return (ns.GetSpecCooldowns())
end
