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

--[=[
	@function zipAll
	@within Array

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

	Zips multiple arrays together into a single array, filling
	in missing values with `None`.

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

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

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

	local maxLength = Reduce(arguments, function(acc, val)
		return math.max(acc, #val)
	end, #arguments[1])

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

		for _, argArray in ipairs(arguments) do
			local value = argArray[index]
			table.insert(values, if value == nil then None else value)
		end

		table.insert(result, values)
	end

	return result :: any
end

return zipAll


--!strict
--[=[
  @function copyDeep
  @within Dictionary

  @param dictionary T -- The dictionary to copy.
  @return T -- The copied dictionary.

  Copies a dictionary recursively.

  ```lua
  local dictionary = { hello = { world = "goodbye" } }

  local new = CopyDeep(dictionary) -- { hello = { world = "goodbye" } }

  print(new == dictionary) -- false
  print(new.hello == dictionary.hello) -- false
  ```
]=]
local function copyDeep<T>(dictionary: T): T
	local new = table.clone(dictionary :: any)

	for key, value in pairs(dictionary :: any) do
		if type(value) == "table" then
			new[key] = copyDeep(value)
		end
	end

	return new :: any
end

return copyDeep


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

--[=[
  @function count
  @within Dictionary

  @param dictionary T -- The dictionary to count.
  @param predicate? (value: T, key: K, dictionary: T) -> any -- The predicate to use to filter the dictionary.
  @return number -- The number of items in the dictionary.

  Counts the number of items in a dictionary.

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

  local value = Count(dictionary) -- 2
  local value = Count(dictionary, function(item, key)
    return item == "world"
  end) -- 1
  ```
]=]
local function count<K, V>(dictionary: { [K]: V }, predicate: ((value: V, key: K, dictionary: { [K]: V }) -> any)?): number
	local counter = 0

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

	for key, value in pairs(dictionary) do
		if (predicate :: any)(value, key, dictionary) then
			counter += 1
		end
	end

	return counter
end

return count


--!strict
--[=[
  @function entries
  @within Dictionary

  @param dictionary {[K]: V} -- The dictionary to get the entries from.
  @return {{ K, V }} -- The entries in the dictionary.

  Returns the entries in the given dictionary as an array of key-value pairs.

  ```lua
  local dictionary = { hello = "roblox", goodbye = "world" }

  local entries = Entries(dictionary) -- { { "hello", "roblox" }, { "goodbye", "world" } }
  ```
]=]
local function entries<K, V>(dictionary: { [K]: V }): { [number]: { [number]: K | V } }
	local result = {}

	for key, value in pairs(dictionary) do
		table.insert(result, { key, value :: any })
	end

	return result
end

return entries


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

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

	for key, value in pairs(a) do
		if b[key] ~= value then
			return false
		end
	end

	for key, value in pairs(b) do
		if a[key] ~= value then
			return false
		end
	end

	return true
end

--[=[
  @function equals
  @within Dictionary

  @param ... ...{ [any]: any } -- The dictionaries to compare.
  @return boolean -- Whether the dictionaries are equal.

  Compares two dictionaries for equality.

  ```lua
  local dictionary = { hello = "world", goodbye = "world" }
  local other1 = { hello = "world", goodbye = "world" }
  local other2 = { hello = "hello", world = "goodbye" }

  local value = Equals(dictionary, other1) -- true
  local value = Equals(dictionary, other1, other2) -- false
  ```
]=]
local function equals(...: _T.AnyDictionary): boolean
	if Util.equalObjects(...) then
		return true
	end

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

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

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

	return true
end

return equals


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

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

	for key, value in pairs(a) do
		if not compareDeep(value, b[key]) then
			return false
		end
	end

	for key, value in pairs(b) do
		if not compareDeep(value, a[key]) then
			return false
		end
	end

	return true
end

--[=[
  @function equalsDeep
  @within Dictionary

  @param ... ...{ [any]: any } -- The dictionaries to compare.
  @return boolean -- Whether the dictionaries are equal.

  Compares two dictionaries for equality using deep comparison.

  ```lua
  local dictionary = { hello = "world", goodbye = { world = "hello" } }
  local other1 = { hello = "world", goodbye = { world = "hello" } }
  local other2 = { hello = "hello", world = "goodbye" }

  local value = EqualsDeep(dictionary, other1) -- true
  local value = EqualsDeep(dictionary, other1, other2) -- false
  ```
]=]
local function equalsDeep(...: _T.AnyDictionary): boolean
	if Util.equalObjects(...) then
		return true
	end

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

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

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

	return true
end

return equalsDeep


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

--[=[
  @function filter
  @within Dictionary

  @param dictionary {[K]: V} -- The dictionary to filter.
  @param predicate? (value: V, key: K, dictionary: {[K]: V}) -> any -- The predicate to use to filter the dictionary.
  @return {[K]: V} -- The filtered dictionary.

  Filters a dictionary using a predicate. Any items that do not pass the predicate will be removed from the dictionary.

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

  local result = Filter(dictionary, function(value, key)
    return value == "world"
  end) -- { hello = "world" }
  ```
]=]
local function filter<K, V>(
	dictionary: { [K]: V },
	predicate: ((value: V, key: K, dictionary: { [K]: V }) -> any)?
): { [K]: V }
	local result = {}

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

	for key, value in pairs(dictionary) do
		if (predicate :: any)(value, key, dictionary) then
			result[key] = value
		end
	end

	return result
end

return filter


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

--[=[
  @function flatten
  @within Dictionary

  @param dictionary T -- The dictionary to flatten.
  @param depth? number -- The depth to flatten the dictionary to.
  @return T -- The flattened dictionary.

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

  ```lua
  local dictionary = {
    hello = "world",
    goodbye = {
      world = "hello",
      roblox = {
        yes = "no",
        no = "yes",
      }
    }
  }

  local new = Flatten(dictionary) -- { hello = "world", world = "hello", yes = "no", no = "yes" }
  local new = Flatten(dictionary, 1) -- { hello = "world", world = "hello", roblox = { yes = "no", no = "yes" } }
  ```
]=]
local function flatten(dictionary: _T.AnyDictionary, depth: number?): _T.AnyDictionary
	depth = if type(depth) == "number" then depth else math.huge

	local result = {}

	for key, value in pairs(dictionary :: _T.AnyDictionary) do
		if type(value) == "table" and depth > 0 then
			local nested = flatten(value, depth :: any - 1)

			for resultKey, resultValue in pairs(result) do
				nested[resultKey] = resultValue
			end

			result = nested
		else
			result[key] = value
		end
	end

	return result
end

return flatten


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

--[=[
  @function freeze
  @within Dictionary

  @param dictionary T -- The dictionary to freeze.
  @return T -- The frozen dictionary.

  Freezes the given dictionary at the top level, making it read-only.

  ```lua
  local dictionary = { hello = "roblox", goodbye = { world = "world" } }

  local new = Freeze(dictionary)

  new.hello = "world" -- error!
  new.goodbye.world = "hello" -- still works!
  ```
]=]
local function freeze(dictionary: _T.AnyDictionary): _T.AnyDictionary
	local new = Copy(dictionary)

	table.freeze(new)

	return new
end

return freeze


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

--[=[
  @function freezeDeep
  @within Dictionary

  @param dictionary T -- The dictionary to freeze.
  @return T -- The frozen dictionary.

  Freezes the entire dictionary, making it read-only, including all nested dictionaries.

  ```lua
  local dictionary = { hello = "roblox", goodbye = { world = "world" } }

  local new = FreezeDeep(dictionary)

  new.hello = "world" -- error!
  new.goodbye.world = "hello" -- error!
  ```
]=]
local function freezeDeep(dictionary: _T.AnyDictionary): _T.AnyDictionary
	local result = {}

	for key, value in pairs(dictionary) do
		if type(value) == "table" then
			result[key] = freezeDeep(value)
		else
			result[key] = value
		end
	end

	table.freeze(result)

	return result
end

return freezeDeep


--!strict
--[=[
  @function fromEntries
  @within Dictionary

  @param entries {{ K, V }} -- An array of key-value pairs.
  @return {[K]: V} -- A dictionary composed of the given key-value pairs.

  Creates a dictionary from the given key-value pairs.

  ```lua
  local entries = { { "hello", "roblox" }, { "goodbye", "world" } }

  local dictionary = FromEntries(entries) -- { hello = "roblox", goodbye = "world" }
  ```
]=]
local function fromEntries<K, V>(entries: { [number]: { [number]: K | V } }): { [K]: V }
	local result = {}

	for _, entry in ipairs(entries) do
		result[entry[1]] = entry[2]
	end

	return result :: any
end

return fromEntries


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

--[=[
  @function has
  @within Dictionary

  @param dictionary {[K]: V} -- The dictionary to check.
  @param key any -- The key to check for.
  @return boolean -- Whether or not the dictionary has the given key.

  Checks whether or not the given dictionary has the given key.

  ```lua
  local dictionary = { hello = "roblox", goodbye = "world" }

  local hasHello = Has(dictionary, "hello") -- true
  local hasCat = Has(dictionary, "cat") -- false
  ```
]=]
local function has(dictionary: _T.AnyDictionary, key: any): boolean
	return dictionary[key] ~= nil
end

return has


--!strict
--[=[
	@class Dictionary

	Dictionaries are a type of data structure that can be used to store key-value pairs.

	```lua
	local dictionary = {
		cats = 2,
		dogs = 1
	}

	print(dictionary.cats) -- 2
	```
]=]
local Dictionary = {
	copy = require("@self/copy"),
	copyDeep = require("@self/copyDeep"),
	count = require("@self/count"),
	entries = require("@self/entries"),
	equals = require("@self/equals"),
	equalsDeep = require("@self/equalsDeep"),
	every = require("@self/every"),
	filter = require("@self/filter"),
	flatten = require("@self/flatten"),
	flip = require("@self/flip"),
	freeze = require("@self/freeze"),
	freezeDeep = require("@self/freezeDeep"),
	fromArrays = require("@self/fromArrays"),
	fromEntries = require("@self/fromEntries"),
	has = require("@self/has"),
	includes = require("@self/includes"),
	keys = require("@self/keys"),
	map = require("@self/map"),
	merge = require("@self/merge"),
	mergeDeep = require("@self/mergeDeep"),
	removeKey = require("@self/removeKey"),
	removeKeys = require("@self/removeKeys"),
	removeValue = require("@self/removeValue"),
	removeValues = require("@self/removeValues"),
	set = require("@self/set"),
	some = require("@self/some"),
	update = require("@self/update"),
	values = require("@self/values"),
	withKeys = require("@self/withKeys"),
}

Dictionary.join = Dictionary.merge
Dictionary.joinDeep = Dictionary.mergeDeep

return Dictionary


--!strict
--[=[
  @function map
  @within Dictionary

  @param dictionary {[K]: V} -- The dictionary to map.
  @param mapper (value: V, key: K, dictionary: {[K]: V}) -> (Y?, X?) -- The mapper function.
  @return {[X]: Y} -- The mapped dictionary.

  Maps the dictionary using the mapper function. The mapper function can
  return a value and a key. If the mapper function does not return a key,
  the original key will be used.

  ```lua
  local dictionary = { hello = 10, goodbye = 20 }

  local new = Map(dictionary, function(value, key)
    return value * 2, key .. "!"
  end) -- { ["hello!"] = 20, ["goodbye!"] = 40 }

  local new = Map(dictionary, function(value, key)
    return value * 10
  end) -- { hello = 100, goodbye = 200 }
  ```
]=]
local function map<K, V, X, Y>(dictionary: { [K]: V }, mapper: (value: V, key: K, dictionary: { [K]: V }) -> (Y?, X?)): { [X]: Y }
	local mapped = {}

	for key, value in pairs(dictionary) do
		local mappedValue, mappedKey = mapper(value, key, dictionary)
		mapped[mappedKey or key] = mappedValue
	end

	return mapped :: any
end

return map


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

--[=[
  @function merge
  @within Dictionary

  @param dictionaries? ...any -- The dictionaries to merge.
  @return T -- The merged dictionary.

  Merges the given dictionaries into a single dictionary. If the
  value is `None`, it will be removed from the result. The
  parameters may be any number of dictionaries or `nil`.
  Non-dictonaries will be ignored.

  Aliases: `join`

  ```lua
  local dictionary1 = { hello = "roblox", goodbye = "world" }
  local dictionary2 = { goodbye = "goodbye" }

  local merged = Merge(dictionary1, dictionary2) -- { hello = "roblox", goodbye = "goodbye" }
  ```
]=]
local function merge<T>(...: any): T
	local result = {}

	for dictionaryIndex = 1, select("#", ...) do
		local dictionary = select(dictionaryIndex, ...)

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

		for key, value in pairs(dictionary) do
			result[key] = if value == None then nil else value
		end
	end

	return result :: any
end

return merge


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

--[=[
	@function mergeDeep
	@within Dictionary

	@param dictionaries? ...any -- The dictionaries to merge.
	@return T -- The merged dictionary.

	Merges the given dictionaries into a single dictionary. If the
	value is `None`, it will be removed from the result. This is
	recursive. The parameters may be any number of dictionaries or
	`nil`. Non-dictonaries will be ignored.

	Aliases: `joinDeep`

	```lua
	local dictionary1 = { hello = "roblox", goodbye = { world = "goodbye" } }
	local dictionary2 = { goodbye = { world = "world" } }

	local merged = MergeDeep(dictionary1, dictionary2) -- { hello = "roblox", goodbye = { world = "world" } }
	```
]=]
local function mergeDeep<T>(...: any): T
	local result = {}

	for dictionaryIndex = 1, select("#", ...) do
		local dictionary = select(dictionaryIndex, ...)

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

		for key, value in pairs(dictionary) do
			if value == None then
				result[key] = nil
			elseif type(value) == "table" then
				if result[key] == nil or type(result[key]) ~= "table" then
					result[key] = copyDeep(value)
				else
					result[key] = mergeDeep(result[key], value)
				end
			else
				result[key] = value
			end
		end
	end

	return result :: any
end

return mergeDeep


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

--[=[
  @function removeKey
  @within Dictionary

  @param dictionary {[K]: V} -- The dictionary to remove the key from.
  @param key K -- The key to remove.
  @return {[K]: V} -- The dictionary without the given key.

  Removes the given key from the given dictionary.

  ```lua
  local dictionary = { hello = "roblox", goodbye = "world" }

  local withoutHello = RemoveKey(dictionary, "hello") -- { goodbye = "world" }
  local withoutGoodbye = RemoveKey(dictionary, "goodbye") -- { hello = "roblox" }
  ```
]=]
local function removeKey<K, V>(dictionary: { [K]: V }, key: K): { [K]: V }
	local result = copy(dictionary)

	result[key] = nil

	return result
end

return removeKey


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

--[=[
  @function removeKeys
  @within Dictionary

  @param dictionary {[K]: V} -- The dictionary to remove the keys from.
  @param keys ...K -- The keys to remove.
  @return {[K]: V} -- The dictionary without the given keys.

  Removes the given keys from the given dictionary.

  ```lua
  local dictionary = { hello = "world", cat = "meow", dog = "woof", unicorn = "rainbow" }

  local withoutCatDog = RemoveKeys(dictionary, "cat", "dog") -- { hello = "world", unicorn = "rainbow" }
  ```
]=]
local function removeKeys<K, V>(dictionary: { [K]: V }, ...: K): { [K]: V }
	local result = copy(dictionary)

	for _, key in ipairs({ ... }) do
		result[key] = nil
	end

	return result
end

return removeKeys


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

--[=[
  @function removeValues
  @within Dictionary

  @param dictionary {[K]: V} -- The dictionary to remove the values from.
  @param values ...V -- The values to remove.
  @return {[K]: V} -- The dictionary without the given values.

  Removes the given values from the given dictionary.

  ```lua
  local dictionary = { hello = "world", cat = "meow", unicorn = "rainbow", goodbye = "world" }

  local withoutWorld = RemoveValues(dictionary, "world") -- { cat = "meow", unicorn = "rainbow" }
  local onlyWorld = RemoveValues(dictionary, "meow", "rainbow") -- { hello = "world", goodbye = "world" }
  ```
]=]
local function removeValues<K, V>(dictionary: { [K]: V }, ...: V): { [K]: V }
	local values = ToSet({ ... })
	local result = {}

	for key, value in pairs(dictionary) do
		if not values[value] then
			result[key] = value
		end
	end

	return result
end

return removeValues


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

--[=[
  @function set
  @within Dictionary

  @param dictionary {[K]: V} -- The dictionary to set the value in.
  @param key K -- The key to set the value in.
  @param value V -- The value to set.
  @return {[K]: V} -- The dictionary with the given value set.

  Sets the given value in the given dictionary.

  ```lua
  local dictionary = { hello = "world", cat = "meow", unicorn = "rainbow" }

  local setCat = Set(dictionary, "cat", "woof") -- { hello = "world", cat = "woof", unicorn = "rainbow" }
  ```
]=]
local function set<K, V>(dictionary: { [K]: V }, key: K, value: V): { [K]: V }
	local result = copy(dictionary)

	result[key] = value

	return result
end

return set


--!strict
--[=[
  @function some
  @within Dictionary

  @param dictionary {[K]: V} -- The dictionary to check.
  @param predicate (value: V, key: K, dictionary: { [K]: V }) -> any -- The predicate to check against.
  @return boolean -- Whether or not the predicate returned true for any value.

  Checks whether or not the predicate returned true for any value in the dictionary.

  ```lua
  local dictionary = { hello = "world", cat = "meow", unicorn = "rainbow" }

  local hasMeow = Some(dictionary, function(value)
    return value == "meow"
  end) -- true

  local hasDog = Some(dictionary, function(_, key)
    return key == "dog"
  end) -- false
  ```
]=]
local function some<K, V>(dictionary: { [K]: V }, predicate: (value: V, key: V, dictionary: { [K]: V }) -> any): boolean
	for key, value in pairs(dictionary) do
		if predicate(value, key :: any, dictionary) then
			return true
		end
	end

	return false
end

return some


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

--[=[
  @function update
  @within Dictionary

  @param dictionary {[K]: V?} -- The dictionary to update.
  @param key K -- The key to update.
  @param updater? (value: V, key: K) -> U -- The updater function.
  @param callback? (key: K) -> C -- The callback function.
  @return {[K]: V | U | C} -- The updated dictionary.

  Updates a value in a dictionary at the given key. If the value at the given key does not exist, `callback` will be called, and its return value will be used as the value at the given key.

  ```lua
  local dictionary = { cats = 2 }

  local new = Update(dictionary, "cats", function(value)
    return value + 1
  end) -- { cats = 3 }

  local new = Update(dictionary, "dogs", function(value)
    return value + 1
  end, function(value)
    return 1
  end) -- { cats = 3, dogs = 1 }
  ```
]=]
local function update<K, V, U, C>(
	dictionary: { [K]: V? },
	key: K,
	updater: ((value: V, key: K) -> U)?,
	callback: ((key: K) -> C)?
): { [K]: V | U | C }
	local result: { [K]: any } = copy(dictionary)

	if result[key] then
		if updater then
			result[key] = updater(result[key], key)
		end
	else
		if typeof(callback) == "function" then
			result[key] = callback(key)
		end
	end

	return result
end

return update


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

--[=[
  @function copy
  @within Set

  @param set { [T]: boolean } -- The set to copy.
  @return { [T]: boolean } -- A copy of the set.

  Creates a copy of a set.

  ```lua
  local set = { hello = true }

  local newSet = Copy(set) -- { hello = true }
  ```
]=]
return copy


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

--[=[
  @function count
  @within Set

  @param set { [T]: boolean } -- The set to count.
  @param predicate? (item: T, set: { [T]: boolean }) -> boolean? -- The predicate to use to count.
  @return number -- The number of items in the set.

  Counts the number of items in a set.

  ```lua
  local set = { hello = true, world = true }

  local count = Count(set) -- 2
  local count = Count(set, function(item)
    return item == "hello"
  end) -- 1
  ```
]=]
local function count<T>(set: { [T]: boolean }, predicate: ((item: T, set: { [T]: boolean }) -> boolean?)?): number
	local counter = 0

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

	for item, _ in pairs(set) do
		if (predicate :: any)(item, set) then
			counter += 1
		end
	end

	return counter
end

return count


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

--[=[
  @function difference
  @within Set

  @param set Set<V> -- The set to compare.
  @param ... ...Set<V> -- The sets to compare against.
  @return Set<V> -- The difference between the sets.

  Returns a set of values that are in the first set, but not in the other sets.

  ```lua
  local set1 = { hello = true, world = true }
  local set2 = { cat = true, dog = true, hello = true }

  local difference = Difference(set1, set2) -- { world = true }
  ```
]=]
local function difference<V>(set: T.Set<V>, ...: T.Set<V>): T.Set<V>
	local diff = table.clone(set)

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

		for value in nextSet do
			diff[value] = nil
		end
	end

	return diff
end

return difference


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

--[=[
  @function differenceSymmetric
  @within Set

  @param set Set<V> -- The set to compare.
  @param ... ...Set<V> -- The sets to compare against.
  @return Set<V> -- The symmetric difference between the sets.

  Returns a set of values that are in the first set, but not in the other sets, and vice versa.

  ```lua
  local set1 = { hello = true, world = true }
  local set2 = { cat = true, dog = true, hello = true }

  local differenceSymmetric = DifferenceSymmetric(set1, set2) -- { world = true, cat = true, dog = true }
  ```
]=]
local function differenceSymmetric<V>(set: T.Set<V>, ...: T.Set<V>): T.Set<V>
	local diff = table.clone(set)

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

		for value in nextSet do
			diff[value] = if diff[value] == nil then true else false
		end
	end

	for value, keep in diff do
		diff[value] = (if keep then true else nil) :: any
	end

	return diff
end

return differenceSymmetric


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

--[=[
  @function filter
  @within Set

  @param set { [T]: boolean } -- The set to filter.
  @param predicate? (item: T, set: { [T]: boolean }) -> any -- The function to filter the set with.
  @return { [T]: boolean } -- The filtered set.

  Filters a set using a predicate. Any items that do not pass the predicate will be removed from the set.

  ```lua
  local set = { hello = true, world = true }

  local newSet = Filter(set, function(value)
    return value ~= "hello"
  end) -- { world = true }
  ```
]=]
local function filter<T>(set: { [T]: boolean }, predicate: ((T, { [T]: boolean }) -> any)?): { [T]: boolean }
	local result = {}

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

	for key, _ in pairs(set) do
		if (predicate :: any)(key, set) then
			result[key] = true
		end
	end

	return result
end

return filter


--!strict
--[=[
  @function fromArray
  @within Set

  @param array {T} -- The array to convert to a set.
  @return {[T]: boolean} -- The set.

  Converts an array to a set, where each item is mapped to true.
  Duplicate items are discarded.

  Aliases: `fromList`

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

  local set = FromArray(array) -- { hello = true, world = true }
  ```
]=]
local function fromArray<T>(array: { T }): { [T]: boolean }
	local result = table.create(#array)

	for _, value in ipairs(array) do
		result[value :: any] = true
	end

	return result :: any
end

return fromArray


--!strict
--[=[
	@class Set

	Sets are a collection of values. They are used to store unique values.
	They are essentially a dictionary, but each value is stored as a boolean.
	This means that a value can only be in a set once.

	```lua
	local set = { hello = true }

	local newSet = Add(set, "world") -- { hello = true, world = true }
	```
]=]
local set = {
	add = require("@self/add"),
	copy = require("@self/copy"),
	count = require("@self/count"),
	delete = require("@self/delete"),
	difference = require("@self/difference"),
	differenceSymmetric = require("@self/differenceSymmetric"),
	filter = require("@self/filter"),
	fromArray = require("@self/fromArray"),
	has = require("@self/has"),
	intersection = require("@self/intersection"),
	isSubset = require("@self/isSubset"),
	isSuperset = require("@self/isSuperset"),
	map = require("@self/map"),
	merge = require("@self/merge"),
	toArray = require("@self/toArray"),
}

set.fromList = set.fromArray
set.join = set.merge
set.subtract = set.delete
set.union = set.merge

return set


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

--[=[
  @function isSuperset
  @within Set

  @param superset { [any]: boolean } -- The superset to check.
  @param subset { [any]: boolean } -- The subset to check against.
  @return boolean -- Whether the superset is a superset of the subset.

  Checks whether a set is a superset of another set.

  ```lua
  local set = { hello = true, world = true }
  local subset = { hello = true }

  local isSuperset = IsSuperset(set, subset) -- true
  ```
]=]
local function isSuperset<any>(superset: { [any]: boolean }, subset: { [any]: boolean }): boolean
	return isSubset(subset, superset)
end

return isSuperset


--selene: allow(unused_variable)
local None = require("./None")

--[=[
  @type None None
  @within Sift
]=]
export type None = typeof(None)
export type Dictionary<K, V> = { [K]: V }
export type Array<T> = Dictionary<number, T>
export type Set<T> = Dictionary<T, boolean>
export type Table = Dictionary<any, any>

export type AnyDictionary = Dictionary<any, any>

return nil


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

--[=[
	@function equalObjects
	@within Sift

	@param ... ...table -- The tables to compare.
	@return boolean -- Whether or not the tables are equal.

	Compares two or more tables to see if they are equal.

	```lua
	local a = { hello = "world" }
	local b = { hello = "world" }

	local equal = EqualObjects(a, b) -- true
	```
]=]
local function equalObjects(...: _T.Table): boolean
	local firstItem = select(1, ...)

	for i = 2, select("#", ...) do
		if firstItem ~= select(i, ...) then
			return false
		end
	end

	return true
end

return equalObjects


return {
	equalObjects = require("@self/equalObjects"),
	func = require("@self/func"),
	isEmpty = require("@self/isEmpty"),
}


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

--[=[
  @function isEmpty
  @within Sift
  @since v0.0.1

  @param table table -- The table to check.
  @return boolean -- Whether or not the table is empty.

  Checks whether or not a table is empty.

  ```lua
  local a = {}
  local b = { hello = "world" }

  local value = isEmpty(a) -- true
  local value = isEmpty(b) -- false
  ```
]=]
local function isEmpty(table: _T.Table): boolean
	return next(table) == nil
end

return isEmpty


-- -----------------------------------------------------------------------------
--               Batched Yield-Safe Signal Implementation                     --
-- This is a Signal class which has effectively identical behavior to a       --
-- normal RBXScriptSignal, with the only difference being a couple extra      --
-- stack frames at the bottom of the stack trace when an error is thrown.     --
-- This implementation caches runner coroutines, so the ability to yield in   --
-- the signal handlers comes at minimal extra cost over a naive signal        --
-- implementation that either always or never spawns a thread.                --
--                                                                            --
-- License:                                                                   --
--   Licensed under the MIT license.                                          --
--                                                                            --
-- Authors:                                                                   --
--   stravant - July 31st, 2021 - Created the file.                           --
--   sleitnick - August 3rd, 2021 - Modified for Knit.                        --
-- -----------------------------------------------------------------------------

-- Signal types
export type Connection = {
	Disconnect: (self: Connection) -> (),
	Destroy: (self: Connection) -> (),
	Connected: boolean,
}

export type Signal<T...> = {
	Fire: (self: Signal<T...>, T...) -> (),
	FireDeferred: (self: Signal<T...>, T...) -> (),
	Connect: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
	Once: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
	DisconnectAll: (self: Signal<T...>) -> (),
	GetConnections: (self: Signal<T...>) -> { Connection },
	Destroy: (self: Signal<T...>) -> (),
	Wait: (self: Signal<T...>) -> T...,
}

export type StrictSignal<T = (...any) -> (), U... = ...any> = {
	Fire: (self: StrictSignal<T, U...>, U...) -> (),
	FireDeferred: (self: StrictSignal<T, U...>, U...) -> (),
	Connect: (self: StrictSignal<T, U...>, fn: T) -> Connection,
	Once: (self: StrictSignal<T, U...>, fn: T) -> Connection,
	DisconnectAll: (self: StrictSignal<T, U...>) -> (),
	GetConnections: (self: StrictSignal<T, U...>) -> { Connection },
	Destroy: (self: StrictSignal<T, U...>) -> (),
	Wait: (self: StrictSignal<T, U...>) -> U...,
}

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

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

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

--[=[
	@within Signal
	@interface SignalConnection
	.Connected boolean
	.Disconnect (SignalConnection) -> ()

	Represents a connection to a signal.
	```lua
	local connection = signal:Connect(function() end)
	print(connection.Connected) --> true
	connection:Disconnect()
	print(connection.Connected) --> false
	```
]=]

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

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

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

Connection.Destroy = Connection.Disconnect

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

--[=[
	@within Signal
	@type ConnectionFn (...any) -> ()

	A function connected to a signal.
]=]

--[=[
	@class Signal

	A Signal is a data structure that allows events to be dispatched
	and observed.

	This implementation is a direct copy of the de facto standard, [GoodSignal](https://devforum.roblox.com/t/lua-signal-class-comparison-optimal-goodsignal-class/1387063),
	with some added methods and typings.

	For example:
	```lua
	local signal = Signal.new()

	-- Subscribe to a signal:
	signal:Connect(function(msg)
		print("Got message:", msg)
	end)

	-- Dispatch an event:
	signal:Fire("Hello world!")
	```
]=]
local Signal = {}
Signal.__index = Signal

--[=[
	Constructs a new Signal

	@return Signal
]=]
function Signal.new<T...>(): Signal<T...>
	local self = setmetatable({
		_handlerListHead = false,
		_proxyHandler = nil,
		_yieldedThreads = nil,
	}, Signal)

	return self
end

--[=[
	Constructs a new Signal that wraps around an RBXScriptSignal.

	@param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap
	@return Signal

	For example:
	```lua
	local signal = Signal.Wrap(workspace.ChildAdded)
	signal:Connect(function(part) print(part.Name .. " added") end)
	Instance.new("Part").Parent = workspace
	```
]=]
function Signal.Wrap<T...>(rbxScriptSignal: RBXScriptSignal): Signal<T...>
	assert(
		typeof(rbxScriptSignal) == "RBXScriptSignal",
		"Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal)
	)

	local signal = Signal.new()
	signal._proxyHandler = rbxScriptSignal:Connect(function(...)
		signal:Fire(...)
	end)

	return signal
end

--[=[
	Checks if the given object is a Signal.

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

--[=[
	@param fn ConnectionFn
	@return SignalConnection

	Connects a function to the signal, which will be called anytime the signal is fired.
	```lua
	signal:Connect(function(msg, num)
		print(msg, num)
	end)

	signal:Fire("Hello", 25)
	```
]=]
function Signal:Connect(fn)
	local connection = setmetatable({
		Connected = true,
		_signal = self,
		_fn = fn,
		_next = false,
	}, Connection)

	if self._handlerListHead then
		connection._next = self._handlerListHead
		self._handlerListHead = connection
	else
		self._handlerListHead = connection
	end

	return connection
end

--[=[
	@deprecated v1.3.0 -- Use `Signal:Once` instead.
	@param fn ConnectionFn
	@return SignalConnection
]=]
function Signal:ConnectOnce(fn)
	return self:Once(fn)
end

--[=[
	@param fn ConnectionFn
	@return SignalConnection

	Connects a function to the signal, which will be called the next time the signal fires. Once
	the connection is triggered, it will disconnect itself.
	```lua
	signal:Once(function(msg, num)
		print(msg, num)
	end)

	signal:Fire("Hello", 25)
	signal:Fire("This message will not go through", 10)
	```
]=]
function Signal:Once(fn)
	local connection
	local done = false

	connection = self:Connect(function(...)
		if done then
			return
		end

		done = true
		connection:Disconnect()
		fn(...)
	end)

	return connection
end

function Signal:GetConnections()
	local items = {}

	local item = self._handlerListHead
	while item do
		table.insert(items, item)
		item = item._next
	end

	return items
end

-- Disconnect all handlers. Since we use a linked list it suffices to clear the
-- reference to the head handler.
--[=[
	Disconnects all connections from the signal.
	```lua
	signal:DisconnectAll()
	```
]=]
function Signal:DisconnectAll()
	local item = self._handlerListHead
	while item do
		item.Connected = false
		item = item._next
	end
	self._handlerListHead = false

	local yieldedThreads = rawget(self, "_yieldedThreads")
	if yieldedThreads then
		for thread in yieldedThreads do
			if coroutine.status(thread) == "suspended" then
				warn(debug.traceback(thread, "signal disconnected; yielded thread cancelled", 2))
				task.cancel(thread)
			end
		end
		table.clear(self._yieldedThreads)
	end
end

-- Signal:Fire(...) implemented by running the handler functions on the
-- coRunnerThread, and any time the resulting thread yielded without returning
-- to us, that means that it yielded to the Roblox scheduler and has been taken
-- over by Roblox scheduling, meaning we have to make a new coroutine runner.
--[=[
	@param ... any

	Fire the signal, which will call all of the connected functions with the given arguments.
	```lua
	signal:Fire("Hello")

	-- Any number of arguments can be fired:
	signal:Fire("Hello", 32, {Test = "Test"}, true)
	```
]=]
function Signal:Fire(...)
	local item = self._handlerListHead
	while item do
		if item.Connected then
			if not freeRunnerThread then
				freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
			end
			task.spawn(freeRunnerThread, item._fn, ...)
		end
		item = item._next
	end
end

--[=[
	@param ... any

	Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse.
	```lua
	signal:FireDeferred("Hello")
	```
]=]
function Signal:FireDeferred(...)
	local item = self._handlerListHead
	while item do
		local conn = item
		task.defer(function(...)
			if conn.Connected then
				conn._fn(...)
			end
		end, ...)
		item = item._next
	end
end

--[=[
	@return ... any
	@yields

	Yields the current thread until the signal is fired, and returns the arguments fired from the signal.
	Yielding the current thread is not always desirable. If the desire is to only capture the next event
	fired, using `Once` might be a better solution.
	```lua
	task.spawn(function()
		local msg, num = signal:Wait()
		print(msg, num) --> "Hello", 32
	end)
	signal:Fire("Hello", 32)
	```
]=]
function Signal:Wait()
	local yieldedThreads = rawget(self, "_yieldedThreads")
	if not yieldedThreads then
		yieldedThreads = {}
		rawset(self, "_yieldedThreads", yieldedThreads)
	end

	local thread = coroutine.running()
	yieldedThreads[thread] = true

	self:Once(function(...)
		yieldedThreads[thread] = nil
		task.spawn(thread, ...)
	end)

	return coroutine.yield()
end

--[=[
	Cleans up the signal.

	Technically, this is only necessary if the signal is created using
	`Signal.Wrap`. Connections should be properly GC'd once the signal
	is no longer referenced anywhere. However, it is still good practice
	to include ways to strictly clean up resources. Calling `Destroy`
	on a signal will also disconnect all connections immediately.
	```lua
	signal:Destroy()
	```
]=]
function Signal:Destroy()
	self:DisconnectAll()

	local proxyHandler = rawget(self, "_proxyHandler")
	if proxyHandler then
		proxyHandler:Disconnect()
	end
end

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

return require("signal") :: {
	new: typeof(Signal.new),
	Wrap: typeof(Signal.Wrap),
	Is: typeof(Signal.Is),
}


--!strict

-- TableUtil
-- Stephen Leitnick
-- September 13, 2017

--[=[
	@class TableUtil

	A collection of helpful table utility functions. Many of these functions are carried over from JavaScript or
	Python that are not present in Lua.

	Tables that only work specifically with arrays or dictionaries are marked as such in the documentation.

	:::info Immutability
	All functions (_except_ `SwapRemove`, `SwapRemoveFirstValue`, and `Lock`) treat tables as immutable and will return
	copies of the given table(s) with the operations performed on the copies.
]=]
local TableUtil = {}

local HttpService = game:GetService("HttpService")
local rng = Random.new()

--[=[
	@within TableUtil
	@function Copy
	@param tbl table -- Table to copy
	@param deep boolean? -- Whether or not to perform a deep copy
	@return table

	Creates a copy of the given table. By default, a shallow copy is
	performed. For deep copies, a second boolean argument must be
	passed to the function.

	:::caution No cyclical references
	Deep copies are _not_ protected against cyclical references. Passing
	a table with cyclical references _and_ the `deep` parameter set to
	`true` will result in a stack-overflow.
]=]
local function Copy<T>(t: T, deep: boolean?): T
	if not deep then
		return (table.clone(t :: any) :: any) :: T
	end
	local function DeepCopy(tbl: { any })
		local tCopy = table.clone(tbl)
		for k, v in tCopy do
			if type(v) == "table" then
				tCopy[k] = DeepCopy(v)
			end
		end
		return tCopy
	end
	return DeepCopy(t :: any) :: T
end

--[=[
	@within TableUtil
	@function Sync
	@param srcTbl table -- Source table
	@param templateTbl table -- Template table
	@return table

	Synchronizes the `srcTbl` based on the `templateTbl`. This will make
	sure that `srcTbl` has all of the same keys as `templateTbl`, including
	removing keys in `srcTbl` that are not present in `templateTbl`. This
	is a _deep_ operation, so any nested tables will be synchronized as
	well.

	```lua
	local template = {kills = 0, deaths = 0, xp = 0}
	local data = {kills = 10, experience = 12}
	data = TableUtil.Sync(data, template)
	print(data) --> {kills = 10, deaths = 0, xp = 0}
	```

	:::caution Data Loss Warning
	This is a two-way sync, so the source table will have data
	_removed_ that isn't found in the template table. This can
	be problematic if used for player data, where there might
	be dynamic data added that isn't in the template.

	For player data, use `TableUtil.Reconcile` instead.
]=]
local function Sync<S, T>(srcTbl: S, templateTbl: T): T
	assert(type(srcTbl) == "table", "First argument must be a table")
	assert(type(templateTbl) == "table", "Second argument must be a table")

	local tbl = table.clone(srcTbl)

	-- If 'tbl' has something 'templateTbl' doesn't, then remove it from 'tbl'
	-- If 'tbl' has something of a different type than 'templateTbl', copy from 'templateTbl'
	-- If 'templateTbl' has something 'tbl' doesn't, then add it to 'tbl'
	for k, v in pairs(tbl) do
		local vTemplate = templateTbl[k]

		-- Remove keys not within template:
		if vTemplate == nil then
			tbl[k] = nil

			-- Synchronize data types:
		elseif type(v) ~= type(vTemplate) then
			if type(vTemplate) == "table" then
				tbl[k] = Copy(vTemplate, true)
			else
				tbl[k] = vTemplate
			end

			-- Synchronize sub-tables:
		elseif type(v) == "table" then
			tbl[k] = Sync(v, vTemplate)
		end
	end

	-- Add any missing keys:
	for k, vTemplate in pairs(templateTbl) do
		local v = tbl[k]

		if v == nil then
			if type(vTemplate) == "table" then
				tbl[k] = Copy(vTemplate, true)
			else
				tbl[k] = vTemplate
			end
		end
	end

	return (tbl :: any) :: T
end

--[=[
	@within TableUtil
	@function Reconcile
	@param source table
	@param template table
	@return table

	Performs a one-way sync on the `source` table against the
	`template` table. Any keys found in `template` that are
	not found in `source` will be added to `source`. This is
	useful for syncing player data against data template tables
	to ensure players have all the necessary keys, while
	maintaining existing keys that may no longer be in the
	template.

	This is a deep operation, so nested tables will also be
	properly reconciled.

	```lua
	local template = {kills = 0, deaths = 0, xp = 0}
	local data = {kills = 10, abc = 20}
	local correctedData = TableUtil.Reconcile(data, template)

	print(correctedData) --> {kills = 10, deaths = 0, xp = 0, abc = 20}
	```
]=]
local function Reconcile<S, T>(src: S, template: T): S & T
	assert(type(src) == "table", "First argument must be a table")
	assert(type(template) == "table", "Second argument must be a table")

	local tbl = table.clone(src)

	for k, v in template do
		local sv = src[k]
		if sv == nil then
			if type(v) == "table" then
				tbl[k] = Copy(v, true)
			else
				tbl[k] = v
			end
		elseif type(sv) == "table" then
			if type(v) == "table" then
				tbl[k] = Reconcile(sv, v)
			else
				tbl[k] = Copy(sv, true)
			end
		end
	end

	return (tbl :: any) :: S & T
end

--[=[
	@within TableUtil
	@function SwapRemove
	@param tbl table -- Array
	@param i number -- Index

	Removes index `i` in the table by swapping the value at `i` with
	the last value in the array, and then trimming off the last
	value from the array.

	This allows removal of the value at `i` in `O(1)` time, but does
	not preserve array ordering. If a value needs to be removed from
	an array, but ordering of the array does not matter, using
	`SwapRemove` is always preferred over `table.remove`.

	In the following example, we remove "B" at index 2. SwapRemove does
	this by moving the last value "E" over top of "B", and then trimming
	off "E" at the end of the array:
	```lua
	local t = {"A", "B", "C", "D", "E"}
	TableUtil.SwapRemove(t, 2) -- Remove "B"
	print(t) --> {"A", "E", "C", "D"}
	```

	:::note Arrays only
	This function works on arrays, but not dictionaries.
]=]
local function SwapRemove<T>(t: { T }, i: number)
	local n = #t
	t[i] = t[n]
	t[n] = nil
end

--[=[
	@within TableUtil
	@function SwapRemoveFirstValue
	@param tbl table -- Array
	@param v any -- Value to find
	@return number?

	Performs `table.find(tbl, v)` to find the index of the given
	value, and then performs `TableUtil.SwapRemove` on that index.

	```lua
	local t = {"A", "B", "C", "D", "E"}
	TableUtil.SwapRemoveFirstValue(t, "C")
	print(t) --> {"A", "B", "E", "D"}
	```

	:::note Arrays only
	This function works on arrays, but not dictionaries.
]=]
local function SwapRemoveFirstValue<T>(t: { T }, v: T): number?
	local index: number? = table.find(t, v)
	if index then
		SwapRemove(t, index)
	end
	return index
end

--[=[
	@within TableUtil
	@function Map
	@param tbl table
	@param predicate (value: any, key: any, tbl: table) -> newValue: any
	@return table

	Performs a map operation against the given table, which can be used to
	map new values based on the old values at given keys/indices.

	For example:

	```lua
	local t = {A = 10, B = 20, C = 30}
	local t2 = TableUtil.Map(t, function(value)
		return value * 2
	end)
	print(t2) --> {A = 20, B = 40, C = 60}
	```
]=]
local function Map<T, M>(t: { T }, f: (T, number, { T }) -> M): { M }
	assert(type(t) == "table", "First argument must be a table")
	assert(type(f) == "function", "Second argument must be a function")
	local newT = table.create(#t)
	for k, v in t do
		newT[k] = f(v, k, t)
	end
	return newT
end

--[=[
	@within TableUtil
	@function Filter
	@param tbl table
	@param predicate (value: any, key: any, tbl: table) -> keep: boolean
	@return table

	Performs a filter operation against the given table, which can be used to
	filter out unwanted values from the table.

	For example:

	```lua
	local t = {A = 10, B = 20, C = 30}
	local t2 = TableUtil.Filter(t, function(value, key)
		return value > 15
	end)
	print(t2) --> {B = 40, C = 60}
	```
]=]
local function Filter<T>(t: { T }, predicate: (T, any, { T }) -> boolean): { T }
	assert(type(t) == "table", "First argument must be a table")
	assert(type(predicate) == "function", "Second argument must be a function")
	local newT = table.create(#t)
	if #t > 0 then
		local n = 0
		for i, v in t do
			if predicate(v, i, t) then
				n += 1
				newT[n] = v
			end
		end
	else
		for k, v in t do
			if predicate(v, k, t) then
				newT[k] = v
			end
		end
	end
	return newT
end

--[=[
	@within TableUtil
	@function Reduce
	@param tbl table
	@param predicate (accumulator: any, value: any, index: any, tbl: table) -> result: any
	@return table

	Performs a reduce operation against the given table, which can be used to
	reduce the table into a single value. This could be used to sum up a table
	or transform all the values into a compound value of any kind.

	For example:

	```lua
	local t = {10, 20, 30, 40}
	local result = TableUtil.Reduce(t, function(accum, value)
		return accum + value
	end)
	print(result) --> 100
	```
]=]
local function Reduce<T, R>(t: { T }, predicate: (R, T, any, { T }) -> R, init: R): R
	assert(type(t) == "table", "First argument must be a table")
	assert(type(predicate) == "function", "Second argument must be a function")
	local result = init :: R
	if #t > 0 then
		local start = 1
		if init == nil then
			result = (t[1] :: any) :: R
			start = 2
		end
		for i = start, #t do
			result = predicate(result, t[i], i, t)
		end
	else
		local start = nil
		if init == nil then
			result = (next(t) :: any) :: R
			start = result
		end
		for k, v in next, t, start :: any? do
			result = predicate(result, v, k, t)
		end
	end
	return result
end

--[=[
	@within TableUtil
	@function Assign
	@param target table
	@param ... table
	@return table

	Copies all values of the given tables into the `target` table.

	```lua
	local t = {A = 10}
	local t2 = {B = 20}
	local t3 = {C = 30, D = 40}
	local newT = TableUtil.Assign(t, t2, t3)
	print(newT) --> {A = 10, B = 20, C = 30, D = 40}
	```
]=]
local function Assign<T>(target: { T }, ...: { any }): { T } & { any }
	local tbl = table.clone(target)
	for _, src in { ... } do
		for k, v in src do
			tbl[k] = v
		end
	end
	return tbl
end

--[=[
	@within TableUtil
	@function Extend
	@param target table
	@param extension table
	@return table

	Extends the target array with the extension array.

	```lua
	local t = {10, 20, 30}
	local t2 = {30, 40, 50}
	local tNew = TableUtil.Extend(t, t2)
	print(tNew) --> {10, 20, 30, 30, 40, 50}
	```

	:::note Arrays only
	This function works on arrays, but not dictionaries.
]=]
local function Extend<T, E>(target: { T }, extension: { E }): { T } & { E }
	local tbl = table.clone(target) :: { any }
	for _, v in extension do
		table.insert(tbl, v)
	end
	return tbl
end

--[=[
	@within TableUtil
	@function Reverse
	@param tbl table
	@return table

	Reverses the array.

	```lua
	local t = {1, 5, 10}
	local tReverse = TableUtil.Reverse(t)
	print(tReverse) --> {10, 5, 1}
	```

	:::note Arrays only
	This function works on arrays, but not dictionaries.
]=]
local function Reverse<T>(tbl: { T }): { T }
	local n = #tbl
	local tblRev = table.create(n)
	for i = 1, n do
		tblRev[i] = tbl[n - i + 1]
	end
	return tblRev
end

--[=[
	@within TableUtil
	@function Shuffle
	@param tbl table
	@param rngOverride Random?
	@return table

	Shuffles the table.

	```lua
	local t = {1, 2, 3, 4, 5, 6, 7, 8, 9}
	local shuffled = TableUtil.Shuffle(t)
	print(shuffled) --> e.g. {9, 4, 6, 7, 3, 1, 5, 8, 2}
	```

	:::note Arrays only
	This function works on arrays, but not dictionaries.
]=]
local function Shuffle<T>(tbl: { T }, rngOverride: Random?): { T }
	assert(type(tbl) == "table", "First argument must be a table")
	local shuffled = table.clone(tbl)
	local random = if typeof(rngOverride) == "Random" then rngOverride else rng
	for i = #tbl, 2, -1 do
		local j = random:NextInteger(1, i)
		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
	end
	return shuffled
end

--[=[
	@within TableUtil
	@function Sample
	@param tbl table
	@param sampleSize number
	@param rngOverride Random?
	@return table

	Returns a random sample of the table.

	```lua
	local t = {1, 2, 3, 4, 5, 6, 7, 8, 9}
	local sample = TableUtil.Sample(t, 3)
	print(sample) --> e.g. {6, 2, 5}
	```

	:::note Arrays only
	This function works on arrays, but not dictionaries.
]=]
local function Sample<T>(tbl: { T }, size: number, rngOverride: Random?): { T }
	assert(type(tbl) == "table", "First argument must be a table")
	assert(type(size) == "number", "Second argument must be a number")

	-- If given table is empty, just return a new empty table:
	local len = #tbl
	if len == 0 then
		return {}
	end

	local shuffled = table.clone(tbl)
	local sample = table.create(size)
	local random = if typeof(rngOverride) == "Random" then rngOverride else rng

	-- Clamp sample size to be no larger than the given table size:
	size = math.clamp(size, 1, len)

	for i = 1, size do
		local j = random:NextInteger(i, len)
		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
	end

	table.move(shuffled, 1, size, 1, sample)

	return sample
end

--[=[
	@within TableUtil
	@function Flat
	@param tbl table
	@param depth number?
	@return table

	Returns a new table where all sub-arrays have been
	bubbled up to the top. The depth at which the scan
	is performed is dictated by the `depth` parameter,
	which is set to `1` by default.

	```lua
	local t = {{10, 20}, {90, 100}, {30, 15}}
	local flat = TableUtil.Flat(t)
	print(flat) --> {10, 20, 90, 100, 30, 15}
	```

	:::note Arrays only
	This function works on arrays, but not dictionaries.
]=]
local function Flat<T>(tbl: { T }, depth: number?): { T }
	local maxDepth: number = if type(depth) == "number" then depth else 1
	local flatTbl = table.create(#tbl)
	local function Scan(t: { any }, d: number)
		for _, v in t do
			if type(v) == "table" and d < maxDepth then
				Scan(v, d + 1)
			else
				table.insert(flatTbl, v)
			end
		end
	end
	Scan(tbl, 0)
	return flatTbl
end

--[=[
	@within TableUtil
	@function FlatMap
	@param tbl table
	@param predicate (key: any, value: any, tbl: table) -> newValue: any
	@return table

	Calls `TableUtil.Map` on the given table and predicate, and then
	calls `TableUtil.Flat` on the result from the map operation.

	```lua
	local t = {10, 20, 30}
	local result = TableUtil.FlatMap(t, function(value)
		return {value, value * 2}
	end)
	print(result) --> {10, 20, 20, 40, 30, 60}
	```

	:::note Arrays only
	This function works on arrays, but not dictionaries.
]=]
local function FlatMap<T, M>(tbl: { T }, callback: (T, number, { T }) -> M): { M }
	return Flat(Map(tbl, callback))
end

--[=[
	@within TableUtil
	@function Keys
	@param tbl table
	@return table

	Returns an array with all the keys in the table.

	```lua
	local t = {A = 10, B = 20, C = 30}
	local keys = TableUtil.Keys(t)
	print(keys) --> {"A", "B", "C"}
	```

	:::caution Ordering
	The ordering of the keys is never guaranteed. If order is imperative, call
	`table.sort` on the resulting `keys` array.
	```lua
	local keys = TableUtil.Keys(t)
	table.sort(keys)
	```
]=]
local function Keys<K, V>(tbl: { [K]: V }): { K }
	local keys = table.create(#tbl)
	for k in tbl do
		table.insert(keys, k)
	end
	return keys
end

--[=[
	@within TableUtil
	@function Values
	@param tbl table
	@return table

	Returns an array with all the values in the table.

	```lua
	local t = {A = 10, B = 20, C = 30}
	local values = TableUtil.Values(t)
	print(values) --> {10, 20, 30}
	```

	:::caution Ordering
	The ordering of the values is never guaranteed. If order is imperative, call
	`table.sort` on the resulting `values` array.
	```lua
	local values = TableUtil.Values(t)
	table.sort(values)
	```
]=]
local function Values<K, V>(tbl: { [K]: V }): { V }
	local values = table.create(#tbl)
	for _, v in tbl do
		table.insert(values, v)
	end
	return values
end

--[=[
	@within TableUtil
	@function Find
	@param tbl table
	@param callback (value: any, index: any, tbl: table) -> boolean
	@return (value: any?, key: any?)

	Performs a linear scan across the table and calls `callback` on
	each item in the array. Returns the value and key of the first
	pair in which the callback returns `true`.

	```lua
	local t = {
		{Name = "Bob", Age = 20};
		{Name = "Jill", Age = 30};
		{Name = "Ann", Age = 25};
	}

	-- Find first person who has a name starting with J:
	local firstPersonWithJ = TableUtil.Find(t, function(person)
		return person.Name:sub(1, 1):lower() == "j"
	end)

	print(firstPersonWithJ) --> {Name = "Jill", Age = 30}
	```

	:::caution Dictionary Ordering
	While `Find` can also be used with dictionaries, dictionary ordering is never
	guaranteed, and thus the result could be different if there are more
	than one possible matches given the data and callback function.
]=]
local function Find<K, V>(tbl: { [K]: V }, callback: (V, K, { [K]: V }) -> boolean): (V?, K?)
	for k, v in tbl do
		if callback(v, k, tbl) then
			return v, k
		end
	end
	return nil, nil
end

--[=[
	@within TableUtil
	@function Every
	@param tbl table
	@param callback (value: any, index: any, tbl: table) -> boolean
	@return boolean

	Returns `true` if the `callback` also returns `true` for _every_
	item in the table.

	```lua
	local t = {10, 20, 40, 50, 60}

	local allAboveZero = TableUtil.Every(t, function(value)
		return value > 0
	end)

	print("All above zero:", allAboveZero) --> All above zero: true
	```
]=]
local function Every<K, V>(tbl: { [K]: V }, callback: (V, K, { [K]: V }) -> boolean): boolean
	for k, v in tbl do
		if not callback(v, k, tbl) then
			return false
		end
	end
	return true
end

--[=[
	@within TableUtil
	@function Some
	@param tbl table
	@param callback (value: any, index: any, tbl: table) -> boolean
	@return boolean

	Returns `true` if the `callback` also returns `true` for _at least
	one_ of the items in the table.

	```lua
	local t = {10, 20, 40, 50, 60}

	local someBelowTwenty = TableUtil.Some(t, function(value)
		return value < 20
	end)

	print("Some below twenty:", someBelowTwenty) --> Some below twenty: true
	```
]=]
local function Some<K, V>(tbl: { [K]: V }, callback: (V, K, { [K]: V }) -> boolean): boolean
	for k, v in tbl do
		if callback(v, k, tbl) then
			return true
		end
	end
	return false
end

--[=[
	@within TableUtil
	@function Truncate
	@param tbl table
	@param length number
	@return table

	Returns a new table truncated to the length of `length`. Any length
	equal or greater than the current length will simply return a
	shallow copy of the table.

	```lua
	local t = {10, 20, 30, 40, 50, 60, 70, 80}
	local tTruncated = TableUtil.Truncate(t, 3)
	print(tTruncated) --> {10, 20, 30}
	```
]=]
local function Truncate<T>(tbl: { T }, len: number): { T }
	local n = #tbl
	len = math.clamp(len, 1, n)
	return if len == n then table.clone(tbl) else table.move(tbl, 1, len, 1, table.create(len))
end

--[=[
	@within TableUtil
	@function Zip
	@param ... table
	@return (iter: (t: table, k: any) -> (key: any?, values: table?), tbl: table, startIndex: any?)

	Returns an iterator that can scan through multiple tables at the same time side-by-side, matching
	against shared keys/indices.

	```lua
	local t1 = {10, 20, 30, 40, 50}
	local t2 = {60, 70, 80, 90, 100}

	for key,values in TableUtil.Zip(t1, t2) do
		print(key, values)
	end

	--[[
		Outputs:
		1 {10, 60}
		2 {20, 70}
		3 {30, 80}
		4 {40, 90}
		5 {50, 100}
	--]]
	```
]=]
local function Zip(...: { [any]: any }): ((t: { any }, k: any) -> (any, any), { any }, any)
	assert(select("#", ...) > 0, "Must supply at least 1 table")
	local function ZipIteratorArray(all: { any }, k: number): (number?, { any }?)
		k += 1
		local values = {}
		for i, t in all do
			local v = t[k]
			if v ~= nil then
				values[i] = v
			else
				return nil, nil
			end
		end
		return k, values
	end
	local function ZipIteratorMap(all: { [any]: any }, k: any): (number?, { any }?)
		local values = {}
		for i, t in all do
			local v = next(t, k)
			if v ~= nil then
				values[i] = v
			else
				return nil, nil
			end
		end
		return k, values
	end
	local all = { ... }
	if #all[1] > 0 then
		return ZipIteratorArray, all, 0
	else
		return ZipIteratorMap, all, nil
	end
end

--[=[
	@within TableUtil
	@function Lock
	@param tbl table
	@return table

	Locks the table using `table.freeze`, as well as any
	nested tables within the given table. This will lock
	the whole deep structure of the table, disallowing any
	further modifications.

	```lua
	local tbl = {xyz = {abc = 32}}
	tbl.xyz.abc = 28 -- Works fine
	TableUtil.Lock(tbl)
	tbl.xyz.abc = 64 -- Will throw an error (cannot modify readonly table)
	```
]=]
local function Lock<T>(tbl: T): T
	local function Freeze(t: { [any]: any })
		for k, v in pairs(t) do
			if type(v) == "table" then
				t[k] = Freeze(v)
			end
		end
		return table.freeze(t)
	end
	return Freeze(tbl :: any)
end

--[=[
	@within TableUtil
	@function IsEmpty
	@param tbl table
	@return boolean

	Returns `true` if the given table is empty. This is
	simply performed by checking if `next(tbl)` is `nil`
	and works for both arrays and dictionaries. This is
	useful when needing to check if a table is empty but
	not knowing if it is an array or dictionary.

	```lua
	TableUtil.IsEmpty({}) -- true
	TableUtil.IsEmpty({"abc"}) -- false
	TableUtil.IsEmpty({abc = 32}) -- false
	```
]=]
local function IsEmpty(tbl: { any }): boolean
	return next(tbl) == nil
end

--[=[
	@within TableUtil
	@function EncodeJSON
	@param value any
	@return string

	Proxy for [`HttpService:JSONEncode`](https://developer.roblox.com/en-us/api-reference/function/HttpService/JSONEncode).
]=]
local function EncodeJSON(value: any): string
	return HttpService:JSONEncode(value)
end

--[=[
	@within TableUtil
	@function DecodeJSON
	@param value any
	@return string

	Proxy for [`HttpService:JSONDecode`](https://developer.roblox.com/en-us/api-reference/function/HttpService/JSONDecode).
]=]
local function DecodeJSON(str: string): any
	return HttpService:JSONDecode(str)
end

TableUtil.Copy = Copy
TableUtil.Sync = Sync
TableUtil.Reconcile = Reconcile
TableUtil.SwapRemove = SwapRemove
TableUtil.SwapRemoveFirstValue = SwapRemoveFirstValue
TableUtil.Map = Map
TableUtil.Filter = Filter
TableUtil.Reduce = Reduce
TableUtil.Assign = Assign
TableUtil.Extend = Extend
TableUtil.Reverse = Reverse
TableUtil.Shuffle = Shuffle
TableUtil.Sample = Sample
TableUtil.Flat = Flat
TableUtil.FlatMap = FlatMap
TableUtil.Keys = Keys
TableUtil.Values = Values
TableUtil.Find = Find
TableUtil.Every = Every
TableUtil.Some = Some
TableUtil.Truncate = Truncate
TableUtil.Zip = Zip
TableUtil.Lock = Lock
TableUtil.IsEmpty = IsEmpty
TableUtil.EncodeJSON = EncodeJSON
TableUtil.DecodeJSON = DecodeJSON

return require("tableUtil") :: typeof(TableUtil)


--!strict

local RunService = game:GetService("RunService")

export type Trove = {
	Extend: (self: Trove) -> Trove,
	Clone: <T>(self: Trove, instance: T & Instance) -> T,
	Construct: <T, A...>(self: Trove, class: Constructable<T, A...>, A...) -> T,
	Connect: (self: Trove, signal: SignalLike | RBXScriptSignal, fn: (...any) -> ...any) -> ConnectionLike,
	BindToRenderStep: (self: Trove, name: string, priority: number, fn: (dt: number) -> ()) -> (),
	AddPromise: <T>(self: Trove, promise: T & PromiseLike) -> T,
	Add: <T>(self: Trove, object: T & Trackable, cleanupMethod: string?) -> T,
	Remove: <T>(self: Trove, object: T & Trackable) -> boolean,
	Clean: (self: Trove) -> (),
	AttachToInstance: (self: Trove, instance: Instance) -> RBXScriptConnection,
	Destroy: (self: Trove) -> (),
}

type TroveInternal = Trove & {
	_objects: { any },
	_cleaning: boolean,
	_findAndRemoveFromObjects: (self: TroveInternal, object: any, cleanup: boolean) -> boolean,
	_cleanupObject: (self: TroveInternal, object: any, cleanupMethod: string?) -> (),
}

--[=[
	@within Trove
	@type Trackable Instance | RBXScriptConnection | ConnectionLike | PromiseLike | thread | ((...any) -> ...any) | Destroyable | DestroyableLowercase | Disconnectable | DisconnectableLowercase
	Represents all trackable objects by Trove.
]=]
export type Trackable =
	Instance
	| RBXScriptConnection
	| ConnectionLike
	| PromiseLike
	| thread
	| ((...any) -> ...any)
	| Destroyable
	| DestroyableLowercase
	| Disconnectable
	| DisconnectableLowercase

--[=[
	@within Trove
	@interface ConnectionLike
	.Connected boolean
	.Disconnect (self) -> ()
]=]
type ConnectionLike = {
	Connected: boolean,
	Disconnect: (self: ConnectionLike) -> (),
}

--[=[
	@within Trove
	@interface SignalLike
	.Connect (self, callback: (...any) -> ...any) -> ConnectionLike
	.Once (self, callback: (...any) -> ...any) -> ConnectionLike
]=]
type SignalLike = {
	Connect: (self: SignalLike, callback: (...any) -> ...any) -> ConnectionLike,
	Once: (self: SignalLike, callback: (...any) -> ...any) -> ConnectionLike,
}

--[=[
	@within Trove
	@interface PromiseLike
	.getStatus (self) -> string
	.finally (self, callback: (...any) -> ...any) -> PromiseLike
	.cancel (self) -> ()
]=]
type PromiseLike = {
	getStatus: (self: PromiseLike) -> string,
	finally: (self: PromiseLike, callback: (...any) -> ...any) -> PromiseLike,
	cancel: (self: PromiseLike) -> (),
}

--[=[
	@within Trove
	@type Constructable { new: (A...) -> T } | (A...) -> T
]=]
type Constructable<T, A...> = { new: (A...) -> T } | (A...) -> T

--[=[
	@within Trove
	@interface Destroyable
	.disconnect (self) -> ()
]=]
type Destroyable = {
	Destroy: (self: Destroyable) -> (),
}

--[=[
	@within Trove
	@interface DestroyableLowercase
	.disconnect (self) -> ()
]=]
type DestroyableLowercase = {
	destroy: (self: DestroyableLowercase) -> (),
}

--[=[
	@within Trove
	@interface Disconnectable
	.disconnect (self) -> ()
]=]
type Disconnectable = {
	Disconnect: (self: Disconnectable) -> (),
}

--[=[
	@within Trove
	@interface DisconnectableLowercase
	.disconnect (self) -> ()
]=]
type DisconnectableLowercase = {
	disconnect: (self: DisconnectableLowercase) -> (),
}

local FN_MARKER = newproxy()
local THREAD_MARKER = newproxy()
local GENERIC_OBJECT_CLEANUP_METHODS = table.freeze({ "Destroy", "Disconnect", "destroy", "disconnect" })

local function GetObjectCleanupFunction(object: any, cleanupMethod: string?)
	local t = typeof(object)

	if t == "function" then
		return FN_MARKER
	elseif t == "thread" then
		return THREAD_MARKER
	end

	if cleanupMethod then
		return cleanupMethod
	end

	if t == "Instance" then
		return "Destroy"
	elseif t == "RBXScriptConnection" then
		return "Disconnect"
	elseif t == "table" then
		for _, genericCleanupMethod in GENERIC_OBJECT_CLEANUP_METHODS do
			if typeof(object[genericCleanupMethod]) == "function" then
				return genericCleanupMethod
			end
		end
	end

	error(`failed to get cleanup function for object {t}: {object}`, 3)
end

local function AssertPromiseLike(object: any)
	if
		typeof(object) ~= "table"
		or typeof(object.getStatus) ~= "function"
		or typeof(object.finally) ~= "function"
		or typeof(object.cancel) ~= "function"
	then
		error("did not receive a promise as an argument", 3)
	end
end

--[=[
	@class Trove
	A Trove is helpful for tracking any sort of object during
	runtime that needs to get cleaned up at some point.
]=]
local Trove = {}
Trove.__index = Trove

--[=[
	@return Trove
	Constructs a Trove object.

	```lua
	local trove = Trove.new()
	```
]=]
function Trove.new(): Trove
	local self = setmetatable({}, Trove)

	self._objects = {}
	self._cleaning = false

	return (self :: any) :: Trove
end

--[=[
	@method Add
	@within Trove
	@param object any -- Object to track
	@param cleanupMethod string? -- Optional cleanup name override
	@return object: any
	Adds an object to the trove. Once the trove is cleaned or
	destroyed, the object will also be cleaned up.

	The following types are accepted (e.g. `typeof(object)`):

	| Type | Cleanup |
	| ---- | ------- |
	| `Instance` | `object:Destroy()` |
	| `RBXScriptConnection` | `object:Disconnect()` |
	| `function` | `object()` |
	| `thread` | `task.cancel(object)` |
	| `table` | `object:Destroy()` _or_ `object:Disconnect()` _or_ `object:destroy()` _or_ `object:disconnect()` |
	| `table` with `cleanupMethod` | `object:<cleanupMethod>()` |

	Returns the object added.

	```lua
	-- Add a part to the trove, then destroy the trove,
	-- which will also destroy the part:
	local part = Instance.new("Part")
	trove:Add(part)
	trove:Destroy()

	-- Add a function to the trove:
	trove:Add(function()
		print("Cleanup!")
	end)
	trove:Destroy()

	-- Standard cleanup from table:
	local tbl = {}
	function tbl:Destroy()
		print("Cleanup")
	end
	trove:Add(tbl)

	-- Custom cleanup from table:
	local tbl = {}
	function tbl:DoSomething()
		print("Do something on cleanup")
	end
	trove:Add(tbl, "DoSomething")
	```
]=]
function Trove.Add(self: TroveInternal, object: Trackable, cleanupMethod: string?): any
	if self._cleaning then
		error("cannot call trove:Add() while cleaning", 2)
	end

	local cleanup = GetObjectCleanupFunction(object, cleanupMethod)
	table.insert(self._objects, { object, cleanup })

	return object
end

--[=[
	@method Clone
	@within Trove
	@return Instance
	Clones the given instance and adds it to the trove. Shorthand for
	`trove:Add(instance:Clone())`.

	```lua
	local clonedPart = trove:Clone(somePart)
	```
]=]
function Trove.Clone(self: TroveInternal, instance: Instance): Instance
	if self._cleaning then
		error("cannot call trove:Clone() while cleaning", 2)
	end

	return self:Add(instance:Clone())
end

--[=[
	@method Construct
	@within Trove
	@param class { new(Args...) -> T } | (Args...) -> T
	@param ... Args...
	@return T
	Constructs a new object from either the
	table or function given.

	If a table is given, the table's `new`
	function will be called with the given
	arguments.

	If a function is given, the function will
	be called with the given arguments.

	The result from either of the two options
	will be added to the trove.

	This is shorthand for `trove:Add(SomeClass.new(...))`
	and `trove:Add(SomeFunction(...))`.

	```lua
	local Signal = require(somewhere.Signal)

	-- All of these are identical:
	local s = trove:Construct(Signal)
	local s = trove:Construct(Signal.new)
	local s = trove:Construct(function() return Signal.new() end)
	local s = trove:Add(Signal.new())

	-- Even Roblox instances can be created:
	local part = trove:Construct(Instance, "Part")
	```
]=]
function Trove.Construct<T, A...>(self: TroveInternal, class: Constructable<T, A...>, ...: A...)
	if self._cleaning then
		error("Cannot call trove:Construct() while cleaning", 2)
	end

	local object = nil
	local t = type(class)
	if t == "table" then
		object = (class :: any).new(...)
	elseif t == "function" then
		object = (class :: any)(...)
	end

	return self:Add(object)
end

--[=[
	@method Connect
	@within Trove
	@param signal RBXScriptSignal
	@param fn (...: any) -> ()
	@return RBXScriptConnection
	Connects the function to the signal, adds the connection
	to the trove, and then returns the connection.

	This is shorthand for `trove:Add(signal:Connect(fn))`.

	```lua
	trove:Connect(workspace.ChildAdded, function(instance)
		print(instance.Name .. " added to workspace")
	end)
	```
]=]
function Trove.Connect(self: TroveInternal, signal: SignalLike, fn: (...any) -> ...any)
	if self._cleaning then
		error("Cannot call trove:Connect() while cleaning", 2)
	end

	return self:Add(signal:Connect(fn))
end

--[=[
	@method BindToRenderStep
	@within Trove
	@param name string
	@param priority number
	@param fn (dt: number) -> ()
	Calls `RunService:BindToRenderStep` and registers a function in the
	trove that will call `RunService:UnbindFromRenderStep` on cleanup.

	```lua
	trove:BindToRenderStep("Test", Enum.RenderPriority.Last.Value, function(dt)
		-- Do something
	end)
	```
]=]
function Trove.BindToRenderStep(self: TroveInternal, name: string, priority: number, fn: (dt: number) -> ())
	if self._cleaning then
		error("cannot call trove:BindToRenderStep() while cleaning", 2)
	end

	RunService:BindToRenderStep(name, priority, fn)

	self:Add(function()
		RunService:UnbindFromRenderStep(name)
	end)
end

--[=[
	@method AddPromise
	@within Trove
	@param promise Promise
	@return Promise
	Gives the promise to the trove, which will cancel the promise if the trove is cleaned up or if the promise
	is removed. The exact promise is returned, thus allowing chaining.

	```lua
	trove:AddPromise(doSomethingThatReturnsAPromise())
		:andThen(function()
			print("Done")
		end)
	-- Will cancel the above promise (assuming it didn't resolve immediately)
	trove:Clean()

	local p = trove:AddPromise(doSomethingThatReturnsAPromise())
	-- Will also cancel the promise
	trove:Remove(p)
	```

	:::caution Promise v4 Only
	This is only compatible with the [roblox-lua-promise](https://eryn.io/roblox-lua-promise/) library, version 4.
	:::
]=]
function Trove.AddPromise(self: TroveInternal, promise: PromiseLike)
	if self._cleaning then
		error("cannot call trove:AddPromise() while cleaning", 2)
	end
	AssertPromiseLike(promise)

	if promise:getStatus() == "Started" then
		promise:finally(function()
			if self._cleaning then
				return
			end
			self:_findAndRemoveFromObjects(promise, false)
		end)

		self:Add(promise, "cancel")
	end

	return promise
end

--[=[
	@method Remove
	@within Trove
	@param object any
	Removes the object from the Trove and cleans it up.

	```lua
	local part = Instance.new("Part")
	trove:Add(part)
	trove:Remove(part)
	```
]=]
function Trove.Remove(self: TroveInternal, object: Trackable): boolean
	if self._cleaning then
		error("cannot call trove:Remove() while cleaning", 2)
	end

	return self:_findAndRemoveFromObjects(object, true)
end

--[=[
	@method Extend
	@within Trove
	@return Trove
	Creates and adds another trove to itself. This is just shorthand
	for `trove:Construct(Trove)`. This is useful for contexts where
	the trove object is present, but the class itself isn't.

	:::note
	This does _not_ clone the trove. In other words, the objects in the
	trove are not given to the new constructed trove. This is simply to
	construct a new Trove and add it as an object to track.
	:::

	```lua
	local trove = Trove.new()
	local subTrove = trove:Extend()

	trove:Clean() -- Cleans up the subTrove too
	```
]=]
function Trove.Extend(self: TroveInternal)
	if self._cleaning then
		error("cannot call trove:Extend() while cleaning", 2)
	end

	return self:Construct(Trove)
end

--[=[
	@method Clean
	@within Trove
	Cleans up all objects in the trove. This is
	similar to calling `Remove` on each object
	within the trove. The ordering of the objects
	removed is _not_ guaranteed.

	```lua
	trove:Clean()
	```
]=]
function Trove.Clean(self: TroveInternal)
	if self._cleaning then
		return
	end

	self._cleaning = true

	for _, obj in self._objects do
		self:_cleanupObject(obj[1], obj[2])
	end

	table.clear(self._objects)
	self._cleaning = false
end

--[=[
	@method WrapClean
	@within Trove
	Returns a function that wraps the trove's `Clean()`
	method. Calling the returned function will clean up
	the trove.

	This is often useful in contexts where functions
	are the primary mode for cleaning up an environment,
	such as in many "observer" patterns.

	```lua
	local cleanup = trove:WrapClean()

	-- Sometime later...
	cleanup()
	```

	```lua
	-- Common observer pattern example:
	someObserver(function()
		local trove = Trove.new()
		-- Foo
		return trove:WrapClean()
	end)
	```
]=]
function Trove.WrapClean(self: TroveInternal)
	return function()
		self:Clean()
	end
end

function Trove._findAndRemoveFromObjects(self: TroveInternal, object: any, cleanup: boolean): boolean
	local objects = self._objects

	for i, obj in objects do
		if obj[1] == object then
			local n = #objects
			objects[i] = objects[n]
			objects[n] = nil

			if cleanup then
				self:_cleanupObject(obj[1], obj[2])
			end

			return true
		end
	end

	return false
end

function Trove._cleanupObject(_self: TroveInternal, object: any, cleanupMethod: string?)
	if cleanupMethod == FN_MARKER then
		object()
	elseif cleanupMethod == THREAD_MARKER then
		pcall(task.cancel, object)
	else
		object[cleanupMethod](object)
	end
end

--[=[
	@method AttachToInstance
	@within Trove
	@param instance Instance
	@return RBXScriptConnection
	Attaches the trove to a Roblox instance. Once this
	instance is removed from the game (parent or ancestor's
	parent set to `nil`), the trove will automatically
	clean up.

	This inverses the ownership of the Trove object, and should
	only be used when necessary. In other words, the attached
	instance dictates when the trove is cleaned up, rather than
	the trove dictating the cleanup of the instance.

	:::caution
	Will throw an error if `instance` is not a descendant
	of the game hierarchy.
	:::

	```lua
	trove:AttachToInstance(somePart)
	trove:Add(function()
		print("Cleaned")
	end)

	-- Destroying the part will cause the trove to clean up, thus "Cleaned" printed:
	somePart:Destroy()
	```
]=]
function Trove.AttachToInstance(self: TroveInternal, instance: Instance)
	if self._cleaning then
		error("cannot call trove:AttachToInstance() while cleaning", 2)
	elseif not instance:IsDescendantOf(game) then
		error("instance is not a descendant of the game hierarchy", 2)
	end

	return self:Connect(instance.Destroying, function()
		self:Destroy()
	end)
end

--[=[
	@method Destroy
	@within Trove
	Alias for `trove:Clean()`.

	```lua
	trove:Destroy()
	```
]=]
function Trove.Destroy(self: TroveInternal)
	self:Clean()
end

return require("trove") :: {
	new: typeof(Trove.new),
}


return require("tween") :: {
	GetValue: (
		self: Tween,
		alpha: number,
		easingStyle: Enum.EasingStyle,
		easingDirection: Enum.EasingDirection
	) -> number,
}


-- Try and get the ar controller, throw an error if we don't find it using `assert`.
local screen = assert(Network:GetPart("ARController"), "no ar controller connected")

-- Clear the left over screen elements from the last time GUIs were loaded onto it.
-- Note: If you utilise the `Instance.new` API instead, you do not need to do this,
--       as objects created through said API are *automatically* deleted when the
--       microcontroller stops running.
screen:ClearElements()

-- We're going to spawn 32 randomly sized/coloured/positioned cubes.
for index = 1, 32 do
	-- Generate a random `Vector3` in a -16 to 16 cube region.
	local position = Vector3.new(math.random(-16, 16), math.random(-16, 16), math.random(-16, 16))

	-- This may be confusing if you've never worked with `CFrame`s, 'why is pi here?'
	-- the reason is that pi is 180 degrees when working in 'radians' (the angle system
	-- used by `CFrame`s). `math.random` with *no* parameters returns a random decimal
	-- number between 0 and 1, so each parameter is just a number between 0 and pi.
	local orientation = CFrame.Angles(math.random() * math.pi, math.random() * math.pi, math.random() * math.pi)

	-- Select a size between 0 and 4 for all axes, `Vector3.one` is shorthand for
	-- `Vector3.new(1, 1, 1)`, we multiply it by a number between 1 and 4 and this
	-- gives us a vector with a side length of 1 to 4 on all axes.
	local size = Vector3.one * (1 + math.random() * 3)

	-- Pick a random colour with 3 `math.random` calls
	local color = Color3.new(math.random(), math.random(), math.random())

	-- Call the `CreateElement` method with the `ClassName` of the object you want to create,
	-- and a dictionary of the properties to apply (you cannot specify the `Parent` property).
	-- Note the `"3D"` context parameter, this says we want to use the instance viewport, you
	-- could alternatively use `CreateElement3D` to avoid this third parameter.
	local object = screen:CreateElement("Part", {
		CFrame = orientation + position + Microcontroller.Position,
		Size = size,
		Color = color,
		TopSurface = Enum.SurfaceType.Smooth, -- The `Top` and `Bottom` surfaces
		BottomSurface = Enum.SurfaceType.Smooth, -- have a stud pattern by default.
	}, "3D")
end

-- Keep the microcontroller on by permanently 'yielding' the code (making it wait).
coroutine.yield()


local function tabulateConfigurables(object: PilotObject, configuration: { [string]: any })
	local changes = {}

	for _, configurableData in object:GetConfigurables() do
		local configurableName = configurableData.Name
		local configurableValue = object[configurableName]

		if configuration[configurableName] ~= configurableValue then
			table.insert(changes, configurableName)
			configuration[configurableName] = configurableValue
		end
	end

	return changes
end

local object = Network:GetPart()

local previousConfiguration = {}

-- initialise with the current configuration, but discard
-- what 'changes' occurred (they are not actually changes)
tabulateConfigurables(object, previousConfiguration)

object.Configured:Connect(function()
	local currentConfiguration = table.clone(previousConfiguration)
	local changes = tabulateConfigurables(object, currentConfiguration)

	for _, change in changes do
		local previous, current = previousConfiguration[change], currentConfiguration[change]
		print(`{object}'s '{change}' configurable was changed from {previous} to {current}!`)
	end

	previousConfiguration = currentConfiguration
end)


local function tabulateConfigurables(object: PilotObject, configuration: { [string]: any })
	local changes = {}

	for _, configurableData in object:GetConfigurables() do
		local configurableName = configurableData.Name
		local configurableValue = object[configurableName]

		if configuration[configurableName] ~= configurableValue then
			table.insert(changes, configurableName)
			configuration[configurableName] = configurableValue
		end
	end

	return changes
end

local object = Network:GetPart()

local configuration = {}

-- initialise with the current configuration, but discard
-- what 'changes' occurred (they are not actually changes)
tabulateConfigurables(object, configuration)

object.Configured:Connect(function()
	local changes = tabulateConfigurables(object, configuration)

	for _, change in changes do
		local value = configuration[change]
		print(`{object}'s '{change}' configurable was changed to {value}!`)
	end
end)


local signal = require("signal")

local function getInstanceConfigurableChangedSignal(object: PilotObject, configurable: string)
	local signal = signal.new()

	local previousConfiguration = object[configurable]

	object.Configured:Connect(function()
		local currentConfiguration = object[configurable]
		if currentConfiguration == previousConfiguration then
			return
		end

		signal:Fire(currentConfiguration, previousConfiguration)

		previousConfiguration = currentConfiguration
	end)

	return signal
end

local gyro = assert(Network:GetPart("Gyro"), "no gyro connected!")
getInstanceConfigurableChangedSignal(gyro, "Seek"):Connect(function(currentConfiguration, previousConfiguration)
	print(`{object}'s 'Seek' configurable was changed from {previousConfiguration} to {currentConfiguration}!`)
end)


local TRACKED_CONFIGURABLE = "Seek"

local object = Network:GetPart("Gyro")
local previousConfiguration = object[TRACKED_CONFIGURABLE]

object.Configured:Connect(function()
	local currentConfiguration = object[TRACKED_CONFIGURABLE]
	if currentConfiguration == previousConfiguration then
		return
	end

	print(
		`{object}'s '{TRACKED_CONFIGURABLE}' configurable was changed from {previousConfiguration} to {currentConfiguration}!`
	)

	previousConfiguration = currentConfiguration
end)


local sift = require("sift") -- Grab this fancy library for manipulating tables provided by wos.

-- Get all of the types of objects with a `:SetColor` method and merge them into one big table.
local objects = sift.Array.merge(
	Network:GetParts("Light"),
	Network:GetParts("LightTube"),
	Network:GetParts("SpotLight"),
	Network:GetParts("EnergyShield")
)

-- We want this to run infinitely, but we want to run it every frame, rather than every game
-- tick, so we won't use the `Microcontroller.Loop` event.
while true do
	-- First we want to get the current time, the function `os.clock` gives us a very precise
	-- measurement of the current time (if you want to know, it basically uses the internal
	-- clock of the computer running the code).
	local currentTime = os.clock()

	-- We then use the 'modulo' operator, what this does is it gives you the 'remainder'
	-- from the division. So if you do something like `5.25 % 1` you get out the `0.25`,
	-- or if you do, say, `31 % 2`, since 31 isn't divisble by 2 you get a remainder of `1`.

	-- We do this so we just get the fractional aspect of the time (which would be the
	-- milliseconds), giving us a number between 0 and 1.
	local hue = currentTime % 1

	-- We then construct a `Color3` passing in the value we just calculated, which, since
	-- we used the modulo operator is always going to be between 0 and 1, the exact range
	-- needed for the 'hue' of the `fromHSV`. We just set the 'saturation' and 'brightness'
	-- to 1 to keep the colour nice and bright.

	-- A helpful feature of the HSV colour space is that a hue of both `0` and `1` are red!
	local color = Color3.fromHSV(hue, 1, 1)

	-- Now we have our colour, we just want to apply it to all of our objects, so we'll use
	-- a simple for loop. The 'index' or 'key' value is named '_' since we don't need it,
	-- and naming it '_' is a good way to say it's an unused variable.
	for _, object in objects do
		-- Simply call the `SetColor` method (since it's a method, we use a `:` when
		-- calling the function, rather than a `.`) passing in the `Color3`!
		object:SetColor(color)
	end

	-- Wait a little bit before updating the colour again!
	task.wait()
end


-- Get the touch screen.
local screen = assert(Network:GetPart("TouchScreen"), "expected touch screen to be connected")

-- Initialise our lists of draggable frames and the currently dragged frames.
local frames = {}
local dragging = {}

-- Create 100 random draggable frames.
local canvasSize = screen:GetDimensions()
for index = 1, 100 do
	-- Random colour, size and position (position will avoid going off-screen).
	local colour = Color3.new(math.random(), math.random(), math.random())
	local sizeX, sizeY = math.random(20, 100), math.random(20, 100)
	local positionX, positionY = math.random(0, canvasSize.X - sizeX), math.random(0, canvasSize.Y - sizeY)

	-- Create the frame.
	local frame = Instance.new("Frame")
	frame.BorderSizePixel = 0
	frame.BackgroundColor3 = colour
	frame.Size = UDim2.fromOffset(sizeX, sizeY)
	frame.Position = UDim2.fromOffset(positionX, positionY)
	frame.ZIndex = index
	frame.Parent = screen:GetCanvas()

	-- Register it as draggable.
	frames[frame] = true
end

-- This is not optimised, you can replace it with a more optimised version.
-- You can do lots of things, like maintaining the highest frames at the front
-- of your frames list, or grouping frames based on their positions & sizes
-- as to reduce the amount of frames to check for any given position.
local function findFrameAtPosition(position: Vector2): (Frame?, number)
	local candidate = nil
	local maxZIndex = 0

	-- For each of the frames.
	for frame in frames do
		-- If this is a new biggest ZIndex, register it. Otherwise, if it
		-- is lower than the current selection candidate, ignore it.
		if frame.ZIndex > maxZIndex then
			maxZIndex = frame.ZIndex
		elseif candidate and frame.ZIndex <= candidate.ZIndex then
			continue
		end

		-- Compute the position relative to the frame, so (0, 0) is top
		-- left and (1, 1) is bottom right.
		local relativePosition = (position - frame.AbsolutePosition) / frame.AbsoluteSize

		-- If it's within the bounds, we're hovered over it, this is our
		-- new candidate.
		if
			relativePosition.X >= 0
			and relativePosition.Y >= 0
			and relativePosition.X <= 1
			and relativePosition.Y <= 1
		then
			candidate = frame
		end
	end

	return candidate, maxZIndex
end

screen.CursorPressed:Connect(function(cursor)
	-- Find the frame the user is hovered over.
	local cursorPosition = Vector2.new(cursor.X, cursor.Y)
	local selectedFrame, maxZIndex = findFrameAtPosition(cursorPosition)

	if not selectedFrame then
		return
	end

	-- Move the selected frame to the top.
	selectedFrame.ZIndex = maxZIndex + 1

	-- Get the cursor's position relative to the top left corner of the frame.
	local positionalOffset = selectedFrame.AbsolutePosition - cursorPosition

	-- Register the fact this cursor is dragging this frame.
	dragging[cursor.Player] = { frame = selectedFrame, offset = positionalOffset }
end)

screen.CursorMoved:Connect(function(cursor)
	-- If they're not dragging anything, cancel.
	local selected = dragging[cursor.Player]
	if not selected then
		return
	end

	-- If they've deselected it, say as much then cancel.
	if not cursor.Pressed then
		dragging[cursor.Player] = nil
		return
	end

	-- Extract the frame, the frame's parent, and the cursor offset.
	local selectedFrame, offset = selected.frame, selected.offset
	local parent = selectedFrame.Parent

	-- If there's a parent, we have to account for the possibility of the parent
	-- not being in the top left corner. We just add the cursor offset to the
	-- current cursor position.
	local targetPosition = if parent
		then UDim2.fromOffset(
			cursor.X + offset.X - parent.AbsolutePosition.X,
			cursor.Y + offset.Y - parent.AbsolutePosition.Y
		)
		else UDim2.fromOffset(cursor.X + offset.X, cursor.Y + offset.Y)

	-- Just update the position.
	selectedFrame.Position = targetPosition
end)


-- define names of people who are allowed.
local WHITELIST = {
	ArvidSilverlock = true,
	-- could also do `["ArvidSilverlock"] = true,`
}

local sift = require("sift") -- fancy module provided by wos to modify tables

-- try and find the connected keyboard, throw an error if we don't find one
local keyboard = assert(Network:GetPart("Keyboard"), "no keyboard connected!")

-- make one big table with all the `Extractor`s and `MiningLaser`s
local connectedObjects = sift.Array.concat(Network:GetParts("Extractor"), Network:GetParts("MiningLaser"))

keyboard.TextInputted:Connect(function(rawText, username)
	-- don't run the code if the person who inputted text into the keyboard isn't whitelisted
	if not WHITELIST[username] then
		return
	end

	-- remove the trailing line break the game leaves
	local strippedText = rawText:gsub("\n$", "")

	-- iterate over every object that is connected and assign the `MaterialToExtract` configurable to the keyboard input
	for _, object in connectedObjects do
		object.MaterialToExtract = strippedText
	end
end)


-- Define the list of resources to cycle through
local MOUNTAIN_RESOURCES = {
	"Stone",
	"Ruby",
	"Quartz",
	"Silicon",
	"Flint",
	"Sulfur",
	-- Remove the '--' if you also want to harvest these!
	-- "Ice",
	-- "Oil",
	-- "Helium",
}

-- how often we want to change what resource is being mined (in seconds)
local UPDATE_RATE = 1

local sift = require("sift") -- fancy module provided by wos to modify tables

-- make one big table with all the `Extractor`s and `MiningLaser`s
local connectedObjects = sift.Array.concat(Network:GetParts("Extractor"), Network:GetParts("MiningLaser"))

-- variable to keep track of which material is being mined
local resourceIndex = 1

-- variable to keep track of how long it's been since we last, start it off high so we immediately update
local timeSinceLastUpdate = math.huge

-- every game tick we want to change what resource is being mined
Microcontroller.Loop:Connect(function(deltaTime)
	-- increment how long it's been since we checked
	timeSinceLastUpdate += deltaTime

	-- if it's not been as long as we want since we last updated, exit out early
	if timeSinceLastUpdate < UPDATE_RATE then
		return
	end
	deltaTime %= UPDATE_RATE -- loop back round (i.e., 1.06 goes to 0.06 when UPDATE_RATE is 1)

	-- increment what resource we're currently meant to be mining
	resourceIndex += 1
	if resourceIndex > #MOUNTAIN_RESOURCES then
		resourceIndex = 1 -- loop back round when we reach the end of the list
	end

	-- get the name of the resource we want to mine
	local resource = MOUNTAIN_RESOURCES[resourceIndex]

	-- iterate over every object that is connected and assign the `MaterialToExtract` configurable
	for _, object in connectedObjects do
		object.MaterialToExtract = resource
	end
end)


-- Given a list of gyros, you can estimate where they are all pointing.
-- You don't have to understand it, you just have to use it!
-- https://medium.com/data-science/intersect-multiple-3d-lines-closest-point-93e06b296f15
-- https://math.stackexchange.com/questions/61719
-- https://en.wikipedia.org/wiki/Skew_lines#More_than_two_lines
-- https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Nearest_points_to_skew_lines
local function estimateGyroTarget(gyros: { Gyro }): Vector3?
	local A = {
		{ 0, 0, 0 },
		{ 0, 0, 0 },
		{ 0, 0, 0 },
	}
	local b = { 0, 0, 0 }

	for _, gyro in gyros do
		local p = gyro.Position
		local u = gyro.CFrame.UpVector

		local ux, uy, uz = u.X, u.Y, u.Z
		local px, py, pz = p.X, p.Y, p.Z

		local M = {
			{ 1 - ux * ux, -ux * uy, -ux * uz },
			{ -ux * uy, 1 - uy * uy, -uy * uz },
			{ -ux * uz, -uy * uz, 1 - uz * uz },
		}

		for r = 1, 3 do
			for c = 1, 3 do
				A[r][c] += M[r][c]
			end
		end

		b[1] += M[1][1] * px + M[1][2] * py + M[1][3] * pz
		b[2] += M[2][1] * px + M[2][2] * py + M[2][3] * pz
		b[3] += M[3][1] * px + M[3][2] * py + M[3][3] * pz
	end

	for k = 1, 3 do
		local maxVal, piv = math.abs(A[k][k]), k
		for r = k + 1, 3 do
			local v = math.abs(A[r][k])
			if v > maxVal then
				maxVal, piv = v, r
			end
		end

		if maxVal < 1e-12 then
			return nil
		end

		if piv ~= k then
			A[k], A[piv] = A[piv], A[k]
			b[k], b[piv] = b[piv], b[k]
		end

		for i = k + 1, 3 do
			local f = A[i][k] / A[k][k]
			for j = k, 3 do
				A[i][j] -= f * A[k][j]
			end
			b[i] -= f * b[k]
		end
	end

	local x = { 0, 0, 0 }
	for i = 3, 1, -1 do
		local s = b[i]
		for j = i + 1, 3 do
			s -= A[i][j] * x[j]
		end
		x[i] = s / A[i][i]
	end

	return Vector3.new(x[1], x[2], x[3])
end

-- Get the AR controller and the gyros (make sure they are connected with assert)
local arController = assert(Network:GetPart("ARController"), "expected ar controller to be connected")
local gyros = Network:GetPartsFromPort(1, "Gyro")
assert(#gyros > 1, "there must be more than 1 gyro connected")

-- Configure all the gyros to seek an alien core
for _, gyro in gyros do
	gyro.Seek = "Radar AlienCore"
end

-- Create an indicator part on the AR controller
local point = Instance.new("Part")
point.Shape = Enum.PartType.Ball
point.Size = Vector3.one * 3
point.Transparency = 0.5
point.Material = Enum.Material.Neon
point.BrickColor = BrickColor.new("Bright green")
point.Parent = arController:GetCanvas("3D")

-- Every tick, estimate the target and update the indicator part, if it
-- fails to estimate the target, make the indicator red
Microcontroller.Loop:Connect(function()
	local intersection = estimateGyroTarget(gyros)
	if intersection then
		point.Position = intersection
		point.BrickColor = BrickColor.new("Bright green")
	else
		point.BrickColor = BrickColor.new("Bright red")
	end
end)


-- Get all of the necessary parts and throw a useful error if something isn't found.
local anchor = assert(Network:GetPart("Anchor"), "no anchor connected")
local thrusterSwitch = assert(Network:GetPart("Switch"), "no switch connected")
local keyboard = assert(Network:GetPart("Keyboard"), "no keyboard connected")

-- Define the `UserId`s of people who are allowed.
local WHITELIST = {
	[1178125707] = true,
}

-- Create a dictionary of what key to press to what code to run
local BINDS = {
	-- Toggle the anchor state when `R` is pressed.
	[Enum.KeyCode.R] = function()
		anchor.Anchored = not anchor.Anchored
	end,
	-- Toggle the switch state when `X` is pressed.
	[Enum.KeyCode.X] = function()
		thrusterSwitch.SwitchValue = not thrusterSwitch.SwitchValue
	end,
}

-- Connect to the `keyboard.KeyPressed` event, we don't need the `keyName` variable,
-- so we'll call it `_` as to say "we're not going to use this" to anyone reading the code.
keyboard.KeyPressed:Connect(function(key, _, userId)
	-- If the user *isn't* in the whitelist, *cancel* this function using `return`.
	if not WHITELIST[userId] then
		return
	end

	-- Try and find the piece of code to run for the key that was pressed, if we don't find
	-- it, cancel, like it's done when the user isn't in the whitelist.
	local callback = BINDS[key]
	if not callback then
		return
	end

	-- Run the piece of code!
	callback()
end)