local addonName, SinsToolkit = ...

local CVars = {}
SinsToolkit.CVars = CVars

local CATEGORY_NAMES = {
    [0] = "Debug",
    [1] = "Graphics",
    [2] = "Console",
    [3] = "Combat",
    [4] = "Game",
    [5] = "Default",
    [6] = "Net",
    [7] = "Sound",
    [8] = "GM",
    [9] = "None",
}

local cache
local lookup
local valuesCache

local function GetAllCommands()
    if ConsoleGetAllCommands then
        return ConsoleGetAllCommands()
    end
    if C_Console and C_Console.GetAllCommands then
        return C_Console.GetAllCommands()
    end
end

local function GetCVarInfoSafe(name)
    if not name or name == "" or not C_CVar or not C_CVar.GetCVarInfo then
        return false
    end

    local ok, value, defaultValue, serverAccount, serverCharacter, locked, secure, readOnly = pcall(C_CVar.GetCVarInfo, name)
    if not ok or value == nil then
        return false
    end

    return true, value, defaultValue, serverAccount, serverCharacter, locked, secure, readOnly
end

local function ValuesMatch(a, b)
    if a == b then
        return true
    end

    local numA = tonumber(a)
    local numB = tonumber(b)
    if numA and numB then
        return numA == numB
    end

    return tostring(a or "") == tostring(b or "")
end

local function AddCVar(map, name, info)
    if not name or name == "" then
        return
    end

    local key = name:lower()
    if map[key] then
        if info and info.help and info.help ~= "" then
            map[key].description = info.help
        end
        return
    end

    local exists, value, defaultValue, serverAccount, serverCharacter, locked, secure, readOnly = GetCVarInfoSafe(name)
    if not exists then
        return
    end

    map[key] = {
        name = name,
        value = value,
        defaultValue = defaultValue,
        category = info and info.category,
        categoryName = CATEGORY_NAMES[info and info.category] or "Other",
        description = info and info.help or "",
        serverAccount = serverAccount,
        serverCharacter = serverCharacter,
        locked = locked,
        secure = secure,
        readOnly = readOnly,
        isDefault = ValuesMatch(value, defaultValue),
        searchText = (name .. " " .. (info and info.help or "") .. " " .. (CATEGORY_NAMES[info and info.category] or "")):lower(),
    }
end

function CVars:Refresh()
    valuesCache = nil
    local map = {}
    local commands = GetAllCommands()

    if commands then
        for _, info in ipairs(commands) do
            if info and info.command and info.commandType == 0 and info.category ~= 0 and info.category ~= 8 then
                local lower = info.command:lower()
                if not lower:find("debug", 1, true) then
                    AddCVar(map, info.command, info)
                end
            end
        end
    end

    if SinsToolkit.CVarSeedNames then
        for name in pairs(SinsToolkit.CVarSeedNames) do
            AddCVar(map, name)
        end
    end

    cache = {}
    lookup = {}
    for _, info in pairs(map) do
        cache[#cache + 1] = info
        lookup[info.name] = info
        lookup[info.name:lower()] = info
    end

    table.sort(cache, function(a, b)
        return a.name:lower() < b.name:lower()
    end)

    return cache
end

function CVars:GetAll()
    if not cache then
        self:Refresh()
    end
    return cache or {}
end

function CVars:GetInfo(name)
    if not name or name == "" then
        return nil
    end

    if not lookup then
        self:Refresh()
    end

    local info = lookup and (lookup[name] or lookup[name:lower()])
    if not info then
        return nil
    end

    local exists, value, defaultValue, serverAccount, serverCharacter, locked, secure, readOnly = GetCVarInfoSafe(info.name)
    if exists then
        info.value = value
        info.defaultValue = defaultValue
        info.serverAccount = serverAccount
        info.serverCharacter = serverCharacter
        info.locked = locked
        info.secure = secure
        info.readOnly = readOnly
        info.isDefault = ValuesMatch(value, defaultValue)
    end

    return info
end

function CVars:GetValues(search, modifiedOnly)
    local cacheKey = tostring(search or "") .. "\001" .. tostring(modifiedOnly == true)
    if valuesCache and valuesCache[cacheKey] then
        return valuesCache[cacheKey]
    end

    local values = { [""] = "Select a CVar" }
    local needle = (search or ""):lower()

    for _, info in ipairs(self:GetAll()) do
        if (needle == "" or info.searchText:find(needle, 1, true)) and (not modifiedOnly or not info.isDefault) then
            values[info.name] = info.isDefault and info.name or ("|cff55ff55%s|r"):format(info.name)
        end
    end

    valuesCache = valuesCache or {}
    valuesCache[cacheKey] = values
    return values
end

function CVars:GetSummary(name)
    local info = self:GetInfo(name)
    if not info then
        return "Search for and select a CVar to view its current value, default value, and flags."
    end

    local flags = {}
    if info.readOnly then flags[#flags + 1] = "read-only" end
    if info.secure then flags[#flags + 1] = "secure" end
    if info.locked then flags[#flags + 1] = "locked" end
    if info.serverAccount then flags[#flags + 1] = "server account" end
    if info.serverCharacter then flags[#flags + 1] = "server character" end
    if #flags == 0 then flags[#flags + 1] = "editable" end

    return ("Name: %s\nCategory: %s\nStatus: %s\nCurrent: %s\nDefault: %s\nFlags: %s\n\n%s"):format(
        info.name,
        info.categoryName or "Other",
        info.isDefault and "|cff55ff55Default|r" or "|cffff5555Modified|r",
        tostring(info.value),
        tostring(info.defaultValue),
        table.concat(flags, ", "),
        info.description ~= "" and info.description or "No description provided by the WoW client."
    )
end

function CVars:SetValue(name, value)
    local info = self:GetInfo(name)
    if not info then
        return false, "Select a valid CVar first."
    end
    if info.readOnly or info.locked then
        return false, "That CVar is read-only or locked by the client."
    end
    if info.secure and InCombatLockdown and InCombatLockdown() then
        return false, "That secure CVar cannot be changed in combat."
    end
    if value == nil or value == "" then
        return false, "Enter a value before applying."
    end

    if C_CVar and C_CVar.SetCVar then
        C_CVar.SetCVar(info.name, value)
    else
        SetCVar(info.name, value)
    end

    self:Refresh()
    return true, ("Updated %s."):format(info.name)
end

function CVars:ResetToDefault(name)
    local info = self:GetInfo(name)
    if not info then
        return false, "Select a valid CVar first."
    end
    if info.defaultValue == nil then
        return false, "The WoW client did not provide a default value for that CVar."
    end

    return self:SetValue(info.name, tostring(info.defaultValue))
end
