local function S(S)return{value=S;onUpdate=createSignal()}end local function N(N)local G=Component:extend("\080\114\111\118\105\100\101\114")function G.init(p,G)p.contextEntry=S(G.value)p:__addContext(N.key,p.contextEntry)end function G.willUpdate(N,S)if S.value~=N.props.value then N.contextEntry.value=S.value end end function G.didUpdate(N,S)if S.value~=N.props.value then N.contextEntry.onUpdate:fire(N.props.value)end end function G.render(S)return createFragment(S.props[Children])end return G end local function G(S)local N=Component:extend("\067\111\110\115\117\109\101\114")function N.validateProps(S)if type(S.render)~="\102\117\110\099\116\105\111\110"then return false,"\067\111\110\115\117\109\101\114 \101\120\112\101\099\116\115 \097 \096\114\101\110\100\101\114\096 \102\117\110\099\116\105\111\110"else return true end end function N.init(G,N)G.contextEntry=G:__getContext(S.key)end function N.render(N)local G if N.contextEntry~=nil then G=N.contextEntry.value else G=S.defaultValue end return N.props.render(G)end function N.didUpdate(S)if S.contextEntry~=nil then S.lastValue=S.contextEntry.value end end function N.didMount(S)if S.contextEntry~=nil then S.disconnect=S.contextEntry.onUpdate:subscribe(function(N)if N~=S.lastValue then S:setState({})end end)end end function N.willUnmount(S)if S.disconnect~=nil then S.disconnect()end end return N end local p={}p.__index=p function p.new(S)return setmetatable({defaultValue=S;key=Symbol.named("\067\111\110\116\101\120\116\075\101\121")},p)end function p.__tostring(S)return"\082\111\097\099\116\067\111\110\116\101\120\116"end local function H(S)local H=p.new(S)return{Provider=N(H),Consumer=G(H)}end return H

local function createContextEntry(currentValue)
	return {
		value = currentValue,
		onUpdate = createSignal(),
	}
end

local function createProvider(context)
	local Provider = Component:extend("Provider")

	function Provider:init(props)
		self.contextEntry = createContextEntry(props.value)
		self:__addContext(context.key, self.contextEntry)
	end

	function Provider:willUpdate(nextProps)
		-- If the provided value changed, immediately update the context entry.
		--
		-- During this update, any components that are reachable will receive
		-- this updated value at the same time as any props and state updates
		-- that are being applied.
		if nextProps.value ~= self.props.value then
			self.contextEntry.value = nextProps.value
		end
	end

	function Provider:didUpdate(prevProps)
		-- If the provided value changed, after we've updated every reachable
		-- component, fire a signal to update the rest.
		--
		-- This signal will notify all context consumers. It's expected that
		-- they will compare the last context value they updated with and only
		-- trigger an update on themselves if this value is different.
		--
		-- This codepath will generally only update consumer components that has
		-- a component implementing shouldUpdate between them and the provider.
		if prevProps.value ~= self.props.value then
			self.contextEntry.onUpdate:fire(self.props.value)
		end
	end

	function Provider:render()
		return createFragment(self.props[Children])
	end

	return Provider
end

local function createConsumer(context)
	local Consumer = Component:extend("Consumer")

	function Consumer.validateProps(props)
		if type(props.render) ~= "function" then
			return false, "Consumer expects a `render` function"
		else
			return true
		end
	end

	function Consumer:init(props)
		-- This value may be nil, which indicates that our consumer is not a
		-- descendant of a provider for this context item.
		self.contextEntry = self:__getContext(context.key)
	end

	function Consumer:render()
		-- Render using the latest available for this context item.
		--
		-- We don't store this value in state in order to have more fine-grained
		-- control over our update behavior.
		local value
		if self.contextEntry ~= nil then
			value = self.contextEntry.value
		else
			value = context.defaultValue
		end

		return self.props.render(value)
	end

	function Consumer:didUpdate()
		-- Store the value that we most recently updated with.
		--
		-- This value is compared in the contextEntry onUpdate hook below.
		if self.contextEntry ~= nil then
			self.lastValue = self.contextEntry.value
		end
	end

	function Consumer:didMount()
		if self.contextEntry ~= nil then
			-- When onUpdate is fired, a new value has been made available in
			-- this context entry, but we may have already updated in the same
			-- update cycle.
			--
			-- To avoid sending a redundant update, we compare the new value
			-- with the last value that we updated with (set in didUpdate) and
			-- only update if they differ. This may happen when an update from a
			-- provider was blocked by an intermediate component that returned
			-- false from shouldUpdate.
			self.disconnect = self.contextEntry.onUpdate:subscribe(function(newValue)
				if newValue ~= self.lastValue then
					-- Trigger a dummy state update.
					self:setState({})
				end
			end)
		end
	end

	function Consumer:willUnmount()
		if self.disconnect ~= nil then
			self.disconnect()
		end
	end

	return Consumer
end

local Context = {}
Context.__index = Context

function Context.new(defaultValue)
	return setmetatable({
		defaultValue = defaultValue,
		key = Symbol.named("ContextKey"),
	}, Context)
end

function Context:__tostring()
	return "RoactContext"
end

local function createContext(defaultValue)
	local context = Context.new(defaultValue)

	return {
		Provider = createProvider(context),
		Consumer = createConsumer(context),
	}
end

return createContext
