--!strict
-- ROBLOX upstream: https://github.com/facebook/react/blob/v17.0.1/packages/react-devtools-shared/src/backend/index.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.
]]

local Packages = script.Parent.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Array = LuauPolyfill.Array

local Agent = require(script.agent)
type Agent = Agent.Agent

local types = require(script.types)
export type DevToolsHook = types.DevToolsHook
export type ReactRenderer = types.ReactRenderer
export type RendererInterface = types.RendererInterface

type Object = { [string]: any }

local function initBackend(hook: DevToolsHook, agent: Agent, global: Object): () -> ()
	if hook == nil then
		-- DevTools didn't get injected into this page (maybe b'c of the contentType).
		return function() end
	end
	local subs = {
		hook.sub("renderer-attached", function(args: {
			id: number,
			renderer: ReactRenderer,
			rendererInterface: RendererInterface,
		})
			local id = args.id
			local rendererInterface = args.rendererInterface

			agent:setRendererInterface(id, rendererInterface)

			-- Now that the Store and the renderer interface are connected,
			-- it's time to flush the pending operation codes to the frontend.
			rendererInterface.flushInitialOperations()
		end),
		hook.sub("unsupported-renderer-version", function(id: number)
			agent:onUnsupportedRenderer(id)
		end),

		hook.sub("operations", function(...)
			agent:onHookOperations(...)
		end),
		hook.sub("traceUpdates", function(...)
			agent:onTraceUpdates(...)
		end),

		-- TODO Add additional subscriptions required for profiling mode
	}

	local attachRenderer = function(id: number, renderer: ReactRenderer)
		-- ROBLOX deviation: require attach lazily to avoid the require of renderer causing Roact to initialize prematurely.
		local attach = require(script.renderer).attach

		local rendererInterface = hook.rendererInterfaces:get(id)

		-- Inject any not-yet-injected renderers (if we didn't reload-and-profile)
		if rendererInterface == nil then
			if type(renderer.findFiberByHostInstance) == "function" then
				-- react-reconciler v16+
				rendererInterface = attach(hook, id, renderer, global)
			elseif renderer.ComponentTree then
				-- react-dom v15
				-- ROBLOX deviation: Not needed
				-- rendererInterface = attachLegacy(hook, id, renderer, global)
			else
				-- Older react-dom or other unsupported renderer version
			end
			if rendererInterface ~= nil then
				hook.rendererInterfaces:set(id, rendererInterface)
			end
		end

		-- Notify the DevTools frontend about new renderers.
		-- This includes any that were attached early (via __REACT_DEVTOOLS_ATTACH__).
		if rendererInterface ~= nil then
			hook.emit("renderer-attached", {
				id = id,
				renderer = renderer,
				rendererInterface = rendererInterface,
			})
		else
			hook.emit("unsupported-renderer-version", id)
		end
	end

	-- Connect renderers that have already injected themselves.
	hook.renderers:forEach(function(renderer, id)
		attachRenderer(id, renderer)
	end)

	-- Connect any new renderers that injected themselves.
	table.insert(
		subs,
		hook.sub("renderer", function(args: { id: number, renderer: ReactRenderer })
			local id = args.id
			local renderer = args.renderer
			attachRenderer(id, renderer)
		end)
	)

	hook.emit("react-devtools", agent)
	hook.reactDevtoolsAgent = agent
	local function onAgentShutdown()
		Array.forEach(subs, function(fn)
			fn()
		end)
		hook.rendererInterfaces:forEach(function(rendererInterface)
			rendererInterface.cleanup()
		end)
		hook.reactDevtoolsAgent = nil
	end
	agent:addListener("shutdown", onAgentShutdown)
	table.insert(subs, function()
		agent:removeListener("shutdown", onAgentShutdown)
	end)

	return function()
		for _, fn in subs do
			fn()
		end
	end
end

return {
	initBackend = initBackend,
	agent = require(script.agent),
	-- ROBLOX deviation: require renderer lazily to avoid causing React to initialize prematurely.
	getRendererLazy = function()
		return require(script.renderer)
	end,
	NativeStyleEditor = {
		types = require(script.NativeStyleEditor.types),
	},
}
