-- lx-bundle:true

-- This file is part of Lx, created by Liqube <www.liqube.com>
-- Copyright Liqube Audio. All rights reserved.
-- Lx Interim License. See LICENSE for details.

local format = {}

local tostring = tostring

local DUMP_INDENT_STEP = 4 -- Keep nested values readable without growing indentation exponentially
local MAX_DUMP_INDENT = 256 -- Prevent caller-provided indentation from allocating huge strings
local MAX_DUMP_DEPTH = 64 -- Stop hostile or accidental deeply nested tables
local MAX_DUMP_BYTES = 1024 * 1024 -- Keep diagnostic output within a predictable memory budget
local DUMP_TRUNCATED = "<dump truncated>\n"

--- Fast unsigned formatter (signed int64 to unsigned int64 string)
-- Useful when Lua represents uint64 values as negative signed integers
-- @param i64 integer Signed 64-bit integer value
-- @return string Unsigned decimal string representation
function format.i64_to_u64_string(i64)
	-- Lua's integer formatter reinterprets the signed bits without a float conversion
	return string.format("%u", i64)
end

--- Append text while reserving room for one truncation marker
-- @param out table Dumped representation as lines
-- @param state table Current byte count and truncation state
-- @param ... string Text fragments to append
-- @return boolean appended Whether the text fit
local function append_dump_text(out, state, ...)
	local parts = { ... }
	local length = 0
	for _, part in ipairs(parts) do
		length = length + #part
	end

	if state.bytes + length > MAX_DUMP_BYTES - #DUMP_TRUNCATED then
		if not state.truncated then
			out[#out + 1] = DUMP_TRUNCATED
			state.bytes = state.bytes + #DUMP_TRUNCATED
			state.truncated = true
		end

		return false
	end

	out[#out + 1] = table.concat(parts)
	state.bytes = state.bytes + length

	return true
end

--- Recursively format a Lua value within the dump limits
-- @param var any Value to dump
-- @param indent integer Current indentation width
-- @param done table Tables already visited
-- @param depth integer Current table depth
-- @param out table Dumped representation as lines
-- @param state table Current byte count and truncation state
local function dump_value(var, indent, done, depth, out, state)
	local spaces = string.rep(" ", math.min(indent, MAX_DUMP_INDENT))

	if type(var) ~= "table" then
		append_dump_text(out, state, tostring(var), "\n")
		return
	end

	for key, value in pairs(var) do
		local key_text = tostring(key)
		if type(value) == "table" and not done[value] then
			if depth >= MAX_DUMP_DEPTH then
				if not append_dump_text(out, state, spaces, "[", key_text, "] = <maximum depth>\n") then
					return
				end
			else
				done[value] = true -- Mark before recursion so cycles stay finite
				if not append_dump_text(out, state, spaces, "[", key_text, "] =\n")
					or not append_dump_text(out, state, spaces, "(\n")
				then
					return
				end

				dump_value(value, indent + DUMP_INDENT_STEP, done, depth + 1, out, state)
				if state.truncated or not append_dump_text(out, state, spaces, ")\n") then
					return
				end
			end
		elseif type(value) == "string" then
			if not append_dump_text(out, state, spaces, "[", key_text, "] = '", value, "'\n") then
				return
			end
		elseif not append_dump_text(out, state, spaces, "[", key_text, "] = ", tostring(value), "\n") then
			return
		end
	end
end

--- Format a Lua value into readable bounded dump lines
-- Tables use fixed indentation with depth, cycle, and output limits
-- @param var any Value to dump
-- @param indent integer|nil Initial indentation width
-- @param done table|nil Tables already visited
-- @return table lines Dumped representation as lines
function format.dump_to_table(var, indent, done)
	indent = math.tointeger(indent or 0) or 0
	indent = math.max(0, math.min(indent, MAX_DUMP_INDENT))
	done = done or {}
	if type(var) == "table" then
		done[var] = true -- Treat references back to the root as cycles
	end

	local out = {}
	local state = { bytes = 0, truncated = false }
	dump_value(var, indent, done, 0, out, state)

	return out
end

--- Dump a Lua value to a formatted string
-- @param var any Value to dump
-- @param indent integer|nil Initial indentation width
-- @return string Dumped representation
function format.dump(var, indent)
	indent = indent or 4
	local text = table.concat(format.dump_to_table(var, indent))

	return "(\n" .. text .. ")\n"
end

return format
