--[=[
		@class Heading

		For sectioning bodies of text or content, like for a menu title.
]=]

--[=[
	@interface HeadingProps
	@within Heading

	. ... TextProps -- All Text properties are accepted.
	.HeadingSize number? -- Multiplier for the text size, default is 1.75.
	.TextSize number? -- Base text size, default is Theme.TextSize["1"].
]=]

local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)

local Text = require(OnyxUI.Components.Text)
local Components = {
	Text = Text,
}

export type Props = Text.Props & {
	HeadingSize: Fusion.UsedAs<number>?,
	TextSize: Fusion.UsedAs<number>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local HeadingSize = Util.Fallback(Props.HeadingSize, 1.75)
	local TextSize = Util.Fallback(Props.TextSize, Theme.TextSize["1"])

	return Scope:Text(Util.CombineProps(Props, {
		Name = script.Name,
		TextSize = Scope:Computed(function(Use)
			local HeadingSizeValue = Use(HeadingSize)
			local TextSizeValue = Use(TextSize)

			return TextSizeValue * HeadingSizeValue
		end),
		FontFace = Scope:Font(Theme.Font.Heading, Theme.FontWeight.Heading),
	}))
end


local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local Heading = require(script.Parent.Heading)
local Frame = require(script.Parent.Frame)
local Text = require(script.Parent.Text)
local Components = {
	Heading = Heading,
	Text = Text,
	Frame = Frame,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			List = {
				Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
			},

			[Children] = {
				Scope:Heading {
					Parent = Props.target,
					Text = "Heading",
				},
				Scope:Text {
					Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class Icon

		It's an icon, what else can I say?
]=]

--[=[
		@within Icon
		@interface IconProps

		. ... ImageProps -- All Image properties are accepted.
]=]

local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(script.Parent.Parent.Themer)

local Image = require(script.Parent.Image)
local Components = {
	Image = Image,
}

export type Props = Image.Props & {}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	return Scope:Image(Util.CombineProps(Props, {
		Name = script.Name,
		Size = Scope:UDim2Offset(Theme.TextSize["1"], Theme.TextSize["1"]),
		BackgroundTransparency = 1,
	}))
end


local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)

local Icon = require(script.Parent.Icon)
local Components = {
	Icon = Icon,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)

		Scope:Icon {
			Parent = Props.target,
			Image = "rbxassetid://11560341132",
		}
	end,
	fusion = Fusion,
}


--[=[
		@class IconButton

		Like [`Button`](/api/Button), but for a single [`Icon`](/api/Icon).
]=]

--[=[
		@within IconButton
		@interface IconButtonProps

		. ... ButtonProps -- All Button properties are accepted.
		. Image string?
]=]

local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)

local Button = require(script.Parent.Button)
local Components = {
	Button = Button,
}

export type Props = Button.Props & {
	Image: Fusion.UsedAs<string>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local Image = Util.Fallback(Props.Image, "")

	local Padding = Scope:UDim(0, Theme.Spacing["0.25"])

	return Scope:Button(Util.CombineProps(Props, {
		Name = script.Name,
		Padding = {
			Left = Padding,
			Right = Padding,
			Top = Padding,
			Bottom = Padding,
		},
		Content = Scope:Computed(function(Use)
			return { Use(Image) }
		end),
	}))
end


local OnyxUI = script.Parent.Parent

local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)
local Util = require(OnyxUI.Util)

local Children = Fusion.Children

local IconButton = require(script.Parent.IconButton)
local Frame = require(script.Parent.Frame)
local Components = {
	IconButton = IconButton,
	Frame = Frame,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			Padding = {
				All = Scope:UDim(0, Theme.StrokeThickness["1"]),
			},
			List = {
				FillDirection = Enum.FillDirection.Horizontal,
				Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
			},

			[Children] = {
				Scope:IconButton {
					Image = "rbxassetid://10814531047",
				},
				Scope:IconButton {
					Image = "rbxassetid://10814531047",
					Color = Theme.Colors.Primary.Main,
				},
				Scope:IconButton {
					Image = "rbxassetid://11560341132",
					Color = Util.Colors.Amber["500"],
				},
				Scope:IconButton {
					Image = "rbxassetid://13405228418",
					Color = Util.Colors.Red["500"],
					Style = "Outlined",
				},
				Scope:IconButton {
					Image = "rbxassetid://13405228418",
					Color = Util.Colors.Red["500"],
					Style = "Ghost",
				},
				Scope:IconButton {
					Image = "rbxassetid://10814531047",
					Disabled = true,
				},
				Scope:IconButton {
					Image = "rbxassetid://10814531047",
					Style = "Ghost",
					Disabled = true,
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class IconSwap

		Swap between an array of icons.
]=]

--[=[
	@interface IconSwapProps
	@within IconSwap

	. ... IconButtonProps -- All IconButton properties are accepted.
	.Options {string}? -- Array of image strings to swap between
	.Selection number? -- The currently selected option index
]=]

local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)
local Fusion = require(OnyxUI.Parent.Fusion)

local IconButton = require(script.Parent.IconButton)

export type Props = IconButton.Props & {
	Options: Fusion.UsedAs<{ string }>?,
	Selection: Fusion.UsedAs<number>?,
	OnSwap: Fusion.UsedAs<(number) -> ()>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, {
		IconButton = IconButton,
	})
	local Theme = Themer.Theme:now()

	local Options = Util.Fallback(Props.Options, {})
	local Selection = Scope:EnsureValue(Util.Fallback(Props.Selection, 1))
	local OnSwap = Util.Fallback(Props.OnSwap, function() end)

	local function RotateSelection(Increment: number)
		local OptionsValue = Fusion.peek(Options)
		local SelectionValue = Fusion.peek(Selection)
		local NewSelection = SelectionValue + Increment

		if NewSelection > #OptionsValue then
			NewSelection = 1
		elseif NewSelection < 1 then
			NewSelection = #OptionsValue
		end

		Selection:set(NewSelection)
	end

	return Scope:IconButton(Util.CombineProps(Props, {
		Name = script.Name,
		Style = "Ghost",
		Color = Theme.Colors.BaseContent.Main,
		Image = Scope:Computed(function(Use)
			return Use(Options)[Use(Selection)]
		end),

		OnActivate = function()
			local SelectionValue = Fusion.peek(Selection)
			local OnSwapValue = Fusion.peek(OnSwap)

			RotateSelection(1)
			OnSwapValue(SelectionValue, SelectionValue + 1)
		end,
	}))
end


local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local IconSwap = require(script.Parent.IconSwap)
local Frame = require(script.Parent.Frame)

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, {
			IconSwap = IconSwap,
			Frame = Frame,
		})
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			List = {
				Padding = Scope:UDim(0, Theme.Spacing["0.75"]),
			},
			Padding = {
				All = Scope:UDim(0, Theme.StrokeThickness["1"]),
			},

			[Children] = {
				Scope:IconSwap {
					Options = { "rbxassetid://13852158988", "rbxassetid://13852161637" },
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class IconText

		For displaying both text and icons alongside each other as one seamless body.
]=]

--[=[
	@interface IconTextProps
	@within IconText

	. ... FrameProps -- All Frame properties are accepted.
	.Content {string}? -- The content to display, can include both text and icons.
	.ContentColor Color3? -- The color of the content.
	.ContentSize number? -- The size of the content.
	.ContentTransparency number? -- The transparency of the content.
	.ContentWrapped boolean? -- Whether the text content should wrap.
	.ContentRichText boolean? -- Whether the text content supports rich text.
	.ContentFontFace Font? -- The font face of the text content.
]=]

local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local Frame = require(OnyxUI.Components.Frame)
local Text = require(OnyxUI.Components.Text)
local Icon = require(OnyxUI.Components.Icon)
local Components = {
	Text = Text,
	Icon = Icon,
	Frame = Frame,
}

export type Props = Frame.Props & {
	Content: Fusion.UsedAs<{ string }>?,
	ContentColor: Fusion.UsedAs<Color3>?,
	ContentSize: Fusion.UsedAs<number>?,
	ContentTransparency: Fusion.UsedAs<number>?,
	ContentWrapped: Fusion.UsedAs<boolean>?,
	ContentRichText: Fusion.UsedAs<boolean>?,
	ContentFontFace: Fusion.UsedAs<Font>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local Content = Util.Fallback(Props.Content, {})
	local ContentSize = Util.Fallback(Props.ContentSize, Theme.TextSize["1"])
	local ContentColor = Util.Fallback(Props.ContentColor, Theme.Colors.BaseContent.Main)
	local ContentTransparency = Util.Fallback(Props.ContentTransparency, 0)
	local ContentWrapped = Util.Fallback(Props.ContentWrapped, true)
	local ContentRichText = Util.Fallback(Props.ContentRichText, true)
	local ContentFontFace = Util.Fallback(Props.ContentFontFace, Scope:Font(Theme.Font.Body, Theme.FontWeight.Body))

	return Scope:Frame(Util.CombineProps(Props, {
		Name = script.Name,
		List = {
			FillDirection = Enum.FillDirection.Horizontal,
			Padding = Scope:UDim(0, Theme.Spacing["0"]),
		},

		[Children] = {
			Scope:ForPairs(Content, function(Use, Scope, Index: number, ContentString: string)
				return Index,
					Themer.Theme:is(Theme):during(function()
						if string.find(ContentString, "rbxassetid://", 1, true) then
							return Scope:Icon {
								Image = ContentString,
								ImageColor3 = ContentColor,
								Size = Scope:UDim2Offset(ContentSize, ContentSize),
								ImageTransparency = ContentTransparency,
							}
						else
							return Scope:Text {
								Text = ContentString,
								TextColor3 = ContentColor,
								TextSize = ContentSize,
								TextTransparency = ContentTransparency,
								TextWrapped = ContentWrapped,
								RichText = ContentRichText,
								FontFace = ContentFontFace,
							}
						end
					end)
			end),
		},
	}))
end


local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)

local IconText = require(script.Parent.IconText)
local Components = {
	IconText = IconText,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)

		Scope:IconText {
			Parent = Props.target,
			Content = {
				"Here's a shop icon: ",
				"rbxassetid://75029721407761",
				".",
				" And also the Loneka logo: ",
				"rbxassetid://16735398592",
				". Now you can easily have both text and icons!",
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class Image

		Equivalent to Roblox's `ImageLabel`.
]=]

--[=[
	@interface ImageProps
	@within Image

	. ... BaseProps -- All Base properties are accepted.
	.Image string? -- The image asset ID to display.
	.FallbackImage string? -- The fallback image asset ID to display if `Image` is not provided.
	.ImageColor3 Color3? -- The color to apply to the image.
	.ImageTransparency number? -- The transparency of the image.
	.ImageRectOffset Vector2? -- The offset of the image rectangle.
	.ImageRectSize Vector2? -- The size of the image rectangle.
	.ResampleMode Enum.ResamplerMode? -- The resampling mode for the image.
	.ScaleType Enum.ScaleType? -- The scaling mode for the image.
	.SliceCenter Rect? -- The center rectangle for 9-slice scaling.
	.SliceScale number? -- The scale factor for 9-slice scaling.
	.TileSize UDim2? -- The size of the tile for tiled images.
]=]

local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)

local Base = require(script.Parent.Base)
local Components = {
	Base = Base,
}

export type Props = Base.Props & {
	Image: Fusion.UsedAs<string>?,
	FallbackImage: Fusion.UsedAs<string>?,
	ImageColor3: Fusion.UsedAs<Color3>?,
	ImageTransparency: Fusion.UsedAs<number>?,
	ImageRectOffset: Fusion.UsedAs<Vector2>?,
	ImageRectSize: Fusion.UsedAs<Vector2>?,
	ResampleMode: Fusion.UsedAs<Enum.ResamplerMode>?,
	ScaleType: Fusion.UsedAs<Enum.ScaleType>?,
	SliceCenter: Fusion.UsedAs<Rect>?,
	SliceScale: Fusion.UsedAs<number>?,
	TileSize: Fusion.UsedAs<UDim2>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)

	local FallbackImage = Util.Fallback(Props.FallbackImage, "rbxasset://textures/ui/GuiImagePlaceholder.png")
	local Image = Util.Fallback(Props.Image, nil)

	local ImageInUse = Scope:Computed(function(Use)
		if Use(Image) then
			return Use(Image)
		else
			return Use(FallbackImage)
		end
	end)

	return Scope:Hydrate(Scope:Base(Util.CombineProps(Props, {
		ClassName = "ImageLabel",
		Name = script.Name,
		Size = UDim2.fromOffset(100, 100),
		AutomaticSize = Enum.AutomaticSize.None,
	}))) {
		Image = ImageInUse,
		ImageColor3 = Props.ImageColor3,
		ImageTransparency = Props.ImageTransparency,
		ImageRectOffset = Props.ImageRectOffset,
		ImageRectSize = Props.ImageRectSize,
		ResampleMode = Props.ResampleMode,
		ScaleType = Props.ScaleType,
		SliceCenter = Props.SliceCenter,
		SliceScale = Props.SliceScale,
		TileSize = Props.TileSize,
	}
end


local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local Frame = require(OnyxUI.Components.Frame)
local Image = require(OnyxUI.Components.Image)
local Components = {
	Frame = Frame,
	Image = Image,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			AutomaticSize = Enum.AutomaticSize.XY,
			List = {
				FillDirection = Enum.FillDirection.Horizontal,
				Padding = Scope:UDim(0, Theme.Spacing["0.75"]),
			},

			[Children] = {
				Scope:Image {
					Image = "rbxassetid://15307540148",
				},
				Scope:Image {},
				Scope:Image {
					FallbackImage = "rbxassetid://13193902400",
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class ProgressBar

		Useful for communicating progress towards goals.
]=]

--[=[
	@interface ProgressBarProps
	@within ProgressBar

	. ... FrameProps -- All Frame properties are accepted.
	.Inverted boolean? -- Whether the progress bar is inverted.
	.Progress number? -- The progress value, ranging from 0 to 1.
	.Color Color3? -- The color of the progress fill.
	.Direction Enum.FillDirection? -- The direction of the progress bar (Horizontal or Vertical).
	.Length UDim? -- The length of the progress bar.
]=]

local OnyxUI = script.Parent.Parent

local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)
local ColorUtils = require(OnyxUI.Parent.ColorUtils)

local Children = Fusion.Children

local Frame = require(script.Parent.Frame)
local Components = {
	Frame = Frame,
}

export type Props = Frame.Props & {
	Progress: Fusion.UsedAs<number>?,
	Color: Fusion.UsedAs<Color3>?,
	Direction: Fusion.UsedAs<Enum.FillDirection>?,
	Inverted: Fusion.UsedAs<boolean>?,
	Length: Fusion.UsedAs<UDim>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local Progress = Util.Fallback(Props.Progress, 0)
	local Color = Util.Fallback(Props.Color, Theme.Colors.Primary.Main)
	local Direction = Util.Fallback(Props.Direction, Enum.FillDirection.Horizontal)
	local Inverted = Util.Fallback(Props.Inverted, false)
	local Length = Util.Fallback(Props.Length, Scope:UDim(0, Theme.Sizing["6"]))
	local Stroke = Util.Fallback(Props.Stroke, {
		Thickness = Theme.StrokeThickness["1"],
	})
	local Corner = Util.Fallback(Props.Corner, {
		Radius = Scope:UDim(0, Theme.CornerRadius["Full"]),
	})

	return Scope:Frame(Util.CombineProps(Props, {
		Name = script.Name,
		Size = Scope:Computed(function(Use)
			local DirectionValue = Use(Direction)
			local LengthValue = Use(Length)
			if DirectionValue == Enum.FillDirection.Horizontal then
				return UDim2.new(LengthValue, UDim.new(0, Use(Theme.TextSize["0.75"])))
			else
				return UDim2.new(UDim.new(0, Use(Theme.TextSize["0.75"])), LengthValue)
			end
		end),
		AutomaticSize = Enum.AutomaticSize.None,
		BackgroundTransparency = 0,
		BackgroundColor3 = Theme.Colors.Neutral.Dark,
		Corner = Corner,
		Padding = {
			All = Scope:Computed(function(Use)
				return UDim.new(0, Use(Use(Stroke).Thickness))
			end),
		},

		[Children] = {
			Scope:Frame {
				Name = "ProgressFill",
				Selectable = false,
				Size = Scope:Spring(
					Scope:Computed(function(Use)
						if Use(Direction) == Enum.FillDirection.Horizontal then
							return UDim2.fromScale(Use(Progress), 1)
						else
							return UDim2.fromScale(1, Use(Progress))
						end
					end),
					Theme.SpringSpeed["0.5"],
					Theme.SpringDampening["1"]
				),
				AnchorPoint = Scope:Computed(function(Use)
					if Use(Inverted) then
						if Use(Direction) == Enum.FillDirection.Horizontal then
							return Vector2.new(1, 0)
						else
							return Vector2.new(0, 1)
						end
					else
						return Vector2.new(0, 0)
					end
				end),
				Position = Scope:Computed(function(Use)
					if Use(Inverted) then
						if Use(Direction) == Enum.FillDirection.Horizontal then
							return UDim2.fromScale(1, 0)
						else
							return UDim2.fromScale(0, 1)
						end
					else
						return UDim2.fromScale(0, 0)
					end
				end),
				AutomaticSize = Enum.AutomaticSize.None,
				BackgroundTransparency = 0,
				BackgroundColor3 = Color,
				Stroke = {
					Color = Color3.fromRGB(255, 255, 255),
					Gradient = {
						Color = Scope:Computed(function(Use)
							return ColorSequence.new({
								ColorSequenceKeypoint.new(
									0,
									ColorUtils.Lighten(Use(Color), Use(Theme.StrokeEmphasis.Regular))
								),
								ColorSequenceKeypoint.new(1, Use(Color)),
							})
						end),
						Rotation = 90,
					},
				},
				Corner = Corner
			},
		},
	}))
end


local OnyxUI = script.Parent.Parent

local Fusion = require(OnyxUI.Parent.Fusion)
local ColorUtils = require(OnyxUI.Parent.ColorUtils)
local Themer = require(OnyxUI.Themer)
local Util = require(OnyxUI.Util)

local Children = Fusion.Children

local Frame = require(script.Parent.Frame)
local ProgressBar = require(script.Parent.ProgressBar)
local Text = require(script.Parent.Text)
local Components = {
	Frame = Frame,
	ProgressBar = ProgressBar,
	Text = Text,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		local Progress = Scope:Value(0)
		local Color = Scope:Value(Color3.fromRGB(255, 0, 0))

		local ColorThread = task.spawn(function()
			while task.wait(1) do
				Progress:set(math.random(0, 1000) / 1000)
				Color:set(ColorUtils.Rotate(Fusion.peek(Color), 100))
			end
		end)

		Scope:innerScope({
			function()
				task.cancel(ColorThread)
			end,
		})

		Scope:Frame {
			Parent = Props.target,
			List = {
				HorizontalFlex = Enum.UIFlexAlignment.Fill,
				Padding = Scope:UDim(0, Theme.Spacing["1.5"]),
			},

			[Children] = {
				Scope:Frame {
					List = {
						HorizontalFlex = Enum.UIFlexAlignment.Fill,
						Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
					},

					[Children] = {
						Scope:Text {
							Text = "Horizontal",
						},
						Scope:ProgressBar {
							Progress = 0.75,
						},
						Scope:ProgressBar {
							Progress = Progress,
							Color = Color,
						},
					},
				},
				Scope:Frame {
					List = {
						HorizontalFlex = Enum.UIFlexAlignment.Fill,
						Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
					},

					[Children] = {
						Scope:Text {
							Text = "With text",
						},
						Scope:Frame {
							List = {
								FillDirection = Enum.FillDirection.Horizontal,
								VerticalAlignment = Enum.VerticalAlignment.Center,
								Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
							},

							[Children] = {
								Scope:Text {
									Text = "HP",
								},
								Scope:ProgressBar {
									Progress = Progress,
									Color = Util.Colors.Red["500"],
									FlexMode = Enum.UIFlexMode.Fill,
								},
							},
						},
					},
				},
				Scope:Frame {
					List = {
						HorizontalFlex = Enum.UIFlexAlignment.Fill,
						Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
					},

					[Children] = {
						Scope:Text {
							Text = "Inverted",
						},
						Scope:ProgressBar {
							Progress = Progress,
							Inverted = true,
						},
					},
				},
				Scope:Frame {
					List = {
						HorizontalFlex = Enum.UIFlexAlignment.Fill,
						Padding = Scope:UDim(0, Theme.Spacing["0.75"]),
					},

					[Children] = {
						Scope:Text {
							Text = "Vertical",
						},
						Scope:Frame {
							List = {
								FillDirection = Enum.FillDirection.Horizontal,
								Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
							},

							[Children] = {

								Scope:ProgressBar {
									Direction = Enum.FillDirection.Vertical,
									Progress = Progress,
									Color = Color,
								},
								Scope:ProgressBar {
									Direction = Enum.FillDirection.Vertical,
									Progress = Progress,
									Inverted = true,
									Color = Color,
								},
								Scope:ProgressBar {
									Direction = Enum.FillDirection.Vertical,
									Progress = Progress,
									Color = Color,
								},
								Scope:ProgressBar {
									Direction = Enum.FillDirection.Vertical,
									Progress = Progress,
									Inverted = true,
									Color = Color,
								},
								Scope:ProgressBar {
									Direction = Enum.FillDirection.Vertical,
									Progress = Progress,
									Color = Color,
								},
								Scope:ProgressBar {
									Direction = Enum.FillDirection.Vertical,
									Progress = Progress,
									Inverted = true,
									Color = Color,
								},
								Scope:ProgressBar {
									Direction = Enum.FillDirection.Vertical,
									Progress = Progress,
									Color = Color,
								},
								Scope:ProgressBar {
									Direction = Enum.FillDirection.Vertical,
									Progress = Progress,
									Inverted = true,
									Color = Color,
								},
								Scope:ProgressBar {
									Direction = Enum.FillDirection.Vertical,
									Progress = Progress,
									Color = Color,
								},
								Scope:ProgressBar {
									Direction = Enum.FillDirection.Vertical,
									Progress = Progress,
									Inverted = true,
									Color = Color,
								},
							},
						},
					},
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class Scroller

		For making Content scrollable. Equivalent to Roblox's `Scroller` instance.
]=]

--[=[
	@interface ScrollerProps
	@within Scroller

	. ... BaseProps -- All Base properties are accepted.
	.AutomaticCanvasSize Enum.AutomaticSize? -- Determines whether the canvas size is automatically adjusted.
	.BottomImage string? -- The image used for the bottom scroll bar.
	.CanvasPosition Vector2? -- The position of the canvas.
	.CanvasSize UDim2? -- The size of the canvas.
	.ElasticBehavior Enum.ElasticBehavior? -- Determines the elastic behavior of the scrolling frame.
	.HorizontalScrollBarInset Enum.ScrollBarInset? -- Determines the inset behavior of the horizontal scroll bar.
	.MidImage string? -- The image used for the middle scroll bar.
	.ScrollBarImageColor3 Color3? -- The color of the scroll bar image.
	.ScrollBarImageTransparency number? -- The transparency of the scroll bar image.
	.ScrollBarThickness number? -- The thickness of the scroll bar.
	.ScrollingDirection Enum.ScrollingDirection? -- The direction in which scrolling is allowed.
	.ScrollingEnabled boolean? -- Determines whether scrolling is enabled.
	.TopImage string? -- The image used for the top scroll bar.
	.VerticalScrollBarInset Enum.ScrollBarInset? -- Determines the inset behavior of the vertical scroll bar.
	.VerticalScrollBarPosition Enum.VerticalScrollBarPosition? -- Determines the position of the vertical scroll bar.
]=]

local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)
local Util = require(OnyxUI.Util)

local Base = require(script.Parent.Base)
local Components = {
	Base = Base,
}

export type Props = Base.Props & {
	AutomaticCanvasSize: Fusion.UsedAs<Enum.AutomaticSize>?,
	BottomImage: Fusion.UsedAs<string>?,
	CanvasPosition: Fusion.UsedAs<Vector2>?,
	CanvasSize: Fusion.UsedAs<UDim2>?,
	ElasticBehavior: Fusion.UsedAs<Enum.ElasticBehavior>?,
	HorizontalScrollBarInset: Fusion.UsedAs<Enum.ScrollBarInset>?,
	MidImage: Fusion.UsedAs<string>?,
	ScrollBarImageColor3: Fusion.UsedAs<Color3>?,
	ScrollBarImageTransparency: Fusion.UsedAs<number>?,
	ScrollBarThickness: Fusion.UsedAs<number>?,
	ScrollingDirection: Fusion.UsedAs<Enum.ScrollingDirection>?,
	ScrollingEnabled: Fusion.UsedAs<boolean>?,
	TopImage: Fusion.UsedAs<string>?,
	VerticalScrollBarInset: Fusion.UsedAs<Enum.ScrollBarInset>?,
	VerticalScrollBarPosition: Fusion.UsedAs<Enum.VerticalScrollBarPosition>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props): Instance
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local BottomImage = Util.Fallback(
		Props.BottomImage,
		Scope:Computed(function(Use)
			if Use(Theme.CornerRadius["1"]) >= 3 then
				return "rbxassetid://16547643439"
			else
				return "rbxassetid://16547330984"
			end
		end)
	)
	local TopImage = Util.Fallback(
		Props.TopImage,
		Scope:Computed(function(Use)
			if Use(Theme.CornerRadius["1"]) >= 3 then
				return "rbxassetid://16547667444"
			else
				return "rbxassetid://16547330984"
			end
		end)
	)
	local MidImage = Util.Fallback(Props.MidImage, "rbxassetid://16547330984")
	local ScrollBarImageColor3 = Util.Fallback(Props.ScrollBarImageColor3, Theme.Colors.NeutralContent.Dark)
	local ScrollBarImageTransparency = Util.Fallback(Props.ScrollBarImageTransparency, 0)
	local ScrollBarThickness = Util.Fallback(Props.ScrollBarThickness, 8)
	local AutomaticCanvasSize = Util.Fallback(Props.AutomaticCanvasSize, Enum.AutomaticSize.Y)
	local ScrollingDirection = Util.Fallback(Props.ScrollingDirection, Enum.ScrollingDirection.Y)

	return Scope:Hydrate(Scope:Base(Util.CombineProps(Props, {
		ClassName = "ScrollingFrame",
		Name = script.Name,
		Selectable = false,
		BackgroundTransparency = 1,
		AutomaticSize = Enum.AutomaticSize.None,
	}))) {
		BottomImage = BottomImage,
		TopImage = TopImage,
		MidImage = MidImage,
		ScrollBarImageColor3 = ScrollBarImageColor3,
		ScrollBarImageTransparency = ScrollBarImageTransparency,
		ScrollBarThickness = ScrollBarThickness,
		AutomaticCanvasSize = AutomaticCanvasSize,
		ScrollingDirection = ScrollingDirection,
		CanvasPosition = Props.CanvasPosition,
		CanvasSize = Props.CanvasSize,
		ElasticBehavior = Props.ElasticBehavior,
		HorizontalScrollBarInset = Props.HorizontalScrollBarInset,
		ScrollingEnabled = Props.ScrollingEnabled,
		VerticalScrollBarInset = Props.VerticalScrollBarInset,
		VerticalScrollBarPosition = Props.VerticalScrollBarPosition,
	}
end


local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)

local Scroller = require(script.Parent.Scroller)
local Components = {
	Scroller = Scroller,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)

		Scope:Scroller {
			Parent = Props.target,
			Size = UDim2.fromOffset(200, 200),
			CanvasSize = UDim2.new(UDim.new(1, 0), UDim.new(0, 1000)),
			AutomaticCanvasSize = Enum.AutomaticSize.None,
		}
	end,
	fusion = Fusion,
}


--[=[
		@class Slider

		For draggable linear selection.
]=]

--[=[
	@interface SliderProps
	@within Slider

	. ... BaseButtonProps -- All BaseButton properties are accepted.
	.Value number? -- The current value of the slider (0 to 1).
	.Disabled boolean? -- Whether the slider is disabled.
	.Unit number? -- The step size for the slider value.
	.Color Color3? -- The color of the slider.
]=]

local OnyxUI = script.Parent.Parent

local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)
local Util = require(OnyxUI.Util)
local ColorUtils = require(OnyxUI.Parent.ColorUtils)

local Children = Fusion.Children
local Out = Fusion.Out
local OnEvent = Fusion.OnEvent

local BaseButton = require(script.Parent.BaseButton)
local Components = {
	BaseButton = BaseButton,
}

function RoundByUnit(Number, Factor)
	return math.floor(Number / Factor + 0.5) * Factor
end

local DISABLED_BACKGROUND_TRANSPARENCY = 0.925

export type Props = BaseButton.Props & {
	Value: Fusion.UsedAs<number>?,
	Disabled: Fusion.UsedAs<boolean>?,
	Unit: Fusion.UsedAs<number>?,
	Color: Fusion.UsedAs<Color3>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local Unit = Util.Fallback(Props.Unit, 1 / 100)
	local Value = Scope:EnsureValue(Util.Fallback(Props.Value, 1))
	local Color = Util.Fallback(Props.Color, Theme.Colors.Primary.Main)
	local Disabled = Util.Fallback(Props.Disabled, false)

	local Hovering = Scope:EnsureValue(Util.Fallback(Props.Hovering, false))
	local Holding = Scope:EnsureValue(Util.Fallback(Props.Holding, false))

	local DragDetector = Scope:Value(nil)
	local AbsoluteSize = Scope:Value(Vector2.new())
	local AbsolutePosition = Scope:Value(Vector2.new())

	local EffectiveColor = Scope:Computed(function(Use)
		if Use(Disabled) then
			return Use(Theme.Colors.BaseContent.Main)
		else
			if Use(Holding) then
				return Util.Emphasize(Use(Color), Use(Theme.Emphasis.Regular))
			elseif Use(Hovering) then
				return Util.Emphasize(Use(Color), Use(Theme.Emphasis.Light))
			else
				return Use(Color)
			end
		end
	end)
	local EffectiveStrokeEmphasis = Scope:Computed(function(Use)
		local StrokeEmphasis = Use(Theme.StrokeEmphasis.Regular)

		if Use(Disabled) or Use(Hovering) or Use(Holding) then
			return 0
		else
			return StrokeEmphasis
		end
	end)

	local function ProcessDrag(Position: Vector2)
		local AbsoluteSizeValue = Fusion.peek(AbsoluteSize)
		local AbsolutePositionValue = Fusion.peek(AbsolutePosition)
		local UnitValue = Fusion.peek(Unit)

		local RelativePosition = Position.X - AbsolutePositionValue.X

		Value:set(RoundByUnit(math.clamp(RelativePosition / AbsoluteSizeValue.X, 0, 1), UnitValue))
	end

	local Slider = Scope:BaseButton(Util.CombineProps(Props, {
		Name = script.Name,
		Corner = {
			Radius = Scope:UDim(0, Theme.CornerRadius.Full),
		},
		Size = Scope:UDim2Offset(0, Theme.TextSize["1"]),
		AutomaticSize = Enum.AutomaticSize.Y,

		Disabled = Disabled,
		Hovering = Hovering,
		Holding = Holding,

		[Children] = {
			DragDetector,
			Scope:Hydrate(Scope:Frame {
				Name = "Progress",
				Selectable = false,
				BackgroundTransparency = 0,
				BackgroundColor3 = Theme.Colors.Neutral.Main,
				Size = Scope:UDim2(1, 0, 0, Theme.TextSize["0.75"]),
				Corner = {
					Radius = Scope:UDim(0, Theme.CornerRadius.Full),
				},
				Flex = {
					Mode = Enum.UIFlexMode.Fill,
				},

				[Children] = {
					Scope:Frame {
						Name = "Progress",
						Selectable = false,
						Size = Scope:Computed(function(Use)
							local ValueValue = Use(Value)
							return UDim2.fromScale(ValueValue, 1)
						end),
						BackgroundTransparency = Scope:Computed(function(Use)
							if Use(Disabled) then
								return DISABLED_BACKGROUND_TRANSPARENCY
							else
								return 0
							end
						end),
						BackgroundColor3 = Scope:Spring(
							EffectiveColor,
							Theme.SpringSpeed["1"],
							Theme.SpringDampening["1"]
						),
						AutomaticSize = Enum.AutomaticSize.None,
						Corner = {
							Radius = Scope:UDim(0, Theme.CornerRadius.Full),
						},
						Stroke = {
							Color = Color3.fromRGB(255, 255, 255),
							Gradient = {
								Color = Scope:Computed(function(Use)
									return ColorSequence.new({
										ColorSequenceKeypoint.new(
											0,
											ColorUtils.Lighten(Use(EffectiveColor), Use(EffectiveStrokeEmphasis))
										),
										ColorSequenceKeypoint.new(1, Use(EffectiveColor)),
									})
								end),
								Rotation = 90,
							},
							Transparency = Scope:Computed(function(Use)
								if Use(Disabled) then
									return DISABLED_BACKGROUND_TRANSPARENCY
								else
									return 0
								end
							end),
						},

						[Children] = {
							Scope:Frame {
								Name = "Ball",
								Selectable = false,
								AnchorPoint = Scope:Vector2(Value, 0.5),
								Position = UDim2.fromScale(1, 0.5),
								Size = Scope:UDim2Offset(Theme.TextSize["1"], Theme.TextSize["1"]),
								BackgroundTransparency = 0,
								BackgroundColor3 = Scope:Computed(function(Use)
									if Use(Disabled) then
										return Use(Theme.Colors.Neutral.Main)
									else
										return Use(Theme.Colors.BaseContent.Main)
									end
								end),
								AutomaticSize = Enum.AutomaticSize.None,
								Corner = {
									Radius = Scope:UDim(0, Theme.CornerRadius.Full),
								},
								Stroke = {
									Color = Theme.Colors.Neutral.Main,
									Thickness = Theme.StrokeThickness["1"],
								},
								Aspect = {
									Ratio = 1,
								},

								[Children] = {
									Scope:Frame {
										Name = "SmallBall",
										Selectable = false,
										AspectRatio = 1,
										AnchorPoint = Vector2.new(0.5, 0.5),
										Position = UDim2.fromScale(0.5, 0.5),
										Size = Scope:UDim2Offset(Theme.TextSize["0.75"], Theme.TextSize["0.75"]),
										BackgroundTransparency = 0,
										BackgroundColor3 = Scope:Spring(
											Scope:Computed(function(Use)
												if Use(Disabled) then
													return Use(Theme.Colors.Neutral.Dark)
												else
													return Use(EffectiveColor)
												end
											end),
											Theme.SpringSpeed["1"],
											Theme.SpringDampening["1"]
										),
										AutomaticSize = Enum.AutomaticSize.None,
										Corner = {
											Radius = Scope:UDim(0, Theme.CornerRadius.Full),
										},
									},
								},
							},
						},
					},
				},
			}) {
				[Out "AbsoluteSize"] = AbsoluteSize,
				[Out "AbsolutePosition"] = AbsolutePosition,
			},
		},
	}))

	DragDetector:set(Scope:New "UIDragDetector" {
		DragStyle = Enum.UIDragDetectorDragStyle.Scriptable,
		DragAxis = Vector2.new(1, 0),
		BoundingUI = Slider,
		Enabled = Scope:Computed(function(Use)
			return not Use(Disabled)
		end),

		[OnEvent "DragContinue"] = function(Position: Vector2)
			ProcessDrag(Position)
		end,
		[OnEvent "DragStart"] = function(Position: Vector2)
			ProcessDrag(Position)
			Holding:set(true)
		end,
		[OnEvent "DragEnd"] = function()
			Holding:set(false)
		end,
	})

	return Slider
end


local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local Components = {
	Slider = require(OnyxUI.Components.Slider),
	Frame = require(OnyxUI.Components.Frame),
}

return {
	story = function(Props)
		local Scope = Fusion.scoped(Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			Size = Scope:UDim2Offset(Theme.Sizing["16"], 0),
			AutomaticSize = Enum.AutomaticSize.Y,
			Padding = {
				All = Scope:UDim(0, Theme.StrokeThickness["1"]),
			},
			List = {
				Padding = Scope:UDim(0, Theme.Spacing["1"]),
				HorizontalFlex = Enum.UIFlexAlignment.Fill,
			},

			[Children] = {
				Scope:Slider {},
				Scope:Slider {
					Value = 1 / 10,
					Unit = 1 / 10,
					Color = Util.Colors.Cyan["500"],
				},
				Scope:Slider {
					Value = 1 / 4,
					Unit = 1 / 4,
					Color = Util.Colors.Fuchsia["500"],
				},
				Scope:Slider {
					Disabled = true,
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class Switch

		Useful for letting the user switch things on / off.
]=]

--[=[
		@interface SwitchProps
		@within Switch

		. ... BaseButtonProps -- All BaseButton properties are accepted.
		.Switched boolean? -- Whether the switch is toggled on or off
		.Disabled boolean? -- Whether the switch is disabled
		.Color Color3? -- The color of the switch when toggled on
]=]

local OnyxUI = script.Parent.Parent

local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)
local ColorUtils = require(OnyxUI.Parent.ColorUtils)

local Children = Fusion.Children

local Frame = require(script.Parent.Frame)
local BaseButton = require(script.Parent.BaseButton)
local Components = {
	Frame = Frame,
	BaseButton = BaseButton,
}

export type Props = BaseButton.Props & {
	Switched: Fusion.UsedAs<boolean>?,
	Color: Fusion.UsedAs<Color3>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local Switched = Scope:EnsureValue(Util.Fallback(Props.Switched, false))
	local Disabled = Scope:EnsureValue(Util.Fallback(Props.Disabled, false))
	local Color = Util.Fallback(Props.Color, Theme.Colors.Primary.Main)
	local Size = Util.Fallback(
		Props.Size,
		Scope:Computed(function(Use)
			return UDim2.fromOffset(Use(Theme.TextSize["1"]) * 2, Use(Theme.TextSize["1"]))
		end)
	)
	local AutomaticSize = Util.Fallback(Props.AutomaticSize, Enum.AutomaticSize.None)
	local Corner = Util.Fallback(Props.Corner, {
		Radius = Scope:UDim(0, Theme.CornerRadius["Full"]),
	})

	local Hovering = Scope:EnsureValue(Util.Fallback(Props.Hovering, false))
	local Holding = Scope:EnsureValue(Util.Fallback(Props.Holding, false))

	local EffectiveColor = Scope:Computed(function(Use)
		local ActiveColor
		if Use(Switched) then
			ActiveColor = Use(Color)
		else
			ActiveColor = Use(Theme.Colors.NeutralContent.Dark)
		end

		if not Use(Disabled) then
			if Use(Holding) then
				return Util.Emphasize(Use(ActiveColor), Use(Theme.Emphasis.Regular))
			elseif Use(Hovering) then
				return Util.Emphasize(Use(ActiveColor), Use(Theme.Emphasis.Light))
			end
		end

		return ActiveColor
	end)
	local EffectiveBallColor = Scope:Computed(function(Use): any
		local ActiveColor
		if Use(Switched) then
			ActiveColor = Use(Theme.Colors.Base.Main)
		else
			ActiveColor = Use(Theme.Colors.NeutralContent.Dark)
		end

		if not Use(Disabled) then
			if Use(Holding) then
				if not Use(Switched) then
					return Util.Emphasize(Use(ActiveColor), Use(Theme.Emphasis.Regular))
				else
					return ActiveColor
				end
			elseif Use(Hovering) then
				if not Use(Switched) then
					return Util.Emphasize(Use(ActiveColor), Use(Theme.Emphasis.Light))
				else
					return ActiveColor
				end
			end
		end

		return ActiveColor
	end)
	local EffectiveStrokeEmphasis = Scope:Computed(function(Use)
		local StrokeEmphasis = Use(Theme.StrokeEmphasis.Regular)

		if Use(Disabled) or Use(Hovering) or Use(Holding) then
			return 0
		else
			return StrokeEmphasis
		end
	end)

	return Scope:BaseButton(Util.CombineProps(Props, {
		Name = script.Name,
		Size = Size,
		AutomaticSize = AutomaticSize,
		Disabled = Disabled,
		Stroke = {
			Color = Color3.fromRGB(255, 255, 255),
			Gradient = {
				Color = Scope:Computed(function(Use)
					return ColorSequence.new({
						ColorSequenceKeypoint.new(
							0,
							ColorUtils.Lighten(Use(EffectiveColor), Use(EffectiveStrokeEmphasis))
						),
						ColorSequenceKeypoint.new(1, Use(EffectiveColor)),
					})
				end),
				Rotation = 90,
			},
			Transparency = Scope:Spring(
				Scope:Computed(function(Use)
					if Use(Disabled) then
						return 0.8
					end
					if Use(Switched) then
						return 0
					else
						return 0.6
					end
				end),
				Theme.SpringSpeed["1"],
				Theme.SpringDampening["1"]
			),
		},
		Corner = Corner,

		Hovering = Hovering,
		Holding = Holding,

		ClickSound = Theme.Sound.Switch,

		OnActivate = function()
			Switched:set(not Fusion.peek(Switched))
		end,

		[Children] = {
			Scope:Frame {
				Name = "Switch",
				Size = Size,
				AutomaticSize = AutomaticSize,
				BackgroundColor3 = Scope:Spring(EffectiveColor, Theme.SpringSpeed["1"], Theme.SpringDampening["1"]),
				BackgroundTransparency = Scope:Spring(
					Scope:Computed(function(Use)
						if Use(Switched) then
							return 0
						else
							return 1
						end
					end),
					Theme.SpringSpeed["1"],
					Theme.SpringDampening["1"]
				),
				Padding = {
					All = UDim.new(0, 2),
				},
				Corner = Corner,

				[Children] = {
					Scope:Frame {
						Name = "Ball",
						AnchorPoint = Scope:Spring(
							Scope:Computed(function(Use)
								if Use(Switched) then
									return Vector2.new(1, 0.5)
								else
									return Vector2.new(0, 0.5)
								end
							end),
							Theme.SpringSpeed["1"],
							Theme.SpringDampening["1"]
						),
						Position = Scope:Spring(
							Scope:Computed(function(Use)
								if Use(Switched) then
									return UDim2.fromScale(1, 0.5)
								else
									return UDim2.fromScale(0, 0.5)
								end
							end),
							Theme.SpringSpeed["1"],
							Theme.SpringDampening["1"]
						),
						Size = Scope:Spring(
							Scope:Computed(function(Use)
								local Scale = 0.85
								if Use(Hovering) then
									Scale *= 1.1
								end

								return UDim2.fromScale(0, Scale)
							end),
							Theme.SpringSpeed["1"],
							Theme.SpringDampening["1"]
						),
						AutomaticSize = Enum.AutomaticSize.None,
						BackgroundTransparency = Scope:Spring(
							Scope:Computed(function(Use)
								if Use(Disabled) then
									return 0.7
								end
								if Use(Switched) then
									return 0
								else
									return 0
								end
							end),
							Theme.SpringSpeed["1"],
							Theme.SpringDampening["1"]
						),
						BackgroundColor3 = Scope:Spring(
							EffectiveBallColor,
							Theme.SpringSpeed["1"],
							Theme.SpringDampening["1"]
						),
						Aspect = {
							Ratio = Scope:Spring(
								Scope:Computed(function(Use)
									if Use(Holding) then
										return 1.2
									else
										return 1
									end
								end),
								Theme.SpringSpeed["1"],
								Theme.SpringDampening["1"]
							),
							Type = Enum.AspectType.ScaleWithParentSize,
							DominantAxis = Enum.DominantAxis.Height,
						},
						Corner = Corner,
					},
				},
			},
		},
	}, { "Size", "AutomaticSize" }))
end


local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local Switch = require(script.Parent.Switch)
local Frame = require(script.Parent.Frame)
local Components = {
	Switch = Switch,
	Frame = Frame,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			Padding = {
				All = Scope:UDim(0, Theme.StrokeThickness["1"]),
			},
			List = {
				Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
			},

			[Children] = {
				Scope:Switch {},
				Scope:Switch {
					Disabled = true,
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class Tabs

		For tabbing between things.
]=]

--[=[
	@interface TabsProps
	@within Tabs

	. ... ButtonProps -- All Button properties are accepted.
	.Tabs { { string } }? -- List of tabs
	.Tab number? -- The currently active tab
	.Disabled boolean? -- Whether the tabs are disabled
	.Color Color3? -- Color of the active tab
	.ButtonProps Button.Props? -- Property overrides for child buttons
]=]

local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)
local Util = require(OnyxUI.Util)

local Children = Fusion.Children

local Frame = require(script.Parent.Frame)
local Button = require(script.Parent.Button)

export type Props = Frame.Props & {
	Tabs: Fusion.UsedAs<{ { string } }>?,
	Tab: Fusion.UsedAs<number>?,
	Disabled: Fusion.UsedAs<boolean>?,
	Color: Fusion.UsedAs<Color3>?,
	ButtonProps: Button.Props?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, {
		Frame = Frame,
		Button = Button,
	})
	local Theme = Themer.Theme:now()

	local Tabs = Util.Fallback(Props.Tabs, {})
	local Disabled = Util.Fallback(Props.Disabled, false)
	local Color = Util.Fallback(Props.Color, Theme.Colors.Primary.Main)
	local Corner = Util.Fallback(Props.Corner, {
		Radius = Scope:UDim(0, Theme.CornerRadius["1"]),
	})
	local ButtonProps = Util.Fallback(Props.ButtonProps, {})

	local Tab = Scope:EnsureValue(Util.Fallback(Props.Tab, 1))

	return Scope:Frame(Util.CombineProps(Props, {
		Name = script.Name,
		Corner = Corner,
		List = {
			FillDirection = Enum.FillDirection.Horizontal,
			Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
		},

		[Children] = {
			Scope:ForPairs(Tabs, function(Use, Scope, Index: number, TabContent: { string })
				return Index,
					Themer.Theme:is(Theme):during(function()
						return Scope:Button(Util.CombineProps(ButtonProps, {
							Name = "Tab",
							Content = TabContent,
							Disabled = Disabled,
							Color = Scope:Computed(function(Use)
								if Index == Use(Tab) then
									return Use(Color)
								else
									return Use(Theme.Colors.Neutral.Main)
								end
							end),
							Corner = Corner,

							OnActivate = function()
								Tab:set(Index)
							end,
						}))
					end)
			end),
		},
	}))
end


local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local Tabs = require(script.Parent.Tabs)
local Frame = require(script.Parent.Frame)
local Components = {
	Tabs = Tabs,
	Frame = Frame,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			List = {
				Padding = Scope:UDim(0, Theme.Spacing["0.75"]),
			},
			Padding = {
				All = Scope:UDim(0, Theme.StrokeThickness["1"]),
			},

			[Children] = {
				Scope:Tabs {
					Tabs = { { "Tab 1" }, { "Tab 2" } },
				},
				Scope:Tabs {
					Tabs = { { "Tab 1" }, { "Tab 2" } },
					Disabled = true,
				},
				Scope:Tabs {
					Tabs = { { "1" }, { "2" }, { "3" } },
					Color = Theme.Colors.BaseContent.Main,
					Corner = {
						Radius = Scope:UDim(0, Theme.CornerRadius["0"]),
					},
					List = {
						Padding = Scope:UDim(0, Theme.Spacing["0"]),
					},
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class Text

		For displaying text. *Lorem ipsum dolo..*
]=]

--[=[
	@interface TextProps
	@within Text

	. ... BaseProps -- All Base properties are accepted.
	.Text string? -- The text to display.
	.TextColor3 Color3? -- The color of the text.
	.TextSize number? -- The size of the text.
	.RichText boolean? -- Whether the text supports rich text formatting.
	.FontFace Font? -- The font face of the text.
	.TextWrapped boolean? -- Whether the text should wrap when it exceeds the label's width.
	.TextXAlignment Enum.TextXAlignment? -- The horizontal alignment of the text.
	.TextYAlignment Enum.TextYAlignment? -- The vertical alignment of the text.
	.Font Enum.Font? -- The font of the text (deprecated, use FontFace instead).
	.TextScaled boolean? -- Whether the text should scale to fit the label.
	.TextTruncate boolean? -- Whether the text should truncate when it exceeds the label's width.
	.LineHeight number? -- The line height of the text.
	.LocalizedText string? -- The localized text to display.
	.MaxVisibleGraphemes number? -- The maximum number of visible graphemes.
	.TextTransparency number? -- The transparency of the text.
]=]

local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)

local Base = require(script.Parent.Base)
local Components = {
	Base = Base,
}

export type Props = Base.Props & {
	Text: Fusion.UsedAs<string>?,
	TextColor3: Fusion.UsedAs<Color3>?,
	TextSize: Fusion.UsedAs<number>?,
	RichText: Fusion.UsedAs<boolean>?,
	FontFace: Fusion.UsedAs<Font>?,
	TextWrapped: Fusion.UsedAs<boolean>?,
	TextXAlignment: Fusion.UsedAs<Enum.TextXAlignment>?,
	TextYAlignment: Fusion.UsedAs<Enum.TextYAlignment>?,
	Font: Fusion.UsedAs<Enum.Font>?,
	TextScaled: Fusion.UsedAs<boolean>?,
	TextTruncate: Fusion.UsedAs<boolean>?,
	LineHeight: Fusion.UsedAs<number>?,
	LocalizedText: Fusion.UsedAs<string>?,
	MaxVisibleGraphemes: Fusion.UsedAs<number>?,
	TextTransparency: Fusion.UsedAs<number>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props): Instance
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local TextColor3 = Util.Fallback(Props.TextColor3, Theme.Colors.BaseContent.Main)
	local TextSize = Util.Fallback(Props.TextSize, Theme.TextSize["1"])
	local RichText = Util.Fallback(Props.RichText, true)
	local FontFace = Util.Fallback(Props.FontFace, Scope:Font(Theme.Font.Body, Theme.FontWeight.Body))
	local TextWrapped = Util.Fallback(Props.TextWrapped, true)
	local TextXAlignment = Util.Fallback(Props.TextXAlignment, Enum.TextXAlignment.Left)
	local TextYAlignment = Util.Fallback(Props.TextYAlignment, Enum.TextYAlignment.Top)

	return Scope:Hydrate(Scope:Base(Util.CombineProps(Props, {
		ClassName = "TextLabel",
		Name = script.Name,
		AutomaticSize = Enum.AutomaticSize.XY,
		ClipsDescendants = false,
		BackgroundTransparency = 1,
	}))) {
		TextColor3 = TextColor3,
		TextSize = TextSize,
		RichText = RichText,
		FontFace = FontFace,
		TextWrapped = TextWrapped,
		TextXAlignment = TextXAlignment,
		TextYAlignment = TextYAlignment,

		Text = Props.Text,
		TextTransparency = Props.TextTransparency,
		MaxVisibleGraphemes = Props.MaxVisibleGraphemes,
		LocalizedText = Props.LocalizedText,
		LineHeight = Props.LineHeight,
		TextTruncate = Props.TextTruncate,
		TextScaled = Props.TextScaled,
		Font = Props.Font,
	}
end


local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)

local Components = {
	Text = require(OnyxUI.Components.Text),
}

return {
	story = function(Props)
		local Scope = Fusion.scoped(Fusion, Util, Components)

		Scope:Text {
			Parent = Props.target,
			Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
		}
	end,
	fusion = Fusion,
}


--[=[
		@class TextArea

		Like [`TextInput`](/api/TextInput), but dedicated for multi-line use-cases.
]=]

--[=[
		@within TextArea
		@interface TextAreaProps

		. ... TextInputProps -- All TextInput properties are accepted.
]=]

local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Fusion = require(OnyxUI.Parent.Fusion)

local TextInput = require(OnyxUI.Components.TextInput)
local Components = {
	TextInput = TextInput,
}

export type Props = TextInput.Props & {}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)

	return Scope:TextInput(Util.CombineProps(Props, {
		AutomaticSize = Enum.AutomaticSize.None,
		TextWrapped = true,
		MultiLine = true,
	}))
end


local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local TextArea = require(script.Parent.TextArea)
local Frame = require(script.Parent.Frame)
local Components = {
	TextArea = TextArea,
	Frame = Frame,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			Size = UDim2.fromOffset(250, 0),
			AutomaticSize = Enum.AutomaticSize.Y,
			List = {
				Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
			},
			Padding = {
				All = Scope:UDim(0, Theme.StrokeThickness["1"]),
			},

			[Children] = {
				Scope:TextArea {
					Size = Scope:Computed(function(Use)
						return UDim2.new(
							UDim.new(1, 0),
							UDim.new(0, (Use(Theme.TextSize["1"]) * 2) + (Use(Theme.Spacing["0.5"]) * 2))
						)
					end),
				},
				Scope:TextArea {
					Size = UDim2.new(UDim.new(1, 0), UDim.new(0, 100)),
					CharacterLimit = 60,
					PlaceholderText = "This TextArea has a character limit.",
				},
				Scope:TextArea {
					Size = UDim2.new(UDim.new(1, 0), UDim.new(0, 0)),
					AutomaticSize = Enum.AutomaticSize.Y,
					PlaceholderText = "This one will expand as you type.",
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class TextInput

		For letting the user input text.
]=]

--[=[
	@interface TextInputProps
	@within TextInput

	. ... BaseProps -- All Base properties are accepted.
	.Disabled boolean? -- Whether the text input is disabled.
	.CharacterLimit number? -- The maximum number of characters allowed in the text input.
	.Color Color3? -- The color of the text input's border when focused.
	.PlaceholderText string? -- The placeholder text displayed when the input is empty.
	.Text string? -- The current text in the text input.
	.ClearTextOnFocus boolean? -- Whether the text should be cleared when the input gains focus.
	.TextWrapped boolean? -- Whether the text should wrap within the input.
	.TextEditable boolean? -- Whether the text input is editable.
	.MultiLine boolean? -- Whether the text input supports multiple lines.
	.TextSize number? -- The size of the text.
	.TextColor3 Color3? -- The color of the text.
	.FontFace Font? -- The font face of the text.
	.PlaceholderColor3 Color3? -- The color of the placeholder text.
	.TextXAlignment Enum.TextXAlignment? -- The horizontal alignment of the text.
	.TextYAlignment Enum.TextYAlignment? -- The vertical alignment of the text.
	.TextTransparency number? -- The transparency of the text.
	.TextProcessor (string) -> string? -- A function to process the text input, and return new text.
	.Focusing boolean? -- Whether the text input is currently focused.
	.Hovering boolean? -- Whether the mouse is hovering over the text input.
	.OnFocus () -> ()? -- A callback function triggered when the text input gains focus.
	.OnFocusEnd () -> ()? -- A callback function triggered when the text input loses focus.
]=]

local SoundService = game:GetService("SoundService")

local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)

local OnEvent = Fusion.OnEvent

local Base = require(script.Parent.Base)
local Components = {
	Base = Base,
}

export type Props = Base.Props & {
	Disabled: Fusion.UsedAs<boolean>?,
	CharacterLimit: Fusion.UsedAs<number>?,
	Color: Fusion.UsedAs<Color3>?,
	PlaceholderText: Fusion.UsedAs<string>?,
	Text: Fusion.UsedAs<string>?,
	ClearTextOnFocus: Fusion.UsedAs<boolean>?,
	TextWrapped: Fusion.UsedAs<boolean>?,
	TextEditable: Fusion.UsedAs<boolean>?,
	MultiLine: Fusion.UsedAs<boolean>?,
	TextSize: Fusion.UsedAs<number>?,
	TextColor3: Fusion.UsedAs<Color3>?,
	FontFace: Fusion.UsedAs<Font>?,
	PlaceholderColor3: Fusion.UsedAs<Color3>?,
	TextXAlignment: Fusion.UsedAs<Enum.TextXAlignment>?,
	TextYAlignment: Fusion.UsedAs<Enum.TextYAlignment>?,
	TextTransparency: Fusion.UsedAs<number>?,
	TextProcessor: Fusion.UsedAs<(string) -> string>?,

	Focusing: Fusion.UsedAs<boolean>?,
	Hovering: Fusion.UsedAs<boolean>?,

	OnFocus: Fusion.UsedAs<() -> ()>?,
	OnFocusEnd: Fusion.UsedAs<() -> ()>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local Text = Scope:EnsureValue(Util.Fallback(Props.Text, ""))
	local Focusing = Scope:EnsureValue(Util.Fallback(Props.Focusing, false))
	local Hovering = Scope:EnsureValue(Util.Fallback(Props.Hovering, false))

	local Disabled = Util.Fallback(Props.Disabled, false)
	local OnFocus = Util.Fallback(Props.OnFocus, function() end)
	local OnFocusEnd = Util.Fallback(Props.OnFocusEnd, function() end)
	local Color = Util.Fallback(Props.Color, Theme.Colors.Primary.Main)
	local CharacterLimit = Util.Fallback(Props.CharacterLimit, -1)
	local ClearTextOnFocus = Util.Fallback(Props.ClearTextOnFocus, false)
	local PlaceholderText = Util.Fallback(Props.PlaceholderText, "")
	local TextSize = Util.Fallback(Props.TextSize, Theme.TextSize["1"])
	local PlaceholderColor3 = Util.Fallback(Props.PlaceholderColor3, Theme.Colors.NeutralContent.Dark)
	local TextColor3 = Util.Fallback(
		Props.TextColor3,
		Scope:Computed(function(Use)
			return Use(Theme.Colors.BaseContent.Main)
		end)
	)
	local FontFace = Util.Fallback(Props.FontFace, Scope:Font(Theme.Font.Body, Theme.FontWeight.Body))
	local TextXAlignment = Util.Fallback(Props.TextXAlignment, Enum.TextXAlignment.Left)
	local TextYAlignment = Util.Fallback(Props.TextYAlignment, Enum.TextYAlignment.Top)
	local TextTransparency = Util.Fallback(
		Props.TextTransparency,

		Scope:Computed(function(Use)
			if Use(Disabled) then
				return 0.75
			elseif Use(Focusing) then
				return 0
			else
				return 0.5
			end
		end)
	)
	local TextProcessor = Util.Fallback(Props.TextProcessor, function(NewText)
		return NewText
	end)
	local TextEditable = Util.Fallback(
		Props.TextEditable,
		Scope:Computed(function(Use)
			return not Use(Disabled)
		end)
	)

	local InvalidInputActive = Scope:Value(false)

	local function ProcessText(NewText: string)
		local CharacterLimitValue = Fusion.peek(CharacterLimit)
		local TextProcessorValue = Fusion.peek(TextProcessor)
		local LimitedText = NewText:sub(1, utf8.offset(NewText, CharacterLimitValue))
		local FullyProcessedText = TextProcessorValue(LimitedText)

		return FullyProcessedText
	end

	local Object = Scope:Hydrate(Scope:Base(Util.CombineProps(Props, {
		ClassName = "TextBox",
		Name = script.Name,
		Corner = {
			Radius = Scope:UDim(0, Theme.CornerRadius["1"]),
		},
		Padding = {
			All = Scope:UDim(0, Theme.Padding["0.5"]),
		},
		Stroke = {
			Color = Scope:Spring(
				Scope:Computed(function(Use)
					if Use(InvalidInputActive) then
						return Use(Theme.Colors.Error.Main)
					elseif Use(Focusing) then
						return Use(Color)
					else
						return Use(Theme.Colors.NeutralContent.Dark)
					end
				end),
				Theme.SpringSpeed["1"],
				Theme.SpringDampening["1"]
			),
			Transparency = Scope:Spring(
				Scope:Computed(function(Use)
					if Use(Disabled) then
						return 0.8
					end
					if Use(Focusing) then
						return 0
					else
						return 0.6
					end
				end),
				Theme.SpringSpeed["1"],
				Theme.SpringDampening["1"]
			),
		},
		AutomaticSize = Enum.AutomaticSize.XY,
		AutoLocalize = false,
		Active = Scope:Computed(function(Use)
			return not Use(Disabled)
		end),
		BackgroundTransparency = 1,
		ClipsDescendants = true,
	}))) {
		Text = Text,
		TextColor3 = Scope:Spring(
			Scope:Computed(function(Use)
				if Use(InvalidInputActive) then
					return Use(Theme.Colors.Error.Main)
				else
					return Use(TextColor3)
				end
			end),
			Theme.SpringSpeed.Immediate,
			Theme.SpringDampening["1"]
		),
		TextSize = TextSize,
		FontFace = FontFace,
		PlaceholderColor3 = PlaceholderColor3,
		PlaceholderText = PlaceholderText,
		TextXAlignment = TextXAlignment,
		TextYAlignment = TextYAlignment,
		ClearTextOnFocus = ClearTextOnFocus,
		TextTransparency = Scope:Spring(TextTransparency, Theme.SpringSpeed["1"], Theme.SpringDampening["1"]),
		TextEditable = TextEditable,

		MultiLine = Props.MultiLine,
		TextWrapped = Props.TextWrapped,

		[OnEvent "Focused"] = function()
			if (not Fusion.peek(Disabled)) and Fusion.peek(TextEditable) then
				Focusing:set(true)

				SoundService:PlayLocalSound(Fusion.peek(Theme.Sound.Focus))

				Fusion.peek(OnFocus)()
			end
		end,
		[OnEvent "FocusLost"] = function()
			Focusing:set(false)

			Fusion.peek(OnFocusEnd)()
		end,
		[OnEvent "MouseEnter"] = function()
			if not Fusion.peek(Disabled) then
				Hovering:set(true)

				SoundService:PlayLocalSound(Fusion.peek(Theme.Sound.Hover))
			end
		end,
		[OnEvent "MouseLeave"] = function()
			if not Fusion.peek(Disabled) then
				Hovering:set(false)
			end
		end,
		[OnEvent "SelectionGained"] = function()
			if (not Fusion.peek(Disabled)) and Fusion.peek(TextEditable) then
				SoundService:PlayLocalSound(Fusion.peek(Theme.Sound.Hover))
			end
		end,
	}

	table.insert(
		Scope,
		Object:GetPropertyChangedSignal("Text"):Connect(function()
			local NewText = Object.Text
			local ProcessedText = ProcessText(NewText)

			Text:set(ProcessedText)
			if Object.Text ~= ProcessedText then
				Object.Text = ProcessedText
			end

			if utf8.len(NewText) ~= utf8.len(ProcessedText) then
				InvalidInputActive:set(true)
				task.wait(0.075)
				InvalidInputActive:set(false)
			end
		end)
	)

	return Object
end


local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local TextInput = require(script.Parent.TextInput)
local Frame = require(script.Parent.Frame)
local Components = {
	Frame = Frame,
	TextInput = TextInput,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			Size = UDim2.fromOffset(250, 0),
			AutomaticSize = Enum.AutomaticSize.Y,
			List = {
				Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
				HorizontalFlex = Enum.UIFlexAlignment.Fill,
			},
			Padding = {
				All = Scope:UDim(0, Theme.StrokeThickness["1"]),
			},

			[Children] = {
				Scope:TextInput {},
				Scope:TextInput {
					PlaceholderText = "Disabled TextInput.",
					Disabled = true,
				},
				Scope:TextInput {
					PlaceholderText = "Twenty characters only!",
					CharacterLimit = 20,
				},
				Scope:TextInput {
					PlaceholderText = "Something dangerous!",
					Color = Theme.Colors.Error.Main,
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class TextSwap

		Swap between an array values.
]=]

--[=[
	@interface TextSwapProps
	@within TextSwap

	. ... BaseButtonProps -- All BaseButton properties are accepted.
	.Options {string}? -- Array of string options to swap between
	.Selection number? -- The currently selected option index
	.Disabled boolean? -- Whether the TextSwap is disabled
	.Color Color3? -- The color of the content
	.ContentSize number? -- The size of the content
]=]

local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local BaseButton = require(script.Parent.BaseButton)
local Components = {
	BaseButton = BaseButton,
	Text = require(script.Parent.Text),
	IconButton = require(script.Parent.IconButton),
}

export type Props = BaseButton.Props & {
	Options: Fusion.UsedAs<{ string }>?,
	Selection: Fusion.UsedAs<number>?,
	Disabled: Fusion.UsedAs<boolean>?,
	Color: Fusion.UsedAs<Color3>?,
	ContentSize: Fusion.UsedAs<number>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local Options = Util.Fallback(Props.Options, {})
	local Selection = Scope:EnsureValue(Util.Fallback(Props.Selection, 1))
	local Color = Util.Fallback(Props.Color, Theme.Colors.BaseContent.Main)
	local Disabled = Util.Fallback(Props.Disabled, false)
	local ContentSize = Util.Fallback(Props.ContentSize, Theme.TextSize["1"])

	local Holding = Scope:Value(false)
	local Hovering = Scope:Value(false)
	local ShownIncrement = Scope:Value(0)

	local SelectionContent = Scope:Computed(function(Use)
		local OptionsValue = Use(Options)
		local SelectionValue = Use(Selection)

		return OptionsValue[SelectionValue]
	end)
	local EffectiveContentColor = Scope:Computed(function(Use)
		if Use(Disabled) then
			return Use(Theme.Colors.BaseContent.Main)
		else
			if Use(Holding) then
				return Util.Emphasize(Use(Color), Use(Theme.Emphasis.Regular))
			elseif Use(Hovering) then
				return Util.Emphasize(Use(Color), Use(Theme.Emphasis.Light))
			else
				return Use(Color)
			end
		end
	end)

	Scope:Observer(Options):onChange(function()
		Selection:set(1)
	end)

	local function RotateSelection(Increment: number)
		local OptionsValue = Fusion.peek(Options)
		local SelectionValue = Fusion.peek(Selection)
		local NewSelection = SelectionValue + Increment

		if NewSelection > #OptionsValue then
			NewSelection = 1
		elseif NewSelection < 1 then
			NewSelection = #OptionsValue
		end

		Selection:set(NewSelection)
	end

	local OldSelectionValue = Fusion.peek(Props.Selection) or 1
	Scope:Observer(Selection):onChange(function()
		local SelectionValue = Fusion.peek(Selection)

		local SelectionIncreased = SelectionValue > OldSelectionValue
		ShownIncrement:set((SelectionIncreased and 1) or -1)
		task.wait(0.1)
		ShownIncrement:set(0)
	end)

	local NoPadding = Scope:UDim(0, Theme.Spacing["0"])

	return Scope:BaseButton(Util.CombineProps(Props, {
		Name = script.Name,
		List = {
			HorizontalFlex = Enum.UIFlexAlignment.SpaceBetween,
			FillDirection = Enum.FillDirection.Horizontal,
			VerticalAlignment = Enum.VerticalAlignment.Center,
		},
		Padding = {
			All = Scope:UDim(0, Theme.Padding["0.5"]),
		},
		Stroke = {
			Color = Color,
			Transparency = Scope:Spring(
				Scope:Computed(function(Use)
					if Use(Disabled) then
						return 0.9
					else
						return 0.8
					end
				end),
				Theme.SpringSpeed["1"],
				Theme.SpringDampening["1"]
			),
		},
		Corner = {
			Radius = Scope:UDim(0, Theme.CornerRadius["1"]),
		},

		Disabled = Disabled,

		Holding = Holding,
		Hovering = Hovering,

		OnActivate = function()
			RotateSelection(1)
		end,

		[Children] = {
			Scope:IconButton {
				Name = "LeftArrow",
				Image = "rbxassetid://136326079088976",
				Style = "Ghost",
				Color = Color,
				Disabled = Disabled,
				ContentSize = Scope:Spring(
					Scope:Computed(function(Use)
						local ShownIncrementValue = Use(ShownIncrement)
						local ContentSizeValue = Use(ContentSize)

						if ShownIncrementValue < 0 then
							return ContentSizeValue * (1 - Use(Theme.Emphasis.Regular))
						else
							return Use(ContentSizeValue)
						end
					end),
					Theme.SpringSpeed["1"],
					Theme.SpringDampening["1"]
				),
				Padding = {
					Top = NoPadding,
					Left = NoPadding,
					Right = NoPadding,
					Bottom = NoPadding,
				},

				OnActivate = function()
					RotateSelection(-1)
				end,
			},
			Scope:Text {
				Text = Scope:Computed(function(Use)
					local SelectionContentValue = Use(SelectionContent)
					if SelectionContentValue ~= nil then
						return SelectionContentValue
					else
						return ""
					end
				end),
				TextColor3 = EffectiveContentColor,
				TextTransparency = Scope:Computed(function(Use)
					if Use(Disabled) then
						return 0.75
					else
						return 0
					end
				end),
				SizeLimit = {
					Min = Scope:Vector2(0, ContentSize),
				},
			},
			Scope:IconButton {
				Name = "RightArrow",
				Image = "rbxassetid://85293585205481",
				Style = "Ghost",
				Color = Color,
				Disabled = Disabled,
				ContentSize = Scope:Spring(
					Scope:Computed(function(Use)
						local ShownIncrementValue = Use(ShownIncrement)
						local ContentSizeValue = Use(ContentSize)

						if ShownIncrementValue > 0 then
							return Use(ContentSizeValue * (1 - Use(Theme.Emphasis.Regular)))
						else
							return Use(ContentSizeValue)
						end
					end),
					Theme.SpringSpeed["1"],
					Theme.SpringDampening["1"]
				),
				Padding = {
					Top = NoPadding,
					Left = NoPadding,
					Right = NoPadding,
					Bottom = NoPadding,
				},

				OnActivate = function()
					RotateSelection(1)
				end,
			},
		},
	}))
end


local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local Components = {
	TextSwap = require(OnyxUI.Components.TextSwap),
	Frame = require(OnyxUI.Components.Frame),
}

return {
	story = function(Props)
		local Scope = Fusion.scoped(Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			Size = Scope:UDim2Offset(Theme.Sizing["16"], 0),
			AutomaticSize = Enum.AutomaticSize.Y,
			Padding = {
				All = Scope:UDim(0, Theme.StrokeThickness["1"]),
			},
			List = {
				Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
				HorizontalFlex = Enum.UIFlexAlignment.Fill,
			},

			[Children] = {
				Scope:TextSwap {
					Options = { "Hamburger", "Pizza", "Ice Cream" },
				},
				Scope:TextSwap {
					Options = { "Disabled" },
					Disabled = true,
				},
				Scope:TextSwap {
					Options = { "Cats", "Dogs", "Zebras" },
					Color = Theme.Colors.Primary.Main,
				},
			},
		}
	end,
	fusion = Fusion,
}


--[=[
		@class TitleBar

		For displaying a title, and an optional close button.
]=]

--[=[
	@interface TitleBarProps
	@within TitleBar

	. ... FrameProps -- All Frame properties are accepted.
	.Content {string}? -- The content of the title bar.
	.ContentSize number? -- The size of the content text.
	.ContentColor Color3? -- The color of the content text.
	.ContentFontFace Font? -- The font face of the content text.
	.CloseButtonIcon string? -- The icon for the close button.
	.CloseButtonDisabled boolean? -- Whether the close button is disabled.
	.OnClose (() -> ())? -- The callback function when the close button is clicked.
]=]

local OnyxUI = script.Parent.Parent

local Fusion = require(OnyxUI.Parent.Fusion)
local Util = require(OnyxUI.Util)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local Frame = require(script.Parent.Frame)
local IconButton = require(script.Parent.IconButton)
local IconText = require(script.Parent.IconText)
local Components = {
	Frame = Frame,
	IconButton = IconButton,
	IconText = IconText,
}

export type Props = Frame.Props & {
	Content: Fusion.UsedAs<{ string }>?,
	ContentSize: Fusion.UsedAs<number>?,
	ContentColor: Fusion.UsedAs<Color3>?,
	ContentFontFace: Fusion.UsedAs<Font>?,
	CloseButtonIcon: Fusion.UsedAs<string>?,
	CloseButtonDisabled: Fusion.UsedAs<boolean>?,
	OnClose: Fusion.UsedAs<() -> ()>?,
}

return function(Scope: Fusion.Scope<any>, Props: Props)
	local Scope = Fusion.innerScope(Scope, Fusion, Util, Components)
	local Theme = Themer.Theme:now()

	local Content = Util.Fallback(Props.Content, {})
	local ContentSize = Util.Fallback(Props.ContentSize, Theme.TextSize["1"])
	local ContentColor = Util.Fallback(Props.ContentColor, Theme.Colors.BaseContent.Main)
	local ContentFontFace =
		Util.Fallback(Props.ContentFontFace, Scope:Font(Theme.Font.Heading, Theme.FontWeight.Heading))
	local CloseButtonDisabled = Util.Fallback(Props.CloseButtonDisabled, false)
	local CloseButtonIcon = Util.Fallback(Props.CloseButtonIcon, "rbxassetid://80218226919142")
	local OnClose = Util.Fallback(Props.OnClose, function() end)

	return Scope:Frame(Util.CombineProps(Props, {
		Name = script.Name,
		Size = UDim2.fromScale(1, 0),
		AutomaticSize = Enum.AutomaticSize.Y,

		[Children] = {
			Scope:IconText {
				Name = "Title",
				AnchorPoint = Vector2.new(0.5, 0),
				Position = UDim2.fromScale(0.5, 0),
				Content = Content,
				ContentColor = ContentColor,
				ContentSize = ContentSize,
				ContentFontFace = ContentFontFace,
				ContentWrapped = false,
				List = {
					Padding = Scope:UDim(0, Theme.Spacing["0.5"]),
				},
			},
			Scope:Computed(function(Use)
				if Use(CloseButtonDisabled) == false then
					return Themer.Theme:is(Theme):during(function()
						return Scope:IconButton {
							Name = "CloseButton",
							Image = CloseButtonIcon,
							ContentSize = ContentSize,
							ContentColor = ContentColor,
							Color = ContentColor,
							Style = "Ghost",
							AnchorPoint = Vector2.new(1, 0.5),
							Position = UDim2.fromScale(1, 0.5),

							OnActivate = function()
								if Use(OnClose) then
									Use(OnClose)()
								end
							end,
						}
					end)
				else
					return
				end
			end),
		},
	}))
end


local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local Fusion = require(OnyxUI.Parent.Fusion)
local Themer = require(OnyxUI.Themer)

local Children = Fusion.Children

local TitleBar = require(OnyxUI.Components.TitleBar)
local Frame = require(OnyxUI.Components.Frame)
local Components = {
	Frame = Frame,
	TitleBar = TitleBar,
}

return {
	story = function(Props)
		local Scope = Fusion.innerScope(Props.scope, Fusion, Util, Components)
		local Theme = Themer.Theme:now()

		Scope:Frame {
			Parent = Props.target,
			Size = Scope:UDim2Offset(Theme.Sizing["16"], 0),
			AutomaticSize = Enum.AutomaticSize.Y,
			Padding = {
				All = Scope:UDim(0, Theme.StrokeThickness["1"]),
			},
			List = {
				HorizontalFlex = Enum.UIFlexAlignment.Fill,
			},

			[Children] = {
				Scope:TitleBar {
					Content = { "Title" },
				},
				Scope:TitleBar {
					Content = { "rbxassetid://75029721407761", "Shop" },
				},
			},
		}
	end,
	fusion = Fusion,
}


local STORY_EXTENSION = ".story"

local Components = {
	Base = require(script.Base),
	Text = require(script.Text),
	Frame = require(script.Frame),
	Badge = require(script.Badge),
	Avatar = require(script.Avatar),
	AutoScaler = require(script.AutoScaler),
	BaseButton = require(script.BaseButton),
	Button = require(script.Button),
	Card = require(script.Card),
	Checkbox = require(script.Checkbox),
	Divider = require(script.Divider),
	Group = require(script.Group),
	Heading = require(script.Heading),
	Icon = require(script.Icon),
	IconButton = require(script.IconButton),
	IconText = require(script.IconText),
	Image = require(script.Image),
	ProgressBar = require(script.ProgressBar),
	Scroller = require(script.Scroller),
	Switch = require(script.Switch),
	TextInput = require(script.TextInput),
	TextArea = require(script.TextArea),
	TitleBar = require(script.TitleBar),
	TextSwap = require(script.TextSwap),
	Slider = require(script.Slider),
	Tabs = require(script.Tabs),
	IconSwap = require(script.IconSwap),
}

task.spawn(function()
	for _, Child in ipairs(script:GetChildren()) do
		local LastCharacters = string.sub(Child.Name, -string.len(STORY_EXTENSION))
		if LastCharacters ~= STORY_EXTENSION then
			local Success, Result = pcall(function()
				return require(Child)
			end)
			if Success then
				if Components[Child.Name] ~= Result then
					assert(false, `Component {Child} is not listed.`)
				end
			else
				assert(false, `{Child} errored during require.`)
			end
		end
	end
end)

return Components


return {
	name = "OnyxUI",
	storyRoots = {
		script.Parent.Components,
	},
}


local OnyxUI = script.Parent.Parent
local Util = require(OnyxUI.Util)
local NewTheme = require(script.Parent.NewTheme)
local Fusion = require(OnyxUI.Parent.Fusion)

local Scope = Fusion.scoped(Fusion)

local OnyxNight = NewTheme(Scope, {
	Colors = {
		Primary = {
			Main = Util.Colors.Violet["500"],
		},
		Secondary = {
			Main = Util.Colors.White,
		},
		Accent = {
			Main = Util.Colors.White,
		},

		Neutral = {
			Main = Util.Colors.Stone["800"],
		},
		NeutralContent = {
			Main = Util.Colors.Stone["300"],
		},

		Base = {
			Main = Util.Colors.Stone["900"],
		},
		BaseContent = {
			Main = Util.Colors.White,
		},

		Success = {
			Main = Util.Colors.Green["500"],
		},
		Error = {
			Main = Util.Colors.Red["500"],
		},
		Warning = {
			Main = Util.Colors.Amber["500"],
		},
		Info = {
			Main = Util.Colors.Cyan["400"],
		},
	},
	Font = {
		Body = "rbxasset://fonts/families/Montserrat.json",
		Heading = "rbxasset://fonts/families/Montserrat.json",
		Monospace = "rbxasset://fonts/families/Montserrat.json",
	},
	FontWeight = {
		Body = Enum.FontWeight.SemiBold,
		Bold = Enum.FontWeight.Bold,
		Heading = Enum.FontWeight.Bold,
	},
	TextSize = {
		Base = 19,
	},
	CornerRadius = {
		Base = 8,
	},
	StrokeThickness = {
		Base = 1,
	},
	StrokeEmphasis = {
		Regular = 0.2,
	},
	Spacing = {
		Base = 22,
	},
	Sizing = {
		Base = 22,
	},
	Padding = {
		Base = 24,
	},
	SpringSpeed = {
		Base = 50,
	},
	SpringDampening = {
		Base = 1,
	},
	Sound = {},
	Emphasis = {},
})

return OnyxNight


--[=[
		@function CombineProps
		@within Util

		@param Source Fusion.PropertyTable
		@param Target Fusion.PropertyTable
		@param ExcludedKeys { any }?
		@return Fusion.PropertyTable

		Combines two sets of props for easy passthrough, adding `Source`'s props to `Target`. If a prop is already in `Target`, it will be overridden.
]=]

local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)

local function CombineProps(
	Source: Fusion.PropertyTable,
	Target: Fusion.PropertyTable,
	ExcludedKeys: { any }?
): Fusion.PropertyTable
	for Key, Value in pairs(Source) do
		if (typeof(Value) == "table") and (Value.kind == nil) and (typeof(Target[Key]) == "table") then
			Target[Key] = CombineProps(Value, Target[Key])
		else
			if (ExcludedKeys ~= nil) and (table.find(ExcludedKeys, Key) ~= nil) then
				continue
			end

			Target[Key] = Value
		end
	end

	return Target
end

return CombineProps


--[=[
	@function EnsureValue
	@within Util

	@param Scope Fusion.Scope<any>
	@param Value Fusion.UsedAs<any>
	@return Fusion.Value<Fusion.Scope<any>, any>

	Converts the `Value` argument to a `Fusion.Value` object, if it is not one already.
]=]

local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)

return function(Scope: Fusion.Scope<any>, Value: Fusion.UsedAs<any>): Fusion.Value<Fusion.Scope<any>, any>
	if (typeof(Value) == "table") and (Value.kind == "Value") then
		return Value
	else
		return Fusion.Value(Scope, Fusion.peek(Value))
	end
end


--[=[
	@function Fallback
	@within Util

	@param Value Fusion.UsedAs<any>
	@param Fallback any
	@return any

	Defaults the given `Value` to `Fallback` if nil.
]=]

local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)

export type Any = Fusion.UsedAs<any>
export type AnyDictionary = Any | { [any]: Any | AnyDictionary }

local function IsReactive(Value: Any)
	return (typeof(Value) == "table") and (Value.scope ~= nil)
end

local function FallbackUtil(Preferred: Any, Fallback: Any)
	if Preferred == nil then
		return Fallback
	end

	return Preferred
end

local function FallbackDictionary(Preferred: AnyDictionary, Fallback: AnyDictionary)
	if (not IsReactive(Preferred)) and (not IsReactive(Fallback)) then
		if (typeof(Preferred) == "table") and (typeof(Fallback) == "table") then
			for Key, Value in pairs(Fallback) do
				Preferred[Key] = FallbackUtil(Preferred[Key], Value)
			end

			return Preferred
		end
	end

	return FallbackUtil(Preferred, Fallback)
end

return FallbackDictionary


local OnyxUI = script.Parent.Parent
local Fusion = require(OnyxUI.Parent.Fusion)

local Unit = {}

--[=[
	@function Vector2
	@within Util

	@param Scope Fusion.Scope(typeof(Fusion))
	@param X Fusion.UsedAs<number>
	@param Y Fusion.UsedAs<number>
	@return Fusion.Computed<Vector2>
]=]
function Unit.Vector2(
	Scope: Fusion.Scope<typeof(Fusion)>,
	X: Fusion.UsedAs<number>,
	Y: Fusion.UsedAs<number>
): Fusion.Computed<Vector2>
	return Scope:Computed(function(Use)
		return Vector2.new(Use(X), Use(Y))
	end)
end

--[=[
	@function UDim
	@within Util

	@param Scope Fusion.Scope<typeof(Fusion)>
	@param Scale Fusion.UsedAs<number>
	@param Offset Fusion.UsedAs<number>
	@return Fusion.Computed<UDim>
]=]
function Unit.UDim(
	Scope: Fusion.Scope<typeof(Fusion)>,
	Scale: Fusion.UsedAs<number>,
	Offset: Fusion.UsedAs<number>
): Fusion.Computed<UDim>
	return Scope:Computed(function(Use)
		return UDim.new(Use(Scale), Use(Offset))
	end)
end

--[=[
	@function UDim2
	@within Util

	@param Scope Fusion.Scope<typeof(Fusion)>
	@param XScale Fusion.UsedAs<number>
	@param XOffset Fusion.UsedAs<number>
	@param YScale Fusion.UsedAs<number>
	@param YOffset Fusion.UsedAs<number>
	@return Fusion.Computed<UDim2>
]=]
function Unit.UDim2(
	Scope: Fusion.Scope<typeof(Fusion)>,
	XScale: Fusion.UsedAs<number>,
	XOffset: Fusion.UsedAs<number>,
	YScale: Fusion.UsedAs<number>,
	YOffset: Fusion.UsedAs<number>
): Fusion.Computed<UDim2>
	return Scope:Computed(function(Use)
		return UDim2.new(Use(XScale), Use(XOffset), Use(YScale), Use(YOffset))
	end)
end

--[=[
	@function UDim2Offset
	@within Util

	@param Scope Fusion.Scope<typeof(Fusion)>
	@param X Fusion.UsedAs<number>
	@param Y Fusion.UsedAs<number>
	@return Fusion.Computed<UDim2>
]=]
function Unit.UDim2Offset(
	Scope: Fusion.Scope<typeof(Fusion)>,
	X: Fusion.UsedAs<number>,
	Y: Fusion.UsedAs<number>
): Fusion.Computed<UDim2>
	return Unit.UDim2(Scope, 0, X, 0, Y)
end

--[=[
	@function UDim2Scale
	@within Util

	@param Scope Fusion.Scope<typeof(Fusion)>
	@param X Fusion.UsedAs<number>
	@param Y Fusion.UsedAs<number>
	@return Fusion.Computed<UDim2>
]=]
function Unit.UDim2Scale(
	Scope: Fusion.Scope<typeof(Fusion)>,
	X: Fusion.UsedAs<number>,
	Y: Fusion.UsedAs<number>
): Fusion.Computed<UDim2>
	return Unit.UDim2(Scope, X, 0, Y, 0)
end

--[=[
	@function Font
	@within Util

	@param Scope Fusion.Scope<typeof(Fusion)>
	@param X Fusion.UsedAs<number>
	@param Y Fusion.UsedAs<number>
	@return Fusion.Computed
]=]
function Unit.Font(
	Scope: Fusion.Scope<typeof(Fusion)>,
	Family: Fusion.UsedAs<string>,
	Weight: Fusion.UsedAs<Enum.FontWeight>?,
	Style: Fusion.UsedAs<Enum.FontStyle>?
): Fusion.Computed
	return Scope:Computed(function(Use)
		return Font.new(Use(Family), Use(Weight), Use(Style) or Enum.FontStyle.Normal)
	end)
end

return Unit


local Workspace = game:GetService("Workspace")

local ViewportSize = {
	Size = Vector2.new(),
	SizeListener = nil,
	Observers = {},
}

function ViewportSize:Observe(Callback: (Vector2) -> ())
	table.insert(ViewportSize.Observers, Callback)
	Callback(ViewportSize.Size)
end

function ViewportSize:_SetSize(Size: Vector2)
	ViewportSize.Size = Size

	for _, Callback in ViewportSize.Observers do
		Callback(Size)
	end
end

function ViewportSize:_SetCamera(Camera: Camera)
	if ViewportSize.SizeListener ~= nil then
		ViewportSize.SizeListener:Disconnect()
	end

	ViewportSize:_SetSize(Camera.ViewportSize)

	ViewportSize.SizeListener = Camera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
		ViewportSize:_SetSize(Camera.ViewportSize)
	end)
end

Workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
	ViewportSize:_SetCamera(Workspace.CurrentCamera)
end)

ViewportSize:_SetCamera(Workspace.CurrentCamera)

return ViewportSize


--[=[
		@class Util

		Important utilities for UI development.
]=]

local Colors = require(script.Colors)
local EnsureValue = require(script.EnsureValue)
local CombineProps = require(script.CombineProps)
local Fallback = require(script.Fallback)
local Emphasize = require(script.Emphasize)
local Unit = require(script.Unit)

return {
	Colors = Colors,
	EnsureValue = EnsureValue,
	CombineProps = CombineProps,
	Fallback = Fallback,
	Emphasize = Emphasize,

	Vector2 = Unit.Vector2,
	UDim = Unit.UDim,
	UDim2 = Unit.UDim2,
	UDim2Offset = Unit.UDim2Offset,
	UDim2Scale = Unit.UDim2Scale,
	Font = Unit.Font,
}


return {
	ROBLOX_API_KEY = "API_KEY_HERE",
	DISCORD_CLIENT_ID = "YOUR_DISCORD_APP_CLIENT_ID",
}


--[[
	Build Entry Point

	Builds the plugin to a production .rbxm.
]]

local process = require("@lune/process")

local cli = require("./lib/cli/parser")
local command = require("./commands/build")

local function main()
	local parser = cli.parser()
	parser:setDescription(command.description)
	parser:setCommandName(`lune run {command.name}`)

	for _, arg: any in command.arguments do
		parser:add(arg.name, arg.kind, arg.options)
	end

	parser:parse(process.args)

	command.run({
		output = parser:get("output") :: string?,
	})
end

main()


--[[
	CI Entry Point

	Runs static analysis checks for continuous integration.
]]

local process = require("@lune/process")

local cli = require("./lib/cli/parser")
local command = require("./commands/ci")

local function main()
	local parser = cli.parser()
	parser:setDescription(command.description)
	parser:setCommandName(`lune run {command.name}`)

	for _, arg: any in command.arguments do
		parser:add(arg.name, arg.kind, arg.options)
	end

	parser:parse(process.args)

	command.run({
		fix = parser:has("fix"),
	})
end

main()


--[[
	Codegen Entry Point

	Runs all code generation tasks.
]]

local process = require("@lune/process")

local cli = require("./lib/cli/parser")
local command = require("./commands/codegen")

local function main()
	local parser = cli.parser()
	parser:setDescription(command.description)
	parser:setCommandName(`lune run {command.name}`)

	for _, arg: any in command.arguments do
		parser:add(arg.name, arg.kind, arg.options)
	end

	parser:parse(process.args)

	command.run({
		quiet = parser:has("quiet"),
		generateSourcemap = true,
		generateProtos = parser:has("protos"),
	})
end

main()


--[[
	Build Command

	Builds the plugin to a production .rbxm file with production flags set.
]]

local codegen = require("./codegen")
local color = require("../utils/color")
local errors = require("../lib/cli/errors")
local rojo = require("../lib/project/rojo")

local build = {}

-- Metadata for help command
build.name = "build"
build.description = "Builds the plugin to a production .rbxm"
build.usage = "lune run build [--output <path>]"

-- Argument definitions for CLI parsing
build.arguments = {
	{
		name = "output",
		kind = "option",
		options = {
			help = "Output file path (default: StudioActivity.rbxm)",
			aliases = { "o" },
		},
	},
}

export type Options = {
	output: string?,
}

--[[
	Builds the plugin for production.

	@param options Configuration options
]]
function build.run(options_: Options?)
	local options: Options = options_ or {}
	local output = options.output or "StudioActivity.rbxm"

	print(color.bold(color.white("Building for production...\n")))

	local codegenSuccess = codegen.run({
		quiet = false,
		production = true,
	})
	if not codegenSuccess then
		return
	end

	print()

	local buildResult = rojo.build({
		projectFile = "plugin/default.project.jsonc",
		output = output,
		stdio = "inherit",
	})

	if not buildResult.ok then
		errors.processError(
			`Rojo exited with code {buildResult.code} while building`,
			buildResult.code,
			buildResult.stderr
		)
		return
	end

	print()
	print(color.bold(color.green(`Built {output}`)))
end

return build


--[[
	Codegen Command

	Runs all code generation tasks: compiles networking, generates project links,
	and generates the sourcemap.
]]

local fs = require("@lune/fs")
local process = require("@lune/process")

local color = require("../utils/color")
local injectBuildVars = require("../utils/inject-build-vars")
local rojo = require("../lib/project/rojo")

local codegen = {}

-- Metadata for help command
codegen.name = "codegen"
codegen.description = "Runs all code generation (sourcemap only for now)"
codegen.usage = "lune run codegen"

-- Argument definitions for CLI parsing
codegen.arguments = {
	{
		name = "protos",
		kind = "flag",
		options = {
			help = "Generate protobuf files",
		},
	},
}

export type Options = {
	quiet: boolean?,
	production: boolean?,
	generateSourcemap: boolean?,
	generateProtos: boolean?,
}

--[[
	Runs all code generation tasks.

	@param options Configuration options
	@return True if successful, false otherwise
]]
function codegen.run(options_: Options?): boolean
	local options: Options = options_ or {}
	local quiet = if options.quiet ~= nil then options.quiet else false
	local production = if options.production ~= nil then options.production else false

	injectBuildVars({
		production = production,
	})

	if options.generateSourcemap then
		if not fs.isDir("plugin/generated") then
			fs.writeDir("plugin/generated")
		end

		-- Generate sourcemap
		local sourcemapSuccess, sourcemapError = rojo.sourcemap({
			projectFile = "plugin/default.project.jsonc",
			output = "plugin/generated/sourcemap.json",
			absolute = true,
		})
		if not sourcemapSuccess then
			if not quiet then
				print(color.bold(color.red("Failed to generate sourcemap")))
				print(sourcemapError)
			end
			return false
		end

		if not quiet then
			print(color.bold(color.dim("Sourcemap generated successfully!")))
		end
	end

	if options.generateProtos then
		local result = process.exec("buf", {
			"generate",
		}, {
			cwd = "protos",
		})

		if not result.ok then
			if not quiet then
				print(color.bold(color.red("Failed to generate protos")))
				print(result.stderr)
			end
			return false
		end

		if not quiet then
			print(color.bold(color.dim("Protos generated successfully!")))
		end
	end

	if not quiet then
		print(color.bold(color.green("Code generation complete!")))
	end

	return true
end

return codegen


--[[
	Dev Command

	Starts a development session. Runs codegen, builds the project,
	starts Rojo serve, and optionally opens in Studio.
]]

local process = require("@lune/process")
local task = require("@lune/task")

local codegen = require("./codegen")
local color = require("../utils/color")
local errors = require("../lib/cli/errors")
local rojo = require("../lib/project/rojo")

local dev = {}

-- Metadata for help command
dev.name = "dev"
dev.description = "Starts a development session with Rojo serve"
dev.usage = "lune run dev"

-- Argument definitions for CLI parsing
dev.arguments = {}

export type Options = {}

--[[
	Starts a development session.

	@param options Configuration options
]]
function dev.run(_options: Options)
	print(color.bold(color.white("Setting up development environment...\n")))

	-- Run code generation (but not install - assumes setup was already run)
	local codegenSuccess = codegen.run({ quiet = false })
	if not codegenSuccess then
		return
	end

	print()

	-- Build plugin with Rojo
	task.spawn(function()
		local buildResult = rojo.build({
			projectFile = "plugin/default.project.jsonc",
			plugin = "StudioActivity.rbxm",
			watch = true,
			stdio = "inherit",
		})

		if not buildResult.ok then
			errors.processError(
				`Rojo exited with code {buildResult.code} while building`,
				buildResult.code,
				buildResult.stderr
			)
			return
		end
	end)

	-- Server backend
	task.spawn(function()
		local result = process.exec("npx", {
			"wrangler",
			"dev",
			"--cwd",
			"backend",
		}, {
			stdio = "inherit",
		})

		if not result.ok then
			errors.processError(`Failed to start backend server`, result.code, result.stderr)
			return
		end
	end)
end

return dev


--[[
	Patch Command

	Creates or updates a git-format patch for a Wally package.
	Uses a two-phase workflow:
	  1. First run: Snapshots the clean package state (git init + commit)
	  2. Second run: Generates the patch from your changes and cleans up
]]

local fs = require("@lune/fs")
local process = require("@lune/process")

local color = require("../utils/color")

local patch = {}

-- Metadata for help command
patch.name = "patch"
patch.description = "Creates or updates a patch for a Wally package"
patch.usage = "lune run patch <package-path>"

-- Argument definitions for CLI parsing
patch.arguments = {
	{
		name = "package-path",
		kind = "positional",
		options = {
			help = "Path to the package directory in Packages/_Index/",
			required = true,
		},
	},
}

local PATCHES_DIR = "plugin/patches"
local PACKAGES_INDEX = "Packages/_Index/"

export type Options = {
	packagePath: string,
}

--[[
	Extracts the package directory name from a path.
	e.g. "plugin/Packages/_Index/jsdotlua_jest@3.10.0" -> "jsdotlua_jest@3.10.0"
	     "plugin/Packages/_Index/jsdotlua_jest@3.10.0/" -> "jsdotlua_jest@3.10.0"
]]
local function getPackageDirName(packagePath: string): string
	-- Remove trailing slash if present
	local cleaned = packagePath:gsub("/$", "")
	-- Get the last path component
	local parts = cleaned:split("/")
	return parts[#parts]
end

--[[
	Validates that the given path is inside Packages/_Index/.
]]
local function isValidPackagePath(packagePath: string): boolean
	return packagePath:find(PACKAGES_INDEX, 1, true) ~= nil
end

--[[
	Phase 1: Prepare a package for patching by snapshotting its clean state.
]]
local function setupForPatching(packagePath: string): boolean
	print(color.dim("Initializing package for patching..."))

	local initResult = process.exec("git", { "init" }, { cwd = packagePath })
	if not initResult.ok then
		print(color.bold(color.red("Failed to initialize git repo")))
		print(color.red(initResult.stderr))
		return false
	end

	local addResult = process.exec("git", { "add", "-A" }, { cwd = packagePath })
	if not addResult.ok then
		print(color.bold(color.red("Failed to stage files")))
		print(color.red(addResult.stderr))
		return false
	end

	local commitResult = process.exec("git", { "commit", "-m", "original" }, { cwd = packagePath })
	if not commitResult.ok then
		print(color.bold(color.red("Failed to commit original state")))
		print(color.red(commitResult.stderr))
		return false
	end

	print()
	print(color.bold(color.green("Package prepared for patching!")))
	print(color.dim("Make your changes to the package, then run this command again to generate the patch."))

	return true
end

--[[
	Phase 2: Generate a patch from changes and clean up.
]]
local function generatePatch(packagePath: string, packageDirName: string): boolean
	print(color.dim("Generating patch..."))

	-- Stage all changes (including new/deleted files)
	local addResult = process.exec("git", { "add", "-A" }, { cwd = packagePath })
	if not addResult.ok then
		print(color.bold(color.red("Failed to stage changes")))
		print(color.red(addResult.stderr))
		return false
	end

	-- Generate the diff
	local diffResult = process.exec("git", { "diff", "--cached" }, { cwd = packagePath })
	if not diffResult.ok then
		print(color.bold(color.red("Failed to generate diff")))
		print(color.red(diffResult.stderr))
		return false
	end

	local diff = diffResult.stdout

	-- Clean up the git repo before potentially returning early
	fs.removeDir(`{packagePath}/.git`)

	if diff == "" or diff == "\n" then
		print(color.yellow("No changes detected. Nothing to patch."))
		return true
	end

	-- Ensure patches directory exists
	if not fs.isDir(PATCHES_DIR) then
		fs.writeDir(PATCHES_DIR)
	end

	-- Find next available patch filename
	local patchFilePath = `{PATCHES_DIR}/{packageDirName}.patch`
	if fs.isFile(patchFilePath) then
		local n = 2
		while fs.isFile(`{PATCHES_DIR}/{packageDirName}_{n}.patch`) do
			n += 1
		end
		patchFilePath = `{PATCHES_DIR}/{packageDirName}_{n}.patch`
	end

	-- Write the patch file
	fs.writeFile(patchFilePath, diff)

	print()
	print(color.bold(color.green("Patch created successfully!")))
	print(color.dim(`  {patchFilePath}`))
	print()
	print(color.dim("Run 'lune run install' to verify the patch applies cleanly."))

	return true
end

--[[
	Creates or updates a patch for a Wally package.

	@param options Configuration options
	@return True if successful, false otherwise
]]
function patch.run(options: Options): boolean
	local packagePath = options.packagePath

	if not fs.isDir(packagePath) then
		print(color.bold(color.red(`Package directory not found: {packagePath}`)))
		return false
	end

	if not isValidPackagePath(packagePath) then
		print(color.bold(color.red("Path must be inside Packages/_Index/")))
		return false
	end

	local packageDirName = getPackageDirName(packagePath)

	-- Detect which phase we're in
	local hasGit = fs.isDir(`{packagePath}/.git`)

	if hasGit then
		return generatePatch(packagePath, packageDirName)
	else
		return setupForPatching(packagePath)
	end
end

return patch


--[[
	Setup Command

	Sets up the project for development or CI. Runs code generation and installs packages.
]]

local codegen = require("./codegen")
local color = require("../utils/color")
local fs = require("@lune/fs")
local install = require("./install")

local setup = {}

-- Metadata for help command
setup.name = "setup"
setup.description = "Sets up the project (codegen + install packages)"
setup.usage = "lune run setup [--quiet]"

-- Argument definitions for CLI parsing
setup.arguments = {
	{
		name = "quiet",
		kind = "flag",
		options = {
			help = "Suppress output",
			aliases = { "q" },
		},
	},
}

export type Options = {
	quiet: boolean?,
}

--[[
	Sets up the project by running codegen and installing packages.

	@param options Configuration options
	@return True if successful, false otherwise
]]
function setup.run(options_: Options?): boolean
	local options: Options = options_ or {}
	local quiet = if options.quiet ~= nil then options.quiet else false

	if not quiet then
		print(color.bold(color.white("Setting up project...\n")))
	end

	if not fs.isDir("plugin/generated") then
		fs.writeDir("plugin/generated")
	end

	-- Run code generation
	local codegenSuccess = codegen.run({
		quiet = quiet,
		-- Generating the sourcemap requires packages to be installed, but we
		-- can't install packages before this step because it requires the
		-- sourcemap project to be generated. So we generate most of the code
		-- now, and generate the sourcemap later.
		generateSourcemap = false,
	})
	if not codegenSuccess then
		return false
	end

	if not quiet then
		print()
	end

	-- Install packages
	local installSuccess = install.run({ quiet = quiet })
	if not installSuccess then
		return false
	end

	-- Generate sourcemap
	codegenSuccess = codegen.run({
		quiet = quiet,
		generateSourcemap = true,
	})
	if not codegenSuccess then
		return false
	end

	if not quiet then
		print()
		print(color.bold(color.green("Project setup complete!")))
	end

	return true
end

return setup


--[[
	Dev Entry Point

	Starts a development session with Rojo serve.
]]

local process = require("@lune/process")

local cli = require("./lib/cli/parser")
local command = require("./commands/dev")

local function main()
	local parser = cli.parser()
	parser:setDescription(command.description)
	parser:setCommandName(`lune run {command.name}`)

	for _, arg: any in command.arguments do
		parser:add(arg.name, arg.kind, arg.options)
	end

	parser:parse(process.args)

	command.run({
		projectName = parser:get("project") :: string,
	})
end

main()