local React = require("@pkg/@jsdotlua/react")

local Checkbox = require("../Components/Checkbox")
local createStory = require("./Helpers/createStory")

local function StoryItem(props: {
	LayoutOrder: number,
	Value: boolean?,
	Label: string,
})
	return React.createElement("Frame", {
		Size = UDim2.new(0, 200, 0, 50),
		BackgroundTransparency = 1,
		LayoutOrder = props.LayoutOrder,
	}, {
		Layout = React.createElement("UIListLayout", {
			SortOrder = Enum.SortOrder.LayoutOrder,
			Padding = UDim.new(0, 2),
			VerticalAlignment = Enum.VerticalAlignment.Center,
		}),
		Enabled = React.createElement(Checkbox, {
			Label = props.Label,
			Value = props.Value,
			OnChanged = function() end,
			LayoutOrder = 1,
		}),
		Disabled = React.createElement(Checkbox, {
			Label = `{props.Label} (Disabled)`,
			Value = props.Value,
			OnChanged = function() end,
			Disabled = true,
			LayoutOrder = 2,
		}),
	})
end

local function Story()
	local value, setValue = React.useState(true)

	return React.createElement(React.Fragment, {}, {
		Interactive = React.createElement(Checkbox, {
			Size = UDim2.fromOffset(200, 20),
			Label = "Interactive (try me)",
			Value = value,
			OnChanged = function()
				setValue(not value)
			end,
			LayoutOrder = 1,
		}),

		True = React.createElement(StoryItem, {
			Label = "True",
			Value = true,
			LayoutOrder = 2,
		}),

		False = React.createElement(StoryItem, {
			Label = "False",
			Value = false,
			LayoutOrder = 3,
		}),

		Indeterminate = React.createElement(StoryItem, {
			Label = "Indeterminate",
			Value = nil,
			LayoutOrder = 4,
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local ColorPicker = require("../Components/ColorPicker")
local createStory = require("./Helpers/createStory")

local function StoryItem(props: { Disabled: boolean? })
	local color, setColor = React.useState(Color3.fromRGB(255, 255, 0))

	return React.createElement(ColorPicker, {
		Color = color,
		OnChanged = setColor,
		Disabled = props.Disabled,
	})
end

local function Story()
	return React.createElement(React.Fragment, {}, {
		Enabled = React.createElement(StoryItem),
		Disabled = React.createElement(StoryItem, { Disabled = true }),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local DatePicker = require("../Components/DatePicker")
local Label = require("../Components/Label")
local createStory = require("./Helpers/createStory")

local function Story()
	local date, setDate = React.useState(DateTime.now())

	return React.createElement(React.Fragment, {}, {
		Picker = React.createElement(DatePicker, {
			Date = date,
			OnChanged = setDate,
			LayoutOrder = 1,
		}),
		Display = React.createElement(Label, {
			LayoutOrder = 2,
			Size = UDim2.new(1, 0, 0, 20),
			Text = `Selected: {date:FormatUniversalTime("LL", "en-us")}`,
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local Checkbox = require("../Components/Checkbox")
local DropShadowFrame = require("../Components/DropShadowFrame")
local Label = require("../Components/Label")

local createStory = require("./Helpers/createStory")

local function Story()
	local boxValue, setBoxValue = React.useState(false)

	return React.createElement(DropShadowFrame, {
		Size = UDim2.fromOffset(175, 75),
	}, {
		Layout = React.createElement("UIListLayout", {
			SortOrder = Enum.SortOrder.LayoutOrder,
			Padding = UDim.new(0, 10),
		}),
		Padding = React.createElement("UIPadding", {
			PaddingLeft = UDim.new(0, 10),
			PaddingRight = UDim.new(0, 10),
			PaddingTop = UDim.new(0, 10),
			PaddingBottom = UDim.new(0, 10),
		}),
		Label = React.createElement(Label, {
			LayoutOrder = 1,
			Text = "Example label",
			Size = UDim2.new(1, 0, 0, 16),
			TextXAlignment = Enum.TextXAlignment.Left,
		}),
		Checkbox = React.createElement(Checkbox, {
			LayoutOrder = 2,
			Value = boxValue,
			OnChanged = function()
				setBoxValue(not boxValue)
			end,
			Label = "Example checkbox",
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local Constants = require("../Constants")
local Dropdown = require("../Components/Dropdown")

local createStory = require("./Helpers/createStory")
local useTheme = require("../Hooks/useTheme")

local classNames = {
	"Part",
	"Script",
	"Player",
	"Folder",
	"Tool",
	"SpawnLocation",
	"MeshPart",
	"Model",
	"ClickDetector",
	"Decal",
	"ProximityPrompt",
	"SurfaceAppearance",
	"Texture",
	"Animation",
	"Accessory",
	"Humanoid",
}

-- hack to get themed class images
local function getClassImage(className: string, theme: StudioTheme)
	return `rbxasset://studio_svg_textures/Shared/InsertableObjects/{theme}/Standard/{className}.png`
end

local function StoryItem(props: {
	LayoutOrder: number,
	Disabled: boolean?,
})
	local theme = useTheme()

	local selectedClassName: string?, setSelectedClassName = React.useState(nil :: string?)
	local classes = {}
	for i, className in classNames do
		classes[i] = {
			Id = className,
			Text = className,
			Icon = {
				Image = getClassImage(className, theme),
				Size = Vector2.one * 16,
			},
		}
	end

	return React.createElement(Dropdown, {
		Size = UDim2.fromOffset(200, Constants.DefaultDropdownHeight),
		BackgroundTransparency = 1,
		LayoutOrder = props.LayoutOrder,
		Items = classes,
		SelectedItem = selectedClassName,
		OnItemSelected = function(newName: string?)
			setSelectedClassName(newName)
		end,
		DefaultText = "Select a Class...",
		MaxVisibleRows = 8,
		RowHeight = 24,
		ClearButton = true,
		Disabled = props.Disabled,
	})
end

local function Story()
	return React.createElement(React.Fragment, {}, {
		Enabled = React.createElement(StoryItem, {
			LayoutOrder = 1,
		}),
		Disabled = React.createElement(StoryItem, {
			LayoutOrder = 2,
			Disabled = true,
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")
local ReactRoblox = require("@pkg/@jsdotlua/react-roblox")

local PluginProvider = require("../../Components/PluginProvider")
local ScrollFrame = require("../../Components/ScrollFrame")
local ThemeContext = require("../../Contexts/ThemeContext")
local getStoryPlugin = require("./getStoryPlugin")

local themes = settings().Studio:GetAvailableThemes()
themes[1], themes[2] = themes[2], themes[1]

local function StoryTheme(props: {
	Theme: StudioTheme,
	Size: UDim2,
	LayoutOrder: number,
} & {
	children: React.ReactNode,
})
	return React.createElement("Frame", {
		Size = props.Size,
		LayoutOrder = props.LayoutOrder,
		BackgroundColor3 = props.Theme:GetColor(Enum.StudioStyleGuideColor.MainBackground),
		BorderSizePixel = 0,
	}, {
		Provider = React.createElement(ThemeContext.Provider, {
			value = props.Theme,
		}, {
			Inner = React.createElement(ScrollFrame, {
				Layout = {
					ClassName = "UIListLayout",
					SortOrder = Enum.SortOrder.LayoutOrder,
					HorizontalAlignment = Enum.HorizontalAlignment.Center,
					VerticalAlignment = Enum.VerticalAlignment.Center,
					Padding = UDim.new(0, 10),
				},
				PaddingLeft = UDim.new(0, 10),
				PaddingRight = UDim.new(0, 10),
				PaddingTop = UDim.new(0, 10),
				PaddingBottom = UDim.new(0, 10),
			}, props.children),
		}),
	})
end

local function createStory(component: React.FC<any>)
	return function(target: Frame)
		local items: { React.ReactNode } = {}
		local order = 0
		local function getOrder()
			order += 1
			return order
		end
		for i, theme in themes do
			local widthOffset = if #themes > 2 and i == #themes then -1 else 0
			if i == 1 and #themes > 1 then
				widthOffset -= 1
			end
			table.insert(
				items,
				React.createElement(StoryTheme, {
					Theme = theme,
					Size = UDim2.new(1 / #themes, widthOffset, 1, 0),
					LayoutOrder = getOrder(),
				}, React.createElement(component))
			)
			-- invisible divider to prevent scrollframe edges overlapping as
			-- they have default borders (outside, not inset)
			if i < #themes then
				table.insert(
					items,
					React.createElement("Frame", {
						Size = UDim2.new(0, 2, 1, 0),
						BackgroundTransparency = 1,
						LayoutOrder = getOrder(),
					})
				)
			end
		end

		local element = React.createElement(PluginProvider, {
			Plugin = getStoryPlugin(),
		}, {
			Layout = React.createElement("UIListLayout", {
				SortOrder = Enum.SortOrder.LayoutOrder,
				FillDirection = Enum.FillDirection.Horizontal,
			}),
			Padding = React.createElement("UIPadding", {
				PaddingBottom = UDim.new(0, 45), -- tray buttons
			}),
		}, items)

		local root = ReactRoblox.createRoot(Instance.new("Folder"))
		local portal = ReactRoblox.createPortal(element, target)
		root:render(portal)
		return function()
			root:unmount()
		end
	end
end

return createStory


--!nocheck
--!nolint UnknownGlobal

-- selene: allow(undefined_variable)
local plugin = PluginManager():CreatePlugin()

return function()
	return plugin
end


--!nocheck

local React = require("@pkg/@jsdotlua/react")
local ReactRoblox = require("@pkg/@jsdotlua/react-roblox")

return {
	name = "StudioComponents",
	storyRoots = {
		script.Parent.Parent,
	},
	packages = {
		React = React,
		ReactRoblox = ReactRoblox,
	},
}


local React = require("@pkg/@jsdotlua/react")

local Label = require("../Components/Label")
local createStory = require("./Helpers/createStory")

local styles = {
	Enum.StudioStyleGuideColor.MainText,
	Enum.StudioStyleGuideColor.SubText,
	Enum.StudioStyleGuideColor.TitlebarText,
	Enum.StudioStyleGuideColor.BrightText,
	Enum.StudioStyleGuideColor.DimmedText,
	Enum.StudioStyleGuideColor.ButtonText,
	Enum.StudioStyleGuideColor.LinkText,
	Enum.StudioStyleGuideColor.WarningText,
	Enum.StudioStyleGuideColor.ErrorText,
	Enum.StudioStyleGuideColor.InfoText,
}

local function StoryItem(props: {
	TextColorStyle: Enum.StudioStyleGuideColor,
	LayoutOrder: number,
})
	return React.createElement("Frame", {
		Size = UDim2.new(0, 170, 0, 40),
		LayoutOrder = props.LayoutOrder,
		BackgroundTransparency = 1,
	}, {
		Enabled = React.createElement(Label, {
			Text = props.TextColorStyle.Name,
			TextColorStyle = props.TextColorStyle,
			TextXAlignment = Enum.TextXAlignment.Center,
			Size = UDim2.new(1, 0, 0, 20),
		}),
		Disabled = React.createElement(Label, {
			Text = `{props.TextColorStyle.Name} (Disabled)`,
			Size = UDim2.new(1, 0, 0, 20),
			Position = UDim2.fromOffset(0, 20),
			TextColorStyle = props.TextColorStyle,
			TextXAlignment = Enum.TextXAlignment.Center,
			Disabled = true,
		}),
	})
end

local function Story()
	local items = {}
	for i, style in styles do
		items[i] = React.createElement(StoryItem, {
			TextColorStyle = style,
			LayoutOrder = i,
		})
	end
	return React.createElement(React.Fragment, {}, items)
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local LoadingDots = require("../Components/LoadingDots")
local createStory = require("./Helpers/createStory")

local function Story()
	return React.createElement(LoadingDots, {})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local MainButton = require("../Components/MainButton")
local createStory = require("./Helpers/createStory")

local function StoryButton(props: {
	Text: string?,
	HasIcon: boolean?,
	Disabled: boolean?,
})
	return React.createElement(MainButton, {
		LayoutOrder = if props.Disabled then 2 else 1,
		Icon = if props.HasIcon
			then {
				Image = "rbxasset://studio_svg_textures/Shared/InsertableObjects/Dark/Standard/Part.png",
				Size = Vector2.one * 16,
				UseThemeColor = true,
				Alignment = Enum.HorizontalAlignment.Left,
			}
			else nil,
		Text = props.Text,
		OnActivated = if not props.Disabled then function() end else nil,
		Disabled = props.Disabled,
		AutomaticSize = Enum.AutomaticSize.XY,
	})
end

local function StoryItem(props: {
	LayoutOrder: number,
	Text: string?,
	HasIcon: boolean?,
	Disabled: boolean?,
})
	local height, setHeight = React.useBinding(0)

	return React.createElement("Frame", {
		Size = height:map(function(value)
			return UDim2.new(1, 0, 0, value)
		end),
		LayoutOrder = props.LayoutOrder,
		BackgroundTransparency = 1,
	}, {
		Layout = React.createElement("UIListLayout", {
			Padding = UDim.new(0, 10),
			SortOrder = Enum.SortOrder.LayoutOrder,
			FillDirection = Enum.FillDirection.Horizontal,
			HorizontalAlignment = Enum.HorizontalAlignment.Center,
			[React.Change.AbsoluteContentSize] = function(rbx)
				setHeight(rbx.AbsoluteContentSize.Y)
			end,
		}),
		Enabled = React.createElement(StoryButton, {
			Text = props.Text,
			HasIcon = props.HasIcon,
		}),
		-- Disabled = React.createElement(StoryButton, {
		-- 	Text = props.Text,
		-- 	HasIcon = props.HasIcon,
		-- 	Disabled = true,
		-- }),
	})
end

local function Story()
	return React.createElement(React.Fragment, {}, {
		Icon = React.createElement(StoryItem, {
			LayoutOrder = 1,
			HasIcon = true,
		}),
		Text = React.createElement(StoryItem, {
			LayoutOrder = 2,
			Text = "Example Text",
		}),
		TextLonger = React.createElement(StoryItem, {
			LayoutOrder = 3,
			Text = "Example Longer Text",
		}),
		TextMulti = React.createElement(StoryItem, {
			LayoutOrder = 4,
			Text = "Example Text\nover two lines",
		}),
		IconTextIcon = React.createElement(StoryItem, {
			LayoutOrder = 5,
			HasIcon = true,
			Text = "Example Text with Icon",
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local NumberSequencePicker = require("../Components/NumberSequencePicker")
local createStory = require("./Helpers/createStory")

local function Story()
	local value, setValue = React.useState(NumberSequence.new({
		NumberSequenceKeypoint.new(0.0, 0.00),
		NumberSequenceKeypoint.new(0.4, 0.75, 0.10),
		NumberSequenceKeypoint.new(0.5, 0.45, 0.15),
		NumberSequenceKeypoint.new(0.8, 0.75),
		NumberSequenceKeypoint.new(1.0, 0.50),
	}))

	return React.createElement("Frame", {
		BackgroundTransparency = 1,
		Size = UDim2.fromScale(1, 1),
	}, {
		Picker = React.createElement(NumberSequencePicker, {
			Value = value,
			OnChanged = setValue,
			AnchorPoint = Vector2.new(0.5, 0.5),
			Position = UDim2.fromScale(0.5, 0.5),
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local Constants = require("../Constants")
local NumericInput = require("../Components/NumericInput")
local createStory = require("./Helpers/createStory")

local function StoryItem(props: {
	LayoutOrder: number,
	Arrows: boolean?,
	Slider: boolean?,
})
	local value, setValue = React.useState(5)

	local min = 0
	local max = 10
	local step = 0.25

	local function format(n: number)
		return string.format("%.2f", n)
	end

	return React.createElement("Frame", {
		LayoutOrder = props.LayoutOrder,
		Size = UDim2.new(0, 150, 0, Constants.DefaultInputHeight * 2 + 10),
		BackgroundTransparency = 1,
	}, {
		Enabled = React.createElement(NumericInput, {
			LayoutOrder = 1,
			Size = UDim2.new(1, 0, 0, Constants.DefaultInputHeight),
			Value = value,
			Min = min,
			Max = max,
			Step = step,
			ClearTextOnFocus = false,
			OnValidChanged = setValue,
			FormatValue = format,
			Arrows = props.Arrows,
			Slider = props.Slider,
		}),
		Disabled = React.createElement(NumericInput, {
			LayoutOrder = 3,
			Size = UDim2.new(1, 0, 0, Constants.DefaultInputHeight),
			Position = UDim2.fromOffset(0, Constants.DefaultInputHeight + 5),
			Value = value,
			Min = min,
			Max = max,
			Step = step,
			ClearTextOnFocus = false,
			OnValidChanged = function() end,
			FormatValue = format,
			Arrows = props.Arrows,
			Slider = props.Slider,
			Disabled = true,
		}),
	})
end

local function Story()
	return React.createElement(React.Fragment, {}, {
		Regular = React.createElement(StoryItem, {
			LayoutOrder = 1,
		}),
		Arrows = React.createElement(StoryItem, {
			LayoutOrder = 2,
			Arrows = true,
		}),
		Slider = React.createElement(StoryItem, {
			LayoutOrder = 3,
			Slider = true,
		}),
		Both = React.createElement(StoryItem, {
			LayoutOrder = 4,
			Arrows = true,
			Slider = true,
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local ProgressBar = require("../Components/ProgressBar")
local createStory = require("./Helpers/createStory")

local HEIGHT = 14

local function StoryItem(props: {
	Value: number,
	Max: number?,
	Formatter: ((number, number) -> string)?,
	LayoutOrder: number,
})
	return React.createElement("Frame", {
		Size = UDim2.new(1, 0, 0, HEIGHT),
		LayoutOrder = props.LayoutOrder,
		BackgroundTransparency = 1,
	}, {
		Enabled = React.createElement(ProgressBar, {
			Value = props.Value,
			Max = props.Max,
			Formatter = props.Formatter,
			--Size = UDim2.new(0.5, -5, 1, 0),
			Size = UDim2.new(0, 225, 1, 0),
			Position = UDim2.fromOffset(20, 0),
		}),
		-- Disabled = React.createElement(ProgressBar, {
		-- 	Value = props.Value,
		-- 	Max = props.Max,
		-- 	Formatter = props.Formatter,
		-- 	AnchorPoint = Vector2.new(1, 0),
		-- 	Position = UDim2.fromScale(1, 0),
		-- 	Size = UDim2.new(0.5, -5, 1, 0),
		-- 	Disabled = true,
		-- }),
	})
end

local function Story()
	return React.createElement(React.Fragment, {}, {
		Zero = React.createElement(StoryItem, {
			Value = 0,
			LayoutOrder = 1,
		}),
		Fifty = React.createElement(StoryItem, {
			Value = 0.5,
			LayoutOrder = 1,
		}),
		Hundred = React.createElement(StoryItem, {
			Value = 1,
			LayoutOrder = 2,
		}),
		Custom = React.createElement(StoryItem, {
			Value = 5,
			Max = 14,
			LayoutOrder = 3,
			Formatter = function(value, max)
				return `loaded {value} / {max} assets`
			end,
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local RadioButton = require("../Components/RadioButton")
local createStory = require("./Helpers/createStory")

local function Story()
	local value, setValue = React.useState(true)

	return React.createElement(React.Fragment, {}, {
		Enabled = React.createElement(RadioButton, {
			Label = "Enabled",
			Value = value,
			OnChanged = function()
				setValue(not value)
			end,
			LayoutOrder = 1,
		}),
		Disabled = React.createElement(RadioButton, {
			Label = "Disabled",
			Value = value,
			Disabled = true,
			LayoutOrder = 2,
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local Constants = require("../Constants")
local ScrollFrame = require("../Components/ScrollFrame")
local createStory = require("./Helpers/createStory")

local numRows = 10
local numCols = 10

local size = Vector2.new(48, 32)

local function StoryRow(props: {
	Row: number,
})
	local children = {}
	for i = 1, numCols do
		children[i] = React.createElement("TextLabel", {
			LayoutOrder = i,
			Text = string.format("%i,%i", i - 1, props.Row - 1),
			Font = Constants.DefaultFont,
			TextSize = Constants.DefaultTextSize,
			TextColor3 = Color3.fromRGB(0, 0, 0),
			Size = UDim2.new(0, size.X, 1, 0),
			BorderSizePixel = 0,
			BackgroundTransparency = 0,
			BackgroundColor3 = Color3.fromHSV((i + props.Row) % numCols / numCols, 0.6, 0.8),
		})
	end
	return React.createElement("Frame", {
		LayoutOrder = props.Row,
		Size = UDim2.fromOffset(numCols * size.X, size.Y),
		BackgroundTransparency = 1,
	}, {
		Layout = React.createElement("UIListLayout", {
			FillDirection = Enum.FillDirection.Horizontal,
			SortOrder = Enum.SortOrder.LayoutOrder,
		}),
	}, children)
end

local function StoryScroller(props: {
	Size: UDim2,
	LayoutOrder: number,
	Disabled: boolean?,
})
	local rows = {}
	for i = 1, numRows do
		rows[i] = React.createElement(StoryRow, { Row = i })
	end

	return React.createElement(ScrollFrame, {
		ScrollingDirection = Enum.ScrollingDirection.XY,
		Size = props.Size,
		Disabled = props.Disabled,
		Layout = {
			ClassName = "UIListLayout",
			SortOrder = Enum.SortOrder.LayoutOrder,
		},
	}, rows)
end

local function Story()
	return React.createElement(React.Fragment, {}, {
		Enabled = React.createElement(StoryScroller, {
			Size = UDim2.new(1, -10, 0, 220),
			LayoutOrder = 1,
		}),
		Disabled = React.createElement(StoryScroller, {
			Size = UDim2.new(1, -10, 0, 220),
			LayoutOrder = 2,
			Disabled = true,
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local Slider = require("../Components/Slider")
local createStory = require("./Helpers/createStory")

local function StoryItem(props: {
	LayoutOrder: number,
	Disabled: boolean?,
})
	local value, setValue = React.useState(3)
	return React.createElement(Slider, {
		Value = value,
		Min = 0,
		Max = 10,
		Step = 0,
		OnChanged = setValue,
		Disabled = props.Disabled,
	})
end

local function Story()
	return React.createElement(React.Fragment, {}, {
		Enabled = React.createElement(StoryItem, {
			LayoutOrder = 1,
		}),
		Disabled = React.createElement(StoryItem, {
			LayoutOrder = 2,
			Disabled = true,
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local Label = require("../Components/Label")
local Splitter = require("../Components/Splitter")
local createStory = require("./Helpers/createStory")
local useTheme = require("../Hooks/useTheme")

local function StoryItem(props: {
	Size: UDim2,
	LayoutOrder: number,
	Disabled: boolean?,
})
	local theme = useTheme()

	local alpha0, setAlpha0 = React.useState(0.5)
	local alpha1, setAlpha1 = React.useState(0.5)

	local postText = if props.Disabled then "\n(Disabled)" else ""

	return React.createElement("Frame", {
		Size = props.Size,
		LayoutOrder = props.LayoutOrder,
		BackgroundColor3 = theme:GetColor(Enum.StudioStyleGuideColor.MainBackground),
		BorderColor3 = theme:GetColor(Enum.StudioStyleGuideColor.Border),
		BorderMode = Enum.BorderMode.Inset,
	}, {
		Splitter = React.createElement(Splitter, {
			Alpha = alpha0,
			OnChanged = setAlpha0,
			FillDirection = Enum.FillDirection.Vertical,
			Disabled = props.Disabled,
		}, {
			Side0 = React.createElement(Label, {
				Text = "Top" .. postText,
				Disabled = props.Disabled,
			}),
			Side1 = React.createElement(Splitter, {
				Alpha = alpha1,
				OnChanged = setAlpha1,
				FillDirection = Enum.FillDirection.Horizontal,
				Disabled = props.Disabled,
			}, {
				Side0 = React.createElement(Label, {
					Text = "Bottom Left" .. postText,
					Disabled = props.Disabled,
				}),
				Side1 = React.createElement(Label, {
					Text = "Bottom Right" .. postText,
					Disabled = props.Disabled,
				}),
			}),
		}),
	})
end

local function Story()
	return React.createElement(React.Fragment, {}, {
		Enabled = React.createElement(StoryItem, {
			Size = UDim2.new(1, 0, 0.5, -5),
			LayoutOrder = 1,
		}),
		Disabled = React.createElement(StoryItem, {
			Size = UDim2.new(1, 0, 0.5, -5),
			LayoutOrder = 2,
			Disabled = true,
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local TabContainer = require("../Components/TabContainer")
local createStory = require("./Helpers/createStory")

local function StoryItemContent(props: {
	BackgroundColor3: Color3,
})
	return React.createElement("Frame", {
		Position = UDim2.fromOffset(10, 10),
		Size = UDim2.fromOffset(50, 50),
		BackgroundColor3 = props.BackgroundColor3,
	})
end

local function StoryItem(props: {
	LayoutOrder: number,
	Disabled: boolean?,
})
	local selected, setSelected = React.useState("First")

	return React.createElement(TabContainer, {
		Size = UDim2.new(1, -50, 0.5, -50),
		LayoutOrder = props.LayoutOrder,
		SelectedTab = selected,
		OnTabSelected = setSelected,
		Disabled = props.Disabled,
	}, {
		First = {
			LayoutOrder = 1,
			Content = React.createElement(StoryItemContent, {
				BackgroundColor3 = Color3.fromRGB(255, 0, 255),
			}),
		},
		Second = {
			LayoutOrder = 2,
			Content = React.createElement(StoryItemContent, {
				BackgroundColor3 = Color3.fromRGB(255, 255, 0),
			}),
		},
		Third = {
			LayoutOrder = 3,
			Content = React.createElement(StoryItemContent, {
				BackgroundColor3 = Color3.fromRGB(0, 255, 255),
			}),
			Disabled = true,
		},
	})
end

local function Story()
	return React.createElement(React.Fragment, {}, {
		Enabled = React.createElement(StoryItem, {
			LayoutOrder = 1,
		}),
		Disabled = React.createElement(StoryItem, {
			LayoutOrder = 2,
			Disabled = true,
		}),
	})
end

return createStory(Story)


local React = require("@pkg/@jsdotlua/react")

local TextInput = require("../Components/TextInput")
local createStory = require("./Helpers/createStory")

local function StoryItem(props: {
	Label: string,
	LayoutOrder: number,
	Disabled: boolean?,
	Filter: ((s: string) -> string)?,
})
	local text, setText = React.useState(if props.Disabled then props.Label else "")

	return React.createElement("Frame", {
		Size = UDim2.fromOffset(175, 20),
		LayoutOrder = props.LayoutOrder,
		BackgroundTransparency = 1,
	}, {
		Input = React.createElement(TextInput, {
			Text = text,
			PlaceholderText = props.Label,
			Disabled = props.Disabled,
			OnChanged = function(newText)
				local filtered = newText
				if props.Filter then
					filtered = props.Filter(newText)
				end
				setText(filtered)
			end,
		}),
	})
end

local function Story()
	return React.createElement(React.Fragment, {}, {
		Enabled = React.createElement(StoryItem, {
			Label = "Any text allowed",
			LayoutOrder = 1,
		}),
		Filtered = React.createElement(StoryItem, {
			Label = "Numbers only",
			LayoutOrder = 2,
			Filter = function(text)
				return (string.gsub(text, "%D", ""))
			end,
		}),
		Disabled = React.createElement(StoryItem, {
			Label = "Disabled",
			LayoutOrder = 3,
			Disabled = true,
		}),
	})
end

return createStory(Story)


local TextService = game:GetService("TextService")

local Constants = require("./Constants")

local TEXT_SIZE = Constants.DefaultTextSize
local FONT = Constants.DefaultFont
local FRAME_SIZE = Vector2.one * math.huge

local function getTextSize(text: string)
	local size = TextService:GetTextSize(text, TEXT_SIZE, FONT, FRAME_SIZE)
	return Vector2.new(math.ceil(size.X), math.ceil(size.Y)) + Vector2.one
end

return getTextSize


return {
	Constants = require("./Constants"),

	Background = require("./Components/Background"),
	Button = require("./Components/Button"),
	Checkbox = require("./Components/Checkbox"),
	ColorPicker = require("./Components/ColorPicker"),
	DatePicker = require("./Components/DatePicker"),
	Dropdown = require("./Components/Dropdown"),
	DropShadowFrame = require("./Components/DropShadowFrame"),
	Label = require("./Components/Label"),
	LoadingDots = require("./Components/LoadingDots"),
	MainButton = require("./Components/MainButton"),
	NumberSequencePicker = require("./Components/NumberSequencePicker"),
	NumericInput = require("./Components/NumericInput"),
	PluginProvider = require("./Components/PluginProvider"),
	ProgressBar = require("./Components/ProgressBar"),
	RadioButton = require("./Components/RadioButton"),
	ScrollFrame = require("./Components/ScrollFrame"),
	Slider = require("./Components/Slider"),
	Splitter = require("./Components/Splitter"),
	TabContainer = require("./Components/TabContainer"),
	TextInput = require("./Components/TextInput"),

	ThemeContext = require("./Contexts/ThemeContext"),

	useTheme = require("./Hooks/useTheme"),
	usePlugin = require("./Hooks/usePlugin"),
	useMouseIcon = require("./Hooks/useMouseIcon"),
}


--!optimize 2
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local Matter_0_8 = require(ReplicatedStorage.Matter_0_8)
local Matter_0_9 = require(ReplicatedStorage.Matter_0_9)

local A, B = Matter.component(), Matter.component()
local A_0_8, B_0_8 = Matter_0_8.component(), Matter_0_8.component()
local A_0_9, B_0_9 = Matter_0_9.component(), Matter_0_9.component()

local N = 1000

return {
	ParameterGenerator = function()
		local world, world_0_8, world_0_9 = Matter.World.new(), Matter_0_8.World.new(), Matter_0_9.World.new()
		for i = 1, N do
			world_0_8:spawnAt(i, A_0_8({}), B_0_8({}))
			world_0_9:spawnAt(i, A_0_9({}), B_0_9({}))
			world:spawnAt(i, A({ a = true }), B({ b = true }))
		end

		return world_0_8, world_0_9, world
	end,

	Functions = {
		["Matter 0.8"] = function(_, world)
			for i = 1, N do
				world:get(i, B_0_8, A_0_8)
			end
		end,
		["Matter 0.9"] = function(_, _, world)
			for i = 1, N do
				world:get(i, B_0_9, A_0_9)
			end
		end,
		["Matter Unreleased"] = function(_, _, _, world)
			for i = 1, N do
				world:get(i, B, A)
			end
		end,
	},
}


--!optimize 2
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local PinnedMatter = require(ReplicatedStorage.Matter_0_8)

local A, B = Matter.component(), Matter.component()
local pinnedA, pinnedB = PinnedMatter.component(), PinnedMatter.component()

local N = 500

return {
	ParameterGenerator = function()
		local world, pinnedWorld = Matter.World.new(), PinnedMatter.World.new()
		for i = 1, N do
			world:spawnAt(i)
			pinnedWorld:spawnAt(i)
		end

		return world, pinnedWorld
	end,

	Functions = {
		["Matter 0.9"] = function(_, world)
			for i = 1, N do
				world:insert(i, A({ i }))
			end
		end,
		["Matter 0.8.4"] = function(_, _, world)
			for i = 1, N do
				world:insert(i, A({ i }))
			end
		end,
	},
}


--!optimize 2
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local PinnedMatter = require(ReplicatedStorage.Matter_0_8)

local world = Matter.World.new()
local pinnedWorld = PinnedMatter.World.new()

local A, B = Matter.component(), Matter.component()
local pinnedA, pinnedB = PinnedMatter.component(), PinnedMatter.component()

for i = 1, 10_000 do
	world:spawnAt(i, A({}), B({}))
	pinnedWorld:spawnAt(i, pinnedA({}), pinnedB({}))
end

return {
	ParameterGenerator = function()
		return
	end,

	Functions = {
		["Matter 0.9"] = function()
			local query = world:query(A, B)
			for _ = 1, 1_000 do
				query:next()
			end
		end,
		["Matter 0.8.4"] = function()
			local query = pinnedWorld:query(pinnedA, pinnedB)
			for _ = 1, 1_000 do
				query:next()
			end
		end,
	},
}


--!optimize 2
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local PinnedMatter = require(ReplicatedStorage.Matter_0_8)

local world = Matter.World.new()
local pinnedWorld = PinnedMatter.World.new()

local A, B = Matter.component(), Matter.component()
local pinnedA, pinnedB = PinnedMatter.component(), PinnedMatter.component()

for i = 1, 10_000 do
	world:spawnAt(i, A({}), B({}))
	pinnedWorld:spawnAt(i, pinnedA({}), pinnedB({}))
end

return {
	ParameterGenerator = function()
		return
	end,

	Functions = {
		["Matter 0.9"] = function()
			local count = 0
			for _ in world:query(A, B) do
				count += 1
			end
		end,
		["Matter 0.8.4"] = function()
			local count = 0
			for _ in pinnedWorld:query(pinnedA, pinnedB) do
				count += 1
			end
		end,
	},
}


--!optimize 2
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local PinnedMatter = require(ReplicatedStorage.Matter_0_8)

local world = Matter.World.new()
local pinnedWorld = PinnedMatter.World.new()

local A, B, C, D, E, F, G =
	Matter.component(),
	Matter.component(),
	Matter.component(),
	Matter.component(),
	Matter.component(),
	Matter.component(),
	Matter.component()
local pinnedA, pinnedB, pinnedC, pinnedD, pinnedE, pinnedF, pinnedG =
	PinnedMatter.component(),
	PinnedMatter.component(),
	PinnedMatter.component(),
	PinnedMatter.component(),
	PinnedMatter.component(),
	PinnedMatter.component(),
	PinnedMatter.component()

local function flip()
	return math.random() > 0.5
end

local archetypes = {}
for i = 1, 30_000 do
	local id = i
	world:spawnAt(id)
	pinnedWorld:spawnAt(id)

	local str = ""
	if flip() then
		world:insert(id, A({ a = true, id = i }))
		pinnedWorld:insert(id, pinnedA({ a = true, id = i }))
		str ..= "A_"
	end
	if flip() then
		world:insert(id, B({ b = true, id = i }))
		pinnedWorld:insert(id, pinnedB({ b = true, id = i }))
		str ..= "B_"
	end
	if flip() then
		world:insert(id, C({ c = true, id = i }))
		pinnedWorld:insert(id, pinnedC({ c = true, id = i }))
		str ..= "C_"
	end
	if flip() then
		world:insert(id, D({ d = true, id = i }))
		pinnedWorld:insert(id, pinnedD({ d = true, id = i }))
		str ..= "D_"
	end
	if flip() then
		world:insert(id, E({ e = true, id = i }))
		pinnedWorld:insert(id, pinnedE({ e = true, id = i }))
		str ..= "E_"
	end
	if flip() then
		world:insert(id, F({ f = true, id = i }))
		pinnedWorld:insert(id, pinnedF({ f = true, id = i }))
		str ..= "F_"
	end
	if flip() then
		world:insert(id, G({ g = true, id = i }))
		pinnedWorld:insert(id, pinnedG({ g = true, id = i }))
		str ..= "G"
	end

	archetypes[str] = (archetypes[str] or 0) + 1
end

local total = 0
for _ in archetypes do
	total += 1
end

print(total, "different archetypes")

return {
	ParameterGenerator = function()
		return
	end,

	Functions = {
		["Matter 0.8.4"] = function()
			local count = 0
			for _ in pinnedWorld:query(pinnedB, pinnedA) do
				count += 1
			end
		end,
		["Matter 0.9"] = function()
			local count = 0
			for _ in world:query(B, A) do
				count += 1
			end
		end,
	},
}


--!optimize 2
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local PinnedMatter = require(ReplicatedStorage.Matter_0_8)

local world = Matter.World.new()
local pinnedWorld = PinnedMatter.World.new()

local A, B, C, D, E, F, G =
	Matter.component(),
	Matter.component(),
	Matter.component(),
	Matter.component(),
	Matter.component(),
	Matter.component(),
	Matter.component()
local pinnedA, pinnedB, pinnedC, pinnedD, pinnedE, pinnedF, pinnedG =
	PinnedMatter.component(),
	PinnedMatter.component(),
	PinnedMatter.component(),
	PinnedMatter.component(),
	PinnedMatter.component(),
	PinnedMatter.component(),
	PinnedMatter.component()

local function flip()
	return math.random() > 0.5
end

for i = 1, 50_000 do
	local id = i
	world:spawnAt(id)
	pinnedWorld:spawnAt(id)

	if flip() then
		world:insert(id, A({ a = true, id = i }))
		pinnedWorld:insert(id, pinnedA({ a = true, id = i }))
	end
	if flip() then
		world:insert(id, B({ b = true, id = i }))
		pinnedWorld:insert(id, pinnedB({ b = true, id = i }))
	end
	if flip() then
		world:insert(id, C({ c = true, id = i }))
		pinnedWorld:insert(id, pinnedC({ c = true, id = i }))
	end
	if flip() then
		world:insert(id, D({ d = true, id = i }))
		pinnedWorld:insert(id, pinnedD({ d = true, id = i }))
	end
	if flip() then
		world:insert(id, E({ e = true, id = i }))
		pinnedWorld:insert(id, pinnedE({ e = true, id = i }))
	end
	if flip() then
		world:insert(id, F({ f = true, id = i }))
		pinnedWorld:insert(id, pinnedF({ f = true, id = i }))
	end
	if flip() then
		world:insert(id, G({ g = true, id = i }))
		pinnedWorld:insert(id, pinnedG({ g = true, id = i }))
	end
end

return {
	ParameterGenerator = function()
		return
	end,

	Functions = {
		["Matter 0.8.4"] = function()
			local count = 0
			for _ in pinnedWorld:query(pinnedB):without(pinnedC) do
				count += 1
			end
		end,
		["Matter 0.9"] = function()
			local count = 0
			for _ in world:query(B):without(C) do
				count += 1
			end
		end,
	},
}


local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Shared.components)
local Matter = require(ReplicatedStorage.Lib.Matter)

local function roombasHurt(world)
	for _, _, model in world:query(Components.Roomba, Components.Model) do
		for _, part in Matter.useEvent(model.model.PrimaryPart, "Touched") do
			local touchedModel = part:FindFirstAncestorWhichIsA("Model")
			if not touchedModel then
				continue
			end

			local player = Players:GetPlayerFromCharacter(touchedModel)

			if not player then
				continue
			end

			if player ~= Players.LocalPlayer then
				continue
			end

			local humanoid = touchedModel:FindFirstChildWhichIsA("Humanoid")

			if not humanoid then
				continue
			end

			humanoid:TakeDamage(5)
		end
	end
end

return roombasHurt


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Shared.components)

local function spinSpinners(world, _, ui)
	if ui.checkbox("Disable Spinning"):checked() then
		return
	end

	local transparency = ui.slider(1)

	local randomize = ui.button("Randomize colors!"):clicked()

	for _, model in world:query(Components.Model, Components.Spinner) do
		model.model.PrimaryPart.CFrame = model.model.PrimaryPart.CFrame * CFrame.Angles(0, math.rad(5), 0)
		model.model.PrimaryPart.Transparency = transparency

		if randomize then
			model.model.PrimaryPart.BrickColor = BrickColor.random()
		end
	end
end

return {
	system = spinSpinners,
	event = "Stepped",
}


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local start = require(ReplicatedStorage.Shared.start)
local receiveReplication = require(ReplicatedStorage.Client.receiveReplication)

local world, state = start({
	ReplicatedStorage.Shared.systems,
	ReplicatedStorage.Client.systems,
})

receiveReplication(world, state)


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local start = require(ReplicatedStorage.Shared.start)
local setupTags = require(ReplicatedStorage.Shared.setupTags)

local world = start({
	script.systems,
	ReplicatedStorage.Shared.systems,
})

setupTags(world)


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Shared.components)
local Matter = require(ReplicatedStorage.Lib.Matter)

local function mothershipsSpawnRoombas(world)
	for id, model, lasering, transform in
		world:query(Components.Model, Components.Lasering, Components.Transform, Components.Mothership)
	do
		model.model.Beam.Transparency = 1 - lasering.remainingTime

		lasering = lasering:patch({
			remainingTime = lasering.remainingTime - Matter.useDeltaTime(),
		})

		if not lasering.spawned then
			local spawnPosition = Vector3.new(transform.cframe.p.X, 11, transform.cframe.p.Z)

			world:spawn(
				Components.Roomba(),
				Components.Charge({
					charge = 100,
				}),
				Components.Transform({
					cframe = CFrame.new(spawnPosition),
				})
			)

			lasering = lasering:patch({ spawned = true })
		end

		if lasering.remainingTime <= 0 then
			world:remove(id, Components.Lasering)
		else
			world:insert(id, lasering)
		end
	end
end

return mothershipsSpawnRoombas


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Shared.components)
local Matter = require(ReplicatedStorage.Lib.Matter)

local function removeMissingModels(world)
	for id, model in world:query(Components.Model) do
		for _ in Matter.useEvent(model.model, "AncestryChanged") do
			if model.model:IsDescendantOf(game) == false then
				world:remove(id, Components.Model)
				break
			end
		end
		if not model.model.PrimaryPart then
			world:remove(id, Components.Model)
		end
	end

	for _id, modelRecord in world:queryChanged(Components.Model) do
		if modelRecord.new == nil then
			if modelRecord.old and modelRecord.old.model then
				modelRecord.old.model:Destroy()
			end
		end
	end
end

return removeMissingModels


local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Shared.components)
local useEvent = require(ReplicatedStorage.Lib.Matter).useEvent

local RemoteEvent = Instance.new("RemoteEvent")
RemoteEvent.Name = "MatterRemote"
RemoteEvent.Parent = ReplicatedStorage

local REPLICATED_COMPONENTS = {
	"Roomba",
	"Model",
	"Health",
	"Target",
	"Mothership",
	"Spinner",
}

local replicatedComponents = {}

for _, name in REPLICATED_COMPONENTS do
	replicatedComponents[Components[name]] = true
end

local function replication(world)
	for _, player in useEvent(Players, "PlayerAdded") do
		local payload = {}

		for entityId, entityData in world do
			local entityPayload = {}
			payload[tostring(entityId)] = entityPayload

			for component, componentData in entityData do
				if replicatedComponents[component] then
					entityPayload[tostring(component)] = { data = componentData }
				end
			end
		end

		print("Sending initial payload to", player)
		RemoteEvent:FireClient(player, payload)
	end

	local changes = {}

	for component in replicatedComponents do
		for entityId, record in world:queryChanged(component) do
			local key = tostring(entityId)
			local name = tostring(component)

			if changes[key] == nil then
				changes[key] = {}
			end

			if world:contains(entityId) then
				changes[key][name] = { data = record.new }
			end
		end
	end

	if next(changes) then
		RemoteEvent:FireAllClients(changes)
	end
end

return {
	system = replication,
	priority = math.huge,
}


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Shared.components)

local function roombasMove(world)
	local targets = {}
	for _, model in world:query(Components.Model, Components.Target) do
		table.insert(targets, model.model.PrimaryPart.CFrame.p)
	end

	for _, _, charge, model in world:query(Components.Roomba, Components.Charge, Components.Model) do
		if charge.charge <= 0 then
			continue
		end

		local closestPosition, closestDistance
		local currentPosition = model.model.PrimaryPart.CFrame.p

		for _, target in ipairs(targets) do
			local distance = (currentPosition - target).magnitude
			if not closestPosition or distance < closestDistance then
				closestPosition = target
				closestDistance = distance
			end
		end

		if closestPosition then
			local body = model.model.Roomba
			local force = body:GetMass() * 20

			if closestDistance < 4 then
				force = 0
			end

			local lookVector = body.CFrame.LookVector
			local desiredLookVector = (closestPosition - currentPosition).unit

			force = force * lookVector:Dot(desiredLookVector)
			body.VectorForce.Force = Vector3.new(force, 0, 0)

			local absoluteAngle = math.atan2(desiredLookVector.Z, desiredLookVector.X)
			local roombaAngle = math.atan2(lookVector.Z, lookVector.X)

			local angle = math.deg(absoluteAngle - roombaAngle)

			angle = angle % 360
			angle = (angle + 360) % 360
			if angle > 180 then
				angle -= 360
			end

			local angularVelocity = body.AssemblyAngularVelocity

			local sign = math.sign(angle)
			local motor = math.sqrt(math.abs(angle)) * sign * -1 * 20
			local friction = angularVelocity.Y * -12
			local torque = body:GetMass() * (motor + friction)

			body.Torque.Torque = Vector3.new(0, torque, 0)
		end
	end
end

return roombasMove


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Shared.components)

local function spawnRoombas(world)
	for id, _ in world:query(Components.Transform, Components.Roomba):without(Components.Model) do
		local model = ReplicatedStorage.Assets.KillerRoomba:Clone()
		model.Parent = workspace
		model.PrimaryPart:SetNetworkOwner(nil)

		world:insert(
			id,
			Components.Model({
				model = model,
			})
		)
	end
end

return spawnRoombas


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.Lib.Matter)

local COMPONENTS = {
	"Roomba",
	"Model",
	"Charge",
	"Health",
	"Target",
	"Transform",
	"Mothership",
	"Lasering",
	"DebugLabel",
	"Spinner",
}

local components = {}

for _, name in ipairs(COMPONENTS) do
	components[name] = Matter.component(name)
end

return components


local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Packages = ReplicatedStorage.Packages
local Matter = require(ReplicatedStorage.Lib.Matter)
local Plasma = require(Packages.plasma)
local HotReloader = require(Packages.rewire).HotReloader
local components = require(script.Parent.components)

local function start(containers)
	local world = Matter.World.new()
	local state = {}

	local debugger = Matter.Debugger.new(Plasma)

	debugger.findInstanceFromEntity = function(id)
		if not world:contains(id) then
			return
		end

		local model = world:get(id, components.Model)

		return model and model.model or nil
	end

	local loop = Matter.Loop.new(world, state, debugger:getWidgets())
	loop:setWorlds({ World = world })

	-- Set up hot reloading

	local hotReloader = HotReloader.new()

	local firstRunSystems = {}
	local systemsByModule = {}

	local function loadModule(module, context)
		local originalModule = context.originalModule

		local ok, system = pcall(require, module)

		if not ok then
			warn("Error when hot-reloading system", module.name, system)
			return
		end

		if firstRunSystems then
			table.insert(firstRunSystems, system)
		elseif systemsByModule[originalModule] then
			loop:replaceSystem(systemsByModule[originalModule], system)
			debugger:replaceSystem(systemsByModule[originalModule], system)
		else
			loop:scheduleSystem(system)
		end

		systemsByModule[originalModule] = system
	end

	local function unloadModule(_, context)
		if context.isReloading then
			return
		end

		local originalModule = context.originalModule
		if systemsByModule[originalModule] then
			loop:evictSystem(systemsByModule[originalModule])
			systemsByModule[originalModule] = nil
		end
	end

	for _, container in containers do
		hotReloader:scan(container, loadModule, unloadModule)
	end

	loop:scheduleSystems(firstRunSystems)
	firstRunSystems = nil

	debugger:autoInitialize(loop)

	-- Begin running our systems

	loop:begin({
		default = RunService.Heartbeat,
		Stepped = RunService.Stepped,
	})

	if RunService:IsClient() then
		UserInputService.InputBegan:Connect(function(input)
			if input.KeyCode == Enum.KeyCode.F4 then
				debugger:toggle()

				state.debugEnabled = debugger.enabled
			end
		end)
	end

	return world, state
end

return start


local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Shared.components)

local name = RunService:IsServer() and "serverEntityId" or "clientEntityId"

local function updateModelAttribute(world)
	for id, record in world:queryChanged(Components.Model) do
		if record.new then
			record.new.model:SetAttribute(name, id)
		end
	end
end

return updateModelAttribute


export type EntityId = number
export type ComponentId = number
export type ComponentIds = { ComponentId }

export type Component = { [any]: any }
export type ComponentInstance = { [any]: any }

export type ArchetypeId = string
export type Archetype = {
	entities: { EntityId },
	componentIds: { ComponentId },
	idToIndex: { [ComponentId]: number },
	indexToId: { [number]: ComponentId },
	fields: { { ComponentInstance } },
}

function hash(componentIds: { number })
	table.sort(componentIds)
	return table.concat(componentIds, "_")
end

function new(componentIds: { ComponentId }): (Archetype, ArchetypeId)
	local length = #componentIds
	local archetypeId = hash(componentIds)

	local idToIndex, indexToId = {}, {}
	local fields = table.create(length)

	local archetype: Archetype = {
		entities = {},
		componentIds = componentIds,
		idToIndex = idToIndex,
		indexToId = indexToId,
		fields = fields,
	}

	for index, componentId in componentIds do
		idToIndex[componentId] = index
		indexToId[index] = componentId

		fields[index] = {}
	end

	return archetype, archetypeId
end

return {
	new = new,
	hash = hash,
}


local RunService = game:GetService("RunService")

local World = require(script.Parent.World)
local rollingAverage = require(script.Parent.rollingAverage)
local topoRuntime = require(script.Parent.topoRuntime)

local recentErrors = {}
local recentErrorLastTime = 0

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

local function systemFn(system: System)
	if type(system) == "table" then
		return system.system
	end

	return system
end

local function systemName(system: System)
	local fn = systemFn(system)
	return debug.info(fn, "s") .. "->" .. debug.info(fn, "n")
end

--[=[
	@class Loop

	The Loop class handles scheduling and *looping* (who would have guessed) over all of your game systems.

	:::caution Yielding
	Yielding is not allowed in systems. Doing so will result in the system thread being closed early, but it will not
	affect other systems.
	:::
]=]
local Loop = {}
Loop.__index = Loop

--[=[
	Creates a new loop. `Loop.new` accepts as arguments the values that will be passed to all of your systems.

	So typically, you want to pass the World in here, as well as maybe a table of global game state.

	```lua
	local world = World.new()
	local gameState = {}

	local loop = Loop.new(world, gameState)
	```

	@param ... ...any -- Values that will be passed to all of your systems
	@return Loop
]=]
function Loop.new(...)
	return setmetatable({
		_systems = {},
		_skipSystems = {},
		_orderedSystemsByEvent = {},
		_state = { ... },
		_worlds = {},
		_stateLength = select("#", ...),
		_systemState = {},
		_middlewares = {},
		_systemErrors = {},
		_systemLogs = {},
		_debugger = nil,
		profiling = nil,
		trackErrors = false,
	}, Loop)
end

--[=[
	@within Loop
	@type System SystemTable | (...any) -> ()

	Either a plain function or a table defining the system.
]=]

--[=[
	@within Loop
	@interface SystemTable
	.system (...any) -> () -- The system function
	.event? string -- The event the system runs on. A string, a key from the table you pass to `Loop:begin`.
	.priority? number -- Priority influences the position in the frame the system is scheduled to run at.
	.after? {System} -- A list of systems that this system must run after.

	A table defining a system with possible options.

	Systems are scheduled in order of `priority`, meaning lower `priority` runs first.
	The default priority is `0`.
]=]

type System = (...any) -> () | { system: (...any) -> (), event: string?, priority: number?, after: nil | {} }

--[=[
	Schedules a set of systems based on the constraints they define.

	Systems may optionally declare:
	- The name of the event they run on (e.g., RenderStepped, Stepped, Heartbeat)
	- A numerical priority value
	- Other systems that they must run *after*

	If systems do not specify an event, they will run on the `default` event.

	Systems that share an event will run in order of their priority, which means that systems with a lower `priority`
	value run first. The default priority is `0`.

	Systems that have defined what systems they run `after` can only be scheduled after all systems they depend on have
	already been scheduled.

	All else being equal, the order in which systems run is stable, meaning if you don't change your code, your systems
	will always run in the same order across machines.

	:::info
	It is possible for your systems to be in an unresolvable state. In which case, `scheduleSystems` will error.
	This can happen when your systems have circular or unresolvable dependency chains.

	If a system has both a `priority` and defines systems it runs `after`, the system can only be scheduled if all of
	the systems it depends on have a lower or equal priority.

	Systems can never depend on systems that run on other events, because it is not guaranteed or required that events
	will fire every frame or will always fire in the same order.
	:::

	:::caution
	`scheduleSystems` has to perform nontrivial sorting work each time it's called, so you should avoid calling it multiple
	times if possible.
	:::

	@param systems { System } -- Array of systems to schedule.
]=]
function Loop:scheduleSystems(systems: { System })
	for _, system in ipairs(systems) do
		systemNames[system] = systemName(system)

		if table.find(self._systems, system) == nil then
			table.insert(self._systems, system)
		end
		self._systemState[system] = {}

		if RunService:IsStudio() then
			-- In Studio, we start logging immediately.
			self._systemLogs[system] = {}
		end
	end

	self:_sortSystems()
end
--[=[
	Schedules a single system. This is an expensive function to call multiple times. Instead, try batch scheduling
	systems with [Loop:scheduleSystems] if possible.

	@param system System
]=]
function Loop:scheduleSystem(system: System)
	return self:scheduleSystems({ system })
end

--[=[
	Removes a previously-scheduled system from the Loop. Evicting a system also cleans up any storage from hooks.
	This is intended to be used for hot reloading. Dynamically loading and unloading systems for gameplay logic
	is not recommended.

	@param system System
]=]
function Loop:evictSystem(system: System)
	local systemIndex = table.find(self._systems, system)

	if systemIndex == nil then
		error("Can't evict system because it doesn't exist")
	end

	table.remove(self._systems, systemIndex)

	self._systemErrors[system] = nil

	topoRuntime.start({
		system = self._systemState[system],
	}, function() end)

	self._systemState[system] = nil
	self._systemLogs[system] = nil

	systemNames[system] = nil

	self:_sortSystems()
end

--[=[
	Replaces an older version of a system with a newer version of the system. Internal system storage (which is used
	by hooks) will be moved to be associated with the new system. This is intended to be used for hot reloading.

	@param old System
	@param new System
]=]
function Loop:replaceSystem(old: System, new: System)
	local systemIndex = table.find(self._systems, old)

	if not systemIndex then
		error("Before system does not exist!")
	end

	table.remove(self._systems, systemIndex)
	table.insert(self._systems, new)

	self._systemState[new] = self._systemState[old] or {}
	self._systemState[old] = nil

	systemNames[new] = systemName(new)
	systemNames[old] = nil

	if self._skipSystems[old] then
		self._skipSystems[old] = nil
		self._skipSystems[new] = true
	end

	for _, system in self._systems do
		if type(system) == "table" and system.after then
			local index = table.find(system.after, old)

			if index then
				system.after[index] = new
			end
		end
	end

	self:_sortSystems()
end

local function orderSystemsByDependencies(unscheduledSystems: { System })
	local systemPriorityMap = {}
	local visiting = "v"

	local function systemPriority(system: System)
		local name = systemNames[system]

		local priority = systemPriorityMap[system]

		if not priority then
			priority = 0

			systemPriorityMap[system] = visiting

			if type(system) == "table" then
				if system.after then
					for _, dependency in system.after do
						local dependencyName = systemNames[dependency]

						if systemPriorityMap[dependency] ~= visiting then
							priority = math.max(priority, systemPriority(dependency) + 1)
						else
							local errorStatement = {
								`Cyclic dependency detected: System '{name}' is set to execute after System '{dependencyName}', and vice versa. This creates a loop that prevents the systems from being able to execute in a valid order.`,
								"To resolve this issue, reconsider the dependencies between these systems. One possible solution is to update the 'after' field from one of the systems.",
							}
							error(table.concat(errorStatement, "\n"))
						end
					end
				elseif system.priority then
					priority = system.priority
				end
			end

			systemPriorityMap[system] = priority
		end

		return priority
	end

	local scheduledSystems = table.clone(unscheduledSystems)

	table.sort(scheduledSystems, function(a, b)
		local priorityA = systemPriority(a)
		local priorityB = systemPriority(b)

		if priorityA == priorityB then
			local nameA = systemNames[a]
			local nameB = systemNames[b]

			if nameA == nameB then
				return table.find(unscheduledSystems, a) < table.find(unscheduledSystems, b)
			end

			return nameA < nameB
		end

		return priorityA < priorityB
	end)

	return scheduledSystems
end

function Loop:_sortSystems()
	local systemsByEvent = {}

	for _, system in pairs(self._systems) do
		local name = systemNames[system]

		local eventName = "default"

		if type(system) == "table" then
			if system.event then
				eventName = system.event
			end
			if system.after then
				if system.priority then
					error(`{name} shouldn't have both priority and after defined`)
				end

				if #system.after == 0 then
					error(
						`System "{name}" "after" table was provided but is empty; did you accidentally use a nil value or make a typo?`
					)
				end

				for _, dependency in system.after do
					local dependencyName = systemNames[dependency]

					if not table.find(self._systems, dependency) then
						error(
							`Unable to schedule "{name}" because the system "{dependencyName}" is not scheduled.\n\nEither schedule "{dependencyName}" before "{name}" or consider scheduling these systems together with Loop:scheduleSystems`
						)
					end
				end
			end
		end

		if not systemsByEvent[eventName] then
			systemsByEvent[eventName] = {}
		end

		table.insert(systemsByEvent[eventName], system)
	end

	self._orderedSystemsByEvent = {}

	for eventName, systems in pairs(systemsByEvent) do
		self._orderedSystemsByEvent[eventName] = orderSystemsByDependencies(systems)
	end
end

--[=[
	Sets the Worlds to be used by the Loop for deferring commands, and the Debugger for profiling.

	@param worlds {World} | {[string]: World} -- An array or dictionary of Worlds to be used by the Loop. If a dictionary is passed, the keys are used as the names of the Worlds for the Debugger.
]=]
function Loop:setWorlds(worlds: { World.World } | { [string]: World.World })
	self._worlds = worlds
end

--[=[
	Connects to frame events and starts invoking your systems.

	Pass a table of events you want to be able to run systems on, a map of name to event. Systems can use these names
	to define what event they run on. By default, systems run on an event named `"default"`. Custom events may be used
	if they have a `Connect` function.

	```lua
	loop:begin({
		default = RunService.Heartbeat,
		Heartbeat = RunService.Heartbeat,
		RenderStepped = RunService.RenderStepped,
		Stepped = RunService.Stepped,
	})
	```

	Returns a table similar to the one you passed in, but the values are `RBXScriptConnection` values (or whatever is
	returned by `:Connect` if you passed in a synthetic event).

	@param events {[string]: RBXScriptSignal} -- A map from event name to event objects.
	@return {[string]: RBXScriptConnection} -- A map from your event names to connection objects.
]=]
function Loop:begin(events)
	local connections = {}

	for eventName, event in pairs(events) do
		local lastTime = os.clock()
		local generation = false

		local function stepSystems()
			if not self._orderedSystemsByEvent[eventName] then
				-- Skip events that have no systems
				return
			end

			local currentTime = os.clock()
			local deltaTime = currentTime - lastTime
			lastTime = currentTime

			generation = not generation

			local profiling = self.profiling

			for _, world in self._worlds do
				world:startDeferring()
			end

			for _, system in ipairs(self._orderedSystemsByEvent[eventName]) do
				topoRuntime.start({
					system = self._systemState[system],
					frame = {
						generation = generation,
						deltaTime = deltaTime,
						logs = self._systemLogs[system],
					},
					currentSystem = system,
				}, function()
					if self._skipSystems[system] then
						if profiling then
							profiling[system] = nil
						end

						return
					end

					local fn = systemFn(system)
					local name = systemNames[system]

					debug.profilebegin("system: " .. name)
					local commitFailed = false
					local thread = coroutine.create(function(...)
						fn(...)

						for _, world in self._worlds do
							local ok, err = pcall(world.commitCommands, world)
							if not ok then
								commitFailed = true
								error(err)
							end
						end
					end)

					local startTime = os.clock()
					local success, errorValue = coroutine.resume(thread, unpack(self._state, 1, self._stateLength))

					if profiling ~= nil then
						local duration = os.clock() - startTime

						if profiling[system] == nil then
							profiling[system] = {}
						end

						local debugger = self._debugger
						if debugger and debugger.debugSystem == system and debugger._queries then
							local totalQueryTime = 0

							for _, query in debugger._queries do
								totalQueryTime += query.averageDuration
							end

							rollingAverage.addSample(
								profiling[system],
								if debugger.debugSystem then duration - totalQueryTime else duration
							)
						else
							rollingAverage.addSample(profiling[system], duration)
						end
					end

					if coroutine.status(thread) ~= "dead" then
						coroutine.close(thread)

						task.spawn(
							error,
							(
								"Matter: System %s yielded! Its thread has been closed. "
								.. "Yielding in systems is not allowed."
							):format(name)
						)
					end

					if not success then
						if os.clock() - recentErrorLastTime > 10 then
							recentErrorLastTime = os.clock()
							recentErrors = {}
						end

						local errorString = name
							.. ": "
							.. (
								if commitFailed
									-- Strip irrelevant line numbers that point to Loop / World
									then string.gsub(errorValue, "%[.+%]:%d+: ", "Failed to apply commands: ")
									else errorValue
							)
							.. "\n"
							.. debug.traceback(thread)

						if not recentErrors[errorString] then
							task.spawn(error, errorString)
							warn("Matter: The above error will be suppressed for the next 10 seconds")
							recentErrors[errorString] = true
						end

						if self.trackErrors then
							if self._systemErrors[system] == nil then
								self._systemErrors[system] = {}
							end

							local errorStorage = self._systemErrors[system]
							local lastError = errorStorage[#errorStorage]

							if lastError and lastError.error == errorString then
								lastError.when = os.time()
							else
								table.insert(errorStorage, {
									error = errorString,
									when = os.time(),
								})

								if #errorStorage > 100 then
									table.remove(errorStorage, 1)
								end
							end
						end
					end

					debug.profileend()
				end)
			end
		end

		for _, middleware in ipairs(self._middlewares) do
			stepSystems = middleware(stepSystems, eventName)

			if type(stepSystems) ~= "function" then
				error(
					("Middleware function %s:%s returned %s instead of a function"):format(
						debug.info(middleware, "s"),
						debug.info(middleware, "l"),
						typeof(stepSystems)
					)
				)
			end
		end

		connections[eventName] = event:Connect(stepSystems)
	end

	return connections
end

--[=[
	Adds a user-defined middleware function that is called during each frame.

	This allows you to run code before and after each frame, to perform initialization and cleanup work.

	```lua
	loop:addMiddleware(function(nextFn)
		return function()
			Plasma.start(plasmaNode, nextFn)
		end
	end)
	```

	You must pass `addMiddleware` a function that itself returns a function that invokes `nextFn` at some point.

	The outer function is invoked only once. The inner function is invoked during each frame event.

	:::info
	Middleware added later "wraps" middleware that was added earlier. The innermost middleware function is the internal
	function that actually calls your systems.
	:::
	@param middleware (nextFn: () -> (), eventName: string) -> () -> ()
]=]
function Loop:addMiddleware(middleware: (nextFn: () -> ()) -> () -> ())
	table.insert(self._middlewares, middleware)
end

return Loop


local Queue = {}
Queue.__index = Queue

function Queue.new()
	return setmetatable({
		_head = nil,
		_tail = nil,
	}, Queue)
end

function Queue:pushBack(value)
	local entry = {
		value = value,
		next = nil,
	}

	if self._tail ~= nil then
		self._tail.next = entry
	end

	self._tail = entry

	if self._head == nil then
		self._head = entry
	end
end

function Queue:popFront()
	if self._head == nil then
		return nil
	end

	local value = self._head.value
	self._head = self._head.next

	return value
end

return Queue


local RunService = game:GetService("RunService")
local EventBridge = {}
EventBridge.__index = EventBridge

local debouncedEvents = {
	InputChanged = true,
}

local debounce = {}

local function serialize(...)
	local first = ...

	if first and typeof(first) == "Instance" and first:IsA("InputObject") then
		return {
			Delta = first.Delta,
			KeyCode = first.KeyCode,
			Position = first.Position,
			UserInputState = first.UserInputState,
			UserInputType = first.UserInputType,
		}
	end

	return ...
end

local clientConnections = {}
EventBridge.clientActions = {
	connect = function(fire, instance, event)
		local instanceFromServer = instance

		if type(instance) == "string" then
			instance = game:GetService(instance)
		end

		if clientConnections[instance] == nil then
			clientConnections[instance] = {}
		end

		clientConnections[instance][event] = instance[event]:Connect(function(...)
			if debouncedEvents[event] and not RunService:IsStudio() then
				local args = table.pack(serialize(...))

				if debounce[instance] and debounce[instance][event] then
					debounce[instance][event] = args
				else
					if debounce[instance] == nil then
						debounce[instance] = {}
					end

					debounce[instance][event] = args

					task.delay(0.25, function()
						local args = debounce[instance][event]

						fire("event", instanceFromServer, event, unpack(args, 1, args.n))

						debounce[instance][event] = nil

						if next(debounce[instance]) == nil then
							debounce[instance] = nil
						end
					end)
				end

				return
			end

			fire("event", instanceFromServer, event, serialize(...))
		end)
	end,

	disconnect = function(_fire, instance, event)
		if type(instance) == "string" then
			instance = game:GetService(instance)
		end

		if clientConnections[instance] and clientConnections[instance][event] then
			clientConnections[instance][event]:Disconnect()
			clientConnections[instance][event] = nil
		end
	end,
}

function EventBridge.new(fire)
	return setmetatable({
		_fire = fire,
		_storage = {},
		players = {},
	}, EventBridge)
end

function EventBridge:connect(instance, event, handler)
	if RunService:IsClient() then
		return instance[event]:Connect(handler)
	end

	if not game:IsAncestorOf(instance) then
		local connection

		connection = instance.AncestryChanged:Connect(function()
			if connection == nil then
				return
			end

			if game:IsAncestorOf(instance) then
				connection:Disconnect()
				connection = nil
				self:connect(instance, event, handler)
			end
		end)

		return
	end

	if self._storage[instance] == nil then
		self._storage[instance] = {}

		instance.Destroying:Connect(function()
			for event in self._storage[instance] do
				self:_disconnect(instance, event)
			end
		end)
	end

	self._storage[instance][event] = handler

	for _, player in self.players do
		self:_connectPlayerEvent(player, instance, event)
	end

	return {
		Disconnect = function()
			self:_disconnect(instance, event)
		end,
	}
end

function EventBridge:_connectPlayerEvent(player, instance, event)
	if instance.Parent == game then
		instance = instance.ClassName
	end

	self._fire(player, "connect", instance, event)
end

function EventBridge:_disconnectPlayerEvent(player, instance, event)
	if instance.Parent == game then
		instance = instance.ClassName
	end

	self._fire(player, "disconnect", instance, event)
end

function EventBridge:connectPlayer(player)
	for instance, events in self._storage do
		for event in events do
			self:_connectPlayerEvent(player, instance, event)
		end
	end

	table.insert(self.players, player)
end

function EventBridge:disconnectPlayer(player)
	local index = table.find(self.players, player)

	if not index then
		return
	end

	table.remove(self.players, index)

	for instance, events in self._storage do
		for event in events do
			self:_disconnectPlayerEvent(player, instance, event)
		end
	end
end

function EventBridge:_disconnect(instance, event)
	self._storage[instance][event] = nil

	for _, player in self.players do
		self:_disconnectPlayerEvent(player, instance, event)
	end
end

function EventBridge:fireEventFromPlayer(player, instance, event, ...)
	if not table.find(self.players, player) then
		warn(player, "fired a debugger event but they aren't authorized")
		return
	end

	if type(instance) == "string" then
		instance = game:GetService(instance)
	end

	if not self._storage[instance] or not self._storage[instance][event] then
		-- warn(player, "fired a debugger event but the instance has no connections")
		return
	end

	self._storage[instance][event](...)
end

return EventBridge


local UserInputService = game:GetService("UserInputService")
local CollectionService = game:GetService("CollectionService")

local tags = {
	system = "MatterDebuggerTooltip_System",
	altHover = "MatterDebuggerTooltip_AltHover",
}

local function getOffset(mousePos: Vector2, tag: string): UDim2
	if tag == tags.altHover then
		return UDim2.fromOffset(mousePos.X + 20, mousePos.Y)
	elseif tag == tags.system then
		return UDim2.fromOffset(mousePos.X + 20, mousePos.Y + 10)
	end

	return UDim2.fromOffset(mousePos.X, mousePos.Y + 10)
end

local function clientBindings(debugger)
	local connections = {}

	table.insert(
		connections,
		CollectionService:GetInstanceAddedSignal("MatterDebuggerSwitchToClientView"):Connect(function(instance)
			instance.Activated:Connect(function()
				debugger:switchToClientView()
			end)
		end)
	)

	table.insert(
		connections,
		UserInputService.InputChanged:Connect(function(input)
			if input.UserInputType ~= Enum.UserInputType.MouseMovement then
				return
			end

			local mousePosition = UserInputService:GetMouseLocation()

			for _, tag in tags do
				for _, gui in CollectionService:GetTagged(tag) do
					gui.Position = getOffset(mousePosition, tag)
				end
			end
		end)
	)

	for _, tag in tags do
		table.insert(
			connections,
			CollectionService:GetInstanceAddedSignal(tag):Connect(function(gui)
				local mousePosition = UserInputService:GetMouseLocation()
				gui.Position = getOffset(mousePosition, tag)
			end)
		)
	end

	return connections
end

return clientBindings


local stringColor = "#dcdcaa"
local numberColor = "#b0c4de"
local keywordColor = "#c586c0"
local dimmedColor = "#808080"

function colorize(value, color)
	if type(value) == "number" then
		value = string.format("%.1f", value)
	end

	return `<font color="{color}">{value}</font>`
end

function colorizeMany(color, ...)
	local values = { ... }

	for i, value in ipairs(values) do
		values[i] = colorize(value, color)
	end

	return table.unpack(values)
end

function wrap(name, value)
	return `{colorize(name, keywordColor)}({value})`
end

function commaSeparate(dots, ...)
	local str = table.concat({ ... }, ", ")

	if dots then
		str ..= ", .."
	end

	return str
end

local FormatMode = {
	Short = "Short",
	Long = "Long",
}

local function formatTable(object, mode, _padLength, _depth, _seen)
	mode = mode or FormatMode.Short
	_padLength = _padLength or 0
	_depth = _depth or 1
	_seen = _seen or {}

	if _seen[object] then
		return colorize("<cyclic>", dimmedColor)
	end
	_seen[object] = true

	local max = if mode == FormatMode.Short then 7 else 1000

	local str = ""

	if mode == FormatMode.Short or _depth > 1 then
		str ..= "{"
	end

	local values = {}

	for key, value in pairs(object) do
		table.insert(values, {
			key = key,
			value = value,
		})
	end

	table.sort(values, function(a, b)
		return tostring(a.key) < tostring(b.key)
	end)

	local count = 0
	for _, entry in values do
		local key = entry.key
		local value = entry.value

		local part = ""

		if count > 0 then
			part ..= ", "
		end

		if mode == FormatMode.Long and (if count == 0 then _depth > 1 else true) then
			part ..= `\n{string.rep("  ", _depth - 1)}`
		end

		count += 1
		if type(key) == "string" then
			part ..= key .. (if mode == FormatMode.Short then "=" else " = ")
		elseif type(key) == "table" then
			if mode == FormatMode.Short then
				part ..= "[{..}]="
			else
				part ..= "["
				part ..= formatTable(key, FormatMode.Short, #str + #part + _padLength, _depth + 1, _seen)
				part ..= "] = "
			end
		end

		local luaType = type(value)
		local robloxType = typeof(value)

		local valueStr = tostring(value)

		if luaType == "string" then
			part ..= colorize(`"{value:sub(1, max)}"`, stringColor)
		elseif luaType == "number" then
			part ..= colorize(valueStr, numberColor)
		elseif luaType == "boolean" then
			part ..= colorize(valueStr, keywordColor)
		elseif luaType == "table" then
			if mode == FormatMode.Short then
				part ..= "{..}"
			else
				part ..= formatTable(value, FormatMode.Long, #str + #part + _padLength, _depth + 1, _seen)
			end
		elseif mode == FormatMode.Long and (luaType == "userdata" or luaType == "vector") then
			if robloxType == "CFrame" then
				local x, y, z = value:components()
				local separated = commaSeparate(true, colorizeMany(numberColor, x, y, z))
				part ..= wrap("CFrame", separated)
			elseif robloxType == "Vector3" then
				local separated = commaSeparate(false, colorizeMany(numberColor, value.X, value.Y, value.Z))
				part ..= wrap("Vector3", separated)
			elseif robloxType == "Vector2" then
				local separated = commaSeparate(false, colorizeMany(numberColor, value.X, value.Y))
				part ..= wrap("Vector2", separated)
			else
				part ..= wrap(robloxType, valueStr)
			end
		else
			part ..= valueStr:sub(1, max)
		end

		if mode == FormatMode.Short and #str + #part + _padLength > 30 then
			if count > 1 then
				str ..= ", "
			end

			str ..= ".."

			break
		else
			str ..= part
		end

		if mode == FormatMode.Short and #part + _padLength > 30 then
			part ..= ", .."
			break
		end
	end

	if mode == FormatMode.Long and _depth > 1 and #values > 0 then
		str ..= `\n{string.rep("  ", _depth - 2)}`
	end

	if mode == FormatMode.Short or _depth > 1 then
		str ..= "}"
	end

	_seen[object] = nil

	return str
end

return {
	formatTable = formatTable,
	FormatMode = FormatMode,
}


local function getAllComponentData(world, entity: number): { [{ [any]: any }]: { [any]: any } }
	local componentToData = {}

	local location = world.allEntities[entity]
	local archetype = location.archetype
	local indexInArchetype = location.indexInArchetype

	for index, field in archetype.fields do
		local componentId = archetype.componentIds[index]
		local component = world.componentIdToComponent[componentId]
		local data = field[indexInArchetype]

		componentToData[component] = data
	end

	return componentToData
end

return getAllComponentData


local useCurrentSystem = require(script.Parent.Parent.topoRuntime).useCurrentSystem

local widgets = {
	"arrow",
	"blur",
	"button",
	"checkbox",
	"error",
	"portal",
	"row",
	"slider",
	"spinner",
	"window",
	"label",
	"heading",
	"space",
	"table",
}

local dummyHandles = {
	checkbox = {
		clicked = function()
			return false
		end,
		checked = function()
			return false
		end,
	},

	button = {
		clicked = function()
			return false
		end,
	},

	slider = function(config)
		if type(config) == "table" then
			config = config.initial
		end
		return config
	end,

	window = {
		closed = function()
			return false
		end,
	},

	table = {
		selected = function() end,
		hovered = function() end,
	},
}

local function hookWidgets(debugger)
	local hookedWidgets = {}

	for _, name in widgets do
		local widget = debugger.plasma[name]

		hookedWidgets[name] = function(...)
			local debugSystem = debugger.debugSystem

			if debugSystem == nil or debugSystem ~= useCurrentSystem() then
				local dummyHandle = dummyHandles[name]
				if type(dummyHandle) == "function" then
					dummyHandle = dummyHandle(...)
				end
				return dummyHandle
			end

			if debugger._windowCount > 0 then
				return widget(...)
			end

			local args = table.pack(...)

			local parent = if name == "window" then debugger.parent else debugger.frame

			local returnValue
			debugger.plasma.portal(parent, function()
				returnValue = widget(unpack(args, 1, args.n))
			end)

			return returnValue
		end
	end

	local window = hookedWidgets.window
	hookedWidgets.window = function(title, fn)
		return window(title, function()
			debugger._windowCount += 1
			fn()
			debugger._windowCount -= 1
		end)
	end

	return hookedWidgets
end

return hookWidgets


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

local defaultRaycastParams = RaycastParams.new()
defaultRaycastParams.IgnoreWater = true

function getInstanceOnMouse(params: RaycastParams)
	local camera = workspace.CurrentCamera
	if not camera then
		return
	end

	local mouseLocation = UserInputService:GetMouseLocation()
	local ray = camera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)

	local result = workspace:Raycast(ray.Origin, ray.Direction * 1000, params)
	return result and result.Instance
end

local function mouseHighlight(debugger, remoteEvent)
	if not RunService:IsClient() then
		error("Hovering can only be checked on the client")
	end

	local lastSent, setLastSent = debugger.plasma.useState()

	if UserInputService:IsKeyDown(Enum.KeyCode.LeftAlt) then
		local instance = getInstanceOnMouse(debugger.raycastParams or defaultRaycastParams)

		if instance then
			local id
			while instance.Parent do
				id = instance:GetAttribute(debugger:_isServerView() and "serverEntityId" or "clientEntityId")

				if id then
					break
				end

				instance = instance.Parent
			end

			if id then
				if UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) then
					if debugger:_isServerView() then
						remoteEvent:FireServer("inspect", id)
					else
						debugger.debugEntity = id
					end
				else
					debugger.plasma.highlight(instance, {
						fillColor = Color3.fromRGB(218, 62, 62),
					})

					if debugger:_isServerView() then
						if lastSent ~= id then
							setLastSent(id)
							remoteEvent:FireServer("hover", id)
						end
					else
						debugger.hoverEntity = id
					end
				end

				return
			end
		end
	end

	if debugger:_isServerView() then
		if lastSent ~= nil then
			remoteEvent:FireServer("hover", nil)
			setLastSent(nil)
		end
	else
		debugger.hoverEntity = nil
	end
end

return mouseHighlight


local RunService = game:GetService("RunService")
local rollingAverage = require(script.Parent.Parent.rollingAverage)
local World = require(script.Parent.Parent.World)

local function systemName(system)
	local systemFn = if type(system) == "table" then system.system else system
	local name = debug.info(systemFn, "n")

	if name ~= "" and name ~= "system" then
		return name
	end

	local source = debug.info(systemFn, "s")
	local segments = string.split(source, ".")

	return segments[#segments]
end

local timeUnits = { "s", "ms", "μs", "ns" }
local function formatDuration(duration)
	local unit = 1
	while duration < 1 and unit < #timeUnits do
		duration *= 1000
		unit += 1
	end

	return duration, timeUnits[unit]
end

local IS_SERVER = RunService:IsServer()
local IS_CLIENT = RunService:IsClient()
local LONG_SYSTEM_NAME = 24

local function ui(debugger, loop)
	local plasma = debugger.plasma
	local custom = debugger._customWidgets
	local skipSystems = loop._skipSystems

	plasma.setStyle({
		primaryColor = Color3.fromHex("bd515c"),
	})

	local objectStack = plasma.useState({})
	local openWorld, setOpenWorld = plasma.useState()

	local debugEntityWorld

	for _, world in loop._worlds do
		if debugger.hoverEntity and world:contains(debugger.hoverEntity) then
			custom.hoverInspect(world, debugger.hoverEntity, custom)
		end

		if debugger.debugEntity and world:contains(debugger.debugEntity) then
			debugEntityWorld = world
		end
	end

	local hoveredSystem

	custom.container(function()
		if debugger:_isServerView() then
			return
		end

		custom.panel(function()
			if
				custom
					.realmSwitch({
						left = "client",
						right = "server",
						isRight = IS_SERVER,
						tag = if IS_SERVER then "MatterDebuggerSwitchToClientView" else nil,
					})
					:clicked()
			then
				if IS_CLIENT then
					debugger:switchToServerView()
				end
			end

			plasma.space(15)
			plasma.heading("STATE")
			plasma.space(10)

			local items = {}

			for index, world in loop._worlds do
				local selected = openWorld == world

				local name = if type(index) == "number" then `World {index}` else index

				table.insert(items, {
					text = name,
					icon = "🌐",
					object = world,
					selected = selected,
					isWorld = true,
				})
			end

			for index, value in loop._state do
				if type(value) ~= "table" or getmetatable(value) == World then
					continue
				end

				local name = debugger.loopParameterNames[index]
				local defaultName = if name then name else `table {index}`

				local selected = (#objectStack > 0 and value == objectStack[#objectStack].value)

				table.insert(items, {
					text = defaultName,
					icon = "{}",
					object = value,
					selected = selected,
					isWorld = false,
				})
			end

			local selectedState = custom.selectionList(items):selected()

			if selectedState then
				if selectedState.isWorld then
					if openWorld == selectedState.object then
						setOpenWorld(nil)
					else
						setOpenWorld(selectedState.object)
					end
				else
					local previousFirstValue = if objectStack[1] then objectStack[1].value else nil
					table.clear(objectStack)

					if selectedState.object ~= previousFirstValue then
						objectStack[1] = {
							key = selectedState.text,
							icon = selectedState.icon,
							value = selectedState.object,
						}
					end
				end
			end

			local sortByDuration, setSortByDuration = plasma.useState(false)

			plasma.space(15)
			plasma.row({ verticalAlignment = Enum.VerticalAlignment.Center }, function()
				plasma.heading("SYSTEMS")

				if plasma.button(`sort by: {sortByDuration and "duration" or "order"}`):clicked() then
					setSortByDuration(not sortByDuration)
				end
			end)

			plasma.space(10)

			local durations = {}
			local longestDuration = 0

			for _, eventName in debugger._eventOrder do
				local systems = loop._orderedSystemsByEvent[eventName]

				if not systems then
					continue
				end

				plasma.heading(eventName, {
					font = Enum.Font.Gotham,
				})
				plasma.space(5)

				local listOfSystems = {}

				for _, system in systems do
					local samples = loop.profiling[system]
					if samples then
						local duration = rollingAverage.getAverage(samples)
						durations[system] = duration
						longestDuration = math.max(longestDuration, duration)
					end
				end

				for index, system in systems do
					local averageFrameTime = ""
					local icon

					local duration = durations[system] or 0
					local humanDuration, unit = formatDuration(duration)
					averageFrameTime = string.format("%.0f%s", humanDuration, unit)

					if duration > 0.004 then -- 4ms
						icon = "\xe2\x9a\xa0\xef\xb8\x8f"
					end

					if loop._systemErrors[system] then
						icon = "\xf0\x9f\x92\xa5"
					end

					local barWidth
					if longestDuration == 0 then
						barWidth = 0
					else
						barWidth = duration / longestDuration
					end

					local systemIsDisabled = skipSystems[system]
					local systemName = systemName(system)
					local length = string.len(systemName)

					if systemIsDisabled then
						length += 4
					end

					table.insert(listOfSystems, {
						text = if systemIsDisabled then `<i><s>{systemName}</s></i>` else systemName,
						sideText = if systemIsDisabled then `<b>{"(disabled)"}</b>` else averageFrameTime,
						selected = debugger.debugSystem == system,
						system = system,
						icon = icon,
						barWidth = barWidth,
						index = index,
					})
				end

				if sortByDuration then
					table.sort(listOfSystems, function(a, b)
						return (durations[a.system] or 0) > (durations[b.system] or 0)
					end)
				end

				local systemList = custom.selectionList(listOfSystems, custom)
				local selected = systemList:selected()
				local rightClicked = systemList:rightClicked()
				hoveredSystem = systemList:hovered()

				if selected then
					local selectedSystem = selected.system
					if selectedSystem == debugger.debugSystem then
						debugger.debugSystem = nil
					else
						debugger.debugSystem = selectedSystem
					end
				elseif rightClicked then
					local rightClickedSystem = rightClicked.system
					if rightClickedSystem then
						skipSystems[rightClickedSystem] = not skipSystems[rightClickedSystem]
					end
				end

				if debugger.debugSystem then
					debugger.debugSystemRuntime = durations[debugger.debugSystem]
				else
					debugger.debugSystemRuntime = nil
				end

				plasma.space(10)
			end
		end)

		debugger.parent = custom.container(function()
			if openWorld then
				local closed = custom.worldInspect(debugger, openWorld, objectStack)

				if closed then
					setOpenWorld(nil)
				end
			end

			if debugEntityWorld and debugger.debugEntity then
				custom.entityInspect(debugger, debugEntityWorld)
			end

			if #objectStack > 0 then
				custom.valueInspect(objectStack, custom)
			end

			if debugger.debugSystem then
				local queriesOpen, setQueriesOpen = plasma.useState(false)
				local logsOpen, setLogsOpen = plasma.useState(true)

				if loop._systemLogs[debugger.debugSystem] == nil then
					loop._systemLogs[debugger.debugSystem] = {}
				end

				local numLogs = #loop._systemLogs[debugger.debugSystem]

				local name = systemName(debugger.debugSystem)

				local closed = plasma
					.window({
						title = "System",
						closable = true,
					}, function()
						plasma.useKey(name)

						if plasma.button(string.format("View queries (%d)", #debugger._queries)):clicked() then
							setQueriesOpen(true)
						end

						if numLogs > 0 then
							if plasma.button(string.format("View logs (%d)", numLogs)):clicked() then
								setLogsOpen(true)
							end
						end

						local currentlyDisabled = skipSystems[debugger.debugSystem]

						if
							plasma
								.checkbox("Disable System", {
									checked = currentlyDisabled,
								})
								:clicked()
						then
							skipSystems[debugger.debugSystem] = not currentlyDisabled
						end
					end)
					:closed()

				if queriesOpen then
					local closed = custom.queryInspect(debugger)

					if closed then
						setQueriesOpen(false)
					end
				end

				if loop._systemErrors[debugger.debugSystem] then
					custom.errorInspect(debugger, custom)
				end

				plasma.useKey(name)

				if numLogs > 0 and logsOpen then
					local closed = plasma
						.window({
							closable = true,
							title = "Logs",
						}, function()
							local items = {}
							for i = numLogs, 1, -1 do
								table.insert(items, { loop._systemLogs[debugger.debugSystem][i] })
							end
							plasma.table(items, {
								font = Enum.Font.Code,
							})
						end)
						:closed()

					if closed then
						setLogsOpen(false)
					end
				end

				if closed then
					debugger.debugSystem = nil
				end
			end

			plasma.useKey(nil)
			debugger.frame = custom.frame()
		end, {
			marginTop = 46,
			marginLeft = 10,
			direction = Enum.FillDirection.Horizontal,
		})
	end, {
		direction = Enum.FillDirection.Horizontal,
		padding = 0,
	})

	if hoveredSystem and hoveredSystem.system then
		local hoveredSystemName = systemName(hoveredSystem.system)
		local length = string.len(hoveredSystemName)
		local systemDisabled = skipSystems[hoveredSystem.system]

		if systemDisabled then
			length += 2
		end

		if length >= LONG_SYSTEM_NAME then
			custom.tooltip(`<b>{hoveredSystemName}{if systemDisabled then " (disabled)" else ""}</b>`, {
				tag = "MatterDebuggerTooltip_System",
				backgroundTransparency = 0,
			})
		end
	end
end

return ui


return function(Plasma)
	local create = Plasma.create

	return Plasma.widget(function(text, options)
		options = options or {}

		local refs = Plasma.useInstance(function(ref)
			return create("TextButton", {
				[ref] = "label",
				BackgroundTransparency = 1,
				Text = "",
				AutomaticSize = Enum.AutomaticSize.Y,
				Font = Enum.Font.Code,
				TextSize = 18,
				TextStrokeTransparency = 0.5,
				TextXAlignment = Enum.TextXAlignment.Left,
				TextYAlignment = Enum.TextYAlignment.Top,
				TextWrapped = true,
				TextColor3 = Color3.new(1, 1, 1),
				Size = UDim2.new(0, 800, 0, 0),

				create("UIPadding", {
					PaddingBottom = UDim.new(0, 8),
					PaddingLeft = UDim.new(0, 8),
					PaddingRight = UDim.new(0, 8),
					PaddingTop = UDim.new(0, 8),
				}),
			})
		end)

		refs.label.Text = text
	end)
end


return function(Plasma)
	local create = Plasma.create

	return Plasma.widget(function(fn, options)
		options = options or {}

		local padding = options.padding or 5

		local refs = Plasma.useInstance(function(ref)
			return create("Frame", {
				[ref] = "frame",
				BackgroundTransparency = 1,
				Position = UDim2.new(0, 0, 0, 0),
				Size = UDim2.new(1, 0, 1, 0),
				AutoLocalize = false,

				create("UIPadding", {
					PaddingTop = UDim.new(0, options.marginTop or 0),
					PaddingLeft = UDim.new(0, options.marginLeft or 0),
				}),

				create("UIListLayout", {
					SortOrder = Enum.SortOrder.LayoutOrder,
					FillDirection = options.direction or Enum.FillDirection.Vertical,
					Padding = UDim.new(0, padding),
				}),
			})
		end)

		Plasma.scope(fn)

		return refs.frame
	end)
end


return function(plasma)
	return plasma.widget(function(debugger, custom)
		local loop = debugger.loop

		plasma.window("\xf0\x9f\x92\xa5 Errors", function()
			local text, setText = plasma.useState("")

			custom.codeText(text)

			local items = {}
			for index, errorData in loop._systemErrors[debugger.debugSystem] do
				local preview =
					errorData.error:gsub("^(.-):", ""):gsub("^%s?[%w%.]+%.(%w+:)", "%1"):gsub("\n", " "):sub(1, 60)

				items[index] = {
					DateTime.fromUnixTimestamp(errorData.when):ToIsoDate(),
					preview,

					errorData = errorData,
					selected = errorData.error == text,
				}
			end

			plasma.row(function()
				local selected = plasma
					.table(items, {
						selectable = true,
						font = Enum.Font.Code,
					})
					:selected()

				if selected then
					setText(selected.errorData.error)
				end

				if plasma.button("Clear"):clicked() then
					loop._systemErrors[debugger.debugSystem] = nil
				end
			end)
		end)
	end)
end


return function(Plasma)
	return Plasma.widget(function()
		local refs = Plasma.useInstance(function(ref)
			local style = Plasma.useStyle()

			local Frame = Instance.new("Frame")
			Frame.BackgroundColor3 = style.bg2
			Frame.Position = UDim2.new(0.5, 0, 0.5, 0)
			Frame.AnchorPoint = Vector2.new(0.5, 0.5)
			Frame.Size = UDim2.new(0, 50, 0, 40)
			Frame.Visible = false
			Frame.AutoLocalize = false

			local UICorner = Instance.new("UICorner")
			UICorner.Parent = Frame

			local UIPadding = Instance.new("UIPadding")
			UIPadding.PaddingBottom = UDim.new(0, 10)
			UIPadding.PaddingLeft = UDim.new(0, 10)
			UIPadding.PaddingRight = UDim.new(0, 10)
			UIPadding.PaddingTop = UDim.new(0, 10)
			UIPadding.Parent = Frame

			local UIStroke = Instance.new("UIStroke")
			UIStroke.Parent = Frame

			local UIListLayout = Instance.new("UIListLayout")
			UIListLayout.SortOrder = Enum.SortOrder.LayoutOrder
			UIListLayout.Parent = Frame

			local numChildren = #Frame:GetChildren()

			Plasma.automaticSize(Frame)

			local function updateVisibility()
				Frame.Visible = #Frame:GetChildren() > numChildren
			end

			Frame.ChildAdded:Connect(updateVisibility)
			Frame.ChildRemoved:Connect(updateVisibility)

			ref.frame = Frame

			return Frame
		end)

		return refs.frame
	end)
end


local formatTableModule = require(script.Parent.Parent.formatTable)
local getAllComponentData = require(script.Parent.Parent.getAllComponentData)
local formatTable = formatTableModule.formatTable
local FormatMode = formatTableModule.FormatMode

return function(plasma)
	return plasma.widget(function(world, id, custom)
		local entityData = getAllComponentData(world, id)

		local str = "<b>Entity " .. id .. "</b>\n\n"

		for component, componentData in pairs(entityData) do
			str ..= tostring(component) .. " "

			if next(componentData) == nil then
				str ..= "{ }\n"
			else
				str ..= (formatTable(componentData, FormatMode.Long, 0, 2) .. "\n")
			end
		end

		custom.tooltip(str, {
			tag = "MatterDebuggerTooltip_AltHover",
			backgroundTransparency = 0.15,
		})
	end)
end


return function(Plasma)
	local create = Plasma.create

	return Plasma.widget(function(text, options)
		options = options or {}

		local clicked, setClicked = Plasma.useState(false)
		local style = Plasma.useStyle()

		local refs = Plasma.useInstance(function(ref)
			local colorHover = style.textColor

			local darker = colorHover.R * 255 * 0.8 -- 20% darker
			local color = Color3.fromRGB(darker, darker, darker)

			local button = create("TextButton", {
				[ref] = "button",
				BackgroundTransparency = 1,
				Text = "",

				create("UIPadding", {
					PaddingBottom = UDim.new(0, 0),
					PaddingLeft = UDim.new(0, 8),
					PaddingRight = UDim.new(0, 8),
					PaddingTop = UDim.new(0, 0),
				}),

				create("UIListLayout", {
					SortOrder = Enum.SortOrder.LayoutOrder,
					FillDirection = Enum.FillDirection.Horizontal,
				}),

				create("TextLabel", {
					Name = "Icon",
					BackgroundTransparency = 1,
					Size = UDim2.new(0, 30, 1, 0),
					Text = options.icon,
					TextXAlignment = Enum.TextXAlignment.Left,
					TextSize = 16,
					TextColor3 = style.textColor,
					Font = Enum.Font.GothamBold,
				}),

				create("TextLabel", {
					[ref] = "mainText",
					Name = "MainText",
					BackgroundTransparency = 1,
					Size = UDim2.new(0, 0, 1, 0),
					Text = text,
					TextXAlignment = Enum.TextXAlignment.Left,
					TextColor3 = color,
					TextSize = 14,
				}),

				Activated = function()
					if options.disabled then
						return
					end

					setClicked(true)
				end,

				MouseEnter = function()
					if options.disabled then
						return
					end

					ref.button.MainText.TextColor3 = colorHover
				end,

				MouseLeave = function()
					ref.button.MainText.TextColor3 = color
				end,
			})

			Plasma.automaticSize(button)
			Plasma.automaticSize(ref.mainText, {
				axis = Enum.AutomaticSize.X,
			})

			return button
		end)

		refs.button.MainText.Text = text

		refs.button.Icon.Text = options.icon or ""
		refs.button.Icon.Visible = not not options.icon

		refs.mainText.Font = options.font or Enum.Font.Gotham

		return {
			clicked = function()
				if clicked then
					setClicked(false)
					return true
				end

				return false
			end,
		}
	end)
end


return function(Plasma)
	local create = Plasma.create
	return Plasma.widget(function(children, options)
		options = options or {}

		Plasma.useInstance(function()
			local style = Plasma.useStyle()

			local frame = create("Frame", {
				Name = "Panel",
				BackgroundColor3 = style.bg2,
				Position = UDim2.new(0, 0, 0, 0),
				Size = UDim2.new(0, 260, 1, 0),

				create("Frame", {
					-- Account for GUI inset
					-- GuiService:GetGuiInset returns wrong info on the server :(
					Size = UDim2.new(1, 0, 0, 46),
					BackgroundColor3 = Color3.new(0, 0, 0),
					BackgroundTransparency = 0.5,

					create("ImageLabel", {
						Position = UDim2.new(1, -20, 0.5, 0),
						AnchorPoint = Vector2.new(1, 0.5),
						BackgroundTransparency = 1,
						Size = UDim2.new(0, 120, 0, 26),
						Image = "rbxassetid://10111567777",
					}),
				}),

				create("UIStroke", {}),

				create("UIListLayout", {
					SortOrder = Enum.SortOrder.LayoutOrder,
					HorizontalAlignment = Enum.HorizontalAlignment.Center,
				}),

				create("ScrollingFrame", {
					BackgroundTransparency = 1,
					Name = "Container",
					VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar,
					HorizontalScrollBarInset = Enum.ScrollBarInset.ScrollBar,
					BorderSizePixel = 0,
					ScrollBarThickness = 6,
					Size = UDim2.new(1, -40, 0, 0),

					create("UIPadding", {
						PaddingTop = UDim.new(0, 20),
					}),

					create("UIListLayout", {
						SortOrder = Enum.SortOrder.LayoutOrder,
					}),
				}),
			})

			Plasma.automaticSize(frame.Container, {
				axis = Enum.AutomaticSize.Y,
				maxSize = UDim2.new(1, 0, 1, -46),
			})

			return frame, frame.Container
		end)

		Plasma.scope(children)
	end)
end


local CollectionService = game:GetService("CollectionService")

return function(Plasma)
	local create = Plasma.create

	return Plasma.widget(function(options)
		local style = Plasma.useStyle()

		options = options or {}
		local left = options.left
		local right = options.right
		local isRight = options.isRight

		local clicked, setClicked = Plasma.useState(false)
		local refs = Plasma.useInstance(function(ref)
			ref.corner = create("UICorner")

			local style = Plasma.useStyle()

			create("TextButton", {
				[ref] = "button",
				BackgroundTransparency = 1,
				Size = UDim2.new(1, 0, 0, 25),
				Text = "",

				create("UICorner"),

				create("UIListLayout", {
					FillDirection = Enum.FillDirection.Horizontal,
				}),

				create("TextLabel", {
					[ref] = "left",
					Text = left,
					Size = UDim2.new(0.5, 0, 1, 0),
					BackgroundColor3 = style.primaryColor,
					BorderSizePixel = 0,
					TextColor3 = style.textColor,
					Font = Enum.Font.GothamMedium,
					TextSize = 14,
				}),

				create("TextLabel", {
					[ref] = "right",
					Text = right,
					Size = UDim2.new(0.5, 0, 1, 0),
					BackgroundColor3 = style.bg1,
					BorderSizePixel = 0,
					TextColor3 = style.textColor,
					Font = Enum.Font.GothamMedium,
					TextSize = 14,
				}),

				MouseEnter = function()
					local other = isRight and ref.left or ref.right
					other.BackgroundTransparency = 0.5
				end,

				MouseLeave = function()
					local other = isRight and ref.left or ref.right
					other.BackgroundTransparency = 0
				end,

				Activated = function()
					setClicked(true)
				end,
			})

			if options.tag then
				CollectionService:AddTag(ref.button, options.tag)
			end

			return ref.button
		end)

		refs.left.BackgroundColor3 = isRight and style.bg1 or style.primaryColor
		refs.right.BackgroundColor3 = isRight and style.primaryColor or style.bg1

		refs.corner.Parent = isRight and refs.right or refs.left

		local handle = {
			clicked = function()
				if clicked then
					setClicked(false)
					return true
				end

				return false
			end,
		}

		return handle
	end)
end