-- ApiVault safer return serializer. Added in v0.5.0.
-- Metadata/runtime output helper only. No protected/secure execution.
local addonName, ApiVault = ...

ApiVault.ReturnSerializer = ApiVault.ReturnSerializer or {}
local Serializer = ApiVault.ReturnSerializer

Serializer.metadata = {
	version = "0.5.0",
	interface = 120007,
	policy = "serialize test results with depth/key/string caps; summarize frame/userdata-like values; detect recursive tables",
}

Serializer.defaults = {
	maxDepth = 3,
	maxKeys = 30,
	maxString = 512,
	maxResults = 12,
}

local function clampNumber(value, minValue, maxValue, fallback)
	value = tonumber(value)
	if value == nil then
		return fallback
	end
	if value < minValue then
		return minValue
	end
	if value > maxValue then
		return maxValue
	end
	return value
end

function Serializer:GetSettings()
	local db = type(ApiVaultDB) == "table" and ApiVaultDB or nil
	local settings = db and db.settings and db.settings.testLab or nil
	return {
		maxDepth = clampNumber(settings and settings.maxDepth, 1, 6, self.defaults.maxDepth),
		maxKeys = clampNumber(settings and settings.maxKeys, 5, 100, self.defaults.maxKeys),
		maxString = clampNumber(settings and settings.maxString, 80, 2000, self.defaults.maxString),
		maxResults = clampNumber(settings and settings.maxResults, 1, 30, self.defaults.maxResults),
	}
end

local function safeMethod(value, methodName)
	local ok, result = pcall(function()
		local method = value and value[methodName]
		if type(method) == "function" then
			return method(value)
		end
		return nil
	end)
	if ok then
		return result
	end
	return nil
end

function Serializer:SummarizeObject(value)
	local valueType = type(value)
	local objectType = safeMethod(value, "GetObjectType")
	local name = safeMethod(value, "GetName")
	local strata = safeMethod(value, "GetFrameStrata")
	local level = safeMethod(value, "GetFrameLevel")
	local shown = safeMethod(value, "IsShown")
	local parts = { "<" .. valueType }
	if objectType then
		parts[#parts + 1] = "objectType=" .. tostring(objectType)
	end
	if name then
		parts[#parts + 1] = "name=" .. tostring(name)
	end
	if strata then
		parts[#parts + 1] = "strata=" .. tostring(strata)
	end
	if level then
		parts[#parts + 1] = "level=" .. tostring(level)
	end
	if shown ~= nil then
		parts[#parts + 1] = "shown=" .. tostring(shown)
	end
	parts[#parts + 1] = ">"
	return table.concat(parts, " ")
end

function Serializer:IsObjectLike(value)
	if value == nil then
		return false
	end
	local valueType = type(value)
	if valueType ~= "table" and valueType ~= "userdata" then
		return false
	end
	return safeMethod(value, "GetObjectType") ~= nil or safeMethod(value, "GetName") ~= nil
end

function Serializer:Serialize(value, opts, state)
	opts = opts or self:GetSettings()
	state = state or { depth = 0, visited = {} }
	local valueType = type(value)
	if valueType == "nil" or valueType == "number" or valueType == "boolean" then
		return tostring(value)
	end
	if valueType == "string" then
		local text = value
		local maxString = opts.maxString or self.defaults.maxString
		if string.len(text) > maxString then
			text = string.sub(text, 1, maxString) .. "... <string truncated>"
		end
		return string.format("%q", text)
	end
	if valueType == "function" or valueType == "thread" then
		return "<" .. valueType .. ">"
	end
	if valueType == "userdata" then
		return self:SummarizeObject(value)
	end
	if valueType ~= "table" then
		return "<" .. valueType .. ">"
	end
	if self:IsObjectLike(value) then
		return self:SummarizeObject(value)
	end
	if state.visited[value] then
		return "<recursive table>"
	end
	if (state.depth or 0) >= (opts.maxDepth or self.defaults.maxDepth) then
		return "<table max-depth=" .. tostring(opts.maxDepth or self.defaults.maxDepth) .. ">"
	end
	state.visited[value] = true
	local childState = { depth = (state.depth or 0) + 1, visited = state.visited }
	local parts = { "{" }
	local count = 0
	local maxKeys = opts.maxKeys or self.defaults.maxKeys
	for key, child in pairs(value) do
		count = count + 1
		if count > maxKeys then
			parts[#parts + 1] = " ... <key cap " .. tostring(maxKeys) .. ">"
			break
		end
		parts[#parts + 1] = " [" .. self:Serialize(key, opts, childState) .. "] = " .. self:Serialize(child, opts, childState) .. ","
	end
	parts[#parts + 1] = " }"
	state.visited[value] = nil
	return table.concat(parts, "")
end

function Serializer:SerializeResults(packedResults)
	local opts = self:GetSettings()
	local total = tonumber(packedResults and packedResults.n) or 0
	local resultCount = math.max(0, total - 1)
	local maxResults = opts.maxResults or self.defaults.maxResults
	local lines = {}
	if resultCount == 0 then
		return "<no return values>"
	end
	for index = 1, resultCount do
		if index > maxResults then
			lines[#lines + 1] = "... <result cap " .. tostring(maxResults) .. ">"
			break
		end
		lines[#lines + 1] = "[" .. tostring(index) .. "] " .. self:Serialize(packedResults[index + 1], opts)
	end
	return table.concat(lines, "\n")
end

function Serializer:GetPolicyText()
	local opts = self:GetSettings()
	return "serializer caps: maxDepth=" .. tostring(opts.maxDepth) .. ", maxKeys=" .. tostring(opts.maxKeys) .. ", maxString=" .. tostring(opts.maxString) .. ", maxResults=" .. tostring(opts.maxResults)
end


function Serializer:SerializeTableRows(value, maxRows)
    maxRows = tonumber(maxRows) or 40
    if type(value) ~= "table" then
        return self:Serialize(value)
    end
    if self:IsObjectLike(value) then
        return self:SummarizeObject(value)
    end
    local lines = { "Table viewer summary" }
    local count = 0
    for key, child in pairs(value) do
        count = count + 1
        if count > maxRows then
            lines[#lines + 1] = "... <row cap " .. tostring(maxRows) .. ">"
            break
        end
        lines[#lines + 1] = tostring(count) .. ". [" .. self:Serialize(key, {maxDepth=1, maxKeys=8, maxString=120, maxResults=4}) .. "] = " .. self:Serialize(child, {maxDepth=1, maxKeys=8, maxString=160, maxResults=4})
    end
    if count == 0 then lines[#lines + 1] = "<empty table>" end
    return table.concat(lines, "\n")
end
