--[[
    Commander 3.0 - Spells Data Source

    Provides access to player spellbook and known spells.
    Commands: spells, spell, cast

    Supports both Retail (C_SpellBook) and Classic (GetSpellBookItemInfo)
]]

local ADDON_NAME, C3 = ...

-- Use shared utilities
local Colorize = function(...) return C3.Colors.Colorize(...) end
local GetThemeColor = function(...) return C3.Colors.GetColor(...) end

-------------------------------------------------------------------------------
-- Spell Type Registration (extends core/types.lua definition)
-------------------------------------------------------------------------------

-- Register formatter for Spell type
C3.Formatter:Register("Spell", {
    format = function(obj)
        local icon = obj.icon and (C3.Formatter:Icon(obj.icon) .. " ") or ""
        local name = obj.name or "Unknown Spell"
        local rank = obj.rank and obj.rank ~= "" and " " .. Colorize("(" .. obj.rank .. ")", "status.dim") or ""

        -- Color by spell school
        local schoolColors = {
            Physical = "FFFF00",
            Holy = "FFFF99",
            Fire = "FF4500",
            Nature = "00FF00",
            Frost = "00BFFF",
            Shadow = "9900FF",
            Arcane = "FF00FF",
        }
        local color = schoolColors[obj.school] or GetThemeColor("accent.primary") or "71D5FF" -- Default spell blue from theme

        if obj.isPassive then
            name = Colorize(name, "accent.success") .. " " .. Colorize("[Passive]", "status.dim")
        else
            name = C3.Formatter:Colorize(name, color)
        end

        return icon .. name .. rank
    end,
    list = function(obj)
        local icon = obj.icon and (C3.Formatter:Icon(obj.icon) .. " ") or ""
        return icon .. (obj.name or "Unknown")
    end,
    compact = function(obj)
        return obj.name or "?"
    end
})

-------------------------------------------------------------------------------
-- Spell School Detection
-------------------------------------------------------------------------------

local SPELL_SCHOOL_MASK = {
    [1] = "Physical",
    [2] = "Holy",
    [4] = "Fire",
    [8] = "Nature",
    [16] = "Frost",
    [32] = "Shadow",
    [64] = "Arcane",
}

local function getSpellSchool(spellID)
    -- Try C_Spell.GetSpellSchool (may not exist in all versions)
    if C_Spell and C_Spell.GetSpellSchool then
        local school = C_Spell.GetSpellSchool(spellID)
        if school then
            return SPELL_SCHOOL_MASK[school] or "Physical"
        end
    end
    -- Spell school info not available in 12.0+ API
    return nil
end

-------------------------------------------------------------------------------
-- API Compatibility Layer
-------------------------------------------------------------------------------

local function isRetailSpellBook()
    return C_SpellBook and C_SpellBook.GetNumSpellBookSkillLines
end

-- Get number of spell tabs/skill lines
local function getNumSpellTabs()
    if isRetailSpellBook() then
        return C_SpellBook.GetNumSpellBookSkillLines()
    else
        return GetNumSpellTabs()
    end
end

-- Get spell tab info
local function getSpellTabInfo(tabIndex)
    if isRetailSpellBook() then
        local skillLineInfo = C_SpellBook.GetSpellBookSkillLineInfo(tabIndex)
        if skillLineInfo then
            return skillLineInfo.name,
                   skillLineInfo.iconID,
                   skillLineInfo.itemIndexOffset,
                   skillLineInfo.numSpellBookItems,
                   skillLineInfo.isGuild,
                   skillLineInfo.offSpecID
        end
        return nil
    else
        return GetSpellTabInfo(tabIndex)
    end
end

-- Get spell book item info
local function getSpellBookItemInfo(slotIndex)
    if isRetailSpellBook() then
        local spellBank = Enum.SpellBookSpellBank.Player
        -- GetSpellBookItemInfo returns a table with spellID, itemType, etc.
        local info = C_SpellBook.GetSpellBookItemInfo(slotIndex, spellBank)
        if info then
            return info.itemType, info.spellID
        end
        return nil, nil
    else
        return GetSpellBookItemInfo(slotIndex, "spell")
    end
end

-- Get spell book item name
local function getSpellBookItemName(slotIndex)
    if isRetailSpellBook() then
        local spellBank = Enum.SpellBookSpellBank.Player
        local info = C_SpellBook.GetSpellBookItemInfo(slotIndex, spellBank)
        if info and info.spellID and C_Spell.GetSpellInfo then
            local spellInfo = C_Spell.GetSpellInfo(info.spellID)
            if spellInfo then
                return spellInfo.name, nil
            end
        end
        return nil, nil
    else
        return GetSpellBookItemName(slotIndex, "spell")
    end
end

-- Check if spell is passive
local function isPassiveSpellSlot(slotIndex)
    if isRetailSpellBook() then
        local spellBank = Enum.SpellBookSpellBank.Player
        return C_SpellBook.IsSpellBookItemPassive(slotIndex, spellBank)
    else
        return IsPassiveSpell(slotIndex, "spell")
    end
end

-- Check if spell ID is passive
local function isPassiveSpellID(spellID)
    if C_Spell and C_Spell.IsSpellPassive then
        return C_Spell.IsSpellPassive(spellID)
    elseif IsPassiveSpell then
        return IsPassiveSpell(spellID)
    end
    return false
end

-- Get spell info by ID
local function getSpellInfo(spellID)
    if C_Spell and C_Spell.GetSpellInfo then
        local info = C_Spell.GetSpellInfo(spellID)
        if info then
            return info.name, nil, info.iconID, info.castTime, info.minRange, info.maxRange
        end
    end
    -- Fallback to old API
    if GetSpellInfo then
        return GetSpellInfo(spellID)
    end
    return nil
end

-- Check if spell is usable (may return nil for secret values in WoW 12.0+)
local function isSpellUsable(spellID)
    if C_Spell and C_Spell.IsSpellUsable then
        local usable, noMana = C_Spell.IsSpellUsable(spellID)
        -- Handle WoW 12.0+ secret values
        if C3.Secrets and C3.Secrets.IsSecret(usable) then
            return nil, nil  -- Data restricted
        end
        return usable, noMana
    elseif IsUsableSpell then
        return IsUsableSpell(spellID)
    end
    return false, false
end

-- Get spell cooldown (may return nil for secret values in WoW 12.0+)
local function getSpellCooldown(spellID)
    if C_Spell and C_Spell.GetSpellCooldown then
        local cd = C_Spell.GetSpellCooldown(spellID)
        -- Handle WoW 12.0+ secret values
        if C3.Secrets and C3.Secrets.IsSecret(cd) then
            return nil, nil, nil  -- Data restricted
        end
        if cd then
            return cd.startTime, cd.duration, cd.isEnabled and 1 or 0
        end
    elseif GetSpellCooldown then
        return GetSpellCooldown(spellID)
    end
    return 0, 0, 1
end

-- Get spell cooldown remaining (WoW 12.0+ API with fallback)
local function getSpellCooldownRemaining(spellID)
    -- Try new 12.0 API first
    if C_Spell and C_Spell.GetSpellCooldownRemaining then
        local remaining = C_Spell.GetSpellCooldownRemaining(spellID)
        -- Handle secret values
        if C3.Secrets and C3.Secrets.IsSecret(remaining) then
            return nil
        end
        return remaining
    end

    -- Fallback: calculate from start/duration
    local startTime, duration = getSpellCooldown(spellID)
    if startTime == nil then
        return nil  -- Data restricted
    end
    if startTime == 0 or duration == 0 then
        return 0
    end
    local remaining = (startTime + duration) - GetTime()
    return math.max(0, remaining)
end

-------------------------------------------------------------------------------
-- Spells Intellisense Provider
-------------------------------------------------------------------------------

local MAX_SUGGESTIONS = 10

C3.Intellisense:RegisterProvider("Spells", function(partial, ctx)
    -- Use shared Lookup service
    local spells = C3.Lookup:FindSpells(partial, MAX_SUGGESTIONS)

    local results = {}
    for _, spell in ipairs(spells) do
        results[#results + 1] = {
            text = spell.name,
            description = "Spell",
            icon = spell.icon,
        }
    end
    return results
end)

-------------------------------------------------------------------------------
-- spells - List spellbook spells
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "spells",
    description = "List spells from spellbook",
    category = "data",
    producesType = "Spell",
    acceptsInput = false,
    flags = {
        all = { short = "a", description = "Include all spell tabs" },
        passive = { short = "p", description = "Show only passive spells" },
        active = { description = "Show only active (castable) spells" },
        tab = { short = "t", description = "Filter by tab number" },
    },
    args = {
        { name = "tab", required = false, description = "Tab number to filter" }
    },
    execute = function(ctx)
        local showPassiveOnly = ctx:HasFlag("passive") or ctx:HasFlag("p")
        local showActiveOnly = ctx:HasFlag("active")
        local showAll = ctx:HasFlag("all") or ctx:HasFlag("a")
        local tabFilter = tonumber(ctx:GetArg(1)) or (ctx:HasFlag("tab") and tonumber(ctx:GetFlag("tab")))

        local results = {}
        local processed = 0
        local YIELD_INTERVAL = C3.Constants.YIELD_INTERVAL
        local numTabs = getNumSpellTabs()

        for tabIndex = 1, numTabs do repeat
            -- Guard: Tab filter
            if tabFilter and tabFilter ~= tabIndex then break end

            local tabName, tabTexture, offset, numSpells, isGuild, offspecID = getSpellTabInfo(tabIndex)

            -- Guard: Invalid tab
            if not tabName then break end

            -- Guard: Guild perks tab (skip unless --all)
            if isGuild and not showAll then break end

            for i = 1, numSpells do repeat
                -- Yield periodically
                processed = processed + 1
                if processed % YIELD_INTERVAL == 0 then
                    coroutine.yield()
                end

                local spellIndex = offset + i
                local spellType, spellID = getSpellBookItemInfo(spellIndex)

                -- Guard: Invalid spell info
                if not spellType or not spellID then break end

                -- Handle flyout entries (e.g., Summon Demon) - expand into child spells
                if isRetailSpellBook() then
                    local bookInfo = C_SpellBook.GetSpellBookItemInfo(spellIndex, Enum.SpellBookSpellBank.Player)
                    if bookInfo and bookInfo.isOffSpec then break end
                    if bookInfo and bookInfo.itemType == Enum.SpellBookItemType.Flyout and bookInfo.actionID then
                        local flyoutID = bookInfo.actionID
                        local _, _, numSlots, flyoutKnown = GetFlyoutInfo(flyoutID)
                        if flyoutKnown and numSlots then
                            for s = 1, numSlots do
                                local fSpellID, overrideSpellID, isSlotKnown = GetFlyoutSlotInfo(flyoutID, s)
                                if isSlotKnown and fSpellID then
                                    local useID = overrideSpellID or fSpellID
                                    local fname, frank, ficon, fcastTime, fminRange, fmaxRange = getSpellInfo(useID)
                                    if fname then
                                        local fisPassive = isPassiveSpellID(useID)
                                        if not (showPassiveOnly and not fisPassive) and not (showActiveOnly and fisPassive) then
                                            local fschool = getSpellSchool(useID)
                                            local fcdStart, fcdDuration = getSpellCooldown(useID)
                                            local fisOnCooldown = fcdStart and fcdStart > 0 and fcdDuration and fcdDuration > 0
                                            local fisUsable, fnotEnoughMana = isSpellUsable(useID)
                                            table.insert(results, C3.Types.Create("Spell", {
                                                id = useID,
                                                spellID = useID,
                                                name = fname,
                                                rank = frank or "",
                                                icon = ficon,
                                                castTime = fcastTime or 0,
                                                minRange = fminRange or 0,
                                                maxRange = fmaxRange or 0,
                                                isPassive = fisPassive,
                                                isUsable = fisUsable,
                                                notEnoughMana = fnotEnoughMana,
                                                isOnCooldown = fisOnCooldown,
                                                school = fschool,
                                                tab = tabIndex,
                                                tabName = tabName,
                                                spellType = "FLYOUT",
                                            }))
                                        end
                                    end
                                end
                            end
                        end
                        break  -- Done with this flyout entry
                    end
                end

                local spellName, spellSubName = getSpellBookItemName(spellIndex)
                local name, rank, icon, castTime, minRange, maxRange = getSpellInfo(spellID)

                -- Guard: No name
                if not name then break end

                local isPassive = isPassiveSpellSlot(spellIndex)

                -- Guard: Passive filter
                if showPassiveOnly and not isPassive then break end

                -- Guard: Active filter
                if showActiveOnly and isPassive then break end

                -- All guards passed - add result
                local school = getSpellSchool(spellID)
                local cdStart, cdDuration, cdEnabled = getSpellCooldown(spellID)
                local isOnCooldown = cdStart and cdStart > 0 and cdDuration and cdDuration > 0
                local isUsable, notEnoughMana = isSpellUsable(spellID)

                table.insert(results, C3.Types.Create("Spell", {
                    id = spellID,
                    spellID = spellID,
                    name = name,
                    rank = spellSubName or rank or "",
                    icon = icon,
                    castTime = castTime or 0,
                    minRange = minRange or 0,
                    maxRange = maxRange or 0,
                    isPassive = isPassive,
                    isUsable = isUsable,
                    notEnoughMana = notEnoughMana,
                    isOnCooldown = isOnCooldown,
                    school = school,
                    tab = tabIndex,
                    tabName = tabName,
                    spellType = spellType,
                }))
            until true end
        until true end

        return results
    end
})

-------------------------------------------------------------------------------
-- spell - Get specific spell info
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "spell",
    description = "Get spell info by name or ID",
    category = "data",
    acceptsInput = false,
    args = {
        { name = "query", required = true, description = "Spell name or ID" }
    },
    execute = function(ctx)
        local query = table.concat(ctx:GetArgs(), " ")
        if not query or query == "" then
            return ctx:Error("Usage: spell <name or id>")
        end

        -- Try as spell ID first
        local spellID = tonumber(query)
        local name, rank, icon, castTime, minRange, maxRange

        if spellID then
            name, rank, icon, castTime, minRange, maxRange = getSpellInfo(spellID)
        else
            -- Search by name in spellbook
            local queryLower = query:lower()
            local numTabs = getNumSpellTabs()

            for tabIndex = 1, numTabs do repeat
                if name then break end

                local _, _, offset, numSpells = getSpellTabInfo(tabIndex)

                -- Guard: Invalid tab info
                if not offset or not numSpells then break end

                for i = 1, numSpells do repeat
                    if name then break end

                    local spellIndex = offset + i
                    local spellType, id = getSpellBookItemInfo(spellIndex)

                    -- Guard: Invalid spell info
                    if not spellType or not id then break end

                    local spellName = getSpellBookItemName(spellIndex)

                    -- Guard: No name or no match
                    if not spellName then break end
                    if spellName:lower() ~= queryLower then break end

                    -- Found match
                    spellID = id
                    name, rank, icon, castTime, minRange, maxRange = getSpellInfo(spellID)
                until true end
            until true end
        end

        if not name then
            return ctx:Error("Spell not found: " .. query)
        end

        local isPassive = spellID and isPassiveSpellID(spellID)
        local isUsable, notEnoughMana = isSpellUsable(spellID)
        local school = getSpellSchool(spellID)

        return C3.Types.Create("Spell", {
            id = spellID,
            spellID = spellID,
            name = name,
            rank = rank or "",
            icon = icon,
            castTime = castTime or 0,
            minRange = minRange or 0,
            maxRange = maxRange or 0,
            isPassive = isPassive or false,
            isUsable = isUsable,
            notEnoughMana = notEnoughMana,
            school = school,
        })
    end
})

-- Note: cast command is defined in core/secure.lua for proper secure action handling

return true
