--#METADATA#{"CREATABLE_INSTANCES":["TextLabel","Part"], "SERVICES": ["ReplicatedStorage","ServerScriptService","Workspace"]}

declare class Enum
    function GetEnumItems(self): { any }
end
declare class EnumItem
    Name: string
    Value: number
    EnumType: Enum
    function IsA(self, enumName: string): boolean
end

declare class EnumHumanoidRigType extends EnumItem end
declare class EnumHumanoidRigType_INTERNAL extends Enum
    R6: EnumHumanoidRigType
    R15: EnumHumanoidRigType
end
type ENUM_LIST = {
    HumanoidRigType: EnumHumanoidRigType_INTERNAL,
} & { GetEnums: (self: ENUM_LIST) -> { Enum } }
declare Enum: ENUM_LIST

declare class RBXScriptConnection
    Connected: boolean
    function Disconnect(self): nil
end

export type RBXScriptSignal<T... = ...any> = {
    Wait: (self: RBXScriptSignal<T...>) -> T...,
    Connect: (self: RBXScriptSignal<T...>, callback: (T...) -> ()) -> RBXScriptConnection,
    ConnectParallel: (self: RBXScriptSignal<T...>, callback: (T...) -> ()) -> RBXScriptConnection,
}

declare class Object
	Changed: RBXScriptSignal<string>
	ClassName: string
	function GetPropertyChangedSignal(self, property: string): RBXScriptSignal
	function IsA(self, className: string): boolean
end

declare class Instance extends Object
    AncestryChanged: RBXScriptSignal<Instance, Instance?>
    AttributeChanged: RBXScriptSignal<string>
    ChildAdded: RBXScriptSignal<Instance>
    ChildRemoved: RBXScriptSignal<Instance>
    DescendantAdded: RBXScriptSignal<Instance>
    DescendantRemoving: RBXScriptSignal<Instance>
    Destroying: RBXScriptSignal<>
    Name: string
    Parent: Instance?
    function ClearAllChildren(self): nil
    function Clone(self): Instance
    function Destroy(self): nil
    function FindFirstAncestor(self, name: string): Instance?
    function FindFirstAncestorOfClass(self, className: string): Instance?
    function FindFirstAncestorWhichIsA(self, className: string): Instance?
    function FindFirstChild(self, name: string, recursive: boolean?): Instance?
    function FindFirstChildOfClass(self, className: string): Instance?
    function FindFirstChildWhichIsA(self, className: string, recursive: boolean?): Instance?
    function FindFirstDescendant(self, name: string): Instance?
    function GetAttribute(self, attribute: string): unknown?
    function GetAttributeChangedSignal(self, attribute: string): RBXScriptSignal<>
    function GetAttributes(self): { [string]: unknown }
    function GetChildren(self): { Instance }
    function GetDescendants(self): { Instance }
    function GetFullName(self): string
    function IsAncestorOf(self, descendant: Instance): boolean
    function IsDescendantOf(self, ancestor: Instance): boolean
    function IsPropertyModified(self, property: string): boolean
    function ResetPropertyToDefault(self, property: string): nil
    function SetAttribute(self, attribute: string, value: any): nil
    function WaitForChild(self, name: string): Instance
    function WaitForChild(self, name: string, timeout: number): Instance?
    function QueryDescendants(self, selector: string): { Instance }
end

declare class BasePart extends Instance
    Anchored: boolean
end

declare class Part extends BasePart
    Anchored: boolean
end

declare class TextLabel extends Instance
    Text: string
end

declare class ReplicatedStorage extends Instance
end

declare class ServerScriptService extends Instance
end

declare class Workspace extends Instance
end

declare class ServiceProvider extends Instance
    function GetService(self, className: string): Instance
end

declare class DataModel extends ServiceProvider
    function BindToClose(self, func: ((...any) -> ...any)): nil
end

declare Instance: {
    new: ((className: string, parent: Instance?) -> Instance),
    fromExisting: ((existingInstance: Instance) -> Instance),
}

declare game: DataModel

--- This is a documented table
export type DocumentedTable = {
    --- This is documented member1 of the table
    member1: string,
}

--- This is a documented class
declare class DocumentedClass
    --- This is a documented member1 of the class
    member1: string
    --- This is a documented function1 of the class
    function function1(self): number
end

--- This is a documented global function
declare function DocumentedGlobalFunction(): number

--- This is a documented global variable
declare DocumentedGlobalVariable: number


local Workspace = script.Parent

-- In case we need to specify a custom testSetupFile for a project, we need to do that in in a separate jest.config.lua file that's in the project's root folder.
-- Therefore we specify the project here and provide it to the "projects" field in this config file.
-- We also need to add the project to the "testPathIgnorePatterns" field so that Jest doesn't try to run the project's tests again.
local projectsWithCustomJestConfig = {
	Workspace.ReactDevtoolsShared.ReactDevtoolsShared,
}
local testPathIgnorePatterns = {}
local allProjects = { Workspace }

for _, project in projectsWithCustomJestConfig do
	table.insert(testPathIgnorePatterns, tostring(project))
	table.insert(allProjects, project)
end

return {
	setupFilesAfterEnv = { Workspace.jest.testSetupFile },
	projects = allProjects,
	testMatch = {
		"**/__tests__/*.(spec|test)",
	},
	testPathIgnorePatterns = testPathIgnorePatterns,
}


local createConsoleMatcher = require(script.Parent.createConsoleMatcher)

return createConsoleMatcher("error", "toErrorDev")


local createConsoleMatcher = require(script.Parent.createConsoleMatcher)

return createConsoleMatcher("log", "toLogDev")


local createConsoleMatcher = require(script.Parent.createConsoleMatcher)

return createConsoleMatcher("warn", "toWarnDev")


local Packages = script.Parent.RoactAlignment
local RotrieverWorkspace = Packages._Workspace

local Roact = require(RotrieverWorkspace.React.React)
local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox)

local config = {}
if _G.minSamples ~= nil then
	config.sampleCount = tonumber(_G.minSamples)
end

require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).deepTreeBenchmark(
	Roact,
	ReactRoblox
)(config)


local Packages = script.Parent.RoactAlignment
local RotrieverWorkspace = Packages._Workspace

local Roact = require(RotrieverWorkspace.React.React)
local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox)
local Scheduler = require(RotrieverWorkspace.Scheduler.Scheduler)
local firstRenderBenchmark =
	require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).firstRenderBenchmark

local config = {
	minSamples = 200,
}
if _G.minSamples ~= nil then
	config.minSamples = tonumber(_G.minSamples)
end

firstRenderBenchmark(Roact, ReactRoblox, Scheduler)(config)


local Packages = script.Parent.RoactAlignment
local RotrieverWorkspace = Packages._Workspace

local Roact = require(RotrieverWorkspace.React.React)
local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox)
local Scheduler = require(RotrieverWorkspace.Scheduler.Scheduler)
local frameRateBenchmark =
	require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).frameRateBenchmark

local config = {
	minSamples = 600,
}
if _G.minSamples ~= nil then
	config.minSamples = tonumber(_G.minSamples)
end

frameRateBenchmark(Roact, ReactRoblox, Scheduler)(config)


local Packages = script.Parent.RoactAlignment
local RotrieverWorkspace = Packages._Workspace

local Roact = require(RotrieverWorkspace.React.React)
local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox)
local sierpinskiTriangleBenchmark = require(
	RotrieverWorkspace.React.Dev.PerformanceBenchmarks
).sierpinskiTriangleBenchmark

local config = {}
if _G.minSamples ~= nil then
	config.sampleCount = tonumber(_G.minSamples)
end

sierpinskiTriangleBenchmark(Roact, ReactRoblox)(config)
wait(1)


local Packages = script.Parent.RoactAlignment
local RotrieverWorkspace = Packages._Workspace

local Roact = require(RotrieverWorkspace.React.React)
local ReactRoblox = require(RotrieverWorkspace.ReactRoblox.ReactRoblox)
local wideTreeBenchmark =
	require(RotrieverWorkspace.React.Dev.PerformanceBenchmarks).wideTreeBenchmark

local config = {}
if _G.minSamples ~= nil then
	config.sampleCount = tonumber(_G.minSamples)
end

wideTreeBenchmark(Roact, ReactRoblox)(config)


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
local Workspace = script.Parent.RoactAlignment._Workspace
local runCLI = require(Workspace.TestRunner.Dev.Jest).runCLI

local processServiceExists, ProcessService = pcall(function()
	return game:GetService("ProcessService")
end)

local status, result = runCLI(Workspace, {
	verbose = if _G.verbose == "true" then true else nil,
	ci = _G.CI == "true",
	updateSnapshot = _G.UPDATESNAPSHOT == "true",
}, { Workspace }):awaitStatus()

if status == "Rejected" then
	print(result)
end

if
	status == "Resolved"
	and result.results.numFailedTestSuites == 0
	and result.results.numFailedTests == 0
then
	if processServiceExists then
		ProcessService:ExitAsync(0)
	end
end

if processServiceExists then
	ProcessService:ExitAsync(1)
end

return nil


local CorePackages = game:GetService("CorePackages")
local RunService = game:GetService("RunService")

if RunService:IsServer() then
	return
end

-- Set global flags and initialize devtools before React is ever used
local ReactGlobals = require(CorePackages.ReactGlobals)
ReactGlobals.__DEV__ = true
ReactGlobals.__PROFILE__ = true

local ReactDevtoolsCore = require(CorePackages.ReactDevtoolsCore)
ReactDevtoolsCore.backend.connectToDevtools()

require(CorePackages.ExampleApp)


local HttpService = game:GetService("HttpService")

type Record = { [string]: any }

local ENCODE_TO_NULL = newproxy()

--- Serializes a table such that it can be encoded as JSON via HttpService.
local function serializeTable(tbl: Record): Record
	local seen = {}

	local function visitPropsRecursive(parent: Record): any
		if type(parent) ~= "table" then
			return parent
		end

		-- Break cyclic references
		if seen[parent] then
			return nil
		end
		seen[parent] = true

		local newParent = {}
		for key, value in parent do
			if type(value) == "table" then
				local newValue = visitPropsRecursive(value)
				newParent[key] = newValue
			elseif typeof(value) == "UDim" then
				newParent[key] = `UDim({value.Scale}, {value.Offset})`
			elseif typeof(value) == "UDim2" then
				newParent[key] =
					`UDim2({value.X.Scale}, {value.X.Offset}, {value.Y.Scale}, {value.Y.Offset})`
			elseif typeof(value) == "Vector2" then
				newParent[key] = `Vector2({value.X}, {value.Y})`
			elseif typeof(value) == "Vector3" then
				newParent[key] = `Vector3({value.X}, {value.Y}, {value.Z})`
			elseif typeof(value) == "CFrame" then
				newParent[key] = `CFrame({tostring(value)})`
			elseif typeof(value) == "Color3" then
				newParent[key] = `Color3({value.R}, {value.G}, {value.B})`
			elseif typeof(value) == "Rect" then
				newParent[key] =
					`Rect({value.Min.X}, {value.Min.Y}, {value.Max.X}, {value.Max.Y})`
			elseif typeof(value) == "EnumItem" then
				newParent[key] = `EnumItem({value.EnumType}, {value.Name})`
			elseif typeof(value) == "Instance" then
				newParent[key] = `Instance({value:GetFullName()})`
			elseif typeof(value) == "BrickColor" then
				newParent[key] = `BrickColor({value.Name})`
			elseif typeof(value) == "NumberRange" then
				newParent[key] = `NumberRange({value.Min}, {value.Max})`
			elseif typeof(value) == "NumberSequence" then
				local keypoints = {}
				for _, kp in value.Keypoints do
					table.insert(
						keypoints,
						`NumberSequenceKeypoint({kp.Time}, {kp.Value}, {kp.Envelope})`
					)
				end
				newParent[key] = `NumberSequence({table.concat(keypoints, ", ")})`
			elseif typeof(value) == "ColorSequence" then
				local keypoints = {}
				for _, kp in ipairs(value.Keypoints) do
					table.insert(
						keypoints,
						`ColorSequenceKeypoint({kp.Time}, {kp.Value.R}, {kp.Value.G}, {kp.Value.B})`
					)
				end
				newParent[key] = `ColorSequence({table.concat(keypoints, ", ")})`
			elseif typeof(value) == "PhysicalProperties" then
				newParent[key] =
					`PhysicalProperties({value.Density}, {value.Friction}, {value.Elasticity}, {value.FrictionWeight}, {value.ElasticityWeight})`
			elseif typeof(value) == "Vector2int16" then
				newParent[key] = `Vector2int16({value.X}, {value.Y})`
			elseif typeof(value) == "Vector3int16" then
				newParent[key] = `Vector3int16({value.X}, {value.Y}, {value.Z})`
			elseif typeof(value) == "PathWaypoint" then
				newParent[key] =
					`PathWaypoint({value.Position.X}, {value.Position.Y}, {value.Position.Z}, {value.Action})`
			elseif typeof(value) == "OverlapParams" then
				newParent[key] =
					`OverlapParams({value.FilterDescendantsInstances}, {value.FilterType}, {value.MaxParts}, {value.CollisionGroup})`
			elseif typeof(value) == "RaycastParams" then
				newParent[key] =
					`RaycastParams({value.FilterDescendantsInstances}, {value.FilterType}, {value.IgnoreWater}, {value.CollisionGroup})`
			else
				local canSerialize = pcall(HttpService.JSONEncode, HttpService, { value })
				if canSerialize then
					newParent[key] = value
				else
					warn(
						`[React DevTools] Could not serialize value for key "{key}" (type '{typeof(
							value
						)}'):`,
						value
					)
					newParent[key] = ENCODE_TO_NULL
				end
			end
		end
		return newParent
	end

	return visitPropsRecursive(tbl)
end

return serializeTable


-- ROBLOX note: no upstream
-- ROBLOX note: The setup function adds the glue required for DeveloperTools to initialize the Roact devtools correctly
local Packages = script.Parent

return {
	setup = function(debugMode: boolean)
		local ReactGlobals = require(Packages.ReactGlobals)

		-- ROBLOX note: Set globals for React devtools to work
		ReactGlobals.__DEV__ = true
		ReactGlobals.__DEBUG__ = debugMode or false
		ReactGlobals.__PROFILE__ = true
		ReactGlobals.__EXPERIMENTAL__ = true
		-- ROBLOX note: Don't hide host coomponents as the current Developer Inspector uses on these to preserve a
		-- direct mapping between the Inspector tree and the Explorer tree as requested by design.
		ReactGlobals.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = {}

		local ReactDevtoolsShared = require(Packages.ReactDevtoolsShared)
		local setup = require(Packages.ReactDevtoolsExtensions.backend).setup
		local installHook = ReactDevtoolsShared.hook.installHook
		local Store = ReactDevtoolsShared.devtools.store

		-- ROBLOX note: Ensure that the global hook is installed before the injection into DevTools
		installHook(ReactGlobals)

		-- ROBLOX note: Ensure that ReactRoblox is loaded after injection so that the ReactHostConfig is populated correctly
		require(Packages.React)
		require(Packages.ReactRoblox)

		local hook = ReactGlobals.__REACT_DEVTOOLS_GLOBAL_HOOK__

		-- ROBLOX note: Make sure that this method was called before ReactRoblox was first required,
		-- otherwise the profiler will not be enabled for the session.
		local ReactFeatureFlags = require(Packages.Shared).ReactFeatureFlags
		if not ReactFeatureFlags.enableSchedulingProfiler then
			warn(
				"[DeveloperTools] React was initialized before DeveloperTools. Call inspector.setupReactDevtools before requiring React to enable profiling."
			)
		end

		local result = setup(hook)

		-- ROBLOX note: The DeveloperTools library is only passed the ReactDevtoolsExtensions API to keep the
		-- devtools init process compact for users. Initialize the store so DeveloperTools doesn't also need to be
		-- passed the ReactDevtoolsShared API.
		return {
			agent = result.agent,
			bridge = result.bridge,
			hook = result.hook,
			store = Store.new(result.bridge),
		}
	end,
}


local OverlayRect = {}
OverlayRect.__index = OverlayRect

function OverlayRect.new(container: GuiBase2d)
	local self = setmetatable({}, OverlayRect)
	self.container = container

	local node = Instance.new("Frame")
	node.Name = "OverlayRect"
	node.BackgroundTransparency = 1
	node.Parent = container
	self.node = node

	local padding = Instance.new("Frame")
	padding.Name = "OverlayRectPadding"
	padding.BackgroundColor3 = Color3.fromRGB(77, 200, 0)
	padding.Size = UDim2.fromScale(1, 1)
	padding.BackgroundTransparency = 0.4
	padding.BorderSizePixel = 0
	padding.Parent = node
	self.padding = padding

	local content = Instance.new("Frame")
	content.Name = "OverlayRectContent"
	content.BackgroundColor3 = Color3.fromRGB(120, 170, 210)
	content.Size = UDim2.fromScale(1, 1)
	content.BorderSizePixel = 0
	content.ZIndex = 2
	content.Parent = node
	self.content = content

	return self
end

export type OverlayRect = typeof(OverlayRect.new(...))

function OverlayRect.remove(self: OverlayRect)
	self.node:Destroy()
end

function OverlayRect.update(self: OverlayRect, element: GuiBase2d)
	local size = element.AbsoluteSize
	local position = element.AbsolutePosition

	local padding = element:FindFirstChildOfClass("UIPadding")
	if padding then
		local top = (padding.PaddingTop.Scale * size.Y) + padding.PaddingTop.Offset
		local left = (padding.PaddingLeft.Scale * size.X) + padding.PaddingLeft.Offset
		local bottom = (padding.PaddingBottom.Scale * size.Y)
			+ padding.PaddingBottom.Offset
		local right = (padding.PaddingRight.Scale * size.X) + padding.PaddingRight.Offset

		self.content.Position = UDim2.fromOffset(left, top)
		self.content.Size = UDim2.fromOffset(size.X - left - right, size.Y - top - bottom)
	else
		self.content.Position = UDim2.fromOffset(0, 0)
		self.content.Size = UDim2.fromOffset(size.X, size.Y)
	end

	self.node.Size = UDim2.fromOffset(size.X, size.Y)
	self.node.Position = UDim2.fromOffset(position.X, position.Y)
end

return {
	new = OverlayRect.new,
}


type Box = { top: number, left: number, width: number, height: number }

local function calculatePosition(dims: Box, bounds: Box, size: Vector2)
	local tipHeight = math.max(size.Y, 20)
	local tipWidth = math.max(size.X, 60)
	local margin = 5

	local top: number
	if dims.top + dims.height + tipHeight <= bounds.top + bounds.height then
		if dims.top + dims.height < bounds.top + 0 then
			top = bounds.top + margin
		else
			top = dims.top + dims.height + margin
		end
	elseif dims.top - tipHeight <= bounds.top + bounds.height then
		if dims.top - tipHeight - margin < bounds.top + margin then
			top = bounds.top + margin
		else
			top = dims.top - tipHeight - margin
		end
	else
		top = bounds.top + bounds.height - tipHeight - margin
	end

	local left = dims.left + margin
	if dims.left < bounds.left then
		left = bounds.left + margin
	end
	if dims.left + tipWidth > bounds.left + bounds.width then
		left = bounds.left + bounds.width - tipWidth - margin
	end

	return Vector2.new(left, top)
end

local OverlayTip = {}
OverlayTip.__index = OverlayTip

function OverlayTip.new(container: GuiBase2d)
	local self = setmetatable({}, OverlayTip)

	local background = Instance.new("Frame")
	background.Name = "OverlayTip"
	background.BackgroundColor3 = Color3.fromHex("#333740")
	background.AutomaticSize = Enum.AutomaticSize.XY
	background.Size = UDim2.fromScale(0, 0)
	background.BorderSizePixel = 0
	background.ZIndex = 1_000_000 + 1
	self.background = background

	local layout = Instance.new("UIListLayout")
	layout.SortOrder = Enum.SortOrder.LayoutOrder
	layout.FillDirection = Enum.FillDirection.Horizontal
	layout.VerticalFlex = Enum.UIFlexAlignment.Fill
	layout.Padding = UDim.new(0, 6)
	layout.Parent = background

	local padding = Instance.new("UIPadding")
	padding.PaddingTop = UDim.new(0, 4)
	padding.PaddingBottom = UDim.new(0, 4)
	padding.PaddingLeft = UDim.new(0, 6)
	padding.PaddingRight = UDim.new(0, 6)
	padding.Parent = background

	local cornerRadius = Instance.new("UICorner")
	cornerRadius.CornerRadius = UDim.new(0, 2)
	cornerRadius.Parent = background

	local name = Instance.new("TextLabel")
	name.Name = "Name"
	name.Size = UDim2.fromScale(0, 0)
	name.AutomaticSize = Enum.AutomaticSize.XY
	name.BackgroundTransparency = 1
	name.LayoutOrder = 1
	name.Font = Enum.Font.BuilderSansBold
	name.TextColor3 = Color3.fromHex("#ee78e6")
	name.TextSize = 16
	name.ZIndex = 1_000_000 + 2
	name.Parent = background
	self.nameLabel = name

	local divider = Instance.new("Frame")
	divider.Name = "Divider"
	divider.Size = UDim2.fromOffset(1, 0)
	divider.BackgroundColor3 = Color3.fromHex("#aaaaaa")
	divider.BorderSizePixel = 0
	divider.LayoutOrder = 2
	divider.ZIndex = 1_000_000 + 2
	divider.Parent = background

	local dimensions = Instance.new("TextLabel")
	dimensions.Name = "Dimensions"
	dimensions.Size = UDim2.fromScale(0, 0)
	dimensions.AutomaticSize = Enum.AutomaticSize.XY
	dimensions.BackgroundTransparency = 1
	dimensions.LayoutOrder = 3
	dimensions.Font = Enum.Font.BuilderSansBold
	dimensions.TextColor3 = Color3.fromHex("#d7d7d7")
	dimensions.TextSize = 16
	dimensions.ZIndex = 1_000_000 + 2
	dimensions.Parent = background
	self.dimensionsLabel = dimensions

	background.Parent = container

	return self
end

export type OverlayTip = typeof(OverlayTip.new(...))

function OverlayTip.remove(self: OverlayTip)
	self.background:Destroy()
end

function OverlayTip.updateText(
	self: OverlayTip,
	name: string,
	width: number,
	height: number
)
	self.nameLabel.Text = name
	self.dimensionsLabel.Text = `{math.round(width)}px x {math.round(height)}px`
end

function OverlayTip.updatePosition(self: OverlayTip, dims: Box, bounds: Box)
	local position = calculatePosition(dims, bounds, self.background.AbsoluteSize)
	self.background.Position = UDim2.fromOffset(position.X, position.Y)
end

return {
	new = OverlayTip.new,
}


-- ROBLOX TODO: stub for clipboardjs, remove when we know how we'll handle its intent in a Roblox way
return {}


--!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.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Array = LuauPolyfill.Array
local Map = LuauPolyfill.Map
local Set = LuauPolyfill.Set
local console = LuauPolyfill.console
type Array<T> = LuauPolyfill.Array<T>
type Map<K, V> = LuauPolyfill.Map<K, V>
type Object = LuauPolyfill.Object
type Set<K> = LuauPolyfill.Set<K>

local EventEmitter = require(script.Parent.Parent.events)
type EventEmitter<T> = EventEmitter.EventEmitter<T>

local prepareProfilingDataFrontendFromBackendAndStore = require(
	script.Parent.views.Profiler.utils
).prepareProfilingDataFrontendFromBackendAndStore

local devtoolsTypes = require(script.Parent.types)
type ProfilingCache = devtoolsTypes.ProfilingCache
export type ProfilerStore = devtoolsTypes.ProfilerStore
type Store = devtoolsTypes.Store

local Bridge = require(script.Parent.Parent.bridge)
type FrontendBridge = Bridge.FrontendBridge

local backendTypes = require(script.Parent.Parent.backend.types)
type ProfilingDataBackend = backendTypes.ProfilingDataBackend

local profilerTypes = require(script.Parent.views.Profiler.types)
type CommitDataFrontend = profilerTypes.CommitDataFrontend
type ProfilingDataForRootFrontend = profilerTypes.ProfilingDataForRootFrontend
type ProfilingDataFrontend = profilerTypes.ProfilingDataFrontend
type SnapshotNode = profilerTypes.SnapshotNode

type ProfilerStore_statics = {
	new: (
		bridge: FrontendBridge,
		store: Store,
		defaultIsProfiling: boolean
	) -> ProfilerStore,
	__index: {},
}

local ProfilingCache = require(script.Parent.ProfilingCache)

local ProfilerStore: ProfilerStore & ProfilerStore_statics = (
	setmetatable({}, { __index = EventEmitter }) :: any
) :: ProfilerStore & ProfilerStore_statics
ProfilerStore.__index = ProfilerStore

function ProfilerStore.new(
	bridge: FrontendBridge,
	store: Store,
	defaultIsProfiling: boolean
): ProfilerStore
	local profilerStore: ProfilerStore =
		setmetatable(EventEmitter.new() :: any, ProfilerStore)
	profilerStore._dataBackends = {}
	profilerStore._dataFrontend = nil
	profilerStore._initialRendererIDs = Set.new()
	profilerStore._initialSnapshotsByRootID = Map.new()
	profilerStore._inProgressOperationsByRootID = Map.new()
	profilerStore._isProfiling = defaultIsProfiling
	profilerStore._rendererIDsThatReportedProfilingData = Set.new()
	profilerStore._rendererQueue = Set.new()
	profilerStore._bridge = bridge
	profilerStore._store = store

	function profilerStore:_takeProfilingSnapshotRecursive(
		elementID: number,
		profilingSnapshots: Map<number, SnapshotNode>
	)
		local element = self._store:getElementByID(elementID)
		if element ~= nil then
			local snapshotNode: SnapshotNode = {
				id = elementID,
				children = Array.slice(element.children, 0),
				displayName = element.displayName,
				hocDisplayNames = element.hocDisplayNames,
				key = element.key,
				type = element.type,
			}
			profilingSnapshots:set(elementID, snapshotNode)
			Array.forEach(element.children, function(childID)
				return self:_takeProfilingSnapshotRecursive(childID, profilingSnapshots)
			end)
		end
	end
	function profilerStore:onBridgeOperations(operations: Array<number>)
		-- The first two values are always rendererID and rootID
		local rendererID = operations[
			1 --[[ ROBLOX adaptation: added 1 to array index ]]
		]
		local rootID = operations[
			2 --[[ ROBLOX adaptation: added 1 to array index ]]
		]
		if self._isProfiling then
			local profilingOperations = self._inProgressOperationsByRootID:get(rootID)
			if profilingOperations == nil then
				profilingOperations = { operations }
				-- ROBLOX FIXME Luau: nil-ability always remove due to assignment if nil
				self._inProgressOperationsByRootID:set(
					rootID,
					profilingOperations :: Array<Array<number>>
				)
			else
				table.insert(profilingOperations, operations)
			end

			if not self._initialRendererIDs:has(rendererID) then
				self._initialRendererIDs:add(rendererID)
			end

			if not self._initialSnapshotsByRootID:has(rootID) then
				self._initialSnapshotsByRootID:set(rootID, Map.new())
			end
			self._rendererIDsThatReportedProfilingData:add(rendererID)
		end
	end
	function profilerStore:onBridgeProfilingData(dataBackend: ProfilingDataBackend)
		if self._isProfiling then
			-- This should never happen, but if it does- ignore previous profiling data.
			return
		end
		local rendererID = dataBackend.rendererID
		if not self._rendererQueue:has(rendererID) then
			error(
				string.format(
					'Unexpected profiling data update from renderer "%s"',
					tostring(rendererID)
				)
			)
		end
		table.insert(self._dataBackends, dataBackend)
		self._rendererQueue:delete(rendererID)
		if self._rendererQueue.size == 0 then
			self._dataFrontend = prepareProfilingDataFrontendFromBackendAndStore(
				self._dataBackends,
				self._inProgressOperationsByRootID,
				self._initialSnapshotsByRootID
			)
			Array.splice(self._dataBackends, 0)
			self:emit("isProcessingData")
		end
	end
	function profilerStore:onBridgeShutdown()
		self._bridge:removeListener("operations", self.onBridgeOperations)
		self._bridge:removeListener("profilingData", self.onBridgeProfilingData)
		self._bridge:removeListener("profilingStatus", self.onProfilingStatus)
		self._bridge:removeListener("shutdown", self.onBridgeShutdown)
	end
	function profilerStore:onProfilingStatus(isProfiling: boolean)
		if isProfiling then
			Array.splice(self._dataBackends, 0)
			self._dataFrontend = nil
			self._initialRendererIDs:clear()
			self._initialSnapshotsByRootID:clear()
			self._inProgressOperationsByRootID:clear()
			self._rendererIDsThatReportedProfilingData:clear()
			self._rendererQueue:clear()
			-- Record all renderer IDs initially too (in case of unmount)
			-- eslint-disable-next-line no-for-of-loops/no-for-of-loops
			for _, rendererID in self._store:getRootIDToRendererID() do
				if not self._initialRendererIDs:has(rendererID) then
					self._initialRendererIDs:add(rendererID)
				end
			end
			-- Record snapshot of tree at the time profiling is started.
			-- This info is required to handle cases of e.g. nodes being removed during profiling.
			for _, rootID in self._store:getRoots() do
				local profilingSnapshots = Map.new()
				self._initialSnapshotsByRootID:set(rootID, profilingSnapshots)
				self:_takeProfilingSnapshotRecursive(rootID, profilingSnapshots)
			end
		end
		if self._isProfiling ~= isProfiling then
			self._isProfiling = isProfiling -- Invalidate suspense cache if profiling data is being (re-)recorded.
			-- Note that we clear again, in case any views read from the cache while profiling.
			-- (That would have resolved a now-stale value without any profiling data.)
			self._cache:invalidate()
			self:emit("isProfiling") -- If we've just finished a profiling session, we need to fetch data stored in each renderer interface
			-- and re-assemble it on the front-end into a format (ProfilingDataFrontend) that can power the Profiler UI.
			-- During this time, DevTools UI should probably not be interactive.
			if not isProfiling then
				Array.splice(self._dataBackends, 0)
				self._rendererQueue:clear() -- Only request data from renderers that actually logged it.
				-- This avoids unnecessary bridge requests and also avoids edge case mixed renderer bugs.
				-- (e.g. when v15 and v16 are both present)
				for _, rendererID in self._rendererIDsThatReportedProfilingData do
					if not self._rendererQueue:has(rendererID) then
						self._rendererQueue:add(rendererID)
						self._bridge:send("getProfilingData", {
							rendererID = rendererID,
						})
					end
				end
				self:emit("isProcessingData")
			end
		end
	end

	bridge:addListener("operations", function(...)
		return profilerStore:onBridgeOperations(...)
	end)
	bridge:addListener("profilingData", function(...)
		return profilerStore:onBridgeProfilingData(...)
	end)
	bridge:addListener("profilingStatus", function(...)
		return profilerStore:onProfilingStatus(...)
	end)
	bridge:addListener("shutdown", function(...)
		return profilerStore:onBridgeShutdown(...)
	end)

	-- It's possible that profiling has already started (e.g. "reload and start profiling")
	-- so the frontend needs to ask the backend for its status after mounting.
	bridge:send("getProfilingStatus")
	profilerStore._cache = ProfilingCache.new(profilerStore)

	return profilerStore
end
function ProfilerStore:getCommitData(
	rootID: number,
	commitIndex: number
): CommitDataFrontend
	if self._dataFrontend ~= nil then
		local dataForRoot = self._dataFrontend.dataForRoots:get(rootID)
		if dataForRoot ~= nil then
			local commitDatum = dataForRoot.commitData[commitIndex]
			if commitDatum ~= nil then
				return commitDatum
			end
		end
	end
	error(
		string.format(
			'Could not find commit data for root "%s" and commit %s',
			tostring(rootID),
			tostring(commitIndex)
		)
	)
end
function ProfilerStore:getDataForRoot(rootID: number): ProfilingDataForRootFrontend
	if self._dataFrontend ~= nil then
		local dataForRoot = self._dataFrontend.dataForRoots:get(rootID)
		if dataForRoot ~= nil then
			return dataForRoot
		end
	end
	error(string.format('Could not find commit data for root "%s"', tostring(rootID)))
end
function ProfilerStore:didRecordCommits(): boolean
	return self._dataFrontend ~= nil and self._dataFrontend.dataForRoots.size > 0
end
function ProfilerStore:isProcessingData(): boolean
	return self._rendererQueue.size > 0 or #self._dataBackends > 0
end
function ProfilerStore:isProfiling(): boolean
	return self._isProfiling
end
function ProfilerStore:profilingCache(): ProfilingCache
	return self._cache
end
function ProfilerStore:profilingData(
	value: ProfilingDataFrontend | nil
): ...ProfilingDataFrontend?
	if value == nil then
		return self._dataFrontend
	end

	if self._isProfiling then
		console.warn("Profiling data cannot be updated while profiling is in progress.")
		return
	end
	Array.splice(self._dataBackends, 0)
	self._dataFrontend = value
	self._initialRendererIDs:clear()
	self._initialSnapshotsByRootID:clear()
	self._inProgressOperationsByRootID:clear()
	self._cache:invalidate()
	self:emit("profilingData")
	return
end
function ProfilerStore:clear(): ...any?
	Array.splice(self._dataBackends, 0)
	self._dataFrontend = nil
	self._initialRendererIDs:clear()
	self._initialSnapshotsByRootID:clear()
	self._inProgressOperationsByRootID:clear()
	self._rendererQueue:clear() -- Invalidate suspense cache if profiling data is being (re-)recorded.
	-- Note that we clear now because any existing data is "stale".
	self._cache:invalidate()
	self:emit("profilingData")
end
function ProfilerStore:startProfiling(): ...any?
	self._bridge:send("startProfiling", self._store:getRecordChangeDescriptions()) -- Don't actually update the local profiling boolean yet!
	-- Wait for onProfilingStatus() to confirm the status has changed.
	-- This ensures the frontend and backend are in sync wrt which commits were profiled.
	-- We do this to avoid mismatches on e.g. CommitTreeBuilder that would cause errors.
end
function ProfilerStore:stopProfiling(): ...any?
	self._bridge:send("stopProfiling") -- Don't actually update the local profiling boolean yet!
	-- Wait for onProfilingStatus() to confirm the status has changed.
	-- This ensures the frontend and backend are in sync wrt which commits were profiled.
	-- We do this to avoid mismatches on e.g. CommitTreeBuilder that would cause errors.
end

return ProfilerStore


--!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.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Array = LuauPolyfill.Array
local Map = LuauPolyfill.Map

type Map<K, V> = LuauPolyfill.Map<K, V>
type Array<T> = LuauPolyfill.Array<T>

local ProfilerViews = script.Parent.views.Profiler

local CommitTreeBuilderModule = require(ProfilerViews.CommitTreeBuilder)
local getCommitTree = CommitTreeBuilderModule.getCommitTree
local invalidateCommitTrees = CommitTreeBuilderModule.invalidateCommitTrees

local FlamegraphChartBuilderModule = require(ProfilerViews.FlamegraphChartBuilder)
local getFlamegraphChartData = FlamegraphChartBuilderModule.getChartData
local invalidateFlamegraphChartData = FlamegraphChartBuilderModule.invalidateChartData

local InteractionsChartBuilderModule = require(ProfilerViews.InteractionsChartBuilder)
local getInteractionsChartData = InteractionsChartBuilderModule.getChartData
local invalidateInteractionsChartData = InteractionsChartBuilderModule.invalidateChartData

local RankedChartBuilderModule = require(ProfilerViews.RankedChartBuilder)
local getRankedChartData = RankedChartBuilderModule.getChartData
local invalidateRankedChartData = RankedChartBuilderModule.invalidateChartData

local typesModule = require(ProfilerViews.types)
type CommitTree = typesModule.CommitTree

type FlamegraphChartData = FlamegraphChartBuilderModule.ChartData
type InteractionsChartData = InteractionsChartBuilderModule.ChartData
type RankedChartData = RankedChartBuilderModule.ChartData

local devtoolsTypes = require(script.Parent.types)
type ProfilingCache = devtoolsTypes.ProfilingCache
type ProfilerStore = devtoolsTypes.ProfilerStore

type ProfilingCache_statics = { new: (profilerStore: ProfilerStore) -> ProfilingCache }

local ProfilingCache = {} :: ProfilingCache & ProfilingCache_statics;
(ProfilingCache :: any).__index = ProfilingCache

function ProfilingCache.new(profilerStore: ProfilerStore): ProfilingCache
	local profilingCache: ProfilingCache = (
		setmetatable({}, ProfilingCache) :: any
	) :: ProfilingCache
	profilingCache._fiberCommits = Map.new()
	profilingCache._profilerStore = profilerStore

	function profilingCache:getCommitTree(ref: { commitIndex: number, rootID: number })
		local commitIndex, rootID = ref.commitIndex, ref.rootID
		return getCommitTree({
			commitIndex = commitIndex,
			profilerStore = self._profilerStore,
			rootID = rootID,
		})
	end
	function profilingCache:getFiberCommits(
		ref: { fiberID: number, rootID: number }
	): Array<number>
		local fiberID, rootID = ref.fiberID, ref.rootID
		local cachedFiberCommits = self._fiberCommits:get(fiberID)
		if cachedFiberCommits ~= nil then
			return cachedFiberCommits
		end
		local fiberCommits = {} :: Array<number>
		local dataForRoot = self._profilerStore:getDataForRoot(rootID)
		Array.forEach(dataForRoot.commitData, function(commitDatum, commitIndex)
			if commitDatum.fiberActualDurations:has(fiberID) then
				table.insert(fiberCommits, commitIndex)
			end
		end)
		self._fiberCommits:set(fiberID, fiberCommits)
		return fiberCommits
	end
	function profilingCache:getFlamegraphChartData(ref: {
		commitIndex: number,
		commitTree: CommitTree,
		rootID: number,
	}): FlamegraphChartData
		local commitIndex, commitTree, rootID =
			ref.commitIndex, ref.commitTree, ref.rootID
		return getFlamegraphChartData({
			commitIndex = commitIndex,
			commitTree = commitTree,
			profilerStore = self._profilerStore,
			rootID = rootID,
		})
	end
	function profilingCache:getInteractionsChartData(
		ref: { rootID: number }
	): InteractionsChartData
		local rootID = ref.rootID
		return getInteractionsChartData({
			profilerStore = self._profilerStore,
			rootID = rootID,
		})
	end
	function profilingCache:getRankedChartData(ref: {
		commitIndex: number,
		commitTree: CommitTree,
		rootID: number,
	}): RankedChartData
		local commitIndex, commitTree, rootID =
			ref.commitIndex, ref.commitTree, ref.rootID
		return getRankedChartData({
			commitIndex = commitIndex,
			commitTree = commitTree,
			profilerStore = self._profilerStore,
			rootID = rootID,
		})
	end

	return profilingCache
end
function ProfilingCache:invalidate()
	self._fiberCommits:clear()
	invalidateCommitTrees()
	invalidateFlamegraphChartData()
	invalidateInteractionsChartData()
	invalidateRankedChartData()
end

return ProfilingCache


return {
	utils = require(script.utils),
	store = require(script.store),
	cache = require(script.cache),
	devtools = {
		Components = {
			views = {
				types = require(script.views.Components.types),
			},
		},
	},
}


--!strict
local Packages = script.Parent.Parent.Parent

local LuauPolyfill = require(Packages.LuauPolyfill)
type Array<T> = LuauPolyfill.Array<T>
type Map<K, V> = LuauPolyfill.Map<K, V>
type Object = LuauPolyfill.Object
type Set<K> = LuauPolyfill.Set<K>

local ComponentsTypes = require(script.Parent.Parent.devtools.views.Components.types)
type Element = ComponentsTypes.Element

local Types = require(script.Parent.Parent.types)
type ComponentFilter = Types.ComponentFilter
type ElementType = Types.ElementType

local EventEmitter = require(script.Parent.Parent.events)
type EventEmitter<T> = EventEmitter.EventEmitter<T>

local Bridge = require(script.Parent.Parent.bridge)
type FrontendBridge = Bridge.FrontendBridge

local backendTypes = require(script.Parent.Parent.backend.types)
type ProfilingDataBackend = backendTypes.ProfilingDataBackend

local profilerTypes = require(script.Parent.views.Profiler.types)
type CommitDataFrontend = profilerTypes.CommitDataFrontend
type ProfilingDataForRootFrontend = profilerTypes.ProfilingDataForRootFrontend
type ProfilingDataFrontend = profilerTypes.ProfilingDataFrontend
type SnapshotNode = profilerTypes.SnapshotNode

export type Capabilities = { hasOwnerMetadata: boolean, supportsProfiling: boolean }

export type Store = EventEmitter<{
	collapseNodesByDefault: Array<any>,
	componentFilters: Array<any>,
	mutated: Array<any>, -- ROBLOX deviation: can't express jagged array types in Luau
	recordChangeDescriptions: Array<any>,
	roots: Array<any>,
	supportsNativeStyleEditor: Array<any>,
	supportsProfiling: Array<any>,
	supportsReloadAndProfile: Array<any>,
	unsupportedRendererVersionDetected: Array<any>,
}> & {
	_bridge: FrontendBridge,

	-- Should new nodes be collapsed by default when added to the tree?
	_collapseNodesByDefault: boolean,

	_componentFilters: Array<ComponentFilter>,

	-- At least one of the injected renderers contains (DEV only) owner metadata.
	_hasOwnerMetadata: boolean,

	-- Map of ID to (mutable) Element.
	-- Elements are mutated to avoid excessive cloning during tree updates.
	-- The InspectedElementContext also relies on this mutability for its WeakMap usage.
	_idToElement: Map<number, Element>,

	-- Should the React Native style editor panel be shown?
	_isNativeStyleEditorSupported: boolean,

	-- Can the backend use the Storage API (e.g. localStorage)?
	-- If not, features like reload-and-profile will not work correctly and must be disabled.
	_isBackendStorageAPISupported: boolean,

	_nativeStyleEditorValidAttributes: Array<string> | nil,

	-- Map of element (id) to the set of elements (ids) it owns.
	-- This map enables getOwnersListForElement() to avoid traversing the entire tree.
	_ownersMap: Map<number, Set<number>>,

	_profilerStore: ProfilerStore,

	_recordChangeDescriptions: boolean,

	-- Incremented each time the store is mutated.
	-- This enables a passive effect to detect a mutation between render and commit phase.
	_revision: number,

	-- This Array must be treated as immutable!
	-- Passive effects will check it for changes between render and mount.
	_roots: Array<number>,

	_rootIDToCapabilities: Map<number, Capabilities>,

	-- Renderer ID is needed to support inspection fiber props, state, and hooks.
	_rootIDToRendererID: Map<number, number>,

	-- These options may be initially set by a confiugraiton option when constructing the Store.
	-- In the case of "supportsProfiling", the option may be updated based on the injected renderers.
	_supportsNativeInspection: boolean,
	_supportsProfiling: boolean,
	_supportsReloadAndProfile: boolean,
	_supportsTraceUpdates: boolean,

	_unsupportedRendererVersionDetected: boolean,

	-- Total number of visible elements (within all roots).
	-- Used for windowing purposes.
	_weightAcrossRoots: number,
	assertExpectedRootMapSizes: (self: Store) -> (),
	assertMapSizeMatchesRootCount: (
		self: Store,
		map: Map<any, any>,
		mapName: string
	) -> (),
	getCollapseNodesByDefault: (self: Store) -> boolean,
	setCollapseNodesByDefault: (self: Store, boolean) -> (),
	getComponentFilters: (self: Store) -> Array<ComponentFilter>,
	setComponentFilters: (self: Store, Array<ComponentFilter>) -> (),
	getHasOwnerMetadata: (self: Store) -> boolean,
	getNativeStyleEditorValidAttributes: (self: Store) -> Array<string> | nil,
	getNumElements: (self: Store) -> number,
	getProfilerStore: (self: Store) -> ProfilerStore,
	getRecordChangeDescriptions: (self: Store) -> boolean,
	setRecordChangeDescriptions: (self: Store, value: boolean) -> (),
	getRevision: (self: Store) -> number,
	getRootIDToRendererID: (self: Store) -> Map<number, number>,
	getRoots: (self: Store) -> Array<number>,
	getSupportsNativeInspection: (self: Store) -> boolean,
	getSupportsNativeStyleEditor: (self: Store) -> boolean,
	getSupportsProfiling: (self: Store) -> boolean,
	getSupportsReloadAndProfile: (self: Store) -> boolean,
	getSupportsTraceUpdates: (self: Store) -> boolean,
	getUnsupportedRendererVersionDetected: (self: Store) -> boolean,
	containsElement: (self: Store, id: number) -> boolean,
	getElementAtIndex: (self: Store, index: number) -> Element | nil,
	getElementIDAtIndex: (self: Store, index: number) -> number | nil,
	getElementByID: (self: Store, id: number) -> Element | nil,
	getIndexOfElementID: (self: Store, id: number) -> number | nil,
	getOwnersListForElement: (self: Store, ownerID: number) -> Array<Element>,
	getRendererIDForElement: (self: Store, id: number) -> number | nil,
	getRootIDForElement: (self: Store, id: number) -> number | nil,
	isInsideCollapsedSubTree: (self: Store, id: number) -> boolean,
	toggleIsCollapsed: (self: Store, id: number, isCollapsed: boolean) -> (),
	_adjustParentTreeWeight: (
		self: Store,
		parentElement: Element | nil,
		weightDelta: number
	) -> (),
	onBridgeNativeStyleEditorSupported: (
		self: Store,
		options: {
			isSupported: boolean,
			validAttributes: Array<string>,
		}
	) -> (),
	onBridgeOperations: (self: Store, operations: Array<number>) -> (),
	onBridgeOverrideComponentFilters: (
		self: Store,
		componentFilters: Array<ComponentFilter>
	) -> (),
	onBridgeShutdown: (self: Store) -> (),
	onBridgeStorageSupported: (self: Store, isBackendStorageAPISupported: boolean) -> (),
	onBridgeUnsupportedRendererVersion: (self: Store) -> (),
}

export type ProfilingCache = {
	_fiberCommits: Map<number, Array<number>>,
	_profilerStore: ProfilerStore,
	getCommitTree: any,
	getFiberCommits: any,
	getFlamegraphChartData: any,
	getInteractionsChartData: any,
	getRankedChartData: any,
	invalidate: (self: ProfilingCache) -> (),
}

export type ProfilerStore = EventEmitter<{
	isProcessingData: any, --[[ ROBLOX TODO: Unhandled node for type: TupleTypeAnnotation ]] --[[ [] ]]
	isProfiling: any, --[[ ROBLOX TODO: Unhandled node for type: TupleTypeAnnotation ]] --[[ [] ]]
	profilingData: any, --[[ ROBLOX TODO: Unhandled node for type: TupleTypeAnnotation ]] --[[ [] ]]
}> & {
	_bridge: FrontendBridge, -- Suspense cache for lazily calculating derived profiling data.
	_cache: ProfilingCache, -- Temporary store of profiling data from the backend renderer(s).
	-- This data will be converted to the ProfilingDataFrontend format after being collected from all renderers.
	_dataBackends: Array<ProfilingDataBackend>, -- Data from the most recently completed profiling session,
	-- or data that has been imported from a previously exported session.
	-- This object contains all necessary data to drive the Profiler UI interface,
	-- even though some of it is lazily parsed/derived via the ProfilingCache.
	_dataFrontend: ProfilingDataFrontend | nil, -- Snapshot of all attached renderer IDs.
	-- Once profiling is finished, this snapshot will be used to query renderers for profiling data.
	--
	-- This map is initialized when profiling starts and updated when a new root is added while profiling;
	-- Upon completion, it is converted into the exportable ProfilingDataFrontend format.
	_initialRendererIDs: Set<number>, -- Snapshot of the state of the main Store (including all roots) when profiling started.
	-- Once profiling is finished, this snapshot can be used along with "operations" messages emitted during profiling,
	-- to reconstruct the state of each root for each commit.
	-- It's okay to use a single root to store this information because node IDs are unique across all roots.
	--
	-- This map is initialized when profiling starts and updated when a new root is added while profiling;
	-- Upon completion, it is converted into the exportable ProfilingDataFrontend format.
	_initialSnapshotsByRootID: Map<number, Map<number, SnapshotNode>>, -- Map of root (id) to a list of tree mutation that occur during profiling.
	-- Once profiling is finished, these mutations can be used, along with the initial tree snapshots,
	-- to reconstruct the state of each root for each commit.
	--
	-- This map is only updated while profiling is in progress;
	-- Upon completion, it is converted into the exportable ProfilingDataFrontend format.
	_inProgressOperationsByRootID: Map<number, Array<Array<number>>>, -- The backend is currently profiling.
	-- When profiling is in progress, operations are stored so that we can later reconstruct past commit trees.
	_isProfiling: boolean, -- Tracks whether a specific renderer logged any profiling data during the most recent session.
	_rendererIDsThatReportedProfilingData: Set<number>, -- After profiling, data is requested from each attached renderer using this queue.
	-- So long as this queue is not empty, the store is retrieving and processing profiling data from the backend.
	_rendererQueue: Set<number>,
	_store: Store,
	getCommitData: (
		self: ProfilerStore,
		rootID: number,
		commitIndex: number
	) -> CommitDataFrontend,
	getDataForRoot: (self: ProfilerStore, rootID: number) -> ProfilingDataForRootFrontend, -- Profiling data has been recorded for at least one root.
	didRecordCommits: (self: ProfilerStore) -> boolean,
	isProcessingData: (self: ProfilerStore) -> boolean,
	isProfiling: (self: ProfilerStore) -> boolean,
	profilingCache: (self: ProfilerStore) -> ProfilingCache,
	profilingData: (
		self: ProfilerStore,
		value: ProfilingDataFrontend?
	) -> ...ProfilingDataFrontend?,
	clear: (self: ProfilerStore) -> (),
	startProfiling: (self: ProfilerStore) -> (),
	stopProfiling: (self: ProfilerStore) -> (),
	_takeProfilingSnapshotRecursive: any,
	onBridgeOperations: (self: ProfilerStore, operations: Array<number>) -> (),
	onBridgeProfilingData: (self: ProfilerStore, dataBackend: ProfilingDataBackend) -> (),
	onBridgeShutdown: (self: ProfilerStore) -> (),
	onProfilingStatus: (self: ProfilerStore, isProfiling: boolean) -> (),
}

return true


--!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.Parent.Parent.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Boolean = LuauPolyfill.Boolean
local Map = LuauPolyfill.Map
local Set = LuauPolyfill.Set

type Map<K, V> = LuauPolyfill.Map<K, V>
type Array<K> = LuauPolyfill.Array<K>
type Set<T> = LuauPolyfill.Set<T>

local exports = {}

local devtoolsTypes = require(script.Parent.Parent.Parent.types)
type ProfilerStore = devtoolsTypes.ProfilerStore

local formatDuration = require(script.Parent.utils).formatDuration
local typesModule = require(script.Parent.types)
type CommitTree = typesModule.CommitTree
type CommitTreeNode = typesModule.CommitTreeNode

export type ChartNode = {
	actualDuration: number,
	didRender: boolean,
	id: number,
	label: string,
	name: string,
	offset: number,
	selfDuration: number,
	treeBaseDuration: number,
}
export type ChartData = {
	baseDuration: number,
	depth: number,
	idToDepthMap: Map<number, number>,
	maxSelfDuration: number,
	renderPathNodes: Set<number>,
	rows: Array<Array<ChartNode>>,
}
local cachedChartData: Map<string, ChartData> = Map.new()
local function getChartData(ref: {
	commitIndex: number,
	commitTree: CommitTree,
	profilerStore: ProfilerStore,
	rootID: number,
}): ChartData
	local commitIndex, commitTree, profilerStore, rootID =
		ref.commitIndex, ref.commitTree, ref.profilerStore, ref.rootID
	local commitDatum = profilerStore:getCommitData(rootID, commitIndex)
	local fiberActualDurations, fiberSelfDurations =
		commitDatum.fiberActualDurations, commitDatum.fiberSelfDurations
	local nodes = commitTree.nodes
	local chartDataKey = ("%s-%s"):format(tostring(rootID), tostring(commitIndex))
	if cachedChartData:has(chartDataKey) then
		return (cachedChartData:get(chartDataKey) :: any) :: ChartData
	end
	local idToDepthMap: Map<number, number> = Map.new()
	local renderPathNodes: Set<number> = Set.new()
	local rows: Array<Array<ChartNode>> = {}
	local maxDepth = 0
	local maxSelfDuration = 0

	-- Generate flame graph structure using tree base durations.
	local function walkTree(id: number, rightOffset: number, currentDepth: number)
		idToDepthMap:set(id, currentDepth)

		local node = nodes:get(id)
		if node == nil then
			error(
				string.format(
					'Could not find node with id "%s" in commit tree',
					tostring(id)
				)
			)
		end
		-- ROBLOX FIXME Luau: Luau doesn't understand error() narrows, needs type states
		local children, displayName, hocDisplayNames, key, treeBaseDuration =
			(node :: CommitTreeNode).children,
			(node :: CommitTreeNode).displayName,
			(node :: CommitTreeNode).hocDisplayNames,
			(node :: CommitTreeNode).key,
			(node :: CommitTreeNode).treeBaseDuration

		local actualDuration = fiberActualDurations:get(id) or 0
		local selfDuration = fiberSelfDurations:get(id) or 0
		local didRender = fiberActualDurations:has(id)

		local name = displayName or "Anonymous"
		local maybeKey = if Boolean.toJSBoolean(key)
			then (' key="%s"'):format(tostring(key))
			else ""

		local maybeBadge = ""
		if hocDisplayNames ~= nil and #hocDisplayNames > 0 then
			maybeBadge = string.format(" (%s)", tostring(hocDisplayNames[1]))
		end

		local label = string.format(
			"%s%s%s%s",
			tostring(name),
			tostring(maybeBadge),
			tostring(maybeKey),
			if didRender
				then string.format(
					" (%sms of %sms)",
					tostring(formatDuration(selfDuration)),
					tostring(formatDuration(actualDuration))
				)
				else ""
		)

		maxDepth = math.max(maxDepth, currentDepth)
		maxSelfDuration = math.max(maxSelfDuration, selfDuration)
		local chartNode: ChartNode = {
			actualDuration = actualDuration,
			didRender = didRender,
			id = id,
			label = label,
			name = name,
			offset = rightOffset - treeBaseDuration,
			selfDuration = selfDuration,
			treeBaseDuration = treeBaseDuration,
		}
		if currentDepth > #rows then
			table.insert(rows, { chartNode })
		else
			table.insert(rows[currentDepth - 1], chartNode)
		end
		do
			local i = #children
			while i >= 1 do
				local childID = children[i]
				local childChartNode = walkTree(childID, rightOffset, currentDepth)
				rightOffset -= childChartNode.treeBaseDuration
				i -= 1
			end
		end
		return chartNode
	end
	local baseDuration = 0 -- Special case to handle unmounted roots.
	if nodes.size > 0 then
		-- Skip over the root; we don't want to show it in the flamegraph.
		local root = nodes:get(rootID)
		if root == nil then
			error(
				string.format(
					'Could not find root node with id "%s" in commit tree',
					tostring(rootID)
				)
			)
		end
		-- Don't assume a single root.
		-- Component filters or Fragments might lead to multiple "roots" in a flame graph.
		do
			-- ROBLOX FIXME Luau: Luau doesn't understand error() narrows, needs type states
			local i = #(root :: CommitTreeNode).children
			while i >= 1 do
				local id = (root :: CommitTreeNode).children[i]
				local node = nodes:get(id)
				if node == nil then
					error(
						string.format(
							'Could not find node with id "%s" in commit tree',
							tostring(id)
						)
					)
				end
				-- ROBLOX FIXME Luau: Luau doesn't understand error() narrows, needs type states
				baseDuration += (node :: CommitTreeNode).treeBaseDuration
				-- ROBLOX deviation START: walkTree does table.insert(tbl, currentDepth - 1), so the parameter here needs to be a valid index with after substracting 1 at the start
				walkTree(id, baseDuration, 2)
				-- ROBLOX deviation END
				i -= 1
			end
		end
		for id, duration in fiberActualDurations do
			local node = nodes:get(id)
			if node ~= nil then
				local currentID = node.parentID
				while currentID ~= 0 do
					if renderPathNodes:has(currentID) then
						-- We've already walked this path; we can skip it.
						break
					else
						renderPathNodes:add(currentID)
					end
					node = nodes:get(currentID)
					currentID = if node ~= nil then node.parentID else 0
				end
			end
		end
	end
	local chartData = {
		baseDuration = baseDuration,
		depth = maxDepth,
		idToDepthMap = idToDepthMap,
		maxSelfDuration = maxSelfDuration,
		renderPathNodes = renderPathNodes,
		rows = rows,
	}
	cachedChartData:set(chartDataKey, chartData)
	return chartData
end
exports.getChartData = getChartData
local function invalidateChartData(): any
	return cachedChartData:clear()
end
exports.invalidateChartData = invalidateChartData
return exports


--!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.Parent.Parent.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Array = LuauPolyfill.Array
local Map = LuauPolyfill.Map

type Map<K, V> = LuauPolyfill.Map<K, V>
type Array<K> = LuauPolyfill.Array<K>

local exports = {}

local devtoolsTypes = require(script.Parent.Parent.Parent.types)
type ProfilerStore = devtoolsTypes.ProfilerStore
local typesModule = require(script.Parent.types)
type Interaction = typesModule.Interaction
export type ChartData = {
	interactions: Array<Interaction>,
	lastInteractionTime: number,
	maxCommitDuration: number,
}
local cachedChartData: Map<number, ChartData> = Map.new()
local function getChartData(
	ref: { profilerStore: ProfilerStore, rootID: number }
): ChartData
	local profilerStore, rootID = ref.profilerStore, ref.rootID
	if cachedChartData:has(rootID) then
		return (cachedChartData:get(rootID) :: any) :: ChartData
	end
	local dataForRoot = profilerStore:getDataForRoot(rootID)
	if dataForRoot == nil then
		error(
			string.format('Could not find profiling data for root "%s"', tostring(rootID))
		)
	end
	-- ROBLOX FIXME Luau: any cast necessary to work around: Property 'interactions' is not compatible. Type 'Array<Interaction> | Array<a> | Array<string>' could not be converted into 'Array<Interaction>'
	local commitData, interactions: any = dataForRoot.commitData, dataForRoot.interactions
	local lastInteractionTime = if #commitData > 0
		then commitData[#commitData].timestamp
		else 0
	local maxCommitDuration = 0
	Array.forEach(commitData, function(commitDatum)
		maxCommitDuration = math.max(maxCommitDuration, commitDatum.duration)
	end)
	local chartData = {
		interactions = Array.from(interactions:values()) :: Array<Interaction>,
		lastInteractionTime = lastInteractionTime,
		maxCommitDuration = maxCommitDuration,
	}
	cachedChartData:set(rootID, chartData)
	return chartData
end
exports.getChartData = getChartData
local function invalidateChartData(): any?
	return cachedChartData:clear()
end
exports.invalidateChartData = invalidateChartData
return exports


--!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.Parent.Parent.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Array = LuauPolyfill.Array
local Boolean = LuauPolyfill.Boolean
local Map = LuauPolyfill.Map

type Map<K, V> = LuauPolyfill.Map<K, V>
type Array<K> = LuauPolyfill.Array<K>
type Set<T> = LuauPolyfill.Set<T>

local exports = {}

local devtoolsTypes = require(script.Parent.Parent.Parent.types)
type ProfilerStore = devtoolsTypes.ProfilerStore

local typesModule = require(script.Parent.Parent.Parent.Parent.types)
local ElementTypeForwardRef = typesModule.ElementTypeForwardRef
local ElementTypeMemo = typesModule.ElementTypeMemo
local formatDuration = require(script.Parent.utils).formatDuration
local Profiler_typesModule = require(script.Parent.types)
type CommitTree = Profiler_typesModule.CommitTree
type CommitTreeNode = Profiler_typesModule.CommitTreeNode

export type ChartNode = { id: number, label: string, name: string, value: number }
export type ChartData = { maxValue: number, nodes: Array<ChartNode> }
local cachedChartData: Map<string, ChartData> = Map.new()
local function getChartData(ref: {
	commitIndex: number,
	commitTree: CommitTree,
	profilerStore: ProfilerStore,
	rootID: number,
}): ChartData
	local commitIndex, commitTree, profilerStore, rootID =
		ref.commitIndex, ref.commitTree, ref.profilerStore, ref.rootID
	local commitDatum = profilerStore:getCommitData(rootID, commitIndex)
	local fiberActualDurations: Map<number, number>, fiberSelfDurations: Map<number, number> =
		commitDatum.fiberActualDurations, commitDatum.fiberSelfDurations
	local nodes = commitTree.nodes
	local chartDataKey = ("%s-%s"):format(tostring(rootID), tostring(commitIndex))
	if cachedChartData:has(chartDataKey) then
		return (cachedChartData:get(chartDataKey) :: any) :: ChartData
	end
	local maxSelfDuration = 0
	local chartNodes: Array<ChartNode> = {}
	-- ROBLOX deviation? this is a simple Map, but could .forEach() always be generalized into genealized for-in if the loop is 'simple'?
	for id, actualDuration in fiberActualDurations do
		local node = nodes:get(id)
		if node == nil then
			error(
				string.format(
					'Could not find node with id "%s" in commit tree',
					tostring(id)
				)
			)
		end
		-- ROBLOX FIXME Luau: need to understand that error() means `node` has nil-ability stripped
		local displayName, key, parentID, type_ =
			(node :: CommitTreeNode).displayName,
			(node :: CommitTreeNode).key,
			(node :: CommitTreeNode).parentID,
			(node :: CommitTreeNode).type -- Don't show the root node in this chart.
		if parentID == 0 then
			continue
		end
		local selfDuration = fiberSelfDurations:get(id) or 0
		maxSelfDuration = math.max(maxSelfDuration, selfDuration)
		local name = displayName or "Anonymous"
		local maybeKey = if Boolean.toJSBoolean(key)
			then (' key="%s"'):format(tostring(key))
			else ""
		local maybeBadge = ""
		if type_ == ElementTypeForwardRef then
			maybeBadge = " (ForwardRef)"
		elseif type_ == ElementTypeMemo then
			maybeBadge = " (Memo)"
		end
		local label = ("%s%s%s (%sms)"):format(
			tostring(name),
			tostring(maybeBadge),
			tostring(maybeKey),
			tostring(formatDuration(selfDuration))
		)
		table.insert(
			chartNodes,
			{ id = id, label = label, name = name, value = selfDuration }
		)
	end
	local chartData = {
		maxValue = maxSelfDuration,
		nodes = Array.sort(chartNodes, function(a, b)
			return b.value - a.value
		end),
	}
	cachedChartData:set(chartDataKey, chartData)
	return chartData
end
exports.getChartData = getChartData
local function invalidateChartData(): any?
	return cachedChartData:clear()
end
exports.invalidateChartData = invalidateChartData
return exports


--!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.Parent.Parent.Parent
local LuauPolyfill = require(Packages.LuauPolyfill)
local Array = LuauPolyfill.Array
local Boolean = LuauPolyfill.Boolean
local Error = LuauPolyfill.Error
local Map = LuauPolyfill.Map
local Number = LuauPolyfill.Number

type Map<K, V> = LuauPolyfill.Map<K, V>
type Array<K> = LuauPolyfill.Array<K>

local exports = {}

local PROFILER_EXPORT_VERSION =
	require(script.Parent.Parent.Parent.Parent.constants).PROFILER_EXPORT_VERSION
local backendTypes = require(script.Parent.Parent.Parent.Parent.backend.types)
type ProfilingDataBackend = backendTypes.ProfilingDataBackend
local profilerTypes = require(script.Parent.types)
type ProfilingDataExport = profilerTypes.ProfilingDataExport
type ProfilingDataForRootExport = profilerTypes.ProfilingDataForRootExport
type ProfilingDataForRootFrontend = profilerTypes.ProfilingDataForRootFrontend
type ProfilingDataFrontend = profilerTypes.ProfilingDataFrontend
type SnapshotNode = profilerTypes.SnapshotNode

local commitGradient = {
	"var(--color-commit-gradient-0)",
	"var(--color-commit-gradient-1)",
	"var(--color-commit-gradient-2)",
	"var(--color-commit-gradient-3)",
	"var(--color-commit-gradient-4)",
	"var(--color-commit-gradient-5)",
	"var(--color-commit-gradient-6)",
	"var(--color-commit-gradient-7)",
	"var(--color-commit-gradient-8)",
	"var(--color-commit-gradient-9)",
} -- Combines info from the Store (frontend) and renderer interfaces (backend) into the format required by the Profiler UI.
-- This format can then be quickly exported (and re-imported).
local function prepareProfilingDataFrontendFromBackendAndStore(
	dataBackends: Array<
		ProfilingDataBackend
	>,
	operationsByRootID: Map<
		number,
		Array<Array<number>>
	>,
	snapshotsByRootID: Map<
		number,
		Map<number, SnapshotNode>
	>
): ProfilingDataFrontend
	local dataForRoots: Map<number, ProfilingDataForRootFrontend> = Map.new()
	for _, dataBackend in dataBackends do
		for _, ref in dataBackend.dataForRoots do
			local commitData, displayName, initialTreeBaseDurations, interactionCommits, interactions, rootID =
				ref.commitData,
				ref.displayName,
				ref.initialTreeBaseDurations,
				ref.interactionCommits,
				ref.interactions,
				ref.rootID
			local operations = operationsByRootID:get(rootID)
			if operations == nil then
				error(
					Error.new(
						string.format(
							"Could not find profiling operations for root %s",
							tostring(rootID)
						)
					)
				)
			end
			local snapshots = snapshotsByRootID:get(rootID)
			if snapshots == nil then
				error(
					Error.new(
						string.format(
							"Could not find profiling snapshots for root %s",
							tostring(rootID)
						)
					)
				)
			end

			-- Do not filter empty commits from the profiler data!
			-- We used to do this, but it was error prone (see #18798).
			-- A commit may appear to be empty (no actual durations) because of component filters,
			-- but filtering these empty commits causes interaction commit indices to be off by N.
			-- This not only corrupts the resulting data, but also potentially causes runtime errors.
			--
			-- For that matter, hiding "empty" commits might cause confusion too.
			-- A commit *did happen* even if none of the components the Profiler is showing were involved.
			local convertedCommitData = Array.map(
				commitData,
				function(commitDataBackend, commitIndex)
					return {
						changeDescriptions = if commitDataBackend.changeDescriptions
								~= nil
							then Map.new(commitDataBackend.changeDescriptions)
							else nil,
						duration = commitDataBackend.duration,
						fiberActualDurations = Map.new(
							commitDataBackend.fiberActualDurations
						),
						fiberSelfDurations = Map.new(
							commitDataBackend.fiberSelfDurations
						),
						interactionIDs = commitDataBackend.interactionIDs,
						priorityLevel = commitDataBackend.priorityLevel,
						timestamp = commitDataBackend.timestamp,
					}
				end
			)
			dataForRoots:set(rootID, {
				commitData = convertedCommitData,
				displayName = displayName,
				initialTreeBaseDurations = Map.new(initialTreeBaseDurations),
				interactionCommits = Map.new(interactionCommits),
				interactions = Map.new(interactions),
				-- ROBLOX FIXME Luau: need type states to not need manual annotation
				operations = operations :: Array<Array<number>>,
				rootID = rootID,
				-- ROBLOX FIXME Luau: need type states to not need manual annotation
				snapshots = snapshots :: Map<number, SnapshotNode>,
			})
		end
	end
	return {
		dataForRoots = dataForRoots,
		imported = false,
	}
end

-- Converts a Profiling data export into the format required by the Store.
exports.prepareProfilingDataFrontendFromBackendAndStore =
	prepareProfilingDataFrontendFromBackendAndStore
local function prepareProfilingDataFrontendFromExport(
	profilingDataExport: ProfilingDataExport
): ProfilingDataFrontend
	local version_ = profilingDataExport.version
	if version_ ~= PROFILER_EXPORT_VERSION then
		error(
			string.format('Unsupported profiler export version "%s"', tostring(version_))
		)
	end
	local dataForRoots: Map<number, ProfilingDataForRootFrontend> = Map.new()
	Array.forEach(profilingDataExport.dataForRoots, function(ref)
		local commitData, displayName, initialTreeBaseDurations, interactionCommits, interactions, operations, rootID, snapshots =
			ref.commitData,
			ref.displayName,
			ref.initialTreeBaseDurations,
			ref.interactionCommits,
			ref.interactions,
			ref.operations,
			ref.rootID,
			ref.snapshots
		dataForRoots:set(rootID, {
			commitData = Array.map(commitData, function(ref)
				local changeDescriptions, duration, fiberActualDurations, fiberSelfDurations, interactionIDs, priorityLevel, timestamp =
					ref.changeDescriptions,
					ref.duration,
					ref.fiberActualDurations,
					ref.fiberSelfDurations,
					ref.interactionIDs,
					ref.priorityLevel,
					ref.timestamp
				return {
					changeDescriptions = if changeDescriptions ~= nil
						then Map.new(changeDescriptions)
						else nil,
					duration = duration,
					fiberActualDurations = Map.new(fiberActualDurations),
					fiberSelfDurations = Map.new(fiberSelfDurations),
					interactionIDs = interactionIDs,
					priorityLevel = priorityLevel,
					timestamp = timestamp,
				}
			end),
			displayName = displayName,
			initialTreeBaseDurations = Map.new(initialTreeBaseDurations),
			interactionCommits = Map.new(interactionCommits),
			interactions = Map.new(interactions),
			operations = operations,
			rootID = rootID,
			snapshots = Map.new(snapshots),
		})
	end)
	return { dataForRoots = dataForRoots, imported = true }
end
exports.prepareProfilingDataFrontendFromExport = prepareProfilingDataFrontendFromExport -- Converts a Store Profiling data into a format that can be safely (JSON) serialized for export.
local function prepareProfilingDataExport(
	profilingDataFrontend: ProfilingDataFrontend
): ProfilingDataExport
	local dataForRoots: Array<ProfilingDataForRootExport> = {}
	profilingDataFrontend.dataForRoots:forEach(function(ref)
		local commitData, displayName, initialTreeBaseDurations, interactionCommits, interactions, operations, rootID, snapshots =
			ref.commitData,
			ref.displayName,
			ref.initialTreeBaseDurations,
			ref.interactionCommits,
			ref.interactions,
			ref.operations,
			ref.rootID,
			ref.snapshots
		table.insert(dataForRoots, {
			commitData = Array.map(commitData, function(ref)
				local changeDescriptions, duration, fiberActualDurations, fiberSelfDurations, interactionIDs, priorityLevel, timestamp =
					ref.changeDescriptions,
					ref.duration,
					ref.fiberActualDurations,
					ref.fiberSelfDurations,
					ref.interactionIDs,
					ref.priorityLevel,
					ref.timestamp
				return {
					changeDescriptions = if changeDescriptions ~= nil
						-- ROBLOX FIXME: types aren't flowing from entries through to return value of Array.from
						then Array.from(changeDescriptions:entries()) :: Array<Array<any>>
						else nil,
					duration = duration,
					fiberActualDurations = Array.from(fiberActualDurations:entries()) :: Array<Array<number>>,
					fiberSelfDurations = Array.from(fiberSelfDurations:entries()) :: Array<Array<number>>,
					interactionIDs = interactionIDs,
					priorityLevel = priorityLevel,
					timestamp = timestamp,
				}
			end),
			displayName = displayName,
			-- ROBLOX FIXME: types aren't flowing from entries through to return value of Array.from
			initialTreeBaseDurations = Array.from(initialTreeBaseDurations:entries()) :: Array<Array<number>>,
			interactionCommits = Array.from(interactionCommits:entries()) :: Array<Array<Array<number> | number>>,
			interactions = Array.from(interactions:entries()) :: Array<Array<any>>,
			operations = operations,
			rootID = rootID,
			snapshots = Array.from(snapshots:entries()) :: Array<Array<any>>,
		})
	end)
	return { version = PROFILER_EXPORT_VERSION, dataForRoots = dataForRoots }
end
exports.prepareProfilingDataExport = prepareProfilingDataExport
local function getGradientColor(value: number)
	local maxIndex = #commitGradient
	local index
	if Number.isNaN(value) then
		index = 0
	elseif not Number.isFinite(value) then
		index = maxIndex
	else
		index = math.max(0, math.min(maxIndex, value)) * maxIndex
	end
	return commitGradient[math.round(index)]
end
exports.getGradientColor = getGradientColor
local function formatDuration(duration: number)
	local ref = math.round(duration * 10) / 10
	return if Boolean.toJSBoolean(ref) then ref else "<0.1"
end
exports.formatDuration = formatDuration
local function formatPercentage(percentage: number)
	return math.round(percentage * 100)
end
exports.formatPercentage = formatPercentage
local function formatTime(timestamp: number)
	return math.round(math.round(timestamp) / 100) / 10
end
exports.formatTime = formatTime
local function scale(
	minValue: number,
	maxValue: number,
	minRange: number,
	maxRange: number
)
	return function(value: number, fallbackValue: number)
		return if maxValue - minValue == 0
			then fallbackValue
			else (value - minValue) / (maxValue - minValue) * (maxRange - minRange)
	end
end
exports.scale = scale
return exports


-- ROBLOX note: upstream doesn't have a root index.js, we may want to contribute a proper contract upstream

local Bridge = require(script.bridge)
local Types = require(script.types)
local Backend = require(script.backend)

export type BackendBridge = Bridge.BackendBridge
export type ComponentFilter = Types.ComponentFilter
export type DevtoolsHook = Backend.DevToolsHook

return {
	constants = require(script.constants),
	backend = require(script.backend),
	bridge = require(script.bridge),
	devtools = require(script.devtools),
	hydration = require(script.hydration),
	hook = require(script.hook),
	utils = require(script.utils),
}


return {
	setupFilesAfterEnv = { script.Parent.__tests__.setupTests },
	testMatch = { "**/*.(spec|test)" },
}


-- ROBLOX note: no upstream

local exports = {}

local constants = require(script.constants)
exports.REACT_TOTAL_NUM_LANES = constants.REACT_TOTAL_NUM_LANES
exports.SCHEDULING_PROFILER_VERSION = constants.SCHEDULING_PROFILER_VERSION

local types = require(script.types)
export type ScrollState = types.ScrollState
export type ErrorStackFrame = types.ErrorStackFrame
export type Milliseconds = types.Milliseconds
export type ReactLane = types.ReactLane
export type NativeEvent = types.NativeEvent
export type ReactScheduleRenderEvent = types.ReactScheduleRenderEvent
export type ReactScheduleStateUpdateEvent = types.ReactScheduleStateUpdateEvent
export type ReactScheduleForceUpdateEvent = types.ReactScheduleForceUpdateEvent
export type Phase = types.Phase
export type SuspenseEvent = types.SuspenseEvent
export type ThrownError = types.ThrownError
export type SchedulingEvent = types.SchedulingEvent
export type SchedulingEventType = types.SchedulingEventType
export type ReactMeasureType = types.ReactMeasureType
export type BatchUID = types.BatchUID
export type ReactMeasure = types.ReactMeasure
export type NetworkMeasure = types.NetworkMeasure
export type ReactComponentMeasureType = types.ReactComponentMeasureType
export type ReactComponentMeasure = types.ReactComponentMeasure
export type FlamechartStackFrame = types.FlamechartStackFrame
export type UserTimingMark = types.UserTimingMark
export type Snapshot = types.Snapshot
export type FlamechartStackLayer = types.FlamechartStackLayer
export type Flamechart = types.Flamechart
export type HorizontalScrollStateChangeCallback = types.HorizontalScrollStateChangeCallback
export type SearchRegExpStateChangeCallback = types.SearchRegExpStateChangeCallback

return table.freeze(exports)


-- NOTE: No upstream

--[[
    Defines all React globals in a type-safe manner that's scoped to each
    copy of React. For backwards compatibility, this module will load the
    initial values for each flag from _G. However, this is considered legacy
    behavior and new code should import this package and set the flags directly.

    ## Globals Reference

    General debug-related flags:
    __DEV__
    __PROFILE__
    __EXPERIMENTAL__
    __DEBUG__
    __YOLO__
    __DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__

    Devtools flags:
    __REACT_DEVTOOLS_GLOBAL_HOOK__
    __REACT_DEVTOOLS_ATTACH__
    __REACT_DEVTOOLS_COMPONENT_FILTERS__
	__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__
	__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__
    __LOCALSTORAGE__
    __SESSIONSTORAGE__

    Misc flags:
    __COMPAT_WARNINGS__
    __TESTEZ_RUNNING_TEST__
    __ROACT_17_MOCK_SCHEDULER__
    __ROACT_17_INLINE_ACT__
]]

local Packages = script.Parent.Parent
local SafeFlags = require(Packages.SafeFlags)

local FFlagReactIsolatedGlobalsEnabled =
	SafeFlags.createGetFFlag("ReactIsolatedGlobalsEnabled")()

if not FFlagReactIsolatedGlobalsEnabled then
	return _G :: ReactGlobals
end

type DevToolsHook = any
type DevToolsAttach = any
type DevToolsComponentFilters = { any }

local function loadFromGlobal<T>(globalValueKey: string, defaultValue: T): T
	local globalValue = _G[globalValueKey]
	if globalValue ~= nil then
		return globalValue
	end
	return defaultValue
end

local ReactGlobals = {
	__DEV__ = loadFromGlobal("__DEV__", false),
	__PROFILE__ = loadFromGlobal("__PROFILE__", false),
	__EXPERIMENTAL__ = loadFromGlobal("__EXPERIMENTAL__", false),
	__DEBUG__ = loadFromGlobal("__DEBUG__", false),
	__YOLO__ = loadFromGlobal("__YOLO__", false),
	__DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__ = loadFromGlobal(
		"__DISABLE_ALL_WARNINGS_EXCEPT_PROP_VALIDATION__",
		false
	),

	__REACT_DEVTOOLS_GLOBAL_HOOK__ = loadFromGlobal(
		"__REACT_DEVTOOLS_GLOBAL_HOOK__",
		nil :: DevToolsHook?
	),
	__REACT_DEVTOOLS_ATTACH__ = loadFromGlobal(
		"__REACT_DEVTOOLS_ATTACH__",
		nil :: DevToolsAttach?
	),
	__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = loadFromGlobal(
		"__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__",
		false
	),
	__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__ = loadFromGlobal(
		"__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__",
		false
	),
	__REACT_DEVTOOLS_COMPONENT_FILTERS__ = loadFromGlobal(
		"__REACT_DEVTOOLS_COMPONENT_FILTERS__",
		nil :: DevToolsComponentFilters?
	),
	__LOCALSTORAGE__ = loadFromGlobal("__LOCALSTORAGE__", nil :: any?),
	__SESSIONSTORAGE__ = loadFromGlobal("__SESSIONSTORAGE__", nil :: any?),

	__COMPAT_WARNINGS__ = loadFromGlobal("__COMPAT_WARNINGS__", false),
	__TESTEZ_RUNNING_TEST__ = loadFromGlobal("__TESTEZ_RUNNING_TEST__", false),
	__ROACT_17_MOCK_SCHEDULER__ = loadFromGlobal("__ROACT_17_MOCK_SCHEDULER__", false),
	__ROACT_17_INLINE_ACT__ = loadFromGlobal("__ROACT_17_INLINE_ACT__", false),
}

export type ReactGlobals = typeof(ReactGlobals)

return ReactGlobals


local ReactGlobals = require(script["ReactGlobals.global"])

return ReactGlobals


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
-- Targeted performance insights for Roblox Microprofiler
local Packages = script.Parent.Parent
local getComponentName = require(Packages.Shared).getComponentName
local ReactWorkTags = require(script.Parent.ReactWorkTags)

local ReactInternalTypes = require(script.Parent.ReactInternalTypes)
type Fiber = ReactInternalTypes.Fiber
type FiberRoot = ReactInternalTypes.FiberRoot

-- ReactMicroprofilerLevel Levels --
local LEVEL_ROOTS_LOG_ONLY = 1 -- Level 1: Roots for Logging only
local LEVEL_ROOTS = 5 -- Level 5: Roots + Commit time in Microprofiler
local LEVEL_FIBERS = 10 -- Level 10: Individual Fiber "units of work" in Microprofiler

local loadedFlag, ReactMicroprofilerLevel = pcall(function()
	return game:DefineFastInt("ReactMicroprofilerLevel5", 0)
end)
if not loadedFlag then
	ReactMicroprofilerLevel = 0
end

export type Marker = {
	id: string,
	startTime: number,
	endTime: number,
}

export type SamplerCallback = (Marker) -> ()
function noop(...: unknown) end

local enableRootSampling = false
local timerSamplingCallback: SamplerCallback | nil = nil

-- used to inhibit profileend() calls that no longer match the originating profilebegin(...) frame
local numActiveProfilesInFrame = 0

if ReactMicroprofilerLevel >= LEVEL_ROOTS then
	game:GetService("RunService").Heartbeat:Connect(function()
		numActiveProfilesInFrame = 0
	end)
end

local microprofiler = if ReactMicroprofilerLevel >= LEVEL_ROOTS
	then {
		profilebegin = function(...)
			debug.profilebegin(...)
			numActiveProfilesInFrame += 1
		end,
		profileend = function()
			if numActiveProfilesInFrame > 0 then
				debug.profileend()
				numActiveProfilesInFrame -= 1
			end
		end,
	}
	else {
		profilebegin = noop,
		profileend = noop,
	}

function startTimerSampling(timerSamplingCallbackFn: SamplerCallback)
	if enableRootSampling then
		warn("RobloxReactProfiling Timer Sampling already running.")
	end
	enableRootSampling = true
	timerSamplingCallback = timerSamplingCallbackFn
end

function endTimerSampling()
	enableRootSampling = false
	timerSamplingCallback = nil
end

function getFirstStringKey(t: { any: any })
	for key, _ in t do
		if type(key) == "string" then
			return key
		end
	end
	return nil
end

function startTimer(marker: Marker)
	if enableRootSampling then
		marker.startTime = os.clock()
	end
end
function endTimer(marker: Marker)
	if enableRootSampling then
		marker.endTime = os.clock()
		if timerSamplingCallback then
			timerSamplingCallback(marker)
		end
	end
end

function profileRootBeforeUnitOfWork(root: FiberRoot): Marker?
	local rootFiber = root.current
	local profileId = nil

	if rootFiber then
		if rootFiber.memoizedProps then
			-- expecting props table with single item
			profileId = getFirstStringKey(rootFiber.memoizedProps)
		end

		if
			profileId == nil
			and rootFiber.stateNode
			and rootFiber.stateNode.containerInfo
		then
			profileId = rootFiber.stateNode.containerInfo.Name
		end
	end

	-- note: investigate HostRoot vs HostPortal for this condition
	if profileId == "Folder" and rootFiber.child then
		local fiber = rootFiber.child
		local folderProfileId = nil
		if fiber.memoizedProps then
			-- expecting props table with single item
			folderProfileId = getFirstStringKey(fiber.memoizedProps)
		end

		if
			folderProfileId == nil
			and fiber.stateNode
			and fiber.stateNode.containerInfo
		then
			folderProfileId = fiber.stateNode.containerInfo.Name
		end
		if folderProfileId ~= nil then
			profileId = folderProfileId
		end
	end

	if profileId ~= nil then
		local marker = {
			id = profileId,
			startTime = 0,
			endTime = 0,
		}
		startTimer(marker)
		microprofiler.profilebegin(profileId)
		return marker
	end

	return nil
end

function profileRootAfterYielding(marker: Marker?)
	if marker then
		endTimer(marker)
		microprofiler.profileend()
	end
end

function profileUnitOfWorkBefore(unitOfWork: Fiber)
	local profileId = getComponentName(unitOfWork.type)

	if unitOfWork.key then
		profileId = tostring(unitOfWork.key) .. "=" .. (profileId or "?")
	end

	local rootName = nil
	if unitOfWork.stateNode then
		if
			unitOfWork.tag == ReactWorkTags.HostComponent
			or unitOfWork.tag == ReactWorkTags.HostText
		then
			local layerCollector =
				unitOfWork.stateNode:FindFirstAncestorWhichIsA("LayerCollector")
			if layerCollector then
				rootName = "[" .. layerCollector:GetFullName() .. "] "
			end
		end
	end

	if rootName then
		profileId = rootName .. " : " .. (profileId or "?")
	end

	if profileId ~= nil then
		microprofiler.profilebegin(profileId)
		return true
	end

	return false
end

function profileUnitOfWorkAfter(profileRunning: boolean)
	if profileRunning then
		microprofiler.profileend()
	end
end

function profileCommitBefore()
	microprofiler.profilebegin("Commit")
end
function profileCommitAfter()
	microprofiler.profileend()
end

return {
	startTimerSampling = startTimerSampling,
	endTimerSampling = endTimerSampling,
	profileRootBeforeUnitOfWork = if ReactMicroprofilerLevel >= LEVEL_ROOTS_LOG_ONLY
		then profileRootBeforeUnitOfWork
		else noop,
	profileRootAfterYielding = if ReactMicroprofilerLevel >= LEVEL_ROOTS_LOG_ONLY
		then profileRootAfterYielding
		else noop,
	profileUnitOfWorkBefore = if ReactMicroprofilerLevel >= LEVEL_FIBERS
		then profileUnitOfWorkBefore
		else noop,
	profileUnitOfWorkAfter = if ReactMicroprofilerLevel >= LEVEL_FIBERS
		then profileUnitOfWorkAfter
		else noop,
	profileCommitBefore = if ReactMicroprofilerLevel >= LEVEL_ROOTS
		then profileCommitBefore
		else noop,
	profileCommitAfter = if ReactMicroprofilerLevel >= LEVEL_ROOTS
		then profileCommitAfter
		else noop,
}


--!strict
-- ROBLOX deviation: Initializes the reconciler with this package's host
-- config and returns the resulting module

local Packages = script.Parent.Parent
local initializeReconciler = require(Packages.ReactReconciler)

local ReactRobloxHostConfig = require(script.Parent.client.ReactRobloxHostConfig)

return initializeReconciler(ReactRobloxHostConfig)


--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]
local CollectionService = game:GetService("CollectionService")
local Packages = script.Parent.Parent.Parent.Parent
local ReactGlobals = require(Packages.ReactGlobals)
local LuauPolyfill = require(Packages.LuauPolyfill)
local Object = LuauPolyfill.Object
local inspect = LuauPolyfill.util.inspect

local __DEV__ = ReactGlobals.__DEV__ :: boolean

local console = require(Packages.Shared).console

local React = require(Packages.React)
local ReactSymbols = require(Packages.Shared).ReactSymbols
local SingleEventManager = require(script.Parent.SingleEventManager)
type EventManager = SingleEventManager.EventManager
local Type = require(Packages.Shared).Type
local getDefaultInstanceProperty = require(script.Parent.getDefaultInstanceProperty)
local ReactRobloxHostTypes = require(script.Parent.Parent["ReactRobloxHostTypes.roblox"])
type HostInstance = ReactRobloxHostTypes.HostInstance
local Tag = require(Packages.React).Tag

-- ROBLOX deviation: Essentially a placeholder for dom-specific logic, taking the place
-- of ReactDOMComponent. Most of the logic will differ pretty dramatically

type Array<T> = { [number]: T }
type Object = { [any]: any }

-- deviation: Can't assign attributes to Roblox instances, so we use maps to
-- store associated data for host instance features like binding and event
-- management
-- ROBLOX FIXME: Stronger typing for EventManager

local instanceToEventManager: { [HostInstance]: EventManager } = {}
local instanceToBindings: { [HostInstance]: { [string]: any } } = {}

local applyPropsError = [[
Error applying initial props to Roblox Instance '%s' (%s):
  %s
]]

local updatePropsError = [[
Error updating props on Roblox Instance '%s' (%s):
  %s
]]

local updateBindingError = [[
Error updating binding or ref assigned to key %s of '%s' (%s).

Updated value:
  %s

Error:
  %s

%s
]]

local function identity(...)
	return ...
end

local function setRobloxInstanceProperty(hostInstance, key, newValue): ()
	if newValue == nil then
		local success, _ = pcall(hostInstance.ResetPropertyToDefault, hostInstance, key)
		if success then
			return
		end

		local hostClass = hostInstance.ClassName
		local _, defaultValue = getDefaultInstanceProperty(hostClass, key)
		newValue = defaultValue
	end

	-- Assign the new value to the object
	hostInstance[key] = newValue
end

local function removeBinding(hostInstance, key)
	local bindings = instanceToBindings[hostInstance]
	if bindings ~= nil then
		local disconnect = bindings[key]
		disconnect()
		bindings[key] = nil
	end
end

local function attachBinding(hostInstance, key, newBinding): ()
	local function updateBoundProperty(newValue)
		local success, errorMessage =
			xpcall(setRobloxInstanceProperty, identity, hostInstance, key, newValue)

		if not success then
			local source = newBinding._source or "<enable DEV mode for stack>"
			local fullMessage = string.format(
				updateBindingError,
				key,
				hostInstance.Name,
				hostInstance.ClassName,
				tostring(newValue),
				errorMessage,
				source
			)
			console.error(fullMessage)
			-- FIXME: Until console.error can be instrumented to send telemetry, we
			-- need to keep the hard error here
			error(fullMessage, 0)
		end
	end

	if instanceToBindings[hostInstance] == nil then
		instanceToBindings[hostInstance] = {}
	end

	instanceToBindings[hostInstance][key] =
		React.__subscribeToBinding(newBinding, updateBoundProperty)

	updateBoundProperty(newBinding:getValue())
end

local function applyTags(hostInstance: Instance, oldTags: string?, newTags: string?)
	if __DEV__ then
		if newTags ~= nil and typeof(newTags) ~= "string" then
			console.error(
				"Type provided for ReactRoblox.Tag is invalid - tags should be "
					.. "specified as a single string, with individual tags delimited "
					.. "by spaces. Instead received:\n%s",
				inspect(newTags)
			)
			return
		end
	end

	local oldTagSet = {}
	for str in string.gmatch(oldTags or "", "%S+") do
		oldTagSet[str] = true
	end
	local newTagSet = {}
	for str in string.gmatch(newTags or "", "%S+") do
		newTagSet[str] = true
	end

	for tag, _ in oldTagSet do
		if not newTagSet[tag] then
			CollectionService:RemoveTag(hostInstance, tag)
		end
	end
	for tag, _ in newTagSet do
		if not oldTagSet[tag] then
			CollectionService:AddTag(hostInstance, tag)
		end
	end
end

local function removeAllTags(hostInstance: Instance)
	for _, tag in CollectionService:GetTags(hostInstance) do
		CollectionService:RemoveTag(hostInstance, tag)
	end
end

local function applyProp(hostInstance: Instance, key, newValue, oldValue): ()
	-- ROBLOX performance: gets checked in applyProps so we can assume the key is valid
	-- if key == "ref" or key == "children" then
	--   return
	-- end

	local internalKeyType = Type.of(key)

	if internalKeyType == Type.HostEvent or internalKeyType == Type.HostChangeEvent then
		local eventManager = instanceToEventManager[hostInstance]
		if eventManager == nil then
			eventManager = (SingleEventManager.new(hostInstance) :: any) :: EventManager
			instanceToEventManager[hostInstance] = eventManager
		end

		local eventName = key.name

		if internalKeyType == Type.HostChangeEvent then
			eventManager:connectPropertyChange(eventName, newValue)
		else
			eventManager:connectEvent(eventName, newValue)
		end

		return
	end

	-- Handle bindings
	local newIsBinding = typeof(newValue) == "table"
		and newValue["$$typeof"] == ReactSymbols.REACT_BINDING_TYPE
	local oldIsBinding = oldValue ~= nil
		and typeof(oldValue) == "table"
		and oldValue["$$typeof"] == ReactSymbols.REACT_BINDING_TYPE
	if oldIsBinding then
		removeBinding(hostInstance, key)
	end

	if newIsBinding then
		attachBinding(hostInstance, key, newValue)
	elseif key == Tag then
		applyTags(hostInstance, oldValue, newValue)
	else
		setRobloxInstanceProperty(hostInstance, key, newValue)
	end
end

local function applyProps(hostInstance: Instance, props: Object): ()
	for propKey, value in props do
		-- ROBLOX performance: avoid the function call by inlining check here
		if propKey == "ref" or propKey == "children" then
			continue
		end

		applyProp(hostInstance, propKey, value)
	end
end

local function setInitialProperties(
	domElement: HostInstance,
	_tag: string,
	rawProps: Object,
	_rootContainerElement: HostInstance
): ()
	-- deviation: Use Roact's prop application logic
	local success, errorMessage = xpcall(applyProps, identity, domElement, rawProps)
	-- ROBLOX deviation: Roblox renderer doesn't currently track where instances
	-- were created the way that legacy Roact did, but DEV mode should include
	-- component stack traces as a separate warning
	if not success then
		local fullMessage = string.format(
			applyPropsError,
			domElement.Name,
			domElement.ClassName,
			tostring(errorMessage)
		)
		console.error(fullMessage)
		-- FIXME: Until console.error can be instrumented to send telemetry, we need
		-- to keep the hard error here
		error(fullMessage, 0)
	end

	if instanceToEventManager[domElement] ~= nil then
		instanceToEventManager[domElement]:resume()
	end
end

local function safelyApplyProperties(
	domElement: HostInstance,
	updatePayload: Array<any>,
	lastProps: Object
): ()
	local updatePayloadCount = #updatePayload
	for i = 1, updatePayloadCount, 2 do
		local propKey = updatePayload[i]
		local value = updatePayload[i + 1]
		if value == Object.None then
			value = nil
		end
		-- ROBLOX performance: avoid the function call by inlining check here
		if propKey ~= "ref" and propKey ~= "children" then
			applyProp(domElement, propKey, value, lastProps[propKey])
		end
	end
end

local function updateProperties(
	domElement: HostInstance,
	updatePayload: Array<any>,
	lastProps: Object
): ()
	-- deviation: Use Roact's prop application logic
	if instanceToEventManager[domElement] ~= nil then
		instanceToEventManager[domElement]:suspend()
	end

	local success, errorMessage =
		xpcall(safelyApplyProperties, identity, domElement, updatePayload, lastProps)

	if not success then
		-- ROBLOX deviation: Roblox renderer doesn't currently track where instances
		-- were created the way that legacy Roact did, but DEV mode should include
		-- component stack traces as a separate warning
		local fullMessage = string.format(
			updatePropsError,
			domElement.Name,
			domElement.ClassName,
			errorMessage
		)
		console.error(fullMessage)
		-- FIXME: Until console.error can be instrumented to send telemetry, we need
		-- to keep the hard error here
		error(fullMessage, 0)
	end

	if instanceToEventManager[domElement] ~= nil then
		instanceToEventManager[domElement]:resume()
	end
end

local _, FFlagReactFixBindingMemoryLeak = xpcall(function()
	return game:DefineFastFlag("ReactFixBindingMemoryLeak", false)
end, function()
	return true
end)

local function cleanupBindings(domElement: HostInstance)
	local instanceBindings = instanceToBindings[domElement]
	if instanceBindings ~= nil then
		for _, disconnectBinding in instanceBindings do
			disconnectBinding()
		end

		instanceToBindings[domElement] = nil
	end
end

-- ROBLOX deviation: Clear out references to components when they unmount so we
-- avoid leaking memory when they're removed
local function cleanupHostComponent(domElement: HostInstance)
	-- We do not need to manually disconnect the events since the element is being destroyed.
	if instanceToEventManager[domElement] ~= nil then
		instanceToEventManager[domElement] = nil
	end

	if FFlagReactFixBindingMemoryLeak then
		cleanupBindings(domElement)
	else
		if instanceToBindings[domElement] ~= nil then
			instanceToBindings[domElement] = nil
		end
	end

	-- ROBLOX https://jira.rbx.com/browse/LUAFDN-718: Tables are somehow ending up
	-- in this function that expects Instances. In that case, we won't be able to
	-- iterate through its descendants.
	if typeof(domElement :: any) ~= "Instance" then
		return
	end

	removeAllTags(domElement)
	for _, descElement in domElement:GetDescendants() do
		if instanceToEventManager[descElement] ~= nil then
			instanceToEventManager[descElement] = nil
		end

		if FFlagReactFixBindingMemoryLeak then
			cleanupBindings(descElement)
		else
			if instanceToBindings[descElement] ~= nil then
				instanceToBindings[descElement] = nil
			end
		end
		removeAllTags(domElement)
	end
end

return {
	setInitialProperties = setInitialProperties,
	updateProperties = updateProperties,
	cleanupHostComponent = cleanupHostComponent,

	-- ROBLOX deviation: expose maps to test for Instance cleanups
	_instanceToEventManager = instanceToEventManager,
	_instanceToBindings = instanceToBindings,
}


--!strict
--[[
	* Copyright (c) Roblox Corporation. All rights reserved.
	* Licensed under the MIT License (the "License");
	* you may not use this file except in compliance with the License.
	* You may obtain a copy of the License at
	*
	*     https://opensource.org/licenses/MIT
	*
	* Unless required by applicable law or agreed to in writing, software
	* distributed under the License is distributed on an "AS IS" BASIS,
	* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	* See the License for the specific language governing permissions and
	* limitations under the License.
]]

-- Defers remaining execution until after deferred lua events have run
return function()
	task.defer(coroutine.running())
	coroutine.yield()
end


local Packages = script.Parent.Parent

local LuauPolyfill = require(Packages.LuauPolyfill)
local Object = LuauPolyfill.Object

local reportCounter = require(script.Parent.reportCounter)
local customFields = require(script.Parent.customFields)

local ReactTelemetry = {}
ReactTelemetry.customFields = customFields

local function reportNewDevtoolsConnection()
	reportCounter({
		eventName = "react_new_devtools_connection",
		lastUpdated = { 2025, 8, 28 },
		description = "A new connection to React Devtools",
		links = "https://roblox.atlassian.net/wiki/spaces/luauee/pages/3836510338/DevTools+Telemetry",
		customFields = customFields,
	})
end
ReactTelemetry.reportNewDevtoolsConnection = reportNewDevtoolsConnection

local function reportFailedDevtoolsConnection(
	type: "create_client_failed" | "socket_closed"
)
	reportCounter({
		eventName = "react_failed_devtools_connection",
		lastUpdated = { 2025, 8, 28 },
		description = "A failed connection to React Devtools",
		links = "https://roblox.atlassian.net/wiki/spaces/luauee/pages/3836510338/DevTools+Telemetry",
		customFields = Object.assign({}, customFields, {
			error_type = type,
		}),
	})
end
ReactTelemetry.reportFailedDevtoolsConnection = reportFailedDevtoolsConnection

return ReactTelemetry


export type Context = "universal_app" | "in_experience" | "plugin" | "unknown"
export type CustomFields = {
	context: Context,
	plugin_name: string?,
}

local plugin = script:FindFirstAncestorWhichIsA("Plugin")

local customFields: CustomFields = {
	context = if plugin then "plugin" else "unknown",
	plugin_name = if plugin then plugin.Name else nil,
}

return customFields


local customFields = require(script.customFields)
export type CustomFields = customFields.CustomFields

return require(script.ReactTelemetry)


local TelemetryService = game:GetService("TelemetryService")

local Packages = script.Parent.Parent
local SafeFlags = require(Packages.SafeFlags)
local ReactGlobals = require(Packages.ReactGlobals)

local FFlagReactTelemetryEnabled =
	SafeFlags.createGetFFlag("ReactTelemetryEnabled", false)()

export type CounterOptions = {
	eventName: string,
	lastUpdated: { number },
	description: string,
	links: string,
	customFields: { [string]: any }?,
	value: number?,
}

local function reportCounter(options: CounterOptions)
	if not FFlagReactTelemetryEnabled then
		return
	end

	local config = {
		eventName = options.eventName,
		backends = { "RobloxTelemetryCounter" },
		lastUpdated = options.lastUpdated,
		description = options.description,
		links = options.links,
	}

	local data = {
		customFields = options.customFields or {},
	}

	local success, result =
		pcall(TelemetryService.LogCounter, TelemetryService, config, data, options.value)

	if ReactGlobals.__DEV__ and not success then
		warn(`React telemetry collection is enabled but log failed: {result}`)
	end
end

return reportCounter