--[[
    SpellDB.lua - TGXG Interrupt
    -----------------------------------------------------------------------
    Interruptibility database — the fallback for Midnight's secret values.

    When the notInterruptible flag of an enemy cast is hidden (M+ etc.),
    castbar and kick alert ask this DB instead. Two layers:

    1. SEED — curated list of known-kickable dungeon casts of the current
       season (Midnight S1 pool: Magister's Terrace, Maisara Caverns,
       Nexus-Point Xenas, Windrunner Spire, Algeth'ar Academy, Seat of
       the Triumvirate, Skyreach, Pit of Saron). IDs where confirmed;
       English names as an extra key (localized names for ID entries are
       resolved at load via C_Spell.GetSpellName).

    2. LEARNED (SavedVariables TGX.db.spellDB) — fills itself:
         * an observed successful interrupt PROVES the cast was kickable
           (UNIT_SPELLCAST_INTERRUPTED on an enemy → learn true)
         * whenever the flag IS readable, the id/name→flag pair is
           recorded for later runs where it isn't
       So the DB converges on full season coverage just by playing.

    Lookup verification: candidate spell IDs are extracted positionally
    from UnitCastingInfo/UnitChannelInfo returns (laundered if secret)
    and, when the cast name is readable, verified via GetSpellName ==
    castName so a stray castBarID can't cause a false match.
    -----------------------------------------------------------------------
]]

TGX = TGX or {}
TGX.SpellDB = TGX.SpellDB or {}
local SDB = TGX.SpellDB

local MAX_LEARNED = 3000

------------------------------------------------------------
-- Seed data (Midnight Season 1). true = interruptible by kicks.
-- Sources: season interrupt guides; IDs listed where published.
------------------------------------------------------------
local SEED_IDS = {
    [118]     = true,  -- Polymorph            (Magister's Terrace, Arcane Magister)
    [11366]   = true,  -- Pyroblast            (Magister's Terrace, Blazing Pyromancer)
    [51514]   = true,  -- Hex                  (Maisara Caverns, Ritual Hexxer)
    [686]     = true,  -- Shadow Bolt          (Windrunner Spire, Kalis u.a.)
    [5185]    = true,  -- Healing Touch        (Algeth'ar Academy, Ancient Branch)
    [8092]    = true,  -- Mind Blast           (Seat of the Triumvirate, Viceroy Nezhar)
    [154396]  = true,  -- Solar Blast          (Skyreach, High Sage Viryx)
    [69238]   = true,  -- Icy Blast            (Pit of Saron, Dreadpulse Lich)
    [1257595] = true,  -- Divine Guile         (Nexus-Point Xenas, Lothraxion)
}

-- Name-keyed seeds for casts whose IDs aren't published yet. English
-- names — they match on English clients; on other locales the learning
-- layer takes over. All entries are from the season's kick lists.
local SEED_NAMES = {
    -- Magister's Terrace
    ["Terror Wave"] = true, ["Shadow Bolt"] = true,
    -- Maisara Caverns
    ["Shrink"] = true, ["Reanimation"] = true, ["Spirit Rend"] = true,
    ["Hooked Snare"] = true, ["Piercing Screech"] = true, ["Shadowfrost Blast"] = true,
    -- Nexus-Point Xenas
    ["Arcane Explosion"] = true, ["Nullify"] = true, ["Arcing Mana"] = true,
    ["Umbral Bolt"] = true, ["Holy Bolt"] = true, ["Arcane Zap"] = true,
    -- Windrunner Spire
    ["Poison Blades"] = true, ["Chain Lightning"] = true, ["Fungal Bolt"] = true,
    ["Spirit Bolt"] = true,
    -- Algeth'ar Academy
    ["Surge"] = true, ["Monotonous Lecture"] = true, ["Mystic Brand"] = true,
    ["Call of the Flock"] = true, ["Mystic Blast"] = true, ["Arcane Bolt"] = true,
    ["Healing Touch"] = true,
    -- Seat of the Triumvirate
    ["Summon Voidcaller"] = true, ["Shadowmend"] = true, ["Abyssal Enhancement"] = true,
    ["Dread Screech"] = true, ["Mind Blast"] = true,
    -- Skyreach
    ["Repel"] = true, ["Blinding Light"] = true, ["Solar Bolt"] = true, ["Solar Blast"] = true,
    -- Pit of Saron
    ["Icy Blast"] = true, ["Netherburst"] = true, ["Plungegrip"] = true,
    ["Icebolt"] = true, ["Death Bolt"] = true, ["Shadowbind"] = true, ["Plague Bolt"] = true,
    -- Affix (Xal'atath's Bargain: Voidbound)
    ["Dark Prayer"] = true,
}

------------------------------------------------------------
-- Runtime tables (seed + learned, ids and names merged)
------------------------------------------------------------
local _ids, _names = {}, {}
local _learnedCount = 0

local function IsSecret(v)
    if type(issecretvalue) ~= "function" then return false end
    local ok, s = pcall(issecretvalue, v)
    return ok and s or false
end

function SDB:Init()
    wipe(_ids); wipe(_names)
    for id, v in pairs(SEED_IDS) do
        _ids[id] = v
        -- Localized display name for this ID → works on every client locale.
        local ok, n = pcall(C_Spell.GetSpellName, id)
        if ok and n and not IsSecret(n) then _names[n] = v end
    end
    for n, v in pairs(SEED_NAMES) do
        if _names[n] == nil then _names[n] = v end
    end
    -- Merge learned entries from SavedVariables.
    local store = TGX.db and TGX.db.spellDB
    if store then
        _learnedCount = 0
        for id, v in pairs(store.ids or {}) do
            _ids[id] = v
            _learnedCount = _learnedCount + 1
        end
        for n, v in pairs(store.names or {}) do
            if _names[n] == nil then _names[n] = v end
        end
    end
end

------------------------------------------------------------
-- Learning
------------------------------------------------------------
function SDB:Learn(id, name, kickable)
    if not TGX.db then return end
    if TGX.db.spellDBEnabled == false then return end
    TGX.db.spellDB = TGX.db.spellDB or { ids = {}, names = {} }
    local store = TGX.db.spellDB
    if id and TGX.Taint.IsClean(id) then
        if store.ids[id] == nil and _learnedCount >= MAX_LEARNED then return end
        if store.ids[id] == nil then _learnedCount = _learnedCount + 1 end
        store.ids[id] = kickable
        _ids[id]      = kickable
    end
    if name and type(name) == "string" and not IsSecret(name) then
        store.names[name] = kickable
        _names[name]      = kickable
    end
end

------------------------------------------------------------
-- Candidate spell-ID extraction from cast-info returns.
-- Numeric values after position 6 (skips name/text/texture/times);
-- secret values are laundered. castBarID may slip in — the caller
-- verifies against the cast name where possible.
------------------------------------------------------------
local function CandidateIDs(...)
    local n = select("#", ...)
    local out = {}
    for i = 7, n do
        local v = select(i, ...)
        if type(v) == "number" then
            local c = TGX.Taint.IsClean(v) and v or TGX.Taint:ResolveNumber(v)
            if c and c > 0 and c < 10000000 then out[#out + 1] = c end
        end
    end
    return out
end

------------------------------------------------------------
-- Lookup: is the unit's current cast interruptible per DB?
-- Returns true / false / nil (no entry).
------------------------------------------------------------
function SDB:ResolveForUnit(unit)
    if TGX.db and TGX.db.spellDBEnabled == false then return nil end
    local ok, res = pcall(function()
        local castName = UnitCastingInfo(unit)
        local channel = false
        if castName == nil then
            castName = UnitChannelInfo(unit)
            if castName == nil then return nil end
            channel = true
        end
        local nameClean
        if castName ~= nil and not IsSecret(castName) then nameClean = castName end

        local ids
        if channel then ids = CandidateIDs(UnitChannelInfo(unit))
        else            ids = CandidateIDs(UnitCastingInfo(unit)) end

        for _, id in ipairs(ids) do
            local hit = _ids[id]
            if hit ~= nil then
                if nameClean then
                    local okN, sn = pcall(C_Spell.GetSpellName, id)
                    if okN and sn == nameClean then return hit end
                else
                    return hit   -- no name to verify against; ID match counts
                end
            end
        end
        if nameClean and _names[nameClean] ~= nil then
            return _names[nameClean]
        end
        return nil
    end)
    if not ok then return nil end
    return res
end

-- Record a READABLE flag for later runs where it is hidden.
-- Only learns when the cast name is clean and an ID verifies against it.
function SDB:LearnFromUnit(unit, kickable)
    local ok = pcall(function()
        local castName = UnitCastingInfo(unit)
        local channel = false
        if castName == nil then
            castName = UnitChannelInfo(unit)
            if castName == nil then return end
            channel = true
        end
        if castName == nil or IsSecret(castName) then return end

        local ids
        if channel then ids = CandidateIDs(UnitChannelInfo(unit))
        else            ids = CandidateIDs(UnitCastingInfo(unit)) end

        local verifiedID
        for _, id in ipairs(ids) do
            local okN, sn = pcall(C_Spell.GetSpellName, id)
            if okN and sn == castName then verifiedID = id break end
        end
        SDB:Learn(verifiedID, castName, kickable)
    end)
end

------------------------------------------------------------
-- Proof-by-interrupt learning: a cast that GOT interrupted was
-- interruptible — the strongest signal there is, and it works even in
-- M+ (the spell ID is laundered when it arrives secret).
------------------------------------------------------------
local _learnFrame = CreateFrame("Frame")
_learnFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
_learnFrame:SetScript("OnEvent", function(_, _, unit, castGUID, spellID)
    if not unit then return end
    if not (unit:find("^nameplate") or unit == "target" or unit == "focus"
            or unit:find("^boss")) then
        return
    end
    -- Enemy units only (a friendly player's interrupted cast says nothing
    -- about dungeon mobs).
    local okA, attackable = pcall(UnitCanAttack, "player", unit)
    if not (okA and attackable == true) then return end

    local id = spellID
    if id ~= nil and not TGX.Taint.IsClean(id) then
        id = TGX.Taint:ResolveNumber(spellID)
    end
    if not id then return end
    local name
    local okN, sn = pcall(C_Spell.GetSpellName, id)
    if okN and sn and not IsSecret(sn) then name = sn end
    SDB:Learn(id, name, true)
    if TGX.debug then
        print("|cff33ff99TGXG-DB|r gelernt: " .. tostring(name or id) .. " = kickbar")
    end
end)

------------------------------------------------------------
-- Stats for the options window / debugging
------------------------------------------------------------
function SDB:Count()
    local seedN, total = 0, 0
    for _ in pairs(SEED_IDS) do seedN = seedN + 1 end
    for _ in pairs(_ids) do total = total + 1 end
    local store = TGX.db and TGX.db.spellDB
    local learned = 0
    if store then for _ in pairs(store.ids or {}) do learned = learned + 1 end end
    return total, learned
end
