-- given an Instance and a property-value table subset
-- returns true if all property-values in the subset table exist in the Instance
-- and returns false otherwise
-- returns nil for undefined behavior

local function instanceSubsetEquality(instance: any, subset: any): boolean | nil
	local function subsetEqualityWithContext(seenReferences)
		return function(localInstance, localSubset)
			seenReferences = seenReferences or {}

			if getType(localInstance) ~= "Instance" or not isObjectWithKeys(localSubset) then
				return nil
			end

			local instanceProperties = getRobloxProperties(localInstance.ClassName)
			local instanceChildren = {}
			for _, v in ipairs(localInstance:getChildren()) do
				instanceChildren[v.Name] = true
			end

			return Array.every(Object.keys(localSubset), function(prop)
				local subsetVal = localSubset[prop]
				if isObjectWithKeys(subsetVal) then
					-- return false for circular references
					if seenReferences[subsetVal] then
						return false
					end
					seenReferences[subsetVal] = true
				end
				local result = localInstance ~= nil
					and (table.find(instanceProperties, prop) ~= nil or instanceChildren[prop] ~= nil)
					and equals(localInstance[prop], subsetVal, { subsetEqualityWithContext(seenReferences) })

				seenReferences[subsetVal] = nil
				return result
			end)
		end
	end

	local inner = subsetEqualityWithContext()

	if inner then
		return inner(instance, subset)
	end
	return inner
end
