-- ROBLOX upstream: https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react/src/ReactCreateRef.js
--!strict
--[[*
 * 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
*]]

local Packages = script.Parent.Parent
local ReactGlobals = require(Packages.ReactGlobals)
local ReactTypes = require(Packages.Shared)
type RefObject = ReactTypes.RefObject

-- ROBLOX DEVIATION: In Roact, refs are implemented in terms of bindings
--[[
  A ref is nothing more than a binding with a special field 'current'
  that maps to the getValue method of the binding
]]
local Binding = require(script.Parent["ReactBinding.roblox"])

local exports = {}

-- an immutable object with a single mutable value
exports.createRef = function(): RefObject
	local binding, _ = Binding.create(nil)

	local ref = {}

	-- ROBLOX DEVIATION: Since refs are used as bindings, they can often be
	-- assigned to fields of other Instances; we track creation here parallel to
	-- how we do with bindings created via `createBinding` to improve messaging
	-- when something goes wrong
	if ReactGlobals.__DEV__ then
		-- ROBLOX TODO: LUAFDN-619 - improve debug stacktraces for refs
		binding._source = debug.traceback("Ref created at:", 1)
	end

	--[[
    A ref is just redirected to a binding via its metatable
  ]]
	setmetatable(ref, {
		__index = function(self, key)
			if key == "current" then
				return binding:getValue()
			else
				return (binding :: any)[key]
			end
		end,
		__newindex = function(self, key, value)
			if key == "current" then
				-- ROBLOX FIXME: Bindings - This is not allowed in Roact, but is okay in
				-- React. Lots of discussion at
				-- https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065
				-- error("Cannot assign to the 'current' property of refs", 2)
				Binding.update(binding, value)
			end

			(binding :: any)[key] = value
		end,
		__tostring = function(self)
			return string.format("Ref(%s)", tostring(binding:getValue()))
		end,
	})

	return (ref :: any) :: RefObject
end

return exports
