local ServerScriptService = game:GetService("ServerScriptService")

local Test = require(ServerScriptService.TestRunner.Test)

return function(ctx: Test.TestContext)
	local Concur = require(script.Parent)

	local function Awaiter(timeout: number)
		local awaiter = {}
		local thread
		local delayThread
		function awaiter.Resume(...)
			if coroutine.running() ~= delayThread then
				task.cancel(delayThread)
			end
			task.spawn(thread, ...)
		end
		function awaiter.Yield()
			thread = coroutine.running()
			delayThread = task.delay(timeout, function()
				awaiter.Resume()
			end)
			return coroutine.yield()
		end
		return awaiter
	end

	local bindableEvent
	ctx:BeforeEach(function()
		bindableEvent = Instance.new("BindableEvent")
	end)
	ctx:AfterEach(function()
		bindableEvent:Destroy()
		bindableEvent = nil
	end)

	ctx:Describe("Single", function()
		ctx:Test("should spawn a new concur instance", function()
			local value = nil
			ctx:Expect(function()
				Concur.spawn(function()
					value = 10
				end)
			end)
				:Not()
				:ToThrow()
			ctx:Expect(value):ToBe(10)
		end)

		ctx:Test("should defer a new concur instance", function()
			local awaiter = Awaiter(1)
			ctx:Expect(function()
				Concur.defer(function()
					awaiter.Resume(10)
				end)
			end)
				:Not()
				:ToThrow()
			local value = awaiter.Yield()
			ctx:Expect(value):ToBe(10)
		end)

		ctx:Test("should delay a new concur instance", function()
			local awaiter = Awaiter(1)
			ctx:Expect(function()
				Concur.delay(0.1, function()
					awaiter.Resume(10)
				end)
			end)
				:Not()
				:ToThrow()
			local value = awaiter.Yield()
			ctx:Expect(value):ToBe(10)
		end)

		ctx:Test("should create an immediate value concur instance", function()
			local c
			ctx:Expect(function()
				c = Concur.value(10)
			end)
				:Not()
				:ToThrow()
			ctx:Expect(c):ToBeOk()
			ctx:Expect(c:IsCompleted()):ToBe(true)
			local err, val = c:Await()
			ctx:Expect(err):ToBeNil()
			ctx:Expect(val):ToBe(10)
		end)

		ctx:Test("should create a concur instance to watch an event with no predicate", function()
			local c
			ctx:Expect(function()
				c = Concur.event(bindableEvent.Event)
			end)
				:Not()
				:ToThrow()
			ctx:Expect(c:IsCompleted()):ToBe(false)
			bindableEvent:Fire(10)
			local err, val = c:Await(1)
			ctx:Expect(err):ToBeNil()
			ctx:Expect(val):ToBe(10)
		end)

		ctx:Test("should create a concur instance to watch an event with a predicate", function()
			local c
			ctx:Expect(function()
				c = Concur.event(bindableEvent.Event, function(v)
					return v < 10
				end)
			end)
				:Not()
				:ToThrow()
			ctx:Expect(c:IsCompleted()):ToBe(false)
			bindableEvent:Fire(10)
			bindableEvent:Fire(5)
			local err, val = c:Await(1)
			ctx:Expect(err):ToBeNil()
			ctx:Expect(val):ToBe(5)
		end)
	end)

	ctx:Describe("Multi", function()
		ctx:Test("should complete all concur instances", function()
			local c1 = Concur.spawn(function()
				return 10
			end)
			local c2 = Concur.defer(function()
				return 20
			end)
			local c3 = Concur.delay(0, function()
				return 30
			end)
			local c4 = Concur.spawn(function()
				error("fail")
			end)
			local c5 = Concur.event(bindableEvent.Event)
			local c = Concur.all({ c1, c2, c3, c4, c5 })
			ctx:Expect(c:IsCompleted()):ToBe(false)
			bindableEvent:Fire(40)
			local err, res = c:Await(1)
			ctx:Expect(err):ToBeNil()
			ctx:Expect(res[1][1]):ToBeNil()
			ctx:Expect(res[1][2]):ToBe(10)
			ctx:Expect(res[2][1]):ToBeNil()
			ctx:Expect(res[2][2]):ToBe(20)
			ctx:Expect(res[3][1]):ToBeNil()
			ctx:Expect(res[3][2]):ToBe(30)
			ctx:Expect(res[4][1]):ToBeOk()
			ctx:Expect(res[4][2]):ToBeNil()
			ctx:Expect(res[5][1]):ToBeNil()
			ctx:Expect(res[5][2]):ToBe(40)
		end)

		ctx:Test("should complete the first concur instance", function()
			local c1 = Concur.defer(function()
				return 10
			end)
			local c2 = Concur.spawn(function()
				return 20
			end)
			local c = Concur.first({ c1, c2 })
			local err, res = c:Await(1)
			ctx:Expect(err):ToBeNil()
			ctx:Expect(res):ToBe(20)
		end)
	end)

	ctx:Describe("Stop", function()
		ctx:Test("should stop a single concur", function()
			local c1 = Concur.defer(function()
				return 10
			end)
			ctx:Expect(c1:IsCompleted()):ToBe(false)
			c1:Stop()
			ctx:Expect(c1:IsCompleted()):ToBe(true)
			local err, val = c1:Await()
			ctx:Expect(err):ToBe(Concur.Errors.Stopped)
			ctx:Expect(val):ToBeNil()
		end)

		ctx:Test("should stop multiple concurs", function()
			local c1 = Concur.defer(function() end)
			local c2 = Concur.delay(1, function() end)
			local c3 = Concur.event(bindableEvent.Event)
			local c = Concur.all({ c1, c2, c3 })
			c:Stop()
			local err, val = c:Await()
			ctx:Expect(err):ToBe(Concur.Errors.Stopped)
			ctx:Expect(val):ToBeNil()
		end)

		ctx:Test("should not stop an already completed concur", function()
			local c1 = Concur.spawn(function()
				return 10
			end)
			ctx:Expect(c1:IsCompleted()):ToBe(true)
			c1:Stop()
			local err, val = c1:Await()
			ctx:Expect(err):ToBeNil()
			ctx:Expect(val):ToBe(10)
		end)
	end)

	ctx:Describe("IsCompleted", function()
		ctx:Test("should correctly check if a concur instance is completed", function()
			local c1 = Concur.defer(function() end)
			ctx:Expect(c1:IsCompleted()):ToBe(false)
			local err = c1:Await()
			ctx:Expect(err):ToBeNil()
			ctx:Expect(c1:IsCompleted()):ToBe(true)
		end)

		ctx:Test("should be marked as completed if error", function()
			local c1 = Concur.spawn(function()
				error("err")
			end)
			ctx:Expect(c1:IsCompleted()):ToBe(true)
		end)

		ctx:Test("should be marked as completed if stopped", function()
			local c1 = Concur.defer(function() end)
			c1:Stop()
			ctx:Expect(c1:IsCompleted()):ToBe(true)
		end)
	end)

	ctx:Describe("Await", function()
		ctx:Test("should await concur to be completed", function()
			local c1 = Concur.defer(function()
				return 10
			end)
			local err, val = c1:Await(1)
			ctx:Expect(err):ToBeNil()
			ctx:Expect(val):ToBe(10)
		end)

		ctx:Test("should await concur to be completed even if error", function()
			local c1 = Concur.defer(function()
				return error("err")
			end)
			local err, val = c1:Await(1)
			ctx:Expect(err):ToBeOk()
			ctx:Expect(val):ToBeNil()
		end)

		ctx:Test("should await concur to be completed even if stopped", function()
			local c1 = Concur.delay(0.1, function()
				return 10
			end)
			task.defer(function()
				c1:Stop()
			end)
			local err, val = c1:Await(1)
			ctx:Expect(err):ToBe(Concur.Errors.Stopped)
			ctx:Expect(val):ToBeNil()
		end)

		ctx:Test("should return completed values immediately if already completed", function()
			local c1 = Concur.spawn(function()
				return 10
			end)
			ctx:Expect(c1:IsCompleted()):ToBe(true)
			local err, val = c1:Await()
			ctx:Expect(err):ToBeNil()
			ctx:Expect(val):ToBe(10)
		end)

		ctx:Test("should timeout", function()
			local c1 = Concur.delay(0.2, function()
				return 10
			end)
			local err, val = c1:Await(0.1)
			ctx:Expect(err):ToBe(Concur.Errors.Timeout)
			ctx:Expect(val):ToBeNil()
			err, val = c1:Await()
			ctx:Expect(err):ToBeNil()
			ctx:Expect(val):ToBe(10)
		end)
	end)

	ctx:Describe("OnCompleted", function()
		ctx:Test("should fire function once completed", function()
			local awaiter = Awaiter(0.1)
			local c1 = Concur.defer(function()
				return 10
			end)
			ctx:Expect(c1:IsCompleted()):ToBe(false)
			c1:OnCompleted(function(err, val)
				awaiter.Resume(err, val)
			end)
			local err, val = awaiter.Yield()
			ctx:Expect(err):ToBeNil()
			ctx:Expect(val):ToBe(10)
		end)

		ctx:Test("should fire function even if already completed", function()
			local c1 = Concur.spawn(function()
				return 10
			end)
			ctx:Expect(c1:IsCompleted()):ToBe(true)
			local err, val
			c1:OnCompleted(function(e, v)
				err, val = e, v
			end)
			ctx:Expect(err):ToBeNil()
			ctx:Expect(val):ToBe(10)
		end)

		ctx:Test("should fire function even if error", function()
			local awaiter = Awaiter(0.1)
			local c1 = Concur.defer(function()
				error("err")
			end)
			c1:OnCompleted(function(err, val)
				awaiter.Resume(err, val)
			end)
			local err, val = awaiter.Yield()
			ctx:Expect(err):ToBeOk()
			ctx:Expect(val):ToBeNil()
		end)

		ctx:Test("should fire function even if stopped", function()
			local awaiter = Awaiter(0.2)
			local c1 = Concur.delay(0.1, function()
				error("err")
			end)
			c1:OnCompleted(function(err, val)
				awaiter.Resume(err, val)
			end)
			task.defer(function()
				c1:Stop()
			end)
			local err, val = awaiter.Yield()
			ctx:Expect(err):ToBe(Concur.Errors.Stopped)
			ctx:Expect(val):ToBeNil()
		end)

		ctx:Test("should fire function even if timeout", function()
			local awaiter = Awaiter(0.5)
			local c1 = Concur.delay(0.2, function()
				error("err")
			end)
			c1:OnCompleted(function(err, val)
				awaiter.Resume(err, val)
			end, 0.1)
			local err, val = awaiter.Yield()
			ctx:Expect(err):ToBe(Concur.Errors.Timeout)
			ctx:Expect(val):ToBeNil()
		end)

		ctx:Test("should unbind function", function()
			local c1 = Concur.defer(function() end)
			local val = nil
			local unbind = c1:OnCompleted(function()
				val = 10
			end)
			unbind()
			local err = c1:Await()
			ctx:Expect(err):ToBeNil()
			task.wait()
			ctx:Expect(val):ToBeNil()
		end)
	end)
end


-- EnumList
-- Stephen Leitnick
-- January 08, 2021

type EnumNames = { string }

--[=[
	@interface EnumItem
	.Name string
	.Value number
	.EnumType EnumList
	@within EnumList
]=]
export type EnumItem = {
	Name: string,
	Value: number,
	EnumType: any,
}

local LIST_KEY = newproxy()
local NAME_KEY = newproxy()

local function CreateEnumItem(name: string, value: number, enum: any): EnumItem
	local enumItem = {
		Name = name,
		Value = value,
		EnumType = enum,
	}
	table.freeze(enumItem)
	return enumItem
end

--[=[
	@class EnumList
	Defines a new Enum.
]=]
local EnumList = {}
EnumList.__index = EnumList

--[=[
	@param name string
	@param enums {string}
	@return EnumList
	Constructs a new EnumList.

	```lua
	local directions = EnumList.new("Directions", {
		"Up",
		"Down",
		"Left",
		"Right",
	})

	local direction = directions.Up
	```
]=]
function EnumList.new(name: string, enums: EnumNames)
	assert(type(name) == "string", "Name string required")
	assert(type(enums) == "table", "Enums table required")
	local self = {}
	self[LIST_KEY] = {}
	self[NAME_KEY] = name
	for i, enumName in ipairs(enums) do
		assert(type(enumName) == "string", "Enum name must be a string")
		local enumItem = CreateEnumItem(enumName, i, self)
		self[enumName] = enumItem
		table.insert(self[LIST_KEY], enumItem)
	end
	return table.freeze(setmetatable(self, EnumList))
end

--[=[
	@param obj any
	@return boolean
	Returns `true` if `obj` belongs to the EnumList.
]=]
function EnumList:BelongsTo(obj: any): boolean
	return type(obj) == "table" and obj.EnumType == self
end

--[=[
	Returns an array of all enum items.
	@return {EnumItem}
	@since v2.0.0
]=]
function EnumList:GetEnumItems()
	return self[LIST_KEY]
end

--[=[
	Get the name of the enum.
	@return string
	@since v2.0.0
]=]
function EnumList:GetName()
	return self[NAME_KEY]
end

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

return EnumList


local ServerScriptService = game:GetService("ServerScriptService")

local Test = require(ServerScriptService.TestRunner.Test)

return function(ctx: Test.TestContext)
	local EnumList = require(script.Parent)

	ctx:Describe("Constructor", function()
		ctx:Test("should create a new enumlist", function()
			ctx:Expect(function()
				EnumList.new("Test", { "ABC", "XYZ" })
			end)
				:Not()
				:ToThrow()
		end)

		ctx:Test("should fail to create a new enumlist with no name", function()
			ctx:Expect(function()
				EnumList.new(nil, { "ABC", "XYZ" })
			end):ToThrow()
		end)

		ctx:Test("should fail to create a new enumlist with no enums", function()
			ctx:Expect(function()
				EnumList.new("Test")
			end):ToThrow()
		end)

		ctx:Test("should fail to create a new enumlist with non string enums", function()
			ctx:Expect(function()
				EnumList.new("Test", { true, false, 32, "ABC" })
			end):ToThrow()
		end)
	end)

	ctx:Describe("Access", function()
		ctx:Test("should be able to access enum items", function()
			local test = EnumList.new("Test", { "ABC", "XYZ" })
			ctx:Expect(function()
				local _item = test.ABC
			end)
				:Not()
				:ToThrow()
			ctx:Expect(test:BelongsTo(test.ABC)):ToBe(true)
		end)

		ctx:Test("should throw if trying to modify the enumlist", function()
			local test = EnumList.new("Test", { "ABC", "XYZ" })
			ctx:Expect(function()
				test.Hello = 32
			end):ToThrow()
			ctx:Expect(function()
				test.ABC = 32
			end):ToThrow()
		end)

		ctx:Test("should throw if trying to modify an enumitem", function()
			local test = EnumList.new("Test", { "ABC", "XYZ" })
			ctx:Expect(function()
				local abc = test.ABC
				abc.XYZ = 32
			end):ToThrow()
			ctx:Expect(function()
				local abc = test.ABC
				abc.Name = "NewName"
			end):ToThrow()
		end)

		ctx:Test("should get the name", function()
			local test = EnumList.new("Test", { "ABC", "XYZ" })
			local name = test:GetName()
			ctx:Expect(name):ToBe("Test")
		end)
	end)

	ctx:Describe("Get Items", function()
		ctx:Test("should be able to get all enum items", function()
			local test = EnumList.new("Test", { "ABC", "XYZ" })
			local items = test:GetEnumItems()
			ctx:Expect(items):ToBeA("table")
			ctx:Expect(#items):ToBe(2)
			for i, enumItem in ipairs(items) do
				ctx:Expect(enumItem):ToBeA("table")
				ctx:Expect(enumItem.Name):ToBeA("string")
				ctx:Expect(enumItem.Value):ToBeA("number")
				ctx:Expect(enumItem.Value):ToBe(i)
				ctx:Expect(enumItem.EnumType):ToBe(test)
			end
		end)
	end)
end


--!strict

--[=[
	@class Find

	A utility function for finding objects in the data model hierarchy.

	Similar to `FindFirstChild`, except it explicitly errors if any object
	is not found, as well as a more helpful message as to what wasn't found.

	```lua
	local find = require(ReplicatedStorage.Packages.find)

	-- Find instance "workspace.Some.Folder.Here.Item":
	local item = find(workspace, "Some", "Folder", "Here", "Item")
	```

	In the above example, if "Folder" didn't exist, the function would throw an error with the message: `failed to find instance "Folder" within "Workspace.Some"`.

	The return type is simply `Instance`. Any type-checking should be done on the return value:
	```lua
	local part = find(workspace, "SomePart") :: BasePart -- Blindly assume and type this as a BasePart
	assert(part:IsA("BasePart")) -- Extra optional precaution to ensure type
	```
]=]

local function find(parent: Instance, ...: string): Instance
	local instance = parent

	for i = 1, select("#", ...) do
		local name = (select(i, ...))

		local inst = instance:FindFirstChild(name)
		if inst == nil then
			error(`failed to find instance "{name}" within {instance:GetFullName()}`, 2)
		end

		instance = inst
	end

	return instance
end

return find


-- Gamepad
-- Stephen Leitnick
-- December 23, 2021

local Signal = require(script.Parent.Parent.Signal)
local Trove = require(script.Parent.Parent.Trove)

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

local function ApplyDeadzone(value: number, threshold: number): number
	if math.abs(value) < threshold then
		return 0
	end
	return ((math.abs(value) - threshold) / (1 - threshold)) * math.sign(value)
end

local function GetActiveGamepad(): Enum.UserInputType?
	local activeGamepad = nil
	local navGamepads = UserInputService:GetNavigationGamepads()
	if #navGamepads > 1 then
		for _, navGamepad in navGamepads do
			if activeGamepad == nil or navGamepad.Value < activeGamepad.Value then
				activeGamepad = navGamepad
			end
		end
	else
		local connectedGamepads = UserInputService:GetConnectedGamepads()
		for _, connectedGamepad in connectedGamepads do
			if activeGamepad == nil or connectedGamepad.Value < activeGamepad.Value then
				activeGamepad = connectedGamepad
			end
		end
	end
	if activeGamepad and not UserInputService:GetGamepadConnected(activeGamepad) then
		activeGamepad = nil
	end
	return activeGamepad
end

local function HeartbeatDelay(duration: number, callback: () -> nil): RBXScriptConnection
	local start = time()
	local connection
	connection = RunService.Heartbeat:Connect(function()
		local elapsed = time() - start
		if elapsed >= duration then
			connection:Disconnect()
			callback()
		end
	end)
	return connection
end

--[=[
	@class Gamepad
	@client

	The Gamepad class is part of the Input package.

	```lua
	local Gamepad = require(packages.Input).Gamepad

	local gamepad = Gamepad.new()
	```
]=]
local Gamepad = {}
Gamepad.__index = Gamepad

--[=[
	@within Gamepad
	@prop ButtonDown Signal<(button: Enum.KeyCode, processed: boolean)>
	@readonly
	The ButtonDown signal fires when a gamepad button is pressed
	down. The pressed KeyCode is passed to the signal, along with
	whether or not the event was processed.

	```lua
	gamepad.ButtonDown:Connect(function(button: Enum.KeyCode, processed: boolean)
		print("Button down", button, processed)
	end)
	```
]=]

--[=[
	@within Gamepad
	@prop ButtonUp Signal<(button: Enum.KeyCode, processed: boolean)>
	@readonly
	The ButtonUp signal fires when a gamepad button is released.
	The released KeyCode is passed to the signal, along with
	whether or not the event was processed.

	```lua
	gamepad.ButtonUp:Connect(function(button: Enum.KeyCode, processed: boolean)
		print("Button up", button, processed)
	end)
	```
]=]

--[=[
	@within Gamepad
	@prop Connected Signal
	@readonly
	Fires when the gamepad is connected. This will _not_ fire if the
	active gamepad is switched. To detect switching to different
	active gamepads, use the `GamepadChanged` signal.

	There is also a `gamepad:IsConnected()` method.

	```lua
	gamepad.Connected:Connect(function()
		print("Connected")
	end)
	```
]=]

--[=[
	@within Gamepad
	@prop Disconnected Signal
	@readonly
	Fires when the gamepad is disconnected. This will _not_ fire if the
	active gamepad is switched. To detect switching to different
	active gamepads, use the `GamepadChanged` signal.

	There is also a `gamepad:IsConnected()` method.

	```lua
	gamepad.Disconnected:Connect(function()
		print("Disconnected")
	end)
	```
]=]

--[=[
	@within Gamepad
	@prop GamepadChanged Signal<gamepad: Enum.UserInputType>
	@readonly
	Fires when the active gamepad switches. Internally, the gamepad
	object will always wrap around the active gamepad, so nothing
	needs to be changed.

	```lua
	gamepad.GamepadChanged:Connect(function(newGamepad: Enum.UserInputType)
		print("Active gamepad changed to:", newGamepad)
	end)
	```
]=]

--[=[
	@within Gamepad
	@prop DefaultDeadzone number

	:::info Default
	Defaults to `0.05`
	:::

	The default deadzone used for trigger and thumbstick
	analog readings. It is usually best to set this to
	a small value, or allow players to set this option
	themselves in an in-game settings menu.

	The `GetThumbstick` and `GetTrigger` methods also allow
	a deadzone value to be passed in, which overrides this
	value.
]=]

--[=[
	@within Gamepad
	@prop SupportsVibration boolean
	@readonly
	Flag to indicate if the currently-active gamepad supports
	haptic motor vibration.

	It is safe to use the motor methods on the gamepad without
	checking this value, but nothing will happen if the motors
	are not supported.
]=]

--[=[
	@within Gamepad
	@prop State GamepadState
	@readonly
	Maps KeyCodes to the matching InputObjects within the gamepad.
	These can be used to directly read the current input state of
	a given part of the gamepad. For most cases, the given methods
	and properties of `Gamepad` should make use of this table quite
	rare, but it is provided for special use-cases that might occur.

	:::note Do Not Cache
	These state objects will change if the active gamepad changes.
	Because a player might switch up gamepads during playtime, it cannot
	be assumed that these state objects will always be the same. Thus
	they should be accessed directly from this `State` table anytime they
	need to be used.
	:::

	```lua
	local leftThumbstick = gamepad.State[Enum.KeyCode.Thumbstick1]
	print(leftThumbstick.Position)
	-- It would be better to use gamepad:GetThumbstick(Enum.KeyCode.Thumbstick1),
	-- but this is just an example of direct state access.
	```
]=]

--[=[
	@within Gamepad
	@type GamepadState {[Enum.KeyCode]: InputObject}
]=]

--[=[
	@param gamepad Enum.UserInputType?
	@return Gamepad
	Constructs a gamepad object.

	If no gamepad UserInputType is provided, this object will always wrap
	around the currently-active gamepad, even if it changes. In most cases
	where input is needed from just the primary gamepad used by the player,
	leaving the `gamepad` argument blank is preferred.

	Only include the `gamepad` argument when it is necessary to hard-lock
	the object to a specific gamepad input type.

	```lua
	-- In most cases, construct the gamepad as such:
	local gamepad = Gamepad.new()

	-- If the exact UserInputType gamepad is needed, pass it as such:
	local gamepad = Gamepad.new(Enum.UserInputType.Gamepad1)
	```
]=]
function Gamepad.new(gamepad: Enum.UserInputType?)
	local self = setmetatable({}, Gamepad)

	self._trove = Trove.new()
	self._gamepadTrove = self._trove:Construct(Trove)
	self.ButtonDown = self._trove:Construct(Signal)
	self.ButtonUp = self._trove:Construct(Signal)
	self.Connected = self._trove:Construct(Signal)
	self.Disconnected = self._trove:Construct(Signal)
	self.GamepadChanged = self._trove:Construct(Signal)
	self.DefaultDeadzone = 0.05
	self.SupportsVibration = false
	self.State = {}

	self:_setupGamepad(gamepad)
	self:_setupMotors()

	return self
end

function Gamepad:_setupActiveGamepad(gamepad: Enum.UserInputType?)
	local lastGamepad = self._gamepad
	if gamepad == lastGamepad then
		return
	end

	self._gamepadTrove:Clean()
	table.clear(self.State)
	self.SupportsVibration = if gamepad then HapticService:IsVibrationSupported(gamepad) else false

	self._gamepad = gamepad

	-- Stop if disconnected:
	if not gamepad then
		self.Disconnected:Fire()
		self.GamepadChanged:Fire(nil)
		return
	end

	for _, inputObject in UserInputService:GetGamepadState(gamepad) do
		self.State[inputObject.KeyCode] = inputObject
	end

	self._gamepadTrove:Add(self, "StopMotors")

	self._gamepadTrove:Connect(UserInputService.InputBegan, function(input, processed)
		if input.UserInputType == gamepad then
			self.ButtonDown:Fire(input.KeyCode, processed)
		end
	end)

	self._gamepadTrove:Connect(UserInputService.InputEnded, function(input, processed)
		if input.UserInputType == gamepad then
			self.ButtonUp:Fire(input.KeyCode, processed)
		end
	end)

	if lastGamepad == nil then
		self.Connected:Fire()
	end
	self.GamepadChanged:Fire(gamepad)
end

function Gamepad:_setupGamepad(forcedGamepad: Enum.UserInputType?)
	if forcedGamepad then
		-- Forced gamepad:

		self._trove:Connect(UserInputService.GamepadConnected, function(gp)
			if gp == forcedGamepad then
				self:_setupActiveGamepad(forcedGamepad)
			end
		end)

		self._trove:Connect(UserInputService.GamepadDisconnected, function(gp)
			if gp == forcedGamepad then
				self:_setupActiveGamepad(nil)
			end
		end)

		if UserInputService:GetGamepadConnected(forcedGamepad) then
			self:_setupActiveGamepad(forcedGamepad)
		end
	else
		-- Dynamic gamepad:

		local function CheckToSetupActive()
			local active = GetActiveGamepad()
			if active ~= self._gamepad then
				self:_setupActiveGamepad(active)
			end
		end

		self._trove:Connect(UserInputService.GamepadConnected, CheckToSetupActive)
		self._trove:Connect(UserInputService.GamepadDisconnected, CheckToSetupActive)
		self:_setupActiveGamepad(GetActiveGamepad())
	end
end

function Gamepad:_setupMotors()
	self._setMotorIds = {}
	for _, motor in Enum.VibrationMotor:GetEnumItems() do
		self._setMotorIds[motor] = 0
	end
end

--[=[
	@param thumbstick Enum.KeyCode
	@param deadzoneThreshold number?
	@return Vector2
	Gets the position of the given thumbstick. The two thumbstick
	KeyCodes are `Enum.KeyCode.Thumbstick1` and `Enum.KeyCode.Thumbstick2`.

	If `deadzoneThreshold` is not included, the `DefaultDeadzone` value is
	used instead.

	```lua
	local leftThumbstick = gamepad:GetThumbstick(Enum.KeyCode.Thumbstick1)
	print("Left thumbstick position", leftThumbstick)
	```
]=]
function Gamepad:GetThumbstick(thumbstick: Enum.KeyCode, deadzoneThreshold: number?): Vector2
	local pos = self.State[thumbstick].Position
	local deadzone = deadzoneThreshold or self.DefaultDeadzone
	return Vector2.new(ApplyDeadzone(pos.X, deadzone), ApplyDeadzone(pos.Y, deadzone))
end

--[=[
	@param trigger KeyCode
	@param deadzoneThreshold number?
	@return number
	Gets the position of the given trigger. The triggers are usually going
	to be `Enum.KeyCode.ButtonL2` and `Enum.KeyCode.ButtonR2`. These trigger
	buttons are analog, and will output a value between the range of [0, 1].

	If `deadzoneThreshold` is not included, the `DefaultDeadzone` value is
	used instead.

	```lua
	local triggerAmount = gamepad:GetTrigger(Enum.KeyCode.ButtonR2)
	print(triggerAmount)
	```
]=]
function Gamepad:GetTrigger(trigger: Enum.KeyCode, deadzoneThreshold: number?): number
	return ApplyDeadzone(self.State[trigger].Position.Z, deadzoneThreshold or self.DefaultDeadzone)
end

--[=[
	@param gamepadButton KeyCode
	@return boolean
	Returns `true` if the given button is down. This includes
	any button on the gamepad, such as `Enum.KeyCode.ButtonA`,
	`Enum.KeyCode.ButtonL3`, `Enum.KeyCode.DPadUp`, etc.

	```lua
	-- Check if the 'A' button is down:
	if gamepad:IsButtonDown(Enum.KeyCode.ButtonA) then
		print("ButtonA is down")
	end
	```
]=]
function Gamepad:IsButtonDown(gamepadButton: Enum.KeyCode): boolean
	return UserInputService:IsGamepadButtonDown(self._gamepad, gamepadButton)
end

--[=[
	@param motor Enum.VibrationMotor
	@return boolean
	Returns `true` if the given motor is supported.

	```lua
	-- Pulse the trigger (e.g. shooting a weapon), but fall back to
	-- the large motor if not supported:
	local motor = Enum.VibrationMotor.Large
	if gamepad:IsMotorSupported(Enum.VibrationMotor.RightTrigger) then
		motor = Enum.VibrationMotor.RightTrigger
	end
	gamepad:PulseMotor(motor, 1, 0.1)
	```
]=]
function Gamepad:IsMotorSupported(motor: Enum.VibrationMotor): boolean
	return HapticService:IsMotorSupported(self._gamepad, motor)
end

--[=[
	@param motor Enum.VibrationMotor
	@param intensity number
	Sets the gamepad's haptic motor to a certain intensity. The
	intensity value is a number in the range of [0, 1].

	```lua
	gamepad:SetMotor(Enum.VibrationMotor.Large, 0.5)
	```
]=]
function Gamepad:SetMotor(motor: Enum.VibrationMotor, intensity: number): number
	self._setMotorIds[motor] += 1
	local id = self._setMotorIds[motor]
	HapticService:SetMotor(self._gamepad, motor, intensity)

	return id
end

--[=[
	@param motor Enum.VibrationMotor
	@param intensity number
	@param duration number
	Sets the gamepad's haptic motor to a certain intensity for a given
	period of time. The motor will stop vibrating after the given
	`duration` has elapsed.

	Calling any motor setter methods (e.g. `SetMotor`, `PulseMotor`,
	`StopMotor`) _after_ calling this method will override the pulse.
	For instance, if `PulseMotor` is called, and then `SetMotor` is
	called right afterwards, `SetMotor` will take precedent.

	```lua
	-- Pulse the large motor for 0.2 seconds with an intensity of 90%:
	gamepad:PulseMotor(Enum.VibrationMotor.Large, 0.9, 0.2)

	-- Example of PulseMotor being overridden:
	gamepad:PulseMotor(Enum.VibrationMotor.Large, 1, 3)
	task.wait(0.1)
	gamepad:SetMotor(Enum.VibrationMotor.Large, 0.5)
	-- Now the pulse won't shut off the motor after 3 seconds,
	-- because SetMotor was called, which cancels the pulse.
	```
]=]
function Gamepad:PulseMotor(motor: Enum.VibrationMotor, intensity: number, duration: number)
	local id = self:SetMotor(motor, intensity)

	local heartbeat = HeartbeatDelay(duration, function()
		if self._setMotorIds[motor] ~= id then
			return
		end
		self:StopMotor(motor)
	end)

	self._gamepadTrove:Add(heartbeat)
end

--[=[
	@param motor Enum.VibrationMotor
	Stops the given motor. This is equivalent to calling
	`gamepad:SetMotor(motor, 0)`.

	```lua
	gamepad:SetMotor(Enum.VibrationMotor.Large, 1)
	task.wait(0.1)
	gamepad:StopMotor(Enum.VibrationMotor.Large)
	```
]=]
function Gamepad:StopMotor(motor: Enum.VibrationMotor)
	self:SetMotor(motor, 0)
end

--[=[
	Stops all motors on the gamepad.

	```lua
	gamepad:SetMotor(Enum.VibrationMotor.Large, 1)
	gamepad:SetMotor(Enum.VibrationMotor.Small, 1)
	task.wait(0.1)
	gamepad:StopMotors()
	```
]=]
function Gamepad:StopMotors()
	for _, motor in Enum.VibrationMotor:GetEnumItems() do
		if self:IsMotorSupported(motor) then
			self:StopMotor(motor)
		end
	end
end

--[=[
	@return boolean
	Returns `true` if the gamepad is currently connected.
]=]
function Gamepad:IsConnected(): boolean
	return if self._gamepad then UserInputService:GetGamepadConnected(self._gamepad) else false
end

--[=[
	@return Enum.UserInputType?
	Gets the current gamepad UserInputType that the gamepad object
	is using. This will be `nil` if there is no connected gamepad.
]=]
function Gamepad:GetUserInputType(): Enum.UserInputType?
	return self._gamepad
end

--[=[
	@param enabled boolean
	Sets the [`GuiService.AutoSelectGuiEnabled`](https://developer.roblox.com/en-us/api-reference/property/GuiService/AutoSelectGuiEnabled)
	property.

	This sets whether or not the Select button on a gamepad will try to auto-select
	a GUI object on screen. This does _not_ turn on/off GUI gamepad navigation,
	but just the initial selection using the Select button.

	For UX purposes, it usually is preferred to set this to `false` and then
	manually set the [`GuiService.SelectedObject`](https://developer.roblox.com/en-us/api-reference/property/GuiService/SelectedObject)
	property within code to set the selected object for gamepads.

	```lua
	gamepad:SetAutoSelectGui(false)
	game:GetService("GuiService").SelectedObject = someGuiObject
	```
]=]
function Gamepad:SetAutoSelectGui(enabled: boolean)
	GuiService.AutoSelectGuiEnabled = enabled
end

--[=[
	@return boolean
	Returns the [`GuiService.AutoSelectGuiEnabled`](https://developer.roblox.com/en-us/api-reference/property/GuiService/AutoSelectGuiEnabled)
	property.
]=]
function Gamepad:IsAutoSelectGuiEnabled(): boolean
	return GuiService.AutoSelectGuiEnabled
end

--[=[
	Destroys the gamepad object.
]=]
function Gamepad:Destroy()
	self._trove:Destroy()
end

return Gamepad


-- Keyboard
-- Stephen Leitnick
-- October 10, 2021

local Signal = require(script.Parent.Parent.Signal)
local Trove = require(script.Parent.Parent.Trove)

local UserInputService = game:GetService("UserInputService")

--[=[
	@class Keyboard
	@client

	The Keyboard class is part of the Input package.

	```lua
	local Keyboard = require(packages.Input).Keyboard
	```
]=]
local Keyboard = {}
Keyboard.__index = Keyboard

--[=[
	@within Keyboard
	@prop KeyDown Signal<Enum.KeyCode, boolean>
	@tag Event
	Fired when a key is pressed.
	```lua
	keyboard.KeyDown:Connect(function(key: KeyCode)
		print("Key pressed", key)
	end)
	```
]=]
--[=[
	@within Keyboard
	@prop KeyUp Signal<Enum.KeyCode, boolean>
	@tag Event
	Fired when a key is released.
	```lua
	keyboard.KeyUp:Connect(function(key: KeyCode)
		print("Key released", key)
	end)
	```
]=]

--[=[
	@return Keyboard

	Constructs a new keyboard input capturer.

	```lua
	local keyboard = Keyboard.new()
	```
]=]
function Keyboard.new()
	local self = setmetatable({}, Keyboard)
	self._trove = Trove.new()
	self.KeyDown = self._trove:Construct(Signal)
	self.KeyUp = self._trove:Construct(Signal)
	self:_setup()
	return self
end

--[=[
	Check if the given key is down.

	```lua
	local w = keyboard:IsKeyDown(Enum.KeyCode.W)
	if w then ... end
	```
]=]
function Keyboard:IsKeyDown(keyCode: Enum.KeyCode): boolean
	return UserInputService:IsKeyDown(keyCode)
end

--[=[
	Check if _both_ keys are down. Useful for key combinations.

	```lua
	local shiftA = keyboard:AreKeysDown(Enum.KeyCode.LeftShift, Enum.KeyCode.A)
	if shiftA then ... end
	```
]=]
function Keyboard:AreKeysDown(keyCodeOne: Enum.KeyCode, keyCodeTwo: Enum.KeyCode): boolean
	return self:IsKeyDown(keyCodeOne) and self:IsKeyDown(keyCodeTwo)
end

--[=[
	Check if _either_ of the keys are down. Useful when two keys might perform
	the same operation.

	```lua
	local wOrUp = keyboard:AreEitherKeysDown(Enum.KeyCode.W, Enum.KeyCode.Up)
	if wOrUp then
		-- Go forward
	end
	```
]=]
function Keyboard:AreEitherKeysDown(keyCodeOne: Enum.KeyCode, keyCodeTwo: Enum.KeyCode): boolean
	return self:IsKeyDown(keyCodeOne) or self:IsKeyDown(keyCodeTwo)
end

function Keyboard:_setup()
	self._trove:Connect(UserInputService.InputBegan, function(input, processed)
		if input.UserInputType == Enum.UserInputType.Keyboard then
			self.KeyDown:Fire(input.KeyCode, processed)
		end
	end)

	self._trove:Connect(UserInputService.InputEnded, function(input, processed)
		if input.UserInputType == Enum.UserInputType.Keyboard then
			self.KeyUp:Fire(input.KeyCode, processed)
		end
	end)
end

--[=[
	Destroy the keyboard input capturer.
]=]
function Keyboard:Destroy()
	self._trove:Destroy()
end

return Keyboard


-- Mouse
-- Stephen Leitnick
-- November 07, 2020

local Signal = require(script.Parent.Parent.Signal)
local Trove = require(script.Parent.Parent.Trove)

local UserInputService = game:GetService("UserInputService")

local RAY_DISTANCE = 1000

--[=[
	@class Mouse
	@client

	The Mouse class is part of the Input package.

	```lua
	local Mouse = require(packages.Input).Mouse
	```
]=]
local Mouse = {}
Mouse.__index = Mouse

--[=[
	@within Mouse
	@prop LeftDown Signal<boolean>
	@tag Event
]=]
--[=[
	@within Mouse
	@prop LeftUp Signal<boolean>
	@tag Event
]=]
--[=[
	@within Mouse
	@prop RightDown Signal<boolean>
	@tag Event
]=]
--[=[
	@within Mouse
	@prop RightUp Signal<boolean>
	@tag Event
]=]
--[=[
	@within Mouse
	@prop MiddleDown Signal<boolean>
	@tag Event
]=]
--[=[
	@within Mouse
	@prop MiddleUp Signal<boolean>
	@tag Event
]=]
--[=[
	@within Mouse
	@prop Moved Signal<Vector2, boolean>
	@tag Event
	```lua
	mouse.Moved:Connect(function(position, processed) ... end)
	```
]=]
--[=[
	@within Mouse
	@prop Scrolled Signal<number, boolean>
	@tag Event
	```lua
	mouse.Scrolled:Connect(function(scrollAmount, processed) ... end)
	```
]=]

--[=[
	@return Mouse

	Constructs a new mouse input capturer.

	```lua
	local mouse = Mouse.new()
	```
]=]
function Mouse.new()
	local self = setmetatable({}, Mouse)

	self._trove = Trove.new()

	self.LeftDown = self._trove:Construct(Signal)
	self.LeftUp = self._trove:Construct(Signal)
	self.RightDown = self._trove:Construct(Signal)
	self.RightUp = self._trove:Construct(Signal)
	self.MiddleDown = self._trove:Construct(Signal)
	self.MiddleUp = self._trove:Construct(Signal)
	self.Scrolled = self._trove:Construct(Signal)
	self.Moved = self._trove:Construct(Signal)

	self._trove:Connect(UserInputService.InputBegan, function(input, processed)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			self.LeftDown:Fire(processed)
		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
			self.RightDown:Fire(processed)
		elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
			self.MiddleDown:Fire(processed)
		end
	end)

	self._trove:Connect(UserInputService.InputEnded, function(input, processed)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			self.LeftUp:Fire(processed)
		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
			self.RightUp:Fire(processed)
		elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
			self.MiddleUp:Fire(processed)
		end
	end)

	self._trove:Connect(UserInputService.InputChanged, function(input, processed)
		if input.UserInputType == Enum.UserInputType.MouseMovement then
			local position = input.Position
			self.Moved:Fire(Vector2.new(position.X, position.Y), processed)
		elseif input.UserInputType == Enum.UserInputType.MouseWheel then
			self.Scrolled:Fire(input.Position.Z, processed)
		end
	end)

	return self
end

--[=[
	Checks if the left mouse button is down.
]=]
function Mouse:IsLeftDown(): boolean
	return UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1)
end

--[=[
	Checks if the right mouse button is down.
]=]
function Mouse:IsRightDown(): boolean
	return UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2)
end

--[=[
	Checks if the middle mouse button is down.
]=]
function Mouse:IsMiddleDown(): boolean
	return UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton3)
end

--[=[
	Gets the screen position of the mouse.
]=]
function Mouse:GetPosition(): Vector2
	return UserInputService:GetMouseLocation()
end

--[=[
	Gets the delta screen position of the mouse. In other words, the
	distance the mouse has traveled away from its locked position in
	a given frame (see note about mouse locking below).

	:::info Only When Mouse Locked
	Getting the mouse delta is only intended for when the mouse is locked. If the
	mouse is _not_ locked, this will return a zero Vector2. The mouse can be locked
	using the `mouse:Lock()` and `mouse:LockCenter()` method.
	:::
]=]
function Mouse:GetDelta(): Vector2
	return UserInputService:GetMouseDelta()
end

--[=[
	Returns the viewport point ray for the mouse at the current mouse
	position (or the override position if provided).
]=]
function Mouse:GetRay(overridePos: Vector2?): Ray
	local mousePos = overridePos or UserInputService:GetMouseLocation()
	local viewportMouseRay = workspace.CurrentCamera:ViewportPointToRay(mousePos.X, mousePos.Y)
	return viewportMouseRay
end

--[=[
	Performs a raycast operation out from the mouse position (or the
	`overridePos` if provided) into world space. The ray will go
	`distance` studs forward (or 1000 studs if not provided).

	Returns the `RaycastResult` if something was hit, else returns `nil`.

	Use `Raycast` if it is important to capture any objects that could be
	hit along the projected ray. If objects can be ignored and only the
	final position of the ray is needed, use `Project` instead.

	```lua
	local params = RaycastParams.new()
	local result = mouse:Raycast(params)
	if result then
		print(result.Instance)
	else
		print("Mouse raycast did not hit anything")
	end
	```
]=]
function Mouse:Raycast(raycastParams: RaycastParams, distance: number?, overridePos: Vector2?): RaycastResult?
	local viewportMouseRay = self:GetRay(overridePos)
	local result = workspace:Raycast(
		viewportMouseRay.Origin,
		viewportMouseRay.Direction * (distance or RAY_DISTANCE),
		raycastParams
	)
	return result
end

--[=[
	Gets the 3D world position of the mouse when projected forward. This would be the
	end-position of a raycast if nothing was hit. Similar to `Raycast`, optional
	`distance` and `overridePos` arguments are allowed.

	Use `Project` if you want to get the 3D world position of the mouse at a given
	distance but don't care about any objects that could be in the way. It is much
	faster to project a position into 3D space than to do a full raycast operation.

	```lua
	local params = RaycastParams.new()
	local distance = 200

	local result = mouse:Raycast(params, distance)
	if result then
		-- Do something with result
	else
		-- Raycast failed, but still get the world position of the mouse:
		local worldPosition = mouse:Project(distance)
	end
	```
]=]
function Mouse:Project(distance: number?, overridePos: Vector2?): Vector3
	local viewportMouseRay = self:GetRay(overridePos)
	return viewportMouseRay.Origin + (viewportMouseRay.Direction.Unit * (distance or RAY_DISTANCE))
end

--[=[
	Locks the mouse in its current position on screen. Call `mouse:Unlock()`
	to unlock the mouse.

	:::caution Must explicitly unlock
	Be sure to explicitly call `mouse:Unlock()` before cleaning up the mouse.
	The `Destroy` method does _not_ unlock the mouse since there is no way
	to guarantee who "owns" the mouse lock.
	:::
]=]
function Mouse:Lock()
	UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
end

--[=[
	Locks the mouse in the center of the screen. Call `mouse:Unlock()`
	to unlock the mouse.

	:::caution Must explicitly unlock
	See cautionary in `Lock` method above.
	:::
]=]
function Mouse:LockCenter()
	UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
end

--[=[
	Unlocks the mouse.
]=]
function Mouse:Unlock()
	UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end

--[=[
	Destroys the mouse.
]=]
function Mouse:Destroy()
	self._trove:Destroy()
end

return Mouse


-- Touch
-- Stephen Leitnick
-- March 14, 2021

local Trove = require(script.Parent.Parent.Trove)
local Signal = require(script.Parent.Parent.Signal)

local UserInputService = game:GetService("UserInputService")

--[=[
	@class Touch
	@client

	The Touch class is part of the Input package.

	```lua
	local Touch = require(packages.Input).Touch
	```
]=]
local Touch = {}
Touch.__index = Touch

--[=[
	@within Touch
	@prop TouchTap Signal<(touchPositions: {Vector2}, processed: boolean)>
	@tag Event
	Proxy for [UserInputService.TouchTap](https://developer.roblox.com/en-us/api-reference/event/UserInputService/TouchTap).
]=]
--[=[
	@within Touch
	@prop TouchTapInWorld Signal<(position: Vector2, processed: boolean)>
	@tag Event
	Proxy for [UserInputService.TouchTapInWorld](https://developer.roblox.com/en-us/api-reference/event/UserInputService/TouchTapInWorld).
]=]
--[=[
	@within Touch
	@prop TouchMoved Signal<(touch: InputObject, processed: boolean)>
	@tag Event
	Proxy for [UserInputService.TouchMoved](https://developer.roblox.com/en-us/api-reference/event/UserInputService/TouchMoved).
]=]
--[=[
	@within Touch
	@prop TouchLongPress Signal<(touchPositions: {Vector2}, state: Enum.UserInputState, processed: boolean)>
	@tag Event
	Proxy for [UserInputService.TouchLongPress](https://developer.roblox.com/en-us/api-reference/event/UserInputService/TouchLongPress).
]=]
--[=[
	@within Touch
	@prop TouchPan Signal<(touchPositions: {Vector2}, totalTranslation: Vector2, velocity: Vector2, state: Enum.UserInputState, processed: boolean)>
	@tag Event
	Proxy for [UserInputService.TouchPan](https://developer.roblox.com/en-us/api-reference/event/UserInputService/TouchPan).
]=]
--[=[
	@within Touch
	@prop TouchPinch Signal<(touchPositions: {Vector2}, scale: number, velocity: Vector2, state: Enum.UserInputState, processed: boolean)>
	@tag Event
	Proxy for [UserInputService.TouchPinch](https://developer.roblox.com/en-us/api-reference/event/UserInputService/TouchPinch).
]=]
--[=[
	@within Touch
	@prop TouchRotate Signal<(touchPositions: {Vector2}, rotation: number, velocity: number, state: Enum.UserInputState, processed: boolean)>
	@tag Event
	Proxy for [UserInputService.TouchRotate](https://developer.roblox.com/en-us/api-reference/event/UserInputService/TouchRotate).
]=]
--[=[
	@within Touch
	@prop TouchSwipe Signal<(swipeDirection: Enum.SwipeDirection, numberOfTouches: number, processed: boolean)>
	@tag Event
	Proxy for [UserInputService.TouchSwipe](https://developer.roblox.com/en-us/api-reference/event/UserInputService/TouchSwipe).
]=]
--[=[
	@within Touch
	@prop TouchStarted Signal<(touch: InputObject, processed: boolean)>
	@tag Event
	Proxy for [UserInputService.TouchStarted](https://developer.roblox.com/en-us/api-reference/event/UserInputService/TouchStarted).
]=]
--[=[
	@within Touch
	@prop TouchEnded Signal<(touch: InputObject, processed: boolean)>
	@tag Event
	Proxy for [UserInputService.TouchEnded](https://developer.roblox.com/en-us/api-reference/event/UserInputService/TouchEnded).
]=]

--[=[
	Constructs a new Touch input capturer.
]=]
function Touch.new()
	local self = setmetatable({}, Touch)

	self._trove = Trove.new()

	self.TouchTap = self._trove:Construct(Signal.Wrap, UserInputService.TouchTap)
	self.TouchTapInWorld = self._trove:Construct(Signal.Wrap, UserInputService.TouchTapInWorld)
	self.TouchMoved = self._trove:Construct(Signal.Wrap, UserInputService.TouchMoved)
	self.TouchLongPress = self._trove:Construct(Signal.Wrap, UserInputService.TouchLongPress)
	self.TouchPan = self._trove:Construct(Signal.Wrap, UserInputService.TouchPan)
	self.TouchPinch = self._trove:Construct(Signal.Wrap, UserInputService.TouchPinch)
	self.TouchRotate = self._trove:Construct(Signal.Wrap, UserInputService.TouchRotate)
	self.TouchSwipe = self._trove:Construct(Signal.Wrap, UserInputService.TouchSwipe)
	self.TouchStarted = self._trove:Construct(Signal.Wrap, UserInputService.TouchStarted)
	self.TouchEnded = self._trove:Construct(Signal.Wrap, UserInputService.TouchEnded)

	return self
end

--[=[
	Returns the value of [`UserInputService.TouchEnabled`](https://developer.roblox.com/en-us/api-reference/property/UserInputService/TouchEnabled).
]=]
function Touch:IsTouchEnabled(): boolean
	return UserInputService.TouchEnabled
end

--[=[
	Destroys the Touch input capturer.
]=]
function Touch:Destroy()
	self._trove:Destroy()
end

return Touch


-- Input
-- Stephen Leitnick
-- October 10, 2021

--[=[
	@class Input

	The Input package provides access to various user input classes.

	- [PreferredInput](/api/PreferredInput)
	- [Mouse](/api/Mouse)
	- [Keyboard](/api/Keyboard)
	- [Touch](/api/Touch)
	- [Gamepad](/api/Gamepad)

	Reference the desired input modules via the Input package to get started:

	```lua
	local PreferredInput = require(Packages.Input).PreferredInput
	local Mouse = require(Packages.Input).Mouse
	local Keyboard = require(Packages.Input).Keyboard
	local Touch = require(Packages.Input).Touch
	local Gamepad = require(Packages.Input).Gamepad
	```
]=]

return {
	PreferredInput = require(script.PreferredInput),
	Mouse = require(script.Mouse),
	Keyboard = require(script.Keyboard),
	Touch = require(script.Touch),
	Gamepad = require(script.Gamepad),
}


--[=[
	@class Loader

	The Loader module will require all children or descendant ModuleScripts. There are also
	some utility functions included, which assist with loading and starting modules in
	single-script environments.

	For example, here is a loader that loads all ModuleScripts under a folder that end with
	the name Service, and then calls all of their OnStart methods:
	```lua
	local MyModules = ReplicatedStorage.MyModules
	Loader.SpawnAll(
		Loader.LoadDescendants(MyModules, Loader.MatchesName("Service$")),
		"OnStart"
	)
	```
]=]
local Loader = {}

--[=[
	@within Loader
	@type PredicateFn (module: ModuleScript) -> boolean
	Predicate function type.
]=]
type PredicateFn = (module: ModuleScript) -> boolean

--[=[
	Requires all children ModuleScripts.

	If a `predicate` function is provided, then the module will only
	be loaded if the predicate returns `true` for the the given
	ModuleScript.

	```lua
	-- Load all ModuleScripts directly under MyModules:
	Loader.LoadChildren(ReplicatedStorage.MyModules)

	-- Load all ModuleScripts directly under MyModules if they have names ending in 'Service':
	Loader.LoadChildren(ReplicatedStorage.MyModules, function(moduleScript)
		return moduleScript.Name:match("Service$") ~= nil
	end)
	```
]=]
function Loader.LoadChildren(parent: Instance, predicate: PredicateFn?): { [string]: any }
	local modules: { [string]: any } = {}
	for _, child in parent:GetChildren() do
		if child:IsA("ModuleScript") then
			if predicate and not predicate(child) then
				continue
			end
			local m = require(child)
			modules[child.Name] = m
		end
	end
	return modules
end

--[=[
	Requires all descendant ModuleScripts.

	If a `predicate` function is provided, then the module will only
	be loaded if the predicate returns `true` for the the given
	ModuleScript.

	```lua
	-- Load all ModuleScripts under MyModules:
	Loader.LoadDescendants(ReplicatedStorage.MyModules)

	-- Load all ModuleScripts under MyModules if they have names ending in 'Service':
	Loader.LoadDescendants(ReplicatedStorage.MyModules, function(moduleScript)
		return moduleScript.Name:match("Service$") ~= nil
	end)
]=]
function Loader.LoadDescendants(parent: Instance, predicate: PredicateFn?): { [string]: any }
	local modules: { [string]: any } = {}
	for _, descendant in parent:GetDescendants() do
		if descendant:IsA("ModuleScript") then
			if predicate and not predicate(descendant) then
				continue
			end
			local m = require(descendant)
			modules[descendant.Name] = m
		end
	end
	return modules
end

--[=[
	A commonly-used predicate in the `LoadChildren` and `LoadDescendants`
	functions is one to match names. Therefore, the `MatchesName` utility
	function provides a quick way to create such predicates.

	```lua
	Loader.LoadDescendants(ReplicatedStorage.MyModules, Loader.MatchesName("Service$"))
	```
]=]
function Loader.MatchesName(matchName: string): (module: ModuleScript) -> boolean
	return function(moduleScript: ModuleScript): boolean
		return moduleScript.Name:match(matchName) ~= nil
	end
end

--[=[
	Utility function for spawning a specific method in all given modules.
	If a module does not contain the specified method, it is simply
	skipped. Methods are called with `task.spawn` internally.

	For example, if the modules are expected to have an `OnStart()` method,
	then `SpawnAll()` could be used to start all of them directly after
	they have been loaded:

	```lua
	local MyModules = ReplicatedStorage.MyModules

	-- Load all modules under MyModules and then call their OnStart methods:
	Loader.SpawnAll(Loader.LoadDescendants(MyModules), "OnStart")

	-- Same as above, but only loads modules with names that end with Service:
	Loader.SpawnAll(
		Loader.LoadDescendants(MyModules, Loader.MatchesName("Service$")),
		"OnStart"
	)
	```
]=]
function Loader.SpawnAll(loadedModules: { [string]: any }, methodName: string)
	for name, mod in loadedModules do
		local method = mod[methodName]
		if type(method) == "function" then
			task.spawn(function()
				debug.setmemorycategory(name)
				method(mod)
			end)
		end
	end
end

return Loader


local RunService = game:GetService("RunService")

--[=[
	@class Net
	Basic networking module for creating and handling static
	RemoteEvents and RemoteFunctions.
]=]
local Net = {}

--[=[
	Gets a RemoteEvent with the given name.

	On the server, if the RemoteEvent does not exist, then
	it will be created with the given name.

	On the client, if the RemoteEvent does not exist, then
	it will wait until it exists for at least 10 seconds.
	If the RemoteEvent does not exist after 10 seconds, an
	error will be thrown.

	```lua
	local remoteEvent = Net:RemoteEvent("PointsChanged")
	```
]=]
function Net:RemoteEvent(name: string): RemoteEvent
	name = "RE/" .. name
	if RunService:IsServer() then
		local r = script:FindFirstChild(name)
		if not r then
			r = Instance.new("RemoteEvent")
			r.Name = name
			r.Parent = script
		end
		return r
	else
		local r = script:WaitForChild(name, 10)
		if not r then
			error("Failed to find RemoteEvent: " .. name, 2)
		end
		return r
	end
end

--[=[
	Gets an UnreliableRemoteEvent with the given name.

	On the server, if the UnreliableRemoteEvent does not
	exist, then it will be created with the given name.

	On the client, if the UnreliableRemoteEvent does not
	exist, then it will wait until it exists for at least
	10 seconds. If the UnreliableRemoteEvent does not exist
	after 10 seconds, an error will be thrown.

	```lua
	local unreliableRemoteEvent = Net:UnreliableRemoteEvent("PositionChanged")
	```
]=]
function Net:UnreliableRemoteEvent(name: string): UnreliableRemoteEvent
	name = "URE/" .. name
	if RunService:IsServer() then
		local r = script:FindFirstChild(name)
		if not r then
			r = Instance.new("UnreliableRemoteEvent")
			r.Name = name
			r.Parent = script
		end
		return r
	else
		local r = script:WaitForChild(name, 10)
		if not r then
			error("Failed to find UnreliableRemoteEvent: " .. name, 2)
		end
		return r
	end
end

--[=[
	Connects a handler function to the given RemoteEvent.

	```lua
	-- Client
	Net:Connect("PointsChanged", function(points)
		print("Points", points)
	end)

	-- Server
	Net:Connect("SomeEvent", function(player, ...) end)
	```
]=]
function Net:Connect(name: string, handler: (...any) -> ()): RBXScriptConnection
	if RunService:IsServer() then
		return self:RemoteEvent(name).OnServerEvent:Connect(handler)
	else
		return self:RemoteEvent(name).OnClientEvent:Connect(handler)
	end
end

--[=[
	Connects a handler function to the given UnreliableRemoteEvent.

	```lua
	-- Client
	Net:ConnectUnreliable("PositionChanged", function(position)
		print("Position", position)
	end)

	-- Server
	Net:ConnectUnreliable("SomeEvent", function(player, ...) end)
	```
]=]
function Net:ConnectUnreliable(name: string, handler: (...any) -> ()): RBXScriptConnection
	if RunService:IsServer() then
		return self:UnreliableRemoteEvent(name).OnServerEvent:Connect(handler)
	else
		return self:UnreliableRemoteEvent(name).OnClientEvent:Connect(handler)
	end
end

--[=[
	Gets a RemoteFunction with the given name.

	On the server, if the RemoteFunction does not exist, then
	it will be created with the given name.

	On the client, if the RemoteFunction does not exist, then
	it will wait until it exists for at least 10 seconds.
	If the RemoteFunction does not exist after 10 seconds, an
	error will be thrown.

	```lua
	local remoteFunction = Net:RemoteFunction("GetPoints")
	```
]=]
function Net:RemoteFunction(name: string): RemoteFunction
	name = "RF/" .. name
	if RunService:IsServer() then
		local r = script:FindFirstChild(name)
		if not r then
			r = Instance.new("RemoteFunction")
			r.Name = name
			r.Parent = script
		end
		return r
	else
		local r = script:WaitForChild(name, 10)
		if not r then
			error("Failed to find RemoteFunction: " .. name, 2)
		end
		return r
	end
end

--[=[
	@server
	Sets the invocation function for the given RemoteFunction.

	```lua
	Net:Handle("GetPoints", function(player)
		return 10
	end)
	```
]=]
function Net:Handle(name: string, handler: (player: Player, ...any) -> ...any)
	self:RemoteFunction(name).OnServerInvoke = handler
end

--[=[
	@client
	Invokes the RemoteFunction with the given arguments.

	```lua
	local points = Net:Invoke("GetPoints")
	```
]=]
function Net:Invoke(name: string, ...: any): ...any
	return self:RemoteFunction(name):InvokeServer(...)
end

--[=[
	@server
	Destroys all RemoteEvents and RemoteFunctions. This
	should really only be used in testing environments
	and not during runtime.
]=]
function Net:Clean()
	script:ClearAllChildren()
end

return Net


-- Option
-- Stephen Leitnick
-- August 28, 2020

--[[

	MatchTable {
		Some: (value: any) -> any
		None: () -> any
	}

	CONSTRUCTORS:

		Option.Some(anyNonNilValue): Option<any>
		Option.Wrap(anyValue): Option<any>

	STATIC FIELDS:

		Option.None: Option<None>

	STATIC METHODS:

		Option.Is(obj): boolean

	METHODS:

		opt:Match(): (matches: MatchTable) -> any
		opt:IsSome(): boolean
		opt:IsNone(): boolean
		opt:Unwrap(): any
		opt:Expect(errMsg: string): any
		opt:ExpectNone(errMsg: string): void
		opt:UnwrapOr(default: any): any
		opt:UnwrapOrElse(default: () -> any): any
		opt:And(opt2: Option<any>): Option<any>
		opt:AndThen(predicate: (unwrapped: any) -> Option<any>): Option<any>
		opt:Or(opt2: Option<any>): Option<any>
		opt:OrElse(orElseFunc: () -> Option<any>): Option<any>
		opt:XOr(opt2: Option<any>): Option<any>
		opt:Contains(value: any): boolean

	--------------------------------------------------------------------

	Options are useful for handling nil-value cases. Any time that an
	operation might return nil, it is useful to instead return an
	Option, which will indicate that the value might be nil, and should
	be explicitly checked before using the value. This will help
	prevent common bugs caused by nil values that can fail silently.

	Example:

	local result1 = Option.Some(32)
	local result2 = Option.Some(nil)
	local result3 = Option.Some("Hi")
	local result4 = Option.Some(nil)
	local result5 = Option.None

	-- Use 'Match' to match if the value is Some or None:
	result1:Match {
		Some = function(value) print(value) end;
		None = function() print("No value") end;
	}

	-- Raw check:
	if result2:IsSome() then
		local value = result2:Unwrap() -- Explicitly call Unwrap
		print("Value of result2:", value)
	end

	if result3:IsNone() then
		print("No result for result3")
	end

	-- Bad, will throw error bc result4 is none:
	local value = result4:Unwrap()

--]]

export type MatchTable<T> = {
	Some: (value: T) -> any,
	None: () -> any,
}

export type MatchFn<T> = (matches: MatchTable<T>) -> any

export type DefaultFn<T> = () -> T

export type AndThenFn<T> = (value: T) -> Option<T>

export type OrElseFn<T> = () -> Option<T>

export type Option<T> = typeof(setmetatable(
	{} :: {
		Match: (self: Option<T>) -> MatchFn<T>,
		IsSome: (self: Option<T>) -> boolean,
		IsNone: (self: Option<T>) -> boolean,
		Contains: (self: Option<T>, value: T) -> boolean,
		Unwrap: (self: Option<T>) -> T,
		Expect: (self: Option<T>, errMsg: string) -> T,
		ExpectNone: (self: Option<T>, errMsg: string) -> nil,
		UnwrapOr: (self: Option<T>, default: T) -> T,
		UnwrapOrElse: (self: Option<T>, defaultFn: DefaultFn<T>) -> T,
		And: (self: Option<T>, opt2: Option<T>) -> Option<T>,
		AndThen: (self: Option<T>, predicate: AndThenFn<T>) -> Option<T>,
		Or: (self: Option<T>, opt2: Option<T>) -> Option<T>,
		OrElse: (self: Option<T>, orElseFunc: OrElseFn<T>) -> Option<T>,
		XOr: (self: Option<T>, opt2: Option<T>) -> Option<T>,
	},
	{} :: {
		__index: Option<T>,
	}
))

local CLASSNAME = "Option"

--[=[
	@class Option

	Represents an optional value in Lua. This is useful to avoid `nil` bugs, which can
	go silently undetected within code and cause hidden or hard-to-find bugs.
]=]
local Option = {}
Option.__index = Option

function Option._new(value)
	local self = setmetatable({
		ClassName = CLASSNAME,
		_v = value,
		_s = (value ~= nil),
	}, Option)
	return self
end

--[=[
	@param value T
	@return Option<T>

	Creates an Option instance with the given value. Throws an error
	if the given value is `nil`.
]=]
function Option.Some(value)
	assert(value ~= nil, "Option.Some() value cannot be nil")
	return Option._new(value)
end

--[=[
	@param value T
	@return Option<T> | Option<None>

	Safely wraps the given value as an option. If the
	value is `nil`, returns `Option.None`, otherwise
	returns `Option.Some(value)`.
]=]
function Option.Wrap(value)
	if value == nil then
		return Option.None
	else
		return Option.Some(value)
	end
end

--[=[
	@param obj any
	@return boolean
	Returns `true` if `obj` is an Option.
]=]
function Option.Is(obj)
	return type(obj) == "table" and getmetatable(obj) == Option
end

--[=[
	@param obj any
	Throws an error if `obj` is not an Option.
]=]
function Option.Assert(obj)
	assert(Option.Is(obj), "Result was not of type Option")
end

--[=[
	@param data table
	@return Option
	Deserializes the data into an Option. This data should have come from
	the `option:Serialize()` method.
]=]
function Option.Deserialize(data) -- type data = {ClassName: string, Value: any}
	assert(type(data) == "table" and data.ClassName == CLASSNAME, "Invalid data for deserializing Option")
	return data.Value == nil and Option.None or Option.Some(data.Value)
end

--[=[
	@return table
	Returns a serialized version of the option.
]=]
function Option:Serialize()
	return {
		ClassName = self.ClassName,
		Value = self._v,
	}
end

--[=[
	@param matches {Some: (value: any) -> any, None: () -> any}
	@return any

	Matches against the option.

	```lua
	local opt = Option.Some(32)
	opt:Match {
		Some = function(num) print("Number", num) end,
		None = function() print("No value") end,
	}
	```
]=]
function Option:Match(matches)
	local onSome = matches.Some
	local onNone = matches.None
	assert(type(onSome) == "function", "Missing 'Some' match")
	assert(type(onNone) == "function", "Missing 'None' match")
	if self:IsSome() then
		return onSome(self:Unwrap())
	else
		return onNone()
	end
end

--[=[
	@return boolean
	Returns `true` if the option has a value.
]=]
function Option:IsSome()
	return self._s
end

--[=[
	@return boolean
	Returns `true` if the option is None.
]=]
function Option:IsNone()
	return not self._s
end

--[=[
	@param msg string
	@return value: any
	Unwraps the value in the option, otherwise throws an error with `msg` as the error message.
	```lua
	local opt = Option.Some(10)
	print(opt:Expect("No number")) -> 10
	print(Option.None:Expect("No number")) -- Throws an error "No number"
	```
]=]
function Option:Expect(msg)
	assert(self:IsSome(), msg)
	return self._v
end

--[=[
	@param msg string
	Throws an error with `msg` as the error message if the value is _not_ None.
]=]
function Option:ExpectNone(msg)
	assert(self:IsNone(), msg)
end

--[=[
	@return value: any
	Returns the value in the option, or throws an error if the option is None.
]=]
function Option:Unwrap()
	return self:Expect("Cannot unwrap option of None type")
end

--[=[
	@param default any
	@return value: any
	If the option holds a value, returns the value. Otherwise, returns `default`.
]=]
function Option:UnwrapOr(default)
	if self:IsSome() then
		return self:Unwrap()
	else
		return default
	end
end

--[=[
	@param defaultFn () -> any
	@return value: any
	If the option holds a value, returns the value. Otherwise, returns the
	result of the `defaultFn` function.
]=]
function Option:UnwrapOrElse(defaultFn)
	if self:IsSome() then
		return self:Unwrap()
	else
		return defaultFn()
	end
end

--[=[
	@param optionB Option
	@return Option
	Returns `optionB` if the calling option has a value,
	otherwise returns None.

	```lua
	local optionA = Option.Some(32)
	local optionB = Option.Some(64)
	local opt = optionA:And(optionB)
	-- opt == optionB

	local optionA = Option.None
	local optionB = Option.Some(64)
	local opt = optionA:And(optionB)
	-- opt == Option.None
	```
]=]
function Option:And(optionB)
	if self:IsSome() then
		return optionB
	else
		return Option.None
	end
end

--[=[
	@param andThenFn (value: any) -> Option
	@return value: Option
	If the option holds a value, then the `andThenFn`
	function is called with the held value of the option,
	and then the resultant Option returned by the `andThenFn`
	is returned. Otherwise, None is returned.

	```lua
	local optA = Option.Some(32)
	local optB = optA:AndThen(function(num)
		return Option.Some(num * 2)
	end)
	print(optB:Expect("Expected number")) --> 64
	```
]=]
function Option:AndThen(andThenFn)
	if self:IsSome() then
		local result = andThenFn(self:Unwrap())
		Option.Assert(result)
		return result
	else
		return Option.None
	end
end

--[=[
	@param optionB Option
	@return Option
	If caller has a value, returns itself. Otherwise, returns `optionB`.
]=]
function Option:Or(optionB)
	if self:IsSome() then
		return self
	else
		return optionB
	end
end

--[=[
	@param orElseFn () -> Option
	@return Option
	If caller has a value, returns itself. Otherwise, returns the
	option generated by the `orElseFn` function.
]=]
function Option:OrElse(orElseFn)
	if self:IsSome() then
		return self
	else
		local result = orElseFn()
		Option.Assert(result)
		return result
	end
end

--[=[
	@param optionB Option
	@return Option
	If both `self` and `optionB` have values _or_ both don't have a value,
	then this returns None. Otherwise, it returns the option that does have
	a value.
]=]
function Option:XOr(optionB)
	local someOptA = self:IsSome()
	local someOptB = optionB:IsSome()
	if someOptA == someOptB then
		return Option.None
	elseif someOptA then
		return self
	else
		return optionB
	end
end

--[=[
	@param predicate (value: any) -> boolean
	@return Option
	Returns `self` if this option has a value and the predicate returns `true.
	Otherwise, returns None.
]=]
function Option:Filter(predicate)
	if self:IsNone() or not predicate(self._v) then
		return Option.None
	else
		return self
	end
end

--[=[
	@param value any
	@return boolean
	Returns `true` if this option contains `value`.
]=]
function Option:Contains(value)
	return self:IsSome() and self._v == value
end

--[=[
	@return string
	Metamethod to transform the option into a string.
	```lua
	local optA = Option.Some(64)
	local optB = Option.None
	print(optA) --> Option<number>
	print(optB) --> Option<None>
	```
]=]
function Option:__tostring()
	if self:IsSome() then
		return ("Option<" .. typeof(self._v) .. ">")
	else
		return "Option<None>"
	end
end

--[=[
	@return boolean
	@param opt Option
	Metamethod to check equality between two options. Returns `true` if both
	options hold the same value _or_ both options are None.
	```lua
	local o1 = Option.Some(32)
	local o2 = Option.Some(32)
	local o3 = Option.Some(64)
	local o4 = Option.None
	local o5 = Option.None

	print(o1 == o2) --> true
	print(o1 == o3) --> false
	print(o1 == o4) --> false
	print(o4 == o5) --> true
	```
]=]
function Option:__eq(opt)
	if Option.Is(opt) then
		if self:IsSome() and opt:IsSome() then
			return (self:Unwrap() == opt:Unwrap())
		elseif self:IsNone() and opt:IsNone() then
			return true
		end
	end
	return false
end

--[=[
	@prop None Option<None>
	@within Option
	Represents no value.
]=]
Option.None = Option._new()

return (Option :: any) :: {
	Some: <T>(value: T) -> Option<T>,
	Wrap: <T>(value: T?) -> Option<T>,

	Is: (obj: any) -> boolean,

	None: Option<any>,
}


local ServerScriptService = game:GetService("ServerScriptService")

local Test = require(ServerScriptService.TestRunner.Test)

return function(ctx: Test.TestContext)
	local Option = require(script.Parent)

	ctx:Describe("Some", function()
		ctx:Test("should create some option", function()
			local opt = Option.Some(true)
			ctx:Expect(opt:IsSome()):ToBe(true)
		end)

		ctx:Test("should fail to create some option with nil", function()
			ctx:Expect(function()
				Option.Some(nil)
			end):ToThrow()
		end)

		ctx:Test("should not be none", function()
			local opt = Option.Some(10)
			ctx:Expect(opt:IsNone()):ToBe(false)
		end)
	end)

	ctx:Describe("None", function()
		ctx:Test("should be able to reference none", function()
			ctx:Expect(function()
				local _none = Option.None
			end)
				:Not()
				:ToThrow()
		end)

		ctx:Test("should be able to check if none", function()
			local none = Option.None
			ctx:Expect(none:IsNone()):ToBe(true)
		end)

		ctx:Test("should be able to check if not some", function()
			local none = Option.None
			ctx:Expect(none:IsSome()):ToBe(false)
		end)
	end)

	ctx:Describe("Equality", function()
		ctx:Test("should equal the same some from same options", function()
			local opt = Option.Some(32)
			ctx:Expect(opt):ToBe(opt)
		end)

		ctx:Test("should equal the same some from different options", function()
			local opt1 = Option.Some(32)
			local opt2 = Option.Some(32)
			ctx:Expect(opt1):ToBe(opt2)
		end)
	end)

	ctx:Describe("Assert", function()
		ctx:Test("should assert that a some option is an option", function()
			ctx:Expect(Option.Is(Option.Some(10))):ToBe(true)
		end)

		ctx:Test("should assert that a none option is an option", function()
			ctx:Expect(Option.Is(Option.None)):ToBe(true)
		end)

		ctx:Test("should assert that a non-option is not an option", function()
			ctx:Expect(Option.Is(10)):ToBe(false)
			ctx:Expect(Option.Is(true)):ToBe(false)
			ctx:Expect(Option.Is(false)):ToBe(false)
			ctx:Expect(Option.Is("Test")):ToBe(false)
			ctx:Expect(Option.Is({})):ToBe(false)
			ctx:Expect(Option.Is(function() end)):ToBe(false)
			ctx:Expect(Option.Is(coroutine.create(function() end))):ToBe(false)
			ctx:Expect(Option.Is(Option)):ToBe(false)
		end)
	end)

	ctx:Describe("Unwrap", function()
		ctx:Test("should unwrap a some option", function()
			local opt = Option.Some(10)
			ctx:Expect(function()
				opt:Unwrap()
			end)
				:Not()
				:ToThrow()
			ctx:Expect(opt:Unwrap()):ToBe(10)
		end)

		ctx:Test("should fail to unwrap a none option", function()
			local opt = Option.None
			ctx:Expect(function()
				opt:Unwrap()
			end):ToThrow()
		end)
	end)

	ctx:Describe("Expect", function()
		ctx:Test("should expect a some option", function()
			local opt = Option.Some(10)
			ctx:Expect(function()
				opt:Expect("Expecting some value")
			end)
				:Not()
				:ToThrow()
			ctx:Expect(opt:Unwrap()):ToBe(10)
		end)

		ctx:Test("should fail when expecting on a none option", function()
			local opt = Option.None
			ctx:Expect(function()
				opt:Expect("Expecting some value")
			end):ToThrow()
		end)
	end)

	ctx:Describe("ExpectNone", function()
		ctx:Test("should fail to expect a none option", function()
			local opt = Option.Some(10)
			ctx:Expect(function()
				opt:ExpectNone("Expecting some value")
			end):ToThrow()
		end)

		ctx:Test("should expect a none option", function()
			local opt = Option.None
			ctx:Expect(function()
				opt:ExpectNone("Expecting some value")
			end)
				:Not()
				:ToThrow()
		end)
	end)

	ctx:Describe("UnwrapOr", function()
		ctx:Test("should unwrap a some option", function()
			local opt = Option.Some(10)
			ctx:Expect(opt:UnwrapOr(20)):ToBe(10)
		end)

		ctx:Test("should unwrap a none option", function()
			local opt = Option.None
			ctx:Expect(opt:UnwrapOr(20)):ToBe(20)
		end)
	end)

	ctx:Describe("UnwrapOrElse", function()
		ctx:Test("should unwrap a some option", function()
			local opt = Option.Some(10)
			local result = opt:UnwrapOrElse(function()
				return 30
			end)
			ctx:Expect(result):ToBe(10)
		end)

		ctx:Test("should unwrap a none option", function()
			local opt = Option.None
			local result = opt:UnwrapOrElse(function()
				return 30
			end)
			ctx:Expect(result):ToBe(30)
		end)
	end)

	ctx:Describe("And", function()
		ctx:Test("should return the second option with and when both are some", function()
			local opt1 = Option.Some(1)
			local opt2 = Option.Some(2)
			ctx:Expect(opt1:And(opt2)):ToBe(opt2)
		end)

		ctx:Test("should return none when first option is some and second option is none", function()
			local opt1 = Option.Some(1)
			local opt2 = Option.None
			ctx:Expect(opt1:And(opt2):IsNone()):ToBe(true)
		end)

		ctx:Test("should return none when first option is none and second option is some", function()
			local opt1 = Option.None
			local opt2 = Option.Some(2)
			ctx:Expect(opt1:And(opt2):IsNone()):ToBe(true)
		end)

		ctx:Test("should return none when both options are none", function()
			local opt1 = Option.None
			local opt2 = Option.None
			ctx:Expect(opt1:And(opt2):IsNone()):ToBe(true)
		end)
	end)

	ctx:Describe("AndThen", function()
		ctx:Test("should pass the some value to the predicate", function()
			local opt = Option.Some(32)
			opt:AndThen(function(value)
				ctx:Expect(value):ToBe(32)
				return Option.None
			end)
		end)

		ctx:Test("should throw if an option is not returned from predicate", function()
			local opt = Option.Some(32)
			ctx:Expect(function()
				opt:AndThen(function() end)
			end):ToThrow()
		end)

		ctx:Test("should return none if the option is none", function()
			local opt = Option.None
			ctx:Expect(opt:AndThen(function()
				return Option.Some(10)
			end):IsNone()):ToBe(true)
		end)

		ctx:Test("should return option of predicate if option is some", function()
			local opt = Option.Some(32)
			local result = opt:AndThen(function()
				return Option.Some(10)
			end)
			ctx:Expect(result:IsSome()):ToBe(true)
			ctx:Expect(result:Unwrap()):ToBe(10)
		end)
	end)

	ctx:Describe("Or", function()
		ctx:Test("should return the first option if it is some", function()
			local opt1 = Option.Some(10)
			local opt2 = Option.Some(20)
			ctx:Expect(opt1:Or(opt2)):ToBe(opt1)
		end)

		ctx:Test("should return the second option if the first one is none", function()
			local opt1 = Option.None
			local opt2 = Option.Some(20)
			ctx:Expect(opt1:Or(opt2)):ToBe(opt2)
		end)
	end)

	ctx:Describe("OrElse", function()
		ctx:Test("should return the first option if it is some", function()
			local opt1 = Option.Some(10)
			local opt2 = Option.Some(20)
			ctx:Expect(opt1:OrElse(function()
				return opt2
			end)):ToBe(opt1)
		end)

		ctx:Test("should return the second option if the first one is none", function()
			local opt1 = Option.None
			local opt2 = Option.Some(20)
			ctx:Expect(opt1:OrElse(function()
				return opt2
			end)):ToBe(opt2)
		end)

		ctx:Test("should throw if the predicate does not return an option", function()
			local opt1 = Option.None
			ctx:Expect(function()
				opt1:OrElse(function() end)
			end):ToThrow()
		end)
	end)

	ctx:Describe("XOr", function()
		ctx:Test("should return first option if first option is some and second option is none", function()
			local opt1 = Option.Some(1)
			local opt2 = Option.None
			ctx:Expect(opt1:XOr(opt2)):ToBe(opt1)
		end)

		ctx:Test("should return second option if first option is none and second option is some", function()
			local opt1 = Option.None
			local opt2 = Option.Some(2)
			ctx:Expect(opt1:XOr(opt2)):ToBe(opt2)
		end)

		ctx:Test("should return none if first and second option are some", function()
			local opt1 = Option.Some(1)
			local opt2 = Option.Some(2)
			ctx:Expect(opt1:XOr(opt2)):ToBe(Option.None)
		end)

		ctx:Test("should return none if first and second option are none", function()
			local opt1 = Option.None
			local opt2 = Option.None
			ctx:Expect(opt1:XOr(opt2)):ToBe(Option.None)
		end)
	end)

	ctx:Describe("Filter", function()
		ctx:Test("should return none if option is none", function()
			local opt = Option.None
			ctx:Expect(opt:Filter(function() end)):ToBe(Option.None)
		end)

		ctx:Test("should return none if option is some but fails predicate", function()
			local opt = Option.Some(10)
			ctx:Expect(opt:Filter(function(_v)
				return false
			end)):ToBe(Option.None)
		end)

		ctx:Test("should return self if option is some and passes predicate", function()
			local opt = Option.Some(10)
			ctx:Expect(opt:Filter(function(_v)
				return true
			end)):ToBe(opt)
		end)
	end)

	ctx:Describe("Contains", function()
		ctx:Test("should return true if some option contains the given value", function()
			local opt = Option.Some(32)
			ctx:Expect(opt:Contains(32)):ToBe(true)
		end)

		ctx:Test("should return false if some option does not contain the given value", function()
			local opt = Option.Some(32)
			ctx:Expect(opt:Contains(64)):ToBe(false)
		end)

		ctx:Test("should return false if option is none", function()
			local opt = Option.None
			ctx:Expect(opt:Contains(64)):ToBe(false)
		end)
	end)

	ctx:Describe("ToString", function()
		ctx:Test("should return string of none option", function()
			local opt = Option.None
			ctx:Expect(tostring(opt)):ToBe("Option<None>")
		end)

		ctx:Test("should return string of some option with type", function()
			local values = { 10, true, false, "test", {}, function() end, coroutine.create(function() end), workspace }
			for _, value in ipairs(values) do
				local expectedString = ("Option<%s>"):format(typeof(value))
				ctx:Expect(tostring(Option.Some(value))):ToBe(expectedString)
			end
		end)
	end)
end


--!native

export type PID = {
	Reset: (self: PID) -> (),
	Calculate: (self: PID, setpoint: number, input: number, deltaTime: number) -> number,
	Debug: (self: PID, name: string, parent: Instance?) -> (),
	Destroy: (self: PID) -> (),
}

--[=[
	@class PID
	The PID class simulates a [PID controller](https://en.wikipedia.org/wiki/PID_controller). PID is an acronym
	for _proportional, integral, derivative_. PIDs are input feedback loops that try to reach a specific
	goal by measuring the difference between the input and the desired value, and then returning a new
	desired input.

	A common example is a car's cruise control, which would give a PID the current speed
	and the desired speed, and the PID controller would return the desired throttle input to reach the
	desired speed.
]=]
local PID = {}
PID.__index = PID

--[=[
	@param min number -- Minimum value the PID can output
	@param max number -- Maximum value the PID can output
	@param kp number -- Proportional gain coefficient (P)
	@param ki number -- Integral gain coefficient (I)
	@param kd number -- Derivative gain coefficient (D)
	@return PID

	Constructs a new PID.

	```lua
	local pid = PID.new(0, 1, 0.1, 0, 0)
	```
]=]
function PID.new(min: number, max: number, kp: number, ki: number, kd: number): PID
	local self = setmetatable({}, PID)
	self._min = min
	self._max = max
	self._kp = kp
	self._ki = ki
	self._kd = kd
	self._lastError = 0 -- Store the last error for derivative calculation
	self._integralSum = 0 -- Store the sum of errors for integral calculation
	return self
end

--[=[
	Resets the PID to a zero start state.
]=]
function PID:Reset()
	self._lastError = 0
	self._integralSum = 0
end

--[=[
	@param setpoint number	-- The desired point to reach
	@param processVariable number	-- The measured value of the system to compare against the setpoint
	@param deltaTime number	-- Delta time. This is the time between each PID calculation
	@return output: number

	Calculates the new output based on the setpoint and input. For example,
	if the PID was being used for a car's throttle control where the throttle
	can be in the range of [0, 1], then the PID calculation might look like
	the following:
	```lua
	local cruisePID = PID.new(0, 1, ...)
	local desiredSpeed = 50

	RunService.Heartbeat:Connect(function(dt)
		local throttle = cruisePID:Calculate(desiredSpeed, car.CurrentSpeed, dt)
		car:SetThrottle(throttle)
	end)
	```
]=]
function PID:Calculate(setpoint: number, processVariable: number, deltaTime: number): number
	-- Calculate the error e(t) = SP - PV(t)
	local err = setpoint - processVariable

	-- Proportional term
	local pOut = self._kp * err

	-- Integral term
	self._integralSum = self._integralSum + err * deltaTime
	local iOut = self._ki * self._integralSum

	-- Derivative term
	local derivative = (err - self._lastError) / deltaTime
	local dOut = self._kd * derivative

	-- Combine terms
	local output = pOut + iOut + dOut

	-- Clamp output to min/max
	output = math.clamp(output, self._min, self._max)

	-- Save the current error for the next derivative calculation
	self._lastError = err

	return output
end

--[=[
	@param name string -- Folder name
	@param parent Instance? -- Folder parent

	Creates a folder that contains attributes that can be used to
	tune the PID during runtime within the explorer.

	:::info Studio Only
	This will only create the folder in Studio. In a real game server,
	this function will do nothing.
	:::
]=]
function PID:Debug(name: string, parent: Instance?)
	if not game:GetService("RunService"):IsStudio() then
		return
	end

	if self._debug then
		return
	end

	local folder = Instance.new("Folder")
	folder.Name = name
	folder:AddTag("PIDDebug")

	local function Bind(attrName, propName)
		folder:SetAttribute(attrName, self[propName])
		folder:GetAttributeChangedSignal(attrName):Connect(function()
			self[propName] = folder:GetAttribute(attrName)
			self:Reset()
		end)
	end

	folder:SetAttribute("MinMax", NumberRange.new(self._min, self._max))
	folder:GetAttributeChangedSignal("MinMax"):Connect(function()
		local minMax = folder:GetAttribute("MinMax") :: NumberRange
		self._min = minMax.Min
		self._max = minMax.Max
		self:Reset()
	end)

	Bind("kP", "_kp")
	Bind("kI", "_ki")
	Bind("kD", "_kd")

	folder:SetAttribute("Output", self._min)

	local lastOutput = 0
	self.Calculate = function(s, sp, pv, ...)
		lastOutput = PID.Calculate(s, sp, pv, ...)
		folder:SetAttribute("Output", lastOutput)
		return lastOutput
	end

	local delayThread: thread? = nil
	folder:SetAttribute("ShowDebugger", false)
	folder:GetAttributeChangedSignal("ShowDebugger"):Connect(function()
		if delayThread then
			task.cancel(delayThread)
		end

		local showDebugger = folder:GetAttribute("ShowDebugger")

		if showDebugger then
			delayThread = task.delay(0.1, function()
				delayThread = nil
				if folder:GetAttribute("ShowDebugger") then
					folder:SetAttribute("ShowDebugger", false)
					warn("Install the PID Debug plugin: https://create.roblox.com/store/asset/16279661108/PID-Debug")
				end
			end)
		end
	end)

	folder.Parent = parent or workspace
	self._debug = folder
end

--[=[
	Destroys the PID. This is only necessary if calling `PID:Debug`.
]=]
function PID:Destroy()
	if self._debug then
		self._debug:Destroy()
		self._debug = nil
	end
end

return {
	new = PID.new,
}


--!strict

--[[
	Algorithmic credit: https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm
]]

--[=[
	@within Quaternion
	@interface Quaternion
	.X number
	.Y number
	.Z number
	.W number

	Similar to Vector3s, Quaternions are immutable. You cannot manually set the individual properties
	of a Quaternion. Instead, a new Quaternion must first be constructed.
]=]
export type Quaternion = {
	X: number,
	Y: number,
	Z: number,
	W: number,

	Dot: (self: Quaternion, other: Quaternion) -> number,
	Slerp: (self: Quaternion, other: Quaternion, alpha: number) -> Quaternion,
	Angle: (self: Quaternion, other: Quaternion) -> number,
	RotateTowards: (self: Quaternion, other: Quaternion, maxRadiansDelta: number) -> Quaternion,
	ToCFrame: (self: Quaternion, position: Vector3?) -> CFrame,
	ToEulerAngles: (self: Quaternion) -> Vector3,
	ToAxisAngle: (self: Quaternion) -> (Vector3, number),
	Inverse: (self: Quaternion) -> Quaternion,
	Conjugate: (self: Quaternion) -> Quaternion,
	Normalize: (self: Quaternion) -> Quaternion,
	Magnitude: (self: Quaternion) -> number,
	SqrMagnitude: (self: Quaternion) -> number,
}

local EPSILON = 1e-5

--[=[
	@class Quaternion
	Represents a Quaternion. Quaternions are 4D structs that represent rotations
	in 3D space. Quaternions are often used in 3D graphics to avoid the ambiguity
	that comes with Euler angles (e.g. gimbal lock).

	Roblox represents the transformation of an object via CFrame values, which are
	matrices that hold positional and rotational information. Quaternions can be converted
	to and from CFrame values through the `ToCFrame` method and `cframe` constructor.
]=]
local Quaternion = {}
Quaternion.__index = Quaternion

--[=[
	Constructs a Quaternion.

	:::caution
	The `new` constructor assumes the given arguments represent a proper Quaternion. This
	constructor should only be used if you really know what you're doing.
	:::
]=]
function Quaternion.new(x: number, y: number, z: number, w: number): Quaternion
	local self = setmetatable({
		X = x,
		Y = y,
		Z = z,
		W = w,
	}, Quaternion) :: any

	table.freeze(self)

	return self
end

--[=[
	Constructs a Quaternion from Euler angles (radians).

	```lua
	-- Quaternion rotated 45 degrees on the Y axis:
	local quat = Quaternion.euler(0, math.rad(45), 0)
	```
]=]
function Quaternion.euler(x: number, y: number, z: number): Quaternion
	local cx = math.cos(x * 0.5)
	local cy = math.cos(y * 0.5)
	local cz = math.cos(z * 0.5)
	local sx = math.sin(x * 0.5)
	local sy = math.sin(y * 0.5)
	local sz = math.sin(z * 0.5)

	return Quaternion.new(
		cx * sy * sz + cy * cz * sx,
		cx * cz * sy - cy * sx * sz,
		cx * cy * sz - cz * sx * sy,
		sx * sy * sz + cx * cy * cz
	)
end

--[=[
	Constructs a Quaternion representing a rotation of `angle` radians around `axis`.

	```lua
	-- Quaternion rotated 45 degrees on the Y axis:
	local quat = Quaternion.axisAngle(Vector3.yAxis, math.rad(45))
	```
]=]
function Quaternion.axisAngle(axis: Vector3, angle: number): Quaternion
	local halfAngle = angle / 2
	local sin = math.sin(halfAngle)

	return Quaternion.new(sin * axis.X, sin * axis.Y, sin * axis.Z, math.cos(halfAngle))
end

--[=[
	Constructs a Quaternion representing a rotation facing `forward` direction, where
	`upwards` represents the upwards direction (this defaults to `Vector3.yAxis`).

	```lua
	-- Create a quaternion facing the same direction as the camera:
	local camCf = workspace.CurrentCamera.CFrame
	local quat = Quaternion.lookRotation(camCf.LookVector, camCf.UpVector)
	```
]=]
function Quaternion.lookRotation(forward: Vector3, upwards: Vector3?): Quaternion
	local up = if upwards == nil then Vector3.yAxis else upwards.Unit
	forward = forward.Unit

	local v1 = forward
	local v2 = up:Cross(v1)
	local v3 = v1:Cross(v2)

	local m00 = v2.X
	local m01 = v2.Y
	local m02 = v2.Z
	local m10 = v3.X
	local m11 = v3.Y
	local m12 = v3.Z
	local m20 = v1.X
	local m21 = v1.Y
	local m22 = v1.Z

	local n8 = m00 + m11 + m22

	if n8 > 0 then
		local n = math.sqrt(n8 + 1)

		return Quaternion.new((m12 - m21) * n, (m20 - m02) * n, (m01 - m10) * n, n * 0.5)
	end

	if m00 >= m11 and m00 >= m22 then
		local n7 = math.sqrt(((1 + m00) - m11) - m22)
		local n4 = 0.5 / n7

		return Quaternion.new(0.5 * n7, (m01 + m10) * n4, (m02 + m20) * n4, (m12 - m21) * n4)
	end

	if m11 > m22 then
		local n6 = math.sqrt(((1 + m11) - m00) - m22)
		local n3 = 0.5 / n6

		return Quaternion.new((m10 + m01) * n3, 0.5 * n6, (m21 + m12) * n3, (m20 - m02) * n3)
	end

	local n5 = math.sqrt(((1 + m22) - m00) - m11)
	local n2 = 0.5 / n5

	return Quaternion.new((m20 + m02) * n2, (m21 + m12) * n2, 0.5 * n5, (m01 - m10) * n2)
end

--[=[
	Constructs a Quaternion from the rotation components of the given `cframe`.

	This method ortho-normalizes the CFrame value, so there is no need to do this yourself
	before calling the function.

	```lua
	-- Create a Quaternion representing the rotational CFrame of a part:
	local quat = Quaternion.cframe(somePart.CFrame)
	```
]=]
function Quaternion.cframe(cframe: CFrame): Quaternion
	local _, _, _, m00, m01, m02, m10, m11, m12, m20, m21, m22 = cframe:Orthonormalize():GetComponents()

	local x, y, z, w

	local trace = m00 + m11 + m22
	if trace > 0 then
		local s = math.sqrt(trace + 1) * 2
		x = (m21 - m12) / s
		y = (m02 - m20) / s
		z = (m10 - m01) / s
		w = 0.25 * s
	elseif m00 > m11 and m00 > m22 then
		local s = math.sqrt(1 + m00 - m11 - m22) * 2
		x = 0.25 * s
		y = (m01 + m10) / s
		z = (m02 + m20) / s
		w = (m21 - m12) / s
	elseif m11 > m22 then
		local s = math.sqrt(1 + m11 - m00 - m22) * 2
		x = (m01 + m10) / s
		y = 0.25 * s
		z = (m12 + m21) / s
		w = (m02 - m20) / s
	else
		local s = math.sqrt(1 + m22 - m00 - m11) * 2
		x = (m02 + m20) / s
		y = (m12 + m21) / s
		z = 0.25 * s
		w = (m10 - m01) / s
	end

	return Quaternion.new(x, y, z, w)
end

--[=[
	@method Dot
	@within Quaternion
	@param other Quaternion
	@return number

	Calculates the dot product between the two Quaternions.

	```lua
	local dot = quatA:Dot(quatB)
	```
]=]
function Quaternion.Dot(self: Quaternion, other: Quaternion): number
	return self.X * other.X + self.Y * other.Y + self.Z * other.Z + self.W * other.W
end

--[=[
	@method Slerp
	@within Quaternion
	@param other Quaternion
	@param t number
	@return Quaternion

	Calculates a spherical interpolation between the two Quaternions. Parameter `t` represents
	the percentage between the two rotations, from a range of `[0, 1]`.

	Spherical interpolation is great for smoothing or animating between quaternions.

	```lua
	local midWay = quatA:Slerp(quatB, 0.5)
	```
]=]
function Quaternion.Slerp(a: Quaternion, b: Quaternion, t: number): Quaternion
	local cosOmega = a:Dot(b)
	local flip = false

	if cosOmega < 0 then
		flip = true
		cosOmega = -cosOmega
	end

	local s1, s2

	if cosOmega > (1 - EPSILON) then
		s1 = 1 - t
		s2 = if flip then -t else t
	else
		local omega = math.acos(cosOmega)
		local invSinOmega = 1 / math.sin(omega)

		s1 = math.sin((1 - t) * omega) * invSinOmega
		s2 = if flip then -math.sin(t * omega) * invSinOmega else math.sin(t * omega) * invSinOmega
	end

	return Quaternion.new(s1 * a.X + s2 * b.X, s1 * a.Y + s2 * b.Y, s1 * a.Z + s2 * b.Z, s1 * a.W + s2 * b.W)
end

--[=[
	@method Angle
	@within Quaternion
	@param other Quaternion
	@return number

	Calculates the angle (radians) between the two Quaternions.

	```lua
	local angle = quatA:Angle(quatB)
	```
]=]
function Quaternion.Angle(self: Quaternion, other: Quaternion): number
	local dot = math.min(math.abs(self:Dot(other)), 1)
	local angle = if dot > (1 - EPSILON) then 0 else math.acos(dot) * 2

	return angle
end

--[=[
	@method RotateTowards
	@within Quaternion
	@param other Quaternion
	@param maxRadiansDelta number
	@return Quaternion

	Constructs a new Quaternion that rotates from this Quaternion to the `other` quaternion, with a maximum
	rotation of `maxRadiansDelta`. Internally, this calls `Slerp`, but limits the movement to `maxRadiansDelta`.

	```lua
	-- Rotate from quatA to quatB, but only by 10 degrees:
	local q = quatA:RotateTowards(quatB, math.rad(10))
	```
]=]
function Quaternion.RotateTowards(self: Quaternion, other: Quaternion, maxRadiansDelta: number): Quaternion
	local angle = self:Angle(other)

	if angle == 0 then
		return self
	end

	local alpha = maxRadiansDelta / angle

	return self:Slerp(other, alpha)
end

--[=[
	@method ToCFrame
	@within Quaternion
	@param position Vector3?
	@return CFrame

	Constructs a CFrame value representing the Quaternion. An optional `position` Vector can be given to
	represent the position of the CFrame in 3D space. This defaults to `Vector3.zero`.

	```lua
	-- Construct a CFrame from the quaternion, where the position will be at the origin point:
	local cf = quat:ToCFrame()

	-- Construct a CFrame with a given position:
	local cf = quat:ToCFrame(someVector3)

	-- e.g., set a part's CFrame:
	local part = workspace.Part
	local quat = Quaternion.axisAngle(Vector3.yAxis, math.rad(45))
	local cframe = quat:ToCFrame(part.Position) -- Construct CFrame with a positional component
	part.CFrame = cframe
	```
]=]
function Quaternion.ToCFrame(self: Quaternion, position: Vector3?): CFrame
	local pos = if position == nil then Vector3.zero else position

	return CFrame.new(pos.X, pos.Y, pos.Z, self.X, self.Y, self.Z, self.W)
end

--[=[
	@method ToEulerAngles
	@within Quaternion
	@return Vector3

	Calculates the Euler angles (radians) that represent the Quaternion.

	```lua
	local euler = quat:ToEulerAngles()
	print(euler.X, euler.Y, euler.Z)
	```
]=]
function Quaternion.ToEulerAngles(self: Quaternion): Vector3
	local test = self.X * self.Y + self.Z * self.W
	if test > 0.49999 then
		return Vector3.new(0, 2 * math.atan2(self.X, self.W), math.pi / 2)
	elseif test < -0.49999 then
		return Vector3.new(0, -2 * math.atan2(self.X, self.W), -math.pi / 2)
	end

	local sqx = self.X * self.X
	local sqy = self.Y * self.Y
	local sqz = self.Z * self.Z

	return Vector3.new(
		math.atan2(2 * self.X * self.W - 2 * self.Y * self.Z, 1 - 2 * sqx - 2 * sqz),
		math.atan2(2 * self.Y * self.W - 2 * self.X * self.Z, 1 - 2 * sqy - 2 * sqz),
		math.asin(2 * test)
	)
end

--[=[
	@method ToAxisAngle
	@within Quaternion
	@return (Vector3, number)

	Calculates the axis and angle representing the Quaternion.

	```lua
	local axis, angle = quat:ToAxisAngle()
	```
]=]
function Quaternion.ToAxisAngle(self: Quaternion): (Vector3, number)
	local scale = math.sqrt(self.X * self.X + self.Y * self.Y + self.Z * self.Z)

	if math.abs(scale) < EPSILON or self.W > 1 or self.W < -1 then
		return Vector3.yAxis, 0
	end

	local invScale = 1 / scale

	local axis = Vector3.new(self.X * invScale, self.Y * invScale, self.Z * invScale)
	local angle = 2 * math.acos(self.W)

	return axis, angle
end

--[=[
	@method Inverse
	@within Quaternion
	@return Quaternion

	Returns the inverse of the Quaternion.

	```lua
	local quatInverse = quat:Inverse()
	```
]=]
function Quaternion.Inverse(self: Quaternion): Quaternion
	local dot = self:Dot(self)
	local invNormal = 1 / dot
	return Quaternion.new(-self.X * invNormal, -self.Y * invNormal, -self.Z * invNormal, self.W * invNormal)
end

--[=[
	@method Conjugate
	@within Quaternion
	@return Quaternion

	Returns the conjugate of the Quaternion. This is equal to `Quaternion.new(-X, -Y, -Z, W)`.

	```lua
	local quatConjugate = quat:Conjugate()
	```
]=]
function Quaternion.Conjugate(self: Quaternion): Quaternion
	return Quaternion.new(-self.X, -self.Y, -self.Z, self.W)
end

--[=[
	@method Normalize
	@within Quaternion
	@return Quaternion

	Returns the normalized representation of the Quaternion.

	```lua
	local quatNormalized = quat:Normalize()
	```
]=]
function Quaternion.Normalize(self: Quaternion): Quaternion
	local magnitude = self:Magnitude()

	if magnitude < EPSILON then
		return Quaternion.identity
	end

	return Quaternion.new(self.X / magnitude, self.Y / magnitude, self.Z / magnitude, self.W / magnitude)
end

--[=[
	@method Magnitude
	@within Quaternion
	@return number

	Calculates the magnitude of the Quaternion.

	```lua
	local magnitude = quat:Magnitude()
	```
]=]
function Quaternion.Magnitude(self: Quaternion): number
	return math.sqrt(self:Dot(self))
end

--[=[
	@method SqrMagnitude
	@within Quaternion
	@return number

	Calculates the square magnitude of the Quaternion.

	```lua
	local squareMagnitude = quat:Magnitude()
	```
]=]
function Quaternion.SqrMagnitude(self: Quaternion): number
	return self:Dot(self)
end

function Quaternion._MulVector3(self: Quaternion, other: Vector3): Vector3
	local x = self.X * 2
	local y = self.Y * 2
	local z = self.Z * 2

	local xx = self.X * x
	local yy = self.Y * y
	local zz = self.Z * z
	local xy = self.X * y
	local xz = self.X * z
	local yz = self.Y * z
	local wx = self.W * x
	local wy = self.W * y
	local wz = self.W * z

	return Vector3.new(
		(1 - (yy + zz)) * other.X + (xy - wz) * other.Y + (xz + wy) * other.Z,
		(xy + wz) * other.X + (1 - (xx + zz)) * other.Y + (yz - wx) * other.Z,
		(xz - wy) * other.X + (yz + wx) * other.Y + (1 - (xx + yy)) * other.Z
	)
end

function Quaternion._MulQuaternion(self: Quaternion, other: Quaternion): Quaternion
	return Quaternion.new(
		self.W * other.X + self.X * other.W + self.Y * other.Z - self.Z * other.Y,
		self.W * other.Y + self.Y * other.W + self.Z * other.X - self.X * other.Z,
		self.W * other.Z + self.Z * other.W + self.X * other.Y - self.Y * other.X,
		self.W * other.W - self.X * other.X - self.Y * other.Y - self.Z * other.Z
	)
end

--[=[
	Multiplication metamethod. A Quaternion can be multiplied with another Quaternion or
	a Vector3.

	```lua
	local quat = quatA * quatB
	local vec = quatA * vecA
	```
]=]
function Quaternion.__mul(self: Quaternion, other: Quaternion | Vector3): Quaternion | Vector3
	local t = typeof(other)
	if t == "Vector3" then
		return Quaternion._MulVector3(self, other :: any)
	elseif t == "table" and getmetatable(other :: any) == Quaternion then
		return Quaternion._MulQuaternion(self, (other :: any) :: Quaternion)
	else
		error(`cannot multiply quaternion with type {t}`, 2)
	end
end

function Quaternion.__unm(self: Quaternion): Quaternion
	return Quaternion.new(-self.X, -self.Y, -self.Z, -self.W)
end

function Quaternion.__eq(self: Quaternion, other: Quaternion): boolean
	return self.X == other.X and self.Y == other.Y and self.Z == other.Z and self.W == other.W
end

function Quaternion.__tostring(self: Quaternion): string
	return `{self.X}, {self.Y}, {self.Z}, {self.W}`
end

-- Kind of support roblox-ts multiplication (not really, but follows the same pattern):
Quaternion.mul = Quaternion.__mul

--[=[
	@prop identity Quaternion
	@readonly
	@within Quaternion

	Identity Quaternion. Equal to `Quaternion.new(0, 0, 0, 1)`.
]=]
Quaternion.identity = Quaternion.new(0, 0, 0, 1)

return {
	new = Quaternion.new,
	euler = Quaternion.euler,
	axisAngle = Quaternion.axisAngle,
	lookRotation = Quaternion.lookRotation,
	cframe = Quaternion.cframe,

	identity = Quaternion.identity,
}


--!strict

--[=[
	@class Query

	Adds helpful functions that wrap
	[`Instance:QueryDescendants()`](https://create.roblox.com/docs/reference/engine/classes/Instance#QueryDescendants).
]=]

local Query = {}

--[=[
	@within Query

	Equivalent to [`parent:QueryDescendants(selector)`](https://create.roblox.com/docs/reference/engine/classes/Instance#QueryDescendants).
]=]
function Query.all(parent: Instance, selector: string): { Instance }
	return parent:QueryDescendants(selector)
end

--[=[
	@within Query

	Returns the query result, filtered by the `filter` function. Ideally, most of
	the filtering should be done with the selector itself. However, if the selector
	is not enough, this function can be used to further filter the results.

	```lua
	local buttons = Query.filter(workspace, ".Button", function(instance)
		return instance.Transparency > 0.25
	end)
	```
]=]
function Query.filter(parent: Instance, selector: string, filter: (Instance) -> boolean): { Instance }
	local instances = parent:QueryDescendants(selector)
	for i = #instances, 1, -1 do
		if not filter(instances[i]) then
			table.remove(instances, i)
		end
	end
	return instances
end

--[=[
	@within Query

	Returns the query result mapped by the `map` function.

	```lua
	local Button = {}

	function Button.new(btn: BasePart)
		...
	end

	----

	local buttons = Query.map(workspace, ".Button", function(instance)
		return Button.new(instance)
	end)
	```
]=]
function Query.map<T>(parent: Instance, selector: string, map: (Instance) -> T): { T }
	local instances = parent:QueryDescendants(selector)
	local mapped = table.create(#instances)
	for i, instance in instances do
		mapped[i] = map(instance)
	end
	return mapped
end

--[=[
	@within Query

	Returns the first item from the query. Might be `nil` if the query returns
	nothing.

	This is equivalent to `parent:QueryDescendants(selector)[1]`.

	```lua
	-- Find an instance tagged with 'Tycoon' that has
	-- the OwnerId attribute matching the player's UserId:
	local tycoon = Query.first(workspace, `.Tycoon[$OwnerId = {player.UserId}]`)

	if tycoon then
		...
	end
	```
]=]
function Query.first(parent: Instance, selector: string): Instance?
	return parent:QueryDescendants(selector)[1]
end

--[=[
	@within Query

	Asserts that the query returns exactly one instance. The instance is returned.
	This is useful when attempting to find an exact match of an instance that
	must exist.

	If the result returns zero or more than one result, an error is thrown.

	```lua
	-- Find a SpawnLocation that has the MainSpawn attribute set to true:
	local spawnPoint = Query.one(workspace, "SpawnLocation[$MainSpawn = true]")
	```
]=]
function Query.one(parent: Instance, selector: string): Instance
	local instances = parent:QueryDescendants(selector)
	if #instances ~= 1 then
		error(`expected 1 instance from query; got {#instances} instances (selector: {selector})`, 2)
	end

	return instances[1]
end

return Query