local addonName, ApiVault = ...

ApiVault.SafeRunner = ApiVault.SafeRunner or {}

ApiVault.SafeRunner.metadata = {
	version = "0.5.0",
	interface = 120007,
	policy = "hardened pcall test runner; protected/secure execution remains blocked; capped serialization and test history enabled",
}

local function packResults(...)
	return { n = select("#", ...), ... }
end

local function joinWarnings(report)
	if not report or type(report.warnings) ~= "table" or #report.warnings == 0 then
		return ""
	end
	return table.concat(report.warnings, "\n")
end

function ApiVault.SafeRunner:Convert(raw, param)
	if ApiVault.ArgumentBuilder and ApiVault.ArgumentBuilder.Convert then
		local value = ApiVault.ArgumentBuilder:Convert(raw, param)
		return value
	end
	raw = ApiVault:StringTrim(raw)
	if raw == "nil" then return nil end
	if raw == "true" then return true end
	if raw == "false" then return false end
	local numeric = tonumber(raw)
	if numeric ~= nil then return numeric end
	return raw
end

function ApiVault.SafeRunner:BuildArguments(functionInfo, rawValues, entry)
	if ApiVault.ArgumentBuilder and ApiVault.ArgumentBuilder.Build then
		return ApiVault.ArgumentBuilder:Build(functionInfo, rawValues, entry)
	end
	local args = {}
	local definitions = functionInfo.args or {}
	for index, param in ipairs(definitions) do
		args[index] = self:Convert(rawValues[index], param)
	end
	args.n = #definitions
	return args, { warnings = {} }
end

function ApiVault.SafeRunner:CanRun(entry, allowRestricted)
	if not entry then
		return false, "No API entry selected."
	end
	if entry.kind ~= "Function" then
		return false, "Only Function entries can be executed. Events use the event listener. Tables are metadata only."
	end
	if entry.risk == "metadata" then
		return false, "Blocked: metadata-only function. This is probably a ScriptObject method and needs a real object instance harness."
	end
	if entry.risk == "protected" then
		return false, "Blocked: protected function metadata is present."
	end
	if ApiVault.SecurePatterns and ApiVault.SecurePatterns.IsBlockedAPI and ApiVault.SecurePatterns:IsBlockedAPI(entry.display) then
		return false, "Blocked: linked secure/protected pattern policy marks this API inspect-only."
	end
	if InCombatLockdown and InCombatLockdown() and entry.risk ~= "safe" then
		return false, "Blocked in combat: restricted/review API."
	end
	if entry.risk ~= "safe" and not allowRestricted then
		return false, "Blocked: restricted/review API. Enable 'Allow restricted/review test' for manual testing out of combat."
	end
	return true, "allowed"
end

function ApiVault.SafeRunner:GetPolicyReport(entry, allowRestricted)
	local ok, reason = self:CanRun(entry, allowRestricted)
	local text = "Runtime Test Lab policy:\n"
	text = text .. "- selected: " .. tostring(entry and entry.display or "<none>") .. "\n"
	text = text .. "- kind: " .. tostring(entry and entry.kind or "?") .. "\n"
	text = text .. "- risk: " .. tostring(entry and entry.risk or "?") .. "\n"
	text = text .. "- allowRestricted: " .. tostring(allowRestricted and true or false) .. "\n"
	text = text .. "- canRun: " .. tostring(ok) .. "\n"
	text = text .. "- reason: " .. tostring(reason) .. "\n"
	if ApiVault.ReturnSerializer and ApiVault.ReturnSerializer.GetPolicyText then
		text = text .. "- " .. ApiVault.ReturnSerializer:GetPolicyText() .. "\n"
	end
	text = text .. "- protected/secure execution: blocked by policy"
	if not ok then
		text = text .. "\nBlocked policy detail:\n" .. tostring(reason) .. "\n- Use Source Usage, Secure Patterns and generated examples to inspect first.\n- Do not bypass combat lockdown or hardware-event restrictions.\n"
	end
	return text, ok, reason
end

function ApiVault.SafeRunner:Run(entry, rawValues, allowRestricted)
	local canRun, reason = self:CanRun(entry, allowRestricted)
	if not canRun then
		if ApiVault.TestHistory and ApiVault.TestHistory.Record then
			ApiVault.TestHistory:Record({
				display = entry and entry.display or "<none>",
				kind = entry and entry.kind or "?",
				risk = entry and entry.risk or "?",
				status = "blocked",
				blocked = true,
				reason = reason,
			})
		end
		return false, reason
	end
	local systemInfo, functionInfo = ApiVault.Index:GetObject(entry)
	if not functionInfo then
		return false, "Function metadata not found."
	end
	local target = ApiVault:ResolveGlobal(entry.display)
	if type(target) ~= "function" then
		local unavailable = "Global function not available in this client/session: " .. tostring(entry.display)
		if ApiVault.TestHistory and ApiVault.TestHistory.Record then
			ApiVault.TestHistory:Record({ display = entry.display, kind = entry.kind, risk = entry.risk, status = "unavailable", reason = unavailable })
		end
		return false, unavailable
	end
	if ApiVault.TestHistory and ApiVault.TestHistory.RememberArguments then
		ApiVault.TestHistory:RememberArguments(entry.display, rawValues or {})
	end
	local args, argReport = self:BuildArguments(functionInfo, rawValues or {}, entry)
	local function invoke()
		return target(unpack(args, 1, args.n or #args))
	end
	local results = packResults(pcall(invoke))
	local ok = results[1]
	local warnings = joinWarnings(argReport)
	if not ok then
		local errorText = tostring(results[2])
		if warnings ~= "" then
			errorText = errorText .. "\n\nArgument warnings:\n" .. warnings
		end
		if ApiVault.TestHistory and ApiVault.TestHistory.Record then
			ApiVault.TestHistory:Record({ display = entry.display, kind = entry.kind, risk = entry.risk, status = "error", reason = tostring(results[2]), argWarnings = warnings })
		end
		return false, errorText
	end
	local output
	if ApiVault.ReturnSerializer and ApiVault.ReturnSerializer.SerializeResults then
		output = ApiVault.ReturnSerializer:SerializeResults(results)
	else
		local lines = {}
		for index = 2, results.n do
			lines[#lines + 1] = "[" .. tostring(index - 1) .. "] " .. ApiVault:Serialize(results[index])
		end
		output = #lines == 0 and "<no return values>" or table.concat(lines, "\n")
	end
	if warnings ~= "" then
		output = output .. "\n\nArgument warnings:\n" .. warnings
	end
	if ApiVault.TestHistory and ApiVault.TestHistory.Record then
		ApiVault.TestHistory:Record({ display = entry.display, kind = entry.kind, risk = entry.risk, status = "ok", result = output, argWarnings = warnings })
	end
	return true, output
end
