--!strict

local Style = require("../Style")
local Selector = require("../Small/Selector")
local ClassIcon = require("../Small/ClassIcon")
local Types = require("../../Types")
local InstClasses = require("../Info/InstClasses")
local Constants = require("../../Constants")

local rule = Style.rule
local var = Style.var
local cloned = Style.cloned

local getClassTagSlctr = Selector.getClassTagSlctr

local studioSettings = settings().Studio

return function(Manager: Types.Manager): ({[string]: any}, {Style.Rule})
	local theme = studioSettings.Theme :: StudioTheme

	local defaultTokens = {
		["SelectedColor"] = Color3.fromRGB(15, 91, 153),
		["ScriptErrorColor"] = Color3.fromRGB(255, 44, 44),
		["ScriptWarnColor"] = Color3.fromRGB(255, 211, 53),
		["HoverColor"] = var "brightness($BackgroundColor) > 0.5 ? $BackgroundColor - rgb(30, 30, 30) : $BackgroundColor + rgb(30, 30, 30)",

		["BackgroundColor"] = theme:GetColor(Enum.StudioStyleGuideColor.MainBackground),
		["DarkBackgroundColor"] = var "$BackgroundColor - rgb(6, 6, 6)",
		["LightBackgroundColor"] = var "$BackgroundColor + rgb(10, 10, 10)",
		["DarkerBackgroundColor"] = var "$DarkBackgroundColor - rgb(10, 10, 10)",

		["TextColor"] = var "invert($BackgroundColor)",
		["DefaultTextColor"] = var "$TextColor - rgb(80, 80, 80)",
		["TextColorDark"] = var "$TextColor - rgb(130, 130, 130)",

		["MonochromeColor"] = var "invert($BackgroundColor)",

		["InstanceFrameTextSize"] = 14,
		["InstanceFrameTextColor"] = var "$TextColor",
		["InstanceFrameSize"] = UDim2.new(1, 0, 0, 20),

		["IconSizeOffset"] = UDim2.fromOffset(16, 16),
		["TopbarSize"] = UDim2.new(1, 0, 0, 20),
		["PathBarSize"] = UDim2.new(1, 0, 0, 20),
		["TabBarSize"] = UDim2.new(1, 0, 0, 20),
		["ScrollbarWidthOffset"] = 12,
		["ScrollbarColor"] = var "brightness($BackgroundColor) > 0.5 ? $DarkerBackgroundColor - rgb(70, 70, 70) : $DarkerBackgroundColor - rgb(210, 210, 210)",
		["QueryInputTextSize"] = 11,
		["Theme"] = var "brightness($BackgroundColor) > 0.5 ? 'Light' : 'Dark'",

		["LightArrowImage"] = "rbxasset://studio_svg_textures/Shared/Navigation/Dark/Standard/ArrowDown.png",
		["DarkArrowImage"] = "rbxasset://studio_svg_textures/Shared/Navigation/Light/Standard/ArrowDown.png",
		["InvertedArrowImage"] = var "'rbxasset://studio_svg_textures/Shared/Navigation/![$SelectedTheme]/Standard/ArrowDown.png'",
		["ArrowImage"] = var "$LegacyDownArrowImage",

		["LegacyDownArrowImage"] = var "'rbxasset://studio_svg_textures/Shared/Navigation/![$Theme]/Standard/ArrowDown.png'",
		["FadedColor"] = var "brightness($BackgroundColor) > 0.5 ? $BackgroundColor - rgb(100, 100, 100) : $BackgroundColor + rgb(100, 100, 100)",
		["SelectedTheme"] = var "brightness($SelectedColor) > 0.5 ? 'Light' : 'Dark'",
		["SelectedMonochromeColor"] = var "brightness($SelectedColor) > 0.5 ? rgb(0, 0, 0) : rgb(255, 255, 255)",
		["CornerRadius"] = UDim.new(0, 6),
		["TabPickingRootsColor"] = Color3.fromRGB(34, 112, 29),
		["Icon16x16AspectRatio"] = 0.96
	}

	--local rule = function(...)
	--	local newrule = _rule(...)
	--	newrule:AddTag("Default")
	--	newrule:AddTag("DontSaveRule")
	--	return newrule
	--end :: typeof(_rule)

	local defaultRules = {
		rule("NoRichText", "TextButton, TextLabel, TextBox", 0, {
			RichText = false
		}),
		rule("UniversalProps", "GuiObject", 0, {
			BorderSizePixel = 0,
			BorderColor3 = Color3.fromRGB(0, 0, 0),
			Active = true
		}),
		rule("ScrollingFrameStyle", ".JEXP_ScrollingFrame", 0, {
			AnchorPoint = Vector2.new(0.5, 1),
			BackgroundTransparency = 1,
			Position = UDim2.new(0.5, 0, 1, 0),
			ZIndex = 5,
			AutomaticCanvasSize = Enum.AutomaticSize.XY,
			BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
			TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
			HorizontalScrollBarInset = Enum.ScrollBarInset.Always,
			VerticalScrollBarInset = Enum.ScrollBarInset.Always,
			ScrollBarThickness = "$ScrollbarWidthOffset",
			ScrollBarImageTransparency = 0,
			ScrollBarImageColor3 = "$ScrollbarColor",
		}),
		rule("ScrollBackgroundHorizontalStyle", ".JEXP_ScrollBackgroundHorizontal", 0, {
			AnchorPoint = Vector2.new(0, 1),
			BackgroundTransparency = 0,
			Position = UDim2.new(0, 0, 1, 0),
			Size = var "udim2(1, 0, 0, $ScrollbarWidthOffset)",
		}),
		rule("ScrollBackgroundVerticalStyle", ".JEXP_ScrollBackgroundVertical", 0, {
			AnchorPoint = Vector2.new(1, 0),
			BackgroundTransparency = 0,
			Position = UDim2.new(1, 0, 0, 0),
			Size = var "udim2(0, $ScrollbarWidthOffset, 1, 0)",
		}),
		rule("JExplorerUIStyle", "#JexplorerUi", 10, {
			Size = UDim2.fromScale(1, 1),

			rule("InstanceTreeStyle", "> #InstanceTree Frame", 2, {
				Size = var "udim2(1, 0, 1, -($TopbarSize.Y.Offset + $PathBarSize.Y.Offset + $TabBarSize.Y.Offset))",
				AutomaticCanvasSize = Enum.AutomaticSize.None,
				ClipDescendants = true,
				AnchorPoint = Vector2.new(0.5, 1),
				BackgroundTransparency = 1,
				Position = UDim2.new(0.5, 0, 1, 0),
				ZIndex = 5,

				rule("ScrollContainerStyle", "> #ScrollContainer Frame", 2, {
					Size = UDim2.fromScale(1, 1),
					BackgroundTransparency = 1,
				}),
				rule("TreeScrollerStyle", "> #TreeScroller ScrollingFrame", 3, {
					Size = UDim2.fromScale(1, 1),
					BackgroundTransparency = 1,
				})
			}),
			rule("TopbarStyle", "> #Topbar", 0, {
				ZIndex = 10,
				Size = "$TopbarSize",
				Position = var "udim2(0, 0, 0, $TabBarSize.Y.Offset)",

				rule("QueryFrameStyle", "> #QueryInputFrame", 0, {
					AnchorPoint = Vector2.new(0, 0.5),
					Position = var "udim2(0, $IconSizeOffset.X.Offset + 5, 0.5, 0)",
					Size = UDim2.fromScale(0.9, 0.8),

					rule("RoundedCorner", "::UICorner", 0, {
						CornerRadius = "$CornerRadius"
					}),
					rule("BorderStyle", "::UIStroke", 10, {
						Thickness = 0.5,
						LineJoinMode = Enum.LineJoinMode.Round
					}),
					rule("ScrollerStyle", "> #Scroll", 0, {
						AutomaticCanvasSize = Enum.AutomaticSize.X,
						BackgroundTransparency = 1,
						ScrollBarThickness = 1,
						Size = UDim2.new(1, -39, 1, 0),
						BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						HorizontalScrollBarInset = Enum.ScrollBarInset.Always,
						VerticalScrollBarInset = Enum.ScrollBarInset.Always,
						ScrollBarImageTransparency = 0,
						ScrollBarImageColor3 = "$ScrollbarColor",

						rule("QueryBarStyle", "> #QueryInput TextBox", 0, {
							ClearTextOnFocus = false,
							PlaceholderColor3 = "$DefaultTextColor",
							PlaceholderText = "Query Components...",
							BackgroundTransparency = 1,
							FontFace = Font.fromEnum(Enum.Font.SourceSans),
							TextXAlignment = Enum.TextXAlignment.Left,
							Size = UDim2.fromScale(1, 1),
							AutomaticSize = Enum.AutomaticSize.X,
							TextSize = "$QueryInputTextSize",

							rule("TextPadding", "::UIPadding", 0, {
								PaddingLeft = UDim.new(0, 4)
							}),
						})
					}),
					rule("SelectQueriedButtonStyle", "> #SelectQueried ImageButton", 0, {
						AnchorPoint = Vector2.new(1, 0.5),
						AutoButtonColor = false,
						BackgroundTransparency = 1,
						Position = UDim2.new(1, -10, 0.5, 0),
						Size = UDim2.new(0, 15, 0, 15),
						Image = "rbxassetid://103293158278743",
					}),
					rule("QueryCountStyle", "> #QueryCount TextLabel", 0, {
						Text = "0",
						AnchorPoint = Vector2.new(1, 0.5),
						BackgroundTransparency = 1,
						Position = UDim2.new(1, -29, 0.5, 0),
						Size = UDim2.new(0, 15, 0, 15),
						TextWrapped = true,
						TextXAlignment = Enum.TextXAlignment.Right,
						AutomaticSize = Enum.AutomaticSize.X,
						TextSize = 8,
					})
				}),
				rule("OpenOptionsButtonStyle", "> #Options", 0, {
					AnchorPoint = Vector2.new(0, 0.5),
					BackgroundTransparency = 1,
					Position = UDim2.new(0, 2, 0.5, 0),
					Size = UDim2.fromOffset(16, 16),
					Image = "rbxassetid://121156068055943",

					rule("AspectRatioConstraint", "::UIAspectRatioConstraint", 0, {
						AspectRatio = 1
					})
				}),
				rule("TabBarStyle", "> #TabBar", 0, {
					Size = "$TabBarSize",
					Position = UDim2.fromScale(0, 0),
					AnchorPoint = Vector2.new(0, 1),

					rule("ScrollingFrameStyle", "> #Tabs ScrollingFrame", 0, {
						BackgroundTransparency = 1,
						Size = UDim2.new(1, 0, 1, 0),
						Position = var "udim2(0, $IconSizeOffset.X.Offset + 2, 0, 0)",
						--AutomaticCanvasSize = Enum.AutomaticSize.X,
						BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						CanvasSize = UDim2.new(0, 0, 0, 0),
						ElasticBehavior = Enum.ElasticBehavior.Never,
						ScrollBarThickness = 2,
						TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						MidImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						ScrollBarImageColor3 = "$ScrollbarColor",

						rule("InsertTabButtonStyle", "> #InsertTabButton TextButton", 10, {
							LayoutOrder = 999,
							Size = UDim2.new(0, 20, 0, 20)
						}),
						rule("Padding", "::UIPadding", 0, {
							PaddingLeft = UDim.new(0, 4)
						}),
						rule("ListLayout", "::UIListLayout", 0, {
							FillDirection = Enum.FillDirection.Horizontal,
							SortOrder = Enum.SortOrder.LayoutOrder,
							VerticalAlignment = Enum.VerticalAlignment.Center,
							Padding = UDim.new(0, 4)
						})
					})
				}),
				rule("PathBarStyle", "> #PathBar", 0, {
					Size = "$PathBarSize",
					AnchorPoint = Vector2.new(0, 0),
					Position = UDim2.fromScale(0, 1),

					rule("ScrollingFrameStyle", "> #Segments ScrollingFrame", 0, {
						BackgroundTransparency = 1,
						Size = UDim2.new(1, 0, 1, 0),
						AutomaticCanvasSize = Enum.AutomaticSize.X,
						BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						CanvasSize = UDim2.new(0, 0, 0, 0),
						ElasticBehavior = Enum.ElasticBehavior.Never,
						ScrollBarThickness = 2,
						TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						MidImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
						ScrollBarImageColor3 = "$ScrollbarColor",

						rule("ListLayout", "::UIListLayout", 0, {
							FillDirection = Enum.FillDirection.Horizontal,
							SortOrder = Enum.SortOrder.LayoutOrder,
							VerticalAlignment = Enum.VerticalAlignment.Center,
							Padding = UDim.new(0, 4)
						}),
						rule("PathSegmentStyle", "> #PathSegment", 0, {
							AutomaticSize = Enum.AutomaticSize.X,
							BackgroundTransparency = 1,
							Size = UDim2.new(0, 0, 1, 0),
							TextSize = 11,
							Text = "",
							FontFace = Font.fromEnum(Enum.Font.SourceSans),
							TextXAlignment = Enum.TextXAlignment.Left,
						})
					})
				})
			})
		}),

		rule("BackgroundStyle",
			getClassTagSlctr(".JEXP_Background", "GuiObject"), 0, {
				BackgroundColor3 = "$BackgroundColor"
			}
		),
		rule("LightBackgroundStyle",
			getClassTagSlctr(".JEXP_LightBackground", "GuiObject"), 0, {
				BackgroundColor3 = "$LightBackgroundColor"
			}
		),
		rule("DarkBackgroundStyle",
			getClassTagSlctr(".JEXP_DarkBackground", "GuiObject"), 0, {
				BackgroundColor3 = "$DarkBackgroundColor"
			}
		),
		rule("SelectedColor",
			getClassTagSlctr(".JEXP_SelectedColor", "GuiObject"), 12, {
				BackgroundColor3 = "$SelectedColor"
			}
		),
		rule("SelectedTextColor",
			getClassTagSlctr(".JEXP_SelectedColor", "TextButton", "TextBox", "TextLabel"), 20, {
				BackgroundColor3 = "$SelectedColor",
				TextColor3 = Color3.fromRGB(255, 255, 255)
			}
		),
		rule("DarkerBackgroundStyle",
			getClassTagSlctr(".JEXP_DarkerBackground", "GuiObject"), 0, {
				BackgroundColor3 = "$DarkerBackgroundColor"
			}
		),
		rule("ArrowStyle",
			getClassTagSlctr(".JEXP_Arrow", "ImageLabel", "ImageButton"), 1, {
				Image = "$ArrowImage",
				ImageColor3 = "$MonochromeColor",
				Size = "$IconSizeOffset"
			}
		),
		rule("MonochromeImageStyle",
			getClassTagSlctr(".JEXP_MonochromeImage", "ImageButton", "ImageLabel"), 0, {
				ImageColor3 = "$MonochromeColor"
			}
		),
		rule("MonochromeBackgroundStyle",
			getClassTagSlctr(".JEXP_MonochromeBackground", "Frame"), 0, {
				ImageColor3 = "$MonochromeColor"
			}
		),
		rule("DarkTextStyle",
			getClassTagSlctr(".JEXP_DarkText", "TextLabel", "TextButton", "TextBox"), 1, {
				TextColor3 = "$TextColorDark"
			}
		),
		rule("TextStyle",
			getClassTagSlctr(".JEXP_Text", "TextLabel", "TextButton", "TextBox"), 1, {
				TextColor3 = "$TextColor",
			}
		),
		rule("DefaultTextStyle",
			getClassTagSlctr(".JEXP_DefaultText", "TextLabel", "TextButton", "TextBox"), 1, {
				PlaceholderTextColor3 = "$DefaultTextColor",
			}
		),
		rule("FadedTextStyle",
			getClassTagSlctr(".JEXP_FadedText", "TextLabel", "TextButton", "TextBox"), 1, {
				TextColor3 = "$FadedColor"
			}
		),
		rule("UIBorder", ".JEXP_Border ::UIStroke", 0, {
			Thickness = 0.4,
			Color = var "$BackgroundColor.R > 0.5 ? rgb(100, 100, 100) : rgb(0, 0, 0)",
			ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
			LineJoinMode = Enum.LineJoinMode.Miter,

			Enabled = true,
			Transparency = 0,
		}),
		rule("GrayUIBorder", ".JEXP_GrayBorder ::UIStroke", 0, {
			Thickness = 0.4,
			Color = Color3.fromRGB(50, 50, 50),
			ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
			LineJoinMode = Enum.LineJoinMode.Miter,

			Enabled = true,
			Transparency = 0
		}),
		rule("DropTargetOutline", ".JEXP_DropTarget ::UIStroke", 0, {
			Thickness = 0.5,
			Color = "$MonochromeColor",
			ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
			LineJoinMode = Enum.LineJoinMode.Miter,

			Enabled = true,
			Transparency = 0,
		}),
		rule("CircleCorner", ".JEXP_CircleCorner ::UICorner", 0, {
			CornerRadius = UDim.new(1, 0)
		}),
		rule("TabFrameStyle", ".JEXP_TabFrame Frame", 0, {
			BackgroundColor3 = var "brightness($BackgroundColor) > 0.5 ? $DarkBackgroundColor : $LightBackgroundColor",
			AnchorPoint = Vector2.new(0, 0.5),
			rule("RoundedCorner", "::UICorner", 0, {
				CornerRadius = "$CornerRadius"
			}),
			rule("BorderStyle", "::UIStroke", 10, {
				Thickness = 0.5,
				LineJoinMode = Enum.LineJoinMode.Round
			}),
			rule("NameBoxStyle", "> #NameBox TextBox", 0, {
				Position = UDim2.new(0, 24, 0.5, 0),  -- shifted right to make room for icon
				AnchorPoint = Vector2.new(0, 0.5),
				TextXAlignment = Enum.TextXAlignment.Left,
				TextYAlignment = Enum.TextYAlignment.Center,
				ClearTextOnFocus = false,
				Interactable = true,
				TextSize = 14,
				Active = false,
				BackgroundTransparency = 1,
				FontFace = Font.fromName("SourceSans", Enum.FontWeight.Heavy)
			}),
			rule("IconStyle", "> #Icon ImageLabel", 0, {
				Size = UDim2.new(0, 16, 0, 16),
				Position = UDim2.new(0, 2, 0.5, 0),
				AnchorPoint = Vector2.new(0, 0.5),
				BackgroundTransparency = 1,

				rule("RoundCorner", "::UICorner", 0, {
					CornerRadius = "$CornerRadius"
				})
			}),
			rule("ButtonStyle", "> #Button TextButton", 0, {
				Transparency = 1,
				Size = UDim2.new(1, 0, 1, 0),
			})
		}),
		rule("SelectedTabStyle", ".JEXP_TabFrame Frame.JEXP_SelectedColor", 10, {
			rule("NameBoxStyle", "> #NameBox TextBox", 10, {
				TextColor3 = "$SelectedMonochromeColor"
			}),
		}),
		rule("SelectingTabStyle", ".JEXP_TabFrame Frame.JEXP_SelectingRoots", 10, {
			BackgroundColor3 = "$TabPickingRootsColor",
			rule("NameBoxStyle", "> #NameBox TextBox", 10, {
				TextColor3 = var "brightness($TabPickingRootsColor) > 0.5 ? rgb(0, 0, 0) : rgb(255, 255, 255)"
			}),
		}),
		rule("ExplorerFrameRulerStyle", "#StickyRuler Frame", 0, {
			Position = var "udim2(0, 20, 0, 20)",
			Visible = true,
			--Size = UDim2.new(0, 1, 1, 0),
			BackgroundColor3 = var "$BackgroundColor.R > 0.5 ? rgb(200, 200, 200) : rgb(70, 70, 70)",
			BackgroundTransparency = 0.5,
			--AutomaticSize = Enum.AutomaticSize.Y,
			ZIndex = 12
		}),
		rule("ExplorerFrameStyle", ".JEXP_ExplorerFrame Frame", 0, {
			Size = UDim2.new(1, 0, 0, 0),
			AutomaticSize = Enum.AutomaticSize.XY,
			SizeConstraint = Enum.SizeConstraint.RelativeXX,
			BackgroundTransparency = 1,
			BorderSizePixel = 0,
			ZIndex = -2,

			rule("BufferStyle", "> #Buffer Frame", 0, {
				AnchorPoint = Vector2.new(1, 0),
				AutomaticSize = Enum.AutomaticSize.Y,
				BackgroundTransparency = 1,
				BackgroundColor3 = Color3.fromRGB(255, 255, 255),
				Size = UDim2.new(0.94, 0, 0, 0)
			}),
			rule("MainFrameStyle", "> #Main", 0, {
				Size = "$InstanceFrameSize",
				BackgroundTransparency = 1,
				ZIndex = 1,

				rule("OverlayStyle", "> #Overlay Frame", 0, {
					AnchorPoint = Vector2.new(0.5, 0),
					AutomaticSize = Enum.AutomaticSize.Y,
					Size = UDim2.new(20, 0, 1, 0), -- Make it extremely long to reach all the edges
					ZIndex = 2,

					rule("IdleOverlay", ":idle", 0, {
						BackgroundTransparency = 1,
						BackgroundColor3 = "$BackgroundColor",
					}),
					rule("SelectStyle", "> #Select ImageButton", 0, {
						BackgroundTransparency = 1,
						Size = UDim2.new(1, 0, 1, 0),
						ZIndex = 2,
					})
				}),
				rule("InstanceName", getClassTagSlctr("> #InstanceName", "TextLabel", "TextBox"), 0, {
					Active = false,
					AnchorPoint = Vector2.new(0, 0.5),
					AutomaticSize = Enum.AutomaticSize.X,
					TextSize = "$InstanceFrameTextSize",
					TextColor3 = "$InstanceFrameTextColor",
					Position = UDim2.new(0, 41, 0.5, 0),

					BackgroundTransparency = 1,
					ClearTextOnFocus = false,

					Size = UDim2.new(0, 0, 0, 15),
					ZIndex = 3,
					PlaceholderColor3 = Color3.fromRGB(255, 255, 255),
					RichText = true,
					Text = "Instance",
					TextXAlignment = Enum.TextXAlignment.Left,
					FontFace = Font.fromEnum(Enum.Font.SourceSans)
				}),
				rule("ArrowStyle", "> #Arrow ImageButton", 0, {
					AnchorPoint = Vector2.new(0.5, 0.5),
					BackgroundTransparency = 1,
					Position = UDim2.new(0, 10, 0.5, 0),
					--Rotation = -90,
					ZIndex = 3,
				}),
				rule("IconStyle", "> #ClassIcon ImageLabel", 0, {
					Size = "$IconSizeOffset",
					ZIndex = 3,
					AnchorPoint = Vector2.new(0.5, 0.5),
					BackgroundTransparency = 1,
					Position = UDim2.new(0, 30, 0.5, 0),

					rule("AspectRatioConstraint", "::UIAspectRatioConstraint", 0, {
						AspectRatio = 1
					})
				}),
				rule("PackageLinkIconStyle", "> #PackageLinkIcon ImageLabel", 0, {
					AnchorPoint = Vector2.new(1, 0.5),
					BackgroundTransparency = 1,
					Position = UDim2.new(0, 0, 0.5, 0),
					Size = UDim2.new(0, 16, 0, 16),
					Image = var "'rbxasset://studio_svg_textures/Shared/Packages/![$Theme]/Standard/PackageStatus_AutoupdateOff.png'",
					ZIndex = 3,

					rule("AspectRatioConstraint", "::UIAspectRatioConstraint", 0, {
						AspectRatio = "$Icon16x16AspectRatio"
					})
				}),
				rule("PendingChangesIconStyle", "> #PendingChanges ImageLabel", 0, {
					AnchorPoint = Vector2.new(1, 0.5),
					BackgroundTransparency = 1,
					Position = UDim2.new(0, -4, 0.5, 0),
					Size = UDim2.new(0, 16, 0, 16),
					Image = var "'rbxasset://studio_svg_textures/Shared/Packages/![$Theme]/Standard/Modified@3x.png'",
					ZIndex = 3,

					rule("AspectRatioConstraint", "::UIAspectRatioConstraint", 0, {
						AspectRatio = "$Icon16x16AspectRatio"
					})
				}),
				rule("InsertButtonStyle", "> #Insert ImageButton", 0, {
					BackgroundTransparency = 1,
					AnchorPoint = Vector2.new(0, 0.5),
					Size = UDim2.new(0, 10, 0, 10),
					ZIndex = 3,
					Image = "rbxassetid://124663566816110",

					rule("AspectRatioConstraint", "::UIAspectRatioConstraint", 0, {
						AspectRatio = "$Icon16x16AspectRatio"
					})
				}),
				rule("ReclassButtonStyle", "> #Reclass ImageButton", 0, {
					BackgroundTransparency = 1,
					AnchorPoint = Vector2.new(1, 0.5),
					Size = UDim2.new(0, 16, 0, 16),
					ZIndex = 3,
					Image = var "'rbxasset://studio_svg_textures/Shared/DraggerTools/![$Theme]/Standard/Scale@2x.png'",

					rule("AspectRatioConstraint", "::UIAspectRatioConstraint", 0, {
						AspectRatio = "$Icon16x16AspectRatio"
					})
				})
			})
		}),
		rule("SelectedExplorerFrameStyle", ".JEXP_Selected Frame", 11, {
			rule("InstanceNameStyle", "> #Main > #InstanceName", 1, {
				TextColor3 = Color3.fromRGB(255, 255, 255)
			}),
			rule("IconStyle", "> #Main > #ClassIcon", 1, {
				ImageTransparency = 0
			}),
			rule("OverlayStyle", "> #Main > #Overlay", 1, {
				BackgroundTransparency = 0.2,
				BackgroundColor3 = "$SelectedColor",
			}),
			rule("ArrowStyle", "> #Main > #Arrow", 1, {
				ImageColor3 = "$SelectedMonochromeColor",
				Image = "$InvertedArrowImage"
			}),
			rule("InsertButtonStyle", "> #Main > #Insert ImageButton", 10, {
				ImageColor3 = "$SelectedMonochromeColor"
			}),
			rule("ReclassButtonStyle", "> #Main > #Reclass ImageButton", 10, {
				Image = var "'rbxasset://studio_svg_textures/Shared/DraggerTools/![$SelectedTheme]/Standard/Scale@2x.png'",
				ImageColor3 = "$SelectedMonochromeColor"
			})
		}),
		rule("ExplorerFrameStyleDisabled",
			Selector.getTaggedPropertySelector("Enabled", "false"), 10, {
				rule("InstanceNameStyle", "> #Main > #InstanceName", 1, {
					TextSize = "$InstanceFrameTextSize",
					TextColor3 = "$FadedColor"
				}),
				rule("IconStyle", "> #Main > #ClassIcon", 1, {
					ImageTransparency = 0.5
				}),
			}
		),
		rule("ScriptErrorStyle",
			".JEXP_ScriptError Frame > #Main > #Overlay", 15, {
				BackgroundTransparency = 0.6,
				BackgroundColor3 = "$ScriptErrorColor",
			}
		),
		rule("ScriptWarnStyle",
			".JEXP_ScriptWarn Frame > #Main > #Overlay", 15, {
				BackgroundTransparency = 0.6,
				BackgroundColor3 = "$ScriptWarnColor",
			}
		),
		rule("HoverEffect", ".JEXP_Hover :hover Frame", 10, {
			BackgroundTransparency = 0.6,
			BackgroundColor3 = "$HoverColor"
		}),

		rule("LocalScriptIcon", Selector.getIconSelector(nil, "LocalScript"), 1, {
			Image = var "'rbxasset://studio_svg_textures/Shared/Scripting/![$Theme]/Standard/LegacyClientRunContext.png'",
			ImageRectOffset = Vector2.zero,
			ImageRectSize = Vector2.zero
		}),
		rule("LegacyRunContextIcon", `#{Constants.ExplorerFrameScriptClassNameFormat:format("Legacy")} > #Main > #ClassIcon`, 1, {
			Image = var "'rbxasset://studio_svg_textures/Shared/Scripting/![$Theme]/Standard/LegacyServerRunContext.png'",
			ImageRectOffset = Vector2.zero,
			ImageRectSize = Vector2.zero
		}),
		rule("ServerRunContextIcon", `#{Constants.ExplorerFrameScriptClassNameFormat:format("Server")} > #Main > #ClassIcon`, 1, {
			Image = var "'rbxasset://studio_svg_textures/Shared/Scripting/![$Theme]/Standard/ServerRunContext.png'",
			ImageRectOffset = Vector2.zero,
			ImageRectSize = Vector2.zero
		}),
		rule("ClientRunContextIcon", `#{Constants.ExplorerFrameScriptClassNameFormat:format("Client")} > #Main > #ClassIcon`, 1, {
			Image = var "'rbxasset://studio_svg_textures/Shared/Scripting/![$Theme]/Standard/ClientRunContext.png'",
			ImageRectOffset = Vector2.zero,
			ImageRectSize = Vector2.zero
		}),
		rule("PluginRunContextIcon", `#{Constants.ExplorerFrameScriptClassNameFormat:format("Plugin")} > #Main > #ClassIcon`, 1, {
			Image = var "'rbxasset://studio_svg_textures/Shared/Scripting/![$Theme]/Standard/PluginRunContext.png'",
			ImageRectOffset = Vector2.zero,
			ImageRectSize = Vector2.zero
		}),

		rule("LocalScriptIcon_Selected", `.JEXP_Selected {Selector.getIconSelector(nil, "LocalScript")}`, 100, {
			Image = var "'rbxasset://studio_svg_textures/Shared/Scripting/Dark/Standard/LegacyClientRunContext.png'",
			ImageRectOffset = Vector2.zero,
			ImageRectSize = Vector2.zero
		}),
		rule("LegacyRunContextIcon_Selected", `.JEXP_Selected #{Constants.ExplorerFrameScriptClassNameFormat:format("Legacy")} > #Main > #ClassIcon`, 100, {
			Image = var "'rbxasset://studio_svg_textures/Shared/Scripting/Dark/Standard/LegacyServerRunContext.png'",
			ImageRectOffset = Vector2.zero,
			ImageRectSize = Vector2.zero
		}),
		rule("ServerRunContextIcon_Selected", `.JEXP_Selected #{Constants.ExplorerFrameScriptClassNameFormat:format("Server")} > #Main > #ClassIcon`, 100, {
			Image = var "'rbxasset://studio_svg_textures/Shared/Scripting/Dark/Standard/ServerRunContext.png'",
			ImageRectOffset = Vector2.zero,
			ImageRectSize = Vector2.zero
		}),
		rule("ClientRunContextIcon_Selected", `.JEXP_Selected #{Constants.ExplorerFrameScriptClassNameFormat:format("Client")} > #Main > #ClassIcon`, 100, {
			Image = var "'rbxasset://studio_svg_textures/Shared/Scripting/Dark/Standard/ClientRunContext.png'",
			ImageRectOffset = Vector2.zero,
			ImageRectSize = Vector2.zero
		}),
		rule("PluginRunContextIcon_Selected", `.JEXP_Selected #{Constants.ExplorerFrameScriptClassNameFormat:format("Plugin")} > #Main > #ClassIcon`, 100, {
			Image = var "'rbxasset://studio_svg_textures/Shared/Scripting/Dark/Standard/PluginRunContext.png'",
			ImageRectOffset = Vector2.zero,
			ImageRectSize = Vector2.zero
		}),
	}
	for _, v: Style.Rule in defaultRules do
		v:AddTag("Default")
		v:AddTag("DontSaveRule")
	end

	return defaultTokens, defaultRules
end


--!strict
--!optimize 2

local ScriptEditorService = game:GetService("ScriptEditorService")

local Types = require("../Types")
local Jecs = require("../Jecs")
local RawInst = require("./Small/RawInst")
local UITemplates = require("./UITemplates")
local Selector = require("./Small/Selector")
local ClassIcon = require("./Small/ClassIcon")
local Style = require("./Style")
local JexpTags = require("./Small/JexpTags")
local Constants = require("../Constants")
local stickToRightEdge = require("./Small/StickToRightEdge")
local recordChanges = require("./Small/RecordChanges")
local TextBoundsHelper = require("./Small/TextBoundsHelper")
local RulerPool = require("./RulerPool")

local getClassPropMap = require("./Small/GetClassPropMap")

local addTag, removeTag = game.AddTag, game.RemoveTag

local var = Style.var
local rule = Style.rule
local rawindex = RawInst.rawindex
local rawnewindex = RawInst.rawnewindex
local index = RawInst.index

local profilebegin, profileend = debug.profilebegin, debug.profileend

type ExplorerFrame = Types.ExplorerFrame
type ExplorerEntity = Types.ExplorerEntity
type Manager = Types.Manager

local GLOBAL_FRAME_CACHE_SIZE = 50  -- L2 cache: large, slower (parented to nil)
local INSTANCE_ICON = ClassIcon.getIcon("Instance")
assert(INSTANCE_ICON)

local RECLASS_ICON_PADDING = 8

local globalFramePool = {}

local ExplorerFrame = {} :: Types.ExplorerFrameLib

local function setFrameSelected(Manager: Manager, frame: ExplorerFrame, selected: boolean)
	local state = Manager.WidgetState
	local frameWorld = state.FrameWorld
	local components = Manager.Components

	local frameInst = assert(Manager.frameOf(frame))

	profilebegin("ExplorerFrame::ToggleSelection")
	if selected then
		if not frameWorld:has(frame, components.Selected) then
			frameWorld:add(frame, components.Selected)
			frameInst:AddTag("JEXP_Selected")
		end
	else
		if frameWorld:has(frame, components.Selected) then
			frameWorld:remove(frame, components.Selected)
			frameInst:RemoveTag("JEXP_Selected")
		end
	end
	profileend()
end

local function switchSelection(Manager: Manager, entity: ExplorerEntity, isCtrlHeld: boolean, isShiftHeld: boolean)
	profilebegin("Jexplorer::toggleSelection")
	local VirtualScroller = Manager.VirtualScroller
	local HierarchyState = Manager.HierarchyState
	local ExplorerFrame = Manager.ExplorerFrame
	local components = Manager.Components
	local state = Manager.WidgetState
	local world = state.World

	if isCtrlHeld then
		-- Control select
		local currentlySelected = world:has(entity, components.Selected)
		if currentlySelected then
			world:remove(entity, components.Selected)
		else
			world:add(entity, components.Selected)
		end

		if not currentlySelected then
			state.LastSelectedEntity = entity
		end
	elseif isShiftHeld and state.LastSelectedEntity then
		-- Shift select
		local range = Manager.HierarchyState.getEntitiesBetween(Manager, state.LastSelectedEntity, entity)
		profilebegin("Jexplorer::toggleSelection::shiftSelect::addSelectedComponents")
		for _, eid in range do
			world:add(eid, components.Selected)
		end
		profileend()
	else
		-- Normal click
		for entityId in world:query(components.Instance, components.Selected):iter() do
			world:remove(entityId, components.Selected)
		end

		world:add(entity, components.Selected)
		state.LastSelectedEntity = entity
	end

	ExplorerFrame.updateSelectionVisuals(Manager, true)
	VirtualScroller.update(Manager, false, true)
	profileend()
end

local function onSelectExplorerFrame(
	Manager: Manager, world: Types.World, frameWorld: Types.World, frameEntity: ExplorerFrame, state: Types.WidgetState,
	components: Types.Components, frameState: Types.FrameState, input: InputObject
)
	local entity = frameWorld:get(frameEntity, components.Entity)
	if not entity then return end

	local instance = world:get(entity, components.Instance)
	if not instance then return end

	if input.UserInputState ~= Enum.UserInputState.Begin then return end
	if input.UserInputType ~= Enum.UserInputType.MouseMovement then
		Manager.ItemLists.InsertInstance:Hide()
	end
	if input.UserInputType == Enum.UserInputType.MouseMovement then
	elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
		-- If this entity is already selected don't set selection so any other selected instances dont get deselected
		if not world:has(entity, components.Selected) then
			Manager.setExplorerEntitiesAsSelection({entity}, true)
		end

		local chosenMenus = {}
		for _, info in state.InstanceContextMenus do
			local pass = info.Condition(Manager, entity, instance)
			if pass then table.insert(chosenMenus, info) end
		end
		if #chosenMenus > 0 then
			table.sort(chosenMenus, function(a, b)
				return a.Priority > b.Priority
			end)

			state.ContextTarget = entity
			chosenMenus[1].Menu:ShowAsync()
		end
	elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
		profilebegin("InstanceFrame::onInput::LMB (click)")

		task.defer(function()
			frameState.isLMBActive = true
		end)
		frameState.hasBeenSelected = world:has(entity, components.Selected)
		frameState.shouldDeselect = false

		local currentTime = os.clock()
		local timeSinceLastClick = currentTime - frameState.lastClickTime
		frameState.lastClickTime = currentTime

		-- Check for double-click
		if timeSinceLastClick <= Manager.getSetting("DoubleClickToOpenScriptTime") and instance:IsA("LuaSourceContainer") then
			--plugin:OpenScript(instance)
			local success, err = ScriptEditorService:OpenScriptDocumentAsync(instance)
			if not success then warn(`Couldn't open LuaSourceContainer '{instance.Name}': '{err}'`) end
			return
		elseif timeSinceLastClick <= Manager.getSetting("DoubleClickToRenameTime") then
			local instanceName = frameState.instanceName
			instanceName.TextEditable = true
			instanceName.Interactable = true
			instanceName:CaptureFocus()
			return
		end

		local isCtrlHeld = input:IsModifierKeyDown(Enum.ModifierKey.Ctrl)
		local isShiftHeld = input:IsModifierKeyDown(Enum.ModifierKey.Shift)

		-- Handle selection logic immediately (don't wait for drag)
		if not frameState.hasBeenSelected then
			-- First click on unselected item - select it
			switchSelection(Manager, entity, isCtrlHeld, isShiftHeld)
			return -- Don't proceed to drag logic
		elseif isCtrlHeld then
			-- Ctrl+click on selected item - mark for deselection on mouse up
			frameState.shouldDeselect = true
			return -- Don't start drag
		elseif isShiftHeld then
			-- Shift+click on selected item - handle shift selection
			switchSelection(Manager, entity, isCtrlHeld, isShiftHeld)
			return -- Don't start drag
		elseif #state.CurrentSelection > 1 then
			-- Multiple items selected, clicking on one should deselect others
			-- But we need to wait to see if this is a drag or just a click
			-- Don't call toggleSelection here - handle it in InputEnded
		else
			-- Single item selected, clicking on it again - could be drag or deselect
			-- Wait to see if it's a drag
		end
	end
end

local function onInstanceNameFocused(frameWorld: Types.World, frameEntity: ExplorerFrame, components: Types.Components, state: Types.WidgetState, instanceName: TextBox)
	local entity = frameWorld:get(frameEntity, components.Entity)
	if not entity then return end

	state.CurrentlyRenaming = entity
	instanceName.BackgroundTransparency = 0
end

local function onInstanceNameFocusLost(Manager: Manager, frameWorld: Types.World, frameEntity: ExplorerFrame, components: Types.Components, state: Types.WidgetState, world: Types.World, instanceName: TextBox)
	local entity = frameWorld:get(frameEntity, components.Entity)
	if not entity then return end

	local instance = world:get(entity, components.Instance)
	if not instance then return end

	state.CurrentlyRenaming = nil
	instanceName.BackgroundTransparency = 1
	recordChanges("Set Name", function()
		instance.Name = instanceName.Text
	end)
	Manager.NameCache[entity] = instanceName.Text
end

local function onInputEnded(Manager: Manager, world: Types.World, frameWorld: Types.World, frameEntity: ExplorerFrame, components: Types.Components, state: Types.WidgetState, frameState: Types.FrameState, input: InputObject)
	local entity = frameWorld:get(frameEntity, components.Entity)
	if not entity then return end

	local instance = world:get(entity, components.Instance)
	if not instance then return end

	if input.UserInputState ~= Enum.UserInputState.End then return end
	if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end

	local ExplorerFrame = Manager.ExplorerFrame
	local VirtualScroller = Manager.VirtualScroller

	task.defer(function()
		frameState.isLMBActive = false
	end)

	-- Handle deselection for ctrl+click
	if frameState.shouldDeselect then
		world:remove(entity, components.Selected)
		ExplorerFrame.updateSelectionVisuals(Manager, true)
		frameState.shouldDeselect = false
		return
	end
	-- If we have multiple selections and this was just a click (not drag),
	-- then deselect others and select only this one
	if not state.IsDragging and frameState.hasBeenSelected and #state.CurrentSelection > 1 then
		local isCtrlHeld = input:IsModifierKeyDown(Enum.ModifierKey.Ctrl)
		local isShiftHeld = input:IsModifierKeyDown(Enum.ModifierKey.Shift)

		if not isCtrlHeld and not isShiftHeld then
			-- Normal click on multi-selection - select only this one
			for entityId in world:query(components.Instance, components.Selected):iter() do
				world:remove(entityId, components.Selected)
			end
			world:add(entity, components.Selected)
			state.LastSelectedEntity = entity

			ExplorerFrame.updateSelectionVisuals(Manager, true)
			VirtualScroller.update(Manager, false, true)
		end
	end
end

local function onMouseEnter(
	Manager: Manager, frameWorld: Types.World, frameEntity: ExplorerFrame, components: Types.Components, state: Types.WidgetState,
	frame: Instance & any, instanceName: TextBox, insertInstanceButton: GuiButton, reclassButton: GuiButton, frameState: Types.FrameState
)
	local entity = frameWorld:get(frameEntity, components.Entity)
	if not entity then return end

	local inst = Manager.instanceOf(entity)
	if not inst then return end

	if state.IsDragging then
		frame:AddTag("JEXP_DropTarget")
	end

	do
		insertInstanceButton.Position = UDim2.new(0, frameState.currentNameBoundX  + 10, 0.5)
		insertInstanceButton.Visible = true
	end

	frameState.reclassIconVisible = inst.Parent ~= game
	if frameState.reclassIconVisible then
		ExplorerFrame.updateRightEdgeIcons(Manager, frameEntity)
	end
end

local function onMouseLeave(
	Manager: Manager, frameWorld: Types.World, frameEntity: ExplorerFrame, components: Types.Components, frame: Instance & any,
	insertInstanceButton: GuiButton, reclassButton: GuiButton, frameState: Types.FrameState
)
	local entity = frameWorld:get(frameEntity, components.Entity)
	if not entity then return end

	insertInstanceButton.Visible = false
	reclassButton.Visible = false
	frameState.reclassIconVisible = false

	if frame:HasTag("JEXP_DropTarget") then
		frame:RemoveTag("JEXP_DropTarget")
	end

	if frameState.isLMBActive then
		Manager.startDraggingSelected()
	end
end

local function onInsertActivated(Manager: Manager, frameWorld: Types.World, frameEntity: ExplorerFrame, components: Types.Components, state: Types.WidgetState, insertInstanceButton: GuiButton)
	local entity = frameWorld:get(frameEntity, components.Entity)
	if not entity then return end

	state.ContextTarget = entity
	local menu = Manager.ClassSelectionMenu

	local inst = Manager.instanceOf(entity)
	if inst then
		local current = inst
		while current.Parent ~= game and current.Parent do
			current = current.Parent
		end
		menu.menuInfo.setScope(
			Constants.InsertScopeFormat:format(current.ClassName)
		)
	end
	menu.show(Manager, insertInstanceButton.AbsolutePosition, insertInstanceButton.AbsoluteSize, function(class)
		local entity = Manager.WidgetState.ContextTarget
		if not entity then return end

		Manager.insertClassInto(entity, class)
	end)
end

local function onReclassActivated(Manager: Manager, frameWorld: Types.World, frameEntity: ExplorerFrame, components: Types.Components, state: Types.WidgetState, reclassButton: GuiButton)
	local entity = frameWorld:get(frameEntity, components.Entity)
	if not entity then return end

	state.ContextTarget = entity
	local menu = Manager.ClassSelectionMenu

	menu.menuInfo.setScope("Default")
	menu.show(Manager, reclassButton.AbsolutePosition, reclassButton.AbsoluteSize, function(class)
		local entity = Manager.WidgetState.ContextTarget
		if not entity then return end

		Manager.convertEntitiesInto({entity}, class)
	end)
end

local function onInstanceNameFocusLostTyping(instanceName: TextBox)
	instanceName.TextEditable = false
	instanceName.Interactable = false
end

local function onArrowActivated(Manager: Manager, world: Types.World, frameWorld: Types.World, frameEntity: ExplorerFrame, components: Types.Components)
	local entity = frameWorld:get(frameEntity, components.Entity)
	if not entity then return end

	local state = Manager.WidgetState
	local treeScroller = state.TreeScroller
	local HierarchyState = Manager.HierarchyState
	local ExplorerFrame = Manager.ExplorerFrame
	local VirtualScroller = Manager.VirtualScroller

	local expanded = Manager.isExplorerEntityExpanded(entity)
	if expanded then
		world:remove(entity, components.QueryExpanded)
		world:remove(entity, components.Expanded)

		HierarchyState.invalidateEntityStateCache(entity)
		ExplorerFrame.updateCollapsedVisuals(Manager)

		local viewportInfo = Manager.VirtualScroller.getLastVisibleViewportInfo(Manager)
		local newInfo = VirtualScroller.update(Manager, false, true)
		task.delay(1/10, function()
			if not newInfo then return end
			if not viewportInfo then return end

			-- Jump to the position of the actual ExplorerFrame being collapsed if it is sticky
			local visIdx = viewportInfo.entityToVisibleIndex[entity]
			local ancestorInfo = viewportInfo.visibleExpandedAncestors[visIdx]

			if ancestorInfo and ancestorInfo.stickyY then
				local idx = viewportInfo.entityToIndex[entity]
				if idx then
					local depthDiff = viewportInfo.stickyStackDepth - newInfo.stickyStackDepth

					local frameIdx = ((idx - 1 + depthDiff - 1) - 2)
					local frameY = frameIdx * viewportInfo.explorerFrameHeight
					Manager.jumpCanvasTo(nil, frameY, 0)
					VirtualScroller.update(Manager, false, true)
				end
			end
		end)
	else
		world:add(entity, components.Expanded)

		HierarchyState.invalidateEntityStateCache(entity)
		ExplorerFrame.updateExpandedVisuals(Manager)

		VirtualScroller.update(Manager, false, true)
	end
end

local set
local function createExplorerFrame(Manager: Manager, frame: Types.UITEMPLATE_InstanceFrame)
	local VirtualScroller = Manager.VirtualScroller
	local HierarchyState = Manager.HierarchyState
	local ExplorerFrame = Manager.ExplorerFrame
	local components = Manager.Components
	local state = Manager.WidgetState
	local world = state.World

	local cleanup = Manager.CleanupFns
	local frameWorld = state.FrameWorld
	local jexplorerUi = state.JExplorerUI

	local main = frame.Main

	local iconImage = main.ClassIcon
	local instanceName: TextBox = main.InstanceName
	local arrow = main.Arrow

	local overlayFrame = main.Overlay
	local selectInstanceButton = overlayFrame.Select
	local reclassButton = main.Reclass
	local insertInstanceButton = main.Insert

	local connections = state.Connections:extend(cleanup.disconnect)
	local frameState: Types.FrameState = {
		isLMBActive = false,
		shouldDeselect = false,
		tagsToRemove = {},
		lastClickTime = 0,
		packageLinkIcon = main.PackageLinkIcon,
		hasBeenSelected = false,
		isAssigning = false,
		hasPackageLink = false,
		lastPackageIconPos = 0,
		icon = iconImage,
		isVisible = true,
		pendingChanges = main.PendingChanges,
		instanceName = instanceName,
		arrow = arrow,
		insertInstanceButton = insertInstanceButton,
		packageLinkIconVisible = false,
		pendingChangesIconVisible = false,
		reclassButton = reclassButton,
		reclassIconVisible = false,
		currentNameBoundX = 0,
		lastFrameX = math.huge,
		currentFrameX = 0,
		overlayFrame = overlayFrame,
		isSticky = false
	}

	local frameEntity = frameWorld:entity()
	if not set then set = frameWorld.set end

	set(frameWorld, frameEntity, components.Instance, frame)
	set(frameWorld, frameEntity, components.Connections, connections)
	set(frameWorld, frameEntity, components.FrameState, frameState)
	set(frameWorld, frameEntity, components.FRAMEWORLD_Main, main)

	Manager.FrameToInstanceFrame[frame] = frameEntity
	overlayFrame:AddTag("JEXP_Hover")

	local items = connections.items
	local n = #items

	items[n+1] = instanceName.Focused:Connect(function()
		onInstanceNameFocused(frameWorld, frameEntity, components, state, instanceName)
	end)
	items[n+2] = instanceName.FocusLost:Connect(function(enterPressed)
		onInstanceNameFocusLost(Manager, frameWorld, frameEntity, components, state, world, instanceName)
	end)
	items[n+3] = selectInstanceButton.InputBegan:Connect(function(input)
		onSelectExplorerFrame(
			Manager, world, frameWorld, frameEntity,
			state, components, frameState, input
		)
	end)
	items[n+4] = selectInstanceButton.InputEnded:Connect(function(input)
		onInputEnded(Manager, world, frameWorld, frameEntity, components, state, frameState, input)
	end)

	local wasHovering = false
	items[n+5] = selectInstanceButton:GetPropertyChangedSignal("GuiState"):Connect(function()
		local newstate = selectInstanceButton.GuiState
		local ishovered = newstate == Enum.GuiState.Hover or newstate == Enum.GuiState.Press

		if ishovered == wasHovering then return end
		wasHovering = ishovered

		if ishovered then
			onMouseEnter(Manager, frameWorld, frameEntity, components, state, frame, instanceName, insertInstanceButton, reclassButton, frameState)
		else
			onMouseLeave(Manager, frameWorld, frameEntity, components, frame, insertInstanceButton, reclassButton, frameState)
		end
	end)
	items[n+6] = insertInstanceButton.Activated:Connect(function()
		onInsertActivated(Manager, frameWorld, frameEntity, components, state, insertInstanceButton)
	end)
	items[n+7] = instanceName:GetPropertyChangedSignal("TextBounds"):Connect(function()
		local bounds: Vector2 = TextBoundsHelper.GetBounds(Manager, instanceName)
		local x = instanceName:GetStyled("Position").X.Offset + bounds.X
		frameState.currentNameBoundX = x

		rawnewindex(insertInstanceButton, "Position", UDim2.new(0, x + 10, 0.5))
	end)
	items[n+8] = instanceName.FocusLost:Connect(function()
		onInstanceNameFocusLostTyping(instanceName)
	end)
	items[n+9] = arrow.Activated:Connect(function()
		onArrowActivated(Manager, world, frameWorld, frameEntity, components)
	end)
	items[n+10] = reclassButton.Activated:Connect(function()
		onReclassActivated(Manager, frameWorld, frameEntity, components, state, reclassButton)
	end)
	return frameEntity
end

function ExplorerFrame.fillGlobalPoolToLimit(Manager)
	profilebegin("Jexplorer::fillInstanceFramePoolToLimit")
	if #globalFramePool >= GLOBAL_FRAME_CACHE_SIZE then return end

	local framesToCreate = GLOBAL_FRAME_CACHE_SIZE - #globalFramePool
	for _ = 1, framesToCreate do
		local clone = UITemplates.InstanceFrame()

		local frame = createExplorerFrame(Manager, clone)
		clone.Visible = true
		--clone.Parent = Manager.WidgetState.JExplorerUI.InstanceTree

		table.insert(globalFramePool, frame)
	end
	profileend()
end

function ExplorerFrame.getExplorerFrame(Manager): ExplorerFrame
	profilebegin("Jexplorer::allocateExplorerFrame")

	local state = Manager.WidgetState
	local components = Manager.Components
	local frameWorld = state.FrameWorld

	if #globalFramePool == 0 then
		-- Attempt to reuse any pending frames before creating new ones
		local success = ExplorerFrame.flushFrameReuseQueue(Manager)
		if not success or #globalFramePool == 0 then
			ExplorerFrame.fillGlobalPoolToLimit(Manager)
		end
	end
	local reused = table.remove(globalFramePool)::any
	profileend()
	return reused
end

function ExplorerFrame.flushFrameReuseQueue(Manager)
	profilebegin("Jexplorer::flushFrameReuseQueue")

	local state = Manager.WidgetState
	local frameWorld = state.FrameWorld
	local world = state.World
	local components = Manager.Components
	local tounassign = {}
	local tomutate: {ExplorerEntity} = {}

	local query = Jecs.cached_query(world, components.FrameReuseTicket)

	local touI = 1
	local tomuI = 1
	local fpI = #globalFramePool + 1

	local any = false
	for entity, ticketData in query:iter() do
		any = true
		world:remove(entity, components.FrameReuseTicket)
		local frame = ticketData.frame

		tounassign[touI] = frame
		globalFramePool[fpI] = frame
		tomutate[tomuI] = entity

		tomuI += 1
		fpI += 1
		touI += 1
	end
	if not any then
		profileend()
		return false
	end

	for _, entity in tomutate do
		world:remove(entity, components.Frame)
	end
	for _, frame in tounassign do
		RulerPool.releaseRuler(Manager, frame)
		frameWorld:set(frame, components.FRAMEWORLD_FrameSetParentTicket, {newParent = nil})
		ExplorerFrame.assignExplorerFrame(Manager, frame, nil, nil, false, false)
	end

	Manager.DeferredTaskScheduler.FrameTagAssignQueuePending = true

	profileend()
	return true
end

local frameSetParentQuery
function ExplorerFrame.flushFrameSetParentQueue(Manager)
	local state = Manager.WidgetState
	local components = Manager.Components
	local world = state.World
	local frameWorld = state.FrameWorld

	local any = false
	local query
	if frameSetParentQuery then
		query = frameSetParentQuery
	else
		frameSetParentQuery = frameWorld:query(components.Instance, components.FRAMEWORLD_FrameSetParentTicket)
			:cached()
		query = frameSetParentQuery
	end
	if not query:iter()() then return false end

	profilebegin("Jexplorer::stepFlushFrameSetParentQueue")
	local withoutParents: {{inst: Instance, frame: ExplorerFrame}} = {}
	local withParents: {{inst: Instance, parent: Instance, frame: ExplorerFrame}} = {}

	local iter = query:iter()
	for frame: ExplorerFrame, frameInst, ticketData in iter do
		any = true
		frameWorld:remove(frame, components.FRAMEWORLD_FrameSetParentTicket)

		if ticketData.newParent then
			table.insert(withParents, {inst = frameInst, parent = ticketData.newParent, frame = frame})
		else
			table.insert(withoutParents, {inst = frameInst, frame = frame})
		end
	end
	if not any then return false end

	local PARENT = "Parent"
	profilebegin("withoutParents")
	for _, v in withoutParents do
		if frameWorld:get(v.frame, components.FRAMEWORLD_CurrentParent) ~= nil then
			rawnewindex(v.inst, PARENT, nil)
			frameWorld:remove(v.frame, components.FRAMEWORLD_CurrentParent)
		end
	end
	profileend()
	profilebegin("withParents")
	for _, v in withParents do
		if frameWorld:get(v.frame, components.FRAMEWORLD_CurrentParent) ~= v.parent then
			rawnewindex(v.inst, PARENT, v.parent)
			frameWorld:set(v.frame, components.FRAMEWORLD_CurrentParent, v.parent)
		end
	end
	profileend()
	profileend()

	return true
end

function ExplorerFrame.assignExplorerFrame(Manager, frame, entity, parent, parentIsGame, assignSetParentTicket)
	local state = Manager.WidgetState
	local components = Manager.Components
	local world = state.World
	local frameWorld = state.FrameWorld

	if entity ~= nil and frameWorld:get(frame, components.Entity) == entity then
		if (not parent) or world:get(parent, components.Frame) == frameWorld:parent(frame) then
			return
		end
	end

	local frameState = frameWorld:get(frame, components.FrameState)
	if not frameState then return end

	local frameInst = Manager.frameOf(frame)
	if not frameInst then return end

	frameState.isAssigning = true

	local parentFrame = if parent then world:get(parent, components.Frame) else nil
	profilebegin("InstanceFrame::Assign")
	if not entity or (not parent and not parentIsGame) or Manager.isExplorerEntityDeleted(entity) then
		frameWorld:remove(frame, components.Entity)
		local oldParent = frameWorld:parent(frame)
		if oldParent then
			frameWorld:remove(frame, Manager.ChildOf(oldParent))
		end
		frameState.insertInstanceButton.Visible = false
		frameState.reclassButton.Visible = false
		frameState.isAssigning = false

		if assignSetParentTicket then
			frameWorld:set(frame, components.FRAMEWORLD_FrameSetParentTicket, {newParent = nil})
		end

		profileend()
		return
	end

	frameState.reclassIconVisible = false

	if assignSetParentTicket then
		frameWorld:set(frame, components.FRAMEWORLD_FrameSetParentTicket, {newParent = state.InstanceTree})
	end

	local name = Manager.NameCache[entity]
	world:set(entity, components.Frame, frame)
	frameWorld:set(frame, components.Entity, entity)

	if parentFrame then
		frameWorld:add(frame, Manager.ChildOf(parentFrame))
	end

	world:set(entity, components.FrameTagAssignTicket, {frame = frame})

	if not state.LastSelectedEntity then
		state.LastSelectedEntity = nil
	end
	frameState.isLMBActive = false
	frameState.hasBeenSelected = false
	frameState.shouldDeselect = false

	profileend()
	frameState.isAssigning = false

	return true
end

function ExplorerFrame.queueForExplorerFrameReuse(Manager, frame, entity)
	local state = Manager.WidgetState
	local frameWorld = state.FrameWorld
	local world = state.World
	local components = Manager.Components
	local entity = entity or frameWorld:get(frame, components.Entity)

	frameWorld:remove(frame, components.Entity)
	if entity then
		world:set(entity, components.FrameReuseTicket, {frame = frame})
	else
		frameWorld:set(frame, components.FRAMEWORLD_FrameSetParentTicket, {newParent = nil})
	end
end

function ExplorerFrame.initializeFrameCaches(Manager)
	ExplorerFrame.fillGlobalPoolToLimit(Manager)  -- Fill L2
	--ExplorerFrame.fillLocalPoolToLimit(Manager)  -- Fill L1 from L2
end

function ExplorerFrame.updateSelectionVisuals(Manager, updateSelection)
	profilebegin("Jexplorer::updateSelectionVisuals")
	local state = Manager.WidgetState
	local world = state.World
	local components = Manager.Components

	table.clear(state.CurrentSelection)
	local currentSelection = {}

	profilebegin("Jexplorer::updateSelectionVisuals::deselectFrames")
	for entityId, frame in world:query(components.Frame):without(components.Selected):iter() do
		setFrameSelected(Manager, frame, false)
	end
	profileend()

	profilebegin("Jexplorer::updateSelectionVisuals::mainLoop")
	for entityId, instance in world:query(components.Instance, components.Selected):iter() do
		local frame = world:get(entityId, components.Frame)

		if typeof(instance) == "Instance" then
			table.insert(currentSelection, instance)
		end
		table.insert(state.CurrentSelection, entityId)

		if frame then
			setFrameSelected(Manager, frame, true)
		end
	end
	ExplorerFrame.flushExplorerFrameTagAssignQueue(Manager)
	profileend()
	if updateSelection then
		profilebegin("Jexplorer::updateSelectionVisuals::setSelection")
		game.Selection:Set(currentSelection)
		profileend()
	end
end

function ExplorerFrame.updateExpandedVisuals(Manager)
	profilebegin("Jexplorer::toggleExpand")

	local components = Manager.Components
	local state = Manager.WidgetState
	local world = state.World

	local function update(entity: ExplorerEntity, instance: Instance, frame: ExplorerFrame)
		local frameInst = Manager.frameOf(frame)
		if not frameInst then return end

		if instance == game then return end

		Manager.VirtualScroller.invalidateEntityHeightCache(Manager, entity)
	end
	for entity, instance, frame in world:query(components.Instance, components.Frame, components.Expanded):iter() do
		update(entity, instance, frame)
	end
	for entity, instance, frame in world:query(components.Instance, components.Frame, components.QueryExpanded):iter() do
		update(entity, instance, frame)
	end

	profileend()
end

local padding = 4
local frameIconOrderUpdaters = {
	-- package,
	function(Manager: Manager, entity, FrameX: number, frameState: Types.FrameState, state: Types.WidgetState, containerWidth, nameBoundX, currentoffset): (number?, boolean?)
		profilebegin("updatePackageLink")

		local offset
		local iconCanExist = Manager.WidgetState.World:has(entity, Manager.Components.HasPackageLink)

		frameState.hasPackageLink = iconCanExist

		if frameState.hasPackageLink then
			local linkIcon = frameState.packageLinkIcon
			local pos = stickToRightEdge(containerWidth, FrameX, currentoffset)

			local canShow = pos.X.Offset > nameBoundX
			linkIcon.Visible = canShow

			if canShow then
				if FrameX ~= frameState.lastFrameX or pos ~= frameState.linkIconPos then
					linkIcon.Position = pos
					frameState.linkIconPos = pos
				end
				offset = linkIcon.AbsoluteSize.X
			end
		else
			frameState.packageLinkIcon.Visible = false
		end

		return offset, iconCanExist
	end,
	-- pending changes,
	function(Manager: Manager, entity, FrameX: number, frameState: Types.FrameState, state: Types.WidgetState, containerWidth, nameBoundX, currentoffset): (number?, boolean??)
		profilebegin("updatePendingChanges")

		local offset
		local iconCanExist = Manager.WidgetState.World:has(entity, Manager.Components.PendingChanges)

		local pendingchangeicon = frameState.pendingChanges
		local canShow, pos = false, nil

		if iconCanExist then
			pos = stickToRightEdge(containerWidth, FrameX, currentoffset)
			canShow = pos.X.Offset > nameBoundX
		end

		pendingchangeicon.Visible = canShow

		if canShow then
			if FrameX ~= frameState.lastFrameX or pos ~= frameState.pendingChangesIconPos then
				pendingchangeicon.Position = pos
				frameState.pendingChangesIconPos = pos
			end
			offset = pendingchangeicon.AbsoluteSize.X
		end

		return offset, iconCanExist
	end,
	-- reclass
	function(Manager: Manager, entity, FrameX: number, frameState: Types.FrameState, state: Types.WidgetState, containerWidth, nameBoundX, currentoffset): (number?, boolean??)
		profilebegin("updateReclassIcon")

		local offset
		local iconCanExist = frameState.reclassIconVisible

		local reclassicon = frameState.reclassButton
		local canShow, pos = false, nil

		if frameState.reclassIconVisible then
			pos = stickToRightEdge(containerWidth, FrameX, currentoffset)
			canShow = pos.X.Offset > nameBoundX
		end

		reclassicon.Visible = canShow

		if canShow then
			if FrameX ~= frameState.lastFrameX or pos ~= frameState.reclassIconPos then
				reclassicon.Position = pos
				frameState.reclassIconPos = pos
			end
			offset = reclassicon.AbsoluteSize.X
		end

		return offset, iconCanExist
	end,
}
function ExplorerFrame.updateRightEdgeIcons(Manager: Manager, frame, containerWidth, frameX, entity, frameState)
	local components = Manager.Components
	local state = Manager.WidgetState
	local world = state.World

	local frameInst = Manager.frameOf(frame)
	assert(frameInst)

	local frameState = frameState or state.FrameWorld:get(frame, components.FrameState)
	assert(frameState)

	local containerWidth = containerWidth or Manager.InstTreeAbsoluteSizeX
	local frameX = frameX or frameState.currentFrameX

	local entity = entity or state.FrameWorld:get(frame, components.Entity)
	assert(entity)

	local nameBoundX = frameState.currentNameBoundX + 18 + 16 + padding  -- padding

	local currentoffset, canHaveSideIcons = 0, false
	for _, updater in frameIconOrderUpdaters do
		local addoffset, iconcanexist = updater(Manager, entity, frameX, frameState, state, containerWidth, nameBoundX, currentoffset)
		if iconcanexist then canHaveSideIcons = true end
		profileend()

		if addoffset then
			currentoffset -= (addoffset + padding)
		end
	end

	if canHaveSideIcons then
		world:add(entity, components.HasSideIcons)
	else
		world:remove(entity, components.HasSideIcons)
	end
end

function ExplorerFrame.updateCollapsedVisuals(Manager)
	profilebegin("Jexplorer::toggleExpand")

	local components = Manager.Components
	local state = Manager.WidgetState
	local world = state.World
	local treeScroller = state.TreeScroller

	local components = Manager.Components
	for entity, instance, frame in world:query(components.Instance, components.Frame):without(components.Expanded, components.QueryExpanded):iter() do
		local frameInst = Manager.frameOf(frame)
		if not frameInst then continue end

		world:remove(entity, components.Expanded)
		world:remove(entity, components.QueryExpanded)

		frameInst.Main.Arrow.Rotation = -90

		local function reuseChildren(entity: ExplorerEntity)
			for child, frame, _ in world:query(components.Frame, Manager.ChildOf(entity)):iter() do
				ExplorerFrame.queueForExplorerFrameReuse(Manager, frame)
				if not Manager.isExplorerEntityExpanded(child) then continue end
				reuseChildren(child)
			end
		end
		reuseChildren(entity)
		Manager.DeferredTaskScheduler.FrameReuseQueuePending = true

		ExplorerFrame.flushFrameSetParentQueue(Manager)

		Manager.VirtualScroller.invalidateEntityHeightCache(Manager, entity)
	end

	profileend()
end

local createdClassRules = {}
local entitiesToAddIcons: {ExplorerEntity} = {}
local clear = table.clear
local isEntityInvisible
local function addMissingIconStyleRules(Manager: Manager)
	if not isEntityInvisible then
		isEntityInvisible = Manager.isExplorerEntityInvisible
	end

	local sheet = Manager.Style
	local rules = {}
	local any = false
	for _, entity in entitiesToAddIcons do
		if isEntityInvisible(entity) then continue end
		local className = Manager.ClassNameCache[entity]
		local tag = `JEXP_Class_{className}`

		if createdClassRules[tag] then continue end

		local slctr = Selector.getIconSelector(nil, className)
		local icon = ClassIcon.getIcon(className)

		if not icon then continue end
		local name = `ClassIcon_{className}`
		if className == "Script" or className == "LocalScript" then
			-- Do nothing, handled in default style
		else
			local dir, image = string.match(icon.Image, "rbxasset://studio_svg_textures/Shared/(.-)/.-/Standard/(.+)")
			local normalIcon = rule(name, slctr, 0, {
				ImageRectOffset = icon.ImageRectOffset,
				ImageRectSize = icon.ImageRectSize,
				Image = var(`'rbxasset://studio_svg_textures/Shared/{dir}/![$Theme]/Standard/{image}'`)
			})
			local lightIcon = rule(name.."_Selected", ".JEXP_Selected "..slctr, 100, {
				ImageRectOffset = icon.ImageRectOffset,
				ImageRectSize = icon.ImageRectSize,
				Image = var(`'rbxasset://studio_svg_textures/Shared/{dir}/![$SelectedTheme]/Standard/{image}'`)
			})
			normalIcon:AddTag("DontSaveRule")
			lightIcon:AddTag("DontSaveRule")

			table.insert(rules, normalIcon)
			table.insert(rules, lightIcon)
			createdClassRules[tag] = true
			any = true
		end
	end
	if any then
		sheet:AddRules(rules)
	end
end

local isFlushing = false
function ExplorerFrame.flushExplorerFrameTagAssignQueue(Manager)
	if isFlushing then
		task.defer(ExplorerFrame.flushExplorerFrameTagAssignQueue, Manager)
		return false
	end
	isFlushing = true

	local state = Manager.WidgetState
	local components = Manager.Components
	local world = state.World
	local frameWorld = state.FrameWorld

	local query = Jecs.cached_query(world, components.FrameTagAssignTicket)
	local any = false
	profilebegin("Jexplorer::flushTagAssignQueue")

	for entity, ticketData in query:iter() do
		world:remove(entity, components.FrameTagAssignTicket)
		any = true
		local frame = ticketData.frame

		local frameInst = Manager.frameOf(frame)
		assert(frameInst, "frameinst not found")
		local frameState = frameWorld:get(frame, components.FrameState)
		assert(frameState, "framestate not found")
		local inst = world:get(entity, components.Instance)
		assert(inst, "entity instance not found")

		local isSelected = world:has(entity, components.Selected)
		setFrameSelected(Manager, frame, isSelected)

		local hasPendingChanges = world:has(entity, components.PendingChanges)
		local scriptErrors = world:get(entity, components.ScriptErrors)
		local scriptWarns = world:get(entity, components.ScriptWarns)
		table.insert(entitiesToAddIcons, entity)

		local frameIconColor = world:get(entity, components.FrameIconColor)
		local iconLabel = frameState.icon
		local currentColor = frameState.setIconColor
		if frameIconColor and currentColor ~= frameIconColor then
			iconLabel.ImageColor3 = frameIconColor
			frameState.setIconColor = frameIconColor
		elseif not frameIconColor and currentColor ~= nil then
			iconLabel.ImageColor3 = Color3.fromRGB(255, 255, 255)
			frameState.setIconColor = nil
		end

		local className = Manager.ClassNameCache[entity]
		local name = Manager.NameCache[entity]

		local instanceName = frameState.instanceName

		local newTags = {}
		if typeof(inst) == "Instance" then
			local propmap = getClassPropMap(className)
			for _, prop in Constants.TaggedProperties do
				if not propmap[prop] then continue end

				local value
				if prop == "ClassName" then
					value = className
				elseif prop == "Name" then
					value = name
				else
					value = rawindex(inst, prop)
				end
				table.insert(newTags, JexpTags.getTaggedPropertyTag(prop, typeof(value) == "EnumItem" and value.Name or tostring(value)))
			end
		end
		for _, tag in inst:GetTags() do
			table.insert(newTags, `JEXP_TAG_{tag}`)
		end
		local hasScriptError = scriptErrors and #scriptErrors > 0
		local hasScriptWarn = not hasScriptError and scriptWarns and #scriptWarns > 0

		if hasScriptError then
			table.insert(newTags, "JEXP_ScriptError")
		elseif hasScriptWarn then
			table.insert(newTags, "JEXP_ScriptWarn")
		end
		local newTagSet = {}
		for _, tag in newTags do
			newTagSet[tag] = true
		end
		local tagsToAdd = {}
		local tagsToRemove = {}
		local addCount = 0
		local removeCount = 0

		for _, oldTag in frameState.tagsToRemove do
			if not newTagSet[oldTag] then
				removeCount += 1
				tagsToRemove[removeCount] = oldTag
			end
		end

		for _, newTag in newTags do
			local alreadyHas = false
			for _, oldTag in frameState.tagsToRemove do
				if oldTag == newTag then
					alreadyHas = true
					break
				end
			end
			if not alreadyHas then
				addCount += 1
				tagsToAdd[addCount] = newTag
			end
		end

		if removeCount > 0 or addCount > 0 then
			for i = 1, removeCount do
				removeTag(frameInst, tagsToRemove[i])
			end
			for i = 1, addCount do
				addTag(frameInst, tagsToAdd[i])
			end
		end
		frameState.tagsToRemove = newTags

		ExplorerFrame.updateRightEdgeIcons(Manager, frame, nil, nil, entity, frameState)
	end

	if #entitiesToAddIcons > 0 then
		addMissingIconStyleRules(Manager)
		clear(entitiesToAddIcons)
	end

	isFlushing = false

	profileend()
	return any
end

function ExplorerFrame.setExplorerFrameIconColor(Manager, frame, color)
	local state = Manager.WidgetState
	local components = Manager.Components
	local world = state.World
	local frameWorld = state.FrameWorld

	local frameState = frameWorld:get(frame, components.FrameState)
	if not frameState then return end

	frameState.setIconColor = color
	frameState.icon.ImageColor3 = color or Color3.fromRGB(255, 255, 255)
end

return ExplorerFrame


export type APIResult<T... = ()> = {
	Code: number,
	Message: string,
	Success: boolean,
	Unwrap: (APIResult<T...>) -> T...
}

local resultIndex = {}
local resultMt = {
	__index = resultIndex,
	__metatable = "Metatable is locked"
}

function resultMt:__tostring()
	return `APIResult({self.Success})`
end

function resultIndex:Unwrap()
	if not self.Success then
		error(`Attempt to unwrap an unsuccessful result: ({self.Code}) '{self.Message}'`)
	end
	return unpack(self.__v)
end

local function new<T...>(success: boolean, code: number, message: string?, ...: T...): APIResult<T...>
	local msg
	if message then
		msg = message
	elseif success and not message then
		msg = "OK"
	elseif not success and not message then
		msg = "FAIL"
	end
	return setmetatable({
		Code = code,
		Message = msg,
		Success = success,

		__v = table.pack(...)
	}, resultMt) :: any
end

return {
	new = new
}


local endchars = { '', '==', '=' }
local function to_base64(data: string): string -- (XDeltaXen) - https://devforum.roblox.com/t/base64-encoding-and-decoding-in-lua/1719860
	local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
	return ((data:gsub('.', function(x)
		local r,b='',x:byte()
		for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
		return r;
	end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
		if (#x < 6) then return '' end
		local c=0
		for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
		return b:sub(c+1,c+1)
	end)..endchars[#data%3+1])
end

local function from_base64(data: string): string -- (XDeltaXen) - https://devforum.roblox.com/t/base64-encoding-and-decoding-in-lua/1719860
	local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
	data = string.gsub(data, '[^'..b..'=]', '')
	return (data:gsub('.', function(x)
		if (x == '=') then return '' end
		local r,f='',(b:find(x)::any-1)
		for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
		return r;
	end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
		if (#x ~= 8) then return '' end
		local c=0
		for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
		return string.char(c)
	end))
end

return {
	to = to_base64,
	from = from_base64
}


local secretToData = setmetatable({}::{[any]: {value: Secret | string, authorizedEnv: any, metadata: {[string]: any}}}, {__mode = "k"})

local function checkToRootEnv(secret: LuaSecret, levelToRootEnv: number)
	if not levelToRootEnv then
		error("levelToRootEnv is a required parameter.")
	end
	local data = secretToData[secret]
	--if debug.info(levelToRootEnv, "f") ~= data.authorizedEnv then
	--	error("attempt to access 'LuaSecret' from an unauthorized env")
	--end
end

local secretIndex = {}
secretIndex.__index = secretIndex

local newSecret

export type LuaSecret = typeof(setmetatable({}, secretIndex))

-- Returns a new LuaSecret with prefix at the start of the original value.
function secretIndex:AddPrefix(prefix: string): LuaSecret
	local value = secretToData[self].value
	if typeof(value) == "Secret" then
		return newSecret(value:AddPrefix(prefix))
	elseif typeof(value) == "string" then
		return newSecret(prefix .. value)
	else
		error(`Value of 'LuaSecret' is not a 'Secret' nor a 'string'.`)
	end
end

-- Returns a new LuaSecret with suffix at the end of the original value.
function secretIndex:AddSuffix(suffix: string): LuaSecret
	local data = secretToData[self]
	local value = data.value

	if typeof(value) == "Secret" then
		local new = newSecret(value:AddSuffix(suffix))
		secretToData[new].metadata = table.clone(data.metadata)
		return new
	elseif typeof(value) == "string" then
		local new = newSecret(value .. suffix)
		secretToData[new].metadata = table.clone(data.metadata)
		return new
	else
		error(`Value of 'LuaSecret' is not a 'Secret' nor a 'string'.`)
	end
end

-- Used to associate additional metadata with the LuaSecret.
function secretIndex:SetProperty(key: string, value: any, levelToRootEnv: number): ()
	checkToRootEnv(self, levelToRootEnv)
	secretToData[self].metadata[key] = value
end

-- Used to get additional metadata assigned by LuaSecret.SetProperty.
function secretIndex:GetProperty(key: string): any
	return secretToData[self].metadata[key]
end

local function getSecretValue(secret: LuaSecret, levelToRootEnv: number): Secret | string
	checkToRootEnv(secret, levelToRootEnv)

	local value = secretToData[secret].value

	if not value then error(`LuaSecret '{secret}' does not exist.`) end
	return value
end

local function assert_auth(secret: LuaSecret)
	if not secretToData[secret].value then
		error(`Expected 'LuaSecret', got '{typeof(secret)}'`)
	end
end

function newSecret(value: string | Secret, authorizedEnv: (...any) -> ...any): LuaSecret
	local proxy = newproxy(true)
	local mt = getmetatable(proxy)

	mt.__metatable = "Metatable is locked"
	mt.__index = secretIndex
	function mt:__tostring()
		return "LuaSecret"
	end
	function mt:__newindex(name, value)
		error(`'{name}' is not a valid member of 'LuaSecret'`)
	end

	secretToData[proxy] = {
		value = value,
		authorizedEnv = authorizedEnv,
		metadata = {}
	}

	return proxy
end

return {
	new = newSecret,
	get = getSecretValue,
	assert_is = assert_auth
}