-- Written by @boatbomber

local Plugin = script:FindFirstAncestorWhichIsA("Plugin")
local Fusion = require(Plugin:FindFirstChild("Fusion", true))

local StudioComponents = script.Parent
local StudioComponentsUtil = StudioComponents:FindFirstChild("Util")

local getState = require(StudioComponentsUtil.getState)
local themeProvider = require(StudioComponentsUtil.themeProvider)
local constants = require(StudioComponentsUtil.constants)
local unwrap = require(StudioComponentsUtil.unwrap)
local types = require(StudioComponentsUtil.types)
local stripProps = require(StudioComponentsUtil.stripProps)

local Computed = Fusion.Computed
local Hydrate = Fusion.Hydrate
local New = Fusion.New

local COMPONENT_ONLY_PROPERTIES = {
	"Enabled",
	"TextColorStyle",
	"TextColor3",
	"TextSize",
}

type LabelProperties = {
	Enabled: (boolean | types.StateObject<boolean>)?,
	[any]: any,
}

return function(props: LabelProperties): TextLabel
	local isEnabled = getState(props.Enabled, true)
	local textSize = props.TextSize or constants.TextSize * 1.3

	local newLabel = New "TextLabel" {
		Name = "Label",
		Position = UDim2.fromScale(0, 0),
		AnchorPoint = Vector2.new(0, 0),
		Text = "Label",
		Font = themeProvider:GetFont("Bold"),
		TextSize = textSize,
		BackgroundTransparency = 1,
		BorderSizePixel = 0,
		BorderMode = Enum.BorderMode.Inset,

		Size = Computed(function()
			return UDim2.new(1, 0, 0, unwrap(textSize))
		end),

		TextColor3 = props.TextColor3 or themeProvider:GetColor(props.TextColorStyle or Enum.StudioStyleGuideColor.MainText, Computed(function()
			if not unwrap(isEnabled) then
				return Enum.StudioStyleGuideModifier.Disabled
			end
			return Enum.StudioStyleGuideModifier.Default
		end)),
	}

	local hydrateProps = stripProps(props, COMPONENT_ONLY_PROPERTIES)
	return Hydrate(newLabel)(hydrateProps)
end


return {
	CurvedBoxes = true,
	CornerRadius = UDim.new(0, 2),
	TextSize = 14,
}


-- Roact version by @sircfenner
-- Ported to Fusion by @YasuYoshida

--[[

	1. onInputBegan
		setState({ Dragging = true })
		if widget then
			globalConnection = RunService.Heartbeat(() => onInput(widget.relativePosition))
		else
			globalConnection = InputService.InputChanged(() => onInput(input.Position))
	2. onInputEnded
		setState({ Dragging = false })
		globalConnection:Disconnect()
	3. onInputChanged
		if state.Dragging then
			onInput(input.Position)
	guiObject.InputBegan(onInputBegan)
	guiObject.InputEnded(onInputEnded)
	guiObject.InputChanged(onInputChanged)
	(3) is useful in the widget case because
	- heartbeat is a frame late
	- changed will fire on same frame at least while mouse is over the area
	- so we only get frame-late input when mouse is outside the area

--]]

local Plugin = script:FindFirstAncestorWhichIsA("Plugin")
local Fusion = require(Plugin:FindFirstChild("Fusion", true))

local getState = require(script.Parent.getState)
local unwrap = require(script.Parent.unwrap)
local types = require(script.Parent.types)

local New = Fusion.New
local Value = Fusion.Value
local Computed = Fusion.Computed
local Children = Fusion.Children
local OnEvent = Fusion.OnEvent
local Hydrate = Fusion.Hydrate
local Observer = Fusion.Observer
local Cleanup = Fusion.Cleanup

type vector2Value = types.Value<Vector2>
type vector2Input = (Vector2 | vector2Value)?

type DragInputProperites = {
	Instance: GuiObject,
	Enabled: (boolean | types.StateObject<boolean>)?,
	OnChange: ((newValue: Vector2) -> Vector2?)?,
	Value: vector2Input,
	Min: vector2Input,
	Max: vector2Input,
	Step: vector2Input,
}

return function(props: DragInputProperites): (vector2Value, types.Computed<Vector2>, types.Value<boolean>)
	local isEnabled = getState(props.Enabled, true)
	local isDragging = Value(false)

	local connectionProvider = props.Instance
	local globalConnection = nil

	local currentValue = getState(props.Value, Vector2.new(0, 0), "Value")
	local maxValue = getState(props.Max, Vector2.new(1, 1))
	local minValue = getState(props.Min, Vector2.new(0, 0))

	local range = Computed(function()
		return unwrap(maxValue) - unwrap(minValue)
	end)

	local currentAlpha = Computed(function()
		return (unwrap(currentValue) - unwrap(minValue)) / unwrap(range)
	end)

	local function processInput(position)
		local connectionProvider = unwrap(connectionProvider, false)
		if connectionProvider then
			local offset = position - connectionProvider.AbsolutePosition
			local alpha = offset / connectionProvider.AbsoluteSize

			local range = unwrap(range, false)
			local step = unwrap(props.Step or Vector2.new(-1, -1), false)

			local value = range * alpha
			value = Vector2.new(
				if step.X>0 then math.round(value.X / step.X) * step.X else value.X,
				if step.Y>0 then math.round(value.Y / step.Y) * step.Y else value.Y
			)

			value = Vector2.new(
				math.clamp(value.X, 0, range.X) + unwrap(minValue, false).X,
				math.clamp(value.Y, 0, range.Y) + unwrap(minValue, false).Y
			)

			if value ~= unwrap(currentValue) then
				if props.OnChange then
					local newValue = props.OnChange(value)
					currentValue:set(if newValue~=nil then newValue else value)
				else
					currentValue:set(value)
				end
			end
		end
	end

	local function onDragStart(inputObject)
		local connectionProvider = unwrap(connectionProvider)
		local currentlyDragging = unwrap(isDragging)
		if not unwrap(isEnabled) or currentlyDragging or inputObject.UserInputType ~= Enum.UserInputType.MouseButton1 or connectionProvider==nil or globalConnection then
			return
		end

		isDragging:set(true)
		processInput(Vector2.new(inputObject.Position.X, inputObject.Position.Y))

		local widget = connectionProvider:FindFirstAncestorWhichIsA("DockWidgetPluginGui")
		if widget ~= nil then
			globalConnection = game:GetService("RunService").Heartbeat:Connect(function()
				processInput(widget:GetRelativeMousePosition())
			end)
		else
			globalConnection = game:GetService("UserInputService").InputChanged:Connect(function(newInput)
				if newInput.UserInputType == Enum.UserInputType.MouseMovement then
					processInput(Vector2.new(newInput.Position.x, newInput.Position.y))
				end
			end)
		end
	end

	local function cleanupGlobalConnection()
		if globalConnection then
			globalConnection:Disconnect()
			globalConnection = nil
		end
	end

	local tasks = {}
	local function cleanupTasks()
		for _,cleanupTask in pairs(tasks) do
			cleanupTask()
		end
	end

	table.insert(tasks, Observer(props.Instance):onChange(function()
		local connectionProvider = unwrap(connectionProvider, false)
		if connectionProvider == nil then
			cleanupTasks()
		else
			table.insert(tasks, cleanupGlobalConnection)

			Hydrate(connectionProvider)({
				[Cleanup] = cleanupTasks,
				[OnEvent "InputBegan"] = onDragStart,
				[OnEvent "InputEnded"] = function(inputObject)
					if inputObject.UserInputType == Enum.UserInputType.MouseButton1 then
						isDragging:set(false)
						cleanupGlobalConnection()
					end
				end,
				[OnEvent "InputChanged"] = function(inputObject)
					if unwrap(isDragging) then
						processInput(Vector2.new(inputObject.Position.X, inputObject.Position.Y))
					end
				end,
			})
		end
	end))

	return currentValue, currentAlpha, isDragging
end


local Plugin = script:FindFirstAncestorWhichIsA("Plugin")
local Fusion = require(Plugin:FindFirstChild("Fusion", true))

local Computed = Fusion.Computed

local unwrap = require(script.Parent.unwrap)
local types = require(script.Parent.types)

type modifierInput = {
	Enabled: types.CanBeState<boolean>?,
	Hovering: types.CanBeState<boolean>?,
	Selected: types.CanBeState<boolean>?,
	Pressed: types.CanBeState<boolean>?,
	Otherwise: types.CanBeState<Enum.StudioStyleGuideModifier>?,
}

return function(modifierInput: modifierInput): types.Computed<any>
	local isEnabled = modifierInput.Enabled
	local isHovering = modifierInput.Hovering
	local isSelected = modifierInput.Selected
	local isPressed = modifierInput.Pressed

	return Computed(function()
		local isDisabled = not unwrap(isEnabled)
		local isHovering = unwrap(isHovering)
		local isSelected = unwrap(isSelected)
		local isPressed = unwrap(isPressed)
		if isDisabled then
			return Enum.StudioStyleGuideModifier.Disabled
		elseif isSelected then
			return Enum.StudioStyleGuideModifier.Selected
		elseif isPressed then
			return Enum.StudioStyleGuideModifier.Pressed
		elseif isHovering then
			return Enum.StudioStyleGuideModifier.Hover
		end
		return unwrap(modifierInput.Otherwise) or Enum.StudioStyleGuideModifier.Default
	end)
end


local Plugin = script:FindFirstAncestorWhichIsA("Plugin")
local Fusion = require(Plugin:FindFirstChild("Fusion", true))

local Computed = Fusion.Computed

local unwrap = require(script.Parent.unwrap)
local types = require(script.Parent.types)

-- this technically could be changed later on in a way that
-- allows people to change whether this can be toggled
local isMotionEnabled = true

--motionStateTypes: Tween, Spring
return function(goalState: types.StateObject<any>, motionStateType: string, ...:any): types.CanBeState<any>
	local motionTypeFn = Fusion[motionStateType]
	if typeof(motionTypeFn)~="function" then
		warn(("[%s]: No motionStateType with the name '%s' was found in Fusion!"):format(script.Name, tostring(motionStateType)))
		return goalState
	end

	local motionGoalState = motionTypeFn(goalState, ...)
	local isMotionEnabledAState = unwrap(isMotionEnabled)~=isMotionEnabled

	if isMotionEnabledAState then
		return Computed(function()
			if unwrap(isMotionEnabled) then
				return unwrap(motionGoalState)
			end
			return goalState
		end)
	else
		return if isMotionEnabled then motionGoalState else goalState
	end
end


local unwrap = require(script.Parent.unwrap)
local types = require(script.Parent.types)

type GetSelectedStateProperties = {
	Value: types.Value<any>?,
	Options: types.CanBeState<{any}>?,
	OnSelected: ((selectedOption: any)->nil)?,
}

return function(props: GetSelectedStateProperties): ()->any
	return function()
		local currentValue = unwrap(props.Value)
		local availableOptions = unwrap(props.Options) or {}
		if currentValue==nil or not table.find(availableOptions, currentValue) then
			local _,nextItem = next(availableOptions)
			if nextItem~=nil then
				if props.OnSelected then
					props.OnSelected(nextItem)
				end
				return nextItem
			end
		end
		return currentValue
	end
end


local Plugin = script:FindFirstAncestorWhichIsA("Plugin")
local Fusion = require(Plugin:FindFirstChild("Fusion", true))

local unwrap = require(script.Parent.unwrap)
local types = require(script.Parent.types)

type stateOrAny = types.StateObject<any> | any

return function(inputValue: stateOrAny, defaultValue: stateOrAny, mustBeKind: string?)
	local stateKind = mustBeKind or "Value"
	local isInputAState = unwrap(inputValue, false)~=inputValue
	local isDefaultAState = unwrap(defaultValue, false)~=defaultValue

	if isInputAState and (mustBeKind==nil or inputValue.kind==mustBeKind) then
		return inputValue
	elseif inputValue~=nil then
		return Fusion[stateKind](unwrap(inputValue))
	end

	return if isDefaultAState then defaultValue else Fusion[stateKind](defaultValue)
end


return function(props, exclude)
    local export = table.clone(props)
    for _, k in ipairs(exclude) do
        export[k] = nil
    end
    return export
end


local types = require(script.Parent.types)

type styleStyleGuideColor = Enum.StudioStyleGuideColor | types.StateObject<Enum.StudioStyleGuideColor>
type styleGuideModifier = Enum.StudioStyleGuideModifier | types.StateObject<Enum.StudioStyleGuideModifier>
type computedOrValue = types.Computed<Color3> | types.Value<Color3>

local Studio = settings().Studio
local Plugin = script:FindFirstAncestorWhichIsA("Plugin")
local Fusion = require(Plugin:FindFirstChild("Fusion", true))

local unwrap = require(script.Parent.unwrap)

local Computed = Fusion.Computed
local Value = Fusion.Value

local currentTheme = {}
local themeProvider = {
	Theme = Value(Studio.Theme.Name),
	Fonts = {
		Default = Enum.Font.SourceSans,
		SemiBold = Enum.Font.SourceSansSemibold,
		Bold = Enum.Font.SourceSansBold,
		Black = Enum.Font.GothamBlack,
		Mono = Enum.Font.Code,
	},
	IsDark = Value(true),
}

function themeProvider:GetColor(studioStyleGuideColor: styleStyleGuideColor, studioStyleGuideModifier: styleGuideModifier?): computedOrValue
	local hasState = (unwrap(studioStyleGuideModifier, false) ~= studioStyleGuideModifier) or (unwrap(studioStyleGuideColor, false) ~= studioStyleGuideColor)

	local function isCorrectType(value, enumType)
		local unwrapped = unwrap(value, false)
		local isState = unwrapped ~= value and unwrapped~=nil
		assert((value==nil or isState) or (typeof(value)=="EnumItem" and value.EnumType==enumType), "Incorrect type")
	end

	isCorrectType(studioStyleGuideColor, Enum.StudioStyleGuideColor)
	isCorrectType(studioStyleGuideModifier, Enum.StudioStyleGuideModifier)

	local unwrappedColor = unwrap(studioStyleGuideColor, false)
	local unwrappedModifier = unwrap(studioStyleGuideModifier, false)

	if not currentTheme[unwrappedColor] then
		currentTheme[unwrappedColor] = {}
	end

	local themeValue = (function()
		local styleGuideModifier = if unwrappedModifier~=nil then unwrappedModifier else Enum.StudioStyleGuideModifier.Default

		local existingValue = currentTheme[unwrappedColor][styleGuideModifier]
		if existingValue then
			return existingValue
		end

		local newThemeValue = Value(Studio.Theme:GetColor(unwrappedColor, styleGuideModifier))
		currentTheme[unwrappedColor][styleGuideModifier] = newThemeValue

		return newThemeValue
	end)()

	return if not hasState then themeValue else Computed(function()
		local currentColor = unwrap(studioStyleGuideColor)
		local currentModifier = unwrap(studioStyleGuideModifier)
		local currentValueState = self:GetColor(currentColor, currentModifier)
		return currentValueState:get()
	end)
end

function themeProvider:GetFont(fontName: (string | types.StateObject<string>)?): types.Computed<Enum.Font>
	return Computed(function()
		local givenFontName = unwrap(fontName)
		local fontToGet = self.Fonts.Default
		if givenFontName~=nil and self.Fonts[givenFontName] then
			fontToGet = self.Fonts[givenFontName]
		end
		return unwrap(fontToGet)
	end)
end

local function updateTheme()
	for studioStyleGuideColor, styleGuideModifiers: {Enum.StudioStyleGuideModifier} in pairs(currentTheme) do
		for studioStyleGuideModifier, valueState in pairs(styleGuideModifiers) do
			valueState:set(Studio.Theme:GetColor(studioStyleGuideColor, studioStyleGuideModifier))
		end
	end
	themeProvider.Theme:set(Studio.Theme.Name)

	local _,_,v = Studio.Theme:GetColor(Enum.StudioStyleGuideColor.MainBackground):ToHSV()
	themeProvider.IsDark:set(v<=0.6)
end

do
	local themeChangedConnection = Studio.ThemeChanged:Connect(updateTheme)
	updateTheme()

	Plugin.Unloading:Connect(function()
		themeChangedConnection:Disconnect()
		themeChangedConnection = nil
	end)
end

return themeProvider


--!strict

-- This is a duplicate of Fusion's PubTypes module because for whatever reason type checking doesn't like :FindFirstAncestorWhichIsA()
-- While not a pretty solution, it's the easier solution without causing a huge mess within the components
-- If the components are updated to use a newer version of Fusion, then this types module can be updated too

type Set<T> = {[T]: any}

--[[
	General use types
]]

-- A unique symbolic value.
export type Symbol = {
	type: string, -- replace with "Symbol" when Luau supports singleton types
	name: string
}

-- Types that can be expressed as vectors of numbers, and so can be animated.
export type Animatable =
	number |
CFrame |
Color3 |
ColorSequenceKeypoint |
DateTime |
NumberRange |
NumberSequenceKeypoint |
PhysicalProperties |
Ray |
Rect |
Region3 |
Region3int16 |
UDim |
UDim2 |
Vector2 |
Vector2int16 |
Vector3 |
Vector3int16

-- A task which can be accepted for cleanup.
export type Task =
	Instance |
RBXScriptConnection |
() -> () |
{destroy: (any) -> ()} |
{Destroy: (any) -> ()} |
{Task}

-- Script-readable version information.
export type Version = {
	major: number,
	minor: number,
	isRelease: boolean
}
--[[
	Generic reactive graph types
]]

-- A graph object which can have dependents.
export type Dependency = {
	dependentSet: Set<Dependent>
}

-- A graph object which can have dependencies.
export type Dependent = {
	update: (Dependent) -> boolean,
	dependencySet: Set<Dependency>
}

-- An object which stores a piece of reactive state.
export type StateObject<T> = Dependency & {
	type: string, -- replace with "State" when Luau supports singleton types
	kind: string,
	get: (StateObject<T>, asDependency: boolean?) -> T
}

-- Either a constant value of type T, or a state object containing type T.
export type CanBeState<T> = StateObject<T> | T

--[[
	Specific reactive graph types
]]

-- A state object whose value can be set at any time by the user.
export type Value<T> = StateObject<T> & {
	-- kind: "State" (add this when Luau supports singleton types)
	set: (Value<T>, newValue: any, force: boolean?) -> ()
}

-- A state object whose value is derived from other objects using a callback.
export type Computed<T> = StateObject<T> & Dependent & {
	-- kind: "Computed" (add this when Luau supports singleton types)
}

-- A state object whose value is derived from other objects using a callback.
export type ForPairs<KO, VO> = StateObject<{ [KO]: VO }> & Dependent & {
	-- kind: "ForPairs" (add this when Luau supports singleton types)
}
-- A state object whose value is derived from other objects using a callback.
export type ForKeys<KO, V> = StateObject<{ [KO]: V }> & Dependent & {
	-- kind: "ForKeys" (add this when Luau supports singleton types)
}
-- A state object whose value is derived from other objects using a callback.
export type ForValues<K, VO> = StateObject<{ [K]: VO }> & Dependent & {
	-- kind: "ForKeys" (add this when Luau supports singleton types)
}

-- A state object which follows another state object using tweens.
export type Tween<T> = StateObject<T> & Dependent & {
	-- kind: "Tween" (add this when Luau supports singleton types)
}

-- A state object which follows another state object using spring simulation.
export type Spring<T> = StateObject<T> & Dependent & {
	-- kind: "Spring" (add this when Luau supports singleton types)
	-- Uncomment when ENABLE_PARAM_SETTERS is enabled
	-- setPosition: (Spring<T>, newValue: Animatable) -> (),
	-- setVelocity: (Spring<T>, newValue: Animatable) -> (),
	-- addVelocity: (Spring<T>, deltaValue: Animatable) -> ()
}

-- An object which can listen for updates on another state object.
export type Observer = Dependent & {
	-- kind: "Observer" (add this when Luau supports singleton types)
	onChange: (Observer, callback: () -> ()) -> (() -> ())
}

--[[
	Instance related types
]]

-- A semi-weak instance reference.
export type SemiWeakRef = {
	type: string, -- replace with "SemiWeakRef" when Luau supports singleton types
	instance: Instance?
}

-- Denotes children instances in an instance or component's property table.
export type SpecialKey = {
	type: string, -- replace with "SpecialKey" when Luau supports singleton types
	kind: string,
	stage: string, -- replace with "self" | "descendants" | "ancestor" | "observer" when Luau supports singleton types
	apply: (SpecialKey, value: any, applyTo: SemiWeakRef, cleanupTasks: {Task}) -> ()
}

-- A collection of instances that may be parented to another instance.
export type Children = Instance | StateObject<Children> | {[any]: Children}

-- A table that defines an instance's properties, handlers and children.
export type PropertyTable = {[string | SpecialKey]: any}

return nil


return function(x: any, useDependency: boolean?): any
	if typeof(x)=="table" and x.type=="State" then
		return x:get(useDependency)
	end
	return x
end


-- Roact version by @sircfenner
-- Ported to Fusion by @YasuYoshida

local Plugin = script:FindFirstAncestorWhichIsA("Plugin")
local Fusion = require(Plugin:FindFirstChild("Fusion", true))

local StudioComponents = script.Parent
local StudioComponentsUtil = StudioComponents:FindFirstChild("Util")

local VerticalExpandingList = require(StudioComponents.VerticalExpandingList)
local BoxBorder = require(StudioComponents.BoxBorder)
local Label = require(StudioComponents.Label)

local getMotionState = require(StudioComponentsUtil.getMotionState)
local themeProvider = require(StudioComponentsUtil.themeProvider)
local getModifier = require(StudioComponentsUtil.getModifier)
local stripProps = require(StudioComponentsUtil.stripProps)
local constants = require(StudioComponentsUtil.constants)
local getState = require(StudioComponentsUtil.getState)
local unwrap = require(StudioComponentsUtil.unwrap)
local types = require(StudioComponentsUtil.types)

local Children = Fusion.Children
local Computed = Fusion.Computed
local OnEvent = Fusion.OnEvent
local Hydrate = Fusion.Hydrate
local Value = Fusion.Value
local New = Fusion.New

local HEADER_HEIGHT = 25

local COMPONENT_ONLY_PROPERTIES = {
	"Padding",
	"Collapsed",
	"Text",
	"TextColor3",
	"Enabled",
	Children,
}

type VerticalExpandingListProperties = {
	Enabled: (boolean | types.StateObject<boolean>)?,
	Collapsed: (boolean | types.Value<boolean>)?,
	Padding: (UDim | types.StateObject<UDim>)?,
	[any]: any,
}

return function(props: VerticalExpandingListProperties): Frame
	local shouldBeCollapsed = getState(props.Collapsed, false, "Value")
	local isEnabled = getState(props.Enabled, true)
	local isHovering = Value(false)

	local modifier = getModifier({
		Enabled = isEnabled,
		Hovering = isHovering,
		Otherwise = Computed(function()
			return if unwrap(themeProvider.IsDark) then Enum.StudioStyleGuideModifier.Default else Enum.StudioStyleGuideModifier.Pressed
		end),
	})

	local isCollapsed = Computed(function()
		local shouldBeCollapsed = unwrap(shouldBeCollapsed)
		local isEnabled = unwrap(isEnabled)
		return if isEnabled then shouldBeCollapsed else true
	end)

	local labelColor = themeProvider:GetColor(Enum.StudioStyleGuideColor.BrightText, modifier)
	local backgroundColor = themeProvider:GetColor(Enum.StudioStyleGuideColor.MainBackground)
	local themeColorModifier = Computed(function()
		local _, _, v = unwrap(backgroundColor):ToHSV()
		return if v<.5 then -1 else 1
	end)

	return Hydrate(VerticalExpandingList {
		Name = "VerticalCollapsibleSection",
		BackgroundTransparency = 1,
		--TODO: remove this +2 once BorderMode becomes a thing for UIStroke
		Size = UDim2.new(1, 0, 0, HEADER_HEIGHT+2),
		Padding = props.Padding,

		AutomaticSize = Computed(function()
			return isCollapsed:get() and Enum.AutomaticSize.None or Enum.AutomaticSize.Y
		end),

		[Children] = {
			--TODO: remove this UIPadding and Frame once BorderMode becomes a thing for UIStroke
			-- until then, this will need to stay here
			New "UIPadding" {
				Name = "BorderUIPadding",
				PaddingRight = UDim.new(0, 1),
				PaddingLeft = UDim.new(0, 1),
				PaddingTop = UDim.new(0, 1),
				PaddingBottom = UDim.new(0, 1),
			},
			New "Frame" {
				Name = "BorderBottomPadding",
				LayoutOrder = 10^5,
				BackgroundTransparency = 1,
				Size = UDim2.new(1, 0, 0, 0),
			},

			BoxBorder {
				Color = getMotionState(themeProvider:GetColor(Enum.StudioStyleGuideColor.Border), "Spring", 40),

				[Children] = New "Frame" {
					Name = "CollapsibleSectionHeader",
					LayoutOrder = 0,
					Active = true,
					Size = UDim2.new(1, 0, 0, HEADER_HEIGHT),

					BackgroundColor3 = getMotionState(themeProvider:GetColor(Enum.StudioStyleGuideColor.HeaderSection, modifier), "Spring", 40),

					[OnEvent "InputBegan"] = function(inputObject)
						if not unwrap(isEnabled) then
							return
						elseif inputObject.UserInputType == Enum.UserInputType.MouseMovement then
							isHovering:set(true)
						elseif inputObject.UserInputType == Enum.UserInputType.MouseButton1 then
							shouldBeCollapsed:set(not shouldBeCollapsed:get())
						end
					end,

					[OnEvent "InputEnded"] = function(inputObject)
						if not unwrap(isEnabled) then
							return
						elseif inputObject.UserInputType == Enum.UserInputType.MouseMovement then
							isHovering:set(false)
						end
					end,

					[Children] = {
						New "ImageLabel" {
							Name = "Icon",
							AnchorPoint = Vector2.new(0, 0.5),
							Position = UDim2.new(0, 7, 0.5, 0),
							Size = UDim2.fromOffset(10, 10),
							Image = "rbxassetid://5607705156",
							ImageRectSize = Vector2.new(10, 10),
							BackgroundTransparency = 1,

							ImageColor3 = getMotionState(Computed(function()
								local baseColor = Color3.fromRGB(170, 170, 170)
								if unwrap(isEnabled) then
									return baseColor
								end
								local h, s, v = baseColor:ToHSV()
								return Color3.fromHSV(h, s, math.clamp(v - .2, 0, 1))
							end), "Spring", 40),

							ImageRectOffset = Computed(function()
								return Vector2.new(unwrap(isCollapsed) and 0 or 10, 0)
							end),
						},
						Label {
							TextXAlignment = Enum.TextXAlignment.Left,
							Size = UDim2.fromScale(1, 1),

							Font = themeProvider:GetFont("Bold"),
							Text = props.Text or "HeaderText",
							TextSize = constants.TextSize,

							TextColor3 = getMotionState(Computed(function()
								local currentLabelColor = unwrap(props.TextColor3 or labelColor)
								local themeModifier = unwrap(themeColorModifier)
								if unwrap(isEnabled) then
									return currentLabelColor
								end
								local h, s, v = currentLabelColor:ToHSV()
								return Color3.fromHSV(h, s, math.clamp(v + .3 * themeModifier, 0, 1))
							end), "Spring", 40),

							[Children] = New "UIPadding" {
								PaddingLeft = UDim.new(0, 24),
							}
						}
					}
				}
			},
			props[Children],
		}
	})(stripProps(props, COMPONENT_ONLY_PROPERTIES))
end


-- Roact version by @sircfenner
-- Ported to Fusion by @YasuYoshida

local Plugin = script:FindFirstAncestorWhichIsA("Plugin")
local Fusion = require(Plugin:FindFirstChild("Fusion", true))

local StudioComponents = script.Parent
local StudioComponentsUtil = StudioComponents:FindFirstChild("Util")

local Background = require(StudioComponents.Background)
local BoxBorder = require(StudioComponents.BoxBorder)

local getMotionState = require(StudioComponentsUtil.getMotionState)
local stripProps = require(StudioComponentsUtil.stripProps)
local unwrap = require(StudioComponentsUtil.unwrap)
local types = require(StudioComponentsUtil.types)

local Children = Fusion.Children
local Computed = Fusion.Computed
local Hydrate = Fusion.Hydrate
local New = Fusion.New
local Value = Fusion.Value
local Out = Fusion.Out

local COMPONENT_ONLY_PROPERTIES = {
	"Padding",
	"AutomaticSize",
}

type VerticalExpandingListProperties = {
	Padding: (UDim | types.StateObject<UDim>)?,
	[any]: any,
}

return function(props: VerticalExpandingListProperties): Frame
	local hydrateProps = stripProps(props, COMPONENT_ONLY_PROPERTIES)

	local contentSize = Value(Vector2.new(0,0))

	return Hydrate(
		BoxBorder {
			[Children] = Background {
				ClipsDescendants = true,
				Size = getMotionState(Computed(function()
					local mode = unwrap(props.AutomaticSize or Enum.AutomaticSize.Y) -- Custom autosize since engine sizing is unreliable
					if mode == Enum.AutomaticSize.Y then
						local s = unwrap(contentSize)
						if s then
							return UDim2.new(1,0,0,s.Y)
						else
							return UDim2.new(1,0,0,0)
						end
					else
						return props.Size or UDim2.new(1,0,0,0)
					end
				end), "Spring", 40),

				[Children] = New "UIListLayout" {
					SortOrder = Enum.SortOrder.LayoutOrder,
					FillDirection = Enum.FillDirection.Vertical,

					Padding = Computed(function()
						return unwrap(props.Padding) or UDim.new(0, 10)
					end),

					[Out "AbsoluteContentSize"] = contentSize,
				}
			}
		}
	)(hydrateProps)
end


--[[
	MIT License

	Copyright (c) 2024 Ultrasonic54321
	Copyright (c) 2022 Yasu Yoshida

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
]]

local HttpService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local ServerStorage = game:GetService("ServerStorage")

local Plugin = plugin

assert(plugin, "This script must be run as a plugin!")

local Components = script.Parent.Components
local Packages = script.Parent.Packages
local Zap = require(script.zap)

local PluginComponents = Components:FindFirstChild("PluginComponents")
local Widget = require(PluginComponents.Widget)
local Toolbar = require(PluginComponents.Toolbar)
local ToolbarButton = require(PluginComponents.ToolbarButton)

local StudioComponents = Components:FindFirstChild("StudioComponents")
local MainButton = require(StudioComponents.MainButton)
local ScrollFrame = require(StudioComponents.ScrollFrame)
local Label = require(StudioComponents.Label)
local TextInput = require(StudioComponents.TextInput)
local TextInputAccented = require(StudioComponents.TextInputAccented)
local TextInputHighlighted = require(StudioComponents.TextInputHighlighted)
local VerticalCollapsibleSection = require(StudioComponents.VerticalCollapsibleSection)
local Checkbox = require(StudioComponents.Checkbox)

local Highlighter = require(Packages.Highlighter)
local Fusion = require(Packages.Fusion)

local New = Fusion.New
local Computed = Fusion.Computed
local Value = Fusion.Value
local Children = Fusion.Children
local OnChange = Fusion.OnChange
local OnEvent = Fusion.OnEvent
local Observer = Fusion.Observer

local function saveToScript(scriptName: string, scriptParent: Instance, source: string)
	local ExportScript: ModuleScript = (scriptParent:FindFirstChild(scriptName) :: ModuleScript) or Instance.new("ModuleScript", scriptParent)
	ExportScript.Name = scriptName
	ExportScript.Source = source
end

local function saveStringToScript(scriptName: string, scriptParent: Instance, source: string)
	saveToScript(scriptName, scriptParent, `return [[{source}]]`)
end

local function loadFromScript(scriptName: string, scriptParent: Instance): string?
	local ExportScript: ModuleScript = (scriptParent:FindFirstChild(scriptName) :: ModuleScript)
	if not ExportScript then
		return nil
	else
		return ExportScript.Source
	end
end

local function loadStringFromScript(scriptName: string, scriptParent: Instance): string?
	local loadedScript = loadFromScript(scriptName, scriptParent)
	if not loadedScript then
		return nil
	end

	do
		local s, _output = pcall(HttpService.JSONDecode, HttpService, loadedScript)

		-- loadedScript would be just raw JSON so just return it
		-- this is needed because of how older versions of the plugin saved settings
		if s then
			--consumers still expect a string, so don't return parsed script
			return loadedScript
			--return _output
		end
	end

	do
		local s, output = pcall(require, (scriptParent[scriptName] :: ModuleScript):Clone())
		if s and typeof(output) == "string" then
			return output
		end
	end

	return nil
end

do --creates the Zappy plugin
	local pluginToolbar = Toolbar {
		Name = "Zappy"
	}

	local widgetsEnabled = Value(false)
	local enableButton = ToolbarButton {
		Toolbar = pluginToolbar,

		ClickableWhenViewportHidden = true,
		Name = "Zappy",
		ToolTip = "Show or hide the Zappy panel",
		Image = "rbxassetid://17532741556",

		[OnEvent "Click"] = function()
			widgetsEnabled:set(not widgetsEnabled:get())
		end,
	}

	local widgetsEnabledObserver = Observer(widgetsEnabled)

	Plugin.Unloading:Connect(widgetsEnabledObserver:onChange(function()
		enableButton:SetActive(widgetsEnabled:get(false))
	end))

	local function ZappyWidget(children)
		return Widget {
			Id = game:GetService("HttpService"):GenerateGUID(),
			Name = "Zappy",

			InitialDockTo = Enum.InitialDockState.Float,
			InitialEnabled = false,
			ForceInitialEnabled = false,
			FloatingSize = Vector2.new(360, 300),
			MinimumSize = Vector2.new(286, 200),

			Enabled = widgetsEnabled,
			[OnChange "Enabled"] = function(isEnabled)
				widgetsEnabled:set(isEnabled)
			end,

			[Children] = ScrollFrame {
				ZIndex = 1,
				Size = UDim2.fromScale(1, 1),

				CanvasScaleConstraint = Enum.ScrollingDirection.X,

				UILayout = New "UIListLayout" {
					SortOrder = Enum.SortOrder.LayoutOrder,
					Padding = UDim.new(0, 7),
				},

				UIPadding = New "UIPadding" {
					PaddingLeft = UDim.new(0, 5),
					PaddingRight = UDim.new(0, 5),
					PaddingBottom = UDim.new(0, 10),
					PaddingTop = UDim.new(0, 10),
				},

				[Children] = children
			}
		}
	end

	local IDLInput = Value("")

	local ServerOutput = Value("")
	local ClientOutput = Value("")

	local Diagnostics = Value("")
	local DiagnosticsExists = Computed(function()
		return Diagnostics:get() ~= ""
	end)

	local ToolingOutput = Value("")
	local ToolingExists = Computed(function()
		return ToolingOutput:get() ~= ""
	end)

	local Busy = Value(false)

	local Ready = Computed(function()
		return not Busy:get()
	end)

	local NoWarnings = Value(false)

	local ExportOutput = Value(false)
	local ClientExportLocation = Value("ZapClient")
	local ServerExportLocation = Value("ZapServer")
	local ToolingExportLocation = Value("RemoteName.profiler")

	local SaveSettings = Value(false)

	Plugin.Unloading:Connect(widgetsEnabledObserver:onChange(function()
		if widgetsEnabled:get(false) then
			local loaded = loadStringFromScript("__ZappySettings", ServerStorage)
			if loaded then
				local parsedSettings: {
					IDL: string,
					NoWarnings: boolean?,
					ExportOutput: boolean,
					ServerExportLocation: string,
					ClientExportLocation: string,
					ToolingExportLocation: string?
				} = HttpService:JSONDecode(loaded)
				SaveSettings:set(true)

				IDLInput:set(parsedSettings.IDL)
				ExportOutput:set(parsedSettings.ExportOutput)
				ServerExportLocation:set(parsedSettings.ServerExportLocation)
				ClientExportLocation:set(parsedSettings.ClientExportLocation)
				if parsedSettings.ToolingExportLocation ~= nil then
					ToolingExportLocation:set(parsedSettings.ToolingExportLocation)
				end
				if parsedSettings.NoWarnings ~= nil then
					NoWarnings:set(parsedSettings.NoWarnings)
				end
			end
		end
	end))

	ZappyWidget {
		[Children] = {
			Label {
				Text = "Input"
			},
			TextInput {
				PlaceholderText = "Your IDL here",
				ClearTextOnFocus = false,
				MultiLine = true,
				AutomaticSize = Enum.AutomaticSize.Y,
				TextEditable = Ready,
				TextXAlignment = Enum.TextXAlignment.Left,
				Font = Enum.Font.Code,

				Text = IDLInput,
				[Fusion.Out "Text"] = IDLInput
			},
			Checkbox {
				Text = "Disable Warnings",
				Value = NoWarnings
			},
			Checkbox {
				Text = "Auto-export output",
				Value = ExportOutput
			},

			Label {
				Text = "Client export location",
				Visible = ExportOutput,
			},
			TextInput {
				PlaceholderText = "in game.ReplicatedStorage",
				Visible = ExportOutput,
				Text = ClientExportLocation,
				[Fusion.Out "Text"] = ClientExportLocation
			},

			Label {
				Text = "Server export location",
				Visible = ExportOutput,
			},
			TextInput {
				PlaceholderText = "in game.ServerScriptService",
				Visible = ExportOutput,
				Text = ServerExportLocation,
				[Fusion.Out "Text"] = ServerExportLocation
			},

			Label {
				Text = "Tooling export location",
				Visible = ExportOutput,
			},
			TextInput {
				PlaceholderText = "in game.ReplicatedStorage",
				Visible = ExportOutput,
				Text = ToolingExportLocation,
				[Fusion.Out "Text"] = ToolingExportLocation
			},

			Checkbox {
				Text = "Save settings on successful generation",
				Value = SaveSettings
			},

			MainButton {
				Text = Computed(function()
					return if Busy:get() then "Generating..." else "Generate"
				end),
				--Enabled = Ready,-- causes permament discolouration
				Active = Ready,

				Size = UDim2.new(1, 0, 0, 30),
				Activated = function()
					Busy:set(true)

					-- give the UI a chance to update
					task.wait()

					local A,B,C = xpcall(Zap, function(err) return debug.traceback(err, 2) end, IDLInput:get(), NoWarnings:get())
					if C then
						A,B = B,C
					end
					local success, output = A,B

					if (not success) or (typeof(output) ~= "table") then
						warn("Zap error:\n"..output)
						ClientOutput:set("")
						ServerOutput:set("")
						Diagnostics:set(output)
					else
						ClientOutput:set(output.client)
						ServerOutput:set(output.server)
						Diagnostics:set(output.diagnostics)
						ToolingOutput:set(output.tooling)
					end

					if not ((ServerOutput:get()=="") or (ClientOutput:get()=="")) then
						if ExportOutput:get() then
							saveToScript(ServerExportLocation:get(), ServerScriptService, output.server)
							saveToScript(ClientExportLocation:get(), ReplicatedStorage, output.client)
							if ToolingExists:get() then
								saveToScript(ToolingExportLocation:get(), ReplicatedStorage, output.tooling)
							end
						end

						if SaveSettings:get() then
							local zappySettings = {
								IDL = IDLInput:get(),
								NoWarnings = NoWarnings:get(),
								ExportOutput = ExportOutput:get(),
								ServerExportLocation = ServerExportLocation:get(),
								ClientExportLocation = ClientExportLocation:get(),
								ToolingExportLocation = ToolingExportLocation:get()
							}
							local exported = HttpService:JSONEncode(zappySettings)

							saveStringToScript("__ZappySettings", ServerStorage, exported)
						end
					end

					Busy:set(false)
				end,
			},
			VerticalCollapsibleSection {

				Text = "Output",
				[Children] = {
					Label {
						Text = "Diagnostics",
						Visible = DiagnosticsExists
					},
					TextInputAccented {
						Visible = DiagnosticsExists,
						Text = Diagnostics,
						TextColorGuide = Computed(function()
							if (ServerOutput:get()=="") or (ClientOutput:get()=="") then
								return Enum.StudioStyleGuideColor.ErrorText
							else
								return Enum.StudioStyleGuideColor.WarningText
							end
						end),
						TextEditable = false,
						ClearTextOnFocus = false,
						MultiLine = true,
						AutomaticSize = Enum.AutomaticSize.Y,
						TextXAlignment = Enum.TextXAlignment.Left,
						Font = Enum.Font.Code,
					},

					Label {
						Text = "Client"
					},
					TextInputHighlighted {
						Text = ClientOutput,
						TextEditable = false,
						ClearTextOnFocus = false,
						MultiLine = true,
						AutomaticSize = Enum.AutomaticSize.Y,
						TextXAlignment = Enum.TextXAlignment.Left,
						Font = Enum.Font.Code,
					},

					Label {
						Text = "Server"
					},
					TextInputHighlighted {
						Text = ServerOutput,
						TextEditable = false,
						ClearTextOnFocus = false,
						MultiLine = true,
						AutomaticSize = Enum.AutomaticSize.Y,
						TextXAlignment = Enum.TextXAlignment.Left,
						Font = Enum.Font.Code,
					},

					Label {
						Text = "Tooling",
						Visible = ToolingExists
					},
					TextInputHighlighted {
						Text = ToolingOutput,
						Visible = ToolingExists,
						TextEditable = false,
						ClearTextOnFocus = false,
						MultiLine = true,
						AutomaticSize = Enum.AutomaticSize.Y,
						TextXAlignment = Enum.TextXAlignment.Left,
						Font = Enum.Font.Code,
					},
				}
			}
		}
	}
end


-- Alias for ./src/main.luau

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

local callback = require("../source/main")
callback(process.args)


return require("ar_input") :: {
	getKeyDown: (any, any) -> ...any,
	getMouseDown: (any, any) -> ...any,
	map: (any) -> ...any,
}


--[[
An array of 4 `Vector2`s, the first being the min & max ux, the second being the min & max uy, the third being the min and max sx, and the fourth being the min and max sy
]]
export type CoordinateBounds = { Vector2 }

return require("coordinate") :: {
	new: (ux: number, uy: number, sx: number, sy: number, inPlanet: boolean) -> Coordinates,
	withBounds: (
		bounds: CoordinateBounds,
		ux: number,
		uy: number,
		sx: number,
		sy: number,
		inPlanet: boolean
	) -> Coordinates,
	fromString: (coordinate: string, bounds: CoordinateBounds?) -> Coordinates,
	--- NOTE: Does not apply any coordinate bounds
	fromVectors: (universeCoordinate: Vector2, solarCoordinate: Vector2, inPlanet: boolean) -> Coordinates,

	--- The default bounds of the universe
	DEFAULT_COORDINATE_BOUNDS: CoordinateBounds,
	--- Unlimited boundaries
	NO_COORDINATE_BOUNDS: CoordinateBounds,
}


return (require("fs") :: any) :: typeof(setmetatable(({} :: any) :: FileSystemRoot, ({} :: any) :: FileSystem))


-- Handling exceptions

local TYPES = {
	NO_CANVAS_FOUND = "No canvas found, initialize the engine's canvas using Engine:CreateCanvas().",
	NO_RIGIDBODIES_FOUND = "No rigid bodies found on start.",
	PROPERTY_NOT_FOUND = "Invalid Argument #1. Property not found.",
	INVALID_CONSTRAINT_TYPE = "Received Invalid Constraint Type.",
	INVALID_CONSTRAINT_LENGTH = "Received Invalid Constraint Length.",
	INVALID_CONSTRAINT_THICKNESS = "Received Invalid Constraint Thickness.",
	SAME_ID = "Cannot ignore collisions for the same RigidBodies.",
	INVALID_RIGIDBODY = "Received Invalid RigidBody.",
	INVALID_OBJECT = "Received an Invalid Object. Valid objects - RigidBody, Point and Constraint.",
	INVALID_PROPERTY = "Received an Invalid Object Property.",
	MUST_HAVE_PROPERTY = "Missing must-have properties.",
	CANVAS_FRAME_NOT_FOUND = "No canvas frame found, initialize the canvas's frame to render custom Points and Constraints!",
	INVALID_TIME = "Received invalid time to apply force for.",
	ALREADY_STARTED = "Engine is already running.",
	CANNOT_SET_COLLISION_ITERATIONS = "Cannot set collision iterations! You must turn on quadtree usage using Engine:UseQuadtrees(true)."
}

return function (TASK: string, TYPE: string, details: string?)
	if TYPES[TYPE] then
		local exception = string.format("[Nature2D]: %s%s", TYPES[TYPE], if details then " "..details else "")

		if TASK == "warn" then
			warn(exception)
		elseif TASK == "error" then
			error(exception, 2)
		end
	end
end


return function (custom)
	if custom then
		error("[Nature2D]: This method cannot be used with custom RigidBodies", 2)
	end
end


-- Handling type errors

return function (arg: string, param, pos: number, expected: string)
	if typeof(param) ~= expected then
		error(
			string.format(
				"[Nature2D]: Invalid Argument #%s. Expected type %q for %s, got %q",
				tostring(pos),
				expected,
				arg,
				typeof(param)
			),
			2
		)
	end
end


-- Constraints keep two points together in place and maintain uniform distance between the two.
-- Constraints and Points together join to keep a RigidBody in place hence making both Points and Constraints a vital part of the library.
-- Custom constraints such as Ropes, Rods, Bridges and chains can also be made.
-- Points of two rigid bodies can be connected with constraints, two individual points can also be connected with constraints to form Ropes etc.

-- Services and utilities
local line = require("../Utilities/Line")
local Globals = require("../Constants/Globals")
local throwTypeError = require("../Debugging/TypeErrors")
local throwException = require("../Debugging/Exceptions")
local Janitor = require("../Utilities/Janitor")
local Types = require("../Types")
local https = game:GetService("HttpService")

local Constraint = {}
Constraint.__index = Constraint

-- This method is used to initialize a constraint.
function Constraint.new(
	p1: Types.Point,
	p2: Types.Point,
	canvas: Types.Canvas,
	config: Types.SegmentConfig,
	engine,
	parent
)
	local self = setmetatable({
		id = https:GenerateGUID(false),
		_janitor = nil,
		engine = engine,
		Parent = parent,
		frame = nil,
		canvas = canvas,
		point1 = p1,
		point2 = p2,
		restLength = config.restLength or (p2.pos - p1.pos).Magnitude,
		render = config.render,
		thickness = config.thickness or Globals.constraint.thickness,
		support = config.support,
		_TYPE = config.TYPE,
		k = 0.1,
		color = nil,
	}, Constraint)

	local janitor = Janitor.new()
	janitor:Add(self, "Destroy")
	janitor:Add(self.point1, "Destroy")
	janitor:Add(self.point2, "Destroy")
	if self.Parent then
		janitor:Add(self.Parent, "Destroy")
	end
	self._janitor = janitor

	self.point1.Parent = self
	self.point2.Parent = self
	self.point1._janitor:Add(self.point1.Parent, "Destroy")
	self.point2._janitor:Add(self.point2.Parent, "Destroy")

	return self
end

-- This method is used to keep uniform distance between the constraint's points, i.e. constrain.
function Constraint:Constrain()
	local cur = (self.point2.pos - self.point1.pos).Magnitude
	local force

	-- Validate constraint types
	if self._TYPE == "ROPE" then
		local restLength = self.restLength
		if cur < self.thickness then
			restLength = self.thickness
		end

		if cur > self.restLength or self.restLength < self.thickness then
			-- Solve rope constraint force
			local offset = ((restLength - cur) / restLength) / 2
			force = self.point2.pos - self.point1.pos
			force *= offset
		end
	elseif self._TYPE == "ROD" then
		-- Solve rod constraint force
		local offset = self.restLength - cur
		local dif = self.point2.pos - self.point1.pos
		dif = dif.Unit
		force = (dif * offset) / 2
	elseif self._TYPE == "SPRING" then
		-- Solve spring constraint force
		force = self.point2.pos - self.point1.pos
		local mag = force.Magnitude - self.restLength
		force = force.Unit
		force *= -1 * self.k * mag
	else
		return
	end

	-- Apply forces to constraint's points
	if force then
		if not self.point1.snap then
			self.point1.pos -= force
		end
		if not self.point2.snap then
			self.point2.pos += force
		end
	end
end

-- This method is used to update the position and appearance of the constraint on screen.
function Constraint:Render()
	if self.render and not self.support then
		if not self.canvas.frame then
			throwException("error", "CANVAS_FRAME_NOT_FOUND")
		end

		local thickness = self.thickness or Globals.constraint.thickness
		local color = self.color or Globals.constraint.color
		local image = self._TYPE == "SPRING" and "rbxassetid://8404350124" or nil

		if not self.frame then
			self.frame = line(self.point1.pos, self.point2.pos, self.canvas.frame, thickness, color, nil, image)
			self._janitor:Add(self.frame, "Destroy")
		end

		-- Draw constraint on screen
		line(self.point1.pos, self.point2.pos, self.canvas.frame, thickness, color, self.frame, image)
	end
end

-- Used to set the minimum constrained distance between two points.
-- By default, the initial distance between the two points.
function Constraint:SetLength(newLength: number)
	throwTypeError("length", newLength, 1, "number")
	if newLength <= 0 then
		throwException("error", "INVALID_CONSTRAINT_LENGTH")
	end

	self.restLength = newLength
end

-- This method returns the current distance between the two points of a constraint.
function Constraint:GetLength(): number
	return (self.point2.pos - self.point1.pos).Magnitude
end

-- This method is used to change the color of a constraint.
-- By default a constraint's color is set to the default value of (WHITE) Color3.new(1, 1, 1).
function Constraint:Stroke(color: Color3)
	throwTypeError("color", color, 1, "Color3")
	self.color = color
end

-- This method destroys the constraint.
-- Its UI element is no longer rendered on screen and the constraint is removed from the engine.
-- This is irreversible.
function Constraint:Destroy()
	self._janitor:Cleanup()

	if not self.Parent then
		for i, c in ipairs(self.engine.constraints) do
			if c.id == self.id then
				table.remove(self.engine.constraints, i)
				self.engine.ObjectRemoved:Fire(self)
				break
			end
		end
	end

	self.point1 = nil
	self.point2 = nil
end

-- Returns the constraints points.
function Constraint:GetPoints()
	return self.point1, self.point2
end

-- Returns the UI element for the constrained IF rendered.
function Constraint:GetFrame(): Frame?
	return self.frame
end

-- This method is used to update the Spring constant (by default 0.1) used for spring constraint calculations.
function Constraint:SetSpringConstant(k: number)
	throwTypeError("springConstant", k, 1, "number")
	self.k = k
end

-- The constraints's unique ID can be fetched using this method.
function Constraint:GetId(): string
	return self.id
end

-- Returns the Parent (RigidBody) of the Constraint if any.
function Constraint:GetParent()
	return self.Parent
end

return Constraint


local UserInputService = game:GetService("UserInputService")

return function (engine: { any }, range: number)
	local held = nil
	local connections = {}

	connections.InputBegan = UserInputService.InputBegan:Connect(function(input, processedEvent)
		if processedEvent then return end

		if input.UserInputType == Enum.UserInputType.MouseButton1 and not held then
			for _, b in ipairs((engine :: any).bodies) do
				for _, p in ipairs(b.vertices) do
					if (p.pos - UserInputService:GetMouseLocation()).Magnitude <= range then
						p.selectable = true
						p.snap = true
						held = p
						break
					end
				end

				if held then
					break
				end
			end
		end
	end)

	connections.InputEnded = UserInputService.InputEnded:Connect(function(input, processedEvent)
		if processedEvent then return end

		if input.UserInputType == Enum.UserInputType.MouseButton1 and held then
			held.selectable = false
			held.snap = false
			held = nil
		end
	end)

	connections.InputChanged = UserInputService.InputChanged:Connect(function(input, processedEvent)
		if processedEvent then return end

		if input.UserInputType == Enum.UserInputType.MouseMovement and held then
			local mouse = UserInputService:GetMouseLocation()
			held:SetPosition(mouse.X, mouse.Y)
		end
	end)

	return function ()
		held.snap = false
		held = nil
		connections.InputBegan:Disconnect()
		connections.InputEnded:Disconnect()
		connections.InputChanged:Disconnect()
	end
end


-- Returns a quadrilateral structure for Custom RigidBodies given 4 points
return function (a: Vector2, b: Vector2, c: Vector2, d: Vector2)
	return {
		{ a, b, false },
		{ b, c, false },
		{ c, d, false },
		{ d, a, false },
		{ a, c, true },
		{ b, d, true }
	}
end


-- Returns a triangular structure for Custom RigidBodies given 3 points
return function (a: Vector2, b: Vector2, c: Vector2)
	return {
		{ a, b, false },
		{ a, c, false },
		{ b, c, false }
	}
end


return {
	MouseConstraint = require("@self/MouseConstraint"),
	Quad = require("@self/Quad"),
	Triangle = require("@self/Triangle"),
}


-- TODO: When Promise is on Wally, remove this in favor of just `script.Parent.Parent:FindFirstChild("Promise")`.
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local ServerStorage = game:GetService("ServerStorage")

local LOCATIONS_TO_SEARCH = {
	-- script.Parent.Parent,
	ReplicatedFirst,
	ReplicatedStorage,
	ServerScriptService,
	ServerStorage,
}

local function FindFirstDescendantWithNameAndClassName(Parent: Instance, Name: string, ClassName: string)
	for _, Descendant in ipairs(Parent:GetDescendants()) do
		if Descendant:IsA(ClassName) and Descendant.Name == Name then
			return Descendant
		end
	end

	return nil
end

local function GetPromiseLibrary()
	-- I'm not too keen on how this is done.
	-- It's better than the multiple if statements (probably).
	local Plugin = script:FindFirstAncestorOfClass("Plugin")
	if Plugin then
		local Promise = FindFirstDescendantWithNameAndClassName(Plugin, "Promise", "ModuleScript")
		if Promise then
			return true, require(Promise)
		else
			return false
		end
	end

	local Promise
	for _, Location in ipairs(LOCATIONS_TO_SEARCH) do
		Promise = FindFirstDescendantWithNameAndClassName(Location, "Promise", "ModuleScript")
		if Promise then
			break
		end
	end

	if Promise then
		return true, require(Promise)
	else
		return false
	end
end

return GetPromiseLibrary


-- This only exists because the LSP warns Key `__tostring` not found in type `table?`.
local function Symbol(Name: string)
	local self = newproxy(true)
	local Metatable = getmetatable(self)
	function Metatable.__tostring()
		return Name
	end

	return self
end

return Symbol


-- This utility is used to render a constraint on the screen.

-- Services and utilities
local Globals = require("../Constants/Globals")

-- Create the constraint's instance and apply properties
local function draw(
	hyp: number,
	origin: Vector2,
	thickness: number,
	parent: Instance,
	color: Color3,
	l: Frame?,
	image: string?
): Frame
	local line = l or (image and Instance.new("ImageLabel") or Instance.new("Frame"))
	line.Name = "Constraint"
	line.AnchorPoint = Vector2.new(0.5, 0.5)
	line.Size = UDim2.new(0, hyp, 0, (thickness or Globals.constraint.thickness) + (image and 15 or 0))
	line.BackgroundTransparency = image and 1 or 0
	line.BorderSizePixel = 0
	line.Position = UDim2.fromOffset(origin.X, origin.Y)
	line.ZIndex = 1

	if image then
		line.Image = image
		line.ImageColor3 = color or Globals.constraint.color
	else
		line.BackgroundColor3 = color or Globals.constraint.color
	end

	line.Parent = parent

	return line
end

return function(
	origin: Vector2,
	endpoint: Vector2,
	parent: Instance,
	thickness: number,
	color: Color3,
	l: Frame?,
	image: string?
): Frame
	-- Calculate magnitude between the constraint's points
	-- Draw the constraint
	-- Calculate rotation
	local hyp = (endpoint - origin).Magnitude
	local line = draw(hyp, origin, thickness, parent, color, l, image)
	local mid = (origin + endpoint) / 2
	local theta = math.atan2((origin - endpoint).Y, (origin - endpoint).X)

	-- Apply rotation and update position
	line.Position = UDim2.fromOffset(mid.x, mid.y)
	line.Rotation = math.deg(theta)

	return line
end


-- This utility is used in Collision Detection
-- Quadtree data structure

-- Services and utilities
local Types = require("../Types")

local Quadtree = {}
Quadtree.__index = Quadtree

-- Calculate sub-divisions of a node
local function GetDivisions(position: Vector2, size: Vector2)
	return {
		position,
		position + Vector2.new(size.X / 2, 0),
		position + Vector2.new(0, size.Y / 2),
		position + Vector2.new(size.X / 2, size.Y / 2),
	}
end

-- Check if a range overlaps a node of the quadtree
local function RangeOverlapsNode(node: Types.Quadtree<Types.RigidBody>, range: Types.Range): boolean
	local ap1 = range.position
	local as1 = range.size
	local sum = ap1 + as1

	local ap2 = node.position
	local as2 = node.size
	local sum2 = ap2 + as2

	-- Detect overlapping
	return (ap1.x < sum2.x and sum.x > ap2.x) and (ap1.y < sum2.y and sum.y > ap2.y)
end

-- Check if a point lies within a range
local function RangeHasPoint(range: Types.Range, obj: Types.RigidBody): boolean
	local p = obj.center

	return (
		(p.X > range.position.X)
		and (p.X < (range.position.X + range.size.X))
		and (p.Y > range.position.Y)
		and (p.Y < (range.position.Y + range.size.Y))
	)
end

-- Initialize a new quadtree
function Quadtree.new(_position: Vector2, _size: Vector2, _capacity: number)
	return setmetatable({
		position = _position,
		size = _size,
		capacity = _capacity,
		objects = {},
		divided = false,
	}, Quadtree)
end

-- Insert a RigidBody in the quadtree
function Quadtree:Insert(body: Types.RigidBody)
	if not self:HasObject(body.center) then
		return
	end

	if #self.objects < self.capacity then
		self.objects[#self.objects + 1] = body
	else
		-- Subdivide if not already
		if not self.divided then
			self:SubDivide()
			self.divided = true
		end

		-- Insert the RigidBody in the subdivisions if possible
		self.topLeft:Insert(body)
		self.topRight:Insert(body)
		self.bottomLeft:Insert(body)
		self.bottomRight:Insert(body)
	end
end

function Quadtree:HasObject(p: Vector2): boolean
	return (
		(p.X > self.position.X)
		and (p.X < (self.position.X + self.size.X))
		and (p.Y > self.position.Y)
		and (p.Y < (self.position.Y + self.size.Y))
	)
end

-- Create subdivisions of a node
function Quadtree:SubDivide()
	local divisions = GetDivisions(self.position, self.size)

	self.topLeft = Quadtree.new(divisions[1], self.size / 2, self.capacity)
	self.topRight = Quadtree.new(divisions[2], self.size / 2, self.capacity)
	self.bottomLeft = Quadtree.new(divisions[3], self.size / 2, self.capacity)
	self.bottomRight = Quadtree.new(divisions[4], self.size / 2, self.capacity)
end

-- Search through the nodes, given a range query.
-- Returns any rigidbody that lies within the range.
function Quadtree:Search(range: Types.Range, objects: { Types.RigidBody })
	if not objects then
		objects = {}
	end

	if not RangeOverlapsNode(self, range) then
		return objects
	end

	for _, obj in ipairs(self.objects) do
		if RangeHasPoint(range, obj) then
			objects[#objects + 1] = obj
		end
	end

	if self.divided then
		self.topLeft:Search(range, objects)
		self.topRight:Search(range, objects)
		self.bottomLeft:Search(range, objects)
		self.bottomRight:Search(range, objects)
	end

	return objects
end

return Quadtree


-- -----------------------------------------------------------------------------
--               Batched Yield-Safe Signal Implementation                     --
-- This is a Signal class which has effectively identical behavior to a       --
-- normal RBXScriptSignal, with the only difference being a couple extra      --
-- stack frames at the bottom of the stack trace when an error is thrown.     --
-- This implementation caches runner coroutines, so the ability to yield in   --
-- the signal handlers comes at minimal extra cost over a naive signal        --
-- implementation that either always or never spawns a thread.                --
--                                                                            --
-- API:                                                                       --
--   local Signal = require(THIS MODULE)                                      --
--   local sig = Signal.new()                                                 --
--   local connection = sig:Connect(function(arg1, arg2, ...) ... end)        --
--   sig:Fire(arg1, arg2, ...)                                                --
--   connection:Disconnect()                                                  --
--   sig:DisconnectAll()                                                      --
--   local arg1, arg2, ... = sig:Wait()                                       --
--                                                                            --
-- Licence:                                                                   --
--   Licenced under the MIT licence.                                          --
--                                                                            --
-- Authors:                                                                   --
--   stravant - July 31st, 2021 - Created the file.                           --
--   sleitnick - August 3rd, 2021 - Modified for Knit.                        --
-- -----------------------------------------------------------------------------

-- The currently idle thread to run the next handler on
local freeRunnerThread = nil

-- Function which acquires the currently idle handler runner thread, runs the
-- function fn on it, and then releases the thread, returning it to being the
-- currently idle one.
-- If there was a currently idle runner thread already, that's okay, that old
-- one will just get thrown and eventually GCed.
local function acquireRunnerThreadAndCallEventHandler(fn, ...)
	local acquiredRunnerThread = freeRunnerThread
	freeRunnerThread = nil
	fn(...)
	-- The handler finished running, this runner thread is free again.
	freeRunnerThread = acquiredRunnerThread
end

-- Coroutine runner that we create coroutines of. The coroutine can be
-- repeatedly resumed with functions to run followed by the argument to run
-- them with.
local function runEventHandlerInFreeThread(...)
	acquireRunnerThreadAndCallEventHandler(...)
	while true do
		acquireRunnerThreadAndCallEventHandler(coroutine.yield())
	end
end

-- Connection class
local Connection = {}
Connection.__index = Connection

function Connection.new(signal, fn)
	return setmetatable({
		Connected = true,
		_signal = signal,
		_fn = fn,
		_next = false,
	}, Connection)
end

function Connection:Disconnect()
	if not self.Connected then
		return
	end
	self.Connected = false

	-- Unhook the node, but DON'T clear it. That way any fire calls that are
	-- currently sitting on this node will be able to iterate forwards off of
	-- it, but any subsequent fire calls will not hit it, and it will be GCed
	-- when no more fire calls are sitting on it.
	if self._signal._handlerListHead == self then
		self._signal._handlerListHead = self._next
	else
		local prev = self._signal._handlerListHead
		while prev and prev._next ~= self do
			prev = prev._next
		end
		if prev then
			prev._next = self._next
		end
	end
end

Connection.Destroy = Connection.Disconnect

-- Make Connection strict
setmetatable(Connection, {
	__index = function(_tb, key)
		if key ~= "_handlerListHead" then
			error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2)
		end
	end,
	__newindex = function(_tb, key, _value)
		error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2)
	end
})

--[=[
	@class Signal
	Signals allow events to be dispatched and handled.
	For example:
	```lua
	local signal = Signal.new()
	signal:Connect(function(msg)
		print("Got message:", msg)
	end)
	signal:Fire("Hello world!")
	```
]=]
local Signal = {}
Signal.__index = Signal

--[=[
	Constructs a new Signal
	@return Signal
]=]
function Signal.new()
	local self = setmetatable({
		_handlerListHead = false,
		_proxyHandler = nil,
	}, Signal)
	return self
end

--[=[
	Constructs a new Signal that wraps around an RBXScriptSignal.
	@param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap
	@return Signal
	For example:
	```lua
	local signal = Signal.Wrap(workspace.ChildAdded)
	signal:Connect(function(part) print(part.Name .. " added") end)
	Instance.new("Part").Parent = workspace
	```
]=]
function Signal.Wrap(rbxScriptSignal)
	assert(typeof(rbxScriptSignal) == "RBXScriptSignal", "Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal))
	local signal = Signal.new()
	signal._proxyHandler = rbxScriptSignal:Connect(function(...)
		signal:Fire(...)
	end)
	return signal
end

--[=[
	Checks if the given object is a Signal.
	@param obj any -- Object to check
	@return boolean -- `true` if the object is a Signal.
]=]
function Signal.Is(obj)
	return type(obj) == "table" and getmetatable(obj) == Signal
end

--[=[
	Connects a function to the signal, which will be called anytime the signal is fired.
	@param fn (...any) -> nil
	@return Connection -- A connection to the signal
]=]
function Signal:Connect(fn: (...any) -> ())
	local connection = Connection.new(self, fn)
	if self._handlerListHead then
		connection._next = self._handlerListHead
		self._handlerListHead = connection
	else
		self._handlerListHead = connection
	end
	return connection
end

function Signal:GetConnections()
	local items = {}
	local item = self._handlerListHead
	while item do
		table.insert(items, item)
		item = item._next
	end
	return items
end

--[=[
	Disconnects all connections from the signal.
]=]
function Signal:DisconnectAll()
	local item = self._handlerListHead
	while item do
		item.Connected = false

		item = item._next
	end

	self._handlerListHead = false
end

-- Signal:Fire(...) implemented by running the handler functions on the
-- coRunnerThread, and any time the resulting thread yielded without returning
-- to us, that means that it yielded to the Roblox scheduler and has been taken
-- over by Roblox scheduling, meaning we have to make a new coroutine runner.
--[=[
	Fire the signal, which will call all of the connected functions with the given arguments.
	@param ... any -- Arguments to pass to the connected functions
]=]
function Signal:Fire(...)
	local item = self._handlerListHead
	while item do
		if item.Connected then
			if not freeRunnerThread then
				freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
			end
			task.spawn(freeRunnerThread, item._fn, ...)
		end
		item = item._next
	end
end

--[=[
	Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse.
	@param ... any -- Arguments to pass to the connected functions
]=]
function Signal:FireDeferred(...)
	local item = self._handlerListHead
	while item do
		task.defer(item._fn, ...)
		item = item._next
	end
end

--[=[
	Yields the current thread until the signal is fired, and returns the arguments fired from the signal.
	@return ... any -- Arguments passed to the signal when it was fired
	@yields
]=]
function Signal:Wait(): (...any)
	local waitingCoroutine = coroutine.running()
	local cn
	cn = self:Connect(function(...)
		cn:Disconnect()
		task.spawn(waitingCoroutine, ...)
	end)

	return coroutine.yield()
end

--[=[
	Cleans up the signal.
]=]
function Signal:Destroy()
	self:DisconnectAll()
	local proxyHandler = rawget(self, "_proxyHandler")
	if proxyHandler then
		proxyHandler:Disconnect()
	end
end

-- Make signal strict
setmetatable(Signal, {
	__index = function(_tb, key)
		if key ~= "Connected" then
			error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2)
		end
	end,
	__newindex = function(_tb, key, _value)
		error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2)
	end
})

return Signal


export type Octree<T> = {
	ClearAllNodes: (self: Octree<T>) -> (),
	GetAllNodes: (self: Octree<T>) -> { Node<T> },
	ForEachNode: (self: Octree<T>) -> () -> Node<T>?,
	FindFirstNode: (self: Octree<T>, object: T) -> Node<T>?,
	CountNodes: (self: Octree<T>) -> number,
	CreateNode: (self: Octree<T>, position: Vector3, object: T) -> Node<T>,
	RemoveNode: (self: Octree<T>, node: Node<T>) -> (),
	ChangeNodePosition: (self: Octree<T>, node: Node<T>, position: Vector3) -> (),
	SearchRadius: (self: Octree<T>, position: Vector3, radius: number) -> { Node<T> },
	ForEachInRadius: (self: Octree<T>, position: Vector3, radius: number) -> () -> Node<T>?,
	GetNearest: (self: Octree<T>, position: Vector3, radius: number, maxNodes: number?) -> { Node<T> },
}

type OctreeInternal<T> = Octree<T> & {
	Size: number,
	Regions: { Region<T> },
	_getRegion: (self: OctreeInternal<T>, maxLevel: number, position: Vector3) -> Region<T>,
}

export type Region<T> = {
	Center: Vector3,
	Size: number,
	Radius: number,
	Regions: { Region<T> },
	Parent: Region<T>?,
	Level: number,
	Nodes: { Node<T> }?,
}

export type Node<T> = {
	Position: Vector3,
	Object: T,
}

type NodeInternal<T> = Node<T> & {
	Region: Region<T>?,
}
return require("octotree") :: {
	new: <T>(topRegionSize: number?) -> Octree<T>,
}


type FaceOpacities = {
	[Enum.NormalId]: {
		Internal: "Push" | "Pull" | "Opaque",
		External: "Push" | "Pull" | "Opaque",
	} | "Exit" | "Enter",
}

type ClassType = "Natural" | "Craftable" | "Tool" | "Unused"
type Category = "Logic" | "Tools" | "Weapons" | "Electrical" | "Templates" | "Propulsion" | "Resources" | string
type ClassState = "Solid" | "Liquid" | "Gas"

type ConfigValue = {
	Name: string,
	Description: string,
} & ({
	Type: string,
	[string]: any,
} | {
	Type: "number",
	Default: number,
	Options: { number },
} | {
	Type: "boolean",
	Default: boolean,
} | {
	Type: "string",
	Default: string,
} | {
	Type: "Selection",
	Default: number?,
	Options: { string },
})

export type ComponentInfo = {
	-- Metadata
	ClassName: string,
	Description: string?,

	ConfigData: { ConfigValue }?,

	-- Behaviour
	Conflicts: { string }?,
	Events: { string }?,
}

export type PartInfo = {
	-- Metadata
	ClassName: string,
	Categories: { Category },
	ClassState: ClassState,
	ClassType: ClassType,
	Description: string?,
	Color: Color3?,

	Recipe: { [string]: number }?,
	ConfigData: { ConfigValue }?,

	Spawnable: boolean,
	Craftable: boolean,

	-- Physical properties
	Malleability: number,
	BaseSize: Vector3,
	BaseDurability: number,
	BaseHeatCapacity: number,

	-- Behaviour
	Flammable: boolean,
	FaceOpacity: FaceOpacities?,
	Events: { string }?,
	ProgrammableEvents: { string }?,

	ResourceType: string?,
	ResourceAmount: number?,

	GeneratorType: string?,
	GeneratorAmount: number?,

	HeatPowerProduct: number?,
	HeatGeneratorProduct: number?,
}

return require("partdata") :: {
	Components: { [string]: ComponentInfo },
	Parts: { [string]: PartInfo },
}


type Players = {
	GetUserId: (self: Players, username: string) -> number,
	GetUsername: (self: Players, userId: number) -> string,
}

return require("players") :: Players


--[=[
```lua
local myTable = {
	hello = "repr",
	usefulness = 9001,
	isEasyToUse = true,
	array = {"numerical", "arrays", "are", "easy"},
	land = workspace["a b c"]["1 2 3"],
	subTables = {
		moreInfo = "calls itself recursively to print sub-tables"
	},
	usesToString = {__tostring = function () return "__tostring functions are called automatically" end},
	["$YMBOL$"] = "keys that aren't Lua identifiers are quoted";
	[{also = "tables as keys work too";}] = "in case you need that",
	cyclic = {note = "cyclical tables are printed as just {CYCLIC}"}
}
-- create a cycle:
myTable.cyclic.cyclic = myTable.cyclic

local reprSettings = {
	pretty = false;              -- print with \n and indentation?
	semicolons = false;          -- when printing tables, use semicolons (;) instead of commas (,)?
	sortKeys = true;             -- when printing dictionary tables, sort keys alphabetically?
	spaces = 3;                  -- when pretty printing, use how many spaces to indent?
	tabs = false;                -- when pretty printing, use tabs instead of spaces?
	robloxFullName = false;      -- when printing Roblox objects, print full name or just name?
	robloxProperFullName = true; -- when printing Roblox objects, print a proper* full name?
	robloxClassName = true;      -- when printing Roblox objects, also print class name in parens?
}
print(repr(myTable, reprSettings))
```
]=]
return require("repr") :: (
	value: any?,
	configuration: {
		pretty: boolean?,
		robloxFullName: boolean?,
		robloxProperFullName: boolean?,
		robloxClassName: boolean?,
		tabs: boolean?,
		semicolons: boolean?,
		spaces: number?,
		sortKeys: boolean?,
	}?
) -> string


--!strict
local None = require("../None")

--[=[
	@function concat
	@within Array

	@param ... ...any -- The arrays to concatenate.
	@return {T} -- The concatenated array.

	Joins multiple arrays together into a single array.

	#### Aliases

	`join`, `merge`

	```lua
	local table1 = { 1, 2, 3 }
	local table2 = { 4, 5, 6 }

	local new = Concat(table1, table2) -- { 1, 2, 3, 4, 5, 6 }
	```
]=]
local function concat<T>(...: any): { T }
	local result = {}

	for arrayIndex = 1, select("#", ...) do
		local array = select(arrayIndex, ...)

		if type(array) ~= "table" then
			continue
		end

		for _, item in ipairs(array) do
			if item ~= None then
				table.insert(result, item)
			end
		end
	end

	return result
end

return concat


--!strict
local CopyDeep = require("./copyDeep")
local None = require("../None")

--[=[
	@function concatDeep
	@within Array

	@param ... ...any -- The arrays to concatenate.
	@return {T} -- The concatenated array.

	Joins multiple arrays together into a single array, with deep copies of all
	nested arrays.

	#### Aliases

	`joinDeep`, `mergeDeep`

	```lua
	local table1 = { 1, 2, { 3, 4 } }
	local table2 = { 5, 6, { 7, 8 } }

	local new = ConcatDeep(table1, table2) -- { 1, 2, { 3, 4 }, 5, 6, { 7, 8 } }
	```
]=]
local function concatDeep<T>(...: any): { T }
	local result = {}

	for arrayIndex = 1, select("#", ...) do
		local array = select(arrayIndex, ...)

		if type(array) ~= "table" then
			continue
		end

		for _, value in ipairs(array) do
			if value ~= None then
				if type(value) == "table" then
					table.insert(result, CopyDeep(value))
				else
					table.insert(result, value)
				end
			end
		end
	end

	return result :: any
end

return concatDeep


--!strict
local Util = require("../Util")

--[=[
	@function count
	@within Array

	@param array {T} -- The array to count the number of items in.
	@param predicate? (value: T, index: number, array: {T}) -> any -- The predicate to use to filter the array.
	@return number -- The number of items in the array.

	Counts the number of items in an array.

	```lua
	local array = { 1, 2, 3 }

	local value = Count(array) -- 3
	local value = Count(array, function(item, index)
		return item == 2
	end) -- 1
	```
]=]
local function count<T>(array: { T }, predicate: ((value: T, index: number, array: { T }) -> any)?): number
	local counter = 0

	predicate = if type(predicate) == "function" then predicate else Util.func.truthy

	for index, value in ipairs(array) do
		if (predicate :: any)(value, index, array) then
			counter += 1
		end
	end

	return counter
end

return count


--!strict
local T = require("../Types")

local toSet = require("./toSet")
local toArray = require("../Set/toArray")
local setDifference = require("../Set/difference")

--[=[
  @function difference
  @within Array

  @param array Array<V> -- The array to compare.
  @param ... ...Array<V> -- The arrays to compare against.
  @return Array<V> -- The difference between the arrays.

  Returns an array of values that are in the first array, but not in the other arrays.

  ```lua
  local array1 = { "hello", "world" }
  local array2 = { "cat", "dog", "hello" }

  local difference = Difference(array1, array2) -- { "world" }
  ```
]=]
local function difference<V>(array: T.Array<V>, ...: T.Array<V>): T.Array<V>
	local arraySet = toSet(array)
	local otherSets = {}

	for _, nextArray in { ... } do
		if typeof(nextArray) ~= "table" then
			continue
		end

		table.insert(otherSets, toSet(nextArray))
	end

	local differenceSet = setDifference(arraySet, unpack(otherSets))

	return toArray(differenceSet)
end

return difference


--!strict
local T = require("../Types")

local toSet = require("./toSet")
local toArray = require("../Set/toArray")
local setDifferenceSymmetric = require("../Set/differenceSymmetric")

--[=[
  @function differenceSymmetric
  @within Array

  @param array Array<V> -- The array to compare.
  @param ... ...Array<V> -- The arrays to compare against.
  @return Array<V> -- The symmetric difference between the arrays.

  Returns an array of values that are in the first array, but not in the other arrays, and vice versa.

  ```lua
  local array1 = { "hello", "world" }
  local array2 = { "cat", "dog", "hello" }

  local difference = DifferenceSymmetric(array1, array2) -- { "world", "cat", "dog" }
  ```
]=]
local function differenceSymmetric<V>(array: T.Array<V>, ...: T.Array<V>): T.Array<V>
	local arraySet = toSet(array)
	local otherSets = {}

	for _, nextArray in { ... } do
		if typeof(nextArray) ~= "table" then
			continue
		end

		table.insert(otherSets, toSet(nextArray))
	end

	local differenceSet = setDifferenceSymmetric(arraySet, unpack(otherSets))

	return toArray(differenceSet)
end

return differenceSymmetric


--!strict
local Util = require("../Util")

local function compare(a, b)
	if type(a) ~= "table" or type(b) ~= "table" then
		return a == b
	end

	local aLength = #a

	if #b ~= aLength then
		return false
	end

	for i = 1, aLength do
		if a[i] ~= b[i] then
			return false
		end
	end

	return true
end

--[=[
	@function equals
	@within Array

	@param ... ...{any} -- The arrays to compare.
	@return boolean -- Whether the arrays are equal.

	Compares two arrays for equality.

	```lua
	local array = { 1, 2, 3 }
	local other = { 1, 2, 3 }

	local value = Equals(array, other) -- true
	local value = Equals(array, other, { 1, 2, 3 }) -- true
	local value = Equals(array, other, { 1, 2, 4 }) -- false
	```
]=]
local function equals<T>(...: { T }): boolean
	if Util.equalObjects(...) then
		return true
	end

	local totalArgs = select("#", ...)
	local firstItem = select(1, ...)

	for i = 2, totalArgs do
		local item = select(i, ...)

		if not compare(firstItem, item) then
			return false
		end
	end

	return true
end

return equals


--!strict
local Util = require("../Util")

local function compareDeep(a, b)
	if type(a) ~= "table" or type(b) ~= "table" then
		return a == b
	end

	local aLength = #a

	if #b ~= aLength then
		return false
	end

	for i = 1, aLength do
		if not compareDeep(a[i], b[i]) then
			return false
		end
	end

	return true
end

--[=[
	@function equalsDeep
	@within Array

	@param ... ...{any} -- The arrays to compare.
	@return boolean -- Whether the arrays are equal.

	Compares two arrays for equality using deep comparison.

	```lua
	local array = { 1, 2, 3, { 4, 5 } }
	local other = { 1, 2, 3, { 4, 5 } }

	local value = EqualsDeep(array, other) -- true
	local value = EqualsDeep(array, other, { 1, 2, 3, { 4, 5 } }) -- true
	local value = EqualsDeep(array, other, { 1, 2, 3, { 4, 6 } }) -- false
	```
]=]
local function equalsDeep<T>(...: { T }): boolean
	if Util.equalObjects(...) then
		return true
	end

	local totalArgs = select("#", ...)
	local firstItem = select(1, ...)

	for i = 2, totalArgs do
		local item = select(i, ...)

		if not compareDeep(firstItem :: any, item) then
			return false
		end
	end

	return true
end

return equalsDeep


--!strict
local Util = require("../Util")

--[=[
	@function filter
	@within Array

	@param array {T} -- The array to filter.
	@param filterer? (value: T, index: number, array: {T}) -> any -- The callback to use to filter the array.
	@return {T} -- The filtered array.

	Filters an array using a filterer callback. Any items that do not pass the filterer will be removed from the array.

	If no filterer is provided, all items will be kept.

	```lua
	local array = { 1, 2, 3 }

	local result = Filter(array, function(item, index)
		return item > 1
	end) -- { 2, 3 }
	```
]=]
local function filter<T>(array: { T }, predicate: ((value: T, index: number, array: { T }) -> boolean?)?): { T }
	local result = {}

	predicate = if type(predicate) == "function" then predicate else Util.func.truthy

	for index, value in ipairs(array) do
		if (predicate :: any)(value, index, array) then
			table.insert(result, value)
		end
	end

	return result
end

return filter


--!strict
--[=[
	@function find
	@within Array

	@param array {T} -- The array to search.
	@param value? any -- The value to search for.
	@param from? number -- The index to start searching from.
	@return number? -- The index of the first item in the array that matches the value.

	Finds the index of the first item in the array that matches the value. This is
	mostly a wrapper around `table.find`, with the ability to specify a negative
	number as the start index (to search relative to the end of the array).

	#### Aliases
	`indexOf`

	```lua
	local array = { "hello", "world", "hello" }

	local index = Find(array, "hello") -- 1
	local index = Find(array, "hello", 2) -- 3
	```
]=]
local function find<T>(array: { T }, value: any?, from: number?): number?
	local length = #array

	from = if type(from) == "number" then if from < 1 then length + from else from else 1

	return table.find(array, value :: any, from)
end

return find


--!strict
--[=[
	@function findLast
	@within Array

	@param array {T} -- The array to search.
	@param value? any -- The value to search for.
	@param from? number -- The index to start searching from.
	@return number? -- The index of the last item in the array that matches the value.

	Finds the index of the last item in the array that matches the value.

	```lua
	local array = { "hello", "world", "hello" }

	local index = FindLast(array, "hello") -- 3
	local index = FindLast(array, "hello", 2) -- 1
	```
]=]
local function findLast<T>(array: { T }, value: any?, from: number?): number?
	local length = #array

	from = if type(from) == "number" then if from < 1 then length + from else from else length

	for index = from :: any, 1, -1 do
		if array[index] == value then
			return index
		end
	end

	return
end

return findLast


--!strict
--[=[
	@function findWhere
	@within Array

	@param array {T} -- The array to search.
	@param predicate (value: T, index: number, array: {T}) -> any -- The predicate to use to check the array.
	@param from? number -- The index to start searching from.
	@return number -- The index of the first item in the array that matches the predicate.

	Finds the index of the first item in the array that passes the predicate.

	```lua
	local array = { 1, 2, 3 }

	local index = FindWhere(array, function(item, index)
		return item > 1
	end) -- 2
	```
]=]
local function findWhere<T>(
	array: { T },
	predicate: (value: T, index: number, array: { T }) -> any,
	from: number?
): number?
	local length = #array

	from = if type(from) == "number" then if from < 1 then length + from else from else 1

	for index = from :: any, #array do
		if predicate(array[index], index, array) then
			return index
		end
	end

	return
end

return findWhere


--!strict
--[=[
	@function findWhereLast
	@within Array

	@param array {T} -- The array to search.
	@param predicate (value: T, index: number, array: {T}) -> any -- The predicate to use to check the array.
	@param from? number -- The index to start searching from.
	@return number -- The index of the last item in the array that matches the predicate.

	Finds the index of the last item in the array that passes the predicate.

	```lua
	local array = { "hello", "world", "hello" }

	local index = FindWhereLast(array, function(item, index)
		return item == "hello"
	end) -- 3

	local index = FindWhereLast(array, function(item, index)
		return item == "hello"
	end, 2) -- 1
	```
]=]
local function findWhereLast<T>(
	array: { T },
	predicate: (value: T, index: number, array: { T }) -> any,
	from: number?
): number?
	local length = #array

	from = if type(from) == "number" then if from < 1 then length + from else from else length

	for index = from :: any, 1, -1 do
		if predicate(array[index], index, array) then
			return index
		end
	end

	return
end

return findWhereLast


--!strict
local At = require("./at")

--[=[
	@function first
	@within Array

	@param array {T} -- The array to get the first item from.
	@return T -- The first item in the array.

	Gets the first item in the array.

	```lua
	local array = { 1, 2, 3 }

	local value = First(array) -- 1
	```
]=]
local function first<T>(array: { T }): T
	return At(array, 1)
end

return first


--!strict
--[=[
	@function flatten
	@within Array

	@param array {T} -- The array to flatten.
	@param depth? number -- The depth to flatten the array to.
	@return {T} -- The flattened array.

	Flattens an array. If depth is not specified, it will flatten
	the array as far as it can go.

	```lua
	local array = {
		{ 1, 2, 3 },
		{ 4, 5, 6 },
		{ 7, { 8, 9 } },
	}

	local new = Flatten(array) -- { 1, 2, 3, 4, 5, 6, 7, 8, 9 }
	local new = Flatten(array, 1) -- { 1, 2, 3, 4, 5, 6, 7, { 8, 9 } }
	```
]=]
local function flatten<T>(array: { T }, depth: number?): { T }
	depth = if type(depth) == "number" then depth else math.huge

	local result = {}

	for _, value in ipairs(array) do
		if type(value) == "table" and depth > 0 then
			local nested = flatten(value, depth :: any - 1)

			for _, nestedValue in ipairs(nested) do
				table.insert(result, nestedValue)
			end
		else
			table.insert(result, value)
		end
	end

	return result
end

return flatten


--!strict
local Copy = require("./copy")

--[=[
  @function freeze
  @within Array

  @param array {T} -- The array to freeze.
  @return {T} -- The frozen array.

  Freezes the top level of the array, making it read-only.

  ```lua
  local array = { 1, 2, 3, { 4, 5, 6 } }

  local new = Freeze(array)

  new[1] = 4 -- error!
  new[4][1] = 7 -- still works!
  ```
]=]
local function freeze<T>(array: { T }): { T }
	local new = Copy(array)

	table.freeze(new)

	return new
end

return freeze


--!strict
--[=[
	@function freezeDeep
	@within Array

	@param array {T} -- The array to freeze.
	@return {T} -- The frozen array.

	Freezes the entire array, making it read-only, including all
	nested arrays.

	```lua
	local array = { 1, 2, 3, { 4, 5, 6 } }

	local new = FreezeDeep(array)

	new[1] = 4 -- error!
	new[4][1] = 7 -- error!
	```
]=]
local function freezeDeep<T>(array: { T }): { T }
	local result = {}

	for i = 1, #array do
		local value = array[i]

		if type(value) == "table" then
			table.insert(result, freezeDeep(value))
		else
			table.insert(result, value :: any)
		end
	end

	table.freeze(result)

	return result :: any
end

return freezeDeep


--!strict
local Find = require("./find")

--[=[
	@function includes
	@within Array

	@param array {T} -- The array to search.
	@param value any -- The value to search for.
	@param from? number -- The index to start searching from.
	@return boolean -- Whether the array contains the value.

	Checks whether the array contains the value. This is a wrapper
	around `Find`.

	#### Aliases

	`contains`, `has`

	```lua
	local array = { "hello", "world", "goodbye" }

	local value = Includes(array, "hello") -- true
	local value = Includes(array, "sift") -- false
	local value = Includes(array, "hello", 2) -- false
	```
]=]
local function includes<T>(array: { T }, value: any, from: number?): boolean
	return Find(array, value, from) ~= nil
end

return includes


--!strict
--[=[
	@class Array

	An array is a table consisting of index-value pairs. You don't need to
	manually specify the indices when you create an array.

	```lua
	local array = {
		"hello",
		"world",
	}
	```

	#### Aliases

	`List`
]=]
local Array = {
	at = require("@self/at"),
	concat = require("@self/concat"),
	concatDeep = require("@self/concatDeep"),
	copy = require("@self/copy"),
	copyDeep = require("@self/copyDeep"),
	count = require("@self/count"),
	create = require("@self/create"),
	difference = require("@self/difference"),
	differenceSymmetric = require("@self/differenceSymmetric"),
	equals = require("@self/equals"),
	equalsDeep = require("@self/equalsDeep"),
	every = require("@self/every"),
	filter = require("@self/filter"),
	find = require("@self/find"),
	findLast = require("@self/findLast"),
	findWhere = require("@self/findWhere"),
	findWhereLast = require("@self/findWhereLast"),
	first = require("@self/first"),
	flatten = require("@self/flatten"),
	freeze = require("@self/freeze"),
	freezeDeep = require("@self/freezeDeep"),
	includes = require("@self/includes"),
	insert = require("@self/insert"),
	is = require("@self/is"),
	last = require("@self/last"),
	map = require("@self/map"),
	pop = require("@self/pop"),
	push = require("@self/push"),
	reduce = require("@self/reduce"),
	reduceRight = require("@self/reduceRight"),
	removeIndex = require("@self/removeIndex"),
	removeIndices = require("@self/removeIndices"),
	removeValue = require("@self/removeValue"),
	removeValues = require("@self/removeValues"),
	reverse = require("@self/reverse"),
	set = require("@self/set"),
	shift = require("@self/shift"),
	shuffle = require("@self/shuffle"),
	slice = require("@self/slice"),
	some = require("@self/some"),
	sort = require("@self/sort"),
	splice = require("@self/splice"),
	toSet = require("@self/toSet"),
	unshift = require("@self/unshift"),
	update = require("@self/update"),
	zip = require("@self/zip"),
	zipAll = require("@self/zipAll"),
}

Array.join = Array.concat
Array.merge = Array.concat
Array.joinDeep = Array.concatDeep
Array.mergeDeep = Array.concatDeep
Array.append = Array.push
Array.prepend = Array.unshift
Array.indexOf = Array.find
Array.has = Array.includes
Array.contains = Array.includes
Array.isArray = Array.is

return Array


--!strict
local At = require("./at")

--[=[
	@function last
	@within Array

	@param array {T} -- The array to get the last element of.
	@return T -- The last element of the array.

	Gets the last element of the array.

	```lua
	local array = { 1, 2, 3 }

	local value = Last(array) -- 3
	```
]=]
local function last<T>(array: { T }): T
	return At(array, 0)
end

return last


--!strict
--[=[
	@function pop
	@within Array

	@param array {T} -- The array to pop an element from.
	@param count? number = 1 -- The number of elements to pop.
	@return {T} -- An array with the popped elements removed.

	Removes an element from the end of the array, and returns
	the array with the popped elements removed.

	```lua
	local array = { 1, 2, 3 }

	local new = Pop(array) -- { 1, 2 }
	local new = Pop(array, 2) -- { 1 }
	```
]=]
local function pop<T>(array: { T }, count: number?): { T }
	local length = #array
	local result = {}

	count = if type(count) == "number" then count else 1

	for i = 1, length - count :: any do
		table.insert(result, array[i])
	end

	return result
end

return pop


--!strict
--[=[
	@function reduce
	@within Array

	@param array {T} -- The array to reduce.
	@param reducer (accumulator: U, value: T, index: number, array: {T}) -> U -- The reducer to use.
	@param initialReduction? U = {T}[1] -- The initial accumulator value.
	@return U -- The final accumulator value.

	Reduces the array using the given reducer and initial accumulator value.
	If no `initialReduction` value is given, the first item in the array is used.

	```lua
	local array = { 1, 2, 3 }

	local value = Reduce(array, function(accumulator, item, index)
		return accumulator - item
	end) -- -4

	local value = Reduce(array, function(accumulator, item, index)
		table.insert(accumulator, item)
		return accumulator
	end, {}) -- { 1, 2, 3 }
	```
]=]
local function reduce<T, U>(
	array: { T },
	reducer: (accumulator: U, value: T, index: number, array: { T }) -> U,
	initReduction: U?
): U
	local result = initReduction
	local start = 1

	if result == nil then
		result = array[1] :: any
		start = 2
	end

	for index = start, #array do
		result = reducer(result :: any, array[index], index, array)
	end

	return result :: any
end

return reduce


--!strict
--[=[
	@function reduceRight
	@within Array

	@param array {T} -- The array to reduce.
	@param reducer (accumulator: U, value: T, index: number, array: {T}) -> U -- The reducer to use.
	@param initialReduction? U = {T}[#{T}] -- The initial accumulator value.
	@return U -- The final accumulator value.

	Reduces the array using the given reducer and initial accumulator value,
	starting from the end of the array. If no `initialReduction` value is given,
	the last item in the array is used.

	```lua
	local array = { 1, 2, 3 }

	local value = ReduceRight(array, function(accumulator, item, index)
		return accumulator - item
	end) -- 0

	local value = ReduceRight(array, function(accumulator, item, index)
		table.insert(accumulator, item)
		return accumulator
	end, {}) -- { 3, 2, 1 }
	```
]=]
local function reduceRight<T, U>(
	array: { T },
	reducer: (accumulator: U, value: T, index: number, array: { T }) -> U,
	initReduction: U?
): U
	local result = initReduction
	local start = #array

	if result == nil then
		result = array[start] :: any
		start -= 1
	end

	for index = start, 1, -1 do
		result = reducer(result :: any, array[index], index, array)
	end

	return result :: any
end

return reduceRight


--!strict
local ToSet = require("./toSet")

--[=[
	@function removeValues
	@within Array

	@param array {T} -- The array to remove values from.
	@param ... T -- The values to remove.
	@return {T} -- The array with the values removed.

	Removes values from an array.

	```lua
	local array = { "a", "b", "c", "c", "d", "e" }

	local new = RemoveValues(array, "c", "d") -- { "a", "b", "e" }
	```
]=]
local function removeValues<T>(array: { T }, ...: T): { T }
	local valueSet = ToSet({ ... })
	local result = {}

	for _, value in ipairs(array) do
		if not valueSet[value] then
			table.insert(result, value)
		end
	end

	return result
end

return removeValues


--!strict
--[=[
	@function set
	@within Array

	@param array {T} -- The array to set the value on.
	@param index number -- The index to set the value at (can be negative).
	@param value T -- The value to set.
	@return {T} -- The array with the value set.

	Sets a value on an array at the given index.

	```lua
	local array = { 1, 2, 3 }

	local new = Set(array, 2, 4) -- { 1, 4, 3 }
	local new = Set(array, -1, 4) -- { 1, 2, 4 }
	```
]=]
local function set<T>(array: { T }, index: number, value: T?): { T }
	local length = #array
	local result = {}

	if index < 1 then
		index += length
	end

	for arrIndex, arrValue in ipairs(array) do
		if arrIndex == index then
			table.insert(result, value)
		else
			table.insert(result, arrValue)
		end
	end

	return result :: any
end

return set


--!strict
--[=[
	@function shift
	@within Array

	@param array {T} -- The array to shift.
	@param count? number -- The number of items to shift.
	@return {T} -- The shifted array.

	Removes the first item from an array and returns the array
	with the item removed.

	```lua
	local array = { 1, 2, 3 }

	local new = Shift(array) -- { 2, 3 }
	local new = Shift(array, 2) -- { 3 }
	```
]=]
local function shift<T>(array: { T }, count: number?): { T }
	local length = #array
	local result = {}

	count = if type(count) == "number" then count + 1 else 2

	for i = count :: any, length do
		table.insert(result, array[i])
	end

	return result
end

return shift


--!strict
local Copy = require("./copy")

--[=[
	@function shuffle
	@within Array

	@param array {T} -- The array to shuffle.
	@return {T} -- The shuffled array.

	Randomises the order of the items in an array.

	```lua
	local array = { 1, 2, 3 }

	local new = Shuffle(array) -- { 2, 3, 1 }
	```
]=]
local function shuffle<T>(array: { T }): { T }
	local random = Random.new(os.time() * #array)
	local result = Copy(array)

	for index = #result, 1, -1 do
		local randomIndex = random:NextInteger(1, index)
		local temp = result[index]

		result[index] = result[randomIndex]
		result[randomIndex] = temp
	end

	return result
end

return shuffle


--!strict
--[=[
	@function slice
	@within Array

	@param array {T} -- The array to slice.
	@param from? number -- The index to start from (can be negative).
	@param to? number -- The index to end at (can be negative).
	@return {T} -- The sliced array.

	Slices an array.

	```lua
	local array = { 1, 2, 3, 4, 5 }

	local new = Slice(array, 2, 3) -- { 2, 3 }
	local new = Slice(array, -2, -1) -- { 3, 4 }
	local new = Slice(array, 3) -- { 3, 4, 5 }
	```
]=]
local function slice<T>(array: { T }, from: number?, to: number?): { T }
	local length = #array
	local result = {}

	from = if type(from) == "number" then from else 1
	to = if type(to) == "number" then to else length

	if from < 1 then
		from += length :: any
	end

	if to < 1 then
		to += length :: any
	end

	for i = from :: any, to :: any do
		table.insert(result, array[i])
	end

	return result
end

return slice


--!strict
local Copy = require("./copy")

--[=[
	@function sort
	@within Array

	@param array {T} -- The array to sort.
	@param comparator? (a: T, b: T) -> boolean -- The comparator function.
	@return {T} -- The sorted array.

	Sorts an array.

	```lua
	local array = { "a", "b", "c", "d", "e" }

	local new = Sort(array, function(a, b)
		return a > b
	end) -- { "e", "d", "c", "b", "a" }
	```
]=]
local function sort<T>(array: { T }, comparator: ((firstValue: T, secondValue: T) -> boolean)?): { T }
	local result = Copy(array)

	table.sort(result, comparator)

	return result
end

return sort


--!strict
--[=[
	@function splice
	@within Array

	@param array {T} -- The array to splice.
	@param start? number -- The index to start splicing at (can be negative).
	@param end? number -- The index to end splicing at (can be negative).
	@param ... ...T -- The values to insert.
	@return {T} -- The spliced array.

	Splices an array.

	```lua
	local array = { 1, 2, 3, 4, 5 }

	local new = Splice(array, 3, 4, 6, 7) -- { 1, 2, 6, 7, 4, 5 }
	local new = Splice(array, -1, 0, 6, 7) -- { 1, 2, 3, 4, 6, 7 }
	local new = Splice(array, 4, -1, 6, 7) -- { 1, 2, 3, 6, 7, 5 }
	```
]=]
local function splice<T>(array: { T }, from: number?, to: number?, ...: T?): { T }
	local length = #array
	local result = {}

	from = if type(from) == "number" then from else 1
	to = if type(to) == "number" then to else length

	if from < 1 then
		from += length :: any
	end

	if to < 1 then
		to += length :: any
	end

	for index = 1, from :: any - 1 do
		table.insert(result, array[index])
	end

	for _, value in ipairs({ ... }) do
		table.insert(result, value :: any)
	end

	for index = to :: any + 1, length do
		table.insert(result, array[index])
	end

	return result
end

return splice


--!strict
local _T = require("../Types")

--[=[
	@function toSet
	@within Array

	@param array {T} -- The array to convert to a set.
	@return Set<T> -- The set.

	Converts an array to a set.

	```lua
	local array = { "a", "b", "b", "c", "d" }

	local set = ToSet(array) -- { a = true, b = true, c = true, d = true }
	```
]=]
local function toSet<T>(array: { T }): _T.Set<T>
	local set = {}

	for _, value in ipairs(array) do
		set[value] = true
	end

	return set
end

return toSet


--!strict
local Util = require("../Util")
local Copy = require("./copy")

type Callback<T> = (index: number) -> T
type Updater<T> = (currentValue: T, index: number) -> T

local function call<T>(callback: Callback<T>, index: number)
	return if type(callback) == "function" then callback(index) else nil
end

--[=[
	@function update
	@within Array

	@param array {T} -- The array to update.
	@param index number -- The index to update.
	@param updater? (value: T, index: number) -> T -- The updater function.
	@param callback? (index: number) -> T -- The callback function.
	@return {T} -- The updated array.

	Updates an array at the given index. If the value at the given index does
	not exist, `callback` will be called, and its return value will be used
	as the value at the given index.

	```lua
	local array = { 1, 2, 3 }

	local new = Update(array, 2, function(value)
		return value + 1
	end) -- { 2, 3, 3 }

	local new = Update(array, 4, function(value)
		return value + 1
	end, function(value)
		return 10
	end) -- { 1, 2, 3, 10 }
	```
]=]
local function update<T>(array: { T }, index: number, updater: Updater<T>?, callback: Callback<T>?): { T }
	local length = #array
	local result = Copy(array)

	if index < 1 then
		index += length
	end

	updater = if type(updater) == "function" then updater else Util.func.returned

	if result[index] ~= nil then
		result[index] = (updater :: any)(result[index], index)
	else
		result[index] = (call :: any)(callback, index)
	end

	return result
end

return update


--!strict
local Reduce = require("./reduce")

--[=[
	@function zip
	@within Array

	@param ... {any} -- The arrays to zip together.
	@return {any} -- The zipped array.

	Zips multiple arrays together into a single array.

	```lua
	local table1 = { 1, 2, 3 }
	local table2 = { "hello", "world", "goodbye" }

	local new = Zip(table1, table2) -- { { 1, "hello" }, { 2, "world" }, { 3, "goodbye" } }
	```
]=]
local function zip<T>(...: { any }): T
	local argCount = select("#", ...)
	local arguments = { ... }

	local result = {}

	if argCount == 0 then
		return result :: any
	end

	local minLength: number = Reduce(arguments, function(acc, val)
		return math.min(acc, #val)
	end, #arguments[1])

	for index = 1, minLength do
		local values = {}

		for _, argArray in ipairs(arguments) do
			table.insert(values, argArray[index])
		end

		table.insert(result, values)
	end

	return result :: any
end

return zip