--[[
    Core/Debug.lua

    Diagnostics channel. Disabled by default: nothing reaches the chat frame
    unless the user turns it on with "/shiny debug".

    Messages are always kept in a small ring buffer even while debug output is
    off, so "/shiny debug dump" can show what happened during start-up after the
    fact -- which is exactly when the user cannot have had debug enabled yet.
]]

local _, ns = ...

local Debug = ns.NewModule("Debug")
ns.Debug = Debug

local RING_CAPACITY = 250

local ring = {}
local ringCount = 0
local ringNext = 1

Debug.enabled = false

--- Records a diagnostic line, and prints it when debug output is enabled.
-- @param category string Short tag, e.g. "DB", "UI", "DATA".
-- @param message string Message or format string.
-- @param ... any Optional string.format arguments.
function Debug:Log(category, message, ...)
    if select("#", ...) > 0 then
        message = message:format(...)
    end

    local line = ("[%s] %s"):format(category, message)

    ring[ringNext] = line
    ringNext = ringNext % RING_CAPACITY + 1
    if ringCount < RING_CAPACITY then
        ringCount = ringCount + 1
    end

    if self.enabled then
        ns.Print("|cff888888" .. line .. "|r")
    end
end

--- Records a problem. Always kept, printed only in debug mode.
function Debug:Warn(category, message, ...)
    self:Log(category, "|cffff8800WARN|r " .. message, ...)
end

--- Turns debug output on or off.
-- @param enabled boolean
function Debug:SetEnabled(enabled)
    self.enabled = enabled and true or false
end

--- @return boolean Whether debug output currently reaches the chat frame.
function Debug:IsEnabled()
    return self.enabled
end

--- Prints the buffered lines, oldest first.
-- @param limit number|nil How many lines to show (default: all buffered).
function Debug:Dump(limit)
    if ringCount == 0 then
        ns.Print(ns.L["DEBUG_DUMP_EMPTY"])
        return
    end

    limit = math.min(limit or ringCount, ringCount)

    -- The newest `limit` entries end just before the write cursor.
    local first = ringNext - limit

    ns.Print(ns.Plural("DEBUG_DUMP_HEADER", limit):format(limit))
    for offset = 0, limit - 1 do
        local index = (first + offset - 1) % RING_CAPACITY + 1
        ns.Print("|cff888888" .. ring[index] .. "|r")
    end
end

--- Reports an unexpected Lua error without hiding it from the user.
-- Used by the start-up sequence so one broken module cannot silently take the
-- whole addon down.
-- @param context string Where the failure happened.
-- @param errorMessage string
function Debug:ReportError(context, errorMessage)
    local fullMessage = tostring(errorMessage)
    local firstLine = fullMessage:match("^[^\r\n]+") or fullMessage

    ns.Print("|cffff2020" .. ns.L["ERROR_IN_CONTEXT"]:format(context) .. "|r")
    ns.Print("|cffff2020" .. fullMessage .. "|r")

    -- A multiline stack trace scrolls its useful first line out of a small
    -- chat window. Repeat a compact summary last so screenshots always retain
    -- the actual failure instead of only the tail of the call stack.
    if fullMessage:find("[\r\n]") then
        ns.Print("|cffff6060ERROR: " .. firstLine .. "|r")
    end

    self:Log("ERROR", "%s: %s", context, fullMessage)
end
