local function x(x)local N local J local T local X local function h(x,N)local T=x.hostParent local h=x.hostKey local g=x.depth local j=x.parentContext X(x)local z=J(N,T,h,j)if z~=nil then z.depth=g end return z end local function g(x,N,X)if config.internalTypeChecks then internalAssert(Type.of(x)==Type.VirtualNode,"\069\120\112\101\099\116\101\100 \097\114\103 \035\049 \116\111 \098\101 \111\102 \116\121\112\101 \086\105\114\116\117\097\108\078\111\100\101")end local h={}for N,J in pairs(x.children)do local g=ElementUtils.getElementByKey(X,N)local j=T(J,g)if j~=nil then x.children[N]=j else h[N]=true end end for N in pairs(h)do x.children[N]=nil end for T,X in ElementUtils.iterateElements(X)do local h=T if T==ElementUtils.UseParentKey then h=x.hostKey end if x.children[T]==nil then local g=J(X,N,h,x.context)if g~=nil then g.depth=x.depth+1 x.children[T]=g end end end end local function j(x,N,J)g(x,N,J)end local function z(x,N,J)if Type.of(J)==Type.Element or J==nil or typeof(J)=="\098\111\111\108\101\097\110"then g(x,N,J)else error(("\037\115\n\037\115"):format("\067\111\109\112\111\110\101\110\116 \114\101\116\117\114\110\101\100 \105\110\118\097\108\105\100 \099\104\105\108\100\114\101\110\058",x.currentElement.source or"\060\101\110\097\098\108\101 \101\108\101\109\101\110\116 \116\114\097\099\101\098\097\099\107\115\062"),0)end end function X(J)if config.internalTypeChecks then internalAssert(Type.of(J)==Type.VirtualNode,"\069\120\112\101\099\116\101\100 \097\114\103 \035\049 \116\111 \098\101 \111\102 \116\121\112\101 \086\105\114\116\117\097\108\078\111\100\101")end local T=ElementKind.of(J.currentElement)if T==ElementKind.Host then x.unmountHostNode(N,J)elseif T==ElementKind.Function then for x,N in pairs(J.children)do X(N)end elseif T==ElementKind.Stateful then J.instance:__unmount()elseif T==ElementKind.Portal then for x,N in pairs(J.children)do X(N)end elseif T==ElementKind.Fragment then for x,N in pairs(J.children)do X(N)end else error(("\085\110\107\110\111\119\110 \069\108\101\109\101\110\116\075\105\110\100 \037\113"):format(tostring(T),2))end end local function K(x,N)local J=N.component(N.props)z(x,x.hostParent,J)return x end local function P(N,J)local T=N.currentElement local X=T.props.target local g=J.props.target assert(x.isHostObject(g),"\069\120\112\101\099\116\101\100 \116\097\114\103\101\116 \116\111 \098\101 \104\111\115\116 \111\098\106\101\099\116")if g~=X then return h(N,J)end local z=J.props[Children]j(N,g,z)return N end local function E(x,N)j(x,x.hostParent,N.elements)return x end function T(J,T,g)if config.internalTypeChecks then internalAssert(Type.of(J)==Type.VirtualNode,"\069\120\112\101\099\116\101\100 \097\114\103 \035\049 \116\111 \098\101 \111\102 \116\121\112\101 \086\105\114\116\117\097\108\078\111\100\101")end if config.typeChecks then assert(Type.of(T)==Type.Element or typeof(T)=="\098\111\111\108\101\097\110"or T==nil,"\069\120\112\101\099\116\101\100 \097\114\103 \035\050 \116\111 \098\101 \111\102 \116\121\112\101 \069\108\101\109\101\110\116\044 \098\111\111\108\101\097\110\044 \111\114 \110\105\108")end if J.currentElement==T and g==nil then return J end if typeof(T)=="\098\111\111\108\101\097\110"or T==nil then X(J)return nil end if J.currentElement.component~=T.component then return h(J,T)end local j=ElementKind.of(T)local z=true if j==ElementKind.Host then J=x.updateHostNode(N,J,T)elseif j==ElementKind.Function then J=K(J,T)elseif j==ElementKind.Stateful then z=J.instance:__update(T,g)elseif j==ElementKind.Portal then J=P(J,T)elseif j==ElementKind.Fragment then J=E(J,T)else error(("\085\110\107\110\111\119\110 \069\108\101\109\101\110\116\075\105\110\100 \037\113"):format(tostring(j),2))end if not z then return J end J.currentElement=T return J end local function m(N,J,T,X)if config.internalTypeChecks then internalAssert(x.isHostObject(J)or J==nil,"\069\120\112\101\099\116\101\100 \097\114\103 \035\050 \116\111 \098\101 \097 \104\111\115\116 \111\098\106\101\099\116")internalAssert(typeof(X)=="\116\097\098\108\101"or X==nil,"\069\120\112\101\099\116\101\100 \097\114\103 \035\052 \116\111 \098\101 \111\102 \116\121\112\101 \116\097\098\108\101 \111\114 \110\105\108")end if config.typeChecks then assert(T~=nil,"\069\120\112\101\099\116\101\100 \097\114\103 \035\051 \116\111 \098\101 \110\111\110-\110\105\108")assert(Type.of(N)==Type.Element or typeof(N)=="\098\111\111\108\101\097\110","\069\120\112\101\099\116\101\100 \097\114\103 \035\049 \116\111 \098\101 \111\102 \116\121\112\101 \069\108\101\109\101\110\116 \111\114 \098\111\111\108\101\097\110")end return{[Type]=Type.VirtualNode,currentElement=N,depth=1;children={},hostParent=J,hostKey=T,context=X;parentContext=X}end local function G(x)local N=x.currentElement local J=N.component(N.props)z(x,x.hostParent,J)end local function n(N)local J=N.currentElement local T=J.props.target local X=J.props[Children]assert(x.isHostObject(T),"\069\120\112\101\099\116\101\100 \116\097\114\103\101\116 \116\111 \098\101 \104\111\115\116 \111\098\106\101\099\116")j(N,T,X)end local function p(x)local N=x.currentElement local J=N.elements j(x,x.hostParent,J)end function J(J,T,X,h)if config.internalTypeChecks then internalAssert(x.isHostObject(T)or T==nil,"\069\120\112\101\099\116\101\100 \097\114\103 \035\050 \116\111 \098\101 \097 \104\111\115\116 \111\098\106\101\099\116")internalAssert(typeof(h)=="\116\097\098\108\101"or h==nil,"\069\120\112\101\099\116\101\100 \097\114\103 \035\052 \116\111 \098\101 \111\102 \116\121\112\101 \116\097\098\108\101 \111\114 \110\105\108")end if config.typeChecks then assert(X~=nil,"\069\120\112\101\099\116\101\100 \097\114\103 \035\051 \116\111 \098\101 \110\111\110-\110\105\108")assert(Type.of(J)==Type.Element or typeof(J)=="\098\111\111\108\101\097\110","\069\120\112\101\099\116\101\100 \097\114\103 \035\049 \116\111 \098\101 \111\102 \116\121\112\101 \069\108\101\109\101\110\116 \111\114 \098\111\111\108\101\097\110")end if typeof(J)=="\098\111\111\108\101\097\110"then return nil end local g=ElementKind.of(J)local j=m(J,T,X,h)if g==ElementKind.Host then x.mountHostNode(N,j)elseif g==ElementKind.Function then G(j)elseif g==ElementKind.Stateful then if J.component.__mount==nil then print("\078\105\108 \099\111\109\112\111\110\101\110\116\033")for x,N in pairs(J)do print(x,N)end print("\099\111\109\112\111\110\101\110\116 \112\114\111\112\115")for x,N in pairs(J.component)do print(x,N)end end J.component:__mount(N,j)elseif g==ElementKind.Portal then n(j)elseif g==ElementKind.Fragment then p(j)else error(("\085\110\107\110\111\119\110 \069\108\101\109\101\110\116\075\105\110\100 \037\113"):format(tostring(g),2))end return j end local function y(N,T,X)if config.typeChecks then assert(Type.of(N)==Type.Element,"\069\120\112\101\099\116\101\100 \097\114\103 \035\049 \116\111 \098\101 \111\102 \116\121\112\101 \069\108\101\109\101\110\116")assert(x.isHostObject(T)or T==nil,"\069\120\112\101\099\116\101\100 \097\114\103 \035\050 \116\111 \098\101 \097 \104\111\115\116 \111\098\106\101\099\116")end if X==nil then X="\082\111\097\099\116\084\114\101\101"end local h={[Type]=Type.VirtualTree;[InternalData]={rootNode=nil;mounted=true}}h[InternalData].rootNode=J(N,T,X)return h end local function f(x)local N=x[InternalData]if config.typeChecks then assert(Type.of(x)==Type.VirtualTree,"\069\120\112\101\099\116\101\100 \097\114\103 \035\049 \116\111 \098\101 \097 \082\111\097\099\116 \104\097\110\100\108\101")assert(N.mounted,"\067\097\110\110\111\116 \117\110\109\111\117\110\116\101\100 \097 \082\111\097\099\116 \116\114\101\101 \116\104\097\116 \104\097\115 \097\108\114\101\097\100\121 \098\101\101\110 \117\110\109\111\117\110\116\101\100")end N.mounted=false if N.rootNode~=nil then X(N.rootNode)end end local function c(x,N)local J=x[InternalData]if config.typeChecks then assert(Type.of(x)==Type.VirtualTree,"\069\120\112\101\099\116\101\100 \097\114\103 \035\049 \116\111 \098\101 \097 \082\111\097\099\116 \104\097\110\100\108\101")assert(Type.of(N)==Type.Element,"\069\120\112\101\099\116\101\100 \097\114\103 \035\050 \116\111 \098\101 \097 \082\111\097\099\116 \069\108\101\109\101\110\116")end J.rootNode=T(J.rootNode,N)return x end N={mountVirtualTree=y;unmountVirtualTree=f,updateVirtualTree=c,createVirtualNode=m;mountVirtualNode=J;unmountVirtualNode=X;updateVirtualNode=T;updateVirtualNodeWithChildren=j,updateVirtualNodeWithRenderResult=z}return N end return x

local function createReconciler(renderer)
	local reconciler
	local mountVirtualNode
	local updateVirtualNode
	local unmountVirtualNode

	--[[
		Unmount the given virtualNode, replacing it with a new node described by
		the given element.

		Preserves host properties, depth, and context from parent.
	]]
	local function replaceVirtualNode(virtualNode, newElement)
		local hostParent = virtualNode.hostParent
		local hostKey = virtualNode.hostKey
		local depth = virtualNode.depth
		local parentContext = virtualNode.parentContext

		unmountVirtualNode(virtualNode)
		local newNode = mountVirtualNode(newElement, hostParent, hostKey, parentContext)

		-- mountVirtualNode can return nil if the element is a boolean
		if newNode ~= nil then
			newNode.depth = depth
		end

		return newNode
	end

	--[[
		Utility to update the children of a virtual node based on zero or more
		updated children given as elements.
	]]
	local function updateChildren(virtualNode, hostParent, newChildElements)
		if config.internalTypeChecks then
			internalAssert(Type.of(virtualNode) == Type.VirtualNode, "Expected arg #1 to be of type VirtualNode")
		end

		local removeKeys = {}

		-- Changed or removed children
		for childKey, childNode in pairs(virtualNode.children) do
			local newElement = ElementUtils.getElementByKey(newChildElements, childKey)
			local newNode = updateVirtualNode(childNode, newElement)

			if newNode ~= nil then
				virtualNode.children[childKey] = newNode
			else
				removeKeys[childKey] = true
			end
		end

		for childKey in pairs(removeKeys) do
			virtualNode.children[childKey] = nil
		end

		-- Added children
		for childKey, newElement in ElementUtils.iterateElements(newChildElements) do
			local concreteKey = childKey
			if childKey == ElementUtils.UseParentKey then
				concreteKey = virtualNode.hostKey
			end

			if virtualNode.children[childKey] == nil then
				local childNode = mountVirtualNode(newElement, hostParent, concreteKey, virtualNode.context)

				-- mountVirtualNode can return nil if the element is a boolean
				if childNode ~= nil then
					childNode.depth = virtualNode.depth + 1
					virtualNode.children[childKey] = childNode
				end
			end
		end
	end

	local function updateVirtualNodeWithChildren(virtualNode, hostParent, newChildElements)
		updateChildren(virtualNode, hostParent, newChildElements)
	end

	local function updateVirtualNodeWithRenderResult(virtualNode, hostParent, renderResult)
		if Type.of(renderResult) == Type.Element
			or renderResult == nil
			or typeof(renderResult) == "boolean"
		then
			updateChildren(virtualNode, hostParent, renderResult)
		else
			error(("%s\n%s"):format(
				"Component returned invalid children:",
				virtualNode.currentElement.source or "<enable element tracebacks>"
			), 0)
		end
	end

	--[[
		Unmounts the given virtual node and releases any held resources.
	]]
	function unmountVirtualNode(virtualNode)
		if config.internalTypeChecks then
			internalAssert(Type.of(virtualNode) == Type.VirtualNode, "Expected arg #1 to be of type VirtualNode")
		end

		local kind = ElementKind.of(virtualNode.currentElement)

		if kind == ElementKind.Host then
			renderer.unmountHostNode(reconciler, virtualNode)
		elseif kind == ElementKind.Function then
			for _, childNode in pairs(virtualNode.children) do
				unmountVirtualNode(childNode)
			end
		elseif kind == ElementKind.Stateful then
			virtualNode.instance:__unmount()
		elseif kind == ElementKind.Portal then
			for _, childNode in pairs(virtualNode.children) do
				unmountVirtualNode(childNode)
			end
		elseif kind == ElementKind.Fragment then
			for _, childNode in pairs(virtualNode.children) do
				unmountVirtualNode(childNode)
			end
		else
			error(("Unknown ElementKind %q"):format(tostring(kind), 2))
		end
	end

	local function updateFunctionVirtualNode(virtualNode, newElement)
		local children = newElement.component(newElement.props)

		updateVirtualNodeWithRenderResult(virtualNode, virtualNode.hostParent, children)

		return virtualNode
	end

	local function updatePortalVirtualNode(virtualNode, newElement)
		local oldElement = virtualNode.currentElement
		local oldTargetHostParent = oldElement.props.target

		local targetHostParent = newElement.props.target

		assert(renderer.isHostObject(targetHostParent), "Expected target to be host object")

		if targetHostParent ~= oldTargetHostParent then
			return replaceVirtualNode(virtualNode, newElement)
		end

		local children = newElement.props[Children]

		updateVirtualNodeWithChildren(virtualNode, targetHostParent, children)

		return virtualNode
	end

	local function updateFragmentVirtualNode(virtualNode, newElement)
		updateVirtualNodeWithChildren(virtualNode, virtualNode.hostParent, newElement.elements)

		return virtualNode
	end

	--[[
		Update the given virtual node using a new element describing what it
		should transform into.

		`updateVirtualNode` will return a new virtual node that should replace
		the passed in virtual node. This is because a virtual node can be
		updated with an element referencing a different component!

		In that case, `updateVirtualNode` will unmount the input virtual node,
		mount a new virtual node, and return it in this case, while also issuing
		a warning to the user.
	]]
	function updateVirtualNode(virtualNode, newElement, newState)
		if config.internalTypeChecks then
			internalAssert(Type.of(virtualNode) == Type.VirtualNode, "Expected arg #1 to be of type VirtualNode")
		end
		if config.typeChecks then
			assert(
				Type.of(newElement) == Type.Element or typeof(newElement) == "boolean" or newElement == nil,
				"Expected arg #2 to be of type Element, boolean, or nil"
			)
		end

		-- If nothing changed, we can skip this update
		if virtualNode.currentElement == newElement and newState == nil then
			return virtualNode
		end

		if typeof(newElement) == "boolean" or newElement == nil then
			unmountVirtualNode(virtualNode)
			return nil
		end

		if virtualNode.currentElement.component ~= newElement.component then
			return replaceVirtualNode(virtualNode, newElement)
		end

		local kind = ElementKind.of(newElement)

		local shouldContinueUpdate = true

		if kind == ElementKind.Host then
			virtualNode = renderer.updateHostNode(reconciler, virtualNode, newElement)
		elseif kind == ElementKind.Function then
			virtualNode = updateFunctionVirtualNode(virtualNode, newElement)
		elseif kind == ElementKind.Stateful then
			shouldContinueUpdate = virtualNode.instance:__update(newElement, newState)
		elseif kind == ElementKind.Portal then
			virtualNode = updatePortalVirtualNode(virtualNode, newElement)
		elseif kind == ElementKind.Fragment then
			virtualNode = updateFragmentVirtualNode(virtualNode, newElement)
		else
			error(("Unknown ElementKind %q"):format(tostring(kind), 2))
		end

		-- Stateful components can abort updates via shouldUpdate. If that
		-- happens, we should stop doing stuff at this point.
		if not shouldContinueUpdate then
			return virtualNode
		end

		virtualNode.currentElement = newElement

		return virtualNode
	end

	--[[
		Constructs a new virtual node but not does mount it.
	]]
	local function createVirtualNode(element, hostParent, hostKey, context)
		if config.internalTypeChecks then
			internalAssert(renderer.isHostObject(hostParent) or hostParent == nil, "Expected arg #2 to be a host object")
			internalAssert(typeof(context) == "table" or context == nil, "Expected arg #4 to be of type table or nil")
		end
		if config.typeChecks then
			assert(hostKey ~= nil, "Expected arg #3 to be non-nil")
			assert(
				Type.of(element) == Type.Element or typeof(element) == "boolean",
				"Expected arg #1 to be of type Element or boolean"
			)
		end

		return {
			[Type] = Type.VirtualNode,
			currentElement = element,
			depth = 1,
			children = {},
			hostParent = hostParent,
			hostKey = hostKey,
			context = context,
			-- This copy of context is useful if the element gets replaced
			-- with an element of a different component type
			parentContext = context,
		}
	end

	local function mountFunctionVirtualNode(virtualNode)
		local element = virtualNode.currentElement

		local children = element.component(element.props)

		updateVirtualNodeWithRenderResult(virtualNode, virtualNode.hostParent, children)
	end

	local function mountPortalVirtualNode(virtualNode)
		local element = virtualNode.currentElement

		local targetHostParent = element.props.target
		local children = element.props[Children]

		assert(renderer.isHostObject(targetHostParent), "Expected target to be host object")

		updateVirtualNodeWithChildren(virtualNode, targetHostParent, children)
	end

	local function mountFragmentVirtualNode(virtualNode)
		local element = virtualNode.currentElement
		local children = element.elements

		updateVirtualNodeWithChildren(virtualNode, virtualNode.hostParent, children)
	end

	--[[
		Constructs a new virtual node and mounts it, but does not place it into
		the tree.
	]]
	function mountVirtualNode(element, hostParent, hostKey, context)
		if config.internalTypeChecks then
			internalAssert(renderer.isHostObject(hostParent) or hostParent == nil, "Expected arg #2 to be a host object")
			internalAssert(typeof(context) == "table" or context == nil, "Expected arg #4 to be of type table or nil")
		end
		if config.typeChecks then
			assert(hostKey ~= nil, "Expected arg #3 to be non-nil")
			assert(
				Type.of(element) == Type.Element or typeof(element) == "boolean",
				"Expected arg #1 to be of type Element or boolean"
			)
		end

		-- Boolean values render as nil to enable terse conditional rendering.
		if typeof(element) == "boolean" then
			return nil
		end

		local kind = ElementKind.of(element)

		local virtualNode = createVirtualNode(element, hostParent, hostKey, context)

		if kind == ElementKind.Host then
			renderer.mountHostNode(reconciler, virtualNode)
		elseif kind == ElementKind.Function then
			mountFunctionVirtualNode(virtualNode)
		elseif kind == ElementKind.Stateful then
			if element.component.__mount == nil then
				print("Nil component!")
				for k, v in pairs(element) do
					print(k, v)
				end
				
				print("component props")
				for k, v in pairs(element.component) do
					print(k, v)
				end
			end
			element.component:__mount(reconciler, virtualNode)
		elseif kind == ElementKind.Portal then
			mountPortalVirtualNode(virtualNode)
		elseif kind == ElementKind.Fragment then
			mountFragmentVirtualNode(virtualNode)
		else
			error(("Unknown ElementKind %q"):format(tostring(kind), 2))
		end

		return virtualNode
	end

	--[[
		Constructs a new Roact virtual tree, constructs a root node for
		it, and mounts it.
	]]
	local function mountVirtualTree(element, hostParent, hostKey)
		if config.typeChecks then
			assert(Type.of(element) == Type.Element, "Expected arg #1 to be of type Element")
			assert(renderer.isHostObject(hostParent) or hostParent == nil, "Expected arg #2 to be a host object")
		end

		if hostKey == nil then
			hostKey = "RoactTree"
		end

		local tree = {
			[Type] = Type.VirtualTree,
			[InternalData] = {
				-- The root node of the tree, which starts into the hierarchy of
				-- Roact component instances.
				rootNode = nil,
				mounted = true,
			},
		}

		tree[InternalData].rootNode = mountVirtualNode(element, hostParent, hostKey)

		return tree
	end

	--[[
		Unmounts the virtual tree, freeing all of its resources.

		No further operations should be done on the tree after it's been
		unmounted, as indicated by its the `mounted` field.
	]]
	local function unmountVirtualTree(tree)
		local internalData = tree[InternalData]
		if config.typeChecks then
			assert(Type.of(tree) == Type.VirtualTree, "Expected arg #1 to be a Roact handle")
			assert(internalData.mounted, "Cannot unmounted a Roact tree that has already been unmounted")
		end

		internalData.mounted = false

		if internalData.rootNode ~= nil then
			unmountVirtualNode(internalData.rootNode)
		end
	end

	--[[
		Utility method for updating the root node of a virtual tree given a new
		element.
	]]
	local function updateVirtualTree(tree, newElement)
		local internalData = tree[InternalData]
		if config.typeChecks then
			assert(Type.of(tree) == Type.VirtualTree, "Expected arg #1 to be a Roact handle")
			assert(Type.of(newElement) == Type.Element, "Expected arg #2 to be a Roact Element")
		end

		internalData.rootNode = updateVirtualNode(internalData.rootNode, newElement)

		return tree
	end

	reconciler = {
		mountVirtualTree = mountVirtualTree,
		unmountVirtualTree = unmountVirtualTree,
		updateVirtualTree = updateVirtualTree,

		createVirtualNode = createVirtualNode,
		mountVirtualNode = mountVirtualNode,
		unmountVirtualNode = unmountVirtualNode,
		updateVirtualNode = updateVirtualNode,
		updateVirtualNodeWithChildren = updateVirtualNodeWithChildren,
		updateVirtualNodeWithRenderResult = updateVirtualNodeWithRenderResult,
	}

	return reconciler
end

return createReconciler
