--!strict
-- ROBLOX upstream: https://github.com/facebook/react/blob/1faf9e3dd5d6492f3607d5c721055819e4106bc6/packages/react-reconciler/src/ReactFiberBeginWork.new.js
--[[*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
]]
-- ROBLOX TODO remove this when CLI-38793 lands
--!nolint LocalShadow
-- FIXME (roblox): remove this when our unimplemented
local function unimplemented(message: string)
	print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
	print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
	print("UNIMPLEMENTED ERROR: " .. tostring(message))
	error("FIXME (roblox): " .. message .. " is unimplemented", 2)
end

local __DEV__ = _G.__DEV__ :: boolean
local __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ =
	_G.__DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ :: boolean
local __COMPAT_WARNINGS__ = _G.__COMPAT_WARNINGS__ :: boolean

-- ROBLOX: use patched console from Shared
local Shared = require(script.Parent.Parent:WaitForChild('shared'))
local console = Shared.console
local LuauPolyfill = require(script.Parent.Parent:WaitForChild('luau-polyfill'))
local Array = LuauPolyfill.Array
local Object = LuauPolyfill.Object
local inspect = LuauPolyfill.util.inspect

local ReactTypes = require(script.Parent.Parent:WaitForChild('shared'))
type ReactProviderType<T> = ReactTypes.ReactProviderType<T>
type ReactContext<T> = ReactTypes.ReactContext<T>
type React_Component<Props, State> = ReactTypes.React_Component<Props, State>

local React = require(script.Parent.Parent:WaitForChild('react'))
type LazyComponentType<T, P> = React.LazyComponent<T, P>

local ReactInternalTypes = require(script.Parent:WaitForChild('ReactInternalTypes'))
type Fiber = ReactInternalTypes.Fiber
type FiberRoot = ReactInternalTypes.FiberRoot
local ReactFiberLane = require(script.Parent:WaitForChild('ReactFiberLane'))
type Lanes = ReactFiberLane.Lanes
-- type Lane = ReactFiberLane.Lane;
-- local type {MutableSource} = require(Packages.Shared.ReactTypes)

local ReactFiberSuspenseComponent = require(script.Parent:WaitForChild('ReactFiberSuspenseComponent.new'))
type SuspenseState = ReactFiberSuspenseComponent.SuspenseState
type SuspenseListRenderState = ReactFiberSuspenseComponent.SuspenseListRenderState
type SuspenseListTailMode = ReactFiberSuspenseComponent.SuspenseListTailMode
local ReactFiberSuspenseContext = require(script.Parent:WaitForChild('ReactFiberSuspenseContext.new'))
type SuspenseContext = ReactFiberSuspenseContext.SuspenseContext

local ReactFiberOffscreenComponent = require(script.Parent:WaitForChild('ReactFiberOffscreenComponent'))
type OffscreenProps = ReactFiberOffscreenComponent.OffscreenProps
type OffscreenState = ReactFiberOffscreenComponent.OffscreenState

local checkPropTypes = require(script.Parent.Parent:WaitForChild('shared')).checkPropTypes

local ReactWorkTags = require(script.Parent:WaitForChild('ReactWorkTags'))-- local IndeterminateComponent = ReactWorkTags.IndeterminateComponent

local FunctionComponent = ReactWorkTags.FunctionComponent
local ClassComponent = ReactWorkTags.ClassComponent
local HostRoot = ReactWorkTags.HostRoot
local HostComponent = ReactWorkTags.HostComponent
local HostText = ReactWorkTags.HostText
local HostPortal = ReactWorkTags.HostPortal
local ForwardRef = ReactWorkTags.ForwardRef
local Fragment = ReactWorkTags.Fragment
local Mode = ReactWorkTags.Mode
local ContextProvider = ReactWorkTags.ContextProvider
local ContextConsumer = ReactWorkTags.ContextConsumer
local Profiler = ReactWorkTags.Profiler
local SuspenseComponent = ReactWorkTags.SuspenseComponent
local SuspenseListComponent = ReactWorkTags.SuspenseListComponent
local MemoComponent = ReactWorkTags.MemoComponent
local SimpleMemoComponent = ReactWorkTags.SimpleMemoComponent
local LazyComponent = ReactWorkTags.LazyComponent
local IncompleteClassComponent = ReactWorkTags.IncompleteClassComponent
-- local FundamentalComponent = ReactWorkTags.FundamentalComponent
-- local ScopeComponent = ReactWorkTags.ScopeComponent
local OffscreenComponent = ReactWorkTags.OffscreenComponent
local LegacyHiddenComponent = ReactWorkTags.LegacyHiddenComponent
local ReactFiberFlags = require(script.Parent:WaitForChild('ReactFiberFlags'))
local NoFlags = ReactFiberFlags.NoFlags
local StaticMask = ReactFiberFlags.StaticMask
local PerformedWork = ReactFiberFlags.PerformedWork
local Placement = ReactFiberFlags.Placement
local Hydrating = ReactFiberFlags.Hydrating
local ContentReset = ReactFiberFlags.ContentReset
local DidCapture = ReactFiberFlags.DidCapture
-- local Update = ReactFiberFlags.Update
local Ref = ReactFiberFlags.Ref
local Deletion = ReactFiberFlags.Deletion
local ForceUpdateForLegacySuspense = ReactFiberFlags.ForceUpdateForLegacySuspense
local ReactSharedInternals = require(script.Parent.Parent:WaitForChild('shared')).ReactSharedInternals
local ReactFeatureFlags = require(script.Parent.Parent:WaitForChild('shared')).ReactFeatureFlags
local debugRenderPhaseSideEffectsForStrictMode =
	ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode
local disableLegacyContext = ReactFeatureFlags.disableLegacyContext
local disableModulePatternComponents = ReactFeatureFlags.disableModulePatternComponents
local enableProfilerTimer = ReactFeatureFlags.enableProfilerTimer
local enableSchedulerTracing = ReactFeatureFlags.enableSchedulerTracing
local enableSuspenseServerRenderer = ReactFeatureFlags.enableSuspenseServerRenderer
-- local enableFundamentalAPI = ReactFeatureFlags.enableFundamentalAPI
local warnAboutDefaultPropsOnFunctionComponents =
	ReactFeatureFlags.warnAboutDefaultPropsOnFunctionComponents
-- local enableScopeAPI = ReactFeatureFlags.enableScopeAPI
local invariant = require(script.Parent.Parent:WaitForChild('shared')).invariant
local describeError = require(script.Parent.Parent:WaitForChild('shared')).describeError
local shallowEqual = require(script.Parent.Parent:WaitForChild('shared')).shallowEqual
local getComponentName = require(script.Parent.Parent:WaitForChild('shared')).getComponentName
local ReactSymbols = require(script.Parent.Parent:WaitForChild('shared')).ReactSymbols
local REACT_LAZY_TYPE = ReactSymbols.REACT_LAZY_TYPE
local _getIteratorFn = ReactSymbols.getIteratorFn
local ReactStrictModeWarnings = require(script.Parent:WaitForChild('ReactStrictModeWarnings.new'))
local ReactCurrentFiber = require(script.Parent:WaitForChild('ReactCurrentFiber'))
local getCurrentFiberOwnerNameInDevOrNull =
	ReactCurrentFiber.getCurrentFiberOwnerNameInDevOrNull
local setIsRendering = ReactCurrentFiber.setIsRendering
local ReactFiberHotReloadingModule = require(script.Parent:WaitForChild('ReactFiberHotReloading.new'))
local resolveFunctionForHotReloading =
	ReactFiberHotReloadingModule.resolveFunctionForHotReloading
local resolveForwardRefForHotReloading =
	ReactFiberHotReloadingModule.resolveForwardRefForHotReloading
local resolveClassForHotReloading =
	ReactFiberHotReloadingModule.resolveClassForHotReloading

local ReactChildFiber = require(script.Parent:WaitForChild('ReactChildFiber.new')):: any
local mountChildFibers = ReactChildFiber.mountChildFibers
local reconcileChildFibers = ReactChildFiber.reconcileChildFibers
local cloneChildFibers = ReactChildFiber.cloneChildFibers
local ReactUpdateQueue = require(script.Parent:WaitForChild('ReactUpdateQueue.new'))
local processUpdateQueue = ReactUpdateQueue.processUpdateQueue
local cloneUpdateQueue = ReactUpdateQueue.cloneUpdateQueue
local initializeUpdateQueue = ReactUpdateQueue.initializeUpdateQueue
local ReactTypeOfMode = require(script.Parent:WaitForChild('ReactTypeOfMode'))
local ConcurrentMode = ReactTypeOfMode.ConcurrentMode
local NoMode = ReactTypeOfMode.NoMode
local ProfileMode = ReactTypeOfMode.ProfileMode
local StrictMode = ReactTypeOfMode.StrictMode
local BlockingMode = ReactTypeOfMode.BlockingMode
local ReactFiberHostConfig = require(script.Parent:WaitForChild('ReactFiberHostConfig'))
local shouldSetTextContent = ReactFiberHostConfig.shouldSetTextContent
local isSuspenseInstancePending = ReactFiberHostConfig.isSuspenseInstancePending
local isSuspenseInstanceFallback = ReactFiberHostConfig.isSuspenseInstanceFallback
local registerSuspenseInstanceRetry = ReactFiberHostConfig.registerSuspenseInstanceRetry
local supportsHydration = ReactFiberHostConfig.supportsHydration
type SuspenseInstance = ReactFiberHostConfig.SuspenseInstance

local ReactFiberHostContext = require(script.Parent:WaitForChild('ReactFiberHostContext.new'))
local pushHostContext = ReactFiberHostContext.pushHostContext
local pushHostContainer = ReactFiberHostContext.pushHostContainer
local suspenseStackCursor = ReactFiberSuspenseContext.suspenseStackCursor
local hasSuspenseContext = ReactFiberSuspenseContext.hasSuspenseContext
local ForceSuspenseFallback = ReactFiberSuspenseContext.ForceSuspenseFallback
local addSubtreeSuspenseContext = ReactFiberSuspenseContext.addSubtreeSuspenseContext
local InvisibleParentSuspenseContext =
	ReactFiberSuspenseContext.InvisibleParentSuspenseContext
local pushSuspenseContext = ReactFiberSuspenseContext.pushSuspenseContext
-- local setShallowSuspenseContext = ReactFiberSuspenseContext.setShallowSuspenseContext
local setDefaultShallowSuspenseContext =
	ReactFiberSuspenseContext.setDefaultShallowSuspenseContext
-- local {findFirstSuspended} = require("./ReactFiberSuspenseComponent/new")
-- local {
--   ,
local ReactFiberNewContext = require(script.Parent:WaitForChild('ReactFiberNewContext.new'))
local propagateContextChange = ReactFiberNewContext.propagateContextChange
local readContext = ReactFiberNewContext.readContext
local calculateChangedBits = ReactFiberNewContext.calculateChangedBits
-- local scheduleWorkOnParentPath = ReactFiberNewContext.scheduleWorkOnParentPath
local prepareToReadContext = ReactFiberNewContext.prepareToReadContext
local pushProvider = ReactFiberNewContext.pushProvider

-- ROBLOX deviation: Lazy init all methods from ReactFiberHooks
local lazyRefs = {
	renderWithHooksRef = nil :: any,
	bailoutHooksRef = nil :: any,
	shouldSuspendRef = nil :: any,
}

local function shouldSuspend(fiber: Fiber): boolean
	if not lazyRefs.shouldSuspendRef then
		lazyRefs.shouldSuspendRef = require(script.Parent:WaitForChild('ReactFiberReconciler')).shouldSuspend
	end
	return lazyRefs.shouldSuspendRef(fiber)
end

-- ROBLOX deviation: collective lazy init methods from ReactFiberHooks
local function initReactFiberHooks()
	local ReactFiberHooks = require(script.Parent:WaitForChild('ReactFiberHooks.new'))	
lazyRefs.renderWithHooksRef = ReactFiberHooks.renderWithHooks
	lazyRefs.bailoutHooksRef = ReactFiberHooks.bailoutHooks
end

-- ROBLOX deviation: Lazy init renderWithHooks from ReactFiberHooks
local function renderWithHooks(...)
	if not lazyRefs.renderWithHooksRef then
		initReactFiberHooks()
	end
	return lazyRefs.renderWithHooksRef(...)
end

-- ROBLOX deviation: Lazy init bailoutHooks from ReactFiberHooks
local function bailoutHooks(...)
	if not lazyRefs.bailoutHooksRef then
		initReactFiberHooks()
	end
	return lazyRefs.bailoutHooksRef(...)
end

local stopProfilerTimerIfRunning =
	require(script.Parent:WaitForChild('ReactProfilerTimer.new')).stopProfilerTimerIfRunning
local ReactFiberContext = require(script.Parent:WaitForChild('ReactFiberContext.new'))
local getMaskedContext = ReactFiberContext.getMaskedContext
local getUnmaskedContext = ReactFiberContext.getUnmaskedContext
local hasLegacyContextChanged = ReactFiberContext.hasContextChanged
local pushLegacyContextProvider = ReactFiberContext.pushContextProvider
local isLegacyContextProvider = ReactFiberContext.isContextProvider
local pushTopLevelContextObject = ReactFiberContext.pushTopLevelContextObject
local invalidateContextProvider = ReactFiberContext.invalidateContextProvider

local ReactFiberHydrationContext = require(script.Parent:WaitForChild('ReactFiberHydrationContext.new'))
local resetHydrationState = ReactFiberHydrationContext.resetHydrationState
local enterHydrationState = ReactFiberHydrationContext.enterHydrationState
local reenterHydrationStateFromDehydratedSuspenseInstance =
	ReactFiberHydrationContext.reenterHydrationStateFromDehydratedSuspenseInstance
local tryToClaimNextHydratableInstance =
	ReactFiberHydrationContext.tryToClaimNextHydratableInstance
local warnIfHydrating = ReactFiberHydrationContext.warnIfHydrating
local ReactFiberClassComponent = require(script.Parent:WaitForChild('ReactFiberClassComponent.new')):: any
local adoptClassInstance = ReactFiberClassComponent.adoptClassInstance
local applyDerivedStateFromProps = ReactFiberClassComponent.applyDerivedStateFromProps
local constructClassInstance = ReactFiberClassComponent.constructClassInstance
local mountClassInstance = ReactFiberClassComponent.mountClassInstance
local resumeMountClassInstance = ReactFiberClassComponent.resumeMountClassInstance
local updateClassInstance = ReactFiberClassComponent.updateClassInstance

local resolveDefaultProps =
	require(script.Parent:WaitForChild('ReactFiberLazyComponent.new')).resolveDefaultProps
local ReactFiber = require(script.Parent:WaitForChild('ReactFiber.new'))
local resolveLazyComponentTag = ReactFiber.resolveLazyComponentTag
local createFiberFromFragment = ReactFiber.createFiberFromFragment
local createFiberFromOffscreen = ReactFiber.createFiberFromOffscreen

local createFiberFromTypeAndProps = ReactFiber.createFiberFromTypeAndProps
local isSimpleFunctionComponent = ReactFiber.isSimpleFunctionComponent
local createWorkInProgress = ReactFiber.createWorkInProgress
local ReactFiberWorkLoop = require(script.Parent:WaitForChild('ReactFiberWorkLoop.new')):: any
local pushRenderLanes = ReactFiberWorkLoop.pushRenderLanes
local markSpawnedWork = ReactFiberWorkLoop.markSpawnedWork
local retryDehydratedSuspenseBoundary = ReactFiberWorkLoop.retryDehydratedSuspenseBoundary
local scheduleUpdateOnFiber = ReactFiberWorkLoop.scheduleUpdateOnFiber
local renderDidSuspendDelayIfPossible = ReactFiberWorkLoop.renderDidSuspendDelayIfPossible
local getWorkInProgressRoot = ReactFiberWorkLoop.getWorkInProgressRoot
local getExecutionContext = ReactFiberWorkLoop.getExecutionContext
local RetryAfterError = ReactFiberWorkLoop.RetryAfterError
local NoContext = ReactFiberWorkLoop.NoContext

local Schedule_tracing_wrap
local setWorkInProgressVersion =
	require(script.Parent:WaitForChild('ReactMutableSource.new')).setWorkInProgressVersion
local markSkippedUpdateLanes =
	require(script.Parent:WaitForChild('ReactFiberWorkInProgress')).markSkippedUpdateLanes
local ConsolePatchingDev = require(script.Parent.Parent:WaitForChild('shared')).ConsolePatchingDev
local disableLogs = ConsolePatchingDev.disableLogs
local reenableLogs = ConsolePatchingDev.reenableLogs

local ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner

local exports: { [string]: any } = {}

-- ROBLOX deviation: Pre-declare functions
local bailoutOnAlreadyFinishedWork, updateFunctionComponent

local didReceiveUpdate: boolean = false

-- ROBLOX deviation: put didWarns in table to reduce number of local variables
local DidWarn = {
	didWarnAboutBadClass = {} :: { [string]: boolean },
	didWarnAboutModulePatternComponent = {} :: { [string]: boolean },
	didWarnAboutContextTypeOnFunctionComponent = {} :: { [string]: boolean },
	didWarnAboutGetDerivedStateOnFunctionComponent = {} :: { [string]: boolean },
	didWarnAboutFunctionRefs = {} :: { [string]: boolean },
	didWarnAboutDefaultPropsOnFunctionComponent = {} :: { [string]: boolean },
}
-- export local didWarnAboutReassigningProps
-- local didWarnAboutRevealOrder
-- local didWarnAboutTailOptions
local updateSimpleMemoComponent

if __DEV__ then
	DidWarn.didWarnAboutBadClass = {}
	DidWarn.didWarnAboutModulePatternComponent = {}
	DidWarn.didWarnAboutContextTypeOnFunctionComponent = {}
	DidWarn.didWarnAboutGetDerivedStateOnFunctionComponent = {}
	DidWarn.didWarnAboutFunctionRefs = {}
	exports.didWarnAboutReassigningProps = false
	--   didWarnAboutRevealOrder = {}
	--   didWarnAboutTailOptions = {}
	DidWarn.didWarnAboutDefaultPropsOnFunctionComponent = {}
end

local function reconcileChildren(
	current: Fiber | nil,
	workInProgress: Fiber,
	nextChildren: any,
	renderLanes: Lanes
)
	if current == nil then
		-- If this is a fresh new component that hasn't been rendered yet, we
		-- won't update its child set by applying minimal side-effects. Instead,
		-- we will add them all to the child before it gets rendered. That means
		-- we can optimize this reconciliation pass by not tracking side-effects.
		workInProgress.child =
			mountChildFibers(workInProgress, nil, nextChildren, renderLanes)
	else
		-- If the current child is the same as the work in progress, it means that
		-- we haven't yet started any work on these children. Therefore, we use
		-- the clone algorithm to create a copy of all the current children.

		-- If we had any progressed work already, that is invalid at this point so
		-- let's throw it out.
		workInProgress.child = reconcileChildFibers(
			workInProgress,
			(current :: Fiber).child,
			nextChildren,
			renderLanes
		)
	end
end

local function forceUnmountCurrentAndReconcile(
	current: Fiber,
	workInProgress: Fiber,
	nextChildren: any,
	renderLanes: Lanes
)
	-- This function is fork of reconcileChildren. It's used in cases where we
	-- want to reconcile without matching against the existing set. This has the
	-- effect of all current children being unmounted; even if the type and key
	-- are the same, the old child is unmounted and a new child is created.
	--
	-- To do this, we're going to go through the reconcile algorithm twice. In
	-- the first pass, we schedule a deletion for all the current children by
	-- passing nil.
	workInProgress.child =
		reconcileChildFibers(workInProgress, current.child, nil, renderLanes)
	-- In the second pass, we mount the new children. The trick here is that we
	-- pass nil in place of where we usually pass the current child set. This has
	-- the effect of remounting all children regardless of whether their
	-- identities match.
	workInProgress.child =
		reconcileChildFibers(workInProgress, nil, nextChildren, renderLanes)
end

local function updateForwardRef(
	current: Fiber | nil,
	workInProgress: Fiber,
	Component: any,
	nextProps: any,
	renderLanes: Lanes
)
	-- TODO: current can be non-null here even if the component
	-- hasn't yet mounted. This happens after the first render suspends.
	-- We'll need to figure out if this is fine or can cause issues.

	if __DEV__ or __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ then
		if workInProgress.type ~= workInProgress.elementType then
			-- Lazy component props can't be validated in createElement
			-- because they're only guaranteed to be resolved here.
			-- ROBLOX deviation: adds support for legacy Roact's validateProps()
			local innerPropTypes = Component.propTypes
			local validateProps = Component.validateProps
			if innerPropTypes or validateProps then
				checkPropTypes(
					innerPropTypes,
					validateProps,
					nextProps, -- Resolved props
					"prop",
					getComponentName(Component)
				)
			end
		end
	end

	local render = Component.render
	local ref = workInProgress.ref

	-- The rest is a fork of updateFunctionComponent
	local nextChildren
	prepareToReadContext(
		workInProgress,
		renderLanes,
		exports.markWorkInProgressReceivedUpdate
	)
	if __DEV__ then
		ReactCurrentOwner.current = workInProgress
		setIsRendering(true)
		nextChildren =
			renderWithHooks(current, workInProgress, render, nextProps, ref, renderLanes)
		if
			debugRenderPhaseSideEffectsForStrictMode
			and bit32.band(workInProgress.mode, StrictMode) ~= 0
		then
			disableLogs()
			local ok, result = xpcall(
				renderWithHooks,
				describeError,
				current,
				workInProgress,
				render,
				nextProps,
				ref,
				renderLanes
			)
			if ok then
				nextChildren = result
			end
			-- finally
			reenableLogs()

			if not ok then
				error(result)
			end
		end
		setIsRendering(false)
	else
		nextChildren =
			renderWithHooks(current, workInProgress, render, nextProps, ref, renderLanes)
	end

	if current ~= nil and not didReceiveUpdate then
		bailoutHooks(current, workInProgress, renderLanes)
		return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
	end

	-- React DevTools reads this flag.
	workInProgress.flags = bit32.bor(workInProgress.flags, PerformedWork)
	reconcileChildren(current, workInProgress, nextChildren, renderLanes)
	return workInProgress.child
end

local function updateMemoComponent(
	current: Fiber | nil,
	workInProgress: Fiber,
	Component: any,
	nextProps: any,
	updateLanes: Lanes,
	renderLanes: Lanes
): nil | Fiber
	if current == nil then
		local type_ = Component.type
		if
			isSimpleFunctionComponent(type_)
			and Component.compare == nil
			-- SimpleMemoComponent codepath doesn't resolve outer props either.
			and Component.defaultProps == nil
		then
			local resolvedType = type_
			if __DEV__ then
				resolvedType = resolveFunctionForHotReloading(type_)
			end
			-- If this is a plain function component without default props,
			-- and with only the default shallow comparison, we upgrade it
			-- to a SimpleMemoComponent to allow fast path updates.
			workInProgress.tag = SimpleMemoComponent
			workInProgress.type = resolvedType
			if __DEV__ then
				validateFunctionComponentInDev(workInProgress, type_)
			end
			return updateSimpleMemoComponent(
				current,
				workInProgress,
				resolvedType,
				nextProps,
				updateLanes,
				renderLanes
			)
		end
		if __DEV__ or __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ then
			-- ROBLOX deviation: adds support for legacy Roact's validateProps()
			local innerPropTypes
			local validateProps
			-- ROBLOX deviation: avoid accessing propTypes on a function, Lua doesn't support fields on functions
			if type(type_) == "table" then
				innerPropTypes = type_.propTypes
				validateProps = type_.validateProps
			end

			if innerPropTypes or validateProps then
				-- Inner memo component props aren't currently validated in createElement.
				-- We could move it there, but we'd still need this for lazy code path.
				checkPropTypes(
					innerPropTypes,
					validateProps,
					nextProps, -- Resolved props
					"prop",
					getComponentName(type_)
				)
			end
		end
		local child = createFiberFromTypeAndProps(
			Component.type,
			nil,
			nextProps,
			workInProgress,
			workInProgress.mode,
			renderLanes
		)
		child.ref = workInProgress.ref
		child.return_ = workInProgress
		workInProgress.child = child
		return child
	end
	-- ROBLOX TODO Deviation: remove redefinition + typecast when this lands: CLI-38793
	-- ROBLOX the if clause above returns early if current is nil
	local current = current :: Fiber
	if __DEV__ or __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ then
		local type_ = Component.type
		-- ROBLOX deviation: adds support for legacy Roact's validateProps()
		local innerPropTypes
		local validateProps
		-- ROBLOX deviation: only check for propTypes on class components, Lua doesn't support fields on functions
		if type(type_) == "table" then
			innerPropTypes = type_.propTypes
			validateProps = type_.validateProps
		end

		if innerPropTypes or validateProps then
			-- Inner memo component props aren't currently validated in createElement.
			-- We could move it there, but we'd still need this for lazy code path.
			checkPropTypes(
				innerPropTypes,
				validateProps,
				nextProps, -- Resolved props
				"prop",
				getComponentName(type_)
			)
		end
	end
	local currentChild = (current.child :: any) :: Fiber -- This is always exactly one child
	if not ReactFiberLane.includesSomeLane(updateLanes, renderLanes) then
		-- This will be the props with resolved defaultProps,
		-- unlike current.memoizedProps which will be the unresolved ones.
		local prevProps = currentChild.memoizedProps
		-- Default to shallow comparison
		local compare = Component.compare
		if compare == nil then
			compare = shallowEqual
		end
		if compare(prevProps, nextProps) and current.ref == workInProgress.ref then
			return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
		end
	end
	-- React DevTools reads this flag.
	workInProgress.flags = bit32.bor(workInProgress.flags, PerformedWork)
	local newChild = createWorkInProgress(currentChild, nextProps)
	newChild.ref = workInProgress.ref
	newChild.return_ = workInProgress
	workInProgress.child = newChild
	return newChild
end

function updateSimpleMemoComponent(
	current: Fiber | nil,
	workInProgress: Fiber,
	Component: any,
	nextProps: any,
	updateLanes: Lanes,
	renderLanes: Lanes
): nil | Fiber
	-- TODO: current can be non-null here even if the component
	-- hasn't yet mounted. This happens when the inner render suspends.
	-- We'll need to figure out if this is fine or can cause issues.

	if __DEV__ or __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ then
		if workInProgress.type ~= workInProgress.elementType then
			-- Lazy component props can't be validated in createElement
			-- because they're only guaranteed to be resolved here.
			local outerMemoType = workInProgress.elementType
			if outerMemoType["$$typeof"] == REACT_LAZY_TYPE then
				-- We warn when you define propTypes on lazy()
				-- so let's just skip over it to find memo() outer wrapper.
				-- Inner props for memo are validated later.
				local lazyComponent: LazyComponentType<any, any> = outerMemoType
				local payload = lazyComponent._payload
				local init = lazyComponent._init
				local ok, result = xpcall(init, describeError, payload)
				if ok then
					outerMemoType = result
				else
					outerMemoType = nil
				end
				-- Inner propTypes will be validated in the function component path.
				-- ROBLOX deviation: adds support for legacy Roact's validateProps()
				local outerPropTypes
				local validateProps
				-- ROBLOX deviation: avoid accessing propTypes on a function, Lua doesn't support fields on functions
				if outerMemoType ~= nil and type(outerMemoType) == "table" then
					outerPropTypes = (outerMemoType :: any).propTypes
					-- ROBLOX deviation: support legacy Roact's equivalent of propTypes
					validateProps = (outerMemoType :: any).validateProps
				end

				if outerPropTypes or validateProps then
					checkPropTypes(
						outerPropTypes,
						validateProps,
						nextProps, -- Resolved (SimpleMemoComponent has no defaultProps)
						"prop",
						getComponentName(outerMemoType)
					)
				end
			end
		end
	end
	if current ~= nil then
		-- ROBLOX TODO Deviation: remove redefinition + typecast when this lands: CLI-38793
		-- This unfortunately breaks if current gets reassigned somewhere in this if clause
		local current = current :: Fiber
		local prevProps = current.memoizedProps
		-- ROBLOX Deviation: replacing ternary operator
		local preventBailout = true
		if __DEV__ then
			preventBailout = workInProgress.type == current.type
		end
		if
			shallowEqual(prevProps, nextProps)
			and current.ref == workInProgress.ref
			-- Prevent bailout if the implementation changed due to hot reload.
			and preventBailout
		then
			didReceiveUpdate = false
			if not ReactFiberLane.includesSomeLane(renderLanes, updateLanes) then
				-- The pending lanes were cleared at the beginning of beginWork. We're
				-- about to bail out, but there might be other lanes that weren't
				-- included in the current render. Usually, the priority level of the
				-- remaining updates is accumlated during the evaluation of the
				-- component (i.e. when processing the update queue). But since since
				-- we're bailing out early *without* evaluating the component, we need
				-- to account for it here, too. Reset to the value of the current fiber.
				-- NOTE: This only applies to SimpleMemoComponent, not MemoComponent,
				-- because a MemoComponent fiber does not have hooks or an update queue
				-- rather, it wraps around an inner component, which may or may not
				-- contains hooks.
				-- TODO: Move the reset at in beginWork out of the common path so that
				-- this is no longer necessary.
				workInProgress.lanes = current.lanes
				return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
			elseif bit32.band(current.flags, ForceUpdateForLegacySuspense) ~= NoFlags then
				-- This is a special case that only exists for legacy mode.
				-- See https://github.com/facebook/react/pull/19216.
				didReceiveUpdate = true
			end
		end
	end
	return updateFunctionComponent(
		current,
		workInProgress,
		Component,
		nextProps,
		renderLanes
	)
end

local function updateOffscreenComponent(
	current: Fiber?,
	workInProgress: Fiber,
	renderLanes: Lanes
): Fiber | nil
	local nextProps: OffscreenProps = workInProgress.pendingProps
	local nextChildren = nextProps.children

	local prevState: OffscreenState?
	if current ~= nil then
		-- ROBLOX FIXME: remove :: recast once Luau understands if-statement nil checks
		prevState = (current :: Fiber).memoizedState
	end

	if
		nextProps.mode == "hidden"
		or nextProps.mode == "unstable-defer-without-hiding"
	then
		if bit32.band(workInProgress.mode, ConcurrentMode) == NoMode then
			-- In legacy sync mode, don't defer the subtree. Render it now.
			-- TODO: Figure out what we should do in Blocking mode.
			local nextState: OffscreenState = {
				baseLanes = ReactFiberLane.NoLanes,
			}
			workInProgress.memoizedState = nextState
			pushRenderLanes(workInProgress, renderLanes)
		-- ROBLOX TODO: recast ReactFiberLane.OffscreenLane to type Lane
		elseif
			not ReactFiberLane.includesSomeLane(renderLanes, ReactFiberLane.OffscreenLane)
		then
			local nextBaseLanes
			if prevState ~= nil then
				-- ROBLOX FIXME: remove :: recast once Luau understands if-statement nil check
				local prevBaseLanes = (prevState :: OffscreenState).baseLanes
				nextBaseLanes = ReactFiberLane.mergeLanes(prevBaseLanes, renderLanes)
			else
				nextBaseLanes = renderLanes
			end

			-- Schedule this fiber to re-render at offscreen priority. Then bailout.
			if enableSchedulerTracing then
				markSpawnedWork(ReactFiberLane.OffscreenLane)
			end

			-- deviation: unchain multiple assignment into two discrete assignments.
			workInProgress.childLanes =
				ReactFiberLane.laneToLanes(ReactFiberLane.OffscreenLane)
			workInProgress.lanes = workInProgress.childLanes

			local nextState: OffscreenState = {
				baseLanes = nextBaseLanes,
			}
			workInProgress.memoizedState = nextState
			-- We're about to bail out, but we need to push this to the stack anyway
			-- to avoid a push/pop misalignment.
			pushRenderLanes(workInProgress, nextBaseLanes)
			return nil
		else
			-- Rendering at offscreen, so we can clear the base lanes.
			local nextState: OffscreenState = {
				baseLanes = ReactFiberLane.NoLanes,
			}
			workInProgress.memoizedState = nextState
			-- Push the lanes that were skipped when we bailed out.
			local subtreeRenderLanes = renderLanes

			-- deviation: ternary converted to if statement
			if prevState ~= nil then
				-- ROBLOX FIXME: remove :: recast once Luau understands if-statement nil check
				subtreeRenderLanes = (prevState :: OffscreenState).baseLanes
			end

			pushRenderLanes(workInProgress, subtreeRenderLanes)
		end
	else
		local subtreeRenderLanes
		if prevState ~= nil then
			-- ROBLOX FIXME: remove :: recast once Luau understands if-statement nil check
			subtreeRenderLanes = ReactFiberLane.mergeLanes(
				(prevState :: OffscreenState).baseLanes,
				renderLanes
			)
			-- Since we're not hidden anymore, reset the state
			workInProgress.memoizedState = nil
		else
			-- We weren't previously hidden, and we still aren't, so there's nothing
			-- special to do. Need to push to the stack regardless, though, to avoid
			-- a push/pop misalignment.
			subtreeRenderLanes = renderLanes
		end
		pushRenderLanes(workInProgress, subtreeRenderLanes)
	end

	reconcileChildren(current, workInProgress, nextChildren, renderLanes)
	return workInProgress.child
end

-- Note: These happen to have identical begin phases, for now. We shouldn't hold
-- ourselves to this constraint, though. If the behavior diverges, we should
-- fork the function.
local updateLegacyHiddenComponent = updateOffscreenComponent

function updateFragment(current: Fiber | nil, workInProgress: Fiber, renderLanes: Lanes)
	local nextChildren = workInProgress.pendingProps
	reconcileChildren(current, workInProgress, nextChildren, renderLanes)
	return workInProgress.child
end

function updateMode(current: Fiber | nil, workInProgress: Fiber, renderLanes: Lanes)
	local nextChildren = workInProgress.pendingProps.children
	reconcileChildren(current, workInProgress, nextChildren, renderLanes)
	return workInProgress.child
end

function updateProfiler(current: Fiber | nil, workInProgress: Fiber, renderLanes: Lanes)
	if enableProfilerTimer then
		-- Reset effect durations for the next eventual effect phase.
		-- These are reset during render to allow the DevTools commit hook a chance to read them,
		local stateNode = workInProgress.stateNode
		stateNode.effectDuration = 0
		stateNode.passiveEffectDuration = 0
	end
	local nextProps = workInProgress.pendingProps
	local nextChildren = nextProps.children
	reconcileChildren(current, workInProgress, nextChildren, renderLanes)
	return workInProgress.child
end

local function markRef(current: Fiber | nil, workInProgress: Fiber)
	local ref = workInProgress.ref
	if
		(current == nil and ref ~= nil)
		or (current ~= nil and (current :: Fiber).ref ~= ref)
	then
		-- Schedule a Ref effect
		workInProgress.flags = bit32.bor(workInProgress.flags, Ref)
	end
end

function updateFunctionComponent(
	current,
	workInProgress,
	Component,
	nextProps: any,
	renderLanes
)
	if __DEV__ or __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ then
		-- ROBLOX deviation: function components can't have props in Lua
		if
			type(Component) ~= "function"
			and (workInProgress.type ~= workInProgress.elementType)
		then
			-- Lazy component props can't be validated in createElement
			-- because they're only guaranteed to be resolved here.
			-- ROBLOX deviation: adds support for legacy Roact's validateProps()
			local innerPropTypes
			local validateProps
			-- ROBLOX deviation: Roact won't support propTypes on functional components
			if type(Component) == "table" then
				innerPropTypes = (Component :: any).propTypes
				validateProps = (Component :: any).validateProps
			end

			if innerPropTypes or validateProps then
				checkPropTypes(
					innerPropTypes,
					validateProps,
					nextProps, -- Resolved props
					"prop",
					getComponentName(Component)
				)
			end
		end
	end

	local context
	if not disableLegacyContext then
		local unmaskedContext = getUnmaskedContext(workInProgress, Component, true)
		context = getMaskedContext(workInProgress, unmaskedContext)
	end

	local nextChildren
	prepareToReadContext(
		workInProgress,
		renderLanes,
		exports.markWorkInProgressReceivedUpdate
	)
	if __DEV__ then
		ReactCurrentOwner.current = workInProgress
		setIsRendering(true)
		nextChildren = renderWithHooks(
			current,
			workInProgress,
			Component,
			nextProps,
			context,
			renderLanes
		)
		if
			debugRenderPhaseSideEffectsForStrictMode
			and bit32.band(workInProgress.mode, StrictMode) ~= 0
		then
			disableLogs()
			local ok, result = xpcall(
				renderWithHooks,
				describeError,
				current,
				workInProgress,
				Component,
				nextProps,
				context,
				renderLanes
			)
			-- finally
			reenableLogs()
			if ok then
				nextChildren = result
			else
				error(result)
			end
		end
		setIsRendering(false)
	else
		nextChildren = renderWithHooks(
			current,
			workInProgress,
			Component,
			nextProps,
			context,
			renderLanes
		)
	end

	if current ~= nil and not didReceiveUpdate then
		bailoutHooks(current, workInProgress, renderLanes)
		return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
	end

	-- React DevTools reads this flag.
	workInProgress.flags = bit32.bor(workInProgress.flags, PerformedWork)
	reconcileChildren(current, workInProgress, nextChildren, renderLanes)
	return workInProgress.child
end

-- function updateBlock<Props, Data>(
--   current: Fiber | nil,
--   workInProgress: Fiber,
--   block: BlockComponent<Props, Data>,
--   nextProps: any,
--   renderLanes: Lanes,
-- )
--   -- TODO: current can be non-null here even if the component
--   -- hasn't yet mounted. This happens after the first render suspends.
--   -- We'll need to figure out if this is fine or can cause issues.

--   local render = block._render
--   local data = block._data

--   -- The rest is a fork of updateFunctionComponent
--   local nextChildren
--   prepareToReadContext(workInProgress, renderLanes, exports.markWorkInProgressReceivedUpdate)
--   if  __DEV__ then
--     ReactCurrentOwner.current = workInProgress
--     setIsRendering(true)
--     nextChildren = renderWithHooks(
--       current,
--       workInProgress,
--       render,
--       nextProps,
--       data,
--       renderLanes,
--     )
--     if
--       debugRenderPhaseSideEffectsForStrictMode and
--       workInProgress.mode & StrictMode
--     )
--       disableLogs()
--       try {
--         nextChildren = renderWithHooks(
--           current,
--           workInProgress,
--           render,
--           nextProps,
--           data,
--           renderLanes,
--         )
--       } finally {
--         reenableLogs()
--       end
--     end
--     setIsRendering(false)
--   else
--     nextChildren = renderWithHooks(
--       current,
--       workInProgress,
--       render,
--       nextProps,
--       data,
--       renderLanes,
--     )
--   end

--   if current ~= nil and !didReceiveUpdate)
--     bailoutHooks(current, workInProgress, renderLanes)
--     return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
--   end

--   -- React DevTools reads this flag.
--   workInProgress.flags |= PerformedWork
--   reconcileChildren(current, workInProgress, nextChildren, renderLanes)
--   return workInProgress.child
-- end

-- ROBLOX FIXME: type refinement
-- local function updateClassComponent(
--   current: Fiber | nil,
--   ...
-- )
local function updateClassComponent(
	current: any,
	workInProgress: Fiber,
	Component: any,
	nextProps: any,
	renderLanes: Lanes
)
	if __DEV__ or __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ then
		if workInProgress.type ~= workInProgress.elementType then
			-- Lazy component props can't be validated in createElement
			-- because they're only guaranteed to be resolved here.
			-- ROBLOX deviation: adds support for legacy Roact's validateProps()
			local innerPropTypes = Component.propTypes
			local validateProps = Component.validateProps
			if innerPropTypes or validateProps then
				checkPropTypes(
					innerPropTypes,
					validateProps,
					nextProps, -- Resolved props
					"prop",
					getComponentName(Component)
				)
			end
		end
	end

	-- Push context providers early to prevent context stack mismatches.
	-- During mounting we don't know the child context yet as the instance doesn't exist.
	-- We will invalidate the child context in finishClassComponent() right after rendering.
	local hasContext
	if isLegacyContextProvider(Component) then
		hasContext = true
		pushLegacyContextProvider(workInProgress)
	else
		hasContext = false
	end
	-- ROBLOX deviation: pass in function to break cyclic require dependency
	prepareToReadContext(
		workInProgress,
		renderLanes,
		exports.markWorkInProgressReceivedUpdate
	)

	local instance = workInProgress.stateNode
	local shouldUpdate
	if instance == nil then
		if current ~= nil then
			-- A class component without an instance only mounts if it suspended
			-- inside a non-concurrent tree, in an inconsistent state. We want to
			-- treat it like a new mount, even though an empty version of it already
			-- committed. Disconnect the alternate pointers.
			current.alternate = nil
			workInProgress.alternate = nil
			-- Since this is conceptually a new fiber, schedule a Placement effect
			workInProgress.flags = bit32.bor(workInProgress.flags, Placement)
		end
		-- In the initial pass we might need to construct the instance.
		constructClassInstance(workInProgress, Component, nextProps)
		mountClassInstance(workInProgress, Component, nextProps, renderLanes)
		shouldUpdate = true
	elseif current == nil then
		-- In a resume, we'll already have an instance we can reuse.
		shouldUpdate =
			resumeMountClassInstance(workInProgress, Component, nextProps, renderLanes)
	else
		shouldUpdate = updateClassInstance(
			current,
			workInProgress,
			Component,
			nextProps,
			renderLanes
		)
	end
	local nextUnitOfWork = finishClassComponent(
		current,
		workInProgress,
		Component,
		shouldUpdate,
		hasContext,
		renderLanes
	)
	if __DEV__ then
		local inst = workInProgress.stateNode
		if shouldUpdate and inst.props ~= nextProps then
			if not exports.didWarnAboutReassigningProps then
				console.error(
[[It looks like %s is reassigning its own `this.props` while rendering. This is not supported and can lead to confusing bugs.]]
,
					getComponentName(workInProgress.type) or "a component"
				)
			end
			exports.didWarnAboutReassigningProps = true
		end
	end
	return nextUnitOfWork
end

function finishClassComponent(
	current: Fiber | nil,
	workInProgress: Fiber,
	Component: any,
	shouldUpdate: boolean,
	hasContext: boolean,
	renderLanes: Lanes
)
	-- Refs should update even if shouldComponentUpdate returns false
	markRef(current, workInProgress)

	local didCaptureError = bit32.band(workInProgress.flags, DidCapture) ~= NoFlags

	if not shouldUpdate and not didCaptureError then
		-- Context providers should defer to sCU for rendering
		if hasContext then
			invalidateContextProvider(workInProgress, Component, false)
		end

		return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
	end

	local instance = workInProgress.stateNode

	-- Rerender
	ReactCurrentOwner.current = workInProgress
	local nextChildren
	if
		didCaptureError
		and (
			Component.getDerivedStateFromError == nil
			or type(Component.getDerivedStateFromError) ~= "function"
		)
	then
		-- If we captured an error, but getDerivedStateFromError is not defined,
		-- unmount all the children. componentDidCatch will schedule an update to
		-- re-render a fallback. This is temporary until we migrate everyone to
		-- the new API.
		-- TODO: Warn in a future release.
		nextChildren = nil

		if enableProfilerTimer then
			stopProfilerTimerIfRunning(workInProgress)
		end
	else
		if __DEV__ then
			setIsRendering(true)
			-- deviation: Call with ':' instead of '.' so that render can access self
			nextChildren = instance:render()
			if
				debugRenderPhaseSideEffectsForStrictMode
				and bit32.band(workInProgress.mode, StrictMode) ~= 0
			then
				disableLogs()
				-- deviation: Pass instance so that render can access self
				local ok, result = xpcall(instance.render, describeError, instance)
				-- finally
				reenableLogs()
				if not ok then
					error(result)
				end
			end
			setIsRendering(false)
		else
			-- deviation: Call with ':' instead of '.' so that render can access self
			nextChildren = instance:render()
		end
	end

	-- React DevTools reads this flag.
	workInProgress.flags = bit32.bor(workInProgress.flags, PerformedWork)
	if current ~= nil and didCaptureError then
		-- If we're recovering from an error, reconcile without reusing any of
		-- the existing children. Conceptually, the normal children and the children
		-- that are shown on error are two different sets, so we shouldn't reuse
		-- normal children even if their identities match.
		forceUnmountCurrentAndReconcile(
			current :: Fiber,
			workInProgress,
			nextChildren,
			renderLanes
		)
	else
		reconcileChildren(current, workInProgress, nextChildren, renderLanes)
	end

	-- Memoize state using the values we just used to render.
	-- TODO: Restructure so we never read values from the instance.
	workInProgress.memoizedState = instance.state

	-- The context might have changed so we need to recalculate it.
	if hasContext then
		invalidateContextProvider(workInProgress, Component, true)
	end

	return workInProgress.child
end

local function pushHostRootContext(workInProgress)
	-- FIXME (roblox): type refinement '(workInProgress.stateNode: FiberRoot)'
	local root = workInProgress.stateNode
	if root.pendingContext then
		pushTopLevelContextObject(
			workInProgress,
			root.pendingContext,
			root.pendingContext ~= root.context
		)
	elseif root.context then
		-- Should always be set
		pushTopLevelContextObject(workInProgress, root.context, false)
	end
	pushHostContainer(workInProgress, root.containerInfo)
end

local function updateHostRoot(current, workInProgress, renderLanes)
	pushHostRootContext(workInProgress)
	local updateQueue = workInProgress.updateQueue
	invariant(
		current ~= nil and updateQueue ~= nil,
[[If the root does not have an updateQueue, we should have already bailed out. This error is likely caused by a bug in React. Please file an issue.]]	


)
	local nextProps = workInProgress.pendingProps
	local prevState = workInProgress.memoizedState
	local prevChildren
	if prevState ~= nil then
		prevChildren = prevState.element
	end
	cloneUpdateQueue(current, workInProgress)
	processUpdateQueue(workInProgress, nextProps, nil, renderLanes)
	local nextState = workInProgress.memoizedState
	-- Caution: React DevTools currently depends on this property
	-- being called "element".
	local nextChildren = nextState.element
	if nextChildren == prevChildren then
		resetHydrationState()
		return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
	end
	local root: FiberRoot = workInProgress.stateNode
	if root.hydrate and enterHydrationState(workInProgress) then
		-- If we don't have any current children this might be the first pass.
		-- We always try to hydrate. If this isn't a hydration pass there won't
		-- be any children to hydrate which is effectively the same thing as
		-- not hydrating.

		if supportsHydration then
			local mutableSourceEagerHydrationData = root.mutableSourceEagerHydrationData
			if mutableSourceEagerHydrationData ~= nil then
				for i = 1, #mutableSourceEagerHydrationData, 2 do
					-- FIXME (roblox): type refinement
					-- local mutableSource = ((mutableSourceEagerHydrationData[
					--   i
					-- ]: any): MutableSource<any>)
					local mutableSource = mutableSourceEagerHydrationData[i]
					local version = mutableSourceEagerHydrationData[i + 1]
					setWorkInProgressVersion(mutableSource, version)
				end
			end
		end

		local child = mountChildFibers(workInProgress, nil, nextChildren, renderLanes)
		workInProgress.child = child

		local node = child
		while node do
			-- Mark each child as hydrating. This is a fast path to know whether this
			-- tree is part of a hydrating tree. This is used to determine if a child
			-- node has fully mounted yet, and for scheduling event replaying.
			-- Conceptually this is similar to Placement in that a new subtree is
			-- inserted into the React tree here. It just happens to not need DOM
			-- mutations because it already exists.
			node.flags =
				bit32.bor(bit32.band(node.flags, bit32.bnot(Placement)), Hydrating)
			node = node.sibling
		end
	else
		-- Otherwise reset hydration state in case we aborted and resumed another
		-- root.
		reconcileChildren(current, workInProgress, nextChildren, renderLanes)
		resetHydrationState()
	end
	return workInProgress.child
end

-- FIXME (roblox): type refinement
-- local function updateHostComponent(
--   current: Fiber | nil,
--   workInProgress: Fiber,
--   renderLanes: Lanes
-- )
local function updateHostComponent(
	current: any,
	workInProgress: Fiber,
	renderLanes: Lanes
)
	pushHostContext(workInProgress)

	if current == nil then
		tryToClaimNextHydratableInstance(workInProgress)
	end

	local type_ = workInProgress.type
	local nextProps = workInProgress.pendingProps
	local prevProps
	if current ~= nil then
		prevProps = current.memoizedProps
	end

	local nextChildren = nextProps.children
	local isDirectTextChild = shouldSetTextContent(type_, nextProps)

	if isDirectTextChild then
		-- We special case a direct text child of a host node. This is a common
		-- case. We won't handle it as a reified child. We will instead handle
		-- this in the host environment that also has access to this prop. That
		-- avoids allocating another HostText fiber and traversing it.
		nextChildren = nil
	elseif prevProps ~= nil and shouldSetTextContent(type_, prevProps) then
		-- If we're switching from a direct text child to a normal child, or to
		-- empty, we need to schedule the text content to be reset.
		workInProgress.flags = bit32.bor(workInProgress.flags, ContentReset)
	end

	-- React DevTools reads this flag.
	workInProgress.flags = bit32.bor(workInProgress.flags, PerformedWork)

	markRef(current, workInProgress)
	reconcileChildren(current, workInProgress, nextChildren, renderLanes)
	return workInProgress.child
end

local function updateHostText(current, workInProgress)
	if current == nil then
		tryToClaimNextHydratableInstance(workInProgress)
	end
	-- Nothing to do here. This is terminal. We'll do the completion step
	-- immediately after.
	return nil
end

local function mountLazyComponent(
	_current,
	workInProgress,
	elementType,
	updateLanes,
	renderLanes
)
	if _current ~= nil then
		-- A lazy component only mounts if it suspended inside a non-
		-- concurrent tree, in an inconsistent state. We want to treat it like
		-- a new mount, even though an empty version of it already committed.
		-- Disconnect the alternate pointers.
		_current.alternate = nil
		workInProgress.alternate = nil
		-- Since this is conceptually a new fiber, schedule a Placement effect
		workInProgress.flags = bit32.bor(workInProgress.flags, Placement)
	end

	local props = workInProgress.pendingProps
	local lazyComponent: LazyComponentType<any, any> = elementType
	local payload = lazyComponent._payload
	local init = lazyComponent._init
	local Component = init(payload)
	-- Store the unwrapped component in the type.
	workInProgress.type = Component
	workInProgress.tag = resolveLazyComponentTag(Component)
	local resolvedTag = workInProgress.tag
	local resolvedProps = resolveDefaultProps(Component, props)
	local child
	if resolvedTag == FunctionComponent then
		if __DEV__ then
			validateFunctionComponentInDev(workInProgress, Component)
			Component = resolveFunctionForHotReloading(Component)
			workInProgress.type = Component
		end
		child = updateFunctionComponent(
			nil,
			workInProgress,
			Component,
			resolvedProps,
			renderLanes
		)
		return child
	elseif resolvedTag == ClassComponent then
		if __DEV__ then
			Component = resolveClassForHotReloading(Component)
			workInProgress.type = Component
		end
		child = updateClassComponent(
			nil,
			workInProgress,
			Component,
			resolvedProps,
			renderLanes
		)
		return child
	elseif resolvedTag == ForwardRef then
		if __DEV__ then
			Component = resolveForwardRefForHotReloading(Component)
			workInProgress.type = Component
		end
		child =
			updateForwardRef(nil, workInProgress, Component, resolvedProps, renderLanes)
		return child
	elseif resolvedTag == MemoComponent then
		if __DEV__ or __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ then
			if workInProgress.type ~= workInProgress.elementType then
				-- ROBLOX deviation: adds support for legacy Roact's validateProps()
				local outerPropTypes = Component.propTypes
				local validateProps = Component.validateProps
				if outerPropTypes or validateProps then
					checkPropTypes(
						outerPropTypes,
						validateProps,
						resolvedProps, -- Resolved for outer only
						"prop",
						getComponentName(Component)
					)
				end
			end
		end
		child = updateMemoComponent(
			nil,
			workInProgress,
			Component,
			resolveDefaultProps(Component.type, resolvedProps), -- The inner type can have defaults too
			updateLanes,
			renderLanes
		)
		return child
		-- elseif resolvedTag == Block then
		--   unimplemented("Blocks API")
		--   if enableBlocksAPI then
		--     -- TODO: Resolve for Hot Reloading.
		--     child = updateBlock(
		--       nil,
		--       workInProgress,
		--       Component,
		--       props,
		--       renderLanes,
		--     )
		--     return child
		--   end
		--   -- ROBLOX deviation: break
	end
	local hint = ""
	if __DEV__ then
		if
			Component ~= nil
			and type(Component) == "table"
			and Component["$$typeof"] == REACT_LAZY_TYPE
		then
			hint = " Did you wrap a component in React.lazy() more than once?"
		elseif type(Component) == "table" and Component["$$typeof"] == nil then
			hint = "\n" .. inspect(Component)
		end
	end
	-- This message intentionally doesn't mention ForwardRef or MemoComponent
	-- because the fact that it's a separate type of work is an
	-- implementation detail.
	invariant(
		false,
[[Element type is invalid. Received a promise that resolves to: %s. Lazy element type must resolve to a class or function.%s]]
,
		tostring(Component),
		hint
	)
	-- ROBLOX deviation: add nil to satisfy Luau, which doesn't doesn't bubble up the unconditional error() inside invariant
	return nil
end

function mountIncompleteClassComponent(
	_current,
	workInProgress,
	Component,
	nextProps,
	renderLanes
)
	if _current ~= nil then
		-- An incomplete component only mounts if it suspended inside a non-
		-- concurrent tree, in an inconsistent state. We want to treat it like
		-- a new mount, even though an empty version of it already committed.
		-- Disconnect the alternate pointers.
		_current.alternate = nil
		workInProgress.alternate = nil
		-- Since this is conceptually a new fiber, schedule a Placement effect
		workInProgress.flags = bit32.bor(workInProgress.flags, Placement)
	end

	-- Promote the fiber to a class and try rendering again.
	workInProgress.tag = ClassComponent

	-- The rest of this function is a fork of `updateClassComponent`

	-- Push context providers early to prevent context stack mismatches.
	-- During mounting we don't know the child context yet as the instance doesn't exist.
	-- We will invalidate the child context in finishClassComponent() right after rendering.
	local hasContext
	if isLegacyContextProvider(Component) then
		hasContext = true
		pushLegacyContextProvider(workInProgress)
	else
		hasContext = false
	end
	prepareToReadContext(
		workInProgress,
		renderLanes,
		exports.markWorkInProgressReceivedUpdate
	)

	constructClassInstance(workInProgress, Component, nextProps)
	mountClassInstance(workInProgress, Component, nextProps, renderLanes)

	return finishClassComponent(
		nil,
		workInProgress,
		Component,
		true,
		hasContext,
		renderLanes
	)
end

local function mountIndeterminateComponent(
	current,
	workInProgress,
	Component,
	renderLanes
)
	if current ~= nil then
		-- An indeterminate component only mounts if it suspended inside a non-
		-- concurrent tree, in an inconsistent state. We want to treat it like
		-- a new mount, even though an empty version of it already committed.
		-- Disconnect the alternate pointers.
		current.alternate = nil
		workInProgress.alternate = nil
		-- Since this is conceptually a new fiber, schedule a Placement effect
		workInProgress.flags = bit32.bor(workInProgress.flags, Placement)
	end

	local props = workInProgress.pendingProps
	local context
	if not disableLegacyContext then
		local unmaskedContext = getUnmaskedContext(workInProgress, Component, false)
		context = getMaskedContext(workInProgress, unmaskedContext)
	end

	prepareToReadContext(
		workInProgress,
		renderLanes,
		exports.markWorkInProgressReceivedUpdate
	)
	local value

	if __DEV__ then
		if
			-- deviation: Instead of checking for the prototype, see if Component is a
			-- table with a render function
			type(Component) == "table" and type(Component.render) == "function"
		then
			local componentName = getComponentName(Component) or "Unknown"

			if not DidWarn.didWarnAboutBadClass[componentName] then
				console.error(
[[The <%s /> component appears to have a render method, but doesn't extend React.Component. This is likely to cause errors. Change %s to extend React.Component instead.]]
,
					componentName,
					componentName
				)
				DidWarn.didWarnAboutBadClass[componentName] = true
			end
		end

		if bit32.band(workInProgress.mode, StrictMode) ~= 0 then
			ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress)
		end

		setIsRendering(true)
		ReactCurrentOwner.current = workInProgress
		value =
			renderWithHooks(nil, workInProgress, Component, props, context, renderLanes)
		setIsRendering(false)
	else
		value =
			renderWithHooks(nil, workInProgress, Component, props, context, renderLanes)
	end
	-- React DevTools reads this flag.
	workInProgress.flags = bit32.bor(workInProgress.flags, PerformedWork)
	-- ROBLOX deviation START: cache type(value)
	local typeofValue = type(value)
	-- ROBLOX deviation END

	if __DEV__ then
		-- Support for module components is deprecated and is removed behind a flag.
		-- Whether or not it would crash later, we want to show a good message in DEV first.
		if
			value ~= nil
			and typeofValue == "table"
			and type(value.render) == "function"
			and value["$$typeof"] == nil
		then
			local componentName = getComponentName(Component) or "Unknown"
			if not DidWarn.didWarnAboutModulePatternComponent[componentName] then
				console.error(
[[The <%s /> component appears to be a function component that returns a class instance. Change %s to a class that extends React.Component instead. ]]
,
					-- ROBLOX deviation: Don't print JS-specific remediation advice
					-- "If you can't use a class try assigning the prototype on the function as a workaround. " ..
					-- "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " ..
					-- "cannot be called with `new` by React.",
					componentName,
					componentName
					-- componentName
				)
				DidWarn.didWarnAboutModulePatternComponent[componentName] = true
			end
		end
	end

	if
		-- Run these checks in production only if the flag is off.
		-- Eventually we'll delete this branch altogether.
		not disableModulePatternComponents
		and value ~= nil
		and typeofValue == "table"
		and type(value.render) == "function"
		and value["$$typeof"] == nil
	then
		if __DEV__ then
			local componentName = getComponentName(Component) or "Unknown"
			if not DidWarn.didWarnAboutModulePatternComponent[componentName] then
				console.error(
					"The <%s /> component appears to be a function component that returns a class instance. "
						.. "Change %s to a class that extends React.Component instead. "
						-- ROBLOX deviation: Don't print JS-specific remediation advice
						-- "If you can't use a class try assigning the prototype on the function as a workaround. " ..
						-- "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " ..
						-- "cannot be called with `new` by React.",
						.. componentName,
					componentName
					-- componentName
				)
				DidWarn.didWarnAboutModulePatternComponent[componentName] = true
			end
		end

		-- Proceed under the assumption that this is a class instance
		workInProgress.tag = ClassComponent

		-- Throw out any hooks that were used.
		workInProgress.memoizedState = nil
		workInProgress.updateQueue = nil

		-- Push context providers early to prevent context stack mismatches.
		-- During mounting we don't know the child context yet as the instance doesn't exist.
		-- We will invalidate the child context in finishClassComponent() right after rendering.
		local hasContext = false
		if isLegacyContextProvider(Component) then
			hasContext = true
			pushLegacyContextProvider(workInProgress)
		else
			hasContext = false
		end

		-- deviation: Lua doesn't need to coerce `T | null | undefined` to `T | null`
		workInProgress.memoizedState = value.state

		initializeUpdateQueue(workInProgress)

		-- ROBLOX deviation: don't access field on function
		local getDerivedStateFromProps
		if type(Component) ~= "function" then
			getDerivedStateFromProps = (Component :: React_Component<any, any>).getDerivedStateFromProps
		end
		if
			getDerivedStateFromProps ~= nil
			and type(getDerivedStateFromProps) == "function"
		then
			applyDerivedStateFromProps(
				workInProgress,
				Component,
				getDerivedStateFromProps,
				props
			)
		end

		adoptClassInstance(workInProgress, value)
		mountClassInstance(workInProgress, Component, props, renderLanes)
		return finishClassComponent(
			nil,
			workInProgress,
			Component,
			true,
			hasContext,
			renderLanes
		)
	else
		-- Proceed under the assumption that this is a function component
		workInProgress.tag = FunctionComponent
		if __DEV__ then
			if disableLegacyContext and Component.contextTypes then
				console.error(
[[%s uses the legacy contextTypes API which is no longer supported. Use React.createContext() with React.useContext() instead.]]
,
					getComponentName(Component) or "Unknown"
				)
			end

			if
				debugRenderPhaseSideEffectsForStrictMode
				and bit32.band(workInProgress.mode, StrictMode) ~= 0
			then
				disableLogs()
				local ok, result = xpcall(
					renderWithHooks,
					describeError,
					nil,
					workInProgress,
					Component,
					props,
					context,
					renderLanes
				)
				-- finally
				reenableLogs()
				if ok then
					value = result
				else
					error(result)
				end
			end
		end
		reconcileChildren(nil, workInProgress, value, renderLanes)
		if __DEV__ then
			validateFunctionComponentInDev(workInProgress, Component)
		end
		return workInProgress.child
	end
end

function validateFunctionComponentInDev(workInProgress: Fiber, Component: any)
	if __DEV__ then
		-- ROBLOX deviation: Lua doesn't allow fields on functions, so this never happens
		-- if Component then
		--   if Component.childContextTypes then
		--     console.error(
		--       '%s(...): childContextTypes cannot be defined on a function component.',
		--       Component.displayName or Component.name or 'Component'
		--     )
		--   end
		-- end
		if workInProgress.ref ~= nil then
			local info = ""
			local ownerName = getCurrentFiberOwnerNameInDevOrNull()
			if ownerName then
				info ..= "\n\nCheck the render method of `" .. ownerName .. "`."
			end

			local warningKey = ownerName or workInProgress._debugID or ""
			local debugSource = workInProgress._debugSource
			if debugSource then
				warningKey = debugSource.fileName .. ":" .. debugSource.lineNumber
			end
			if not DidWarn.didWarnAboutFunctionRefs[warningKey] then
				DidWarn.didWarnAboutFunctionRefs[warningKey] = true
				console.error(
[[Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?%s]]

,
					info
				)
			end
		end

		if
			warnAboutDefaultPropsOnFunctionComponents
			-- ROBLOX deviation: functions can't have fields in Lua
			and type(Component) ~= "function"
			and Component.defaultProps ~= nil
		then
			local componentName = getComponentName(Component) or "Unknown"

			if not DidWarn.didWarnAboutDefaultPropsOnFunctionComponent[componentName] then
				console.error(
[[%s: Support for defaultProps will be removed from function components in a future major release.]]

, -- Use JavaScript default parameters instead.',
					componentName
				)
				DidWarn.didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true
			end
		end

		-- ROBLOX deviation: Lua functions can't have fields
		if
			type(Component) ~= "function"
			and Component.getDerivedStateFromProps ~= nil
			and type(Component.getDerivedStateFromProps) == "function"
		then
			local componentName = getComponentName(Component) or "Unknown"

			if
				not DidWarn.didWarnAboutGetDerivedStateOnFunctionComponent[componentName]
			then
				console.error(
					"%s: Function components do not support getDerivedStateFromProps.",
					componentName
				)
				DidWarn.didWarnAboutGetDerivedStateOnFunctionComponent[componentName] =
					true
			end
		end

		-- ROBLOX deviation: Lua functions can't have fields
		if
			type(Component) ~= "function"
			and Component.contextType ~= nil
			and type(Component.contextType) == "table"
		then
			local componentName = getComponentName(Component) or "Unknown"

			if not DidWarn.didWarnAboutContextTypeOnFunctionComponent[componentName] then
				console.error(
					"%s: Function components do not support contextType.",
					componentName
				)
				DidWarn.didWarnAboutContextTypeOnFunctionComponent[componentName] = true
			end
		end
	end
end

local SUSPENDED_MARKER: SuspenseState = {
	dehydrated = nil,
	retryLane = ReactFiberLane.NoLane,
}

local function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState
	return {
		baseLanes = renderLanes,
	}
end

local function updateSuspenseOffscreenState(
	prevOffscreenState: OffscreenState,
	renderLanes: Lanes
): OffscreenState
	return {
		baseLanes = ReactFiberLane.mergeLanes(prevOffscreenState.baseLanes, renderLanes),
	}
end

-- TODO: Probably should inline this back
local function shouldRemainOnFallback(
	suspenseContext: SuspenseContext,
	current: nil | Fiber,
	workInProgress: Fiber,
	renderLanes: Lanes
)
	-- If we're already showing a fallback, there are cases where we need to
	-- remain on that fallback regardless of whether the content has resolved.
	-- For example, SuspenseList coordinates when nested content appears.
	if current ~= nil then
		local suspenseState: SuspenseState = (current :: Fiber).memoizedState
		if suspenseState == nil then
			-- Currently showing content. Don't hide it, even if ForceSuspenseFallack
			-- is true. More precise name might be "ForceRemainSuspenseFallback".
			-- Note: This is a factoring smell. Can't remain on a fallback if there's
			-- no fallback to remain on.
			return false
		end
	end

	-- Not currently showing content. Consult the Suspense context.
	return hasSuspenseContext(suspenseContext, ForceSuspenseFallback)
end

local function getRemainingWorkInPrimaryTree(current: Fiber, renderLanes)
	-- TODO: Should not remove render lanes that were pinged during this render
	return ReactFiberLane.removeLanes(current.childLanes, renderLanes)
end

-- ROBLOX deviation: predeclare these methods to resolve method declaration ordering
local updateSuspensePrimaryChildren
local mountDehydratedSuspenseComponent
local mountSuspensePrimaryChildren
local updateSuspenseFallbackChildren
local updateDehydratedSuspenseComponent

local function updateSuspenseComponent(current, workInProgress, renderLanes)
	local nextProps = workInProgress.pendingProps

	-- This is used by DevTools to force a boundary to suspend.
	if __DEV__ then
		if shouldSuspend(workInProgress) then
			workInProgress.flags = bit32.bor(workInProgress.flags, DidCapture)
		end
	end

	local suspenseContext: SuspenseContext = suspenseStackCursor.current

	local showFallback = false
	local didSuspend = bit32.band(workInProgress.flags, DidCapture) ~= NoFlags

	if
		didSuspend
		or shouldRemainOnFallback(suspenseContext, current, workInProgress, renderLanes)
	then
		-- Something in this boundary's subtree already suspended. Switch to
		-- rendering the fallback children.
		showFallback = true
		workInProgress.flags = bit32.band(workInProgress.flags, bit32.bnot(DidCapture))
	else
		-- Attempting the main content
		if current == nil or current.memoizedState ~= nil then
			-- This is a new mount or this boundary is already showing a fallback state.
			-- Mark this subtree context as having at least one invisible parent that could
			-- handle the fallback state.
			-- Boundaries without fallbacks or should be avoided are not considered since
			-- they cannot handle preferred fallback states.
			if
				nextProps.fallback ~= nil
				and nextProps.unstable_avoidThisFallback ~= true
			then
				suspenseContext = addSubtreeSuspenseContext(
					suspenseContext,
					InvisibleParentSuspenseContext
				)
			end
		end
	end

	suspenseContext = setDefaultShallowSuspenseContext(suspenseContext)

	pushSuspenseContext(workInProgress, suspenseContext)

	-- OK, the next part is confusing. We're about to reconcile the Suspense
	-- boundary's children. This involves some custom reconcilation logic. Two
	-- main reasons this is so complicated.
	--
	-- First, Legacy Mode has different semantics for backwards compatibility. The
	-- primary tree will commit in an inconsistent state, so when we do the
	-- second pass to render the fallback, we do some exceedingly, uh, clever
	-- hacks to make that not totally break. Like transferring effects and
	-- deletions from hidden tree. In Concurrent Mode, it's much simpler,
	-- because we bailout on the primary tree completely and leave it in its old
	-- state, no effects. Same as what we do for Offscreen (except that
	-- Offscreen doesn't have the first render pass).
	--
	-- Second is hydration. During hydration, the Suspense fiber has a slightly
	-- different layout, where the child points to a dehydrated fragment, which
	-- contains the DOM rendered by the server.
	--
	-- Third, even if you set all that aside, Suspense is like error boundaries in
	-- that we first we try to render one tree, and if that fails, we render again
	-- and switch to a different tree. Like a try/catch block. So we have to track
	-- which branch we're currently rendering. Ideally we would model this using
	-- a stack.
	if current == nil then
		-- Initial mount
		-- If we're currently hydrating, try to hydrate this boundary.
		-- But only if this has a fallback.
		if nextProps.fallback ~= nil then
			tryToClaimNextHydratableInstance(workInProgress)
			-- This could've been a dehydrated suspense component.
			if enableSuspenseServerRenderer then
				local suspenseState: nil | SuspenseState = workInProgress.memoizedState
				if suspenseState ~= nil then
					local dehydrated = (suspenseState :: SuspenseState).dehydrated
					if dehydrated ~= nil then
						return mountDehydratedSuspenseComponent(
							workInProgress,
							dehydrated,
							renderLanes
						)
					end
				end
			end
		end

		local nextPrimaryChildren = nextProps.children
		local nextFallbackChildren = nextProps.fallback
		if showFallback then
			local fallbackFragment = mountSuspenseFallbackChildren(
				workInProgress,
				nextPrimaryChildren,
				nextFallbackChildren,
				renderLanes
			)
			local primaryChildFragment: Fiber = workInProgress.child :: any
			primaryChildFragment.memoizedState = mountSuspenseOffscreenState(renderLanes)
			workInProgress.memoizedState = SUSPENDED_MARKER
			return fallbackFragment
		elseif
			nextProps.unstable_expectedLoadTime ~= nil
			and type(nextProps.unstable_expectedLoadTime) == "number"
		then
			-- This is a CPU-bound tree. Skip this tree and show a placeholder to
			-- unblock the surrounding content. Then immediately retry after the
			-- initial commit.
			local fallbackFragment = mountSuspenseFallbackChildren(
				workInProgress,
				nextPrimaryChildren,
				nextFallbackChildren,
				renderLanes
			)
			local primaryChildFragment: Fiber = workInProgress.child :: any
			primaryChildFragment.memoizedState = mountSuspenseOffscreenState(renderLanes)
			workInProgress.memoizedState = SUSPENDED_MARKER

			-- Since nothing actually suspended, there will nothing to ping this to
			-- get it started back up to attempt the next item. While in terms of
			-- priority this work has the same priority as this current render, it's
			-- not part of the same transition once the transition has committed. If
			-- it's sync, we still want to yield so that it can be painted.
			-- Conceptually, this is really the same as pinging. We can use any
			-- RetryLane even if it's the one currently rendering since we're leaving
			-- it behind on this node.
			workInProgress.lanes = ReactFiberLane.SomeRetryLane
			if enableSchedulerTracing then
				markSpawnedWork(ReactFiberLane.SomeRetryLane)
			end
			return fallbackFragment
		else
			return mountSuspensePrimaryChildren(
				workInProgress,
				nextPrimaryChildren,
				renderLanes
			)
		end
	else
		-- This is an update.

		-- If the current fiber has a SuspenseState, that means it's already showing
		-- a fallback.
		local prevState: nil | SuspenseState = current.memoizedState
		if prevState ~= nil then
			-- The current tree is already showing a fallback

			-- Special path for hydration
			if enableSuspenseServerRenderer then
				-- ROBLOX FIXME: remove :: when Luau understands ~= nil
				local dehydrated = (prevState :: SuspenseState).dehydrated

				if dehydrated ~= nil then
					if not didSuspend then
						return updateDehydratedSuspenseComponent(
							current,
							workInProgress,
							dehydrated,
							prevState,
							renderLanes
						)
					elseif workInProgress.memoizedState ~= nil then
						-- Something suspended and we should still be in dehydrated mode.
						-- Leave the existing child in place.
						workInProgress.child = current.child
						-- The dehydrated completion pass expects this flag to be there
						-- but the normal suspense pass doesn't.
						workInProgress.flags = bit32.bor(workInProgress.flags, DidCapture)
						return nil
					else
						-- Suspended but we should no longer be in dehydrated mode.
						-- Therefore we now have to render the fallback.
						local nextPrimaryChildren = nextProps.children
						local nextFallbackChildren = nextProps.fallback
						local fallbackChildFragment =
							mountSuspenseFallbackAfterRetryWithoutHydrating(
								current,
								workInProgress,
								nextPrimaryChildren,
								nextFallbackChildren,
								renderLanes
							)
						local primaryChildFragment: Fiber = workInProgress.child :: any
						primaryChildFragment.memoizedState =
							mountSuspenseOffscreenState(renderLanes)
						workInProgress.memoizedState = SUSPENDED_MARKER
						return fallbackChildFragment
					end
				end
			end

			if showFallback then
				local nextFallbackChildren = nextProps.fallback
				local nextPrimaryChildren = nextProps.children
				local fallbackChildFragment = updateSuspenseFallbackChildren(
					current,
					workInProgress,
					nextPrimaryChildren,
					nextFallbackChildren,
					renderLanes
				)
				local primaryChildFragment: Fiber = workInProgress.child :: any
				local prevOffscreenState: OffscreenState | nil = (current.child :: any).memoizedState

				-- ROBLOX deviation: if/else in place of ternary
				if prevOffscreenState == nil then
					primaryChildFragment.memoizedState =
						mountSuspenseOffscreenState(renderLanes)
				else
					-- ROBLOX FIXME: remove :: when Luau understands ~= nil
					primaryChildFragment.memoizedState = updateSuspenseOffscreenState(
						prevOffscreenState :: OffscreenState,
						renderLanes
					)
				end

				primaryChildFragment.childLanes =
					getRemainingWorkInPrimaryTree(current, renderLanes)
				workInProgress.memoizedState = SUSPENDED_MARKER
				return fallbackChildFragment
			else
				local nextPrimaryChildren = nextProps.children
				local primaryChildFragment = updateSuspensePrimaryChildren(
					current,
					workInProgress,
					nextPrimaryChildren,
					renderLanes
				)
				workInProgress.memoizedState = nil
				return primaryChildFragment
			end
		else
			-- The current tree is not already showing a fallback.
			if showFallback then
				-- Timed out.
				local nextFallbackChildren = nextProps.fallback
				local nextPrimaryChildren = nextProps.children
				local fallbackChildFragment = updateSuspenseFallbackChildren(
					current,
					workInProgress,
					nextPrimaryChildren,
					nextFallbackChildren,
					renderLanes
				)
				local primaryChildFragment: Fiber = workInProgress.child :: any
				local prevOffscreenState: OffscreenState | nil = (current.child :: any).memoizedState

				-- ROBLOX deviation: if/else in place of ternary
				if prevOffscreenState == nil then
					primaryChildFragment.memoizedState =
						mountSuspenseOffscreenState(renderLanes)
				else
					-- ROBLOX FIXME: remove :: once Luau understands nil check
					primaryChildFragment.memoizedState = updateSuspenseOffscreenState(
						prevOffscreenState :: OffscreenState,
						renderLanes
					)
				end

				primaryChildFragment.childLanes =
					getRemainingWorkInPrimaryTree(current, renderLanes)
				-- Skip the primary children, and continue working on the
				-- fallback children.
				workInProgress.memoizedState = SUSPENDED_MARKER
				return fallbackChildFragment
			else
				-- Still haven't timed out. Continue rendering the children, like we
				-- normally do.
				local nextPrimaryChildren = nextProps.children
				local primaryChildFragment = updateSuspensePrimaryChildren(
					current,
					workInProgress,
					nextPrimaryChildren,
					renderLanes
				)
				workInProgress.memoizedState = nil
				return primaryChildFragment
			end
		end
	end
end

function mountSuspensePrimaryChildren(workInProgress, primaryChildren, renderLanes)
	local mode = workInProgress.mode
	local primaryChildProps: OffscreenProps = {
		mode = "visible",
		children = primaryChildren,
	}
	local primaryChildFragment =
		createFiberFromOffscreen(primaryChildProps, mode, renderLanes, nil)
	primaryChildFragment.return_ = workInProgress
	workInProgress.child = primaryChildFragment
	return primaryChildFragment
end

function mountSuspenseFallbackChildren(
	workInProgress,
	primaryChildren,
	fallbackChildren,
	renderLanes
)
	local mode = workInProgress.mode
	local progressedPrimaryFragment: Fiber | nil = workInProgress.child

	local primaryChildProps: OffscreenProps = {
		mode = "hidden",
		children = primaryChildren,
	}

	local primaryChildFragment
	local fallbackChildFragment
	if bit32.band(mode, BlockingMode) == NoMode and progressedPrimaryFragment ~= nil then
		-- In legacy mode, we commit the primary tree as if it successfully
		-- completed, even though it's in an inconsistent state.
		primaryChildFragment = progressedPrimaryFragment
		primaryChildFragment.childLanes = ReactFiberLane.NoLanes
		primaryChildFragment.pendingProps = primaryChildProps

		if enableProfilerTimer and bit32.band(workInProgress.mode, ProfileMode) ~= 0 then
			-- Reset the durations from the first pass so they aren't included in the
			-- final amounts. This seems counterintuitive, since we're intentionally
			-- not measuring part of the render phase, but this makes it match what we
			-- do in Concurrent Mode.
			primaryChildFragment.actualDuration = 0
			primaryChildFragment.actualStartTime = -1			
primaryChildFragment.selfBaseDuration = 0
			primaryChildFragment.treeBaseDuration = 0
		end

		fallbackChildFragment =
			createFiberFromFragment(fallbackChildren, mode, renderLanes, nil)
	else
		primaryChildFragment =
			createFiberFromOffscreen(primaryChildProps, mode, ReactFiberLane.NoLanes, nil)
		fallbackChildFragment =
			createFiberFromFragment(fallbackChildren, mode, renderLanes, nil)
	end

	primaryChildFragment.return_ = workInProgress
	fallbackChildFragment.return_ = workInProgress
	primaryChildFragment.sibling = fallbackChildFragment
	workInProgress.child = primaryChildFragment
	return fallbackChildFragment
end

local function createWorkInProgressOffscreenFiber(
	current: Fiber,
	offscreenProps: OffscreenProps
)
	-- The props argument to `createWorkInProgress` is `any` typed, so we use this
	-- wrapper function to constrain it.
	return createWorkInProgress(current, offscreenProps)
end

function updateSuspensePrimaryChildren(
	current,
	workInProgress,
	primaryChildren,
	renderLanes
)
	local currentPrimaryChildFragment: Fiber = current.child :: any
	local currentFallbackChildFragment: Fiber | nil = currentPrimaryChildFragment.sibling

	local primaryChildFragment =
		createWorkInProgressOffscreenFiber(currentPrimaryChildFragment, {
			mode = "visible",
			children = primaryChildren,
		})
	if bit32.band(workInProgress.mode, BlockingMode) == NoMode then
		primaryChildFragment.lanes = renderLanes
	end
	primaryChildFragment.return_ = workInProgress
	primaryChildFragment.sibling = nil
	if currentFallbackChildFragment ~= nil then
		-- Delete the fallback child fragment
		local deletions = workInProgress.deletions
		if deletions == nil then
			workInProgress.deletions = { currentFallbackChildFragment }
			-- TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
			workInProgress.flags = bit32.bor(workInProgress.flags, Deletion)
		else
			table.insert(deletions, currentFallbackChildFragment)
		end
	end

	workInProgress.child = primaryChildFragment
	return primaryChildFragment
end

function updateSuspenseFallbackChildren(
	current,
	workInProgress,
	primaryChildren,
	fallbackChildren,
	renderLanes
)
	local mode = workInProgress.mode
	local currentPrimaryChildFragment: Fiber = current.child :: any
	local currentFallbackChildFragment: Fiber | nil = currentPrimaryChildFragment.sibling

	local primaryChildProps: OffscreenProps = {
		mode = "hidden",
		children = primaryChildren,
	}

	local primaryChildFragment
	if
		-- In legacy mode, we commit the primary tree as if it successfully
		-- completed, even though it's in an inconsistent state.
		bit32.band(mode, BlockingMode) == NoMode
		-- Make sure we're on the second pass, i.e. the primary child fragment was
		-- already cloned. In legacy mode, the only case where this isn't true is
		-- when DevTools forces us to display a fallback; we skip the first render
		-- pass entirely and go straight to rendering the fallback. (In Concurrent
		-- Mode, SuspenseList can also trigger this scenario, but this is a legacy-
		-- only codepath.)
		and workInProgress.child ~= currentPrimaryChildFragment
	then
		local progressedPrimaryFragment: Fiber = workInProgress.child
		primaryChildFragment = progressedPrimaryFragment
		primaryChildFragment.childLanes = ReactFiberLane.NoLanes
		primaryChildFragment.pendingProps = primaryChildProps

		if enableProfilerTimer and bit32.band(workInProgress.mode, ProfileMode) ~= 0 then
			-- Reset the durations from the first pass so they aren't included in the
			-- final amounts. This seems counterintuitive, since we're intentionally
			-- not measuring part of the render phase, but this makes it match what we
			-- do in Concurrent Mode.
			primaryChildFragment.actualDuration = 0
			primaryChildFragment.actualStartTime = -1			
primaryChildFragment.selfBaseDuration =
				currentPrimaryChildFragment.selfBaseDuration
			primaryChildFragment.treeBaseDuration =
				currentPrimaryChildFragment.treeBaseDuration
		end

		-- The fallback fiber was added as a deletion effect during the first pass.
		-- However, since we're going to remain on the fallback, we no longer want
		-- to delete it.
		workInProgress.deletions = nil
	else
		primaryChildFragment = createWorkInProgressOffscreenFiber(
			currentPrimaryChildFragment,
			primaryChildProps
		)

		-- Since we're reusing a current tree, we need to reuse the flags, too.
		-- (We don't do this in legacy mode, because in legacy mode we don't re-use
		-- the current tree; see previous branch.)
		primaryChildFragment.subtreeFlags =
			bit32.band(currentPrimaryChildFragment.subtreeFlags, StaticMask)
	end
	local fallbackChildFragment
	if currentFallbackChildFragment ~= nil then
		fallbackChildFragment =
			createWorkInProgress(currentFallbackChildFragment, fallbackChildren)
	else
		fallbackChildFragment =
			createFiberFromFragment(fallbackChildren, mode, renderLanes, nil)
		-- Needs a placement effect because the parent (the Suspense boundary) already
		-- mounted but this is a new fiber.
		fallbackChildFragment.flags = bit32.bor(fallbackChildFragment.flags, Placement)
	end

	fallbackChildFragment.return_ = workInProgress
	primaryChildFragment.return_ = workInProgress
	primaryChildFragment.sibling = fallbackChildFragment
	workInProgress.child = primaryChildFragment

	return fallbackChildFragment
end

local function retrySuspenseComponentWithoutHydrating(
	current: Fiber,
	workInProgress: Fiber,
	renderLanes: Lanes
)
	-- This will add the old fiber to the deletion list
	reconcileChildFibers(workInProgress, current.child, nil, renderLanes)

	-- We're now not suspended nor dehydrated.
	local nextProps = workInProgress.pendingProps
	local primaryChildren = nextProps.children
	local primaryChildFragment =
		mountSuspensePrimaryChildren(workInProgress, primaryChildren, renderLanes)
	-- Needs a placement effect because the parent (the Suspense boundary) already
	-- mounted but this is a new fiber.
	primaryChildFragment.flags = bit32.bor(primaryChildFragment.flags, Placement)
	workInProgress.memoizedState = nil

	return primaryChildFragment
end

function mountSuspenseFallbackAfterRetryWithoutHydrating(
	current,
	workInProgress,
	primaryChildren,
	fallbackChildren,
	renderLanes
)
	local mode = workInProgress.mode
	local primaryChildFragment =
		createFiberFromOffscreen(primaryChildren, mode, ReactFiberLane.NoLanes, nil)
	local fallbackChildFragment =
		createFiberFromFragment(fallbackChildren, mode, renderLanes, nil)
	-- Needs a placement effect because the parent (the Suspense
	-- boundary) already mounted but this is a new fiber.
	fallbackChildFragment.flags = bit32.bor(fallbackChildFragment.flags, Placement)

	primaryChildFragment.return_ = workInProgress
	fallbackChildFragment.return_ = workInProgress
	primaryChildFragment.sibling = fallbackChildFragment
	workInProgress.child = primaryChildFragment

	if bit32.band(workInProgress.mode, BlockingMode) ~= NoMode then
		-- We will have dropped the effect list which contains the
		-- deletion. We need to reconcile to delete the current child.
		reconcileChildFibers(workInProgress, current.child, nil, renderLanes)
	end

	return fallbackChildFragment
end

function mountDehydratedSuspenseComponent(
	workInProgress: Fiber,
	suspenseInstance: SuspenseInstance,
	renderLanes: Lanes
): nil | Fiber
	-- During the first pass, we'll bail out and not drill into the children.
	-- Instead, we'll leave the content in place and try to hydrate it later.
	if bit32.band(workInProgress.mode, BlockingMode) == NoMode then
		if __DEV__ then
			console.error(
[[Cannot hydrate Suspense in legacy mode. Switch fromReactDOM.hydrate(element, container) to ReactDOM.createBlockingRoot(container, { hydrate: true }).render(element) or remove the Suspense componentsthe server rendered components.]]			




)
		end
		workInProgress.lanes = ReactFiberLane.laneToLanes(ReactFiberLane.SyncLane)
	elseif isSuspenseInstanceFallback(suspenseInstance) then
		-- This is a client-only boundary. Since we won't get any content from the server
		-- for this, we need to schedule that at a higher priority based on when it would
		-- have timed out. In theory we could render it in this pass but it would have the
		-- wrong priority associated with it and will prevent hydration of parent path.
		-- Instead, we'll leave work left on it to render it in a separate commit.

		-- TODO This time should be the time at which the server rendered response that is
		-- a parent to this boundary was displayed. However, since we currently don't have
		-- a protocol to transfer that time, we'll just estimate it by using the current
		-- time. This will mean that Suspense timeouts are slightly shifted to later than
		-- they should be.
		-- Schedule a normal pri update to render this content.
		if enableSchedulerTracing then
			markSpawnedWork(ReactFiberLane.DefaultHydrationLane)
		end
		workInProgress.lanes =
			ReactFiberLane.laneToLanes(ReactFiberLane.DefaultHydrationLane)
	else
		-- We'll continue hydrating the rest at offscreen priority since we'll already
		-- be showing the right content coming from the server, it is no rush.
		workInProgress.lanes = ReactFiberLane.laneToLanes(ReactFiberLane.OffscreenLane)
		if enableSchedulerTracing then
			markSpawnedWork(ReactFiberLane.OffscreenLane)
		end
	end
	return nil
end

function updateDehydratedSuspenseComponent(
	current: Fiber,
	workInProgress: Fiber,
	suspenseInstance: SuspenseInstance,
	suspenseState: SuspenseState,
	renderLanes: Lanes
): nil | Fiber
	-- We should never be hydrating at this point because it is the first pass,
	-- but after we've already committed once.
	warnIfHydrating()

	if bit32.band(getExecutionContext(), RetryAfterError) ~= NoContext then
		return retrySuspenseComponentWithoutHydrating(
			current,
			workInProgress,
			renderLanes
		)
	end

	if bit32.band(workInProgress.mode, BlockingMode) == NoMode then
		return retrySuspenseComponentWithoutHydrating(
			current,
			workInProgress,
			renderLanes
		)
	end

	if isSuspenseInstanceFallback(suspenseInstance) then
		-- This boundary is in a permanent fallback state. In this case, we'll never
		-- get an update and we'll never be able to hydrate the final content. Let's just try the
		-- client side render instead.
		return retrySuspenseComponentWithoutHydrating(
			current,
			workInProgress,
			renderLanes
		)
	end
	-- We use lanes to indicate that a child might depend on context, so if
	-- any context has changed, we need to treat is as if the input might have changed.
	local hasContextChanged =
		ReactFiberLane.includesSomeLane(renderLanes, current.childLanes)
	if didReceiveUpdate or hasContextChanged then
		-- This boundary has changed since the first render. This means that we are now unable to
		-- hydrate it. We might still be able to hydrate it using a higher priority lane.
		local root = getWorkInProgressRoot()
		if root ~= nil then
			local attemptHydrationAtLane =
				ReactFiberLane.getBumpedLaneForHydration(root, renderLanes)
			if
				attemptHydrationAtLane ~= ReactFiberLane.NoLane
				and attemptHydrationAtLane ~= suspenseState.retryLane
			then
				-- Intentionally mutating since this render will get interrupted. This
				-- is one of the very rare times where we mutate the current tree
				-- during the render phase.
				suspenseState.retryLane = attemptHydrationAtLane
				-- TODO: Ideally this would inherit the event time of the current render
				local eventTime = ReactFiberLane.NoTimestamp
				scheduleUpdateOnFiber(current, attemptHydrationAtLane, eventTime)
				-- We have already tried to ping at a higher priority than we're rendering with
				-- so if we got here, we must have failed to hydrate at those levels. We must
				-- now give up. Instead, we're going to delete the whole subtree and instead inject
				-- a new real Suspense boundary to take its place, which may render content
				-- or fallback. This might suspend for a while and if it does we might still have
				-- an opportunity to hydrate before this pass commits.
			
end
		end

		-- If we have scheduled higher pri work above, this will probably just abort the render
		-- since we now have higher priority work, but in case it doesn't, we need to prepare to
		-- render something, if we time out. Even if that requires us to delete everything and
		-- skip hydration.
		-- Delay having to do this as long as the suspense timeout allows us.
		renderDidSuspendDelayIfPossible()
		return retrySuspenseComponentWithoutHydrating(
			current,
			workInProgress,
			renderLanes
		)
	elseif isSuspenseInstancePending(suspenseInstance) then
		-- This component is still pending more data from the server, so we can't hydrate its
		-- content. We treat it as if this component suspended itself. It might seem as if
		-- we could just try to render it client-side instead. However, this will perform a
		-- lot of unnecessary work and is unlikely to complete since it often will suspend
		-- on missing data anyway. Additionally, the server might be able to render more
		-- than we can on the client yet. In that case we'd end up with more fallback states
		-- on the client than if we just leave it alone. If the server times out or errors
		-- these should update this boundary to the permanent Fallback state instead.
		-- Mark it as having captured (i.e. suspended).
		workInProgress.flags = bit32.bor(workInProgress.flags, DidCapture)
		-- Leave the child in place. I.e. the dehydrated fragment.
		workInProgress.child = current.child

		-- Register a callback to retry this boundary once the server has sent the result.
		-- ROBLOX deviation: wrapper function in place of bind
		local retry = function()
			return retryDehydratedSuspenseBoundary(current)
		end

		if enableSchedulerTracing then
			if Schedule_tracing_wrap == nil then
				Schedule_tracing_wrap =
					require(script.Parent.Parent:WaitForChild('scheduler')).tracing.unstable_wrap
			end
			retry = Schedule_tracing_wrap(retry)
		end

		registerSuspenseInstanceRetry(suspenseInstance, retry)
		return nil
	else
		-- This is the first attempt.
		reenterHydrationStateFromDehydratedSuspenseInstance(
			workInProgress,
			suspenseInstance
		)
		local nextProps = workInProgress.pendingProps
		local primaryChildren = nextProps.children
		local primaryChildFragment =
			mountSuspensePrimaryChildren(workInProgress, primaryChildren, renderLanes)
		-- Mark the children as hydrating. This is a fast path to know whether this
		-- tree is part of a hydrating tree. This is used to determine if a child
		-- node has fully mounted yet, and for scheduling event replaying.
		-- Conceptually this is similar to Placement in that a new subtree is
		-- inserted into the React tree here. It just happens to not need DOM
		-- mutations because it already exists.
		primaryChildFragment.flags = bit32.bor(primaryChildFragment.flags, Hydrating)
		return primaryChildFragment
	end
end

-- function scheduleWorkOnFiber(fiber: Fiber, renderLanes: Lanes)
--   fiber.lanes = ReactFiberLane.mergeLanes(fiber.lanes, renderLanes)
--   local alternate = fiber.alternate
--   if alternate ~= nil)
--     alternate.lanes = ReactFiberLane.mergeLanes(alternate.lanes, renderLanes)
--   end
--   scheduleWorkOnParentPath(fiber.return, renderLanes)
-- end

-- function propagateSuspenseContextChange(
--   workInProgress: Fiber,
--   firstChild: nil | Fiber,
--   renderLanes: Lanes,
-- ): void {
--   -- Mark any Suspense boundaries with fallbacks as having work to do.
--   -- If they were previously forced into fallbacks, they may now be able
--   -- to unblock.
--   local node = firstChild
--   while (node ~= nil)
--     if node.tag == SuspenseComponent)
--       local state: SuspenseState | nil = node.memoizedState
--       if state ~= nil)
--         scheduleWorkOnFiber(node, renderLanes)
--       end
--     } elseif node.tag == SuspenseListComponent)
--       -- If the tail is hidden there might not be an Suspense boundaries
--       -- to schedule work on. In this case we have to schedule it on the
--       -- list itself.
--       -- We don't have to traverse to the children of the list since
--       -- the list will propagate the change when it rerenders.
--       scheduleWorkOnFiber(node, renderLanes)
--     } elseif node.child ~= nil)
--       node.child.return = node
--       node = node.child
--       continue
--     end
--     if node == workInProgress)
--       return
--     end
--     while (node.sibling == nil)
--       if node.return == nil or node.return == workInProgress)
--         return
--       end
--       node = node.return
--     end
--     node.sibling.return = node.return
--     node = node.sibling
--   end
-- end

-- function findLastContentRow(firstChild: nil | Fiber): nil | Fiber {
--   -- This is going to find the last row among these children that is already
--   -- showing content on the screen, as opposed to being in fallback state or
--   -- new. If a row has multiple Suspense boundaries, any of them being in the
--   -- fallback state, counts as the whole row being in a fallback state.
--   -- Note that the "rows" will be workInProgress, but any nested children
--   -- will still be current since we haven't rendered them yet. The mounted
--   -- order may not be the same as the new order. We use the new order.
--   local row = firstChild
--   local lastContentRow: nil | Fiber = nil
--   while (row ~= nil)
--     local currentRow = row.alternate
--     -- New rows can't be content rows.
--     if currentRow ~= nil and findFirstSuspended(currentRow) == nil)
--       lastContentRow = row
--     end
--     row = row.sibling
--   end
--   return lastContentRow
-- end

-- type SuspenseListRevealOrder = 'forwards' | 'backwards' | 'together' | void

-- function validateRevealOrder(revealOrder: SuspenseListRevealOrder)
--   if  __DEV__ then
--     if
--       revealOrder ~= undefined and
--       revealOrder ~= 'forwards' and
--       revealOrder ~= 'backwards' and
--       revealOrder ~= 'together' and
--       !didWarnAboutRevealOrder[revealOrder]
--     )
--       didWarnAboutRevealOrder[revealOrder] = true
--       if typeof revealOrder == 'string')
--         switch (revealOrder.toLowerCase())
--           case 'together':
--           case 'forwards':
--           case 'backwards': {
--             console.error(
--               '"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
--                 'Use lowercase "%s" instead.',
--               revealOrder,
--               revealOrder.toLowerCase(),
--             )
--             break
--           end
--           case 'forward':
--           case 'backward': {
--             console.error(
--               '"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
--                 'React uses the -s suffix in the spelling. Use "%ss" instead.',
--               revealOrder,
--               revealOrder.toLowerCase(),
--             )
--             break
--           end
--           default:
--             console.error(
--               '"%s" is not a supported revealOrder on <SuspenseList />. ' +
--                 'Did you mean "together", "forwards" or "backwards"?',
--               revealOrder,
--             )
--             break
--         end
--       else
--         console.error(
--           '%s is not a supported value for revealOrder on <SuspenseList />. ' +
--             'Did you mean "together", "forwards" or "backwards"?',
--           revealOrder,
--         )
--       end
--     end
--   end
-- end

-- function validateTailOptions(
--   tailMode: SuspenseListTailMode,
--   revealOrder: SuspenseListRevealOrder,
-- )
--   if  __DEV__ then
--     if tailMode ~= undefined and !didWarnAboutTailOptions[tailMode])
--       if tailMode ~= 'collapsed' and tailMode ~= 'hidden')
--         didWarnAboutTailOptions[tailMode] = true
--         console.error(
--           '"%s" is not a supported value for tail on <SuspenseList />. ' +
--             'Did you mean "collapsed" or "hidden"?',
--           tailMode,
--         )
--       } elseif revealOrder ~= 'forwards' and revealOrder ~= 'backwards')
--         didWarnAboutTailOptions[tailMode] = true
--         console.error(
--           '<SuspenseList tail="%s" /> is only valid if revealOrder is ' +
--             '"forwards" or "backwards". ' +
--             'Did you mean to specify revealOrder="forwards"?',
--           tailMode,
--         )
--       end
--     end
--   end
-- end

-- function validateSuspenseListNestedChild(childSlot: mixed, index: number)
--   if  __DEV__ then
--     local isArray = Array.isArray(childSlot)
--     local isIterable =
--       !isArray and typeof getIteratorFn(childSlot) == 'function'
--     if isArray or isIterable)
--       local type = isArray ? 'array' : 'iterable'
--       console.error(
--         'A nested %s was passed to row #%s in <SuspenseList />. Wrap it in ' +
--           'an additional SuspenseList to configure its revealOrder: ' +
--           '<SuspenseList revealOrder=...> ... ' +
--           '<SuspenseList revealOrder=...>{%s}</SuspenseList> ... ' +
--           '</SuspenseList>',
--         type,
--         index,
--         type,
--       )
--       return false
--     end
--   end
--   return true
-- end

-- function validateSuspenseListChildren(
--   children: mixed,
--   revealOrder: SuspenseListRevealOrder,
-- )
--   if  __DEV__ then
--     if
--       (revealOrder == 'forwards' or revealOrder == 'backwards') and
--       children ~= undefined and
--       children ~= nil and
--       children ~= false
--     )
--       if Array.isArray(children))
--         for (local i = 0; i < children.length; i++)
--           if not validateSuspenseListNestedChild(children[i], i))
--             return
--           end
--         end
--       else
--         local iteratorFn = getIteratorFn(children)
--         if typeof iteratorFn == 'function')
--           local childrenIterator = iteratorFn.call(children)
--           if childrenIterator)
--             local step = childrenIterator.next()
--             local i = 0
--             for (; !step.done; step = childrenIterator.next())
--               if not validateSuspenseListNestedChild(step.value, i))
--                 return
--               end
--               i++
--             end
--           end
--         else
--           console.error(
--             'A single row was passed to a <SuspenseList revealOrder="%s" />. ' +
--               'This is not useful since it needs multiple rows. ' +
--               'Did you mean to pass multiple children or an array?',
--             revealOrder,
--           )
--         end
--       end
--     end
--   end
-- end

-- function initSuspenseListRenderState(
--   workInProgress: Fiber,
--   isBackwards: boolean,
--   tail: nil | Fiber,
--   lastContentRow: nil | Fiber,
--   tailMode: SuspenseListTailMode,
-- ): void {
--   local renderState: nil | SuspenseListRenderState =
--     workInProgress.memoizedState
--   if renderState == nil)
--     workInProgress.memoizedState = ({
--       isBackwards: isBackwards,
--       rendering: nil,
--       renderingStartTime: 0,
--       last: lastContentRow,
--       tail: tail,
--       tailMode: tailMode,
--     }: SuspenseListRenderState)
--   else
--     -- We can reuse the existing object from previous renders.
--     renderState.isBackwards = isBackwards
--     renderState.rendering = nil
--     renderState.renderingStartTime = 0
--     renderState.last = lastContentRow
--     renderState.tail = tail
--     renderState.tailMode = tailMode
--   end
-- end

-- -- This can end up rendering this component multiple passes.
-- -- The first pass splits the children fibers into two sets. A head and tail.
-- -- We first render the head. If anything is in fallback state, we do another
-- -- pass through beginWork to rerender all children (including the tail) with
-- -- the force suspend context. If the first render didn't have anything in
-- -- in fallback state. Then we render each row in the tail one-by-one.
-- -- That happens in the completeWork phase without going back to beginWork.
-- function updateSuspenseListComponent(
--   current: Fiber | nil,
--   workInProgress: Fiber,
--   renderLanes: Lanes,
-- )
--   local nextProps = workInProgress.pendingProps
--   local revealOrder: SuspenseListRevealOrder = nextProps.revealOrder
--   local tailMode: SuspenseListTailMode = nextProps.tail
--   local newChildren = nextProps.children

--   validateRevealOrder(revealOrder)
--   validateTailOptions(tailMode, revealOrder)
--   validateSuspenseListChildren(newChildren, revealOrder)

--   reconcileChildren(current, workInProgress, newChildren, renderLanes)

--   local suspenseContext: SuspenseContext = suspenseStackCursor.current

--   local shouldForceFallback = hasSuspenseContext(
--     suspenseContext,
--     (ForceSuspenseFallback: SuspenseContext),
--   )
--   if shouldForceFallback)
--     suspenseContext = setShallowSuspenseContext(
--       suspenseContext,
--       ForceSuspenseFallback,
--     )
--     workInProgress.flags |= DidCapture
--   else
--     local didSuspendBefore =
--       current ~= nil and (current.flags & DidCapture) ~= NoFlags
--     if didSuspendBefore)
--       -- If we previously forced a fallback, we need to schedule work
--       -- on any nested boundaries to local them know to try to render
--       -- again. This is the same as context updating.
--       propagateSuspenseContextChange(
--         workInProgress,
--         workInProgress.child,
--         renderLanes,
--       )
--     end
--     suspenseContext = setDefaultShallowSuspenseContext(suspenseContext)
--   end
--   pushSuspenseContext(workInProgress, suspenseContext)

--   if (workInProgress.mode & BlockingMode) == NoMode)
--     -- In legacy mode, SuspenseList doesn't work so we just
--     -- use make it a noop by treating it as the default revealOrder.
--     workInProgress.memoizedState = nil
--   else
--     switch (revealOrder)
--       case 'forwards': {
--         local lastContentRow = findLastContentRow(workInProgress.child)
--         local tail
--         if lastContentRow == nil)
--           -- The whole list is part of the tail.
--           -- TODO: We could fast path by just rendering the tail now.
--           tail = workInProgress.child
--           workInProgress.child = nil
--         else
--           -- Disconnect the tail rows after the content row.
--           -- We're going to render them separately later.
--           tail = lastContentRow.sibling
--           lastContentRow.sibling = nil
--         end
--         initSuspenseListRenderState(
--           workInProgress,
--           false, -- isBackwards
--           tail,
--           lastContentRow,
--           tailMode,
--         )
--         break
--       end
--       case 'backwards': {
--         -- We're going to find the first row that has existing content.
--         -- At the same time we're going to reverse the list of everything
--         -- we pass in the meantime. That's going to be our tail in reverse
--         -- order.
--         local tail = nil
--         local row = workInProgress.child
--         workInProgress.child = nil
--         while (row ~= nil)
--           local currentRow = row.alternate
--           -- New rows can't be content rows.
--           if currentRow ~= nil and findFirstSuspended(currentRow) == nil)
--             -- This is the beginning of the main content.
--             workInProgress.child = row
--             break
--           end
--           local nextRow = row.sibling
--           row.sibling = tail
--           tail = row
--           row = nextRow
--         end
--         -- TODO: If workInProgress.child is nil, we can continue on the tail immediately.
--         initSuspenseListRenderState(
--           workInProgress,
--           true, -- isBackwards
--           tail,
--           nil, -- last
--           tailMode,
--         )
--         break
--       end
--       case 'together': {
--         initSuspenseListRenderState(
--           workInProgress,
--           false, -- isBackwards
--           nil, -- tail
--           nil, -- last
--           undefined,
--         )
--         break
--       end
--       default: {
--         -- The default reveal order is the same as not having
--         -- a boundary.
--         workInProgress.memoizedState = nil
--       end
--     end
--   end
--   return workInProgress.child
-- end

function updatePortalComponent(
	current: Fiber | nil,
	workInProgress: Fiber,
	renderLanes: Lanes
)
	pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo)
	local nextChildren = workInProgress.pendingProps
	if current == nil then
		-- Portals are special because we don't append the children during mount
		-- but at commit. Therefore we need to track insertions which the normal
		-- flow doesn't do during mount. This doesn't happen at the root because
		-- the root always starts with a "current" with a nil child.
		-- TODO: Consider unifying this with how the root works.
		workInProgress.child =
			reconcileChildFibers(workInProgress, nil, nextChildren, renderLanes)
	else
		reconcileChildren(current, workInProgress, nextChildren, renderLanes)
	end
	return workInProgress.child
end

local hasWarnedAboutUsingNoValuePropOnContextProvider = false

local function updateContextProvider(
	current: Fiber | nil,
	workInProgress: Fiber,
	renderLanes: Lanes
)
	local providerType: ReactProviderType<any> = workInProgress.type
	local context: ReactContext<any> = providerType._context

	local newProps = workInProgress.pendingProps
	local oldProps = workInProgress.memoizedProps

	local newValue = newProps.value

	if __DEV__ or __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ then
		if Array.indexOf(Object.keys(newProps), "value") < 1 then
			if not hasWarnedAboutUsingNoValuePropOnContextProvider then
				hasWarnedAboutUsingNoValuePropOnContextProvider = true
				console.error(
					"The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?"
				)
			end
		end
		-- ROBLOX deviation: adds support for legacy Roact's validateProps()
		local providerPropTypes = workInProgress.type.propTypes
		local validateProps = workInProgress.type.validateProps

		if providerPropTypes or validateProps then
			checkPropTypes(
				providerPropTypes,
				validateProps,
				newProps,
				"prop",
				"Context.Provider"
			)
		end
	end

	pushProvider(workInProgress, newValue)

	if oldProps ~= nil then
		local oldValue = oldProps.value
		local changedBits = calculateChangedBits(context, newValue, oldValue)
		if changedBits == 0 then
			-- No change. Bailout early if children are the same.
			if
				oldProps.children == newProps.children
				and not hasLegacyContextChanged()
			then
				return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
			end
		else
			-- The context value changed. Search for matching consumers and schedule
			-- them to update.
			propagateContextChange(workInProgress, context, changedBits, renderLanes)
		end
	end

	local newChildren = newProps.children
	reconcileChildren(current, workInProgress, newChildren, renderLanes)
	return workInProgress.child
end

-- ROBLOX deviation: combine two warning flags to dodge the registers limit
local hasWarnedAbout = {
	usingContextAsConsumer = false,
	usingLegacyConsumer = false,
}

function updateContextConsumer(
	current: Fiber | nil,
	workInProgress: Fiber,
	renderLanes: Lanes
)
	local context: ReactContext<any> = workInProgress.type
	-- The logic below for Context differs depending on PROD or DEV mode. In
	-- DEV mode, we create a separate object for Context.Consumer that acts
	-- like a proxy to Context. This proxy object adds unnecessary code in PROD
	-- so we use the old behaviour (Context.Consumer references Context) to
	-- reduce size and overhead. The separate object references context via
	-- a property called "_context", which also gives us the ability to check
	-- in DEV mode if this property exists or not and warn if it does not.
	if __DEV__ then
		if (context :: any)._context == nil then
			-- This may be because it's a Context (rather than a Consumer).
			-- Or it may be because it's older React where they're the same thing.
			-- We only want to warn if we're sure it's a new React.
			if context ~= context.Consumer then
				if not hasWarnedAbout.usingContextAsConsumer then
					hasWarnedAbout.usingContextAsConsumer = true
					console.error(
[[Rendering <Context> directly is not supported and will be removed in a future major release. Did you mean to render <Context.Consumer> instead?]]					

)
				end
			end
		else
			context = (context :: any)._context
		end
	end
	local newProps = workInProgress.pendingProps

	-- ROBLOX deviation: compatibility for old Roact's context consumer API
	local render
	if newProps.render then
		if __DEV__ and __COMPAT_WARNINGS__ then
			if not hasWarnedAbout.usingLegacyConsumer then
				hasWarnedAbout.usingLegacyConsumer = true
				console.warn(
[[Your Context.Consumer component is using legacy Roact syntax, which won't be supported in future versions of Roact. 
Please provide no props and supply the 'render' function as a child (the 3rd argument of createElement). For example: 
       createElement(ContextConsumer, {render = function(...) end})
becomes:
       createElement(ContextConsumer, nil, function(...) end)
For more info, reference the React documentation here: 
https://reactjs.org/docs/context.html#contextconsumer]]				
)
			end
		end
		render = newProps.render
	else
		render = newProps.children
	end

	if __DEV__ then
		if type(render) ~= "function" then
			console.error(
[[A context consumer was rendered with multiple children, or a child that isn't a function. A context consumer expects a single child that is a function. If you did pass a function, make sure there is no trailing or leading whitespace around it.]]			



)
		end
	end

	prepareToReadContext(
		workInProgress,
		renderLanes,
		exports.markWorkInProgressReceivedUpdate
	)
	local newValue = readContext(context, newProps.unstable_observedBits)
	local newChildren
	if __DEV__ then
		ReactCurrentOwner.current = workInProgress
		setIsRendering(true)
		newChildren = render(newValue)
		setIsRendering(false)
	else
		newChildren = render(newValue)
	end

	-- React DevTools reads this flag.
	workInProgress.flags = bit32.bor(workInProgress.flags, PerformedWork)
	reconcileChildren(current, workInProgress, newChildren, renderLanes)
	return workInProgress.child
end

-- ROBLOX TODO: fundamental component is removed in React 18, clean up all traces when we upgrade
-- function updateFundamentalComponent(current, workInProgress, renderLanes)
--   local fundamentalImpl = workInProgress.type.impl
--   if fundamentalImpl.reconcileChildren == false then
--     return nil
--   end
--   local nextProps = workInProgress.pendingProps
--   local nextChildren = nextProps.children

--   reconcileChildren(current, workInProgress, nextChildren, renderLanes)
--   return workInProgress.child
-- end

-- ROBLOX TODO: scope component is disabled in our FeatureFlags, uncomment when we enable it
-- function updateScopeComponent(current, workInProgress, renderLanes)
--   local nextProps = workInProgress.pendingProps
--   local nextChildren = nextProps.children

--   reconcileChildren(current, workInProgress, nextChildren, renderLanes)
--   return workInProgress.child
-- end

exports.markWorkInProgressReceivedUpdate = function()
	didReceiveUpdate = true
end

function bailoutOnAlreadyFinishedWork(
	current: Fiber | nil,
	workInProgress: Fiber,
	renderLanes: Lanes
): Fiber | nil
	if current then
		-- Reuse previous dependencies
		workInProgress.dependencies = current.dependencies
	end

	if enableProfilerTimer then
		-- Don't update "base" render times for bailouts.
		stopProfilerTimerIfRunning(workInProgress)
	end

	markSkippedUpdateLanes(workInProgress.lanes)

	-- Check if the children have any pending work.
	if not ReactFiberLane.includesSomeLane(renderLanes, workInProgress.childLanes) then
		-- The children don't have any work either. We can skip them.
		-- TODO: Once we add back resuming, we should check if the children are
		-- a work-in-progress set. If so, we need to transfer their effects.
		return nil
	else
		-- This fiber doesn't have work, but its subtree does. Clone the child
		-- fibers and continue.
		cloneChildFibers(current, workInProgress)
		return workInProgress.child
	end
end

function remountFiber(
	current: Fiber,
	oldWorkInProgress: Fiber,
	newWorkInProgress: Fiber
): Fiber | nil
	if __DEV__ then
		local returnFiber = oldWorkInProgress.return_
		if returnFiber == nil then
			error("Cannot swap the root fiber.")
		end
		-- ROBLOX FIXME Luau: remove this assert when Luau type states understands the above guard
		assert(returnFiber ~= nil, "returnFiber was nil in remountFiber")

		-- Disconnect from the old current.
		-- It will get deleted.
		current.alternate = nil
		oldWorkInProgress.alternate = nil

		-- Connect to the new tree.
		newWorkInProgress.index = oldWorkInProgress.index
		newWorkInProgress.sibling = oldWorkInProgress.sibling
		newWorkInProgress.return_ = oldWorkInProgress.return_
		newWorkInProgress.ref = oldWorkInProgress.ref

		-- Replace the child/sibling pointers above it.
		if oldWorkInProgress == returnFiber.child then
			returnFiber.child = newWorkInProgress
		else
			local prevSibling = returnFiber.child
			if prevSibling == nil then
				error("Expected parent to have a child.")
			end
			-- ROBLOX FIXME Luau: remove this assert when Luau type states understands the above guard
			assert(prevSibling ~= nil, "prevSibling was nil in remountFiber")

			while prevSibling.sibling ~= oldWorkInProgress do
				prevSibling = prevSibling.sibling
				if prevSibling == nil then
					error("Expected to find the previous sibling.")
				end
			end
			prevSibling.sibling = newWorkInProgress
		end

		-- Delete the old fiber and place the new one.
		-- Since the old fiber is disconnected, we have to schedule it manually.
		local deletions = returnFiber.deletions
		if deletions == nil then
			returnFiber.deletions = { current }
			-- TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
			returnFiber.flags = bit32.bor(returnFiber.flags, Deletion)
		else
			table.insert(deletions, current)
		end

		newWorkInProgress.flags = bit32.bor(newWorkInProgress.flags, Placement)

		-- Restart work from the new fiber.
		return newWorkInProgress
	else
		error(
[[Did not expect this call in production. This is a bug in React. Please file an issue.]]		

)
	end
end

-- FIXME (roblox): restore types when refinement is better:
-- current: Fiber | nil,
local function beginWork(current: any, workInProgress: Fiber, renderLanes: Lanes): Fiber?
	local updateLanes = workInProgress.lanes

	if __DEV__ then
		if workInProgress._debugNeedsRemount and current ~= nil then
			-- This will restart the begin phase with a new fiber.
			return remountFiber(
				current,
				workInProgress,
				createFiberFromTypeAndProps(
					workInProgress.type,
					-- ROBLOX FIXME: we widen this to be number|string for Roact compatibility
					workInProgress.key :: string?,
					workInProgress.pendingProps,
					workInProgress._debugOwner or nil,
					workInProgress.mode,
					workInProgress.lanes
				)
			)
		end
	end

	if current ~= nil then
		local oldProps = current.memoizedProps
		local newProps = workInProgress.pendingProps

		if
			oldProps ~= newProps
			or hasLegacyContextChanged()
			-- Force a re-render if the implementation changed due to hot reload:
			or if __DEV__ then workInProgress.type ~= current.type else false
		then
			-- If props or context changed, mark the fiber as having performed work.
			-- This may be unset if the props are determined to be equal later (memo).
			didReceiveUpdate = true
		elseif not ReactFiberLane.includesSomeLane(renderLanes, updateLanes) then
			didReceiveUpdate = false
			-- This fiber does not have any pending work. Bailout without entering
			-- the begin phase. There's still some bookkeeping we that needs to be done
			-- in this optimized path, mostly pushing stuff onto the stack.
			if workInProgress.tag == HostRoot then
				pushHostRootContext(workInProgress)
				resetHydrationState()
			elseif workInProgress.tag == HostComponent then
				pushHostContext(workInProgress)
			elseif workInProgress.tag == ClassComponent then
				local Component = workInProgress.type
				if isLegacyContextProvider(Component) then
					pushLegacyContextProvider(workInProgress)
				end
			elseif workInProgress.tag == HostPortal then
				pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo)
			elseif workInProgress.tag == ContextProvider then
				local newValue = workInProgress.memoizedProps.value
				pushProvider(workInProgress, newValue)
			elseif workInProgress.tag == Profiler then
				if enableProfilerTimer then
					-- Reset effect durations for the next eventual effect phase.
					-- These are reset during render to allow the DevTools commit hook a chance to read them,
					local stateNode = workInProgress.stateNode
					stateNode.effectDuration = 0
					stateNode.passiveEffectDuration = 0
				end
			elseif workInProgress.tag == SuspenseComponent then
				local state: SuspenseState | nil = workInProgress.memoizedState
				if state ~= nil then
					if enableSuspenseServerRenderer then
						if (state :: SuspenseState).dehydrated ~= nil then
							pushSuspenseContext(
								workInProgress,
								setDefaultShallowSuspenseContext(
									suspenseStackCursor.current
								)
							)
							-- We know that this component will suspend again because if it has
							-- been unsuspended it has committed as a resolved Suspense component.
							-- If it needs to be retried, it should have work scheduled on it.
							workInProgress.flags =
								bit32.bor(workInProgress.flags, DidCapture)
							-- We should never render the children of a dehydrated boundary until we
							-- upgrade it. We return nil instead of bailoutOnAlreadyFinishedWork.
							return nil
						end
					end

					-- If this boundary is currently timed out, we need to decide
					-- whether to retry the primary children, or to skip over it and
					-- go straight to the fallback. Check the priority of the primary
					-- child fragment.
					local primaryChildFragment: Fiber = workInProgress.child :: any
					local primaryChildLanes = primaryChildFragment.childLanes
					if
						ReactFiberLane.includesSomeLane(renderLanes, primaryChildLanes)
					then
						-- The primary children have pending work. Use the normal path
						-- to attempt to render the primary children again.
						return updateSuspenseComponent(
							current,
							workInProgress,
							renderLanes
						)
					else
						-- The primary child fragment does not have pending work marked
						-- on it
						pushSuspenseContext(
							workInProgress,
							setDefaultShallowSuspenseContext(suspenseStackCursor.current)
						)
						-- The primary children do not have pending work with sufficient
						-- priority. Bailout.
						local child = bailoutOnAlreadyFinishedWork(
							current,
							workInProgress,
							renderLanes
						)
						if child ~= nil then
							-- The fallback children have pending work. Skip over the
							-- primary children and work on the fallback.
							return child.sibling
						else
							return nil
						end
					end
				else
					pushSuspenseContext(
						workInProgress,
						setDefaultShallowSuspenseContext(suspenseStackCursor.current)
					)
				end
			elseif workInProgress.tag == SuspenseListComponent then
				unimplemented("beginWork: SuspenseListComponent")
			-- local didSuspendBefore = bit32.band(current.flags, DidCapture) ~= NoFlags

			-- local hasChildWork = ReactFiberLane.includesSomeLane(
			--   renderLanes,
			--   workInProgress.childLanes
			-- )

			-- if didSuspendBefore then
			--   if hasChildWork then
			--     -- If something was in fallback state last time, and we have all the
			--     -- same children then we're still in progressive loading state.
			--     -- Something might get unblocked by state updates or retries in the
			--     -- tree which will affect the tail. So we need to use the normal
			--     -- path to compute the correct tail.
			--     return updateSuspenseListComponent(
			--       current,
			--       workInProgress,
			--       renderLanes
			--     )
			--   end
			--   -- If none of the children had any work, that means that none of
			--   -- them got retried so they'll still be blocked in the same way
			--   -- as before. We can fast bail out.
			--   workInProgress.flags = bit32.bor(workInProgress.flags, DidCapture)
			-- end

			-- -- If nothing suspended before and we're rendering the same children,
			-- -- then the tail doesn't matter. Anything new that suspends will work
			-- -- in the "together" mode, so we can continue from the state we had.
			-- local renderState = workInProgress.memoizedState
			-- if renderState ~= nil then
			--   -- Reset to the "together" mode in case we've started a different
			--   -- update in the past but didn't complete it.
			--   renderState.rendering = nil
			--   renderState.tail = nil
			-- end
			-- pushSuspenseContext(workInProgress, suspenseStackCursor.current)

			-- if not hasChildWork then
			--   -- If none of the children had any work, that means that none of
			--   -- them got retried so they'll still be blocked in the same way
			--   -- as before. We can fast bail out.
			--   return nil
			-- end
			elseif
				workInProgress.tag == OffscreenComponent
				or workInProgress.tag == LegacyHiddenComponent
			then
				-- Need to check if the tree still needs to be deferred. This is
				-- almost identical to the logic used in the normal update path,
				-- so we'll just enter that. The only difference is we'll bail out
				-- at the next level instead of this one, because the child props
				-- have not changed. Which is fine.
				-- TODO: Probably should refactor `beginWork` to split the bailout
				-- path from the normal path. I'm tempted to do a labeled break here
				-- but I won't :)
				workInProgress.lanes = ReactFiberLane.NoLanes
				return updateOffscreenComponent(current, workInProgress, renderLanes)
			end
			return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
		else
			if bit32.band(current.flags, ForceUpdateForLegacySuspense) ~= NoFlags then
				-- This is a special case that only exists for legacy mode.
				-- See https://github.com/facebook/react/pull/19216.
				didReceiveUpdate = true
			else
				-- An update was scheduled on this fiber, but there are no new props
				-- nor legacy context. Set this to false. If an update queue or context
				-- consumer produces a changed value, it will set this to true. Otherwise,
				-- the component will assume the children have not changed and bail out.
				didReceiveUpdate = false
			end
		end
	else
		didReceiveUpdate = false
	end

	-- Before entering the begin phase, clear pending update priority.
	-- TODO: This assumes that we're about to evaluate the component and process
	-- the update queue. However, there's an exception: SimpleMemoComponent
	-- sometimes bails out later in the begin phase. This indicates that we should
	-- move this assignment out of the common path and into each branch.
	workInProgress.lanes = ReactFiberLane.NoLanes

	if workInProgress.tag == ReactWorkTags.IndeterminateComponent then
		return mountIndeterminateComponent(
			current,
			workInProgress,
			workInProgress.type,
			renderLanes
		)
	elseif workInProgress.tag == LazyComponent then
		local elementType = workInProgress.elementType
		return mountLazyComponent(
			current,
			workInProgress,
			elementType,
			updateLanes,
			renderLanes
		)
	elseif workInProgress.tag == FunctionComponent then
		local Component = workInProgress.type
		local unresolvedProps = workInProgress.pendingProps
		local resolvedProps
		if workInProgress.elementType == Component then
			resolvedProps = unresolvedProps
		else
			resolvedProps = resolveDefaultProps(Component, unresolvedProps)
		end
		return updateFunctionComponent(
			current,
			workInProgress,
			Component,
			resolvedProps,
			renderLanes
		)
	elseif workInProgress.tag == ClassComponent then
		local Component = workInProgress.type
		local unresolvedProps = workInProgress.pendingProps
		local resolvedProps = workInProgress.elementType == Component and unresolvedProps
			or resolveDefaultProps(Component, unresolvedProps)
		return updateClassComponent(
			current,
			workInProgress,
			Component,
			resolvedProps,
			renderLanes
		)
	elseif workInProgress.tag == HostRoot then
		return updateHostRoot(current, workInProgress, renderLanes)
	elseif workInProgress.tag == HostComponent then
		return updateHostComponent(current, workInProgress, renderLanes)
	elseif workInProgress.tag == HostText then
		return updateHostText(current, workInProgress)
	elseif workInProgress.tag == SuspenseComponent then
		return updateSuspenseComponent(current, workInProgress, renderLanes)
	elseif workInProgress.tag == HostPortal then
		return updatePortalComponent(current, workInProgress, renderLanes)
	elseif workInProgress.tag == ForwardRef then
		local type = workInProgress.type
		local unresolvedProps = workInProgress.pendingProps
		local resolvedProps = unresolvedProps
		if workInProgress.elementType ~= type then
			resolvedProps = resolveDefaultProps(type, unresolvedProps)
		end
		return updateForwardRef(current, workInProgress, type, resolvedProps, renderLanes)
	elseif workInProgress.tag == Fragment then
		return updateFragment(current, workInProgress, renderLanes)
	elseif workInProgress.tag == Mode then
		return updateMode(current, workInProgress, renderLanes)
	elseif workInProgress.tag == Profiler then
		return updateProfiler(current, workInProgress, renderLanes)
	elseif workInProgress.tag == ContextProvider then
		return updateContextProvider(current, workInProgress, renderLanes)
	elseif workInProgress.tag == ContextConsumer then
		return updateContextConsumer(current, workInProgress, renderLanes)
	elseif workInProgress.tag == MemoComponent then
		local type_ = workInProgress.type
		local unresolvedProps = workInProgress.pendingProps
		-- Resolve outer props first, then resolve inner props.
		local resolvedProps = resolveDefaultProps(type_, unresolvedProps)
		if __DEV__ or __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ then
			if workInProgress.type ~= workInProgress.elementType then
				-- ROBLOX deviation: adds support for legacy Roact's validateProps()
				local outerPropTypes
				local validateProps
				-- ROBLOX deviation: only get propTypes from class components, Lua doesn't support fields on functions
				if type(type_) == "table" then
					outerPropTypes = type_.propTypes
					validateProps = type_.validateProps
				end
				if outerPropTypes or validateProps then
					checkPropTypes(
						outerPropTypes,
						validateProps,
						resolvedProps, -- Resolved for outer only
						"prop",
						getComponentName(type_)
					)
				end
			end
		end
		resolvedProps = resolveDefaultProps(type_.type, resolvedProps)
		return updateMemoComponent(
			current,
			workInProgress,
			type_,
			resolvedProps,
			updateLanes,
			renderLanes
		)
	elseif workInProgress.tag == SimpleMemoComponent then
		return updateSimpleMemoComponent(
			current,
			workInProgress,
			workInProgress.type,
			workInProgress.pendingProps,
			updateLanes,
			renderLanes
		)
	elseif workInProgress.tag == IncompleteClassComponent then
		local Component = workInProgress.type
		local unresolvedProps = workInProgress.pendingProps
		local resolvedProps = workInProgress.elementType == Component and unresolvedProps
			or resolveDefaultProps(Component, unresolvedProps)
		return mountIncompleteClassComponent(
			current,
			workInProgress,
			Component,
			resolvedProps,
			renderLanes
		)
	-- elseif workInProgress.tag == SuspenseListComponent then
	--   unimplemented("beginWork: SuspenseListComponent")
	-- return updateSuspenseListComponent(current, workInProgress, renderLanes)
	-- elseif workInProgress.tag == FundamentalComponent then
	--   if enableFundamentalAPI then
	--     return updateFundamentalComponent(current, workInProgress, renderLanes)
	--   end
	-- elseif workInProgress.tag == ScopeComponent then
	--   if enableScopeAPI then
	--     return updateScopeComponent(current, workInProgress, renderLanes)
	--   end
	elseif workInProgress.tag == OffscreenComponent then
		return updateOffscreenComponent(current, workInProgress, renderLanes)
	elseif workInProgress.tag == LegacyHiddenComponent then
		return updateLegacyHiddenComponent(current, workInProgress, renderLanes)
	end
	invariant(
		false,
[[Unknown unit of work tag (%s). This error is likely caused by a bug in React. Please file an issue.]]
,
		tostring(workInProgress.tag)
	)
	return nil
end
exports.beginWork = beginWork

return exports
