--!nonstrict
local root = script.Parent.Parent

local getEngineFeatureRemoveProxyWrap = require(root.flags.getEngineFeatureRemoveProxyWrap)

local checkForProxyWrap = require(root.util.checkForProxyWrap)

local function checkName(nameList, instanceName)
	if type(nameList) == "table" then
		for _, name in pairs(nameList) do
			if name == instanceName then
				return true
			end
		end
	elseif type(nameList) == "string" then
		return nameList == instanceName
	end
	return false
end

local function getReadableName(nameList)
	if type(nameList) == "table" then
		return table.concat(nameList, " or ")
	elseif type(nameList) == "string" then
		return nameList
	end
	return "*"
end

local function validateWithSchemaHelper(schema, instance, authorizedSet)
	-- validate
	if instance.ClassName ~= schema.ClassName or (schema.Name ~= nil and not checkName(schema.Name, instance.Name)) then
		return { success = false }
	end

	-- validate children
	if schema._children then
		for _, childSchema in pairs(schema._children) do
			local found = false
			local mostRecentFailure
			for _, child in pairs(instance:GetChildren()) do
				local result = validateWithSchemaHelper(childSchema, child, authorizedSet)
				if result.success then
					found = true
					break
				elseif result.message then
					mostRecentFailure = result
				end
			end
			if not found and not childSchema._optional then
				if mostRecentFailure then
					return mostRecentFailure
				else
					return {
						success = false,
						message = "Could not find a " .. childSchema.ClassName .. " called " .. getReadableName(
							childSchema.Name
						) .. " inside " .. instance.Name,
					}
				end
			end
		end
	end

	authorizedSet[instance] = true

	return { success = true }
end

local function validateWithSchema(schema, instance, validationContext)
	if
		instance.ClassName ~= schema.ClassName or (schema.Name ~= nil and (not checkName(schema.Name, instance.Name)))
	then
		return {
			success = false,
			message = "Expected top-level instance to be a " .. schema.ClassName .. " named " .. getReadableName(
				schema.Name
			),
		}
	end

	local allowEditableInstances = validationContext.allowEditableInstances

	local authorizedSet = {}
	local result = validateWithSchemaHelper(schema, instance, authorizedSet)

	if not result.success then
		return result
	end

	-- check for extra descendants
	local unauthorizedDescendantPaths = {}
	for _, descendant in pairs(instance:GetDescendants()) do
		if authorizedSet[descendant] == nil then
			if not getEngineFeatureRemoveProxyWrap() then
				if allowEditableInstances and checkForProxyWrap(descendant) then
					continue
				end
			end
			unauthorizedDescendantPaths[#unauthorizedDescendantPaths + 1] = descendant:GetFullName()
		end
	end

	if #unauthorizedDescendantPaths > 0 then
		return {
			success = false,
			message = "Unexpected Descendants:\n" .. table.concat(unauthorizedDescendantPaths, "\n"),
		}
	end

	return { success = true }
end

return validateWithSchema
