local _, GSE = ...
local Statics = GSE.Static

local GNOME = Statics.DebugModules["Translator"]


local function normaliseSpellIDValue(value)
    if type(value) == "table" then
        value = value.spellID or value.id
    end
    return tonumber(value) or value
end

local function findBaseSpellID(spellID)
    spellID = normaliseSpellIDValue(spellID)
    if not spellID then return nil end

    local FindBaseSpellByID = (C_SpellBook and C_SpellBook.FindBaseSpellByID) or FindBaseSpellByID
    if FindBaseSpellByID then
        local ok, baseSpell = pcall(FindBaseSpellByID, spellID)
        baseSpell = ok and normaliseSpellIDValue(baseSpell) or nil
        if baseSpell and baseSpell ~= 0 then
            return baseSpell
        end
    end

    if not GSE.isEmpty(Statics.BaseSpellTable[spellID]) then
        return Statics.BaseSpellTable[spellID]
    end

    return spellID
end

local function findCurrentSpellID(spellID)
    spellID = normaliseSpellIDValue(spellID)
    if not spellID then return nil end

    local FindSpellOverrideByID = (C_SpellBook and C_SpellBook.FindSpellOverrideByID) or FindSpellOverrideByID
    if FindSpellOverrideByID then
        local ok, currentSpell = pcall(FindSpellOverrideByID, spellID)
        currentSpell = ok and normaliseSpellIDValue(currentSpell) or nil
        if currentSpell and currentSpell ~= 0 then
            return currentSpell
        end
    end

    return spellID
end

local function getSpellInfoID(spell)
    if GSE.isEmpty(spell) then
        return nil
    end

    local spellinfo = GSE.GetSpellInfo(spell)
    if spellinfo then
        return normaliseSpellIDValue(spellinfo.spellID)
    end
end

local function spellIDIsInSpellBook(spellID)
    -- TBC Classic Anniversary + MoP Classic expose C_SpellBook but not
    -- FindSpellBookSlotForSpell (Retail-only). The unguarded call there
    -- threw `attempt to call a nil value` every CompileMacroText pass —
    -- ~179 spam errors per Notes save in issue #1925. Mirror the
    -- defensive pattern used by findBaseSpellID / findCurrentSpellID
    -- above: prefer C_SpellBook.X, fall back to the legacy top-level
    -- global, and default to true ("treat as in book") when neither
    -- exists. That matches the nil-slot path on Retail, where an
    -- unresolved slot is also reported as in-book by this function.
    local FindSlot = (C_SpellBook and C_SpellBook.FindSpellBookSlotForSpell)
        or FindSpellBookSlotForSpell
    if not FindSlot then
        return true
    end
    local slot = FindSlot(spellID)
    return slot == nil or slot > 0
end

local function canCacheSpellLookup(spellstring, spellID, rawSpellID)
    if tonumber(spellstring) ~= nil or GSE.isEmpty(spellID) then
        return false
    end

    if rawSpellID and rawSpellID ~= spellID then
        return true
    end

    return spellIDIsInSpellBook(spellID)
end

function GSE.GetBaseSpellID(spell)
    local spellID = tonumber(spell) or getSpellInfoID(spell)
    return findBaseSpellID(spellID)
end

function GSE.GetCurrentSpellID(spell)
    local baseSpell = GSE.GetBaseSpellID(spell)
    return findCurrentSpellID(baseSpell)
end

function GSE.SanitizeSpellCache()
    if GSE.isEmpty(GSESpellCache) then return end

    for _, cache in pairs(GSESpellCache) do
        if type(cache) == "table" then
            for spellName, spellID in pairs(cache) do
                local baseSpell = findBaseSpellID(spellID)
                if baseSpell and baseSpell ~= spellID then
                    cache[spellName] = baseSpell
                end
            end
        end
    end
end

--- GSE.TranslateSequence will translate from local spell name to spell id and back again.\
-- Mode of "STRING" will return local names where mode "ID" will return id's
-- dropAbsolute will remove "$$" from the start of lines.
function GSE.TranslateSequence(tab, mode, dropAbsolute)
    --[==[@debug@
    GSE.PrintDebugMessage("GSE.TranslateSequence  Mode: " .. mode, GNOME)
    --@end-debug@]==]
    for k, v in ipairs(tab) do
        -- Translate Sequence
        if type(v) == "table" then
            tab[k] = GSE.TranslateSequence(v, mode, dropAbsolute)
        else
            local translation = GSE.TranslateString(v, mode, nil, dropAbsolute)
            tab[k] = translation
        end
    end

    -- Check for blanks
    for i, v in ipairs(tab) do
        if GSE.isEmpty(v) or v == "" then
            table.remove(tab, i)
        end
    end
    return tab
end

local function translateStringUncached(instring, mode, cleanNewLines, dropAbsolute)
    instring = GSE.UnEscapeString(instring)
    if type(instring) ~= "string" then return instring and tostring(instring) or "" end
    local lines = GSE.SplitMeIntoLines(instring)
    if #lines > 1 then
        local output = {}
        for k, v in ipairs(lines) do
            output[k] = GSE.TranslateString(v, mode, cleanNewLines, dropAbsolute)
        end
        return table.concat(output, "\n")
    else
        --[==[@debug@
        GSE.PrintDebugMessage("Entering GSE.TranslateString with : \n" .. instring .. "\n " .. mode, GNOME)
        --@end-debug@]==]
        local output = ""
        if not GSE.isEmpty(instring) then
            local absolute = false
            if instring:find("$$", 1, true) then
                --[==[@debug@
                GSE.PrintDebugMessage("Setting Absolute", GNOME)
                --@end-debug@]==]
                absolute = true
                output = string.gsub(instring, "%$%$", "")
            elseif GSE.isEmpty(string.find(instring, "--", 1, true)) then
                for cmd, etc in string.gmatch(instring or "", "/(%w+)%s+([^\n]+)") do
                    --[==[@debug@
                    GSE.PrintDebugMessage("cmd : \n" .. cmd .. " etc: " .. etc, GNOME)
                    --@end-debug@]==]
                    output = output .. GSEOptions.WOWSHORTCUTS .. "/" .. cmd .. Statics.StringReset .. " "
                    if string.lower(cmd) == "use" then
                        local conditionals, mods, trinketstuff = GSE.GetConditionalsFromString(etc)
                        if conditionals then
                            output = output .. mods .. " "
                            --[==[@debug@
                            GSE.PrintDebugMessage("GSE.TranslateSpell conditionals found ", GNOME)
                            --@end-debug@]==]
                        end
                        if tonumber(trinketstuff) and tonumber(trinketstuff) < 17 then
                            output = output .. GSEOptions.KEYWORD .. trinketstuff .. Statics.StringReset
                        else
                            if not cleanNewLines then
                                trinketstuff = string.match(trinketstuff, "^%s*(.-)%s*$")
                            end
                            if string.sub(trinketstuff, 1, 1) == "!" then
                                trinketstuff = string.sub(trinketstuff, 2)
                                output = output .. "!"
                            end
                            local foundspell, returnval =
                                GSE.TranslateSpell(trinketstuff, mode, (cleanNewLines and cleanNewLines or false), true)
                            if foundspell then
                                output = output .. returnval
                            else
                                --[==[@debug@
                                GSE.PrintDebugMessage("Did not find : " .. trinketstuff, GNOME)
                                --@end-debug@]==]
                                output = output .. trinketstuff
                            end
                        end
                    elseif string.lower(cmd) == "castsequence" then
                        --[==[@debug@
                        GSE.PrintDebugMessage("attempting to split : " .. etc, GNOME)
                        --@end-debug@]==]
                        for _, y in ipairs(GSE.split(etc, ";")) do
                            for _, w in ipairs(GSE.SplitCastSequence(y)) do
                                -- Look for conditionals at the startattack
                                local conditionals, mods, uetc = GSE.GetConditionalsFromString(w)
                                if conditionals then
                                    output = output .. GSEOptions.STANDARDFUNCS .. mods .. Statics.StringReset .. " "
                                end

                                uetc = uetc:gsub("^%s*", "")
                                if string.sub(uetc, 1, 1) == "!" then
                                    uetc = string.sub(uetc, 2)
                                    output = output .. "!"
                                end
                                local foundspell, returnval =
                                    GSE.TranslateSpell(uetc, mode, (cleanNewLines and cleanNewLines or false), absolute)
                                output = output .. returnval .. ", "
                            end
                            output = output .. ";"
                        end
                        output = string.sub(output, 1, string.len(output) - 1)
                        local resetleft = string.find(output, ", , ")
                        if not GSE.isEmpty(resetleft) then
                            output = string.sub(output, 1, resetleft - 1)
                        end
                        if string.sub(output, string.len(output) - 1) == ", " then
                            output = string.sub(output, 1, string.len(output) - 2)
                        end
                    elseif string.lower(cmd) == "click" then
                        local trimRight = string.find(etc, " LeftButton")
                        if not GSE.isEmpty(trimRight) then
                            etc = string.sub(etc, 1, trimRight - 1)
                        end
                        -- Always emit a bare `/click NAME` (down=false). GSE
                        -- sequence buttons now pin useOnKeyDown=false, so a
                        -- key-DOWN forward (`LeftButton t`) would no longer match
                        -- the executor's cast edge. Bare /click resolves on the
                        -- up edge under both ActionButtonUseKeyDown states.
                        output = output .. " " .. etc
                    elseif Statics.CastCmds[string.lower(cmd)] then
                        -- Check for cast Sequences
                        if not cleanNewLines then
                            etc = string.match(etc, "^%s*(.-)%s*$")
                        end
                        if string.sub(etc, 1, 1) == "!" then
                            etc = string.sub(etc, 2)
                            output = output .. "!"
                        end
                        local foundspell, returnval =
                            GSE.TranslateSpell(etc, mode, (cleanNewLines and cleanNewLines or false), absolute)
                        if foundspell then
                            output = output .. returnval
                        else
                            --[==[@debug@
                            GSE.PrintDebugMessage("Did not find : " .. etc, GNOME)
                            --@end-debug@]==]
                            output = output .. etc
                        end
                    else
                        -- Pass it through
                        output = output .. " " .. etc
                    end
                end
                -- look for single line commands and mark them up
                for _, v in ipairs(Statics.MacroCommands) do
                    output =
                        string.gsub(
                        output,
                        "/" .. v .. " ",
                        GSEOptions.WOWSHORTCUTS .. "/" .. v .. " " .. Statics.StringReset
                    )
                end
            else
                --[==[@debug@
                GSE.PrintDebugMessage("Detected Comment " .. string.find(instring, "--", 1, true), GNOME)
                --@end-debug@]==]
                output = output .. GSEOptions.CONCAT .. instring .. Statics.StringReset
            end
            -- If nothing was found, pass through
            if GSE.isEmpty(output) then
                output = instring
                -- look for single line commands and mark them up
                for _, v in ipairs(Statics.MacroCommands) do
                    output =
                        string.gsub(
                        output,
                        "/" .. v .. " ",
                        GSEOptions.WOWSHORTCUTS .. "/" .. v .. " " .. Statics.StringReset
                    )
                end
            end

            if GSE.isEmpty(dropAbsolute) then
                dropAbsolute = false
            end
            if absolute and not dropAbsolute then
                output = "$$" .. output
            end
        elseif cleanNewLines then
            output = output .. instring
        end
        --[==[@debug@
        GSE.PrintDebugMessage("Exiting GSE.TranslateString with : \n" .. output, GNOME)
        --@end-debug@]==]
        -- Check for random "," at the end
        if string.sub(output, string.len(output) - 1) == ", " then
            output = string.sub(output, 1, string.len(output) - 2)
        end
        output = string.gsub(output, ", ;", "; ")

        output = string.gsub(output, "%s+", " ")
        return output
    end
end

local translateCache = {}
function GSE.ClearTranslateStringCache()
    translateCache = {}
end

function GSE.TranslateString(instring, mode, cleanNewLines, dropAbsolute)
    if type(instring) ~= "string" then
        return translateStringUncached(instring, mode, cleanNewLines, dropAbsolute)
    end
    local key = instring .. "\30" .. tostring(mode) .. "\30" ..
        tostring(cleanNewLines) .. "\30" .. tostring(dropAbsolute)
    local cached = translateCache[key]
    if cached ~= nil then
        return cached
    end
    local result = translateStringUncached(instring, mode, cleanNewLines, dropAbsolute)
    translateCache[key] = result
    return result
end

function GSE.TranslateSpell(str, mode, cleanNewLines, absolute)
    local output = ""
    local found = false
    -- Check for cases like /cast [talent:7/1] Bladestorm;[talent:7/3] Dragon Roar
    if not cleanNewLines then
        str = string.match(str, "^%s*(.-)%s*$")
    end
    --[==[@debug@
    GSE.PrintDebugMessage("GSE.TranslateSpell Attempting to translate " .. str, GNOME)
    --@end-debug@]==]
    if string.sub(str, string.len(str)) == "," then
        str = string.sub(str, 1, string.len(str) - 1)
    end
    if string.match(str, ";") then
        --[==[@debug@
        GSE.PrintDebugMessage("GSE.TranslateSpell found ; in " .. str .. " about to do recursive call.", GNOME)
        --@end-debug@]==]
        for _, w in ipairs(GSE.split(str, ";")) do
            local returnval
            found, returnval =
                GSE.TranslateSpell(
                (cleanNewLines and w or string.match(w, "^%s*(.-)%s*$")),
                mode,
                (cleanNewLines and cleanNewLines or false)
            )
            output = output .. GSEOptions.KEYWORD .. returnval .. Statics.StringReset .. "; "
        end
        if string.sub(output, string.len(output) - 1) == "; " then
            output = string.sub(output, 1, string.len(output) - 2)
        end
    else
        local conditionals, mods, etc = GSE.GetConditionalsFromString(str)
        if conditionals then
            output = output .. mods .. " "
            --[==[@debug@
            GSE.PrintDebugMessage("GSE.TranslateSpell conditionals found ", GNOME)
            --@end-debug@]==]
        end
        --[==[@debug@
        GSE.PrintDebugMessage("output: " .. output .. " mods: " .. mods .. " etc: " .. etc, GNOME)
        --@end-debug@]==]
        if not cleanNewLines then
            etc = string.match(etc, "^%s*(.-)%s*$")
        end
        if mode == Statics.TranslatorMode.Current then
            if GSEOptions.showCurrentSpells then
                local test = tonumber(etc)
                if test then
                    local currentSpell = GSE.GetCurrentSpellID(test)
                    if currentSpell and currentSpell ~= test then
                        ---@diagnostic disable-next-line: cast-local-type
                        etc = currentSpell
                    end
                end
            end
        end
        local foundspell = GSE.GetSpellId(etc, mode, absolute)

        -- print("Foudn Spell: " .. foundspell .. " etc:" .. etc .. " mode:" .. mode .. " str:" .. str)

        if foundspell then
            --[==[@debug@
            GSE.PrintDebugMessage("Translating Spell ID : " .. etc .. " to " .. foundspell, GNOME)
            --@end-debug@]==]
            output = output .. GSEOptions.KEYWORD .. foundspell .. Statics.StringReset
            found = true
        else
            --[==[@debug@
            GSE.PrintDebugMessage("Did not find : " .. etc .. ".  Spell may no longer exist", GNOME)
            --@end-debug@]==]
            output = output .. GSEOptions.UNKNOWN .. etc .. Statics.StringReset
        end
    end
    return found, output
end

function GSE.GetConditionalsFromString(str)
    --[==[@debug@
    GSE.PrintDebugMessage("Entering GSE.GetConditionalsFromString with : " .. str, GNOME)
    --@end-debug@]==]
    -- Check for conditionals
    local found = false
    local mods = ""
    local leftstr
    local rightstr
    local leftfound = false
    for i = 1, #str do
        local c = str:sub(i, i)
        if c == "[" and not leftfound then
            leftfound = true
            leftstr = i
        end
        if c == "]" then
            rightstr = i
        end
    end
    --[==[@debug@
    GSE.PrintDebugMessage("checking left : " .. (leftstr and leftstr or "nope"), GNOME)
    --@end-debug@]==]
    --[==[@debug@
    GSE.PrintDebugMessage("checking right : " .. (rightstr and rightstr or "nope"), GNOME)
    --@end-debug@]==]
    if rightstr and leftstr then
        found = true
        --[==[@debug@
        GSE.PrintDebugMessage("We have left and right stuff", GNOME)
        --@end-debug@]==]
        mods = string.sub(str, leftstr, rightstr)
        --[==[@debug@
        GSE.PrintDebugMessage("mods changed to: " .. mods, GNOME)
        --@end-debug@]==]
        str = string.sub(str, rightstr + 1)
        str = string.gsub(str, "^%s+", "")
        --[==[@debug@
        GSE.PrintDebugMessage("str changed to: " .. str, GNOME)
        --@end-debug@]==]
    end
    -- if not cleanNewLines then
    --     str = string.match(str, "^%s*(.-)%s*$")
    -- end
    -- Check for resets
    --[==[@debug@
    GSE.PrintDebugMessage("checking for reset= in " .. str, GNOME)
    --@end-debug@]==]
    local resetleft = string.find(str, "reset=")
    if not GSE.isEmpty(resetleft) then
        --[==[@debug@
        GSE.PrintDebugMessage("found reset= at" .. resetleft, GNOME)
        --@end-debug@]==]
    end

    if resetleft then
        local resetright = string.find(str, "%s", resetleft) or (string.len(str) + 1)
        local resetmod = string.sub(str, resetleft, resetright - 1)
        if not GSE.isEmpty(mods) then
            mods = mods .. " "
        end
        mods = mods .. resetmod
        --[==[@debug@
        GSE.PrintDebugMessage("reset= mods changed to: " .. mods, GNOME)
        --@end-debug@]==]
        str = string.sub(str, resetright)
        str = string.gsub(str, "^%s+", "")
        --[==[@debug@
        GSE.PrintDebugMessage("reset= test str changed to: " .. str, GNOME)
        --@end-debug@]==]
        found = true
    end

    mods = GSEOptions.COMMENT .. mods .. Statics.StringReset
    return found, mods, str
end

--- Converts a string spell name to an id and back again.
function GSE.GetSpellId(spellstring, mode, absolute)
    if GSE.isEmpty(mode) then
        mode = Statics.TranslatorMode.ID
    end
    -- C_Spell.GetSpellInfo errors on nil / non-string-non-number input.
    -- CreateSpellEditBox calls us with action.spell, which is nil for a
    -- freshly-added Spell action the user hasn't filled in yet.
    if GSE.isEmpty(spellstring) then
        return nil
    end
    if type(spellstring) ~= "string" and type(spellstring) ~= "number" then
        return nil
    end
    if GSE.isEmpty(GSESpellCache) then
        GSESpellCache = {
            ["enUS"] = {}
        }
    end

    if GSE.isEmpty(GSESpellCache[GetLocale()]) then
        GSESpellCache[GetLocale()] = {}
    end
    local returnval, name, rank, spellId, rawSpellId

    local spellinfo = GSE.GetSpellInfo(spellstring)
    if not spellinfo then
        if type(spellstring) == "string" then
            ---@diagnostic disable-next-line: missing-fields
            spellinfo = {}
            spellinfo.name = spellstring
            if GSESpellCache[GetLocale()][spellstring] then
                spellinfo.spellID = GSESpellCache[GetLocale()][spellstring]
            end
        else
            -- Numeric spell ID that the client doesn't know about: nothing
            -- meaningful to return. Bail rather than indexing nil below.
            return nil
        end
    end
    rank = spellinfo.rank and spellinfo.rank or nil
    rawSpellId = normaliseSpellIDValue(spellinfo.spellID)
    spellId = rawSpellId
    name = spellinfo.name
    if mode ~= Statics.TranslatorMode.ID then
        if not GSE.isEmpty(rank) then
            returnval = name .. "(" .. rank .. ")"
        else
            returnval = name
        end
    else
        returnval = spellId
        -- Check for overrides like Crusade and Avenging Wrath.
        if not absolute and not GSE.isEmpty(returnval) then
            returnval = findBaseSpellID(returnval)
        end
    end
    if not GSE.isEmpty(returnval) then
        if mode == Statics.TranslatorMode.ID and tonumber(spellstring) == nil then
            local existingCache = GSESpellCache[GetLocale()][spellstring]
            if canCacheSpellLookup(spellstring, returnval, rawSpellId) then
                if GSE.isEmpty(existingCache) == true or existingCache ~= returnval then
                    GSESpellCache[GetLocale()][spellstring] = returnval
                end
            elseif not GSE.isEmpty(existingCache) then
                returnval = existingCache
            end
        end
        --[==[@debug@
        GSE.PrintDebugMessage(
            "Converted " .. spellstring .. " to " .. returnval .. " using mode " .. mode,
            "Translator"
        )
        --@end-debug@]==]
    else
        if not GSE.isEmpty(spellstring) then
            --[==[@debug@
            GSE.PrintDebugMessage(spellstring .. " was not found", "Translator")
            --@end-debug@]==]
            if not GSE.isEmpty(GSESpellCache[GetLocale()][spellstring]) then
                returnval = GSESpellCache[GetLocale()][spellstring]
            end
            if GSE.isEmpty(returnval) then
                -- hail mary - try the enUS cache
                if not GSE.isEmpty(GSESpellCache["enUS"][spellstring]) then
                    returnval = GSESpellCache["enUS"][spellstring]
                end
            end
        else
            --[==[@debug@
            GSE.PrintDebugMessage("Nothing was there to be found", "Translator")
            --@end-debug@]==]
        end
    end
    -- print("returning " .. returnval .. " from " .. spellstring)
    return returnval
end

--- Takes a section of a sequence and returns the spells used.
function GSE.IdentifySpells(tab)
    local foundspells = {}
    local returnval = ""
    for _, p in ipairs(tab) do
        -- Run a regex to find all spell id's from the table and add them to the table foundspells
        for m in string.gmatch(p, "%w%d+") do
            foundspells[m] = 1
        end
    end

    for k, _ in pairs(foundspells) do
        if not GSE.isEmpty(GSE.GetSpellId(k, Statics.TranslatorMode.Current, false)) then
            local wowheaddata = "spell=" .. k

            returnval =
                returnval ..
                '<a href="http://www.wowhead.com/spell=' ..
                    k ..
                        '" data-wowhead="' ..
                            wowheaddata .. '">' .. GSE.GetSpellId(k, Statics.TranslatorMode.Current, false) .. "</a>, "
        end
    end

    return string.sub(returnval, 1, string.len(returnval) - 2), foundspells
end

GSE.TranslatorAvailable = true

if type(GSE.DebugProfile) == "function" then GSE.DebugProfile("Translator") end

